<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
 <head>
  <title>Planet Debian</title>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta name="generator" content="http://intertwingly.net/code/venus/" />
  <link rel="stylesheet" href="common/planet.css" type="text/css" />
  <link rel="icon" type="image/x-icon" href="common/favicon.ico" />
  <link href="rss10.xml" rel="alternate" type="application/rss+xml"  title="RSS 1.0 Feed" />
  <link href="rss20.xml" rel="alternate" type="application/rss+xml"  title="RSS 2.0 Feed" />
  <link href="atom.xml"  rel="alternate" type="application/atom+xml" title="Atom Feed" />

  <script type="text/javascript" src="common/hide.js"></script>
  <script type="text/javascript">
  <!--
	 window.onload=hideHosts;
	-->
  </script>
 </head>

 <body>
  <div id="header">
   <div id="upperheader">
    <div id="logo">
		<a href="https://www.debian.org/" title="Debian Home"><img src="common/Pics/openlogo-50.png" alt="Debian" width="50" height="61" /></a> 
    </div><!-- end logo -->
    <p class="section"><a href="/">planet</a></p>
   </div><!-- end upperheader -->
   <div id="navbar">
    <p class="hidecss"><a href="#inner">Skip Quicknav</a></p>
    <ul>
     <li><a href="https://www.debian.org/intro/about">About Debian</a></li>
     <li><a href="https://www.debian.org/distrib/">Getting Debian</a></li>
     <li><a href="https://www.debian.org/support">Support</a></li>
     <li><a href="https://www.debian.org/devel/">Developers'&nbsp;Corner</a></li>
    </ul>
   </div><!-- end navbar -->
  <p id="breadcrumbs"><a href="https://planet.debian.org/">Planet Debian</a></p>
  </div><!-- end header -->
   <div id="content">

    
     
    <h1>September 15, 2026</h1>
     

    <div class="channel">
     
      
     <a href="https://notes.pault.ag/">
      <img class="face" src="heads/paultag.png"
            width="65" height="85"
            alt="hackergotchi for Paul Tagliamonte" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://notes.pault.ag/" title="Paul's Notes">Paul Tagliamonte</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://notes.pault.ag/desfire-ev3/_hide\" onClick=\"exclude( 'https://notes.pault.ag/desfire-ev3/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://notes.pault.ag/desfire-ev3/_show\"  style=\"display:none;\" onClick=\"show( 'https://notes.pault.ag/desfire-ev3/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://notes.pault.ag/desfire-ev3/" class="https://notes.pault.ag/desfire-ev3/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://notes.pault.ag/desfire-ev3/">DESFire EV3</a>
       </h3>
       <div class="content">
        <p>I’ve <em>long</em> been interested in hardware key material storage devices. I’ve been
a fan of <a href="https://github.com/go-piv/go-ykpiv">yubikeys</a> (I still remember
when my fancy new NEO-N showed up), <a href="https://github.com/paultag/go-piv">PIV</a>
(and its associated
<a href="https://github.com/paultag/go-othername">smattering</a> of
<a href="https://github.com/paultag/go-fasc">additional</a>
<a href="https://github.com/paultag/go-cbeff">fields</a>),
<a href="https://github.com/hallowauth/hallow">SaaS HSMs</a>,
the <a href="https://github.com/paultag/go-keyring">kernel keyring</a>, some tooling
I’ve fairly satisfied with the design of at prior companies, and of course,
our dear friend, the TPM. All that is not even to mention the scores of exotic
hardware security modules one generally comes across from time to time when
you’re keeping a sharp eye out that you wind up playing with.</p>
<aside class="right">
I have not used any LLMs in the course of this adventure. Not for
writing these posts, and not for this code. The intent here was to learn
more about how DESFire works. LLMs defeat that purpose.
</aside>
<p>The concept of storing private key material on a disk, or even having it in
RAM has always skeeved me out, so I have a natural inclination to hardware
modules, and how shifting keying material around can change your risks
and threat model(s) in interesting ways.</p>
<p>I don’t remember when I first came across the
<a href="https://www.nxp.com/products/MF3DHx3">MIFARE DESFire EV3</a>, but a few weeks ago
I did a deep-dive into the state of the art of authentication schemes using
ID cards. My complete overview of what tradeoffs exist is pretty extensive
(and likely not interesting to the vast majority of the world), but the tl;dr
wound up being one of “use PIV” or “use MIFARE DESFire EV3”. I wound up
picking DESFire for a recent project, and figured it’s worth talking
a bit about what I learned, share some thoughts, and some code. That code is
published on <a href="https://crates.io/crates/desox">crates.io/desox</a>, and docs,
as is our custom, may be found at
<a href="https://docs.rs/desox/latest/desox/">docs.rs/desox</a></p>
<aside class="left">
PIV, while oft-maligned, is exceptional for public key cryptography using
asymmetric keys, and can safely interoperate with x.509. If any of those things
are a hard must, I don't think that's going anywhere.
</aside>
<p>DESFire supports DES (I’m sure most readers saw that one coming), 3DES (I
didn’t bother playing with 3DES at all) or AES-128 (AFAICT always use this?)
keying material. It’s worth noting that the DESFire only supports symmetric
keys and is not designed for public key cryptography, and operates exclusively
using shared symmetric key material. The DESFire EV series use those keys and
related authentication schemes to interact with “files” stored on the on-chip
EEPROM (2k, 4k, 8k, and 16k versions exist), or “applications” (groups of files
and authentication keys).</p>
<h1 id="talking-to-a-desfire-ev3">Talking to a DESFire EV3</h1>
<p>Interactions with the card are done over NFC (ISO/IEC 14443 Type A), and
commands to/from the card may be in the usual ISO/IEC 7816-4 APDU format, or
“unencapsulated” bytes sent to/from the card are sent using a fixed instruction
set and return code structure – saving a few bytes per message. I’ve opted to
use their undocumented and proprietary format – I found it easier to work
with and with a maximum message of 60 bytes, the savings matter a lot.</p>
<aside class="right">
I keep calling the DESFire messages I implemented "APDU messages" since I have
to use a bunch of API surface saying it is -- but they're not.
</aside>
<p>While powered via NFC, the card maintains a small amount of state about the
connection between the reader and the card in its RAM, including if the
session is authenticated or unauthenticated. I’ll dig into how authentication
happens later, but it’s worth knowing that sessions <em>can</em> become authenticated
using one of the symmetric keys shared by the card and the reader. The vast
majority of the
<a href="https://docs.rs/desox/0.5.1/desox/enum.Instruction.html">DESFire commands</a>
I know about tend to work while either authenticated or unauthenticated,
with a few exceptions (<code>GetUid</code>, <code>ChangeKey</code>, and <code>ChangeKeySettings</code>
for example).</p>
<p>In general, I found working with this card particularly pleasant. There is a
fair amount of backwards-compatible behavior and multiple methods of
communication that confuse things a bit, but overall, it was better than
average to integrate with. Kudos to the NXP team. If the docs on this chip
were public, things would be orders of magnitude easier – it’s not entirely
clear to my why they’re keeping so much of the interface documentation under
NDA, but it’s the largest knock against the chip, by far.</p>
<h2 id="authentication">Authentication</h2>
<p>I found a lot of really great resources outlining how the handshake and
protocol works for a DESFire EV3, especially from
<a href="https://ridrix.wordpress.com/2009/09/19/mifare-desfire-communication-example/">Ridrix</a>,
some <a href="https://www.cardlogix.com/wp-content/uploads/MIFARE-Application-Programming-Guide-for-DESFfire_rev.e.pdf">public datasheets</a>
<a href="https://www.scribd.com/document/683260738/DESFire">ThrRealRevK</a>
and posts from <a href="https://medium.com/@androidcrypto/mifare-desfire-ev3-a-beginner-tutorial-android-java-using-the-desfire-for-android-tools-00aaecb8fa93">AndroidCrypto</a>.</p>
<aside class="left">
It's not super clear to me why all of this is under such heavy NDA, surely
a robust ecosystem is nothing but good?
</aside>
<p>The gist here is that, because the DESFire only does symmetric key operations,
the key exchange (a type of SKA – Symmetric Key Agreement) uses symmetric keys
to establish a unique session key which is used to sign or encrypt data
exchanged between the reader and the card. I’m not going to get too in-depth
here, since there’s a ton of other resources out there to dig into – but I
will do a quick high-level description to keep this post mostly self-contained.</p>
<p>The authentication protocol serves two main functions – to verify that both
parties know the same shared secret, as well as to act as a SKA to construct a
new session shared secret key. Here’s a quick overview of how a shared session
key is derived between the reader and the card using our symmetric keys
(<code>AES-128</code> in the case below).</p>
<ol>
<li>the reader requests to start authentication with the card (something like
<code>AA 00</code> to start an AES Authentication handshake with keyslot <code>0x00</code>).</li>
<li>The card will then reply with <code>AF</code> (a status code that indicates more data
is to follow), followed by 16 bytes (in the case of <code>AES-128</code>) of encrypted
(using CBC) data.</li>
<li>The hosts then decrypts this block with the symmetric key from keyslot 0,
returning the card’s session nonce.</li>
<li>The host generates 16 bytes (usually random) for its session
nonce.</li>
<li>The host sends an instruction of <code>AF</code> (indicating a continuation of
the previous command), followed by 32 bytes of encrypted data. When decrypted,
the first 16 bytes are our nonce generated in step #4, followed by the 16 bytes
provided by the card, decrypted in step #3, except where every byte is shifted
to the left by one place (the 0th byte is copied to the end).</li>
<li>The card will reply with <code>00</code> indicating a successful operation, followed
by 16 bytes, which when decrypted, is our session nonce from step #4, shifted
to the left by one byte in the same way that we did in step #5 with the card’s
nonce.</li>
<li>At this point, both the reader and card have confirmed the other party has
the same symmetric secret key. The session is now “authenticated” and a
“session key” is derived using the two nonce blocks. Two hashing keys (K1
and K2) are derived from this key, which is used to maintain an ongoing
<a href="https://csrc.nist.gov/pubs/sp/800/38/b/upd1/final">CMAC</a> hash of the messages
coming and going to/from the card.</li>
</ol>
<p>From here on out, the session is “authenticated”, and responses from the card
which were previously “plain” will now contain a 8-byte CMAC signature,
which can be used to ensure that the replies in question come from the
active session.</p>
<p>In my <a href="https://github.com/paultag/desox-rs/tree/v0.5.4/src/handshake">implementation of the handshake</a>
I opted to encode the handshake state into rust types, just so I wouldn’t make
any mistakes. The <code>Handshake</code> type contains the session internals (session
nonce values, keying state, to include IV, etc). This means the authentication
flow (from within my code) uses the <code>Handshake</code> struct to generate the commands
to send to the card in order:</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span><span style="color: #e6db74;">/// Create a new `Handshake`, and return the
</span></span></span><span style="display: flex;"><span><span style="color: #e6db74;">/// start auth command (something like `AA 00`)
</span></span></span><span style="display: flex;"><span><span style="color: #66d9ef;">fn</span> <span style="color: #a6e22e;">Handshake</span>::<span style="color: #f92672;">&lt;</span>Initial<span style="color: #f92672;">&gt;</span>::begin(
</span></span><span style="display: flex;"><span> output: <span style="color: #66d9ef;">&amp;</span><span style="color: #a6e22e;">mut</span> [<span style="color: #66d9ef;">u8</span>],
</span></span><span style="display: flex;"><span> key: [<span style="color: #66d9ef;">u8</span>; <span style="color: #ae81ff;">16</span>],
</span></span><span style="display: flex;"><span> key_id: <span style="color: #66d9ef;">u8</span>,
</span></span><span style="display: flex;"><span>) -&gt; (Self, <span style="color: #f92672;">&amp;</span>[<span style="color: #66d9ef;">u8</span>]);
</span></span></code></pre></div><p>After we get a reply back from the card (the encrypted version of the card’s
session nonce, sometimes called <code>Rnd_B</code> in code I’ve seen), we transition
states from <code>Initial</code> into <code>HalfOpen</code>.</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span><span style="color: #e6db74;">/// Given the card's encrypted response, generate
</span></span></span><span style="display: flex;"><span><span style="color: #e6db74;">/// our session nonce and generate a reply
</span></span></span><span style="display: flex;"><span><span style="color: #e6db74;">/// (something that starts with `AF` followed by
</span></span></span><span style="display: flex;"><span><span style="color: #e6db74;">/// 32 bytes of encrypted data).
</span></span></span><span style="display: flex;"><span><span style="color: #66d9ef;">fn</span> <span style="color: #a6e22e;">Handshake</span>::<span style="color: #f92672;">&lt;</span>Initial<span style="color: #f92672;">&gt;</span>::rnd_b(
</span></span><span style="display: flex;"><span> self,
</span></span><span style="display: flex;"><span> output: <span style="color: #66d9ef;">&amp;</span><span style="color: #a6e22e;">mut</span> [<span style="color: #66d9ef;">u8</span>],
</span></span><span style="display: flex;"><span> input: <span style="color: #66d9ef;">&amp;</span>[<span style="color: #66d9ef;">u8</span>]
</span></span><span style="display: flex;"><span>) -&gt; (Handshake::<span style="color: #f92672;">&lt;</span>HalfOpen<span style="color: #f92672;">&gt;</span>, <span style="color: #f92672;">&amp;</span>[<span style="color: #66d9ef;">u8</span>]);
</span></span></code></pre></div><p>Now that we’re “<code>HalfOpen</code>”, we’re waiting to hear back from the card to ensure
that it, too, can byte-shift our provided nonce. Once we have the card’s reply,
we can check it using our <code>complete</code> helper, transitioning from <code>HalfOpen</code> to
<code>Successful</code>.</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span><span style="color: #e6db74;">/// Check to ensure that the card replied with
</span></span></span><span style="display: flex;"><span><span style="color: #e6db74;">/// our nonce byte-shifted by one place, indicating
</span></span></span><span style="display: flex;"><span><span style="color: #e6db74;">/// that they know the symmetric secret in
</span></span></span><span style="display: flex;"><span><span style="color: #e6db74;">/// this key slot.
</span></span></span><span style="display: flex;"><span><span style="color: #66d9ef;">fn</span> <span style="color: #a6e22e;">Handshake</span>::<span style="color: #f92672;">&lt;</span>HalfOpen<span style="color: #f92672;">&gt;</span>::complete(
</span></span><span style="display: flex;"><span> self,
</span></span><span style="display: flex;"><span> input: <span style="color: #66d9ef;">&amp;</span>[<span style="color: #66d9ef;">u8</span>]
</span></span><span style="display: flex;"><span>) -&gt; <span style="color: #a6e22e;">Handshake</span>::<span style="color: #f92672;">&lt;</span>Successful<span style="color: #f92672;">&gt;</span>;
</span></span></code></pre></div><p>Once the <code>Handshake</code> is successful, the only thing left to do is consume
the <code>Handshake</code> struct and turn it into the shared session key by running
it through the <a href="https://github.com/paultag/desox-rs/blob/v0.5.4/src/crypto/kdf.rs">key derivation function</a>.</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span><span style="color: #e6db74;">/// Consume the `Handshake` struct and return the
</span></span></span><span style="display: flex;"><span><span style="color: #e6db74;">/// new shared session secret key.
</span></span></span><span style="display: flex;"><span><span style="color: #66d9ef;">fn</span> <span style="color: #a6e22e;">Handshake</span>::<span style="color: #f92672;">&lt;</span>Successful<span style="color: #f92672;">&gt;</span>::into_key(self) -&gt; [<span style="color: #66d9ef;">u8</span>; <span style="color: #ae81ff;">16</span>];
</span></span></code></pre></div><p>From here on out we can use this session key for the remainder of our
interactions with the card – signing messages from (and sometimes to!) the
card, or encrypted messages to and from the card. This key is used in CBC block
mode, where the session IV is updated with the last block of the encrypted
data.</p>
<h2 id="unit-testing">Unit Testing</h2>
<p>A nice proprietary of the SKA scheme we’re using as part of DESFire is that the
derived session key is actually deterministic if you control your nonce RNG
(ok, actually, pretty true for most key agreements, but anyway), which means it
is possible to capture traffic over the NFC interface, and “replay” the NFC I/O
with cooked RNGs and ensure byte-identical messages and keys are generated.
Within <code>desox-rs</code> this is called <code>replay</code> (I’m creative), and I’ve got a few
replay sessions checked into VCS, which exercise a signficant amount fo the API
surface. All were derived from an actual session with a real DESFire card, and
can be updated with a live card and a <code>--cfg</code> flag.</p>
<aside class="left">
This replay stuff wound up being super dope, it caught a ton of
almost-regressions during the heavy development phases. If I did this again
from scratch -- this would be the first thing I did.
</aside>
<p>Each <code>replay</code> file is a set of lines (request-response transactions), each
containing two space-delimited hex encoded NFC messages. For instance,
here’s an authentication handshake in <code>replay</code> format:</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>1a00 afc7bbd82ff8fefae8
</span></span><span style="display: flex;"><span>afc6dab54df2278d2952d560821be7e4c3 007d9abe94a9b14748
</span></span></code></pre></div><p>The code that generated that exchange came from the test stored adjacent
to that file – a handshake with the default DES key (all zeros), and
an <code>RndA</code> value hardcoded to <code>32c28fdafd3960de</code>.</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span><span style="color: #66d9ef;">let</span> <span style="color: #66d9ef;">mut</span> card <span style="color: #f92672;">=</span> card
</span></span><span style="display: flex;"><span> .authenticate_with_rnd_a(
</span></span><span style="display: flex;"><span> <span style="color: #ae81ff;">0x00</span>,
</span></span><span style="display: flex;"><span> Key::Des([<span style="color: #ae81ff;">0</span>; <span style="color: #ae81ff;">8</span>]),
</span></span><span style="display: flex;"><span> Key::Des(hex_literal::<span style="color: #a6e22e;">hex!</span>(<span style="color: #e6db74;">"32 c2 8f da fd 39 60 de"</span>)),
</span></span><span style="display: flex;"><span> )
</span></span><span style="display: flex;"><span> .<span style="color: #66d9ef;">await</span>
</span></span><span style="display: flex;"><span> .unwrap();
</span></span></code></pre></div><p>Since the card’s <code>RndB</code> is similarly unchanging (I’m replaying this file every
time), this will always derive the same session key, which means messages
(including encrypted ones or CMAC signed responses) will be identical, as well.
If you’re playing with the DESFire yourself, feel free to grab my <a href="https://github.com/paultag/desox-rs/tree/main/src/replay">replay
files</a> if you need a
“known good” baseline.</p>
<p>By default this will run using the
<a href="https://docs.rs/desox/latest/desox/io/struct.MockBackend.html">MockBackend</a>,
replaying each file – expecting a byte-identical request, and responding with
the harcoded customary reply. If the code (or test!) needs to change, updating
the tests is done by swapping the <code>MockBackend</code> out for a real one. Since I
had to do this a bunch during development, running <code>cargo test</code>
with <code>RUSTFLAGS="--cfg desox_replay_rw"</code> will, on run, overwrite the replay
file(s) for the executed test(s), ensuring all line-protocol changes are
explicitly caught and reviewed.</p>
<h1 id="observations">Observations</h1>
<p>Most commands, even ones which require authentication, are transmitted without
CMAC signature(s) or encryption. <code>CMAC</code> signatures from the reader to the card
are not really used (except for writes to a file which specifies communication
must be <code>CMAC</code> signed), ditto for encryption (although that one is used for key
change operations, in addition to file writes on files that specify encrypted
communication must be used). The vast majority of commands take a “plain”
request from the reader, and return a CMAC signed response.</p>
<aside class="right">
I really wish there was a mode or configuration flag I could flip that would
enforce CMAC signatures from the reader to the card.
</aside>
<p>By my eye, this means that a malicious reader, or something otherwise capable
of holding the card online after communication with an authentic reader is
complete are able to execute privilaged commands (since one can simply ignore
the <code>CMAC</code> signatures on responses), so long as the command doesn’t require the
reader to provide CMAC signatures (or encryption), or allow the card to power
down.</p>
<h1 id="fun-with-desfire">Fun with DESFire</h1>
<p>I’ve played around a bit with ways to use the DESFire cards in interesting
configurations, given what they’re capable of. Here’s some half-baked thoughts
I had while mucking around with the cards – these are all poorly thought out
sketches of some things we can do given the specific tradeoffs I see with the
DESFire card. It’s also worth noting that I don’t have any of the actual
documentation, and am not a cryptographic grown-up, so take these sketches with
a massive grain of salt.</p>
<aside class="left">
This stuff is right around when I really miss having asymmetric cryptographic
operations handy.
</aside>
<p>The first thing that came to mind when implementing this is how the
authentication scheme can shift the boundary of what is and is not trusted
(assuming good secure keying, and provided the key slots and card/application
permissions are configured correctly). Rather than push the key material out to
the machine connected to the NFC reader (“reader machine”), I instead tried
turning the NFC reader and computer into something psuedo-untrusted by “merely”
having it pass messages from the card to a trusted remote system (“remote
machine”). This means that the “reader machine” is exchanging NFC data with the
card, but that data is being decrypted, encrypted and processed by the trusted
“remote machine” – the reader is unable to derive the session key.</p>
<p>For each of these, I wind up needing to authenticate – so there’s still a few
latent risks, but these can mostly be mitigated by asking for a readbacks of
any changed file(s), setting key permissions carefully, and requesting the
card’s UID via the encrypted channel – all of which would require the
symmetric secrets (which undermine the whole security model if comprimised).</p>
<aside class="right">
This all feels a bit messy at times -- but I have to keep grounding myself
in the threat model -- "if you have the key, you can clone the card (or snoop
the session key)"
</aside>
<p>This general construction is also subject to a hostile takeover of the
untrusted “reader machine”, since most commands (including destructive ones!)
are sent in “<code>PLAIN</code>” mode – the reader machine can wait until authentication
is complete and then inject commands into the card and “simply” ignore the CMAC
signatures on responses, severing ties with the remote machine. As such, we
also need to take steps to ensure that the key being used is not one that
allows any access beyond what is allowed. Here were some ideas I sketched out
off the back of this theory.</p>
<h2 id="the-second-factor">The “second-factor”</h2>
<p>Given some established (and authenticated) connection, part of the initial
authentication flow may use the DESFire card to prove physical control over it
as part of a handshake. This can serve as a second factor during some
authentication flow, requiring physical card presence at a reader to fully
initialize a connection. This does have one glaring downside, however – it’s
phishable. To use this “for real”, we’d need to take some steps to prevent
obvious MITM flows (XOR the NFC messages with the URI as seen by the client?),
but maybe there’s something interesting there.</p>
<aside class="left">
WebAuthN is objectively better in basically every way to this -- this scheme
has some heafty downsides, but also a few interesting properties.
</aside>
<p>This also has a second interesting attribute – when used as part of a physical
system authentication flow, this becomes a logical place to inject access
control, being able to determine if some person is permitted to operate some
device at that particular time (Is “Joe” current on his Laser Cutter
certifications?) I think of the ideas I landed on, while conceptually
interesting (using an employee id card as a 2FA token, it’s very fast), this
one is the least likely to turn into something real.</p>
<h2 id="the-encrypted-cookie">The “encrypted-cookie”</h2>
<p>This construction, when paired with an encrypted DESFire file, allows the
“remote machine” to read/write an ’encrypted cookie’ to the card – storing
small amount of encrypted data that the “remote machine” can read/write, but
not the “reader machine”, since this uses an encrypted and authenticated
channel from the “remote machine” directly to the DESFire card, without any
intermediate hosts needing to be <em>fully</em> trusted. I keep calling this the
“encrypted cookie” in my head because it feels conceptually similar to how Ruby
on Rails and Laravel handles cookies.</p>
<aside class="right">
I never really liked encrypted cookies.
</aside>
<p>We’d need to take a few extra steps here (for instance, ensure that you read
the cookie back over the encrypted channel after writing to prevent a malicious
reader from dropping writes) to secure the system, but it feels like the
structure of this is definitely decent.</p>
<h2 id="the-takeover">The “takeover”</h2>
<p>This time, let’s say the computer attached to the NFC reader (“reader machine”)
<em>is</em> semi-trusted. For this scheme, our trusted “remote machine” and the
“reader machine” pass messages over the network to handle authentication to the
card (as above), where the handshake data is being decrypted, encrypted and
processed by the trusted “remote machine” as usual. However, once the
authentication handshake is complete and a session key has been derived, the
“remote system” return the session key to the “reader machine”, giving it a
one-time-use key and authenticated session to the card.</p>
<aside class="left">
Like a hermit crab.
</aside>
<p>We need to be careful about global/application permissions and key access
control to files – but in this construction, we can allow the “reader machine”
to take over privileged actions using a scope-limited DESFire key without
handing over the card’s true keying material (preventing cloning of the card).
This can be helpful to ensure messages to/from the card are <em>truely</em> from the
card (verifying CMAC signatures), enables the “reader machine” to directly
read/write to/from encrypted file(s), but allows the symmetric key material to
remain in as few places as possible – which is critical given compromising
that secret will undermine the security of the entire system.</p>
       </div>
       <p class="date">
        <a href="https://notes.pault.ag/desfire-ev3/">15 September, 2026 01:25PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <a href="http://www.corsac.net/?cat=3">
      <img class="face" src="heads/corsac-planet.png"
            width="80" height="81"
            alt="hackergotchi for Yves-Alexis Perez" />
     </a>
      
     <h2 class="planet-title">
      <a href="http://www.corsac.net/?cat=3" title="Corsac.net - Debian">Yves-Alexis Perez</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://www.corsac.net/?rub=blog&amp;post=1617_hide\" onClick=\"exclude( 'http://www.corsac.net/?rub=blog&amp;post=1617' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://www.corsac.net/?rub=blog&amp;post=1617_show\"  style=\"display:none;\" onClick=\"show( 'http://www.corsac.net/?rub=blog&amp;post=1617' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://www.corsac.net/?rub=blog&amp;post=1617" class="http://www.corsac.net/?rub=blog&amp;post=1617">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://www.corsac.net/?rub=blog&amp;post=1617">IKEv1 protocol disabled in strongSwan package for Debian unstable</a>
       </h3>
       <div class="content">
        <p>Heads up, Debian IKE/IPsec users.</p>
<p>Starting with strongSwan <a href="https://tracker.debian.org/pkg/strongswan" title="strongSwan page in Debian package tracker">6.1.0-1</a> (currently
in Debian unstable and targeted at Debian 14 Forky), the IKEv1
protocol has been disabled. This is aligned with <a href="https://strongswan.org/blog/2026/09/07/strongswan-6.1.0-released.html" title="strongSwan 6.1.0 release blog post">
upstream decision</a>. Considering IKEv2 is already nearly old
enough to drink in the USA (<a href="https://datatracker.ietf.org/doc/rfc4306/" title="RFC 4306 on IETF.org">RFC 4306 will turn
21</a> next December) and IKEv1 has weaknesses, the disabling is
long overdue amd will permit upstream to remove some code in the
upcoming years.</p>
<p>At this point there is no good reason not to migrate to IKEv2
and exposing IKEv1 code in all Debian installation is no longer
relevant. All IKEv1 users using Debian 13 Trixie (either site to
site, gateway or roadwarrior client) should investigate IKEv2
protocol (or other options).</p>
<p>Note that some plugins have also been disabled upstream for
security/maintenance reasons and we followed suite in Debian. The
Debian relevant ones are: af-alg, led, padlock.</p>
       </div>
       <p class="date">
        <a href="http://www.corsac.net/?rub=blog&amp;post=1617">15 September, 2026 07:28AM</a>
        
         by Yves-Alexis (corsac@debian.org)
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://reproducible-builds.org/blog/" title="reproducible-builds.org">Reproducible Builds</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://reproducible-builds.org/news/2026/09/15/supporter-spotlight-jochen-sprickerhof/_hide\" onClick=\"exclude( 'https://reproducible-builds.org/news/2026/09/15/supporter-spotlight-jochen-sprickerhof/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://reproducible-builds.org/news/2026/09/15/supporter-spotlight-jochen-sprickerhof/_show\"  style=\"display:none;\" onClick=\"show( 'https://reproducible-builds.org/news/2026/09/15/supporter-spotlight-jochen-sprickerhof/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://reproducible-builds.org/news/2026/09/15/supporter-spotlight-jochen-sprickerhof/" class="https://reproducible-builds.org/news/2026/09/15/supporter-spotlight-jochen-sprickerhof/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://reproducible-builds.org/news/2026/09/15/supporter-spotlight-jochen-sprickerhof/">Supporter spotlight: Jochen Sprickerhof on ... Reproducible Builds!</a>
       </h3>
       <div class="content">
        <p><img alt="" src="https://reproducible-builds.org/images/news/supporter-spotlight-jochen-sprickerhof/jochen.jpg#right" /></p>

<p><big>The Reproducible Builds project <a href="https://reproducible-builds.org/who/">relies on several projects, supporters and sponsors</a> for financial support, but they are also valued as ambassadors who spread the word about our project and the work that we do.</big></p>

<p>This is the <em>ninth</em> installment in a series featuring the projects, companies and individuals who support the Reproducible Builds project. We started this series by <a href="https://reproducible-builds.org/news/2020/10/21/supporter-spotlight-cip-project/">featuring the Civil Infrastructure Platform</a> project, and followed this up with a <a href="https://reproducible-builds.org/news/2021/04/06/supporter-spotlight-ford-foundation/">post about the Ford Foundation</a> as well as recent ones about <a href="https://reproducible-builds.org/news/2022/04/14/supporter-spotlight-ardc/">ARDC</a>, the <a href="https://reproducible-builds.org/news/2022/04/26/supporter-spotlight-google-open-source-security-team/">Google Open Source Security Team (GOSST)</a>, <a href="https://reproducible-builds.org/news/2022/05/18/jan-nieuwenhuizen-on-bootrappable-builds-gnu-mes-and-gnu-guix/">Bootstrappable Builds</a>, <a href="https://reproducible-builds.org/news/2022/06/24/supporter-spotlight-hans-christoph-steiner-f-droid-project/">the F-Droid project</a>, <a href="https://reproducible-builds.org/news/2022/12/15/supporter-spotlight-davidawheeler-supply-chain-security/">David A. Wheeler</a>, <a href="https://reproducible-builds.org/news/2023/08/01/supporter-spotlight-simon-butler/">Simon Butler</a> and <a href="https://reproducible-builds.org/news/2024/09/29/supporter-spotlight-kees-cook/">Kees Cook</a>.</p>

<p>Today, however, we will be talking with <big><strong>Jochen Sprickerhof</strong></big>,
one of the newer members of the Reproducible Builds project core team.</p>

<p><br />
<br /></p>

<p><strong>Vagrant Cascadian: Could you tell me a bit about yourself? What sort of things do you work on?</strong></p>

<p><strong>Jochen Sprickerhof:</strong>
I am a freelance programmer working on Open Source. Mainly doing <a href="https://debian.org">Debian</a>, <a href="https://f-droid.org">F-Droid</a> and some smaller software projects. In general I made it a habit to look into every software I use and try to fix bugs or add features I need. In Debian, I maintain about 180 packages with topics covering home banking, build systems and robotics. Most of my time, I currently work on <a href="https://reproduce.debian.net"><em>reproduce.debian.net</em></a>, where we try to bit-for-bit reproduce the packages distributed by Debian.</p>

<p><br /></p>

<p><strong>Vagrant: Could you describe the path that lead you to working on reproducible builds?</strong></p>

<p><strong>Jochen:</strong>
I started my Debian journey as a teenager, converting my school to Debian and serving as its system administrator for 13 years. After studying Applied System Science, I joined the university’s robotics labs, where I worked on the <a href="https://www.ros.org/">Robot Operating System</a> (ROS) and the <a href="https://pointclouds.org/">Point Cloud Library</a> (PCL). In the end, I enjoyed programming more than writing papers, so I eventually left academia for a robotics startup. Some years ago, I realized that the open source work I was doing in my spare time was actually the work I cared most about. Nowadays I am really grateful that I can spend my days working on things I find important and have lots of fun with.</p>

<p><br /></p>

<p><strong>Vagrant: What projects did you recently make big progress on?</strong></p>

<p><strong>Jochen:</strong>
A recent example is <a href="https://metasnap.debian.net"><em>metasnap.debian.net</em></a>. It is a ‘meta archive’ of
<a href="https://snapshot.debian.org"><em>snapshot.debian.org</em></a> which is itself archive of all packages in Debian. But let
me explain it the other way round: with <em>reproduce.debian.net</em>, we try to reproduce the packages as they are distributed by the Debian archive. For that, we need the same build environment (compilers, libraries, build tools, etc) that was used by Debian back when the original package was compiled. Luckily, <em>snapshot.debian.org</em> has all those packages, but they are not easily accessible via <em>apt</em>, Debian’s package manager. So, <em>metasnap</em> provides a mapping from a package name and version pair to the APT repo on <em>snapshot.debian.org</em> needed to download it from. It was created by <code class="language-plaintext highlighter-rouge">josch</code> some time ago, and it’s awesome work. But when we tried to reproduce more and more packages on <em>reproduce.debian.net</em>, we found that some were missing packages from the build environment — even though they where visible on <em>snapshot.debian.org</em>. We found that <em>metasnap</em> excluded some archive areas because they where not expected to be needed. Reimporting all the data took more than two months and surfaced a couple more flaws.</p>

<p>With this fixed, we were able to build more packages, only to find out that <em>metasnap</em> also needs better support for version numbers. Luckily we were able to rewrite the data in a day instead of starting the import again.</p>

<p><br /></p>

<p><strong>Vagrant: You have been working on infrastructure to support reproducible builds for a while. Has recent adoption of <em>reproduce.debian.net</em> into the Debian release tooling changed the focus of your work?</strong></p>

<p><strong>Jochen:</strong>
Quite a bit. When we started <em>reproduce.debian.net</em> in 2024, only around 33% of the packages could be reproduced successfully. Today we are above 98%. Most were not bugs in the packages themselves but in the <em>infrastructure</em>. Similar to the <em>metasnap</em> issue I reference above, packages just needed a rebuild because something else, like the toolchain, was fixed in the meantime. In May, people from the Debian release team and the Reproducible Builds project sat together and decided that the overall state is good enough, and now packages that regress on reproducibility are blocked from entering the next Debian release. But that does not mean all the work is on the shoulders of Debian package maintainers. Since then I have been constantly looking at the migration tooling to spot regressions and provide fixes. Furthermore, a couple of maintainers reached out to us for help and I hope more will do so in future.</p>

<p><br /></p>

<p><strong>Vagrant: What is one small thing you (or others) have not yet gotten to that you would really like to see?</strong></p>

<p><strong>Jochen:</strong>
The central tool to reproduce Debian packages is <em>debrebuild</em>, also written by <code class="language-plaintext highlighter-rouge">josch</code>. Currently it has two ways to retrieve the build dependencies of a package. Either it uses <em>metasnap.debian.net</em> (as explained above), or it can access the Debian <em>unstable</em> APT repository directly. This allows to test packages locally before everything is indexed on <em>metasnap</em> by compiling against Debian <em>unstable</em>. But actually there are many other APT repositories to query, like Debian stable or even derivatives. Adding support for an optional list of APT repositories in <code class="language-plaintext highlighter-rouge">debrebuild</code> would be great. That would also be a big step to support reproducing other Debian based distributions.</p>

<p><br /></p>

<p><strong>Vagrant: … and one <em>big</em> thing?</strong></p>

<p><strong>Jochen:</strong>
It would be great to integrate <em>metasnap.debian.net</em> into <em>snapshot.debian.org</em>. There is some discussion on it already in Debian bug <a href="https://bugs.debian.org/650783">#650783</a>.</p>

<p><br /></p>

<p><strong>Vagrant: What are the tools you use the most?</strong></p>

<p><strong>Jochen:</strong>
According to my <em>fish</em> shell history:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ history | cut -d' ' -f1 | sort | uniq -c | sort -nr | head -10
  36199 git
  20941 vi
  12271 rm
   8599 cd
   7917 ls
   6407 apt
   5631 grep
   4249 mv
   3655 dpkg
   2873 cp
</code></pre></div></div>

<p><br /></p>

<p><strong>Vagrant: So, is the <em>fish</em> shell reproducible? I remember it did not used to be…</strong></p>

<p><strong>Jochen:</strong>
You can check for yourself — <a href="https://reproduce.debian.net/excuses.html?source_name=fish">it was last time I checked</a>. But looking through the other commands, <a href="https://reproduce.debian.net/excuses.html?source_name=neovim"><em>neovim</em> sadly is not</a>. I hope we can fix that in future.</p>

<p><br /></p>

<p><strong>Vagrant: Oh, that’s a nice URL to check for reproducible package… you can just pass the source package name to check the current results?</strong></p>

<p><strong>Jochen:</strong>
Yes. Another one is <a href="https://udd.debian.org/reproducibility/"><code class="language-plaintext highlighter-rouge">udd.debian.org/reproducibility/</code></a>, where you
can list all packages of a Debian maintainer. It also lists source reproducibility and has nice filters as well.</p>

<p><br /></p>

<p><strong>Vagrant: What tools do you use specifically working on reproducible builds?</strong></p>

<p><strong>Jochen:</strong>
I don’t have statistics for that, but I would say <em>sbuild</em> to build the package, <em>debrebuild</em> to reproduce it, and <code class="language-plaintext highlighter-rouge">diffoscope</code> to analyze the differences. Obviously I also need run <code class="language-plaintext highlighter-rouge">apt source &lt;package&gt;</code> or use <em>git-buildpackage</em> to get the sources and all the tools I mentioned above.</p>

<p><br /></p>

<p><strong>Vagrant: So how many packages are left to build reproducibly, and once those are finished, what is next?</strong></p>

<p><strong>Jochen:</strong>
Right now, <em>reproduce.debian.net</em> shows over 98% reproducibility, though there are still over 650 package left and some will probably need a lot of work. But actually I think making packages reproducible is just the first step. For me, this is a project to build confidence in the system. To reproduce a package we have two parts: the source of the package and the build environment. Fixing the packages means gaining confidence in the first part but we still rely on the individual build environments for each package as we need to use the same compiler that was used when the package was build initially. Because of this, we have to keep around every historical version of all toolchain packages. I really would like to remove this extra archive, which means we would have to rebuild all of Debian around release time. I am dreaming of a Debian release where you could bit-for-bit reproduce every package just from the released versions. Due to how Debian works, however, this is not a trivial rebuild and it would need some work on the infrastructure. By the way, initially there was a third component to pay attention to: any connection to the outside world during the build. Luckily we fixed the Debian build daemons to not allow network connections during the build some time ago.</p>

<p><br /></p>

<p><strong>Vagrant: Thanks for all that work, and taking the time to tell us a bit about yourself!</strong></p>

<p><strong>Jochen:</strong>
Thanks a lot for the interview!</p>

<p><br /></p>

<hr />

<p><br />
<em>For more information about the Reproducible Builds project, please see our website at
<a href="https://reproducible-builds.org/">reproducible-builds.org</a>. If you are interested in
ensuring the ongoing security of the software that underpins our civilisation
and wish to sponsor the Reproducible Builds project, please reach out to the
project by emailing
<a href="mailto:contact@reproducible-builds.org">contact@reproducible-builds.org</a>.</em></p>
       </div>
       <p class="date">
        <a href="https://reproducible-builds.org/news/2026/09/15/supporter-spotlight-jochen-sprickerhof/">15 September, 2026 03:54AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <a href="https://www.freexian.com/tags/planet-debian/">
      <img class="face" src="heads/freexian.png"
            width="215" height="101"
            alt="hackergotchi for Freexian Collaborators" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://www.freexian.com/tags/planet-debian/" title="Planet-Debian on Freexian">Freexian Collaborators</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.freexian.com/blog/debian-contributions-08-2026/_hide\" onClick=\"exclude( 'https://www.freexian.com/blog/debian-contributions-08-2026/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.freexian.com/blog/debian-contributions-08-2026/_show\"  style=\"display:none;\" onClick=\"show( 'https://www.freexian.com/blog/debian-contributions-08-2026/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.freexian.com/blog/debian-contributions-08-2026/" class="https://www.freexian.com/blog/debian-contributions-08-2026/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.freexian.com/blog/debian-contributions-08-2026/">Debian Contributions: Security-tracker git performance, OpenSSH GSS-API split, Incus replacing lxc in Debian CI and more! (by Anupa Ann Joseph)</a>
       </h3>
       <div class="content">
        <h1 id="debian-contributions-2026-08">Debian Contributions: 2026-08</h1>
<p><a href="https://www.freexian.com/about/debian-contributions/">Contributing to Debian</a>
is part of <a href="https://www.freexian.com/about/">Freexian’s mission</a>. This article
covers the latest achievements of Freexian and their collaborators.
All of this is made possible by organizations subscribing to our
<a href="https://www.freexian.com/lts/">Long Term Support contracts</a> and
<a href="https://www.freexian.com/services/">consulting services</a>.</p>
<h2 id="replacing-lxc-with-incus-for-debian-ci-by-antonio-terceiro">Replacing lxc with Incus for Debian CI, by Antonio Terceiro</h2>
<p>At the beginning of this month,
<a href="https://salsa.debian.org/ci-team/debian-ci-config/-/commit/f59bce15da7b278dac600986701ba4792f20f134">the default backend for tests was changed to incus-lxc</a>,
leaving only a few dozen packages explicitly configured to run as lxc. Those
packages got <a href="https://udd.debian.org/cgi-bin/bts-usertags.cgi?user=debian-ci@lists.debian.org&amp;tag=lxc">bug reports filed</a>
and once those bugs are fixed they will be migrated over to incus-lxc. This
marks an important milestone for Debian CI, as the low level of isolation
between the worker host OS and the OS under test when using lxc was a
significant source of instability in our platform. Common causes for test
failures when comparing runs under plain lxc with runs with incus-lxc are
documented in the <a href="https://wiki.debian.org/ContinuousIntegration/LxcToIncus">wiki page</a>
linked from the bug reports.</p>
<h2 id="security-tracker-git-performance-by-helmut-grohne">security-tracker git performance, by Helmut Grohne</h2>
<p>Developers working with Debian’s <a href="https://salsa.debian.org/security-tracker-team/security-tracker/">security-tracker</a>
have <a href="https://bugs.debian.org/908678">reported</a> <a href="https://salsa.debian.org/security-tracker-team/security-tracker/-/work_items/2">degrading</a>
performance for a while. The options for solving this are few with repository
sizes now reaching 30GB for a repository whose working tree is a mere 60MB.
While there have been a number of proposals for changing the storage layout,
Helmut evaluated options not requiring such changes. Much of the problem hinges
on the 50MB <code>data/CVE/list</code> file that is updated in most commits. Efficiency
hinges on representing its content in git packs.</p>
<p>Practically, git frequently fails to represent its content as a delta and stores
a full copy that is typically compressed to 12MB. Add a few thousand 12MB full
blobs and you quickly have a large repository. In particular, the copy at
salsa.debian.org stores many such full blobs, so pulling from it consumes
significant bandwidth.</p>
<p>One might think that running <code>git gc</code> helps, but its utility is limited here.
When git stores objects in packs, it stores the
<a href="https://git-scm.com/book/en/v2/Git-Internals-Packfiles">history in reverse</a>.
It starts with the current version and represents older objects as differences
(deltas) to more recent objects. A delta effectively is a sequence of copying
ranges from a base object and insertion of new content. Given that humans tend
to add content over time, those additions are often represented as deletions.
The crux here is finding that base object. Given that the <code>data/CVE/list</code> file
is large, finding it involves comparing quite a few versions of it with each
other. This requires both a significant amount of RAM and CPU time. How many
objects git considers for this comparison is controlled with the <code>--window</code>
option. It is beneficial if the base object does not come from a direct child
commit but skips over commits. In doing so, long chains of deltas are avoided.
The <code>--depth</code> option controls the maximum chain length.</p>
<p>In this search, git combines all objects into a single window to facilitate file
renames. As such, it tends to compare <code>data/CVE/list</code> with <code>data/DSA/list</code>,
<code>data/DLA/list</code> and others. This is less than helpful and tends to evict all
<code>data/CVE/list</code> versions from the comparison window such that a new full blob of
it becomes required. Since the security-tracker repository rarely renames files,
we can ask git to instead <a href="https://manpages.debian.org/forky/git-man/git-repack.1.en.html">consider one window per filename</a>
via <code>--path-walk</code>. Once doing so, it becomes quite a bit better at finding
suitable deltas. The technique is less applicable to older history (2025 and
earlier), but given a lot of RAM and a bit of partitioning, <code>git gc</code> can shrink
that as well. Combining these techniques, we can shrink the repository into
700MB and keep new growth somewhat under control.</p>
<p>Temporarily, Helmut is providing a proof-of-concept mirror at
<code>git://git.subdivi.de/~helmut/security-tracker.git</code> using these techniques.
Avoid pulling from it directly as it does not provide a secure transport. While
it does reduce the amount of data being downloaded, it does not resolve a number
of related problems. After downloading, a git client will still expend
considerable amounts of CPU on verifying received deltas and attempting to
<code>git blame data/CVE/list</code> is not improved in any way. Still, there is work on
integrating some of the improvements <a href="https://salsa.debian.org/salsa/support/-/work_items/602">back into salsa</a>.</p>
<h2 id="openssh-gss-api-split-by-colin-watson">OpenSSH GSS-API split, by Colin Watson</h2>
<p>In an <a href="https://lists.debian.org/debian-devel/2024/04/msg00044.html">option review</a>
Colin did in 2024, shortly after the <a href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor">xz-utils backdoor</a>,
he explained that having GSS-API authentication and key exchange support in the
main OpenSSH packages is problematic. The key exchange patch is large and
intrusive. Even linking to the necessary libraries isn’t without risk: as the
<a href="https://attack.mitre.org/software/S0377/">Ebury malware attack</a> demonstrated
way back in 2009, each extra library linked into security-critical daemons such
as <code>sshd</code> (or nowadays into its privilege-separated helper programs) can modify
the behaviour of the daemon even if you aren’t doing anything that would involve
calling into that library. Of course some of that risk remains, but as
<a href="https://lists.debian.org/debian-devel/2024/04/msg00045.html">Damien Miller wrote</a>,
minimizing the number of libraries that end up in the address space of <code>sshd</code>
and friends is still valuable.</p>
<p>This split is now complete in testing. As of openssh 1:10.4p1-5, the OpenSSH
client and server are built without GSS-API authentication and key exchange
support. If you need those features, install <code>openssh-client-gssapi</code> or
<code>openssh-server-gssapi</code> instead, as appropriate. Debian 13 (trixie) already has
packages with those names that just depend on the regular <code>openssh-client</code> and
<code>openssh-server</code> so that you can pre-emptively install them, as
<a href="https://www.debian.org/releases/trixie/release-notes/issues.en.html#deprecated-components-for-releasename">documented in the release notes</a>.</p>
<p>The new <code>openssh-*-gssapi</code> packages have relatively tight dependencies on
<code>openssh-common</code>, in order for the testing migration system to ensure that we
can’t forget to keep them up to date. This will mean a bit more ongoing work on
each new upstream version, but it should be manageable.</p>
<h2 id="catching-up-on-debconf-video-publication-by-stefano-rivera">Catching up on DebConf video publication, by Stefano Rivera</h2>
<p>DebConf 25 videos <a href="https://salsa.debian.org/debconf-video-team/archive-meta/-/work_items/7">suffered terribly</a>
from <a href="https://github.com/voc/voctomix/issues/381">a bug</a> in voctomix that
stopped us from being able to publish the videos immediately after the
conference. Ivo De Decker did some work on the videos earlier this year, fixing
enough of the audio problems to make the videos at least intelligible.</p>
<p>While at <a href="https://ch2026.mini.debconf.org/">MiniDebConf Winterthur</a> Stefano
published all the videos from 2025 and 2026 to <a href="https://peertube.debian.social/">PeerTube</a>
and <a href="https://www.youtube.com/channel/UC7SbfAPZf8SMvAxp8t51qtQ">YouTube</a>.
DebConf 25 and DebConf 26 videos as well as all the videos from miniconfs that
had got caught up in the backlog were published.</p>
<h2 id="miscellaneous-contributions">Miscellaneous contributions</h2>
<ul>
<li>Carles used <a href="https://salsa.debian.org/carlespina/po-debconf-manager">po-debconf-manager</a>
to review and submit Catalan translations for six packages.</li>
<li>Carles prepared a <a href="https://salsa.debian.org/carlespina/webwml/-/merge_requests/1">prototype</a>
of building webwml and running “tidy” (linter) in Salsa.</li>
<li>Carles created different webwml
<a href="https://salsa.debian.org/webmaster-team/webwml/-/merge_requests/?sort=merged_at_desc&amp;state=all&amp;author_username=carlespina&amp;first_page_size=100&amp;created_after=2026-08-01">merge requests</a>
with different fixes: invalid links to different architectures, 404s to external
sites, fixed templates with invalid URL to favicon.ico, making USE_SAMPLE_FILES=1
to avoid fetching external files, typos in English, etc.</li>
<li>Carles updated the <a href="https://wiki.debian.org/LibreOffice">LibreOffice Wiki page</a>.</li>
<li>Emilio managed several transitions, such as glibc 2.43, perl 5.42, kdepim,
nginx, gnome-desktop 51, python3.14-only, and many others.</li>
<li>Emilio updated libxfont to 2.0.9, xorg-server to 21.1.24, xwayland to 24.1.13,
and fixed a FTBFS bug in terminator.</li>
<li>Antonio deployed the website for <a href="https://beijing2026.mini.debconf.org/">MiniDebConf Beijing 2026</a>,
and archived the Hamburg 2026 and Kanpur 2026 MiniDebConf websites.</li>
<li>Antonio <a href="https://tracker.debian.org/news/1787657/accepted-auto-apt-proxy-18-source-into-unstable/">released auto-apt-proxy 18</a>,
adding new features and fixing bugs; that needed a
<a href="https://tracker.debian.org/news/1788075/accepted-auto-apt-proxy-181-source-into-unstable/">followup 18.1 release</a>
to fix an issue identified in the testing migration tests.</li>
<li>Antonio did several <a href="https://salsa.debian.org/">Salsa</a> maintenance tasks,
including his first platform security update and investigating an issue that was
causing the background tasks to be severely delayed, imparting the site usability.</li>
<li>Thorsten tried to fix some bugs in hplip and cups. Unfortunately this did not
result in an upload this month, but stay tuned!</li>
<li>Helmut continued to report undeclared file conflicts and correspond about them.</li>
<li>Helmut reviewed and merged Johannes Schauer Marin Rodrigues’ work on adding
<a href="https://salsa.debian.org/helmutg/debvm/-/merge_requests/39">hurd support to debvm</a>.</li>
<li>Helmut fixed a pile of minor issues in the support libraries underpinning
<a href="https://git.subdivi.de/~helmut/pconr.git/tree/examples/unschroot_proc.py">unschroot</a>.</li>
<li>Helmut adapted rebootstrap to archive regressions and improved its handling of
non-default gcc versions.</li>
<li>Helmut fixed the rendering of decompressed logs at
<a href="https://www.freexian.com/crossqa.debian.net">crossqa.debian.net</a>.</li>
<li>Helmut sent patches for two cross build failures.</li>
<li>Colin deployed the fix for <a href="https://bugs.debian.org/979319">Invalid link rel=“canonical” on bugs.debian.org</a>.
In the process he found a few bugs in recent undeployed code and
<a href="https://salsa.debian.org/debbugs-team/debbugs/-/compare/66628595f23bd079a748ad549dd986cd51dc498e...ecc8def8320b0f5297a2e31f9b3a051d164f5d59">fixed them</a>.</li>
<li>Santiago continued helping Aryan Karamtoth with the GSoC project on
introducing livepatching support for linux. Several challenges have been
identified in the recent couple of months, and Santiago is trying to guide Aryan
to address them and design the solution. The mentoring is done in collaboration
with Emmanuel Arias.</li>
<li>Lucas onboarded a co-maintainer for Valkey.</li>
<li>Lucas has started to work on a draft for the Debian Outreach project for
onboarding newcomers interested in Debian packaging via a video course.</li>
<li>Stefano worked on the after-math of DebConf 26, handling the bursary expense
reimbursement process and winding down the conference finances.</li>
<li>Stefano uploaded Python 3.15 RC 1 to Debian unstable.</li>
<li>Stefano did some budgeting and travel planning for the DebConf 27 site-visit
in Asahikawa, Japan. The DebConf committee sends a couple of members to meet
with the local team and view the venue together, in advance of the next DebConf.</li>
</ul>
       </div>
       <p class="date">
        <a href="https://www.freexian.com/blog/debian-contributions-08-2026/">15 September, 2026 12:00AM</a>
        
         by Anupa Ann Joseph
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 13, 2026</h1>
     

    <div class="channel">
     
      
     <a href="http://dirk.eddelbuettel.com/blog">
      <img class="face" src="heads/dirk.png"
            width="65" height="90"
            alt="hackergotchi for Dirk Eddelbuettel" />
     </a>
      
     <h2 class="planet-title">
      <a href="http://dirk.eddelbuettel.com/blog" title="Thinking inside the box">Dirk Eddelbuettel</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://dirk.eddelbuettel.com/blog/2026/09/13#td_0.0.7_hide\" onClick=\"exclude( 'http://dirk.eddelbuettel.com/blog/2026/09/13#td_0.0.7' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://dirk.eddelbuettel.com/blog/2026/09/13#td_0.0.7_show\"  style=\"display:none;\" onClick=\"show( 'http://dirk.eddelbuettel.com/blog/2026/09/13#td_0.0.7' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://dirk.eddelbuettel.com/blog/2026/09/13#td_0.0.7" class="http://dirk.eddelbuettel.com/blog/2026/09/13#td_0.0.7">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/13#td_0.0.7">td 0.0.7 on CRAN: New Features and Updates</a>
       </h3>
       <div class="content">
        <p>A new version 0.0.7 of the <a href="https://github.com/eddelbuettel/td">td</a> package for accessing
the <a href="https://www.twelvedata.com">twelvedata</a> API for
financial is now on <a href="https://cran.r-project.org">CRAN</a>, and
has been built for <a href="https://eddelbuettel.github.io/r2u/">r2u</a>.</p>
<p>This release combines the standard set of maintenance changes that
accrue in a four and a half year period (!!) as that much time has past
since the previous release. But it also contains two contributed
functions to retried, respectively, a profile (available under a paid
plan) and a set of main fundamental data statistics, both provided
Kenneth Rose.</p>
<p>The NEWS entry follows.</p>
<blockquote>
<h4 id="changes-in-version-0.0.7-2026-09-13">Changes in version 0.0.7
(2026-09-13)</h4>
<ul>
<li><p>Added <code>fun_statistics</code> and <code>fun_profile</code>
function</p></li>
<li><p>Expanded README.md with additional badges, and updated
URLs</p></li>
<li><p>Updated continuous integration multiple times</p></li>
<li><p>Switched to Authors@R</p></li>
</ul>
</blockquote>
<p>Thanks to <a href="https://dirk.eddelbuettel.com/cranberries/">CRANberries</a>, you
can also look at the most recent <a href="https://dirk.eddelbuettel.com/cranberries/2026/09/13#td_0.0.7">diff
to the previous release</a>. See the <a href="https://dirk.eddelbuettel.com/code/td.html">project page</a>, the
<a href="https://github.com/eddelbuettel/td">github repo</a>, and the
package documentation for more details.</p>
<p style="font-size: 80%; font-style: italic;">
This post by <a href="https://dirk.eddelbuettel.com">Dirk
Eddelbuettel</a> originated on his <a href="https://dirk.eddelbuettel.com/blog/">Thinking inside the box</a>
blog. If you like this or other open-source work I do, you can now <a href="https://github.com/sponsors/eddelbuettel">sponsor me at
GitHub</a>.
</p><p></p>
       </div>
       <p class="date">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/13#td_0.0.7">13 September, 2026 08:57PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 12, 2026</h1>
     

    <div class="channel">
     
     <div id="http://dirk.eddelbuettel.com/blog/2026/09/12#sanitizers_0.1.2" class="http://dirk.eddelbuettel.com/blog/2026/09/12#sanitizers_0.1.2">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/12#sanitizers_0.1.2">sanitizers 0.1.2 on CRAN: Maintenance</a>
       </h3>
       <div class="content">
        <p><img alt="bleach" src="http://pixabay.com/static/uploads/photo/2013/07/12/13/53/bleach-147520_150.png" style="float: left; margin: 10px 10px 10px 10px;" /></p>
<p>The third release (in twelve years !!) of the <a href="https://github.com/eddelbuettel/sanitizers">sanitizers</a> package
is now on <a href="https://cran.r-project.org">CRAN</a>. <a href="https://github.com/eddelbuettel/sanitizers">sanitizers</a>
provides ‘true positives’ for programming errors detected by the Address
Sanitizer and friends such as the Undefined Behavior Sanitizer. This
permits validation of the setup when chasing such bug reports: it allows
us to ascertain that the compiler (and instrumented R version) are
correctly set up and the errors we expect to be reported are in fact
reported. That established, a proposed fix no longer exhibiting that
same error will then likely be a suitable one.</p>
<p>A very good resources for all things sanitizers is the <a href="https://github.com/google/sanitizers">Google repo at GitHub</a>
and especially <a href="https://github.com/google/sanitizers/wiki">its
wiki</a>.</p>
<p>A little over twelve years since the first release, and three years
since the second one, this update brings chiefly internal package
changes and maintenance. No functional changes, no behavioural
changes.</p>
<p>The brief NEWS entry follows.</p>
<blockquote>
<h4 id="changes-in-version-0.1.2-2026-09-12">Changes in version 0.1.2
(2026-09-12)</h4>
<ul>
<li><p>Expanded README.md with additional badges, and updated
URLs</p></li>
<li><p>Updated continuous integration multiple times</p></li>
<li><p>Switched to Authors@R</p></li>
<li><p>Added <code>usage</code>, <code>arguments</code> and
<code>value</code> sections to manual page</p></li>
</ul>
</blockquote>
<p>Thanks to <a href="https://dirk.eddelbuettel.com/cranberries/">CRANberries</a>, you
can also look at the most recent <a href="https://dirk.eddelbuettel.com/cranberries/2026/09/12#sanitizers_0.1.2">diff
to the previous release</a>. See the <a href="https://dirk.eddelbuettel.com/code/sanitizers.html">project
page</a>, the <a href="https://github.com/eddelbuettel/sanitizers">github repo</a>, and
the package documentation for more details.</p>
<p style="font-size: 80%; font-style: italic;">
This post by <a href="https://dirk.eddelbuettel.com">Dirk
Eddelbuettel</a> originated on his <a href="https://dirk.eddelbuettel.com/blog/">Thinking inside the box</a>
blog. If you like this or other open-source work I do, you can now <a href="https://github.com/sponsors/eddelbuettel">sponsor me at
GitHub</a>.
</p><p></p>
       </div>
       <p class="date">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/12#sanitizers_0.1.2">12 September, 2026 09:47PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 11, 2026</h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://reproducible-builds.org/blog/" title="reproducible-builds.org">Reproducible Builds</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://reproducible-builds.org/reports/2026-08/_hide\" onClick=\"exclude( 'https://reproducible-builds.org/reports/2026-08/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://reproducible-builds.org/reports/2026-08/_show\"  style=\"display:none;\" onClick=\"show( 'https://reproducible-builds.org/reports/2026-08/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://reproducible-builds.org/reports/2026-08/" class="https://reproducible-builds.org/reports/2026-08/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://reproducible-builds.org/reports/2026-08/">Reproducible Builds in August 2026</a>
       </h3>
       <div class="content">
        <p class="lead"><strong>Welcome to the August 2026 report from the <a href="https://reproducible-builds.org">Reproducible Builds</a> project!</strong></p>

<p><a href="https://reproducible-builds.org/"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/reproducible-builds.png#right" /></a></p>

<p>In our reports, we try to outline the most important things that we have been up to over the past month. As a quick recap about what problem our project intends to solve, whilst anyone may inspect the source code of free software for malicious flaws, almost all software is distributed to end users as pre-compiled binaries. The motivation behind the reproducible builds effort is to ensure no flaws have been introduced during this compilation process by promising identical results are always generated from a given source, thus allowing multiple third-parties to come to a consensus on whether a build was compromised or not.</p>

<p>In this month’s report, we cover:</p>

<ol>
  <li><a href="https://reproducible-builds.org/blog/index.rss#new-updated-sbom-specification-from-cisagov">New updated SBOM specification from CISA.gov</a></li>
  <li><a href="https://reproducible-builds.org/blog/index.rss#lwn-on-bootstrappable-builds-at-fossy-2026">LWN on Bootstrappable builds at FOSSY 2026</a></li>
  <li><a href="https://reproducible-builds.org/blog/index.rss#whats-missing-to-have-reproducible-builds-on-pypi">”What’s missing to have reproducible builds on PyPI?”</a></li>
  <li><a href="https://reproducible-builds.org/blog/index.rss#distribution-work">Distribution work</a></li>
  <li><a href="https://reproducible-builds.org/blog/index.rss#unreproducible-builds-under-erofs-filesystem-fixed">Unreproducible builds under EROFS filesystem fixed</a></li>
  <li><a href="https://reproducible-builds.org/blog/index.rss#tool-and-documentation-development">Tool and documentation development</a></li>
  <li><a href="https://reproducible-builds.org/blog/index.rss#six-new-scholarly-papers">Six new scholarly papers</a></li>
  <li><a href="https://reproducible-builds.org/blog/index.rss#patches">Patches</a></li>
</ol>

<hr />

<h3 id="new-updated-sbom-specification-from-cisagov">New updated SBOM specification from CISA.gov</h3>

<p><a href="https://www.cisa.gov/sites/default/files/2026-07/2026_cisa_sbom_minimum_elements_508c.pdf"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/2026_cisa_sbom_minimum_elements_508c.png#right" /></a></p>

<p><a href="https://en.wikipedia.org/wiki/Cybersecurity_and_Infrastructure_Security_Agency">CISA</a>, the Cybersecurity and Infrastructure Security Agency of the U.S. government published some the joint guidance entitled <a href="https://www.cisa.gov/sites/default/files/2026-07/2026_cisa_sbom_minimum_elements_508c.pdf"><em>Minimum Elements for a Software Bill of Materials</em></a> (SBOM), which updates and supersedes the baseline 2021 version covered in previous editions of these reports.</p>

<p>Whilst <a href="https://www.cisa.gov/sites/default/files/2026-07/2026_cisa_sbom_minimum_elements_508c.pdf">the PDF</a> is worth skimming, the interesting changes include that the specification now mandates standard cryptographic hashes: unlike earlier standards that allowed hash omission or manifest-only parsing, hashes must be computed from the output. This is is important for reproducible builds, as it ensures the recording of the metadata required to demonstrate the shipped software matches the build output precisely where applicable. In addition, where the top-level only dependency limitation that was present in the 2021 version has been removed in favour of complete coverage with no minimum depth. That is, SBOMs are expected to reflect all linked libraries, vendored dependencies and other build-time inclusions.</p>

<p><br /></p>

<h3 id="lwn-on-bootstrappable-builds-at-fossy-2026">LWN on Bootstrappable builds at FOSSY 2026</h3>

<p><a href="https://lwn.net/Articles/1088279/"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/fossy-sample-sm.png#right" /></a></p>

<p>In the “Toolchains and Other Development Tools” track at <a href="https://sfconservancy.org/">Software Freedom Conservancy</a>’s <a href="https://2026.fossy.ca/">FOSSY 2026</a> in British Columbia, Canada, Timothy Sample gave a presentation on <a href="https://bootstrappable.org/">bootstrappable builds</a>. This <a href="https://lwn.net/Articles/1088279/">presentation was then covered</a> in a <a href="https://lwn.net/">Linux Weekly News</a> article by Jake Edge entitled <a href="https://lwn.net/Articles/1088279/"><em>Bootstrappable builds: how and why</em></a>, which serves as an excellent introduction to the concept:</p>

<blockquote>
  <p>The basic idea behind bootstrappable builds is to create a system that can be built without relying on pre-built artifacts. “Can we go from zero to the modern day without having to just assume the existence of these already-built-for-us artifacts?” The classic recipe for yogurt requires some yogurt to start the process, which is like how we normally build a C compiler today—we start with an existing C compiler binary. You might think about making sourdough bread with your grandmother’s starter brought over from the old country; “we’re basically making C compilers with Dennis Ritchie’s starter carried over from Bell Labs”.</p>
</blockquote>

<p>The article, which goes on to cover GNU Mes and other projects that overlap with Reproducible Builds, also has <a href="https://lwn.net/Articles/1088279/#Comments">a number of thought-provoking comments</a>.</p>

<p><br /></p>

<h3 id="whats-missing-to-have-reproducible-builds-on-pypi">”<a href="https://snarky.ca/whats-missing-to-have-reproducible-builds-on-pypi/"><em>What’s missing to have reproducible builds on PyPI?</em></a>”</h3>

<p><a href="https://snarky.ca/whats-missing-to-have-reproducible-builds-on-pypi/"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/pypi.png#right" /></a></p>

<p>Core Python developer, <a href="https://snarky.ca/">Brett Cannon</a> wrote an interesting blog post this month addressing <a href="https://snarky.ca/whats-missing-to-have-reproducible-builds-on-pypi/"><em>What’s missing to have reproducible builds on PyPI</em></a>, the <a href="https://pypi.org/">official public repository for third-party Python software packages</a>:</p>

<blockquote>
  <p>The reason I like the idea of making reproducible builds work is that I think it can be done in such a way as to not require any work on the part of the producer of a distribution (which is a technical term for sdists or wheels, i.e., the people who upload stuff to PyPI), and thus make reproducible builds very low-friction for people to opt into supporting. [<a href="https://snarky.ca/whats-missing-to-have-reproducible-builds-on-pypi/">…</a>]</p>
</blockquote>

<p>Brett goes on to outline “What’s missing from the specs” and how reproducibility might be visible on PyPI to consumers:</p>

<p>Assuming all of this comes to pass and we record the where the source code is that went into a distribution and the software used to make the distribution, how do we make it useful to people? Does every person who cares about having a secure supply chain have to rebuild everything they use themselves? Is there some way for even people who don’t care about this stuff to benefit? [<a href="https://snarky.ca/whats-missing-to-have-reproducible-builds-on-pypi/">…</a>]</p>

<p><br /></p>

<h3 id="distribution-work">Distribution work</h3>

<p><a href="https://debian.org/"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/debian.png#right" /></a></p>

<p>In <strong>Debian</strong> this month, 23 reviews of Debian packages were added, 28 were updated and 27 were removed this month adding to <a href="https://tests.reproducible-builds.org/debian/index_issues.html">our knowledge about identified issues</a>. A number of issue types have been updated as well, such as the addition of a new toolchain issue related to <code class="language-plaintext highlighter-rouge">python-traitlets</code> [<a href="https://salsa.debian.org/reproducible-builds/reproducible-notes/commit/666e1847">…</a>], and the note for an existing issue related to <code class="language-plaintext highlighter-rouge">texi2html</code> was updated as well [<a href="https://salsa.debian.org/reproducible-builds/reproducible-notes/commit/8c55be8f">…</a>].</p>

<p><a href="https://www.opensuse.org/"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/opensuse.png#right" /></a></p>

<p>Lastly, Bernhard M. Wiedemann posted another <a href="https://www.opensuse.org/"><strong>openSUSE</strong></a> <a href="https://lists.opensuse.org/archives/list/factory@lists.opensuse.org/thread/C7BCS4OC2XPR5ZNOEXCJGP6NXL2W5BNT/">monthly update</a> for their reproducibility work there.</p>

<p><br /></p>

<h3 id="unreproducible-builds-under-erofs-filesystem-fixed">Unreproducible builds under EROFS filesystem fixed</h3>

<p><a href="https://fosstodon.org/@martinpitt">Martin Pitt</a> reported on <a href="https://fosstodon.org/">Fosstodon</a> that they had <a href="https://fosstodon.org/@martinpitt/117034829700534836">identified an issue</a> where the <code class="language-plaintext highlighter-rouge">mkfs</code> (“make filesystem”) command for the <a href="https://en.wikipedia.org/wiki/EROFS">EROFS</a> (Enhanced Read-Only File System) subsystem of the Linux kernel did not have sorted <a href="https://en.wikipedia.org/wiki/Extended_file_attributes">extended file attributes</a>, leading to reproducible builds.</p>

<p>Thankfully, Martin also reported that <a href="https://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git/commit/?id=37fc4113694e1c9690bd871aefe91f55bf8bd029">they had fixed this in a commit to the kernel</a> which “order[s] each inode’s xattrs by name so that images stay reproducible”. [<a href="https://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git/commit/?id=37fc4113694e1c9690bd871aefe91f55bf8bd029">…</a>]</p>

<p><br /></p>

<h3 id="tool-and-documentation-development">Tool and documentation development</h3>

<p><a href="https://diffoscope.org/"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/diffoscope.png#right" /></a></p>

<p><a href="https://diffoscope.org"><strong>diffoscope</strong></a> is our in-depth and content-aware diff utility that can locate and diagnose reproducibility issues. This month, Chris Lamb made a number of changes, including preparing and uploading versions <code class="language-plaintext highlighter-rouge">327</code>, <code class="language-plaintext highlighter-rouge">328</code> and <code class="language-plaintext highlighter-rouge">329</code> to Debian. In particular, he ensured that <em>diffoscope</em> did not require <em>python3-guestfs</em> in the autopkgtests on 32-bit architectures in order to fix Debian bug (<a href="https://bugs.debian.org/1144372">#1144372</a>) [<a href="https://salsa.debian.org/reproducible-builds/diffoscope/commit/5440dd50">…</a>]. Colin Watson made an additional change, handling a potentially missing <code class="language-plaintext highlighter-rouge">openssh-client</code> package when running the autopkgtests [<a href="https://salsa.debian.org/reproducible-builds/diffoscope/commit/a4fd009a">…</a>], and Jochen Sprickerhof made a similar change to cope with missing <code class="language-plaintext highlighter-rouge">cpio</code> and <code class="language-plaintext highlighter-rouge">qemu-img</code> functionality [<a href="https://salsa.debian.org/reproducible-builds/diffoscope/commit/ae4f6f86">…</a>] whilst also updating the XML comparator to be considered when comparing SVG images [<a href="https://salsa.debian.org/reproducible-builds/diffoscope/commit/c7f23bb0">…</a>].</p>

<p><br /></p>

<p><a href="https://reproducible-builds.org/"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/website.png#right" /></a></p>

<p>Yet again, there were a number of improvements made to <a href="https://reproducible-builds.org/"><strong>our website</strong></a> this month as well. For example:</p>

<ul>
  <li>
    <p>Chris Lamb added draft for a <a href="https://reproducible-builds.org/news/2026/08/12/reproducible-builds-summit-in-gothenburg/">Gothenburg summit</a>-related news article. [<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/126f1774">…</a>][<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/194c4f24">…</a>]</p>
  </li>
  <li>
    <p>Holger Levsen then published <a href="https://reproducible-builds.org/news/2026/08/12/reproducible-builds-summit-in-gothenburg/">the same article</a>. [<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/12265a52">…</a>][<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/3be2ff73">…</a>]</p>
  </li>
  <li>
    <p>Lastly, a large number of commits were pushed comprising an interview with Reproducible Builds developer Jochen Sprickerhof to be published within the next week. [<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/22f405e8">…</a>][<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/f11b768a">…</a>][<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/953236c7">…</a>][<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/60aa39b1">…</a>][<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/0eb0aa3c">…</a>][<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/53682fea">…</a>][<a href="https://salsa.debian.org/reproducible-builds/reproducible-website/commit/11cd8461">…</a>]</p>
  </li>
</ul>

<p><br /></p>

<h3 id="six-new-scholarly-papers">Six new scholarly papers</h3>

<p><a href="https://arxiv.org/abs/2607.21888"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/paper-2607.21888.png#right" /></a></p>

<p>Jens Dietrich, Spencer Sun, Tim W. White and Behnaz Hassanshahi (the result of a collaboration between <a href="https://www.wgtn.ac.nz/">Victoria University of Wellington</a> and <a href="https://www.oracle.com/anz/">Oracle Australia</a> published a paper this month entitled <a href="https://arxiv.org/abs/2607.21888"><em>No Snake Oil: Verifying Python Package Builds</em></a>. Drawing on the metaphor of “snake oil”, that is, a fake or ineffective medicine or solution sold with exaggerated claims of curing or fixing everything, the authors write that</p>

<blockquote>
  <p>Two tools that are designed to automate [PyPI] rebuilds and run them at scale are <code class="language-plaintext highlighter-rouge">macaron</code> and <code class="language-plaintext highlighter-rouge">oss-rebuild</code>. We study 12,180 popular releases from PyPI and find that the byte-for-byte equivalence rate is generally low. We analyse the reasons why they produce different wheels, and find that equivalence between the original and rebuilt wheels can often still be established, preserving most of the guarantees users expect from rebuildable releases. <strong>We present and evaluate daleq4py, a tool to establish the equivalence of Python wheels through the kernel of a normalisation function that is based on provenance-preserving datalog rules. Experimental results show that daleq4py substantially expands the set of rebuilds that can be accepted as equivalent.</strong></p>
</blockquote>

<p>The <a href="https://arxiv.org/pdf/2607.21888">full PDF of their paper</a> can be viewed online, and Jens Dietrich to <a href="https://lists.reproducible-builds.org/listinfo/rb-general/">our mailing list</a> to <a href="https://lists.reproducible-builds.org/pipermail/rb-general/2026-August/004146.html">announce the availability</a> of both the paper and the <a href="https://github.com/binaryeq/daleq4py/"><code class="language-plaintext highlighter-rouge">daleq4py</code> tool</a> itself.</p>

<p><br /></p>

<p><a href="https://arxiv.org/abs/2607.26719"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/paper-2607.26719.png#right" /></a></p>

<p>Dimitri Kokkonis, Michaël Marcozzi and Stefano Zacchiroli published an article this month titled <a href="https://arxiv.org/abs/2607.26719"><em>Not In My Git Yard: Catching Backdoors at Commit and Release Time</em></a> on the topic of “code-level backdoors” — that is, “stealthy code changes that grant hidden privileges via secret triggers”. These issues:</p>

<blockquote>
  <p>… pose a persistent threat to opensource software. Known attempts to inject such backdoors into widely used projects through malicious commits, tampered release packages, or compromised third-party dependencies, were stopped only by luck and manual review. Existing Continuous Integration (CI) pipelines cannot detect these attacks, and downstream binary analysis tools require substantial manual effort. <strong>In this work, we present Lily, an automated approach that strengthens open-source development and release processes against backdoor injection. Lily integrates a backdoor detection mechanism into (1) CI pipelines to block malicious commits, and (2) release vetting workflows to prevent tampered releases or compromised dependencies from entering large ecosystems, such as Linux distributions.</strong></p>
</blockquote>

<p>The <a href="https://arxiv.org/pdf/2607.26719">full PDF</a> can be read online.</p>

<p><br /></p>

<p><a href="https://arxiv.org/abs/2608.20678"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/paper-2608.20678.png#right" /></a></p>

<p>Ranindya Paramitha and Laurie Williams of North Carolina State University along with  Christian Kästner of Carnegie Mellon University published a paper this month with the title of <a href="https://arxiv.org/abs/2608.20678"><em>The Software Supply Chain as a Market for Lemons: A Multivocal Review of Trust Signal Collapse</em></a>. (A <a href="https://en.wikipedia.org/wiki/Lemon_(automobile)">“lemon”</a> in American English, is a vehicle that “turns out to have several manufacturing defects”.) Their abstract is as follows:</p>

<blockquote>
  <p>Practitioners evaluating open-source dependencies rely on cheap trust signals, e.g., stars, download counts, and contributor activity, as substitutes for direct code inspection, assuming those signals reflect genuine trustworthiness. Prior work has documented individual signal gaming, but the landscape of collapses across all dependency-adoption signals, as well as the ecosystem’s response, remains unexplored. <strong>The goal of this study is to aid software practitioners in understanding the reliability of dependency adoption trust signals, such as download counts and contributor activity, by conducting a multivocal review of 252 Google Search sources and 870 Reddit threads.</strong></p>
</blockquote>

<p>Worryingly, after their review, the authors conclude that “cheap trust signals collapse under three simultaneous forces: adversarial manipulation, gaming techniques indistinguishable from legitimate behavior, and non-adversarial AI-driven inflation.”</p>

<p>The <a href="https://arxiv.org/pdf/2608.20678">full PDF of the paper</a> is available online.</p>

<p><br /></p>

<p><a href="https://arxiv.org/abs/2607.24888"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/paper-2607.24888.png#right" /></a></p>

<p>Julien Malka, Aman Sharma, Martin Monperrus, Stefano Zacchiroli and Théo Zimmermann published a paper this month on <a href="https://arxiv.org/abs/2607.24888"><em>Trusting-Trust Attack against an Entire Linux Distribution through Binary Manipulation</em></a>:</p>

<blockquote>
  <p><a href="https://en.wikipedia.org/wiki/Backdoor_(computing)#Compiler_backdoors">Ken Thompson’s trusting-trust attack</a>, in which a compromised compiler backdoors the programs it builds and reproduces the backdoor in subsequent rebuilds of itself, is widely regarded as a threat specific to compilers. We show that it is not. <strong>We construct a complete trusting-trust attack around GNU <em>strip</em>, an ordinary build utility that neither inspects nor generates source code, using only manipulations of finished ELF files.</strong></p>
</blockquote>

<p>Scarily, in the authors’ example, “a single tampered <em>strip</em> in the binary seed implants a payload that propagates from one generation of strip to the next and survives into the final standard environment after the seed leaves the dependency closure […] without failures and backdoors”.</p>

<p>A <a href="https://arxiv.org/pdf/2607.24888">full PDF of the paper</a> is available for download online.</p>

<p><br /></p>

<p><a href="https://arxiv.org/abs/2608.27125"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/paper-2608.27125.png#right" /></a></p>

<p>Mehdi Keshanimm, Amirhossein Rahmati, Mohammad Hossein Aref and Abbas Heydarnoori published a paper that is currently under review at <em>Emperical Software Engineering</em> titled <a href="https://arxiv.org/abs/2608.27125"><em>AROMA+: A Study of Factors Affecting Reproducible Builds in the Maven Ecosystem</em></a>. (<a href="https://maven.apache.org/">Maven</a> is a/the build automation tool used for Java projects.) In their paper, the authors note that</p>

<blockquote>
  <p>[…] reusing external software in a project presents a security risk when the source of the component is unknown or the consistency of a component cannot be verified. The <a href="https://en.wikipedia.org/wiki/SolarWinds#2019%E2%80%932020_supply_chain_attacks">SolarWinds attack</a> serves as a popular example in which the injection of malicious code into a library affected thousands of customers and caused a loss of billions of dollars. […] Our research aims to support [reproducibility] efforts in the Maven ecosystem through automation. We investigate the feasibility of automatically finding the source code of a library from its Maven release and recovering information about the original release environment. Our tool, AROMA+, can obtain this critical information from the artifact and the source repository through several heuristics and we use the results for reproduction attempts of packages on Maven Central.</p>
</blockquote>

<p>The <a href="https://arxiv.org/pdf/2608.27125">full PDF of their article</a> can be downloaded online.</p>

<p><br /></p>

<p><a href="https://arxiv.org/abs/2608.18180"><img alt="" src="https://reproducible-builds.org/images/reports/2026-08/paper-2608.18180.png#right" /></a></p>

<p>Lastly, Oreofe Solarin, Kelechi Kalu, James C. Davis and Paschal Amusuo published a paper this month titled <a href="https://arxiv.org/abs/2608.18180"><em>Reproducibility is Not Enough: Artifact Verifiability in Decentralized-Build Package Ecosystems</em></a>:</p>

<blockquote>
  <p>[A]rtifact verification requires more than deterministic builds: a verifier must also recover the source state, build environment, dependencies, and build instructions that produced the artifact. Decentralized-build ecosystems make this difficult because artifacts are produced through heterogeneous tools, maintainer-controlled workflows, and fragmented metadata. As a result, it remains unclear how often artifacts in these ecosystems can be independently verified. <strong>This paper studies artifact verifiability across four popular decentralized-build package ecosystems. We define an independent verifier model that relies only on registry-derivable metadata and an artifact comparison model with tiered equivalence levels. We implement these models in an Artifact Verification Pipeline and use it to measure artifact verifiability across the target ecosystems.</strong></p>
</blockquote>

<p>The authors conclude that “beyond build determinism, verifiability is limited by missing source and build metadata, implicit release transformations, and unconventional build practices”.</p>

<p>A <a href="https://arxiv.org/pdf/2608.18180">PDF</a> of their paper can be reviewed online.</p>

<p><br /></p>

<h3 id="patches">Patches</h3>

<p>The Reproducible Builds project detects, dissects and attempts to fix as many currently-unreproducible packages as possible. We endeavour to send all of our patches upstream where applicable or possible. This month, we wrote a large number of such patches, including:</p>

<ul>
  <li>
    <p>Bernhard M. Wiedemann:</p>

    <ul>
      <li><a href="https://bugzilla.opensuse.org/show_bug.cgi?id=1274324"><code class="language-plaintext highlighter-rouge">Botan</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1371954"><code class="language-plaintext highlighter-rouge">Rivet</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1374641"><code class="language-plaintext highlighter-rouge">boost</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1370073"><code class="language-plaintext highlighter-rouge">ceph</code></a></li>
      <li><a href="https://github.com/pop-os/xdgen/pull/2"><code class="language-plaintext highlighter-rouge">cosmic/xdgen</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1374640"><code class="language-plaintext highlighter-rouge">cpio</code></a></li>
      <li><a href="https://github.com/dlang/dmd/pull/23746"><code class="language-plaintext highlighter-rouge">dmd</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1374399"><code class="language-plaintext highlighter-rouge">dvgt</code></a></li>
      <li><a href="https://git.enlightenment.org/enlightenment/efl/pulls/148"><code class="language-plaintext highlighter-rouge">efl</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1375163"><code class="language-plaintext highlighter-rouge">firefox-esr</code></a></li>
      <li><a href="https://github.com/fritzing/fritzing-app/pull/4336"><code class="language-plaintext highlighter-rouge">fritzing</code></a></li>
      <li><a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108534#c3"><code class="language-plaintext highlighter-rouge">gcc/esbuild</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1373692"><code class="language-plaintext highlighter-rouge">git-annex</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1373163"><code class="language-plaintext highlighter-rouge">go1.27/esbuild</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1370297"><code class="language-plaintext highlighter-rouge">gputils/sdcc</code></a></li>
      <li><a href="https://github.com/OSGeo/grass/pull/7849"><code class="language-plaintext highlighter-rouge">grass</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1370260"><code class="language-plaintext highlighter-rouge">gri</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1369973"><code class="language-plaintext highlighter-rouge">java-21-openj9</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1369976"><code class="language-plaintext highlighter-rouge">java-25-openj9</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1370294"><code class="language-plaintext highlighter-rouge">ksh</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1374284"><code class="language-plaintext highlighter-rouge">llvm22</code></a></li>
      <li><a href="https://github.com/llvm/llvm-project/pull/219384"><code class="language-plaintext highlighter-rouge">llvm</code></a></li>
      <li><a href="https://github.com/llvm/llvm-project/pull/219388"><code class="language-plaintext highlighter-rouge">llvm</code></a></li>
      <li><a href="https://github.com/ozwaldorf/lutgen-rs/pull/46"><code class="language-plaintext highlighter-rouge">lutgen</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1373751"><code class="language-plaintext highlighter-rouge">maxima</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1371702"><code class="language-plaintext highlighter-rouge">mingw64-filesystem</code></a></li>
      <li><a href="https://invent.kde.org/network/neochat/-/merge_requests/2987"><code class="language-plaintext highlighter-rouge">neochat</code></a></li>
      <li><a href="https://github.com/openSUSE/obs-build/pull/1164"><code class="language-plaintext highlighter-rouge">obs</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1373241"><code class="language-plaintext highlighter-rouge">python-PyMuPDF</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1373864"><code class="language-plaintext highlighter-rouge">python-gguf</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1371636"><code class="language-plaintext highlighter-rouge">python-langgraph</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1369446"><code class="language-plaintext highlighter-rouge">python-pook</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1369499"><code class="language-plaintext highlighter-rouge">python-pyzmq</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1371724"><code class="language-plaintext highlighter-rouge">python-xgrammar</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1373248"><code class="language-plaintext highlighter-rouge">python-xlsx2csv</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1370201"><code class="language-plaintext highlighter-rouge">qt6-tools</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1371634"><code class="language-plaintext highlighter-rouge">sbcl</code></a></li>
      <li><a href="https://build.opensuse.org/request/show/1374393"><code class="language-plaintext highlighter-rouge">scons</code></a></li>
      <li><a href="https://github.com/VirtualBox/virtualbox/pull/827"><code class="language-plaintext highlighter-rouge">virtualbox</code></a></li>
      <li><a href="https://gitlab.com/wvermin/xpenguins/-/merge_requests/1"><code class="language-plaintext highlighter-rouge">xpenguins</code></a></li>
      <li><a href="https://github.com/zabbix/zabbix/pull/184"><code class="language-plaintext highlighter-rouge">zabbix</code></a></li>
    </ul>
  </li>
  <li>
    <p>Chris Lamb:</p>

    <ul>
      <li><a href="https://bugs.debian.org/1143615">#1143615</a> filed against <a href="https://tracker.debian.org/pkg/keychain"><code class="language-plaintext highlighter-rouge">keychain</code></a>.</li>
      <li><a href="https://bugs.debian.org/1143616">#1143616</a> filed against <a href="https://tracker.debian.org/pkg/python-nameparser"><code class="language-plaintext highlighter-rouge">python-nameparser</code></a>.</li>
      <li><a href="https://bugs.debian.org/1143617">#1143617</a> filed against <a href="https://tracker.debian.org/pkg/lcov"><code class="language-plaintext highlighter-rouge">lcov</code></a>.</li>
      <li><a href="https://bugs.debian.org/1144065">#1144065</a> filed against <a href="https://tracker.debian.org/pkg/displaycal-py3"><code class="language-plaintext highlighter-rouge">displaycal-py3</code></a>.</li>
      <li><a href="https://bugs.debian.org/1144066">#1144066</a> filed against <a href="https://tracker.debian.org/pkg/redmine"><code class="language-plaintext highlighter-rouge">redmine</code></a>.</li>
      <li><a href="https://bugs.debian.org/1144407">#1144407</a> filed against <a href="https://tracker.debian.org/pkg/django-htmx"><code class="language-plaintext highlighter-rouge">django-htmx</code></a>.</li>
      <li><a href="https://bugs.debian.org/1144409">#1144409</a> filed against <a href="https://tracker.debian.org/pkg/pybdsf"><code class="language-plaintext highlighter-rouge">pybdsf</code></a>.</li>
      <li><a href="https://bugs.debian.org/1145914">#1145914</a> filed against <a href="https://tracker.debian.org/pkg/golang-github-adamkorcz-go-fuzz-headers-1"><code class="language-plaintext highlighter-rouge">golang-github-adamkorcz-go-fuzz-headers-1</code></a>.</li>
      <li><a href="https://bugs.debian.org/1145915">#1145915</a> filed against <a href="https://tracker.debian.org/pkg/googletest"><code class="language-plaintext highlighter-rouge">googletest</code></a>.</li>
    </ul>
  </li>
  <li>
    <p>Jochen Sprickerhof:</p>

    <ul>
      <li><a href="https://bugs.debian.org/1145443">#1145443</a> filed against <a href="https://tracker.debian.org/pkg/cross-toolchain-base"><code class="language-plaintext highlighter-rouge">cross-toolchain-base</code></a>.</li>
      <li><a href="https://bugs.debian.org/1146088">#1146088</a> filed against <a href="https://tracker.debian.org/pkg/ferret-vis"><code class="language-plaintext highlighter-rouge">ferret-vis</code></a>.</li>
    </ul>
  </li>
  <li>
    <p>Robin Candau:</p>

    <ul>
      <li><a href="https://codeberg.org/aelinden/BashBurn/pulls/1"><code class="language-plaintext highlighter-rouge">bashburn</code></a></li>
      <li><a href="https://github.com/LMMS/lmms/pull/8494"><code class="language-plaintext highlighter-rouge">LMMS</code></a></li>
    </ul>
  </li>
  <li>
    <p>Werner Fink:</p>

    <ul>
      <li><a href="https://build.opensuse.org/request/show/1364197"><code class="language-plaintext highlighter-rouge">bash</code></a></li>
    </ul>
  </li>
</ul>

<p><br />
<br /></p>

<p class="small">If you are interested in contributing to the project, please visit our <a href="https://reproducible-builds.org/contribute/"><em>Contribute</em></a> page on our website.</p>
       </div>
       <p class="date">
        <a href="https://reproducible-builds.org/reports/2026-08/">11 September, 2026 10:12AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 10, 2026</h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://blog.tenstral.net" title="Debian – Ximions Blog">Matthias Klumpp</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://blog.tenstral.net/2026/09/jpeg-xl-appstream-and-better-media-processing.html_hide\" onClick=\"exclude( 'https://blog.tenstral.net/2026/09/jpeg-xl-appstream-and-better-media-processing.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://blog.tenstral.net/2026/09/jpeg-xl-appstream-and-better-media-processing.html_show\"  style=\"display:none;\" onClick=\"show( 'https://blog.tenstral.net/2026/09/jpeg-xl-appstream-and-better-media-processing.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://blog.tenstral.net/2026/09/jpeg-xl-appstream-and-better-media-processing.html" class="https://blog.tenstral.net/2026/09/jpeg-xl-appstream-and-better-media-processing.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://blog.tenstral.net/2026/09/jpeg-xl-appstream-and-better-media-processing.html">JPEG-XL as default in AppStream, and better media processing</a>
       </h3>
       <div class="content">
        <p class="wp-block-paragraph">Two weeks ago, I released AppStream 1.2.0. This release contains a lot of great changes, but one of the most important ones concerns how media are being handled, and AppStream’s default image export format.</p>



<p class="wp-block-paragraph"><a href="https://www.freedesktop.org/software/appstream/docs/">AppStream</a> is a Freedesktop metadata standard to describe software components. That can be anything from system services over fonts to console and graphical applications. AppStream metadata is supposed to give users enough information to decide whether they want to install a piece of software, to represent that piece of software, and to give the operating system enough information to decide whether a software component should be installed automatically and (to some extent) what capabilities and relations it has, to provide the user with sensible options.</p>



<p class="wp-block-paragraph">Especially for the first two goals, and especially for GUI applications, AppStream supports icons and screenshots, which are used to showcase applications. Today, AppStream is used by all kinds of services, from Linux distributions over firmware updates to Flatpak and desktops directly. AppStream’s original design however comes from the perspective of Linux distributions in 2011, where you may want to browse the software catalog offline, without delay, and without pinging an external server (which could be a privacy concern).</p>



<p class="wp-block-paragraph">Therefore, a common way to deploy an AppStream-enabled software repository is to ship all icons of all applications in the repository to the user as part of the repository metadata download. AppStream does support remote icon downloads nowadays, and for a while I thought that this would become the default eventually. However, especially in today’s world, having a bandwidth-saving, instantly responsive, privacy-protecting application browsing experience seems more important that ever.</p>



<h3 class="wp-block-heading">PNG images are great!</h3>



<p class="wp-block-paragraph">The only format that AppStream supports for icons <strong>and</strong> screenshots (which are downloaded on-demand from your distributor’s CDN) has always been exclusively <a href="https://en.wikipedia.org/wiki/PNG">PNG</a>. PNG images are perfect for icons, because they compress well (especially for common icon shapes), are fast and simple to load, and can be loaded anywhere, by any toolkit or webbrowser. They also ensure we deliver faithful screenshot images, even though we may have scaled or re-rendered them. Still though, PNG images are less great for screenshots, as they are not very efficient, which puts strain on any CDN that has to deliver them, as well as on people’s internet connections when browsing screenshots. Having smaller thumbnails alleviates that problem a little, but does not fully solve it.</p>



<p class="wp-block-paragraph">But even for icons, PNG could be improved upon: In many cases, icons are re-downloaded with the repository metadata again and again, so having a large icon tarball adds up to the data transferred during metadata refreshes. AppStream also now supports large 128x128px icons, which nobody in 2012 expected we would need, adding even more data that will be re-downloaded. Saving some space here translates directly to lower bandwidth costs as well as faster downloads for users.</p>



<p class="wp-block-paragraph">To improve PNG file sizes, the AppStream Compose library, which handles all image processing and metadata catalog composition, was running <a href="https://optipng.sourceforge.net/">optipng</a> on all generated PNG images. That does create smaller PNG images, but they were still relatively large compared to other image formats.</p>



<p class="wp-block-paragraph">For a long time though, there was no alternative to PNG images for icons: There was no lossless image compression format that could give us the same quality as PNG images and that was also widely supported.</p>



<h3 class="wp-block-heading">JPEG-XL vs PNG in AppStream</h3>



<p class="wp-block-paragraph">Since 2021 we have <a href="https://jpegxl.info/">JPEG-XL</a> (JXL), which offers a true lossless mode with often better compression than PNG. The issue was that JPEG-XL wasn’t widely supported. Then, in 2025, the PDF Association selected JPEG-XL as the preferred image format for HDR images in PDFs, and now we are finally getting browser support and more ubiquitous availability of the format (you can try it right now in Firefox!).</p>



<p class="wp-block-paragraph">For screenshots, using JXL’s lossy mode, it has obvious and extreme size advantages over PNG, so supporting JXL or WebP for screenshot images was an obvious choice. If JXL would support the lossless case very well as well though, we could serve many use cases with the same exported image format, which is very attractive to me.</p>



<p class="wp-block-paragraph">So, the obvious next question was whether it was worth the pain of switching the icon format, so I did some measurements on real icons. For that I used the AppStream component icon pool that Debian Unstable ships, which is almost 5000 application icons of various sizes, and converted them to PNG:</p>



<div>
<table>
  <thead>
    <tr><th>Icon size</th><th align="right">Icons</th><th align="right">PNG total</th><th align="right">JXL total</th><th align="right">Pool saved</th><th align="right">PNG avg</th><th align="right">JXL avg</th><th align="right">Median saved</th><th align="right">Mean saved</th><th align="right">  Worst</th><th align="right"> Best</th><th align="right">Larger as JXL</th></tr>
  </thead>
  <tbody>
    <tr><td>48×48    </td><td align="right"> 1544</td><td align="right">  3.7 MiB</td><td align="right">  3.0 MiB</td><td align="right">     17.8%</td><td align="right">2.4 KiB</td><td align="right">2.0 KiB</td><td align="right">       17.9%</td><td align="right">     16.7%</td><td align="right">-118.7%</td><td align="right">60.0%</td><td align="right">          206</td></tr>
    <tr><td>64×64    </td><td align="right"> 2018</td><td align="right">  7.0 MiB</td><td align="right">  5.8 MiB</td><td align="right">     17.8%</td><td align="right">3.6 KiB</td><td align="right">2.9 KiB</td><td align="right">       18.0%</td><td align="right">     15.8%</td><td align="right">-112.7%</td><td align="right">70.0%</td><td align="right">          279</td></tr>
    <tr><td>128×128  </td><td align="right"> 1411</td><td align="right"> 11.2 MiB</td><td align="right">  8.7 MiB</td><td align="right">     22.0%</td><td align="right">8.1 KiB</td><td align="right">6.3 KiB</td><td align="right">       20.1%</td><td align="right">     17.5%</td><td align="right"> -89.7%</td><td align="right">61.0%</td><td align="right">          209</td></tr>
  </tbody>
  <tfoot>
    <tr><th>TOTAL    </th><th align="right"> 4973</th><th align="right"> 21.9 MiB</th><th align="right"> 17.5 MiB</th><th align="right">     19.9%</th><th align="right">4.5 KiB</th><th align="right">3.6 KiB</th><th align="right">       18.6%</th><th align="right">     16.6%</th><th align="right">-118.7%</th><th align="right">70.0%</th><th align="right">          694</th></tr>
  </tfoot>
</table>
</div>
<p><small>PNG images saved with libpng at <code>effort=4, compression=9</code>, then optimized using <code>optipng -o2</code>, JXL images encoded using <code>vips jxlsave lossless=1 effort=7 strip=1</code> via VIPS/libjxl.</small></p>



<p class="wp-block-paragraph">As the table shows, using lossless JXL images over size-optimized PNG images (using optipng’s default settings) provides a roughly 20% gain. This does not look like much, until you consider how often these files are downloaded: A 20% file size reduction may only save 1-2 MiB of disk space, but if they are downloaded over and over again by many clients, it will save a lot of bandwidth.</p>



<h3 class="wp-block-heading">Interesting JXL encoding findings</h3>



<p class="wp-block-paragraph">As a sidequest, I was curious why some images were larger than their PNG counterparts when encoded with JXL, and what the ones that were significantly smaller were.</p>



<p class="wp-block-paragraph">In short, the biggest size reductions for JXL existed on images that were already small as PNG, and contained large, flat color surfaces with hard edges and simple shapes. They were not very interesting, and much of JXL’s wins come from accumulating smaller gains across all files, which compound the bigger icons get (especially at 128x128px, where JXL truly shines).</p>



<p class="wp-block-paragraph">The events were JXL loses to PNG are more interesting: For example, it does quite poorly with pixel-art images that have a lot of repeating patterns. Those are encoded well by PNG, but less efficiently by JXL. Take for example Vonsh:</p>



<figure class="wp-block-image size-full is-resized"><a href="https://blog.tenstral.net/wp-content/uploads/2026/09/vonsh_vonsh.png"><img alt="" class="wp-image-2137" height="64" src="https://blog.tenstral.net/wp-content/uploads/2026/09/vonsh_vonsh.png" style="width: 151px; height: auto;" width="64" /></a>Icon of <a href="https://github.com/aurb/vonsh/">Vonsh</a>, an SDL-based snake game, which PNG compresses better than JXL</figure>



<p class="wp-block-paragraph">My guess is that while PNG can exploit the repeating pixel patterns for compression, JXL’s predicts surrounding pixels from its neighbours, which fails too often and makes it pay almost full entropy per pixel. In this single rare case, the PNG is at 5.4 KiB, while the JXL is almost 8 KiB in size.</p>



<p class="wp-block-paragraph">Other cases I looked at were arguably buggy input data, where color channels were hidden under the alpha channel of the input image. PNG could probably again exploit repeats, while we were forcing JXL to encode pixels that were invisible in the final image. This is arguably a problem with the original input data. Currently, AppStream does not make any changes to icons at all, but in future we might add a filter that removes invisible colors from images to solve this pathological case (it was only two icons out of 5000 though, so it is not a high priority).</p>



<p class="wp-block-paragraph">The third case I found where JXL loses to PNG were icons with checkerboard-like patterns:</p>



<figure class="wp-block-image size-full is-resized"><a href="https://blog.tenstral.net/wp-content/uploads/2026/09/x3270_x3270.png"><img alt="" class="wp-image-2139" height="64" src="https://blog.tenstral.net/wp-content/uploads/2026/09/x3270_x3270.png" style="width: 148px; height: auto;" width="64" /></a>Icon of <a href="https://x3270.bgp.nu/">x3270</a>, an IBM 3270 Terminal Emulator</figure>



<p class="wp-block-paragraph">For those, PNG can likely again exploit the repeating patterns, while a checkerboard layout is pretty bad for left/top predictors like JXL’s. However, in this case the size difference (and loss for JXL) is only 450 bytes, so even though JXL loses to PNG, it does so not by much.</p>



<h3 class="wp-block-heading">JXL in AppStream</h3>



<p class="wp-block-paragraph">Given these findings, JPEG-XL is the default image format starting with AppStream 1.2.0. AppStream Compose will encode all images losslessly as JXL, while screenshots are encoded in lossy mode at <code>Q=90 effort=7</code>. Since the optipng step does not happen for JXL images, this comes at no speed penalty and is even a bit faster on modern x86_64 CPUs (where libjxl can use SIMD). PNG is still available, and Compose can be told to switch between the two formats.</p>



<h4 class="wp-block-heading">Upsides of JXL in AppStream right now</h4>



<p class="wp-block-paragraph">If you use JXL in Compose or the recent release of <a href="https://github.com/ximion/appstream-generator">appstream-generator</a>, you will get much smaller images and, for screenshots, will benefit from other JPEG-XL features such as progressive decoding, providing a far nicer user experience. libAppStream has supported JXL icons since version 1.1.3, so your clients will need that version or a newer one, and all software centers will have to support loading JXL images (which all of them do, provided the right plugins are installed).</p>



<h4 class="wp-block-heading">Downsides of switching to JXL too quickly</h4>



<p class="wp-block-paragraph">JXL is a very new format, so web browsers might not yet display it if you are serving webpages. Your clients may also have bugs in processing JXL images, as the format is still “new”. For example, switching on JXL in Debian sent KDE Discover into an infinite loop on startup while trying to load the icons (an issue which <a href="https://invent.kde.org/frameworks/kimageformats/-/commit/458257b7a32417c995d3a95a65b38586eee7a91a">has been fixed</a>, but clients will need that patch first before JXL is switched on).</p>



<p class="wp-block-paragraph">This currently makes JXL enablement only possible when you <strong>know</strong> that your clients can support it. This is the case for me in Debian Unstable and Debian 14, which are using JXL images for a few weeks now, but not for any older releases. Platforms like Flatpak have it even harder, because they do know even less about their clients. So, even though it has big advantages, you may want to hold off on using JXL right away, and force PNG by setting the <code>ImageFormat</code> key to <code>png</code> in <code>appstream-generator</code>‘s configuration, or passing <code>--image-format=png</code> to <code>appstreamcli compose</code>.</p>



<p class="wp-block-paragraph">It is also worth mentioning that JPEG-XL is much, much slower on systems that do not have SIMD instructions or for which the libjxl/jxl-rs library does not have them (such as apparently riscv64 right now). If this is a concern, you might not want to switch to JXL right away.</p>



<h3 class="wp-block-heading">Media pipeline improvements </h3>



<p class="wp-block-paragraph">Besides the JXL default change, AppStream 1.2.0 also comes with a complete overhaul of its media processing pipeline. While <code>libappstream</code>, AppStream’s main library, does not do any media processing and comes with very minimal dependencies to be embedded in client applications and used on servers, the same can not be said about <code>libappstream-compose</code>, AppStream’s library to build metadata generating applications (the server-side part, usually).</p>



<p class="wp-block-paragraph">The <code>compose</code> library has to render fonts into font specimen cards, inspect translation files, render SVG images, decode all kinds of raster images, inspect video files, etc. Especially the fonts, and the fact that fonts can appear in SVG images, has caused issues in the past, as <code>libappstream-compose</code> is a heavily threaded library and most font libraries can only work from a single thread. This forced the library to essentially go into single-thread mode anytime anything that could touch a font was being processed.</p>



<p class="wp-block-paragraph">AppStream also originally was created for a “safe world” where applications were vetted by the distributors before their metadata was processed. This is increasingly not the case, so it made sense to put at least a few guardrails on the most complex part of the pipeline: The media processing. As part of the change, media processing was split out into a separate worker process. This solved two problems at once: Font handling was isolated in a single-threaded binary – if we wanted to handle fonts in parallel, we could simply spawn more workers. And, being in a separate process, the media processing could now be sandboxed.</p>



<p class="wp-block-paragraph">As part of the multiprocess changes, Compose also switched from using <a href="https://gitlab.gnome.org/GNOME/gdk-pixbuf/">GdkPixbuf</a> to <a href="https://github.com/libvips/libvips">VIPS</a> for image processing. The latter allows for much more fine-grained control over the image output and encoding, and comes with a lot of well-maintained filters and operations, which made it possible to eliminate a fair chunk of AppStream’s hand-rolled image processing operations. As part of this transition, we unfortunately lost the ability to read <a href="https://en.wikipedia.org/wiki/X_PixMap">XPM</a> images, which dropped about 20-30 applications from the pool at Debian. But in the name of security, this is a sensible choice, especially since most XPM icons were very small and low-resolution, and applications using them could benefit from adding a high-quality PNG icon anyway. With VIPS, we also now restrict the amount of image formats we can load to a sensible set, so extremely niche or unexpected formats will be outright rejected (this includes sane-but-unusual formats for screenshots and icons, such as <a href="https://de.wikipedia.org/wiki/Tagged_Image_File_Format">TIFF</a> images).</p>



<p class="wp-block-paragraph">The Compose library, with all of these changes, will now just request high-level operations (e.g. “render a font card for this font to a JXL image”) from the worker, and provide it with input data in sealed <a href="https://man7.org/linux/man-pages/man2/memfd_create.2.html">memfd</a>s and output locations as FDs as well. On Linux systems, the worker will use <a href="https://docs.kernel.org/userspace-api/landlock.html">Landlock</a> if available, to block all write access to the filesystem, deny device access and deny TCP and UDP as well. The sandbox can certainly be tightened a fair bit in future, but this was a good and safe start to gain some experience with it without having things break too easily, given the many places Compose is used in (also, Landlock’s API is surprisingly nice to use, so it was easier than I thought to add in this early version).</p>



<p class="wp-block-paragraph">With all of these changes, the <code>libappstream-compose</code> library is now also officially marked API-stable, so you should be able to rely on it in future to build new things (its API has barely changed in the past, and now with the new media API and defaults change in place, it was time to declare it stable).</p>



<h3 class="wp-block-heading">I want to see / try this!</h3>



<p class="wp-block-paragraph">Currently, the easiest way to have a look at the new data is to check out Debian Unstable. If you have a JXL-enabled browser, you can also see the icons in <a href="https://appstream.debian.org/sid/">AppStream Generator’s HTML pages for Debian Sid</a>. If you are using appstream-generator for your distribution, you will also get much more pleasant statistics and HTML pages, as well as fully deterministic media output and a whole bunch of security updates, so, update to its recent 1.0 release.</p>



<p class="wp-block-paragraph">Please keep in mind that if you switch to JXL, the client tools receiving the image data have to support it. Support varies depending on the Linux distribution, so, test it first and switch the default back to PNG in case you encounter any issues.</p>



<h3 class="wp-block-heading">What’s next?</h3>



<p class="wp-block-paragraph">With so many features and changes landed, the next changes in AppStream will focus on improving what already exists and fixing any issues (there will be more blogposts about the other features 1.2.x delivers!). Testing with the entire Debian archive as data source makes me fairly confident though that there will not be many problems. In the longer term, tightening the media processing sandbox will also be something we might want to do, e.g. by hiding parts of the filesystem tree or filtering syscalls.</p>



<p class="wp-block-paragraph">For JPEG-XL, one obvious question is “Will you add support for it to the Freedesktop icon-theme specification as supported format alongside PNG, SVG(Z), and XPM?”. For on-disk icon repositories, JXL’s space-savings are less compelling, and it being HDR-capable is also not necessarily a killer feature (PNG can go a long way!). However, JPEG-XL’s ability to immediately decode larger images at reduced resolution without resampling could legitimately be very powerful here, as applications could ship a single large image and quickly decode it at 1/2, 1/4 or 1/8 the size for different purposes in their UI. JPEG-XL also supports spot-color extra channels, which applications could use as masks to recolor raster icons at render time. This could be incredibly nice to color symbolic icons on-the-fly without any SVG and CSS. JXL also provides richer metadata, which might be neat for (license/author) documentation. So, the answer here is: Maybe it makes sense to allow another format, but this will have to be discussed first, as it would force JXL into every toolkit and desktop, which is a much bigger ask than supporting it only in AppStream.</p>



<p class="wp-block-paragraph">As always, let me know what you think and please report any issues or bugs directly against <a href="https://github.com/ximion/appstream">AppStream</a> or <a href="https://github.com/ximion/appstream-generator">AppStream Generator</a> if you encounter problems that are with the tools, and not with a project’s metadata.</p>
       </div>
       <p class="date">
        <a href="https://blog.tenstral.net/2026/09/jpeg-xl-appstream-and-better-media-processing.html">10 September, 2026 05:48PM</a>
        
         by Matthias
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <a href="http://dirk.eddelbuettel.com/blog">
      <img class="face" src="heads/dirk.png"
            width="65" height="90"
            alt="hackergotchi for Dirk Eddelbuettel" />
     </a>
      
     <h2 class="planet-title">
      <a href="http://dirk.eddelbuettel.com/blog" title="Thinking inside the box">Dirk Eddelbuettel</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://dirk.eddelbuettel.com/blog/2026/09/10#rdieharder_0.2.8_hide\" onClick=\"exclude( 'http://dirk.eddelbuettel.com/blog/2026/09/10#rdieharder_0.2.8' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://dirk.eddelbuettel.com/blog/2026/09/10#rdieharder_0.2.8_show\"  style=\"display:none;\" onClick=\"show( 'http://dirk.eddelbuettel.com/blog/2026/09/10#rdieharder_0.2.8' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://dirk.eddelbuettel.com/blog/2026/09/10#rdieharder_0.2.8" class="http://dirk.eddelbuettel.com/blog/2026/09/10#rdieharder_0.2.8">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/10#rdieharder_0.2.8">RDieHarder 0.2.8 on CRAN: Minor Maintenance</a>
       </h3>
       <div class="content">
        <p>An new maintenance version 0.2.8 of the random-number generator
tester <a href="https://dirk.eddelbuettel.com/code/rdieharder.html">RDieHarder</a>
(based on the <a href="http://webhome.phy.duke.edu/~rgb/General/dieharder.php">DieHarder</a>
suite developed / maintained by Robert Brown with contributions by David
Bauer and myself along with other contributors) is now on <a href="https://cran.r-project.org">CRAN</a> and available via <a href="https://eddelbuettel.github.io/r2u/">r2u</a>.</p>
<p>This release contains only internal maintenance changes: continuous
integration was updated a few times, newer nags from R are addressed in
Rd files and the vignette, and we also updated a few URLs in the
vignette and README.me. No new code, no new features.</p>
<p>Thanks to <a href="https://dirk.eddelbuettel.com/cranberries/">CRANberries</a>, you
can also look at the most recent <a href="https://dirk.eddelbuettel.com/cranberries/2026/09/10#RDieHarder_0.2.8">diff
to the previous release</a>.</p>
<p style="font-size: 80%; font-style: italic;">
This post by <a href="https://dirk.eddelbuettel.com">Dirk
Eddelbuettel</a> originated on his <a href="https://dirk.eddelbuettel.com/blog/">Thinking inside the box</a>
blog. If you like this or other open-source work I do, you can now <a href="https://github.com/sponsors/eddelbuettel">sponsor me at
GitHub</a>.
</p><p></p>
       </div>
       <p class="date">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/10#rdieharder_0.2.8">10 September, 2026 05:38PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <a href="https://www.decadent.org.uk/ben/blog/">
      <img class="face" src="heads/benh.png"
            width="109" height="100"
            alt="hackergotchi for Ben Hutchings" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://www.decadent.org.uk/ben/blog/" title="Better living through software">Ben Hutchings</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.decadent.org.uk/ben/blog/2026/09/10/foss-activity-in-august-2026.html_hide\" onClick=\"exclude( 'https://www.decadent.org.uk/ben/blog/2026/09/10/foss-activity-in-august-2026.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.decadent.org.uk/ben/blog/2026/09/10/foss-activity-in-august-2026.html_show\"  style=\"display:none;\" onClick=\"show( 'https://www.decadent.org.uk/ben/blog/2026/09/10/foss-activity-in-august-2026.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.decadent.org.uk/ben/blog/2026/09/10/foss-activity-in-august-2026.html" class="https://www.decadent.org.uk/ben/blog/2026/09/10/foss-activity-in-august-2026.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.decadent.org.uk/ben/blog/2026/09/10/foss-activity-in-august-2026.html">FOSS activity in August 2026</a>
       </h3>
       <div class="content">
        <p>There’s not a whole lot to report here.  During August I spent some
time on holiday and also had less work time available for Debian LTS.</p>

<ul>
  <li>Debian packages:
    <ul>
      <li><a href="https://tracker.debian.org/pkg/firmware-nonfree">firmware-nonfree</a>:
        <ul>
          <li><a href="https://bugs.debian.org/src:firmware-nonfree">Bugs</a>:
            <ul>
              <li>replied to <a href="https://bugs.debian.org/1143446">#1143446: firmware-nonfree: split Lontium firmware into a separate binary package</a></li>
            </ul>
          </li>
          <li>Merge requests:
            <ul>
              <li>closed <a href="https://salsa.debian.org/kernel-team/firmware-nonfree/-/merge_requests/152">!152: lontium: create new package containing firmware for Lontium display bridge devices</a></li>
              <li>opened and merged <a href="https://salsa.debian.org/kernel-team/firmware-nonfree/-/merge_requests/153">!153: Update to 20260810</a></li>
            </ul>
          </li>
          <li><a href="https://tracker.debian.org/pkg/firmware-nonfree/news/">Uploads</a>:
            <ul>
              <li>uploaded version 20260810-1 to unstable</li>
            </ul>
          </li>
        </ul>
      </li>
      <li><a href="https://tracker.debian.org/pkg/linux">linux</a>:
        <ul>
          <li><a href="https://bugs.debian.org/src:linux">Bugs</a>:
            <ul>
              <li>replied to <a href="https://bugs.debian.org/1141407">#1141407: linux-image-amd64: hid-generic 0003:051D:0002.0002: Event data for report 22 was too short (4 vs 2)</a></li>
              <li>closed <a href="https://bugs.debian.org/1143619">#1143619: linux-image-686: metapackage missing for 6.1.180-1 on 686</a> (LTS)</li>
              <li>closed <a href="https://bugs.debian.org/1144056">#1144056: linux-image-amd64: missing CONFIG_CRYPTO_SHA384 causes IMA error</a></li>
            </ul>
          </li>
          <li>Merge requests:
            <ul>
              <li>merged <a href="https://salsa.debian.org/kernel-team/linux/-/merge_requests/2039">!2039: Add support for Alcor Micro AU6625 PCI-E Flash card reader</a></li>
              <li>merged <a href="https://salsa.debian.org/kernel-team/linux/-/merge_requests/2041">!2041: Update to 5.10.262</a> (LTS)</li>
              <li>merged <a href="https://salsa.debian.org/kernel-team/linux/-/merge_requests/2042">!2042: 6.1 stable updates</a> (LTS)</li>
              <li>merged <a href="https://salsa.debian.org/kernel-team/linux/-/merge_requests/2048">!2048: Update to 6.1.180</a> (LTS)</li>
              <li>merged <a href="https://salsa.debian.org/kernel-team/linux/-/merge_requests/2050">!2050: 6.1 stable updates</a> (LTS)</li>
              <li>reviewed <a href="https://salsa.debian.org/kernel-team/linux/-/merge_requests/2059">!2059: [arm64] Enable CONFIG_SND_SOC_ES8328_I2C as module</a></li>
              <li>reviewed and merged <a href="https://salsa.debian.org/kernel-team/linux/-/merge_requests/2063">!2063: 6.1 stable updates</a> (LTS)</li>
              <li>merged <a href="https://salsa.debian.org/kernel-team/linux/-/merge_requests/2068">!2068: 6.1 stable updates</a> (LTS)</li>
            </ul>
          </li>
          <li><a href="https://tracker.debian.org/pkg/linux/news/">Uploads</a>:
            <ul>
              <li>uploaded version 6.1.180-1 to bookworm-security (LTS)</li>
              <li>uploaded version 7.1.7-1~bpo13+1 to trixie-backports</li>
              <li>uploaded version 7.1.8-1~bpo13+1 to trixie-backports</li>
            </ul>
          </li>
        </ul>
      </li>
      <li><a href="https://tracker.debian.org/pkg/linux-6.12">linux-6.12</a> (LTS):
        <ul>
          <li><a href="https://tracker.debian.org/pkg/linux-6.12/news/">Uploads</a>:
            <ul>
              <li>uploaded version 6.12.100-1~deb12u1 to bookworm-security</li>
              <li>uploaded version 6.12.101-1~deb12u1 to bookworm-security</li>
            </ul>
          </li>
        </ul>
      </li>
      <li><a href="https://tracker.debian.org/pkg/linux-base">linux-base</a>:
        <ul>
          <li>Merge requests:
            <ul>
              <li>merged <a href="https://salsa.debian.org/kernel-team/linux-base/-/merge_requests/22">!22: Also copy config file to /boot</a></li>
              <li>merged <a href="https://salsa.debian.org/kernel-team/linux-base/-/merge_requests/23">!23: New hooks ignore all calls by unpackaged kernels</a></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Mailing lists:
    <ul>
      <li><a href="https://lists.debian.org/debian-kernel/">debian-kernel</a>:
        <ul>
          <li>posted <a href="https://lists.debian.org/b844e73d47b44cbf08be2664e26fc2393038346a.camel@decadent.org.uk">Agenda items for kernel-team meeting on 2026-08-19</a></li>
          <li>posted <a href="https://lists.debian.org/83730f8bd7aac43fb90af084984a84c35a177e57.camel@decadent.org.uk">Opaque tables of numbers in r8169 driver</a></li>
        </ul>
      </li>
      <li><a href="https://lists.debian.org/debian-lts-announce/">debian-lts-announce</a>:
        <ul>
          <li>posted <a href="https://lists.debian.org/anNgZasHL66gqrCC@decadent.org.uk">[SECURITY] [DLA 4720-1] linux security update</a></li>
          <li>posted <a href="https://lists.debian.org/anWlW0WUJpgxfCMc@decadent.org.uk">[SECURITY] [DLA 4724-1] linux-6.12 new package</a></li>
          <li>posted <a href="https://lists.debian.org/aoQ5HmDwwEXKCs73@decadent.org.uk">[SECURITY] [DLA 4745-1] linux-6.12 security update</a></li>
        </ul>
      </li>
      <li><a href="https://lore.kernel.org/linux-input/">linux-input</a>:
        <ul>
          <li>posted <a href="https://lore.kernel.org/linux-input/aoF-jIn9gk9kJ7Pz@decadent.org.uk/T/">[PATCH] HID: core: demote warning to debug level</a></li>
        </ul>
      </li>
    </ul>
  </li>
</ul>
       </div>
       <p class="date">
        <a href="https://www.decadent.org.uk/ben/blog/2026/09/10/foss-activity-in-august-2026.html">10 September, 2026 01:57PM</a>
        
         by Ben Hutchings
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://raju.dev/" title="raju.dev">Raju Devidas</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://raju.dev/installing-ubuntu-on-intel-macbook-pro-2017-with-touchbar/_hide\" onClick=\"exclude( 'https://raju.dev/installing-ubuntu-on-intel-macbook-pro-2017-with-touchbar/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://raju.dev/installing-ubuntu-on-intel-macbook-pro-2017-with-touchbar/_show\"  style=\"display:none;\" onClick=\"show( 'https://raju.dev/installing-ubuntu-on-intel-macbook-pro-2017-with-touchbar/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://raju.dev/installing-ubuntu-on-intel-macbook-pro-2017-with-touchbar/" class="https://raju.dev/installing-ubuntu-on-intel-macbook-pro-2017-with-touchbar/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://raju.dev/installing-ubuntu-on-intel-macbook-pro-2017-with-touchbar/">Installing Ubuntu on intel Macbook Pro 2017 with touchbar</a>
       </h3>
       <div class="content">
        <img alt="Installing Ubuntu on intel Macbook Pro 2017 with touchbar" src="https://raju.dev/content/images/2026/09/Screenshot-From-2026-09-10-17-07-04.png" /><p>Just some notes about fixing some issues while installing Ubuntu on Macbook Pro 2017</p><h3 id="fix-audio">Fix Audio</h3><p>Audio is not working by default after a fresh install. </p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/davidjo/snd_hda_macbookpro?ref=raju.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - davidjo/snd_hda_macbookpro: Kernel audio driver for Macs with 8409 HDA chip + MAX98706/SSM3515 amps</div><small><div class="kg-bookmark-description">Kernel audio driver for Macs with 8409 HDA chip + MAX98706/SSM3515 amps - davidjo/snd_hda_macbookpro</div></small></div></a></figure><pre><code>johndoe@mac ~&gt; sudo apt install gcc linux-headers-generic make patch wget

johndoe@mac ~&gt; sudo apt install linux-source-7.0.0

johndoe@mac ~/dev&gt; git clone https://github.com/davidjo/snd_hda_macbookpro.git

johndoe@mac ~/dev&gt; cd snd_hda_macbookpro/

johndoe@mac ~/d/snd_hda_macbookpro (master)&gt; sudo ./install.cirrus.driver.sh


johndoe@mac ~/d/snd_hda_macbookpro (master)&gt; sudo reboot
</code></pre>
<p></p><h3 id="fix-touchbar">Fix Touchbar</h3><pre><code>
&gt; sudo apt install git dkms build-essential linux-headers-$(uname -r)

&gt; git clone https://github.com/AJ-dev-i60/t1-touchbar.git
&gt; cd t1-touchbar

&gt; sudo ./install.sh
&gt; sudo reboot
</code></pre>
<h3 id="fix-wi-fi">Fix Wi-Fi</h3><p>Wifi actually works out of the box, but the signal strength is usually very bad. We&amp;aposll try to fix that</p><pre><code>johndoe@mac ~&gt; cd /tmp
               wget -O brcmfmac43602-pcie.txt \
                     https://raw.githubusercontent.com/jsoyer/MacBookPro14-2/main/firmware/brcm/brcmfmac43602-pcie.txt
--2026-09-10 18:36:39--  https://raw.githubusercontent.com/jsoyer/MacBookPro14-2/main/firmware/brcm/brcmfmac43602-pcie.txt
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.111.133, 185.199.110.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... failed: Connection timed out.
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.111.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6051 (5.9K) [text/plain]
Saving to: ‘brcmfmac43602-pcie.txt’

brcmfmac43602-pcie.txt    100%[=====================================&gt;]   5.91K  --.-KB/s    in 0.04s   

2026-09-10 18:38:53 (163 KB/s) - ‘brcmfmac43602-pcie.txt’ saved [6051/6051]



johndoe@mac /tmp&gt; sed -i &amp;aposs/^macaddr=.*/macaddr=&lt;your-wi-fi-cards-mac-id&gt;/&amp;apos brcmfmac43602-pcie.txt


johndoe@mac /tmp&gt; sudo cp /tmp/brcmfmac43602-pcie.txt \
                        "/lib/firmware/brcm/brcmfmac43602-pcie.Apple Inc.-MacBookPro14,2.txt"

johndoe@mac /tmp&gt; sudo ln -sf \
                        "brcmfmac43602-pcie.Apple Inc.-MacBookPro14,2.txt" \
                        /lib/firmware/brcm/brcmfmac43602-pcie.txt
                     
                     
                     
johndoe@mac /tmp&gt; sudo reboot


</code></pre>
       </div>
       <p class="date">
        <a href="https://raju.dev/installing-ubuntu-on-intel-macbook-pro-2017-with-touchbar/">10 September, 2026 01:37PM</a>
        
         by Raju Vindane
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://www.eyrie.org/~eagle/" title="Eagle's Path">Russ Allbery</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.eyrie.org/~eagle/journal/2026-09/001.html_hide\" onClick=\"exclude( 'https://www.eyrie.org/~eagle/journal/2026-09/001.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.eyrie.org/~eagle/journal/2026-09/001.html_show\"  style=\"display:none;\" onClick=\"show( 'https://www.eyrie.org/~eagle/journal/2026-09/001.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.eyrie.org/~eagle/journal/2026-09/001.html" class="https://www.eyrie.org/~eagle/journal/2026-09/001.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.eyrie.org/~eagle/journal/2026-09/001.html">podlators v6.1.1</a>
       </h3>
       <div class="content">
        <p>
podlators is the package containing Pod::Man, Pod::Text, and other
tools for converting POD documentation into manual pages and simple
text documents.
</p>

<p>
This release fixes a long-standing bug in Pod::Text and subclasses
where a pathological level of indentation could cause the wrapping
code to go into an infinite loop. Thanks to Jitka Plesnikova for
the report. This was assigned CVE-2026-82560, although I make no
guarantees that podlators is safe to run on untrusted input and
therefore not fully treating this like a security issue.
</p>

<p>
While fixing that bug, I noticed a bug in Pod::Text::Overstrike's
wrapping code that would leave stray formatting at the start of the
next line in some situations. That is also fixed in this release.
</p>

<p>
You can get the current podlators release from CPAN or from the
<a href="https://www.eyrie.org/~eagle/software/podlators/">podlators distribution page</a>.
</p>
       </div>
       <p class="date">
        <a href="https://www.eyrie.org/~eagle/journal/2026-09/001.html">10 September, 2026 02:28AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 09, 2026</h1>
     

    <div class="channel">
     
      
     <a href="https://codon.org.uk/~mjg59/blog/post/">
      <img class="face" src="heads/mjg59.png"
            width="69" height="85"
            alt="hackergotchi for Matthew Garrett" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://codon.org.uk/~mjg59/blog/post/" title="Posts on Matthew Garrett's Blog">Matthew Garrett</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://codon.org.uk/~mjg59/blog/p/systemio-conflicts-are-not-firmware-bugs/_hide\" onClick=\"exclude( 'https://codon.org.uk/~mjg59/blog/p/systemio-conflicts-are-not-firmware-bugs/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://codon.org.uk/~mjg59/blog/p/systemio-conflicts-are-not-firmware-bugs/_show\"  style=\"display:none;\" onClick=\"show( 'https://codon.org.uk/~mjg59/blog/p/systemio-conflicts-are-not-firmware-bugs/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://codon.org.uk/~mjg59/blog/p/systemio-conflicts-are-not-firmware-bugs/" class="https://codon.org.uk/~mjg59/blog/p/systemio-conflicts-are-not-firmware-bugs/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://codon.org.uk/~mjg59/blog/p/systemio-conflicts-are-not-firmware-bugs/">SystemIO conflicts are not firmware bugs</a>
       </h3>
       <div class="content">
        <p>I’m looking at something entirely unrelated, but tripped over some search results that made me realise that a lot of people still think getting errors like <code>ACPI Warning: SystemIO range 0x0000000000001828-0x000000000000182F conflicts with OpRegion 0x0000000000001800-0x000000000000187F</code> indicate a firmware bug. This is generally untrue. We need to dive a little into what ACPI is to clarify why.</p>
<p>The <a class="link" href="https://uefi.org/specifications" rel="noopener" target="_blank">Advanced Configuration and Power Interface</a><sup id="fnref:1"><a class="footnote-ref" href="https://codon.org.uk/~mjg59/blog/index.xml#fn:1">1</a></sup> specification defines a whole bunch of stuff, but what’s interesting to us here is the hardware abstraction it performs. While PCs are nominally a well-defined platform that’s really not true at the hardware level once you get beyond a certain level of complexity. When you suspend a system you want to power down the hardware in the correct order, for instance, and knowing what that order is requires you to know details about the specific motherboard design. The approach taken in the embedded world is to just bake that knowledge into the OS in some form, which is how we end up with <a class="link" href="https://devicetree.org" rel="noopener" target="_blank">Devicetree</a>. ACPI takes an alternative approach - rather than provide that information as data that has to be consumed by OS drivers, it distributes it as code.</p>
<p>The ACPI Source Language, or ASL, is a simple language that gets compiled into a bytecode that’s then interpreted by the OS at runtime. One of the features of this language is the ability to define “Operation Regions”, effectively structure definitions that describe access to underlying hardware. Let’s imagine a simple device with two exposed registers. The first is an index register - it describes which internal register we want to access. The second is a data register, where reading it gives us the value of the internal register whose address is currently in the index register, and writing to it modifies that register. An example operation region declaration would look something like</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tbody><tr><td class="lntd">
<pre class="chroma" tabindex="0"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre class="chroma" tabindex="0"><code class="language-fallback"><span class="line"><span class="cl">OperationRegion(OPR1, SystemIO, 0x400, 0x2)
</span></span><span class="line"><span class="cl">Field(OPR1, ByteAcc, NoLock, Preserve)
</span></span><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">  INDX, 8
</span></span><span class="line"><span class="cl">  DATA, 8
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></tbody></table>
</div>
</div><p>This defines an operation region called “OPR1” at IO port 0x400, 2 bytes long. Inside it are two 8-bit fields, INDX and DATA. These are to be accessed one at a time, do not need the ACPI interpreter to take a global lock when accessing them, and if a subset of the register is modified then the other values should be preserved (irrelevant in this case since the fields are only a byte wide). Now any references to INDX or DATA in this scope will trigger accesses to those registers. So, a method to read the value of register 0x03 would look something like:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tbody><tr><td class="lntd">
<pre class="chroma" tabindex="0"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma" tabindex="0"><code class="language-fallback"><span class="line"><span class="cl">Method (RD03) {
</span></span><span class="line"><span class="cl">  INDX = 0x3
</span></span><span class="line"><span class="cl">  Return (DATA)
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></tbody></table>
</div>
</div><p>ie, set INDX to 3, and then read the value of DATA and return it. But! What if another ACPI method is running at the same time? Let’s say we have one that writes to register 0x05:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tbody><tr><td class="lntd">
<pre class="chroma" tabindex="0"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma" tabindex="0"><code class="language-fallback"><span class="line"><span class="cl">Method (WR05, 1) {
</span></span><span class="line"><span class="cl">  INDX = 0x05
</span></span><span class="line"><span class="cl">  DATA = Arg1
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></tbody></table>
</div>
</div><p>What happens if RD03 executes while we’re part-way through WR05? INDX might get reset to 0x03, and now WR05 will modify register 0x03 instead of 0x05. Oh no! But we can avoid this - we declare a mutex (<code>Mutex (MUTX, 0x00)</code>), and update our methods to be something like:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tbody><tr><td class="lntd">
<pre class="chroma" tabindex="0"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre class="chroma" tabindex="0"><code class="language-fallback"><span class="line"><span class="cl">Method (RD03) {
</span></span><span class="line"><span class="cl">  Acquire (MUTX, 0xFFFF)
</span></span><span class="line"><span class="cl">  INDX = 0x3
</span></span><span class="line"><span class="cl">  Local0 = DATA
</span></span><span class="line"><span class="cl">  Release (MUTX)
</span></span><span class="line"><span class="cl">  Return (Local0)
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Method (WR05, 1) {
</span></span><span class="line"><span class="cl">  Acquire (MUTX, 0xFFFF)
</span></span><span class="line"><span class="cl">  INDX = 0x05
</span></span><span class="line"><span class="cl">  DATA = Arg1
</span></span><span class="line"><span class="cl">  Release (MUTX)
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></tbody></table>
</div>
</div><p>Each method takes a lock (waiting up to 0xffff milliseconds and then erroring out if it doesn’t), and performs the access. There’s now no chance of a race. Phew!</p>
<p>Now suppose someone writes a Linux driver for this piece of hardware. It accesses the hardware directly, with no knowledge of ACPI. What stops the driver from racing against one of the ACPI access methods? Nothing at all. Oh no! Again! This isn’t hypothetical, by the way - <a class="link" href="https://bugzilla.kernel.org/show_bug.cgi?id=13620" rel="noopener" target="_blank">here’s</a> a relatively harmless example, but back in the day we did trip over cases where temperature monitoring chips would be accessed by the firmware and Linux simultaneously and as a result you might end up thinking you’re reading a temperature when you’re actually reading a status flag, resulting in an impossibly high temperature and an immediate thermal shutdown.</p>
<p>In this case, the kernel saves you from this (potentially hardware damaging) outcome by printing a message like <code>ACPI Warning: SystemIO range 0x0000000000000400-0x000000000000401 conflicts with OpRegion 0x0000000000000400-0x0000000000000401 (OPR1)</code>, telling you that the kernel has detected that a driver is attempting to allocate IO ports 0x400-0x401, but that there’s an ACPI operation region called OPR1 that is claiming the same addresses. The kernel isn’t in a position to know what type of access the firmware might perform in that region, so assumes that it might be dangerous and blocks the driver from loading.</p>
<p>But all is not lost! The kernel also prints some helpful advice, <code>ACPI: If an ACPI driver is available for this device, you should use it instead of the native driver</code>. And ACPI tables will often actually have a definition that looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tbody><tr><td class="lntd">
<pre class="chroma" tabindex="0"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre class="chroma" tabindex="0"><code class="language-fallback"><span class="line"><span class="cl">Device (HDW1)
</span></span><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">  Name (_HID, "VEND0001")
</span></span><span class="line"><span class="cl">  OperationRegion(OPR1, SystemIO, 0x400, 0x2)
</span></span><span class="line"><span class="cl">  Field(OPR1, ByteAcc, NoLock, Preserve)
</span></span><span class="line"><span class="cl">  {
</span></span><span class="line"><span class="cl">    INDX, 8
</span></span><span class="line"><span class="cl">    DATA, 8
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">  Mutex (MUTX, 0)
</span></span><span class="line"><span class="cl">  Method (RD03) {
</span></span><span class="line"><span class="cl">    Acquire (MUTX, 0xFFFF)
</span></span><span class="line"><span class="cl">    INDX = 0x3
</span></span><span class="line"><span class="cl">    Local0 = DATA
</span></span><span class="line"><span class="cl">    Release (MUTX)
</span></span><span class="line"><span class="cl">    Return (Local0)
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  Method (WR05, 1) {
</span></span><span class="line"><span class="cl">    Acquire (MUTX, 0xFFFF)
</span></span><span class="line"><span class="cl">    INDX = 0x05
</span></span><span class="line"><span class="cl">    DATA = Arg1
</span></span><span class="line"><span class="cl">    Release (MUTX)
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></tbody></table>
</div>
</div><p>which defines an ACPI device and associated methods. The <code>_HID</code> field defines the device type, and a Linux driver can be written that will be automatically loaded if a device with type <code>VEND0001</code> is seen. That driver can then call ACPI methods associated with the device and access the resources in a way that matches the firmware’s expectations.</p>
<p>(Interested in writing such a driver? I wrote <a class="link" href="https://lwn.net/Articles/367630/" rel="noopener" target="_blank">a guide</a> back in 2009)</p>
<p>The firmware did absolutely nothing wrong here<sup id="fnref:2"><a class="footnote-ref" href="https://codon.org.uk/~mjg59/blog/index.xml#fn:2">2</a></sup>, but trying to load the native driver will generate an error and the internet will tell you that PC firmware developers are incompetent<sup id="fnref:3"><a class="footnote-ref" href="https://codon.org.uk/~mjg59/blog/index.xml#fn:3">3</a></sup> and you should pass a kernel argument that overrides this behaviour and it never did them any harm, and it probably won’t do you any harm either but it <em>might</em> and you might never know why your system occasionally wedges or catches fire.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>The ACPI spec used to live at <code>acpi.info</code>, but sadly that seems to have vanished some time after UEFI took over stewardship of the spec <a class="footnote-backref" href="https://codon.org.uk/~mjg59/blog/index.xml#fnref:1">↩︎</a></p>
</li>
<li id="fn:2">
<p>You might argue that the firmware should simply not do anything at runtime because it is not the firmware’s job to do that, and I do understand that and you can certainly boot with <code>acpi=off</code> if you want to and no ACPI code will be executed at runtime. Let me know how that goes. <a class="footnote-backref" href="https://codon.org.uk/~mjg59/blog/index.xml#fnref:2">↩︎</a></p>
</li>
<li id="fn:3">
<p>I’m not going to present an opinion on that here, merely say that <em>this</em> provides no supporting evidence for that assertion <a class="footnote-backref" href="https://codon.org.uk/~mjg59/blog/index.xml#fnref:3">↩︎</a></p>
</li>
</ol>
</div>
       </div>
       <p class="date">
        <a href="https://codon.org.uk/~mjg59/blog/p/systemio-conflicts-are-not-firmware-bugs/">09 September, 2026 06:15PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <a href="http://dirk.eddelbuettel.com/blog">
      <img class="face" src="heads/dirk.png"
            width="65" height="90"
            alt="hackergotchi for Dirk Eddelbuettel" />
     </a>
      
     <h2 class="planet-title">
      <a href="http://dirk.eddelbuettel.com/blog" title="Thinking inside the box">Dirk Eddelbuettel</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://dirk.eddelbuettel.com/blog/2026/09/09#rcppxts_0.0.7_hide\" onClick=\"exclude( 'http://dirk.eddelbuettel.com/blog/2026/09/09#rcppxts_0.0.7' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://dirk.eddelbuettel.com/blog/2026/09/09#rcppxts_0.0.7_show\"  style=\"display:none;\" onClick=\"show( 'http://dirk.eddelbuettel.com/blog/2026/09/09#rcppxts_0.0.7' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://dirk.eddelbuettel.com/blog/2026/09/09#rcppxts_0.0.7" class="http://dirk.eddelbuettel.com/blog/2026/09/09#rcppxts_0.0.7">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/09#rcppxts_0.0.7">RcppXts 0.0.7 on CRAN: Minor Maintenance</a>
       </h3>
       <div class="content">
        <p>A new maintenance release 0.0.7 of <a href="https://github.com/eddelbuettel/rcppxts">RcppXts</a> is now on <a href="https://cran.r-project.org">CRAN</a>, and has been built for <a href="https://eddelbuettel.github.io/r2u/">r2u</a>. The <a href="https://github.com/eddelbuettel/rcppxts">RcppXts</a> package
demonstrates how to access the export C API of <a href="https://cran.r-project.org/package=xts">xts</a> which we
contributed a looong time ago. There are by now a more example packages
around this C level access to another package, but this one was an early
example.</p>
<p>This release is strictly maintenance, updating continuous
integration, the README.md file and other packaging conventions adopted
since the last release four years ago.</p>
<p>The NEWS entries follow.</p>
<blockquote>
<h4 id="changes-in-version-0.0.7-2026-09-09">Changes in version 0.0.7
(2026-09-09)</h4>
<ul>
<li><p>Corrected a docstring for the module</p></li>
<li><p>Updated continuous integration setup several times</p></li>
<li><p>Simplified setup by removing no-longer-needed Makevars</p></li>
<li><p>Added badges to README.md</p></li>
</ul>
</blockquote>
<p>Courtesy of my <a href="https://dirk.eddelbuettel.com/cranberries/">CRANberries</a>, there
is also a diffstat report for <a href="https://dirk.eddelbuettel.com/cranberries/2026/09/09#RcppXts_0.0.7">this
release</a>. For questions, suggestions, or issues please use the <a href="https://github.com/eddelbuettel/rcppxts/issues">issue tracker</a>
at the <a href="https://github.com/eddelbuettel/rcppxts">GitHub
repo</a>.</p>
<p style="font-size: 80%; font-style: italic;">
This post by <a href="https://dirk.eddelbuettel.com">Dirk
Eddelbuettel</a> originated on his <a href="https://dirk.eddelbuettel.com/blog/">Thinking inside the box</a>
blog. If you like this or other open-source work I do, you can now <a href="https://github.com/sponsors/eddelbuettel">sponsor me at
GitHub</a>.
</p><p></p>
       </div>
       <p class="date">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/09#rcppxts_0.0.7">09 September, 2026 04:41PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="http://blog.alteholz.eu" title="planetdebian – blog.alteholz.eu">Thorsten Alteholz</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://blog.alteholz.eu/2026/09/my-debian-activities-in-august-2026/_hide\" onClick=\"exclude( 'http://blog.alteholz.eu/2026/09/my-debian-activities-in-august-2026/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://blog.alteholz.eu/2026/09/my-debian-activities-in-august-2026/_show\"  style=\"display:none;\" onClick=\"show( 'http://blog.alteholz.eu/2026/09/my-debian-activities-in-august-2026/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://blog.alteholz.eu/2026/09/my-debian-activities-in-august-2026/" class="http://blog.alteholz.eu/2026/09/my-debian-activities-in-august-2026/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://blog.alteholz.eu/2026/09/my-debian-activities-in-august-2026/">My Debian Activities in August 2026</a>
       </h3>
       <div class="content">
        <h3><strong>Debian LTS/ELTS</strong></h3>



<p></p><p>This was my hundred-forty-sixth month that I did some work for the Debian LTS initiative, started by Raphael Hertzog at Freexian.
</p>
<p>
Unfortunately the number of distributed working hours had been rather low this month, so the list contains much less entries than normal. During my allocated time I uploaded or worked on:  
</p><p></p>



<ul><li>[<a href="https://lists.debian.org/debian-lts-announce/2026/08/msg00004.html">DLA 4713-1</a>]  sslh security update to fix one CVE in Bookworm related to a so-called “link following”
vulnerability.</li><li>[<a href="https://lists.debian.org/debian-lts-announce/2026/08/msg00003.html">DLA 4714-1</a>] libmodbus security update to fix one CVEs in Bookworm related to a stack-based Buffer Overflow vulnerability. 
</li><li>[<a href="https://lists.debian.org/debian-lts-announce/2026/07/msg00031.html">DLA 4715-1</a>]  kissfft security update to fix two CVEs in Bookworm related to integer overflows.
</li></ul>



<p>
Last but not least I spent days with FD work at the beginning of the month. The last remaining hours I continued to work on <i>cups</i> and <i>hplip</i>. Unfortunately this work did not result in an upload yet.
</p>



<h3><strong>Debian Printing</strong></h3>



<p>This month I did not upload any package but just worked on some bugs.</p>



<p><strong>This work is generously funded by <a href="https://www.freexian.com">Freexian</a>!</strong></p>



<h3><strong>Debian Lomiri</strong></h3>



<p>This month I worked on new Lomiri Apps. Due to a broken disk, the progress was not as expected. But stay tuned!</p>



<p>Not really related to Lomiri, but to Debian EDU, I fixed an incus related bug in <i>sitesummary</i>.
</p>



<p><strong>This work is generously funded by <a href="https://freiesoftware.gmbh/">Fre(i)e Software GmbH</a>!</strong></p>



<h3><strong>Debian Astro</strong></h3>



<p>This month I uploaded a new upstream version  or a bugfix version of:</p>



<ul><li>… <a href="https://tracker.debian.org/einsteinpy">einsteinpy</a> to unstable, fixing a dependency bug.</li><li>… <a href="https://tracker.debian.org/supernovas">supernovas</a> to unstable, sponsored upload of a new upstream version.</li></ul>



<h3><strong>Debian IoT</strong></h3>



<p>Unfortunately I had no time to work in this category this month.</p>



<h3><strong>Debian Mobcom</strong></h3>



<p>This month I uploaded a new upstream version  or a bugfix version of:</p>



<ul><li>… <a href="https://tracker.debian.org/libosmocore">libosmocore</a> to unstable.</li><li>… <a href="https://tracker.debian.org/osmocom-analog">osmocom-analog</a> to unstable.</li><li>… <a href="https://tracker.debian.org/libosmo-netif">libosmo-netif</a> to unstable.</li><li>… <a href="https://tracker.debian.org/osmo-trx">osmo-trx</a> to unstable.</li><li>… <a href="https://tracker.debian.org/libosmo-abis">libosmo-abis</a> to unstable.</li><li>… <a href="https://tracker.debian.org/libosmo-sigtran">libosmo-sigtran</a> to experimental.</li><li>… <a href="https://tracker.debian.org/osmo-mgw">osmo-mgw</a> to unstable.</li><li>… <a href="https://tracker.debian.org/osmo-bsc">osmo-bsc</a> to experimental.</li><li>… <a href="https://tracker.debian.org/osmo-sgsn">osmo-sgsn</a> to experimental.</li><li>… <a href="https://tracker.debian.org/osmo-pcu">osmo-pcu</a> to unstable.</li><li>… <a href="https://tracker.debian.org/osmo-hlr">osmo-hlr</a> to unstable.</li><li>… <a href="https://tracker.debian.org/osmo-tetra">osmo-tetra</a> to unstable.</li></ul>



<h3><strong>misc</strong></h3>



<p>This month I uploaded a new upstream version  or a bugfix version of:</p>



<ul><li>… <a href="https://tracker.debian.org/tango">tango</a> to unstable to fix bugs and do a soname transition.</li><li>… <a href="https://tracker.debian.org/pytango">pytango</a> to unstable to fix bugs and do a soname transition.</li></ul>
       </div>
       <p class="date">
        <a href="http://blog.alteholz.eu/2026/09/my-debian-activities-in-august-2026/">09 September, 2026 03:49PM</a>
        
         by alteholz
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 08, 2026</h1>
     

    <div class="channel">
     
      
     <a href="http://dirk.eddelbuettel.com/blog">
      <img class="face" src="heads/dirk.png"
            width="65" height="90"
            alt="hackergotchi for Dirk Eddelbuettel" />
     </a>
      
     <h2 class="planet-title">
      <a href="http://dirk.eddelbuettel.com/blog" title="Thinking inside the box">Dirk Eddelbuettel</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://dirk.eddelbuettel.com/blog/2026/09/08#rcpparmadillo_15.6.0-1_hide\" onClick=\"exclude( 'http://dirk.eddelbuettel.com/blog/2026/09/08#rcpparmadillo_15.6.0-1' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://dirk.eddelbuettel.com/blog/2026/09/08#rcpparmadillo_15.6.0-1_show\"  style=\"display:none;\" onClick=\"show( 'http://dirk.eddelbuettel.com/blog/2026/09/08#rcpparmadillo_15.6.0-1' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://dirk.eddelbuettel.com/blog/2026/09/08#rcpparmadillo_15.6.0-1" class="http://dirk.eddelbuettel.com/blog/2026/09/08#rcpparmadillo_15.6.0-1">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/08#rcpparmadillo_15.6.0-1">RcppArmadillo 15.6.0-1 on CRAN: New Upstream Minor</a>
       </h3>
       <div class="content">
        <p><img alt="armadillo image" src="https://dirk.eddelbuettel.com/images/armadillo_logo_two.png" style="float: left; margin: 10px 10px 10px 0;" /></p>
<p><a href="https://arma.sourceforge.net/">Armadillo</a> is a powerful
and expressive C++ template library for linear algebra and scientific
computing. It aims towards a good balance between speed and ease of use,
has a syntax deliberately close to Matlab, and is useful for algorithm
development directly in C++, or quick conversion of research code into
production environments. <a href="https://dirk.eddelbuettel.com/code/rcpp.armadillo.html">RcppArmadillo</a>
integrates this library with the <a href="https://www.r-project.org">R</a> environment and language–and is
widely used by (currently) 1331 other packages on <a href="https://cran.r-project.org">CRAN</a>, downloaded 48.5 million
times (per the partial logs from the cloud mirrors of CRAN), and the <a href="https://doi.org/10.1016/j.csda.2013.02.005">CSDA paper</a> (<a href="https://cran.r-project.org/package=RcppArmadillo/vignettes/RcppArmadillo-intro.pdf">preprint
/ vignette</a>) by Conrad and myself has been cited 727 times according
to Google Scholar.</p>
<p>This versions updates to the 15.6.0 upstream <a href="https://arma.sourceforge.net/">Armadillo</a> release made
yesterday. It extends solver options for poorly conditioned systems, and
brings some updates and extension to the <code>cube</code> data type.
For this release, we once again ran the usual complete
reverse-dependency check which came back spotless, and did CRAN so no
email exchange needed despite nearly 1300 reverse dependencies (but it
ended up taking more than a single business day). Still, automation can
be helpful when used with a well-maintained software stack. The package
has also already been updated for <a href="https://www.debian.org">Debian</a>, built for <a href="https://eddelbuettel.github.io/r2u/">r2u</a> and <a href="https://rcppcore.r-universe.dev/RcppArmadillo">r-universe</a>, and
will build shortly at <a href="https://cran.r-project.org">CRAN</a> for
the different binary releases.</p>
<p>All changes since the last CRAN release follow.</p>
<blockquote>
<h4 id="changes-in-rcpparmadillo-version-15.6.0-1-2026-09-07">Changes in
RcppArmadillo version 15.6.0-1 (2026-09-07)</h4>
<ul>
<li><p>Upgraded to Armadillo release 15.6.0 (Medium Roast Cortado)</p>
<ul>
<li><p>Expanded <code>solve()</code> with
<code>solve_opts::scale_thresh</code> option to widen detection of
poorly conditioned systems</p></li>
<li><p>Expanded <code>trans()</code> and <code>.t()</code> to handle
cubes</p></li>
<li><p>Added <code>permute()</code> to rearrange dimensions of cubes
(generalised transpose)</p></li>
<li><p>Added <code>cubemul()</code> for batched matrix multiplication of
cube slices</p></li>
</ul></li>
</ul>
</blockquote>
<p>Courtesy of my <a href="https://dirk.eddelbuettel.com/cranberries/">CRANberries</a>, there
is a <a href="https://dirk.eddelbuettel.com/cranberries/2026/09/08#RcppArmadillo_15.6.0-1">diffstat
report</a> relative to previous release. More detailed information is on
the <a href="https://dirk.eddelbuettel.com/code/rcpp.armadillo.html">RcppArmadillo
page</a>. Questions, comments etc should go to the <a href="https://lists.r-forge.r-project.org/cgi-bin/mailman/listinfo/rcpp-devel">rcpp-devel
mailing list</a> off the <a href="https://r-forge.r-project.org/projects/rcpp/">Rcpp R-Forge</a>
page.</p>
<p style="font-size: 80%; font-style: italic;">
This post by <a href="https://dirk.eddelbuettel.com">Dirk
Eddelbuettel</a> originated on his <a href="https://dirk.eddelbuettel.com/blog/">Thinking inside the box</a>
blog. If you like this or other open-source work I do, you can <a href="https://github.com/sponsors/eddelbuettel">sponsor me at
GitHub</a>.
</p><p></p>
       </div>
       <p class="date">
        <a href="http://dirk.eddelbuettel.com/blog/2026/09/08#rcpparmadillo_15.6.0-1">08 September, 2026 03:31PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <a href="https://k3xec.com/">
      <img class="face" src="heads/paultag.png"
            width="65" height="85"
            alt="hackergotchi for Paul Tagliamonte" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://k3xec.com/" title="K3XEC">Paul Tagliamonte</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://k3xec.com/pigeon-ip/_hide\" onClick=\"exclude( 'https://k3xec.com/pigeon-ip/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://k3xec.com/pigeon-ip/_show\"  style=\"display:none;\" onClick=\"show( 'https://k3xec.com/pigeon-ip/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://k3xec.com/pigeon-ip/" class="https://k3xec.com/pigeon-ip/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://k3xec.com/pigeon-ip/">IP over Avian Carriers (Part 12/12) 🕊️</a>
       </h3>
       <div class="content">
        <div class="hz-alert-ok">
🕊️ This post is part of a series called "Pigeon". If this is the first post
you've found, it'd be worth reading the
<a href="https://k3xec.com/pigeon/">intro post</a> first and then looking over
<a href="https://k3xec.com/tags/pigeon/">all posts in the series</a>.<br />
</div>
<p>The final step to all of this was to tie together all my <a href="https://k3xec.com/pigeon-phy/">PHY</a> RF
code, <a href="https://k3xec.com/pigeon-link/">Link</a> layer parsers, and my
<a href="https://people.debian.org/~paultag/bio/">background with operating systems</a>
to make this all feel like a normal thing my computer should be doing.</p>
<p>At the end of the day, I want my host system to know how to talk with a
<code>pigeon</code> daemon, so I don’t have to reimplement basically everything else.
My ability to use normal tools like <code>curl</code> or <code>ping6</code> is pretty important
here, so I need to reach for my old friend, the
<a href="https://docs.kernel.org/networking/tuntap.html">TUN</a> interface. The
TAP/TUN interface allows the kernel to route ethernet frames (TAP) or ip
packets (TUN) to a userspace program responsible for handling delivery and
reception – avoiding the need for a kernelspace driver for something that
can be handled in userland.</p>
<div class="hz-alert-warning">
🔒 wondering if doing this violates FCC Part 97 rules? I wrote up
<a href="https://k3xec.com/pigeon-background-testing/">notes on my test setup</a>
for exactly this question, but the short answer is "no"!
</div>
<p>I’m <a href="http://github.com/hztools/go-tap">no</a> <a href="https://k3xec.com/packrat-proxy/">stranger</a> to
playing with TAP/TUN, so this was pretty easy to snap together – although this
time I avoided the whole ethernet proxying thing (to side-step lossy
translations, maintaining two sets of mac address tables, and handle proxying
NDP/ARP messages) – it was kinda a bad idea last time – so I just used <code>TUN</code>
and straight IP for now. While implementing this, I decided I’d make a key
assertion about all pigeon networks – namely, all pigeon IPv6 networks are a
<code>/64</code> in size, no more, no less. The reason why I’m doing this here is that,
since <code>pigeond</code> does still does need a MAC address for the pigeon layer 2
protocol, we can write our daemon to always use
<a href="https://datatracker.ietf.org/doc/html/rfc4862">SLAAC</a>
to set the TUN IP address without any new information.</p>
<p>Which leads us to a bit of an aside, but I have a point, I swear. A few years
ago, my recreational RF adventures have lead me down a path where I decided to
<a href="https://whois.arin.net/rest/org/PT-1998">engage with ARIN</a> to solve (once and
for all) the massive headache I was running into with IPv6 numbering (really:
always renumbering) my multi-site radio processing networks. It’s a lot
of work to keep running correctly, but it’s solved a huge amount of
problems for me.</p>
<p>The only “internal” thing we really need to outline for this post is that, at
the highest level, my network (<code>paultag.net</code>) is split into an IP plan that
looks roughly like:</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Prefix</td>
<td class="hz-5-6th">Description</td>
</tr>
</thead>
<tbody>
<tr><td>/44</td><td>my <a href="https://whois.arin.net/rest/net/NET6-2602-810-6000-1.html">full allocation</a> of IP space</td></tr>
<tr><td>/48</td><td>15 "regions"</td></tr>
<tr><td>/54</td><td>64 "sites" per region. A "site" is assigned to a physical or logical location.</td></tr>
<tr><td>/64</td><td>1024 subnets per site. A subnet used by directly attached devices.</td></tr>
</tbody>
</table>
<p>For this exercise, I used IP space from <code>paultag.net</code>’s experimental region
(“region 8”), named <code>side.band</code> (<code>2602:810:6008::/48</code>) to connect my RF lab
(“site 1” - <code>2602:810:6008:400::/54</code>), and my two pigeon-specific subnets,
“subnet 0” (<code>2602:810:6008:400::/64</code>) and “subnet 1” (<code>2602:810:6008:401::/64</code>)
to my wider network. The first subnet (“subnet 0”) is a simple ethernet network
to enable my RF-only nodes to communicate with the <code>side.band</code> gateway. The
second subnet (“subnet 1”) is an RF-only pigeon network local to my lab.</p>
<h1 id="back-to-radios">back to radios</h1>
<p>With all that set up, I assigned my first two nodes their MAC addresses, and
set up the local RF only network segment. The nodes I brought online were the
following:</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Callsign</td>
<td class="hz-5-6th">IP</td>
</tr>
</thead>
<tbody>
<tr><td><code>K3XEC/MN</code></td><td><code>2602:810:6008:401:8e1f:64ff:fe35:4001</code></td></tr>
<tr><td><code>K3XEC/TH</code></td><td><code>2602:810:6008:401:8e1f:64ff:fe35:4002</code></td></tr>
</tbody>
</table>
<h1 id="testing-the-pigeon-network">testing the pigeon network</h1>
<p>And with that, I could begin to test that the host operating systems and RF
links could properly exchange data locally from SDR to SDR. We can use
<code>ping6</code> to see if a plain-ole <code>ICMPv6</code> ping round trips between hosts correctly:</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>$ ping6 2602:810:6008:401:8e1f:64ff:fe35:4001
</span></span><span style="display: flex;"><span>PING 2602:810:6008:401:8e1f:64ff:fe35:4001 (2602:810:6008:401:8e1f:64ff:fe35:4001) 56 data bytes
</span></span><span style="display: flex;"><span>64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=1 ttl=64 time=426 ms
</span></span><span style="display: flex;"><span>64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=2 ttl=64 time=397 ms
</span></span><span style="display: flex;"><span>64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=3 ttl=64 time=419 ms
</span></span><span style="display: flex;"><span>64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=4 ttl=64 time=418 ms
</span></span><span style="display: flex;"><span>64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=5 ttl=64 time=436 ms
</span></span><span style="display: flex;"><span>64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=6 ttl=64 time=391 ms
</span></span><span style="display: flex;"><span>64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=7 ttl=64 time=397 ms
</span></span></code></pre></div><p>And it does! Latency is horrid (and there’s a bunch of tx artifacts that
cause issues for us) – but both of those things are problems for later. Let’s
see how it handles a TCP connection by firing off a quick cURL across the
Pigeon network:</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>$ curl http://[2602:810:6008:401:8e1f:64ff:fe35:4001]:8000/testing.txt
</span></span><span style="display: flex;"><span>The rock dove (Columba livia), also known as the common pigeon or rock pigeon
</span></span><span style="display: flex;"><span>(but see also Petrophassa), is a member of the bird family Columbidae (doves
</span></span><span style="display: flex;"><span>and pigeons).
</span></span></code></pre></div><p>As expected, our “remote” end here running the server reports the correct peer
IP address, which is another indication (beyond the log messages and blinking
LEDs) that we’re routing over our TUN interface.</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>Serving HTTP on 2602:810:6008:401:8e1f:64ff:fe35:4001 port 8000 (http://[2602:810:6008:401:8e1f:64ff:fe35:4001]:8000/) ...
</span></span><span style="display: flex;"><span>2602:810:6008:401:8e1f:64ff:fe35:4002 - - [28/May/2026 12:53:21] "GET /testing.txt HTTP/1.1" 200 -
</span></span></code></pre></div><p>That … worked? First shot! Nice! It’s pretty slow and seems like we have a
lot of packet loss, but it does, however, beg the question – can it nethack?</p>
<h2 id="nethack">nethack!</h2>
<p>Yes! It can nethack! No clickbait here. The way I went about this one is a bit
anit-cimatic – I set up a <code>nethack</code> server (using <code>inetd</code> in this case) on one
of the hosts’ <code>pigeon0</code> network interface, and hit that port over RF from
the other:</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/nethack.gif" /></p>
<p>However, when playing it, it becomes very obvious (as you can likely see) that
there’s a fair amount of packet loss (understandable) and probably some packet
collisions taking place.</p>
<h2 id="iperf">iperf</h2>
<p>Let’s try and put a number to exactly how bad the bandwidth and packet loss
is by running <code>iperf</code> between the two pigeon hosts over rf:</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>$ iperf -c 2602:810:6008:401:8e1f:64ff:fe35:4002
</span></span><span style="display: flex;"><span>------------------------------------------------------------
</span></span><span style="display: flex;"><span>Client connecting to 2602:810:6008:401:8e1f:64ff:fe35:4002, TCP port 5001
</span></span><span style="display: flex;"><span>TCP window size: 16.0 KByte (default)
</span></span><span style="display: flex;"><span>------------------------------------------------------------
</span></span><span style="display: flex;"><span>[ 1] local 2602:810:6008:401:: port 58248 connected with 2602:810:6008:401:8e1f:64ff:fe35:4002 port 5001
</span></span><span style="display: flex;"><span>[ ID] Interval Transfer Bandwidth
</span></span><span style="display: flex;"><span>[ 1] 0.0000-20.2348 sec 76.8 KBytes 31.1 Kbits/sec
</span></span></code></pre></div><p>Shockingly, not <strong>nearly</strong> as bad as I thought it was going to be. Given I’ve
spent exactly zero time making this operate to a level that I would call
acceptable, this is a very fucking solid start. I expect I could get that
number up if I spent a few weeks on it – it’s just not been a priority
at any point yet (and the first time I’ve instrumented it, even!).</p>
<p>This’ll be good enough to get started. Let’s see what else we can pull
off here.</p>
<h2 id="ip-multicast-to-some-rtl-sdrs">IP multicast to some rtl-sdrs</h2>
<p>Back when I designed what I wanted <a href="https://k3xec.com/pigeon-phy-modes/">Mode A</a> to
look like, I intentionally picked a signal bandwidth that could be received
by an <code>rtl-sdr</code> – so let’s put that to use. It may go without saying,
but just to say it – the rtl-sdr can not transmit, so this will be capable
of receiving pigeon frames – but not sending any in reply.</p>
<p>However, this means I can use a bunch of low-cost computers (raspberry
pi-class), and low-cost SDRs (rtl-sdr) and still receive IP traffic from
transmitting pigeon network stations. This could be a lot of fun for things
like <a href="https://en.wikipedia.org/wiki/Fountain_code">fountain coding</a> a data
stream, or adapting multicast streaming protocols to work over RF links.
Anywho, I swapped my “far” end to an rtl-sdr (one config file change!), and
figured I’d start with some (basic) multicast traffic, transmitting the time
once a second:</p>
<div class="highlight"><pre tabindex="0"><code class="language-sh"><span style="display: flex;"><span>$ <span style="color: #66d9ef;">while</span> <span style="color: #f92672;">[</span> true <span style="color: #f92672;">]</span>; <span style="color: #66d9ef;">do</span>
</span></span><span style="display: flex;"><span> echo <span style="color: #66d9ef;">$(</span>date +%s<span style="color: #66d9ef;">)</span> <span style="color: #ae81ff;">\
</span></span></span><span style="display: flex;"><span> | socat - UDP6-DATAGRAM:<span style="color: #f92672;">[</span>ff02::114%pigeon0<span style="color: #f92672;">]</span>:62804
</span></span><span style="display: flex;"><span> sleep <span style="color: #ae81ff;">1</span>
</span></span><span style="display: flex;"><span><span style="color: #66d9ef;">done</span>
</span></span></code></pre></div><p>If I had more time to burn, I was planning on bridging
<a href="https://en.wikipedia.org/wiki/Automatic_Packet_Reporting_System">APRS</a> traffic
to UDP multicast within a pigeon network subnet. However, since I’m already 4
years late on this blog post, I figured this would be enough for now (and you
can imagine that fun project in this space if you so wish!)</p>
<p>I fired up <code>pigeond</code> again (except this time connected to an rtl-sdr),
and was pleasantly surprised to be greeted by some decoded traffic right
off the bat:</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>⪧ [k3xec/mn] 8c:1f:64:35:40:02 ⇢ 00:00:00:00:00:00 ipv6 fe80::23ee:2969:53f7:b332 ⇢ ff02::114 17 (UDP - User Datagram)
</span></span><span style="display: flex;"><span>⪧ [k3xec/mn] 8c:1f:64:35:40:02 ⇢ 00:00:00:00:00:00 ipv6 fe80::23ee:2969:53f7:b332 ⇢ ff02::114 17 (UDP - User Datagram)
</span></span><span style="display: flex;"><span>⪧ [k3xec/mn] 8c:1f:64:35:40:02 ⇢ 00:00:00:00:00:00 ipv6 fe80::23ee:2969:53f7:b332 ⇢ ff02::114 17 (UDP - User Datagram)
</span></span><span style="display: flex;"><span>⪧ [k3xec/mn] 8c:1f:64:35:40:02 ⇢ 00:00:00:00:00:00 ipv6 fe80::23ee:2969:53f7:b332 ⇢ ff02::114 17 (UDP - User Datagram)
</span></span></code></pre></div><p>Of course, I took a <code>tcpdump</code> to confirm for completeness sake that the
traffic actually made it out of our TUN interface:</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>$ tcpdump -i pigeon0
</span></span><span style="display: flex;"><span>22:39:34.808705 IP6 (flowlabel 0x92a0e, hlim 1, next-header UDP (17), payload length 19) fe80::23ee:2969:53f7:b332.35911 &gt; ff02::114.62804: [udp sum ok] UDP, length 11
</span></span><span style="display: flex;"><span>22:39:36.429887 IP6 (flowlabel 0x4dc84, hlim 1, next-header UDP (17), payload length 19) fe80::23ee:2969:53f7:b332.50026 &gt; ff02::114.62804: [udp sum ok] UDP, length 11
</span></span><span style="display: flex;"><span>22:39:39.365977 IP6 (flowlabel 0x224d5, hlim 1, next-header UDP (17), payload length 19) fe80::23ee:2969:53f7:b332.36308 &gt; ff02::114.62804: [udp sum ok] UDP, length 11
</span></span><span style="display: flex;"><span>22:39:40.944889 IP6 (flowlabel 0x4721c, hlim 1, next-header UDP (17), payload length 19) fe80::23ee:2969:53f7:b332.35003 &gt; ff02::114.62804: [udp sum ok] UDP, length 11
</span></span></code></pre></div><p>Looks great! <code>tcpdump</code> is showing multicast packets show up (as we assumed they
would), on the <code>pigeon0</code> interface, on the machine connected to an <code>rtl-sdr</code>. Of
course, any replies will get sent to the
<a href="https://en.wikipedia.org/wiki/Bit_bucket">bit bucket</a>, but it can definitely
decode things just fine! Very fucking cool.</p>
<p>Well right, ok! Let’s go back to two rx/tx radios, and see what we can do with
our newfound network stack over ham radio frequencies – let’s try to do some
fun (and traditional!) ham radio things with it!</p>
<h2 id="winlink">Winlink</h2>
<p><a href="https://winlink.org">Winlink</a> is a ham radio mail relay system for ham radio
operators to send, receive or relay mail over the internet, or RF (usually HF
or VHF/2M). Winlink relays are accessible via whatever transport you can
find – most commonly <code>telnet</code> (using the internet), <code>ax.25</code> (usually 2m VHF)
or <code>VARA HF</code> (unsurprisingly, on HF). I use <a href="https://getpat.io/">pat</a> as my
Winlink client – it’s written in Go, doesn’t require windows, and is just
generally nice to work with.</p>
<p>Let’s try the easy thing first – let’s connect by proxying the Winlink server
into the pigeon network using <code>socat</code> (lightly edited to remove date/times)</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>$ pat connect pigeon
</span></span><span style="display: flex;"><span>Connecting to WL2K (telnet)...
</span></span><span style="display: flex;"><span>Connected to [2602:810:6008:401:8e1f:64ff:fe35:4002]:8772 (tcp)
</span></span><span style="display: flex;"><span>[WL2K-5.0-B2FWIHJM$]
</span></span><span style="display: flex;"><span>;PQ: 54509561
</span></span><span style="display: flex;"><span>CMS&gt;
</span></span><span style="display: flex;"><span>&gt;FC EM OLU6BP5HKMG2 240 205 0
</span></span><span style="display: flex;"><span>&gt;F&gt; 95
</span></span><span style="display: flex;"><span>FS Y
</span></span><span style="display: flex;"><span>Remote accepted OLU6BP5HKMG2
</span></span><span style="display: flex;"><span>Transmitting [Hello, World] [offset 0]
</span></span><span style="display: flex;"><span>Hello, World: 100%
</span></span><span style="display: flex;"><span>FF
</span></span><span style="display: flex;"><span>&gt;FQ
</span></span><span style="display: flex;"><span>Disconnected.
</span></span><span style="display: flex;"><span>$
</span></span></code></pre></div><p>Lo and behold, shortly after, I got this delightful message to my
email address, relayed in from WINLINK:</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>From: K3XEC@winlink.org
</span></span><span style="display: flex;"><span>Reply-To: K3XEC@winlink.org
</span></span><span style="display: flex;"><span>Subject: Hello, World
</span></span><span style="display: flex;"><span>To: paultag@[...]
</span></span><span style="display: flex;"><span>Message-ID: &lt;OLU6BP5HKMG2@winlink.org&gt;
</span></span><span style="display: flex;"><span>MIME-Version: 1.0
</span></span><span style="display: flex;"><span>X-MARSPrecedence: Routine
</span></span><span style="display: flex;"><span>X-WL2KPrecedence: Routine
</span></span><span style="display: flex;"><span>Content-Type: text/plain
</span></span><span style="display: flex;"><span>Content-Transfer-Encoding: 8bit
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span>Hello, World!
</span></span></code></pre></div><p>The only shame is I won’t be able to check in to a <a href="https://winlinkwednesday.net/">winlink
wednesday</a> using this scheme unless I further
proxy this message over AX.25 instead of relaying to Winlink’s servers over
telnet (which, to be fair, is definitely also possible – I just got lazy when
I glued this one together – see note above about being 4 years late on this
post).</p>
<p>But, you know, connecting to a host that is using <code>socat</code> to proxy a
connection to an internet resource is interesting but – you know what, fuck it
– hang on, dear reader – let’s bang a hard left turn and just ship this thing
hard and <strong>directly connect it to the internet</strong>. Let’s take our dinky,
home-built PHY and Layer 2 and see if we can wire it directly into the internet
– something that, every time I go to think about it, reminds me of
<a href="https://www.themoth.org/stories/all-at-sea">Tim FitzHigham</a> and his crapper.</p>
<h1 id="crossing-the-english-channel-in-a-bathtub">Crossing the english channel in a bathtub</h1>
<p>Ok, ok. I decided to bury the lede a bit here – I didn’t mention that the
<code>side.band</code> network is currently
<a href="https://bgp.tools/prefix/2602:810:6008::/48">BGP announced</a>. Although we
haven’t used it – this does mean that we’re most of the way to sending packets
to the wider internet, and we <strong>should</strong> be able to “just” fix a few routing
tables, and see packets begin to flow.</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/network-overview.png" /></p>
<p>After tweaking the local routing tables (and restarting <code>pigeond</code> for good
measure), I decided to test my newfound connectivity by pinging something over
our new network transport.</p>
<h2 id="ping-github">ping github</h2>
<p>Why don’t we start with the world’s premier software engineering platform,
operated by one of the largest companies in the world, <strong>GitHub</strong>! After all,
they have an all knowing (and, apparently, arguably sentiant?) AI on hand to
instantly and automatically fix any stray
<a href="https://mrshu.github.io/github-statuses/">reliability issues</a> in the
background, so we should definitely see replies right off the bat:</p>
<div class="highlight"><pre tabindex="0"><code class="language-txt"><span style="display: flex;"><span>$ ping6 github.com
</span></span><span style="display: flex;"><span>ping6: github.com: Address family for hostname not supported
</span></span></code></pre></div><p>Wait, oh no – that can’t be right?</p>
<p>After all, it’s 2026, and both
<a href="https://www.google.com/intl/en/ipv6/statistics.html">Google</a>
and <a href="https://radar.cloudflare.com/adoption-and-usage/north-america">CloudFlare</a>
(in North America) are reporting
over half of all traffic they see is IPv6 – and GitHub still doesn’t support
IPv6? <strong>Definitely not</strong>, this is for sure a bug with my code or network.</p>
<h2 id="lets-ping-something-that-supports-ipv6-instead">lets ping something that supports ipv6 instead</h2>
<p>That being said, just for completeness sake, since that error is also given
when there’s no IPv6 DNS record, let’s go ahead and double check with
<a href="https://he.net/">Hurricane Electric</a> too, you know, just to be sure.</p>
<div class="highlight"><pre tabindex="0"><code class="language-sh"><span style="display: flex;"><span>$ ping6 he.net
</span></span><span style="display: flex;"><span>PING he.net <span style="color: #f92672;">(</span>2001:470:0:503::2<span style="color: #f92672;">)</span> <span style="color: #ae81ff;">56</span> data bytes
</span></span><span style="display: flex;"><span><span style="color: #ae81ff;">64</span> bytes from he.net <span style="color: #f92672;">(</span>2001:470:0:503::2<span style="color: #f92672;">)</span>: icmp_seq<span style="color: #f92672;">=</span><span style="color: #ae81ff;">1</span> ttl<span style="color: #f92672;">=</span><span style="color: #ae81ff;">53</span> time<span style="color: #f92672;">=</span><span style="color: #ae81ff;">514</span> ms
</span></span><span style="display: flex;"><span><span style="color: #ae81ff;">64</span> bytes from he.net <span style="color: #f92672;">(</span>2001:470:0:503::2<span style="color: #f92672;">)</span>: icmp_seq<span style="color: #f92672;">=</span><span style="color: #ae81ff;">2</span> ttl<span style="color: #f92672;">=</span><span style="color: #ae81ff;">53</span> time<span style="color: #f92672;">=</span><span style="color: #ae81ff;">230</span> ms
</span></span><span style="display: flex;"><span><span style="color: #ae81ff;">64</span> bytes from he.net <span style="color: #f92672;">(</span>2001:470:0:503::2<span style="color: #f92672;">)</span>: icmp_seq<span style="color: #f92672;">=</span><span style="color: #ae81ff;">3</span> ttl<span style="color: #f92672;">=</span><span style="color: #ae81ff;">53</span> time<span style="color: #f92672;">=</span><span style="color: #ae81ff;">248</span> ms
</span></span><span style="display: flex;"><span><span style="color: #ae81ff;">64</span> bytes from he.net <span style="color: #f92672;">(</span>2001:470:0:503::2<span style="color: #f92672;">)</span>: icmp_seq<span style="color: #f92672;">=</span><span style="color: #ae81ff;">4</span> ttl<span style="color: #f92672;">=</span><span style="color: #ae81ff;">53</span> time<span style="color: #f92672;">=</span><span style="color: #ae81ff;">246</span> ms
</span></span><span style="display: flex;"><span><span style="color: #ae81ff;">64</span> bytes from he.net <span style="color: #f92672;">(</span>2001:470:0:503::2<span style="color: #f92672;">)</span>: icmp_seq<span style="color: #f92672;">=</span><span style="color: #ae81ff;">5</span> ttl<span style="color: #f92672;">=</span><span style="color: #ae81ff;">53</span> time<span style="color: #f92672;">=</span><span style="color: #ae81ff;">265</span> ms
</span></span><span style="display: flex;"><span><span style="color: #ae81ff;">64</span> bytes from he.net <span style="color: #f92672;">(</span>2001:470:0:503::2<span style="color: #f92672;">)</span>: icmp_seq<span style="color: #f92672;">=</span><span style="color: #ae81ff;">6</span> ttl<span style="color: #f92672;">=</span><span style="color: #ae81ff;">53</span> time<span style="color: #f92672;">=</span><span style="color: #ae81ff;">240</span> ms
</span></span></code></pre></div><p>Well, shit. Right, OK, i’ll be damned. 18 years in and GitHub still can’t crack
that nut.</p>
<h2 id="curl-works">cURL works!</h2>
<p>Right, anyway, yes, back on track – good news! Our uplink is up and routing,
and wait, holy shit! <span class="hz-highlight">Check it out! <code>pigeon</code> is
exchanging packets with the internet and no one is any the wiser!</span>
Literlaly amazing. Let’s try a cURL across the internet now (although no
TLS allowed, so, http only for now):</p>
<div class="highlight"><pre tabindex="0"><code class="language-sh"><span style="display: flex;"><span>$ curl -6 -I http://facebook.com
</span></span><span style="display: flex;"><span>HTTP/1.1 <span style="color: #ae81ff;">301</span> Moved Permanently
</span></span><span style="display: flex;"><span>Location: https://facebook.com/
</span></span><span style="display: flex;"><span>Content-Type: text/plain
</span></span><span style="display: flex;"><span>Server: proxygen-bolt
</span></span><span style="display: flex;"><span>Connection: keep-alive
</span></span><span style="display: flex;"><span>Content-Length: <span style="color: #ae81ff;">0</span>
</span></span></code></pre></div><h2 id="irc-works-too">IRC works, too</h2>
<p>Sweeeeet. That all works! Forget HTTP, let’s do some other 90’s era
stuff, it’s high-time to log into IRC with a quick <code>/connect -notls</code>, and see
what’s going on in the <code>#debian-hams</code> channel – pleased that I got online
fairly quickly, and was able to even talk to myself!</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/irc.gif" /></p>
<h2 id="the-gopherspace">THE GOPHERSPACE</h2>
<p>Naturally, let’s keep this train of nostalga running, and give
the 2026 <a href="https://en.wikipedia.org/wiki/Gopher_(protocol)">gopher</a>space a
shot.</p>
<p>I know the kind folks over at <a href="https://tilde.town/">tilde.town</a> (hello,
townies!) have a robust gopherspace, so let’s give it a dial! Let’s try and see
if we can load <a href="https://tilde.town/~vilmibm/">vilmibm</a>’s
<a href="gopher://tilde.town/0/~vilmibm/zoo/slug">slug</a> over gopher:</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/gopher.png" /></p>
<p>Yes! I forgot to make this one a video, so no gif. I did wind up having a bit
if trouble with a few gopher clients and IPv6 support – I may send some
patches if I can find the time.</p>
<h1 id="so-whats-next">So, what’s next?</h1>
<p>Alright, that’s it. I have a few more fun ideas but they’re going to have to
wait for another day. Carrying IP is fun and all but kinda not the point behind
pigeon, after all. Rather than trying to make this into “a thing”, I’m planning
on exploring the loose ends first – different types of modulation schemes
(like QAM-NUC), implementing LDPC error correction and some layer 2 logic
into the <code>pigeond</code> (like switching traffic, and gain control). I also plan
on spending some time with my (currently, very basic) simulator to better dial
in tradeoffs throughout the stack.</p>
<p>Since, structurally, <code>pigeon</code> is something I feel like I can work with, I’m
hoping i’ll be able to find the time for some (much smaller!) followup posts
without it taking 4 years this time. If I do, they’ll show up under the
<a href="https://k3xec.com/tags/pigeon/">pigeon</a> tag – and I’ll be sure to update this post with
a link below (and the intro post).</p>
<p>I’m hoping that this series (which was supposed to be one post) was helpful
to someone out there – if it was, feel free to reach out and let me know!</p>
       </div>
       <p class="date">
        <a href="https://k3xec.com/pigeon-ip/">08 September, 2026 02:06PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
     <div id="https://k3xec.com/pigeon-link-cal/" class="https://k3xec.com/pigeon-link-cal/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://k3xec.com/pigeon-link-cal/">can you hear me now? good! (Part 11/12) 🕊️</a>
       </h3>
       <div class="content">
        <div class="hz-alert-ok">
🕊️ This post is part of a series called "Pigeon". If this is the first post
you've found, it'd be worth reading the
<a href="https://k3xec.com/pigeon/">intro post</a> first and then looking over
<a href="https://k3xec.com/tags/pigeon/">all posts in the series</a>.<br />
<br />
<b>If you're looking for it, the intro to the Link (layer 2) that this belongs at a
high level and listing of the parts that make it up (including this one) is on
the <a href="https://k3xec.com/pigeon-link/">link post</a></b>.
</div>
<p>Built-in to the pigeon link protocol is a message type called <code>cal</code> (short for,
you guessed it, <code>calibration</code>). A pigeon <code>frame</code> with a type of cal
(which is <code>0x02</code>) carries a JSON encoded <a href="https://k3xec.com/index.xml?_cache_bust=1705936052#cal">payload</a> in the body, which can
either be a <a href="https://k3xec.com/index.xml?_cache_bust=1705936052#beacon">beacon</a>, requesting signal reports in response,
or a <a href="https://k3xec.com/index.xml?_cache_bust=1705936052#report">report</a>, describing the received <code>beacons</code>.</p>
<p>This serves a few interesting purposes – firstly, network operators can better
understand the coverage footprint, propagation under different conditions, and
how gain impacts reception when tuning for the lowest practical power levels.
Secondly, this <em>can</em> be used (and I plan to eventually implement!) to construct
a mapping of minimum power level and peer mac address to dynamically control
the transmission power based on the destination station.</p>
<p>That being said, for now, all I’ve used this for is getting a rough sense for
what gain value(s) make sense between two nodes (manually). In the future,
beyond all the fancy neighbor gain stuff, I plan to wire this into the daemon
to happen automatically, “debouncing” for <code>beacon</code> and <code>report</code> messages, such
that transmitting stations only beacon, and receiving stations only report a
max of once over some time period for a given peer.</p>
<h2 id="version">Version</h2>
<p>Given all the above, I do intend to make some massive changes to this protocol
(I promise to blog all about it) when I get around to hacking on switching
Layer 2 frames within a network segment. Just to avoid having to dig myself out
of a hole later, I’m going to explicitly send (and check) the <code>version</code> field
to avoid having a big “flag day” switchover or needing to use a new <code>link</code>
<code>type</code>.</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Version ID</td>
<td class="hz-5-6th">Description</td>
</tr>
</thead>
<tbody>
<tr><td><code>V1</code></td><td>this version of the cal protocol</td></tr>
</tbody>
</table>
<h2 id="location">Location</h2>
<p>Both flavors of <code>cal</code> messages (<code>beacon</code> and <code>report</code>) may contain a location,
which is the location that the <code>beacon</code> was transmitted, or for or the location
where the <code>beacon</code> was heard for a <code>response</code>. This can be used to derive a
coverage map and to (operationally) better understand what stations should be
within range, and generally what gain level(s) are effective.</p>
<p>All fields assume <code>WGS84</code> latitude and longitude values, and elevation is
distance, in meters, above the <code>WGS84</code> ellipsoid – <strong>NOT</strong> height above
sea level, or altitude above the ground.</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Field</td>
<td class="hz-5-6th">Description</td>
</tr>
</thead>
<tbody>
<tr><td>lat</td><td>WGS84 Latitude</td></tr>
<tr><td>lon</td><td>WGS84 Longitude</td></tr>
<tr><td>elevation</td><td>height, in meters above the WGS84 ellipsoid</td></tr>
</tbody>
</table>
<h2 id="sequence">Sequence</h2>
<p>Each <code>beacon</code> contains a <code>Sequence</code> identifier, which is used to communicate
which message number is being heard, and how many total were transmitted
by the originating station. The current approach with <code>Beacon</code> messages is to
transmit some number of <code>Beacon</code> messages at different gain levels, each
with a unique Sequence identifier.</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Field</td>
<td class="hz-5-6th">Description</td>
</tr>
</thead>
<tbody>
<tr><td>number</td><td>beacon sequence number</td></tr>
<tr><td>total</td><td>total number of beacons transmitted</td></tr>
</tbody>
</table>
<h2 id="gains">Gains</h2>
<p>Recorded gain setting(s). For a <code>Beacon</code> this indicates the gain settings
(which, in spite of its name, includes things like amplifiers, or attenuators).
Changing this over different <code>Beacon</code> frames enables a better understanding
of what an appropriate gain level is for the transmitting station over time.</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Field</td>
<td class="hz-5-6th">Description</td>
</tr>
</thead>
<tbody>
<tr><td>name</td><td>gain stage name</td></tr>
<tr><td>db</td><td>gain value, in dBm</td></tr>
</tbody>
</table>
<h2 id="cal">Cal</h2>
<p>All messages contained in a <code>cal</code> frame are of this type. The <code>type</code> field
communicates if this is a <code>Beacon</code> or <code>Report</code> message type.</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Type</td>
<td class="hz-5-6th">Description</td>
</tr>
</thead>
<tbody>
<tr><td>beacon</td><td>sent intermittently by idle stations</td></tr>
<tr><td>report</td><td>reception report in response to a <code>beacon</code></td></tr>
</tbody>
</table>
<h1 id="beacon">Beacon</h1>
<p>A <code>beacon</code> message may be sent periodically by pigeon nodes capable of
transmitting to announce their prescience to peers and, implicitly, to receive
signal reports from nearby listeners who are capable and configured to transmit
reports.</p>
<p>The <code>beacon</code> JSON message is made up of the following fields:</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Field</td>
<td class="hz-5-6th">Description</td>
</tr>
</thead>
<tbody>
<tr><td>version</td><td><a href="https://k3xec.com/index.xml?_cache_bust=1705936052#version">version</a> enum value</td></tr>
<tr><td>gains</td><td><a href="https://k3xec.com/index.xml?_cache_bust=1705936052#gains">gains</a> object</td></tr>
<tr><td>sequence</td><td><a href="https://k3xec.com/index.xml?_cache_bust=1705936052#sequence">sequence</a> number</td></tr>
<tr><td>location</td><td><a href="https://k3xec.com/index.xml?_cache_bust=1705936052#location">location</a> object</td></tr>
</tbody>
</table>
<p>An example <code>beacon</code> looks, unsupprisingly, as follows:</p>
<div class="highlight"><pre tabindex="0"><code class="language-json"><span style="display: flex;"><span>{
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">"type"</span>: <span style="color: #e6db74;">"beacon"</span>,
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">"version"</span>: <span style="color: #e6db74;">"V1"</span>,
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">"sequence"</span>: {
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">"number"</span>: <span style="color: #ae81ff;">2</span>,
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">"total"</span>: <span style="color: #ae81ff;">5</span>
</span></span><span style="display: flex;"><span> },
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">"gains"</span>: []
</span></span><span style="display: flex;"><span>}
</span></span></code></pre></div><h1 id="report">Report</h1>
<p>A <code>report</code> message may be sent in response to a <a href="https://k3xec.com/index.xml?_cache_bust=1705936052#beacon">beacon</a> message by
pigeon nodes capable of receiving and transmitting to assist with setting the
lowest usable gain value, and to better understand the area of coverage and
propagation.</p>
<p>The <code>report</code> JSON message is made up of the following fields:</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Field</td>
<td class="hz-5-6th">Description</td>
</tr>
</thead>
<tbody>
<tr><td>version</td><td><a href="https://k3xec.com/index.xml?_cache_bust=1705936052#version">version</a> object</td></tr>
<tr><td>location</td><td><a href="https://k3xec.com/index.xml?_cache_bust=1705936052#location">location</a> object</td></tr>
</tbody>
</table>
<p>An example <code>report</code> looks as follows:</p>
<div class="highlight"><pre tabindex="0"><code class="language-json"><span style="display: flex;"><span>{
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">"type"</span>: <span style="color: #e6db74;">"report"</span>,
</span></span><span style="display: flex;"><span>}
</span></span></code></pre></div><p>With all that out of the way</p>
<p><a href="https://k3xec.com/pigeon-ip/">lets send some ip →</a></p>
       </div>
       <p class="date">
        <a href="https://k3xec.com/pigeon-link-cal/">08 September, 2026 02:05PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
     <div id="https://k3xec.com/pigeon-link/" class="https://k3xec.com/pigeon-link/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://k3xec.com/pigeon-link/">You would never break the chain (Part 10/12) 🕊️</a>
       </h3>
       <div class="content">
        <div class="hz-alert-ok">
🕊️ This post is part of a series called "Pigeon". If this is the first post
you've found, it'd be worth reading the
<a href="https://k3xec.com/pigeon/">intro post</a> first and then looking over
<a href="https://k3xec.com/tags/pigeon/">all posts in the series</a>.<br />
</div>
<p>Now that we have a working Layer 1, we have a way to send a block of bits
from one place to anyone who cares to listen to us. This is very welcome
news, but we are now facing a new, different and just as fun question – what
shape should that data take?</p>
<div class="hz-alert-warning">
⏳ Need a bit more of a crash course on what "Layer 1" and "Layer 2" mean? No
problem, I wrote up <a href="https://k3xec.com/pigeon-background-osi/">short summary here</a> to
help.
</div>
<p>Given our incredibly limited functionality of our nodes, we could definitely
skip all this work and just stuff an IP packet into the link; but I decided
to not since I am (<em>eventually</em>) interested in adding some sort of
<a href="https://en.wikipedia.org/wiki/Spanning_Tree_Protocol">spanning tree</a>-<strong>like</strong>
protocol to implement network
<a href="https://en.wikipedia.org/wiki/Network_switch">switching</a> so not all nodes
need to communicate directly with all other nodes – but that day is not today.</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/layer-2.png" /></p>
<p>Given i’m going to stub most of that out, let’s take a look at what some
similar Layer 2 protocols use – things like Ethernet or WiFi frames. Both
contain structured information regarding the transmitter, desired recipient,
type of data, and the higher-level data itself (such as IP packets). As a
result of attempting to learn from others, the Pigeon Layer 2 (called, simply,
“<code>link</code>”) is also split into a fixed-length header, followed by the contents
described by the header.</p>
<div class="hz-abi">
<div class="hz-abi-yellow hz-abi-2b" type="u8[6]">
dst mac
</div>
<div class="hz-abi-yellow hz-abi-2b" type="u8[6]">
src mac
</div>
<div class="hz-abi-yellow hz-abi-2b" type="u8[8]">
callsign
</div>
<div class="hz-abi-yellow hz-abi-1b" type="u8">
<a href="https://k3xec.com/index.xml?_cache_bust=1705936052#types">type</a>
</div>
<div class="hz-abi-yellow hz-abi-2b" type="u16">
length
</div>
<div class="hz-abi-yellow hz-abi-Nb" type="u8[]">payload</div>
</div>
<p>The header is a fixed-length (23 byte) structure, which contains the source
MAC address (<code>src mac</code>), destination MAC address (<code>dst mac</code>), the ITU
coordinated ham radio callsign of the control operator of this message
(<code>callsign</code>), the type of payload to follow (<code>type</code>; defined below), and
the length of the data to follow the header (<code>length</code> as a 16 bit big-endian
unsigned integer).</p>
<p>The <code>type</code> field indicates how the <code>payload</code> is to be interpreted – currently
I’ve only defined 3 possible payload types so far:</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Type</td>
<td class="hz-5-6th">Description</td>
</tr>
</thead>
<tbody>
<tr><td>0x01</td><td>Raw (testing only)</td></tr>
<tr><td>0x02</td><td><a href="https://k3xec.com/pigeon-link-cal/">Cal</a></td></tr>
<tr><td>0x04</td><td><a href="https://k3xec.com/pigeon-ip/">Ipv6</a></td></tr>
</tbody>
</table>
<p>Keen observers will perhaps infer that there <em>used</em> to be an <code>Ipv4</code> type at
<code>0x03</code> – which is true – however, i’ve since removed it since i’ve never once
used it and the codepath was more trouble than it was worth. As is my wont,
I’ve optend to just lean into Ipv6-only IP transport – it’s easy enough to
shim ipv4 in, if someone REALLY wanted to using something like <code>64:ff9b:1::/48</code>
and a bit of code in the transmitter/receiver (or even using something like
<a href="https://www.jool.mx/en/intro-xlat.html">jool</a> and unbound’s
<a href="https://github.com/NLnetLabs/unbound/blob/master/doc/README.DNS64">dns64-prefix</a>
at the router). I don’t think I’ll bring it back, but just in case I have
to for some reason in the future, it’s there.</p>
<p>Additionally, friends of the pod may also recognize that this structure is
basically the exact same structure as what I had in <a href="https://k3xec.com/packrat-proxy/">PACKRAT</a>,
which, is also true. I started this project off maintaining interoperability in
the Layer 2 for pigeon and packrat, but at some point just gave up on it during
one of the many cleanups. I’m hopeful I can maintain compatibility going
forward, and that won’t have to muck with this header too much more. We’ll see
what happens once I start to push the bounds of what is possible with pigeon.</p>
<p>Hopefully it feels like carrying IP data inside this frame to be a pretty
self-explanatory exercise – the 0th byte of the <code>payload</code> is the 0th byte of
an IPv6 header (followed by all the usual stuff, like UDP or TCP header(s) and
any carried data, just like you’d find anywhere else.</p>
<p><a href="https://k3xec.com/pigeon-link-cal/">Pigeon’s calabration protocol →</a></p>
       </div>
       <p class="date">
        <a href="https://k3xec.com/pigeon-link/">08 September, 2026 02:05PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
     <div id="https://k3xec.com/pigeon-phy-modes/" class="https://k3xec.com/pigeon-phy-modes/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://k3xec.com/pigeon-phy-modes/">Mode A (Part 9/12) 🕊️</a>
       </h3>
       <div class="content">
        <div class="hz-alert-ok">
🕊️ This post is part of a series called "Pigeon". If this is the first post
you've found, it'd be worth reading the
<a href="https://k3xec.com/pigeon/">intro post</a> first and then looking over
<a href="https://k3xec.com/tags/pigeon/">all posts in the series</a>.<br />
<br />
<b>If you're looking for it, the intro to the PHY (layer 1) that this belongs at a
high level and listing of the parts that make it up (including this one) is on
the <a href="https://k3xec.com/pigeon-phy/">phy post</a></b>.
</div>
<p>While developing Pigeon, I’ve called the group of all the configuration of the
Layer 1 PHY parameters the “Mode”. I’ve experimented with a few different
“modes”, but one in particular has been the most resilient to the innumerable
mistakes and bugs i’ve wrought into existence – and that is the first mode
I wrote down, “Mode A”. This is even (mostly) backwards compatible to my
original Go implementation of Pigeon Mode A (back in 2022) over the air,
and has largely withstood the problems I’ve thrown at it.</p>
<p>I’ve removed the bulk of the support I wrote out for other modes, but i’m
likely to bring them back over time as I use pigeon to learn more (such as
“Mode B” (QAM-16), “Mode C” (QAM-16 NUC), and “Mode AW” which is the exact same
as Mode A, except 5MHz in bandwidth. More to come on those as I get further
along – but for now let’s braindump the parameters i’ve picked out for Mode A:</p>
<table>
<thead>
<tr>
<td class="hz-1-6th">Attribute</td>
<td class="hz-1-6th">Value</td>
<td class="hz-4-6th">Description</td>
</tr>
</thead>
<tbody>
<tr>
<td>Rate</td>
<td>2.5 MHz</td>
<td>Sampling Rate / Bandwidth</td>
</tr>
<tr>
<td><a href="https://k3xec.com/pigeon-phy-modulation/#whitening">LCG</a></td>
<td>3149721335</td>
<td>LCG "RNG" whitening constant (randomly selected)</td>
</tr>
<tr>
<td><a href="https://k3xec.com/pigeon-phy-preamble/">Preamble</a></td>
<td>seq=16, order=4, count=2</td>
<td>(this is as-written in the preamble post)</td>
</tr>
<tr>
<td><a href="https://k3xec.com/pigeon-phy-modulation/">Modulation</a></td>
<td>QPSK/QAM-4</td>
<td>2 bits per data subcarrier</td>
</tr>
<tr>
<td><a href="https://k3xec.com/pigeon-background-frequency-domain/">FFT Size</a></td>
<td>64</td>
<td></td>
</tr>
<tr>
<td>Cyc Len</td>
<td>16</td>
<td>Cyclic Prefix length (16 IQ samples)</td>
</tr>
<tr>
<td>Symbols</td>
<td>168</td>
<td>Number of OFDM Symbols</td>
</tr>
<tr>
<td><a href="https://k3xec.com/pigeon-phy-ldpc/">LDPC Table</a></td>
<td>802.3an</td>
<td>(this is as-written in the ldpc post)</td>
</tr>
<tr>
<td>Raw Bits</td>
<td>14448</td>
<td>1806 bytes (168 symbols, 86 data bits per symbol)</td>
</tr>
<tr>
<td>LDPC Count</td>
<td>7</td>
<td>Number of packed LDPC encoded messages</td>
</tr>
<tr>
<td>Data Bits</td>
<td>12061</td>
<td>1507 bytes</td>
</tr>
</tbody>
</table>
<p>The last bit to describe here is the
<a href="https://k3xec.com/pigeon-phy-symbol/#the-subcarrier-plan">Subcarrier Plan</a>. The plan is ordered
“negative first” (meaning the 0th bin in-memory is the most negative frequency
domain bin of the fft), and
within a Mode A OFDM symbol, there are 64
<a href="https://k3xec.com/pigeon-background-frequency-domain/">frequency domain bins</a> (so, just to
make it explicit: 64 ‘subcarrier usages’ that make up our Mode A
‘subcarrier plan’).</p>
<p>We’ll follow the same structure and conventions that we went through in
the post all about <a href="https://k3xec.com/pigeon-phy-symbol/">OFDM Symbols</a> – which means,
we’ll need to place our guard bins, data bins, and pilot bins. I’ll include
a copy-paste-able version of the images to follow at the end.</p>
<h1 id="guard-bins">Guard Bins</h1>
<p>First up, let’s place our <a href="https://k3xec.com/pigeon-phy-symbol/#guard-subcarrier">guard bins</a>.
As we’ve already gone over, we’re looking to clear some space right up against
the high and low end of the frequency range, so let’s go ahead and do that:</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/mode-a-guard.png" /></p>
<p>I gave up the center bin (0 Hz) and 8 of the 64 bits on each side (1/4 of the
signal!) to give myself a bit of elbow room. This is perhaps definitely a bit
overkill, but it’s been an extremely robust choice. If you multiply that
through, this accounts for 312.5 kHz of frequency domain “padding” at the high
and low end of the bandwidth, or 625.0 kHz of bandwidth which is not to be used.</p>
<h1 id="pilot-bins">Pilot Bins</h1>
<p>Next up was the pilot bins. We’ve <a href="https://k3xec.com/pigeon-phy-symbol/#pilot-subcarrier">already gone
over</a> the purpose (<a href="https://k3xec.com/pigeon-phy-estimate/">and
use</a>) of our pilots, but I’ve found there to be an art
to the placement of the pilots. Interpolation between pilot bins has turned out
to be very reliable, but extrapolation, on the other hand, has been a major
pain, for reasons I don’t fully understand yet.</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/mode-a-pilot.png" /></p>
<p>My intent in placement was to pick out roughly even stretches of data bins
bracketed between pilots, with as few data subcarriers as practical “outside”
of a pilot (using extrapolation). I’ve played a bit with my AGWN simulator(s),
as well as logging errors between two SDRs, and the configuration I have this
in has been fairly resillant (for whatever reason), and withstood a few rounds
of tweaking.</p>
<h1 id="data-bins">Data Bins</h1>
<p>Almost as an afterthought – all of the remaining bins become
<a href="https://k3xec.com/pigeon-phy-symbol/#data-subcarrier">data bins</a>.</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/mode-a-data.png" /></p>
<p>This puts the total number of data bins at 43, which, since Mode A carries data
in QPSK/QAM-4 (two bits per data subcarrier), means we can carry 86 bits of
data per OFDM symbol. That fairly modest capacity is largely due to the
modulation scheme (or fft size, but increasing that has been … fraught) we’re
using for Mode A – but I’ve made up for it by including <code>168</code> OFDM symbols
in a single burst in order to have enough data to carry IP traffic without
splitting the packet into two bursts.</p>
<p>With all that designed and on paper, we’re ready to start to tackle the next
layer up – our Layer 2, named, creatively, “link”.</p>
<p><a href="https://k3xec.com/pigeon-link/">Let’s send some link layer data →</a></p>
<hr />
<div class="hz-alert-warning">
<b>Following along at home?</b> Nice! As promised, I've put the full plan
copy-pasted from the pigeon source tree below so that no one has to feel
the need to transcribe this from the images above. Unlike most of the things
I've "left to the reader", manual transcription from images is not a
particularly useful task for anyone to do.
</div>
<p>The following table is Mode A’s OFDM Subcarrier Plan. This is in negative
first ordering (meaning the 0th member is the most negative fft bin,
and the Nth is the highest frequency fft bin).</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span>SubcarrierPlan([
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Pilot(<span style="color: #a6e22e;">iq!</span>(<span style="color: #f92672;">-</span><span style="color: #ae81ff;">1.0</span>, <span style="color: #ae81ff;">0.0</span>)),
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Pilot(<span style="color: #a6e22e;">iq!</span>(<span style="color: #ae81ff;">1.0</span>, <span style="color: #ae81ff;">0.0</span>)),
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Pilot(<span style="color: #a6e22e;">iq!</span>(<span style="color: #ae81ff;">0.0</span>, <span style="color: #f92672;">-</span><span style="color: #ae81ff;">1.0</span>)),
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Pilot(<span style="color: #a6e22e;">iq!</span>(<span style="color: #ae81ff;">0.0</span>, <span style="color: #ae81ff;">1.0</span>)),
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Data,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span> Guard,
</span></span><span style="display: flex;"><span>])
</span></span></code></pre></div><div class="hz-alert-warning">
<b>Still following along at home?</b> Nicer! I've put the
preamble sequence
<a href="https://k3xec.com/pigeon-phy-preamble/">promised previously</a>
copy-pasted from the pigeon source tree below in case that's actually
important (I don't think it is, but...).
</div>
<p>The following table is Mode A’s frequency-domain preamble. This is, as above,
in negative first ordering. I don’t actually think these values matter much
(at all)? – but in case they do, here’s what I have. I muck with these a lot
and haven’t found many changes in quality of detection or frequency correction
yet.</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span>[
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::new(<span style="color: #ae81ff;">0.0</span>, <span style="color: #ae81ff;">0.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::new(<span style="color: #ae81ff;">0.0</span>, <span style="color: #ae81ff;">0.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">2.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">3.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">4.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">5.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">6.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">7.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">8.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">9.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">10.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">11.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">12.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::polar((<span style="color: #66d9ef;">TAU</span> <span style="color: #f92672;">/</span> <span style="color: #ae81ff;">13.0</span>) <span style="color: #f92672;">*</span> <span style="color: #ae81ff;">13.0</span>, <span style="color: #ae81ff;">1.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::new(<span style="color: #ae81ff;">0.0</span>, <span style="color: #ae81ff;">0.0</span>),
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">IQ</span>::new(<span style="color: #ae81ff;">0.0</span>, <span style="color: #ae81ff;">0.0</span>),
</span></span><span style="display: flex;"><span>]
</span></span></code></pre></div><p><a href="https://k3xec.com/pigeon-link/">Let’s send some link layer data →</a></p>
       </div>
       <p class="date">
        <a href="https://k3xec.com/pigeon-phy-modes/">08 September, 2026 02:00PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
     <div id="https://k3xec.com/pigeon-phy-decoder/" class="https://k3xec.com/pigeon-phy-decoder/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://k3xec.com/pigeon-phy-decoder/">wrapping it all up (Part 8/12) 🕊️</a>
       </h3>
       <div class="content">
        <div class="hz-alert-ok">
🕊️ This post is part of a series called "Pigeon". If this is the first post
you've found, it'd be worth reading the
<a href="https://k3xec.com/pigeon/">intro post</a> first and then looking over
<a href="https://k3xec.com/tags/pigeon/">all posts in the series</a>.<br />
<br />
<b>If you're looking for it, the intro to the PHY (layer 1) that this belongs at a
high level and listing of the parts that make it up (including this one) is on
the <a href="https://k3xec.com/pigeon-phy/">phy post</a></b>.
</div>
<p>The time has come.</p>
<p>If you’re following along at home, we now have all the basics we need to glue
these parts together and see what this looks like.</p>
<p>We’re going to build the highest-level constructs for the PHY in code –
something that takes some number of bytes in and writes out IQ samples fit for
transmit over the airwaves (we’ll call this the <code>Encoder</code>), and something that
takes chunks of IQ samples in, writing out decoded bytes (which we’ll call the
<code>Decoder</code>).</p>
<h1 id="encoder">Encoder</h1>
<p>Let’s begin with the <code>Encoder</code>, since it’s slightly less involved. I’ve tried
to make this a bit more accessible by drawing a diagram out before describing
the order of operations, so that it’s possible to follow along visually.</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/encoder.png" /></p>
<p>While the process here can look like a lot, it’s really not that bad.
We begin by taking the incoming bytes, converting the bytes into bits, and chunk
those bits into parts which are sized to fit completely within an LDPC message. We will then
<a href="https://k3xec.com/pigeon-phy-ldpc/">encode incoming data into LDPC messages</a>, using our
configured LDPC Matrix (the table we appropriated from 802.3an). Next, we
<a href="https://k3xec.com/pigeon-phy-modulation/#data-whitening">apply whitning</a> over all the bits in our
encoded (and packed) LDPC messages, using our configured whitening constant. In
the case of QPSK, pairs of bits will then be
<a href="https://k3xec.com/pigeon-phy-modulation/">modulated into a QAM subcarrier</a>, where each
QAM point represents a range of bits in the message. We’ll go through each of
those modulated IQ subcarriers, and
<a href="https://k3xec.com/pigeon-phy-symbol/#creating-a-burst">set each corresponding data subcarrier</a>
in order, for each OFDM symbol contained in the pigeon Burst. The preamble configuration
is then used to generate (or, more likely, can be used at startup to precompute)
the <a href="https://k3xec.com/pigeon-phy-preamble/">Schmidl-Cox preamble</a>, which is written to the
first IQ samples in our output IQ buffer. Finally, we will do a series of
inverse FFT operations to convert each
<a href="https://k3xec.com/pigeon-phy-symbol/#going-from-iq-to-a-burst-and-back-again">OFDM symbol to the time domain, including their cyclic prefix</a>.</p>
<p>Let’s take a look at doing that, but in code this time now:</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span><span style="color: #75715e;">// (lightly edited for clarity)
</span></span></span><span style="display: flex;"><span><span style="color: #66d9ef;">impl</span> Encoder {
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">..</span>
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #e6db74;">/// Encode the provided bits into the output time-domain IQ samples.
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">fn</span> <span style="color: #a6e22e;">encode</span>(
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span><span style="color: #66d9ef;">mut</span> self,
</span></span><span style="display: flex;"><span> dst: <span style="color: #66d9ef;">&amp;</span><span style="color: #a6e22e;">mut</span> [<span style="color: #66d9ef;">IQ</span>],
</span></span><span style="display: flex;"><span> src: <span style="color: #66d9ef;">&amp;</span><span style="color: #a6e22e;">Vector</span>,
</span></span><span style="display: flex;"><span> ) -&gt; Result<span style="color: #f92672;">&lt;</span>Burst, Error<span style="color: #f92672;">&gt;</span> {
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> src <span style="color: #f92672;">=</span> {
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> <span style="color: #66d9ef;">mut</span> raw <span style="color: #f92672;">=</span> Vector::new(self.fec.message_len());
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// Set `raw`'s data bits, compute and set LDPC
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// checkbits.
</span></span></span><span style="display: flex;"><span> self.fec.add(<span style="color: #f92672;">&amp;</span><span style="color: #66d9ef;">mut</span> raw, src);
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// Apply whitening, and return
</span></span></span><span style="display: flex;"><span> raw.xor(<span style="color: #f92672;">&amp;</span>self.whitening)
</span></span><span style="display: flex;"><span> };
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// copy in the precomputed schmidl-cox preamble to `dst`
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> preamble_len <span style="color: #f92672;">=</span> self.preamble_iq.len();
</span></span><span style="display: flex;"><span> dst[<span style="color: #f92672;">..</span>preamble_len].copy_from_slice(<span style="color: #f92672;">&amp;</span>self.preamble_iq);
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// allocate a new (frequency domain) 'Burst' container.
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> <span style="color: #66d9ef;">mut</span> burst <span style="color: #f92672;">=</span> Burst::new(
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span>self.mode.ofdm.plan,
</span></span><span style="display: flex;"><span> self.mode.ofdm.symbols
</span></span><span style="display: flex;"><span> );
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// modulate bits from 'src' as iq, and set each
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// data subcarrier for each ofdm symbol in the
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// burst.
</span></span></span><span style="display: flex;"><span> self.burst_encoder.multiplex(<span style="color: #f92672;">&amp;</span><span style="color: #66d9ef;">mut</span> burst, <span style="color: #f92672;">&amp;</span>src);
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// convert from frequency-domain data into time
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// domain iq samples, writing out ofdm symbols
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// and cyclic prefixes to `dst`.
</span></span></span><span style="display: flex;"><span> self.burst_encoder
</span></span><span style="display: flex;"><span> .transform(<span style="color: #f92672;">&amp;</span><span style="color: #66d9ef;">mut</span> dst[preamble_len<span style="color: #f92672;">..</span>], <span style="color: #f92672;">&amp;</span>burst)<span style="color: #f92672;">?</span>;
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// normalize all IQ samples; the maximum magnitude
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// in the IQ buffer may be very small, which weakens
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// our transmitted signal. Scale all IQ samples such
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// that the maximum IQ sample magnitude will be '1.0'.
</span></span></span><span style="display: flex;"><span> dst.norm();
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> Ok(burst)
</span></span><span style="display: flex;"><span> }
</span></span><span style="display: flex;"><span>}
</span></span></code></pre></div><p>Using the <code>Encoder</code> should hopefully be fairly straightforward – we’ll
give it a bag of bytes, and get back some IQ samples that we can ask our
nearest SDR to transmit.</p>
<p>As for what happens on the other end?</p>
<h1 id="decoder">Decoder</h1>
<p>Next up is the mirror image of our <code>Encoder</code> – the, imaginatively named,
<code>Decoder</code>. The <code>Decoder</code> is slightly more involved (since it has to find the
packet in the IQ stream, as well as correct for channel error(s)), so we’ll do
the same thing as above – start with a diagram. My hope is going over the
<code>Encoder</code> first helps us only really focus on the “new” stuff, otherwise it
should feel like running the <code>Encoder</code> backwards.</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/decoder.png" /></p>
<p>Here, we start with an incoming stream of IQ, where we will process
<a href="https://k3xec.com/pigeon-phy-detect/#using-the-scanner">scan detections</a> as they come in
from our Schmidl-Cox detector and burst Scanner. This will give us a “snippit”
of IQ, sized to exactly our Burst. We’ll begin to correct our IQ samples by
first doing
<a href="https://k3xec.com/pigeon-phy-estimate/#frequency-offset-calculations">frequency estimation and correction</a>
in the time domain using our preamble and ofdm configuration. We’ll then do
<a href="https://k3xec.com/pigeon-phy-symbol/#going-from-iq-to-a-burst-and-back-again">a series of inverse FFTs to extract each OFDM symbol</a>
in our <code>Burst</code>, where we can then do
<a href="https://k3xec.com/pigeon-phy-estimate/#channel-estimation">channel estimation and correction</a>.
With the OFDM symbols (hopefully) good enough, we can now
<a href="https://k3xec.com/pigeon-phy-modulation/">map each data subcarrier back to bits</a>, and
<a href="https://k3xec.com/pigeon-phy-modulation/#data-whitening">unapply whitning</a>. The resulting
bits are then chunked back up into <a href="https://k3xec.com/pigeon-phy-ldpc/">LDPC messages</a>,
which are then checked, and concatanated data extracted. Finally the bits
are turned back into bytes, which are written to our output buffer.</p>
<p>However, before we get into the code to do this – there’s one last detail. We
know bursts won’t overlap (if they do, it’s likely not possible to recover
right now – even though other PHYs can and do), so any time we see something we believe
to be a burst, we can skip ahead by the burst’s (constant) length within
the IQ, and avoid trying to decode anything else in there.</p>
<p>The nice side-effect here is this also gives us an interesting property for the
<code>Decoder</code> – namely, we know the maximum number of <code>Burst</code> detections we can
get for a given block of incoming IQ data if they were packed end-to-end – and
we can pre-allocate the memory we need, avoiding allocations for every
demodulation attempt (which may or may not even be a valid Burst).</p>
<p>This pre-allocated block of memory to hold the burst’s data is something that
I’ve called a <code>frame buffer</code> internally. Each frame buffer contains exactly
sized buffers to hold decoded information from the <code>burst</code> – an <code>iq</code> buffer
that is exactly the same number of <code>samples</code> required to encode the preamble
and data, exactly the number of bits needed to store pre and post FEC data,
pre-allocated byte array, etc.</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/phy-scox-frame-buffer.png" /></p>
<p>Not shockingly, the code looks like this:</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span><span style="color: #75715e;">#[derive(Clone)]</span>
</span></span><span style="display: flex;"><span><span style="color: #66d9ef;">pub</span> <span style="color: #66d9ef;">struct</span> <span style="color: #a6e22e;">FrameBuffer</span> {
</span></span><span style="display: flex;"><span> <span style="color: #e6db74;">/// Corrected IQ samples
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">pub</span> samples: <span style="color: #a6e22e;">Samples</span>,
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #e6db74;">/// post-correction OFDM burst
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">pub</span> burst: <span style="color: #a6e22e;">Burst</span>,
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #e6db74;">/// demodulated bits from the OFDM burst
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">pub</span> bits: <span style="color: #a6e22e;">Vector</span>,
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #e6db74;">/// demodulated bits from the OFDM burst,
</span></span></span><span style="display: flex;"><span> <span style="color: #e6db74;">/// after FEC, and cleaned
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">pub</span> raw_bits: <span style="color: #a6e22e;">Vector</span>,
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #e6db74;">/// Layer 2 contents of the Frame
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">pub</span> contents: Vec<span style="color: #f92672;">&lt;</span><span style="color: #66d9ef;">u8</span><span style="color: #f92672;">&gt;</span>,
</span></span><span style="display: flex;"><span>}
</span></span></code></pre></div><p>Of course, that alone is handy – but we need to use them. So let’s go ahead
and do what we promised above – each <code>Decoder</code> uses a fixed number of
pre-allocated <code>FrameBuffer</code>s to store packets in-flight, packed into what
is, creatively, called <code>FrameBuffers</code> within my code.</p>
<p><img alt="" src="https://k3xec.com/imgs/pigeon/phy-scox-decoder.png" /></p>
<p>As an aside, I likely should have called this a
<a href="https://en.wikipedia.org/wiki/Memory_pool">Memory Pool</a>, since that’s the
<a href="https://pkg.go.dev/sync#Pool">common</a>
<a href="https://mohitmishra786.github.io/chessman/2024/12/29/Understanding-NGINX-Worker-Architecture.html#memory-architecture-and-resource-management">and accepted</a>
name for this design pattern – but being stuck with unfortunate names is
the burden of those of us who stumble into sensible ideas over time. The only
nuance here is that I use the pools strictly sequentially – we only “<code>save</code>”
the <code>FrameBuffer</code> if the LDPC checksum is correct, allowing us to only keep
track of how many successful packets we have and being able to get the valid
<code>FrameBuffer</code>s, rather than storing a handle to each <code>FrameBuffer</code> as we go –
a promise that most memory pools do not make, since blocks can usually be
taken and returned in any order.</p>
<p>Let’s go ahead and do the whole <code>Decoder</code> dance now:</p>
<div class="highlight"><pre tabindex="0"><code class="language-rust"><span style="display: flex;"><span><span style="color: #75715e;">// (lightly edited for clarity)
</span></span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #66d9ef;">impl</span> Decoder {
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">..</span>
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #e6db74;">/// Process incoming IQ for Pigeon Bursts, and
</span></span></span><span style="display: flex;"><span> <span style="color: #e6db74;">/// demodulate them.
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">pub</span> <span style="color: #66d9ef;">fn</span> <span style="color: #a6e22e;">decode</span>(
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span><span style="color: #66d9ef;">mut</span> self,
</span></span><span style="display: flex;"><span> buf: <span style="color: #66d9ef;">&amp;</span>[<span style="color: #66d9ef;">IQ</span>],
</span></span><span style="display: flex;"><span> ) -&gt; Result<span style="color: #f92672;">&lt;</span>Vec<span style="color: #f92672;">&lt;</span>(Detection, <span style="color: #f92672;">&amp;</span>FrameBuffer)<span style="color: #f92672;">&gt;</span>, Error<span style="color: #f92672;">&gt;</span> {
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> <span style="color: #66d9ef;">mut</span> ret <span style="color: #f92672;">=</span> Vec::new();
</span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> mode <span style="color: #f92672;">=</span> self.scanner.mode().clone();
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// reset the "valid frame buffer count" back to 0
</span></span></span><span style="display: flex;"><span> self.frames.reset();
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// call the scanner and get scan detections for
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// this block of iq (`buf`)
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">for</span> detection <span style="color: #66d9ef;">in</span> self.scanner.scan(buf) {
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// for each detection, we're (only) going to process
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// the iq snippit, but pass along the metadata
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// such as SNR.
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> ScanDetection {
</span></span><span style="display: flex;"><span> snippit,
</span></span><span style="display: flex;"><span> snr,
</span></span><span style="display: flex;"><span> range,
</span></span><span style="display: flex;"><span> m,
</span></span><span style="display: flex;"><span> } <span style="color: #f92672;">=</span> detection;
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// grab the next free frame buffer to work within.
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> frame_buffer <span style="color: #f92672;">=</span> self.frames.next_mut();
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// Copy the snippit into the frame buffer (a mutable
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// location)
</span></span></span><span style="display: flex;"><span> frame_buffer.samples.copy_from_slice(snippit);
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// estimate the frequency offset based on the
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// Burst's Schmidl-Cox preamble.
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> preamble_fo <span style="color: #f92672;">=</span> preamble::estimate_frequency_offset(
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span>mode.preamble,
</span></span><span style="display: flex;"><span> mode.rate,
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span>frame_buffer.samples[<span style="color: #f92672;">..</span>mode.preamble.samples()],
</span></span><span style="display: flex;"><span> );
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// Shift the IQ stream by the estimated frequency
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// offset -- hopefully we're closer to 0Hz
</span></span></span><span style="display: flex;"><span> frame_buffer.samples.shift(mode.rate, preamble_fo);
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// estimate the frequency offset based on the
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// each burst's **cyclic prefix** -- exactly like
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// we did with the Burst Schmidl-Cox preamble,
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// but this time on each OFDM symbol.
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">let</span> ofdm_fo <span style="color: #f92672;">=</span> ofdm::estimate_frequency_offset(
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span>mode.ofdm,
</span></span><span style="display: flex;"><span> mode.rate,
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span>frame_buffer.samples[mode.preamble.samples()<span style="color: #f92672;">..</span>],
</span></span><span style="display: flex;"><span> );
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// Shift the IQ stream closer yet; hopefully this
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// is a very small nudge even closer still to 0Hz.
</span></span></span><span style="display: flex;"><span> frame_buffer.samples.shift(mode.rate, ofdm_fo);
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// Do a bunch of inverse fft operations for each
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// OFDM symbol, filling the frequency-domain Symbol
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// structs in `frame_buffer.burst` (Burst) struct.
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">//
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// this will also do channel estimation and
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// correction before returning.
</span></span></span><span style="display: flex;"><span> self
</span></span><span style="display: flex;"><span> .decoder
</span></span><span style="display: flex;"><span> .transform(
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span><span style="color: #66d9ef;">mut</span> frame_buffer.burst,
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span>frame_buffer.samples[mode.preamble.samples()<span style="color: #f92672;">..</span>],
</span></span><span style="display: flex;"><span> )<span style="color: #f92672;">?</span>;
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// "demultiplex" each data subcarrier's frequency-domain
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// IQ constellation point, setting the correct bit range.
</span></span></span><span style="display: flex;"><span> self.decoder.demultiplex(
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span><span style="color: #66d9ef;">mut</span> frame_buffer.raw_bits,
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span>frame_buffer.burst
</span></span><span style="display: flex;"><span> );
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// unapply whitening by XOR-ing the buffer with
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// the well-known whitening vector.
</span></span></span><span style="display: flex;"><span> frame_buffer.raw_bits <span style="color: #f92672;">=</span> frame_buffer.raw_bits.xor(
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span>self.whitening);
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// verify that the LDPC message(s) are all correct,
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// and if so, concatanate the the data bits (no check
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// bits) to the `bits` vector.
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">if</span> self.fec.decode(
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span><span style="color: #66d9ef;">mut</span> frame_buffer.bits,
</span></span><span style="display: flex;"><span> <span style="color: #f92672;">&amp;</span>frame_buffer.raw_bits
</span></span><span style="display: flex;"><span> ).is_err() {
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// this is where invalid packets fail. we gave it a good go.
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// next packet please.
</span></span></span><span style="display: flex;"><span> <span style="color: #66d9ef;">continue</span>;
</span></span><span style="display: flex;"><span> }
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// copy the raw bits out, as bytes, to the `contents` buffer.
</span></span></span><span style="display: flex;"><span> frame_buffer.bits.copy_as_bytes(<span style="color: #f92672;">&amp;</span><span style="color: #66d9ef;">mut</span> frame_buffer.contents);
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// store metadata/metrics on the demodulation.
</span></span></span><span style="display: flex;"><span> ret.push(Detection {
</span></span><span style="display: flex;"><span> m,
</span></span><span style="display: flex;"><span> snr,
</span></span><span style="display: flex;"><span> index: <span style="color: #a6e22e;">range</span>.start,
</span></span><span style="display: flex;"><span> });
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// save the contents of this frame buffer (don't
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// reuse this buffer next go-around).
</span></span></span><span style="display: flex;"><span> self.frames.save();
</span></span><span style="display: flex;"><span> }
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span> <span style="color: #75715e;">// We're going to take out the borrow on the frame at
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// the end since we don't want to deal with telling the
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// compiler via code gymnastics that the mut and non-mut
</span></span></span><span style="display: flex;"><span> <span style="color: #75715e;">// borrows are OK since they're non-overlapping.
</span></span></span><span style="display: flex;"><span> Ok(ret.into_iter().zip(self.frames.iter()).collect())
</span></span><span style="display: flex;"><span> }
</span></span><span style="display: flex;"><span>}
</span></span></code></pre></div><p>Phew. That was kinda a lot. In fact it’s basically the whole thing. This
function is as close to “how do you read an OFDM packet” as it gets, and
perhaps the most important part of this whole series. Beyond that, though, this
is a huge conceptual unlock. This means we now have an <strong>incredibly</strong> powerful
primitive; the ability to take bytes and go to/from IQ samples over the air.</p>
<p><a href="https://k3xec.com/pigeon-phy-modes/">Let’s talk about pigeon modes →</a></p>
       </div>
       <p class="date">
        <a href="https://k3xec.com/pigeon-phy-decoder/">08 September, 2026 02:00PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://www.ezini.eu/tags/pdo" title="Enrico Zini: posts with tag pdo">Enrico Zini</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.ezini.eu/blog/2026/financial-risks-in-2026_hide\" onClick=\"exclude( 'https://www.ezini.eu/blog/2026/financial-risks-in-2026' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.ezini.eu/blog/2026/financial-risks-in-2026_show\"  style=\"display:none;\" onClick=\"show( 'https://www.ezini.eu/blog/2026/financial-risks-in-2026' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.ezini.eu/blog/2026/financial-risks-in-2026" class="https://www.ezini.eu/blog/2026/financial-risks-in-2026">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.ezini.eu/blog/2026/financial-risks-in-2026">Financial risks in 2026</a>
       </h3>
       <div class="content">
        <p>I asked the banker who is my reference at the bank something like this:</p>
<blockquote>
<p>Give that we are talking about the consequences of the tantrum of a fascist
foreign government, what happened to them (who are also people close and dear
to me), in some future can very well happen to me.</p>
<p>Suddenly my risk profile shot up under the roof.</p>
<p>What do you suggest me to do? Should I find a trusted source of gold bullions
to bury under the cellar at home?</p>
</blockquote>
<p>The answer was something like this:</p>
<blockquote>
<p>Sadly YES, given that the USA have a sort of financial monopoly they can
entitle themselves to arbitrarily define a person/organization as a terrorist
without any trial or judicial course, and as a consequence apply sanctions
that cannot be effectively counteracted, not even abroad.</p>
</blockquote>
<p>I didn't have this in my 2026 bingo card, but here we are.</p>
<hr />
<p>For more details, see:</p>
<ul>
<li>the <a href="https://www.inventati.org/campaign/press">A/I press release</a> under "12. Banca Etica: the sanction also affected an Italian bank account"</li>
<li>the <a href="https://www.bancaetica.it/area-stampa/autistici-inventati-banca-etica-condanna-uso-improprio-ofac-valutazioni-per-non-chiudere-il-conto/">public statement from the bank</a> (in Italian)</li>
</ul>
<p>For some broader context on this kind of actions from the USA, see also:</p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Francesca_Albanese#Sanctions_by_the_United_States">The same sactions against UN Special Rapporteur Francesca Albanese</a></li>
<li><a href="https://en.wikipedia.org/wiki/Executive_Order_14203">Executive Order 14203 titled "Imposing Sanctions on the International Criminal Court"</a></li>
</ul>
       </div>
       <p class="date">
        <a href="https://www.ezini.eu/blog/2026/financial-risks-in-2026">08 September, 2026 07:05AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 07, 2026</h1>
     

    <div class="channel">
     
      
     <a href="https://bits.debian.org/">
      <img class="face" src="heads/dwn.png"
            width="77" height="85"
            alt="hackergotchi for Bits from Debian" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://bits.debian.org/" title="Bits from Debian">Bits from Debian</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://bits.debian.org/2026/09/new-developers-2026-09.html_hide\" onClick=\"exclude( 'https://bits.debian.org/2026/09/new-developers-2026-09.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://bits.debian.org/2026/09/new-developers-2026-09.html_show\"  style=\"display:none;\" onClick=\"show( 'https://bits.debian.org/2026/09/new-developers-2026-09.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://bits.debian.org/2026/09/new-developers-2026-09.html" class="https://bits.debian.org/2026/09/new-developers-2026-09.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://bits.debian.org/2026/09/new-developers-2026-09.html">New Debian Developers and Maintainers (July and August 2026)</a>
       </h3>
       <div class="content">
        <p>The following contributor got their Debian Developer account in the last two
months:</p>
<ul>
<li>Nicolas Peugnet (nicolasp)</li>
</ul>
<p>The following contributors were added as Debian Maintainers in the last two
months:</p>
<ul>
<li>Antoine Lassagne</li>
<li>Ivan Hu</li>
<li>Jesse Rhodes</li>
<li>Haolin Xue</li>
<li>Léo Haf</li>
<li>Rony João de Sousa</li>
<li>Luke Yasuda</li>
<li>Darshaka Pathirana</li>
</ul>
<p>Congratulations!</p>
       </div>
       <p class="date">
        <a href="https://bits.debian.org/2026/09/new-developers-2026-09.html">07 September, 2026 09:00PM</a>
        
         by Jean-Pierre Giraud
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <a href="https://www.chiark.greenend.org.uk/~cjwatson/blog/">
      <img class="face" src="heads/cjwatson.png"
            width="70" height="82"
            alt="hackergotchi for Colin Watson" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://www.chiark.greenend.org.uk/~cjwatson/blog/" title="Colin Watson's blog - planet-debian">Colin Watson</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.chiark.greenend.org.uk/~cjwatson/blog/activity-2026-08.html_hide\" onClick=\"exclude( 'https://www.chiark.greenend.org.uk/~cjwatson/blog/activity-2026-08.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.chiark.greenend.org.uk/~cjwatson/blog/activity-2026-08.html_show\"  style=\"display:none;\" onClick=\"show( 'https://www.chiark.greenend.org.uk/~cjwatson/blog/activity-2026-08.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.chiark.greenend.org.uk/~cjwatson/blog/activity-2026-08.html" class="https://www.chiark.greenend.org.uk/~cjwatson/blog/activity-2026-08.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.chiark.greenend.org.uk/~cjwatson/blog/activity-2026-08.html">Free software activity in August 2026</a>
       </h3>
       <div class="content">
        <p>My Debian contributions this month were all <a href="https://www.freexian.com/about/debian-contributions/">sponsored</a> by Freexian.</p>
<p>You can also support my work directly via <a href="https://liberapay.com/cjwatson">Liberapay</a> or <a href="https://github.com/sponsors/cjwatson">GitHub Sponsors</a>.</p>
<h2>Personal note</h2>
<p>This month, my Dad unexpectedly passed away after a short illness.  As a result I obviously got less work done than usual, and I still have a lot to take care of (since I’m the executor of his will, as well as helping with funeral arrangements) while grieving and generally having less focus and energy.  Having routine work to do is one of the ways I cope with this sort of thing, but all the same, I hope people will bear with me and maybe remind me if I seem to be dropping the ball on something you especially need.</p>
<h2><span class="caps">LLM</span> vote</h2>
<p>[Content note: strong opinions.]</p>
<p>I voted in <a href="https://www.debian.org/vote/2026/vote_002">General Resolution: <span class="caps">LLM</span> usage in Debian</a>.  My vote was pretty much the opposite of what ended up winning, so I’m quite disappointed.  My personal opinion is that LLMs are <a href="https://softwarecrisis.dev/letters/llmentalist/">cognitive hazards to their users</a> that impose ecological costs far out of proportion to their utility at a time when <a href="https://wmo.int/publication-series/state-of-global-climate/state-of-global-climate-2025">the world absolutely cannot afford them</a>.  When the impossible economics of the large commercial models are finally allowed to catch up with reality, I expect there to be <a href="https://www.wheresyoured.at/what-happens-if-openai-dies/">significant macroeconomic consequences</a>, and that people who have become dependent on them will have problems; and who knows what the copyright situation on their output really is.  I’m not convinced that <a href="https://blog.glyph.im/2026/09/but-what-about-video-games.html">local models</a> are better enough on these axes to be worth the costs.</p>
<p>Debian’s direct contribution to all that will be negligible on a global scale, and even the most radical proposals in the <span class="caps">GR</span> didn’t expect that we could do much about upstreams that have gone all-in on LLMs.  Even so, I’d hoped that my fellow developers might be more willing to lean on our position in the free software ecosystem to make at least a moderately radical statement.  Instead, we’ve at best presented an undistinguished fence-sitting position to the world, and further entrenched the idea that humans can reliably do a good job of reviewing the output of tools that are designed to produce output plausible to humans.  I certainly don’t trust my own code review skills that far.</p>
<p>Since I’ve never voluntarily used an <span class="caps">LLM</span> (not counting LLMs being foisted on me by things like search results, support chatbots, or incoming pull requests, regardless of whether I asked for them), and don’t intend to for the foreseeable future, I doubt this will change much for me in terms of the way I work.  The winning option is a very weak one that imposes no new requirements on developers, which means that it also does nothing to stop me continuing to reject <span class="caps">LLM</span>-generated material from Debian bug reports and merge requests in my areas of responsibility.  I know this probably won’t do much to satisfy people who have decided that Debian is slop now, but it’s the best I can do.</p>
<h2>OpenSSH</h2>
<p>I finally landed the <a href="https://www.chiark.greenend.org.uk/~cjwatson/blog/openssh-gssapi-split.html"><span class="caps">GSS</span>-<span class="caps">API</span> key exchange package split</a> in our OpenSSH packaging.  Here’s the <code>NEWS</code> entry:</p>
<div class="highlight"><pre><span></span><code><span class="nx">openssh</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">:</span><span class="m m-Double">10.4</span><span class="nx">p1</span><span class="o">-</span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="nx">unstable</span><span class="p">;</span><span class="w"> </span><span class="nx">urgency</span><span class="p">=</span><span class="nx">medium</span>

<span class="w">  </span><span class="nx">The</span><span class="w"> </span><span class="nx">openssh</span><span class="o">-</span><span class="nx">client</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="nx">openssh</span><span class="o">-</span><span class="nx">server</span><span class="w"> </span><span class="nx">packages</span><span class="w"> </span><span class="nx">no</span><span class="w"> </span><span class="nx">longer</span><span class="w"> </span><span class="nx">include</span><span class="w"> </span><span class="nx">GSS</span><span class="o">-</span><span class="nx">API</span>
<span class="w">  </span><span class="nx">authentication</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="nx">key</span><span class="w"> </span><span class="nx">exchange</span><span class="w"> </span><span class="nx">support</span><span class="p">;</span><span class="w"> </span><span class="nx">this</span><span class="w"> </span><span class="nx">adds</span><span class="w"> </span><span class="nx">pre</span><span class="o">-</span><span class="nx">authentication</span>
<span class="w">  </span><span class="nx">attack</span><span class="w"> </span><span class="nx">surface</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="nx">generally</span><span class="w"> </span><span class="nx">increases</span><span class="w"> </span><span class="nx">complexity</span><span class="p">,</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="nx">should</span><span class="w"> </span><span class="nx">only</span><span class="w"> </span><span class="nx">be</span><span class="w"> </span><span class="nx">used</span>
<span class="w">  </span><span class="k">where</span><span class="w"> </span><span class="nx">specifically</span><span class="w"> </span><span class="nx">needed</span><span class="p">.</span><span class="w">  </span><span class="nx">Users</span><span class="w"> </span><span class="nx">who</span><span class="w"> </span><span class="nx">need</span><span class="w"> </span><span class="nx">these</span><span class="w"> </span><span class="nx">features</span><span class="w"> </span><span class="nx">should</span><span class="w"> </span><span class="nx">install</span>
<span class="w">  </span><span class="nx">openssh</span><span class="o">-</span><span class="nx">client</span><span class="o">-</span><span class="nx">gssapi</span><span class="w"> </span><span class="k">or</span><span class="w"> </span><span class="nx">openssh</span><span class="o">-</span><span class="nx">server</span><span class="o">-</span><span class="nx">gssapi</span><span class="w"> </span><span class="nx">instead</span><span class="p">.</span>

<span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="nx">Colin</span><span class="w"> </span><span class="nx">Watson</span><span class="w"> </span><span class="p">&lt;</span><span class="nx">cjwatson</span><span class="err">@</span><span class="nx">debian</span><span class="p">.</span><span class="nx">org</span><span class="p">&gt;</span><span class="w">  </span><span class="nx">Sun</span><span class="p">,</span><span class="w"> </span><span class="mi">23</span><span class="w"> </span><span class="nx">Aug</span><span class="w"> </span><span class="mi">2026</span><span class="w"> </span><span class="mi">17</span><span class="p">:</span><span class="mi">39</span><span class="p">:</span><span class="mi">55</span><span class="w"> </span><span class="o">+</span><span class="mi">0100</span>
</code></pre></div>

<p>I <a href="https://salsa.debian.org/ssh-team/openssh/-/commit/4fec688eb614ba9ba758e66efa361285722dbdf8">fixed a flaky autopkgtest</a>.</p>
<p>I upgraded from 10.4p1 to 10.5p1, which was a good test of keeping <code>openssh</code> and the new <code>openssh-gssapi</code> source package in sync.</p>
<h2>PuTTY</h2>
<p>I upgraded from 0.84 to 0.85.</p>
<h2>Python packaging</h2>
<p>New upstream versions:</p>
<ul>
<li>anorack</li>
<li>djangorestframework (fixing a <a href="https://bugs.debian.org/1143299">build failure</a>)</li>
<li>isort</li>
<li>lazr.delegates (<a href="https://bugs.debian.org/1083458">dropping pkg_resources</a>)</li>
<li>lazr.restfulclient (<a href="https://bugs.debian.org/1083460">dropping pkg_resources</a>)</li>
<li>lazr.uri (<a href="https://bugs.debian.org/1083461">dropping pkg_resources</a>)</li>
<li>nbclient (fixing a <a href="https://bugs.debian.org/1138816">build failure</a>)</li>
<li>pydantic-settings</li>
<li>python-argcomplete</li>
<li>python-blockbuster</li>
<li>python-makefun (fixing a <a href="https://bugs.debian.org/1143367">build failure</a>)</li>
<li>python-persistent</li>
<li>python-pytest-run-parallel</li>
<li>python-time-machine</li>
<li>python-typing-inspection</li>
<li>wcwidth</li>
<li>zope.hookable</li>
<li>zope.i18nmessageid</li>
<li>zope.interface</li>
<li>zope.proxy</li>
<li>zope.security</li>
<li>zope.testrunner</li>
</ul>
<p>The version treadmill continues: we’ve just finished dropping Python 3.13 as a supported version, so now we’ve started working on enabling Python 3.15 as a supported version.  Maximiliano Curia has been <a href="https://lists.debian.org/debian-python/2026/08/msg00065.html">very helpfully driving this</a>.  I didn’t get as much done here as I’d have liked (see the top of this post), but I fixed a couple of packages:</p>
<ul>
<li><a href="https://bugs.debian.org/1145192">pydantic</a></li>
<li><a href="https://bugs.debian.org/1145112">python-typing-extensions</a></li>
</ul>
<p>Other build/test failures:</p>
<ul>
<li><a href="https://bugs.debian.org/1143283">anorack</a></li>
<li><a href="https://bugs.debian.org/1143285">buildbot</a></li>
<li><a href="https://bugs.debian.org/1144623">codespell</a></li>
<li><a href="https://bugs.debian.org/1144042">django-prometheus</a></li>
<li><a href="https://bugs.debian.org/1143359">pendulum</a></li>
<li><a href="https://bugs.debian.org/1143691">pydantic-core</a></li>
<li><a href="https://bugs.debian.org/1143365">python-argcomplete</a></li>
<li><a href="https://bugs.debian.org/1145426">python-ast-serialize</a></li>
<li><a href="https://bugs.debian.org/1143364">python-bcrypt</a></li>
<li><a href="https://bugs.debian.org/1143366">python-cross-web</a></li>
<li><a href="https://bugs.debian.org/1146106">python-datamodel-code-generator</a></li>
<li><a href="https://salsa.debian.org/python-team/packages/python-djangorestframework-flex-fields/-/commit/42cba56dd872332a1b713b914be0f15bd97e7d43">python-djangorestframework-flex-fields</a></li>
<li><a href="https://bugs.debian.org/1143369">python-openapi-core</a></li>
<li><a href="https://bugs.debian.org/1143469">python-treq</a></li>
<li><a href="https://bugs.debian.org/1143418">scikit-build-core</a></li>
</ul>
<p>I fixed some other bugs:</p>
<ul>
<li><a href="https://bugs.debian.org/1143722">python-librosa: Hardcoded Python 3.13 in rules file</a></li>
<li><a href="https://bugs.debian.org/1045218">python-makefun: Fails to build source after successful build</a></li>
</ul>
<h2>bugs.debian.org</h2>
<p>I deployed the fix for <a href="https://bugs.debian.org/979319">Invalid link rel=”canonical” on bugs.debian.org</a>.  In the process I found a few bugs in recent undeployed code and <a href="https://salsa.debian.org/debbugs-team/debbugs/-/compare/66628595f23bd079a748ad549dd986cd51dc498e...ecc8def8320b0f5297a2e31f9b3a051d164f5d59">fixed them</a>.</p>
       </div>
       <p class="date">
        <a href="https://www.chiark.greenend.org.uk/~cjwatson/blog/activity-2026-08.html">07 September, 2026 07:25PM</a>
        
         by Colin Watson
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <a href="https://daniel-lange.com/">
      <img class="face" src="heads/DLange.png"
            width="81" height="85"
            alt="hackergotchi for Daniel Lange" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://daniel-lange.com/" title="Daniel Lange's blog">Daniel Lange</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://daniel-lange.com/archives/197-Getting-AVIF-thumbnails-in-XFCE4-thunar-Debian-Trixie.html_hide\" onClick=\"exclude( 'https://daniel-lange.com/archives/197-Getting-AVIF-thumbnails-in-XFCE4-thunar-Debian-Trixie.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://daniel-lange.com/archives/197-Getting-AVIF-thumbnails-in-XFCE4-thunar-Debian-Trixie.html_show\"  style=\"display:none;\" onClick=\"show( 'https://daniel-lange.com/archives/197-Getting-AVIF-thumbnails-in-XFCE4-thunar-Debian-Trixie.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://daniel-lange.com/archives/197-Getting-AVIF-thumbnails-in-XFCE4-thunar-Debian-Trixie.html" class="https://daniel-lange.com/archives/197-Getting-AVIF-thumbnails-in-XFCE4-thunar-Debian-Trixie.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://daniel-lange.com/archives/197-Getting-AVIF-thumbnails-in-XFCE4-thunar-Debian-Trixie.html">Getting AVIF thumbnails in XFCE4 thunar (Debian Trixie)</a>
       </h3>
       <div class="content">
        <p>The <a href="https://en.wikipedia.org/wiki/AVIF" title="Wikipedia entry for AVIF">AVIF image</a> format gets more and more popular in the web dev community, so I needed to teach XFCE4's thunar (file manager) and Ristretto (image viewer) to thumbnail these.</p>

<p>Luckily that is not too hard:</p>

<p>Debian Trixie separates its <code>gdk-pixbuf</code> libraries slightly differently than previous versions. That's why it is not "automatically there". Ensure you have the <code>libavif-gdk-pixbuf</code> plugin and the tumbler service (which XFCE uses to process thumbnails):</p>

<div class="bash geshi" style="text-align: left;"><span style="color: #c20cb9; font-weight: bold;">sudo</span> apt <span style="color: #660033;">--update</span> <span style="color: #c20cb9; font-weight: bold;">install</span> libavif-gdk-pixbuf tumbler</div>

<p>Thunar has likely tried (and failed) to load your AVIF files before you installed the package, it will have saved a blank or "broken image" placeholder in a thumbnail cache directory. It will not attempt to regenerate them unless you clear this cache:</p>

<div class="bash geshi" style="text-align: left;"><span style="color: #666666; font-style: italic;"># Clear the thumbnail cache</span><br /><span style="color: #c20cb9; font-weight: bold;">rm</span> <span style="color: #660033;">-rf</span> ~<span style="color: #000000; font-weight: bold;">/</span>.cache<span style="color: #000000; font-weight: bold;">/</span>thumbnails<span style="color: #000000; font-weight: bold;">/*</span><br /><br /><span style="color: #666666; font-style: italic;"># Force-quit thunar and the tumblerd background service</span><br />thunar <span style="color: #660033;">-q</span><br />pkill tumblerd</div>

<p>Tumbled will restart on its own when it is needed. When you open thunar again and navigate to your image directory ... your AVIF images will now generate thumbnails automatically like the other image format did already.</p>

<p><img alt="Avif thumbnails in thunar" class="serendipity_image_center" height="613" src="https://daniel-lange.com/uploads/entries/avif_thumbnails.jpg" width="965" /></p>
       </div>
       <p class="date">
        <a href="https://daniel-lange.com/archives/197-Getting-AVIF-thumbnails-in-XFCE4-thunar-Debian-Trixie.html">07 September, 2026 08:50AM</a>
        
         by Daniel Lange
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://vincent.bernat.ch/en" title="Vincent Bernat">Vincent Bernat</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://vincent.bernat.ch/en/blog/2026-css-sidenotes_hide\" onClick=\"exclude( 'https://vincent.bernat.ch/en/blog/2026-css-sidenotes' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://vincent.bernat.ch/en/blog/2026-css-sidenotes_show\"  style=\"display:none;\" onClick=\"show( 'https://vincent.bernat.ch/en/blog/2026-css-sidenotes' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://vincent.bernat.ch/en/blog/2026-css-sidenotes" class="https://vincent.bernat.ch/en/blog/2026-css-sidenotes">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://vincent.bernat.ch/en/blog/2026-css-sidenotes">Sidenotes with CSS anchor positioning</a>
       </h3>
       <div class="content">
        <p>I am a heavy user of sidenotes:<sup id="fnref:phd"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:phd">1</a></sup> they keep optional content next to the
text instead of sending the reader to the bottom of the page and back. <a href="https://edwardtufte.github.io/tufte-css/" title="Tufte CSS">Tufte
CSS</a> renders them without JavaScript but only accepts inline content. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Anchor_positioning" title="CSS anchor positioning">CSS
anchor positioning</a>, now supported by recent browsers,<sup id="fnref:anchor-support"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:anchor-support">2</a></sup> is an
elegant alternative. Sidenotes can hold several blocks, still without
JavaScript, and fall back below the paragraph referencing them on narrow
viewports and older browsers.</p>
<p>In 2023, Eric Meyer demonstrated this technique in “<a href="https://meyerweb.com/eric/thoughts/2023/09/12/nuclear-anchored-sidenotes/" title="Nuclear Anchored Sidenotes">Nuclear Anchored
Sidenotes</a>.” The main improvement over other solutions is that the notes can
sit anywhere in the HTML document. You can place them after the paragraph
referencing them, as regular block elements for text browsers, screen readers,
feed readers, and reader mode to render them properly:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 465px;"><span class="lf-media-inner"><img alt="Sidenotes rendered in Lynx appear after the paragraph they are called from." class="lf-media" height="600" src="https://d2pzklc15kok91.cloudfront.net/images/css-sidenotes/lynx@1x.62cb4bfb14577a.png" width="465" /></span></div>Rendering in Lynx, a text browser</figure>
<p>When the viewport is too narrow or the browser does not support CSS anchor
positioning, you can style them so the reader can skip them or glance at them
without losing their position in the text:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 375px;"><span class="lf-media-inner"><img alt="Sidenotes rendered on a narrow viewport appear with a distinctive typography after the paragraph they are called from." class="lf-media lf-opaque" height="667" src="https://d2pzklc15kok91.cloudfront.net/images/css-sidenotes/narrow@1x.9afbc67e0bd4e9.png" width="375" /></span></div>Rendering below the paragraph on a narrow viewport</figure>
<p>Once the viewport is large enough, they appear in the margin, at the same
vertical position as the matching reference mark, unless they would collide with
a previous sidenote, as in the example below:<sup id="fnref:runt"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:runt">3</a></sup></p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 1427px;"><span class="lf-media-inner"><img alt="Sidenotes rendered on a large viewport appear in the margin. There are two of them. The first one is vertically aligned with the matching reference mark, while the second is rendered below as it would collide with the first otherwise." class="lf-media lf-opaque" height="570" src="https://d2pzklc15kok91.cloudfront.net/images/css-sidenotes/large@1x.b9822706e29afa.png" width="1427" /></span></div>Rendering in the margin on a large viewport</figure>
<p>The gist of CSS anchoring is to position an element relative to another
element—the anchor. For the sidenotes, the anchor is the reference mark. I use
the following markup, with a data attribute to specify the anchor name:</p>
<div class="language-html codehilite"><pre><span></span><code><span class="p">&lt;</span><span class="nt">sup</span> <span class="na">id</span><span class="o">=</span><span class="s">"fnref:YYY"</span> <span class="na">data-anchor</span><span class="o">=</span><span class="s">"--lf-sn-YYY"</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"#sidenote-YYY"</span><span class="p">&gt;</span>1<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">sup</span><span class="p">&gt;</span>
</code></pre></div>

<p>The matching note is an <code>&lt;aside&gt;</code> element carrying the same data attribute for
the anchor name. We put it after the paragraph holding the reference mark:</p>
<div class="language-html codehilite"><pre><span></span><code><span class="p">&lt;</span><span class="nt">aside</span> <span class="na">role</span><span class="o">=</span><span class="s">"note"</span> <span class="na">id</span><span class="o">=</span><span class="s">"sidenote-YYY"</span> <span class="na">data-anchor</span><span class="o">=</span><span class="s">"--lf-sn-YYY"</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">sup</span><span class="p">&gt;</span>1<span class="p">&lt;/</span><span class="nt">sup</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>A first paragraph.<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>A second paragraph.<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">aside</span><span class="p">&gt;</span>
</code></pre></div>

<p>On a narrow viewport or when the browser is too old for CSS anchoring, we style
the sidenote, which stays below its paragraph, with a muted color:</p>
<div class="language-css codehilite"><pre><span></span><code><span class="nt">aside</span><span class="o">[</span><span class="nt">role</span><span class="o">=</span><span class="s2">"note"</span><span class="o">]</span><span class="w"> </span><span class="p">{</span>
<span class="w">  </span><span class="k">margin-block</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">rlh</span><span class="p">;</span>
<span class="w">  </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#444</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>

<p>On a wide viewport and when the browser is recent enough, we move the sidenote
to the right margin:</p>
<div class="language-css codehilite"><pre><span></span><code><span class="p">@</span><span class="k">supports</span><span class="w"> </span><span class="o">(</span><span class="nt">anchor-name</span><span class="o">:</span><span class="w"> </span><span class="nt">attr</span><span class="o">(</span><span class="nt">data-anchor</span><span class="w"> </span><span class="nt">type</span><span class="o">(&lt;</span><span class="nt">custom-ident</span><span class="o">&gt;)))</span><span class="w"> </span><span class="p">{</span>
<span class="w">  </span><span class="p">@</span><span class="k">media</span><span class="w"> </span><span class="o">(</span><span class="nt">min-width</span><span class="o">:</span><span class="w"> </span><span class="nt">72rem</span><span class="o">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nt">main</span><span class="w"> </span><span class="p">{</span>
<span class="w">      </span><span class="k">position</span><span class="p">:</span><span class="w"> </span><span class="kc">relative</span><span class="p">;</span>
<span class="w">      </span><span class="nt">sup</span><span class="o">[</span><span class="nt">data-anchor</span><span class="o">]</span><span class="w"> </span><span class="p">{</span>
<span class="hll"><span class="w">        </span><span class="k">anchor-name</span><span class="p">:</span><span class="w"> </span><span class="nb">attr</span><span class="p">(</span><span class="n">data-anchor</span><span class="w"> </span><span class="nf">type</span><span class="p">(</span><span class="o">&lt;</span><span class="n">custom-ident</span><span class="o">&gt;</span><span class="p">));</span>
</span><span class="w">        </span><span class="c">/* → anchor-name: --lf-sn-YYY */</span>
<span class="w">      </span><span class="p">}</span>
<span class="w">      </span><span class="nt">aside</span><span class="o">[</span><span class="nt">role</span><span class="o">=</span><span class="s2">"note"</span><span class="o">][</span><span class="nt">data-anchor</span><span class="o">]</span><span class="w"> </span><span class="p">{</span>
<span class="hll"><span class="w">        </span><span class="k">anchor-name</span><span class="p">:</span><span class="w"> </span><span class="nv">--lf-sidenote</span><span class="p">;</span>
</span><span class="w">        </span><span class="k">position</span><span class="p">:</span><span class="w"> </span><span class="kc">absolute</span><span class="p">;</span>
<span class="hll"><span class="w">        </span><span class="k">position-anchor</span><span class="p">:</span><span class="w"> </span><span class="nb">attr</span><span class="p">(</span><span class="n">data-anchor</span><span class="w"> </span><span class="nf">type</span><span class="p">(</span><span class="o">&lt;</span><span class="n">custom-ident</span><span class="o">&gt;</span><span class="p">));</span>
</span><span class="w">        </span><span class="c">/* → position-anchor: --lf-sn-YYY */</span>
<span class="hll"><span class="w">        </span><span class="k">top</span><span class="p">:</span><span class="w"> </span><span class="nf">max</span><span class="p">(</span><span class="nf">anchor</span><span class="p">(</span><span class="kc">top</span><span class="p">),</span><span class="w"> </span><span class="nf">anchor</span><span class="p">(</span><span class="nv">--lf-sidenote</span><span class="w"> </span><span class="kc">bottom</span><span class="p">,</span><span class="w"> </span><span class="mi">-1</span><span class="kt">rlh</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="kt">rlh</span><span class="p">);</span>
</span><span class="w">        </span><span class="k">left</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="kt">%</span><span class="p">;</span>
<span class="w">        </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">2</span><span class="kt">rem</span><span class="p">;</span>
<span class="w">        </span><span class="k">width</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span><span class="kt">rem</span><span class="p">;</span>
<span class="w">        </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="kc">inherit</span><span class="p">;</span>
<span class="w">      </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<p><code>attr()</code> extracts the anchor name for the reference mark from the <code>data-anchor</code>
attribute. It returns a string, unless we specify a CSS unit or a type, like
here: the browser parses the data attribute as a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/custom-ident" title="&lt;custom-ident&gt; on MDN">custom
identifier</a>, which <code>anchor-name</code> validates as a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/dashed-ident" title="&lt;dashed-ident&gt; on MDN">dashed
identifier</a>, a custom identifier starting with two
dashes.<sup id="fnref:attr-support"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:attr-support">4</a></sup></p>
<p>The note itself is absolutely positioned past the right edge of the main block.
It selects the matching reference mark as its anchor with <code>position-anchor</code> set
to the value of the <code>data-anchor</code> attribute. Each note is also an anchor named
<code>--lf-sidenote</code>. We use it to keep the next note from colliding with this one.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/anchor" title="anchor() CSS function on MDN"><code>anchor()</code> CSS function</a> lets us position the note’s top
edge relative to its anchor: <code>anchor(top)</code> aligns the top edge of the note with
the top edge of the reference mark. It can also take another anchor as a
parameter: <code>anchor(--lf-sidenote bottom)</code> would align the top edge of the note
with the bottom edge of the closest preceding anchor named <code>--lf-sidenote</code>—so
the previous note.<sup id="fnref:reference"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:reference">5</a></sup> Like <code>attr()</code>, <code>anchor()</code> accepts a fallback
value as its second parameter and use it when the named anchor does not exist.</p>
<p>The <code>top</code> property handles three cases, illustrated in the following diagram:</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 913px;"><span class="lf-media-inner"><img alt="Diagram of three sidenotes anchored to their reference marks. The first one is aligned with the top of its own reference mark, as no note comes before it. The second one would overlap the first, so it takes the bottom of the first note as anchor and sits one line below it. The third one comes far enough down the page to align with its own reference mark again." class="lf-media" height="660" src="https://d2pzklc15kok91.cloudfront.net/images/css-sidenotes/anchors.7ed3294df30cae.svg" width="913" /></span></div>The three cases for the vertical position of a note</figure>
<ol>
<li>The first note’s top edge aligns with the top edge of its reference mark: as
   there is no previous note, <code>anchor(--lf-sidenote bottom, -1rlh) + 1rlh</code>
   resolves to 0 and <code>max()</code> returns <code>anchor(top)</code>.</li>
<li>When the reference mark of a later note sits above the bottom of the previous
   note, plus some vertical space, the note goes below the previous one to avoid
   a collision. <code>max()</code> returns <code>anchor(--lf-sidenote bottom) + 1rlh</code>.</li>
<li>Otherwise, the note’s top edge aligns with the reference mark’s top edge, as
   <code>max()</code> returns <code>anchor(top)</code>.</li>
</ol>
<hr />
<p>Have a look at the <a href="https://github.com/vincentbernat/vincent.bernat.ch/blob/latest/content/media/css/luffy1.sidenotes.css" title="Stylesheet for the sidenotes of this blog">complete stylesheet</a>, which also adapts the
reference mark to the location of the note: a “↓” arrow when the note sits below
the paragraph, a “→” arrow when it moves to the margin. Gwern’s “<a href="https://gwern.net/sidenote" title="Sidenotes In Web Design">Sidenotes In
Web Design</a>” lists more implementations and their trade-offs.</p>
<p><a href="https://danluu.com/bug-blind/" title="Bug blindness">Some bloggers</a> aim to write a post in 30 minutes. I planned to publish
three web-related articles this weekend. Instead, I spent an inordinate amount
of time elsewhere: about 15 commits on the build system, a pull request to
<a href="https://github.com/pygments/pygments/pull/3294" title="CSS: support nested rule">update CSS highlighting for nested selectors in Pygments</a>, and a
small correction to MDN’s article on the <a href="https://github.com/mdn/content/pull/45532" title="Fix wording for return value of anchor() function"><code>anchor()</code> CSS function</a>. The
SVG illustration took a bit less than an hour and the article itself a handful
of hours. The <code>attr()</code> function came in after I thought “inline style looks
ugly, isn’t there a better way?” But, hey, I still think this is worth it! 🎨</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:phd">
<p>My PhD advisor told me this is unwise. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:phd" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:anchor-support">
<p>The first bits of anchor positioning are <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/anchor-name#browser_compatibility" title="Browser compatibility for anchor-name on MDN">supported</a> from Chrome 125 (May 2024), Firefox 147 (January 2026), and Safari 26 (September 2025).</p>
<p>Before Safari 26.5, sidenotes may collide due to a <a href="https://bugs.webkit.org/show_bug.cgi?id=315704" title="[anchor-position-1] anchor referencing an anchor of the same name does not resolve to the previous one.">bug</a> in how dependency
chains are handled. You can <a href="https://github.com/vincentbernat/vincent.bernat.ch/blob/1e6299da5c95dfce6549dfb20da2cea7c9e58664/content/media/js/luffy1.sidenotes.js">detect this situation with some JavaScript</a>.
It is, however, not needed in the solution described here as we depend on a
more recent feature. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:anchor-support" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:runt">
<p>If you noticed the runt in the first note, I share your pain and lament that
Firefox does not implement <code>text-wrap: pretty</code>. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:runt" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:attr-support">
<p>Typed <code>attr()</code> is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/attr#browser_compatibility" title="Browser compatibility for attr() on MDN">supported</a> from Chrome 133 (February 2025),
Firefox 155 (September 2026), and Safari 27 (not yet released). Check <a href="https://una.im/advanced-attr" title="New capabilities for attr()">Una
Kravets’ article</a> for details. To support more
browsers, you can inline the anchor name and the position anchor directly in
the HTML:</p>
<div class="language-html codehilite"><pre><span></span><code><span class="p">&lt;</span><span class="nt">sup</span> <span class="na">id</span><span class="o">=</span><span class="s">"…"</span> <span class="na">style</span><span class="o">=</span><span class="s">"anchor-name: --lf-sn-…"</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"#sidenote-…"</span><span class="p">&gt;</span>1<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">sup</span><span class="p">&gt;</span>
</code></pre></div>

<p>“<a href="https://blog.master.dev/managing-anchor-associations-with-data-attributes-and-advanced-attr/" title="Managing Anchor Associations With Data Attributes and Advanced attr()">Managing Anchor Associations With Data Attributes and Advanced
<code>attr()</code></a>,” by Daniel Schwarz, explores CSS
anchors and typed <code>attr()</code> in more detail. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:attr-support" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:reference">
<p>The exact rule for the <a href="https://drafts.csswg.org/css-anchor-position-1/#target-anchor-element" title="CSS Anchor Positioning: target anchor element">target anchor element</a> is more complex: “if an
ancestor of [the note] satisfies the following conditions, return the
nearest such element to [the note]. Otherwise, return the last element in
tree order that satisfies the conditions.” One of these conditions is that
“[the candidate] is an <a href="https://drafts.csswg.org/css-anchor-position-1/#acceptable-anchor-element" title="CSS Anchor Positioning: acceptable anchor element">acceptable anchor element</a> for [the note],” which
requires that “[the candidate] is laid out strictly before [the note],”
where the relevant clause is that “[the candidate] is either not absolutely
positioned or occurs earlier in the flat tree order than [the note].” <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:reference" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
</ol>
</div>
       </div>
       <p class="date">
        <a href="https://vincent.bernat.ch/en/blog/2026-css-sidenotes">07 September, 2026 06:49AM</a>
        
         by Vincent Bernat
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <a href="https://www.freexian.com/tags/planet-debian/">
      <img class="face" src="heads/freexian.png"
            width="215" height="101"
            alt="hackergotchi for Freexian Collaborators" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://www.freexian.com/tags/planet-debian/" title="Planet-Debian on Freexian">Freexian Collaborators</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.freexian.com/blog/debusine-debug-symbols/_hide\" onClick=\"exclude( 'https://www.freexian.com/blog/debusine-debug-symbols/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.freexian.com/blog/debusine-debug-symbols/_show\"  style=\"display:none;\" onClick=\"show( 'https://www.freexian.com/blog/debusine-debug-symbols/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.freexian.com/blog/debusine-debug-symbols/" class="https://www.freexian.com/blog/debusine-debug-symbols/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.freexian.com/blog/debusine-debug-symbols/">Debusine can now hand you debug symbols! (by Jugal Patel)</a>
       </h3>
       <div class="content">
        <table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Contributor:</strong></td>
<td>Jugal Patel (Jugal59)</td>
</tr>
<tr>
<td><strong>Organization:</strong></td>
<td>Debian</td>
</tr>
<tr>
<td><strong>Project:</strong></td>
<td><a href="https://salsa.debian.org/freexian-team/debusine/-/work_items/957">Provide debuginfod server</a></td>
</tr>
<tr>
<td><strong>Mentor:</strong></td>
<td>Colin Watson</td>
</tr>
</tbody>
</table>
<h2 id="about-the-project-and-me">About the project and me</h2>
<p>Your program crashes. You open gdb and get <code>??</code> instead of a stack trace. So
you go find the right -dbgsym package, for the right version, for the right
architecture, install it, and start again.
<a href="https://sourceware.org/elfutils/Debuginfod.html">Debuginfod</a> removes that
entire detour: gdb asks a server for symbols by the build-ID baked into the
binary. Debusine already built packages, already produced -dbgsym files, and
already hosted the archives; it just couldn’t answer the question.</p>
<p>This summer I made it answer. My project was to add debuginfod server
functionality to Debusine so that it not only hosts -dbgsym packages, but
also serves their debug symbols over the
<a href="https://manpages.debian.org/trixie/debuginfod/debuginfod.8.en.html">debuginfod(8)</a>
protocol. Debian developers can then debug binaries by setting a single URL
that gdb uses to fetch the matching debug symbols. This project took me
through design, backend work, an extraction pipeline on the worker, HTTP
serving, documentation, and testing from the first blueprint all the way to
a live demo on <a href="https://debusine.debian.net/">debusine.debian.net</a>.</p>
<h2 id="initial-planning-and-design-changes">Initial planning and design changes</h2>
<p>A design first, in
<a href="https://salsa.debian.org/freexian-team/debusine/-/merge_requests/3030">!3030</a>.
The proposal submitted for GSoC 2026 was just an overview of how things will
work, but in reality there were a lot of design questions which needed to be
answered before starting with contribution. Debusine keeps development
blueprints in its docs tree, reviewed like code, it’s basically a blueprint
of what feature or new changes are we going to make. I was assigned the work
item
<a href="https://salsa.debian.org/freexian-team/debusine/-/work_items/957">#957</a>,
which was basically about how the idea of implementing a debuginfod server
functionality inside Debusine was initially proposed by a fellow member
which later became a project idea under GSoC 2026. My developer blueprint
pinned down the four decisions everything else depends on: extraction
happens on the worker after the build, symbols are stored as artifacts keyed
by build-ID, they’re published into suites alongside their binaries, and
they’re served from the archive root rather than per-suite. Settling that up
front meant the design discussions happened in a document instead of across
three merged branches.</p>
<p>  <img alt="Provide debuginfod server work item and all my merged PRs till now" src="https://www.freexian.com/main-work-item.png" /></p>
<p>One of those arguments became its own fix. My wording implied symbols were
unpacked inside the isolated sbuild environment (the consequence was I was
handed a
<a href="https://salsa.debian.org/freexian-team/debusine/-/work_items/1476">bug</a> to
be solved in the first week of contribution period), when they’re actually
extracted afterwards on the worker, where the build output already sits, a
distinction that matters, because doing work inside the unshare environment
means extra tooling in the chroot and more ways to affect the build.
<a href="https://salsa.debian.org/freexian-team/debusine/-/merge_requests/3119">!3119</a>
corrected it before the wrong model spread into the code.</p>
<p>  <img alt="Bug raised for inconsistent wordings in developer blueprint" src="https://www.freexian.com/inconsistent-wordings.png" /></p>
<h2 id="a-new-artifact-type">A new artifact type</h2>
<p><a href="https://freexian-team.pages.debian.net/debusine/reference/artifacts/index.html">Artifacts</a>
are a major concept in Debusine overall, so as per the developer blueprint
we introduced a new artifact which was
<a href="https://freexian-team.pages.debian.net/debusine/reference/artifacts/specs/debug-symbols.html">debian:debug-symbols</a>.
It holds every .debug file from one -dbgsym package. Its data is a validated
list of lowercase 40-character build-IDs, and each file is stored under its
build-ID as the path, so answering “what are the symbols for this ID?” is a
direct lookup, with no path translation in the request handler. One artifact
per package rather than per file: a util-linux build would otherwise spray
hundreds of artifacts, collection items and relations across the database
for no benefit. For implementing debian:debug-symbols artifact, I changed
the main
<a href="https://salsa.debian.org/freexian-team/debusine/-/merge_requests/3088/diffs#edf49f4ff483fcd67a8ae6e91ddb66608a07ce18">models.py</a>
file, along with that since it’s a norm to write unit tests, all mentioned
under
<a href="https://salsa.debian.org/freexian-team/debusine/-/merge_requests/3088">!3088</a>.</p>
<p>  <img alt="sbuild task output showing the new debian:debug-symbols artifact" src="https://www.freexian.com/sbuild-output.png" /></p>
<h2 id="publishing-workflow-and-solving-a-bug">Publishing workflow and solving a bug</h2>
<p>Extracting symbols is only useful if they reach the archive people actually
install from, so
<a href="https://salsa.debian.org/freexian-team/debusine/-/merge_requests/3180">!3180</a>
taught package_publish to follow the relates-to relation: copying binaries
into a suite now brings their debug symbols along automatically, with
nothing extra for the publisher to configure. Each build-ID becomes its own
collection item, for example <code>debugsym:hello_2.10-5_amd64_fcc9064…</code> each
carrying the package name, version and architecture copied from the binary,
so the item is meaningful on its own without dereferencing anything.
Uniqueness is enforced at both the suite and archive level, because the
serving URLs are archive-wide and two suites must never disagree about what
a build-ID means: republishing an identical file is accepted quietly, while
two different files claiming the same ID is an error worth failing on. A
partial index on the build-ID keeps the eventual HTTP lookup fast.</p>
<p>That looked finished until symbols started arriving in target suites
disconnected from their binaries published, but unfindable, because copying
items between collections silently dropped their artifact relations, and
that relation is the only thing tying the two together. The fix sat one
level above my feature, in the generic CopyCollectionItems task that does
the copying, and since it was reusable infrastructure rather than anything
debuginfod-specific, Colin implemented it himself in
<a href="https://salsa.debian.org/freexian-team/debusine/-/merge_requests/3228">!3228</a>.
My project needed it to work at all; every other Debusine feature that
copies items now gets it for free.</p>
<h2 id="endpoint-and-ci-tests">Endpoint and CI tests</h2>
<p>With symbols in the archive,
<a href="https://salsa.debian.org/freexian-team/debusine/-/merge_requests/3212">!3212</a>
added the part users actually touch: <code>GET /{scope}/{workspace}/buildid/&lt;build-id&gt;/debuginfo</code> looks the ID up across
every suite in that workspace’s archive, streams the file, and sets the
<code>X-DEBUGINFOD-FILE</code> and <code>X-DEBUGINFOD-SIZE</code> headers the protocol expects. It
also handles the two things gdb actually does: a <code>HEAD</code> probe before
committing to a download, and ranged requests to pull individual ELF
sections instead of the whole file. Scoping it to the archive rather than
the suite is what lets one URL cover a whole workspace, so the developer
never has to know which suite their binary came from.</p>
<p>  <img alt="Fetching debug files from debusine.debian.net" src="https://www.freexian.com/fetch-debug-files.png" /></p>
<p>Every merge request above landed with unit tests, but those only tell you
that the pieces behave correctly. What Colin and I wanted was a real gdb
fetching real symbols from a real instance, so
<a href="https://salsa.debian.org/freexian-team/debusine/-/merge_requests/3261">!3261</a>
adds an autopkgtest that builds a package, publishes it, checks the HTTP
headers, then sets <code>DEBUGINFOD_URLS</code> and makes gdb go and get the symbols,
wired into the CI integration tests so it runs on every change. It took me a
day to learn that skipping the signing worker doesn’t simplify that test, it
just hangs until the 30-minute timeout, because update_suites needs signing
to produce a usable repository.</p>
<p>The last piece,
<a href="https://salsa.debian.org/freexian-team/debusine/-/merge_requests/3301">!3301</a>
covers the new artifact, the suite and archive changes, the new archive URL,
and a how-to for using it. My first how-to draft explained how everything
worked and offered four ways to set <code>DEBUGINFOD_URLS</code>; the version that
shipped gives one recommended setup and gets out of the way. The same pass
trimmed the blueprint down to only what’s still unimplemented, since a
design document describing merged code is just an obstacle for the next
reader.</p>
<p>  <img alt="Setting debuginfod url for gdb and debugging session!" src="https://www.freexian.com/gdb-session.png" /></p>
<h2 id="whats-left">What’s left</h2>
<p>Only one item on my original plan didn’t land: an archive-level
build_debug_symbols switch, modelled on Launchpad’s equivalent, letting an
archive skip building -dbgsym packages entirely by passing
<code>DEB_BUILD_OPTIONS=noautodbgsym</code> to sbuild. It was always the stretch goal
rather than core scope, landing the extract-publish-serve path solidly
mattered more than landing it broadly. The design is written up in the
blueprint, and I intend to implement it myself.</p>
<p>The other gaps were deliberately out of scope from the start, and the
blueprint says so. DWZ supplement files aren’t ingested, so packages using
compressed debug info may render without the alternate strings table;
debugging still works, it’s just less complete. Source-file serving runs
into the same Debian packaging limits that constrain debuginfod.debian.net
today, making it a design question rather than a coding one. Executable
serving, the metrics and metadata endpoints, and federation to upstream
debuginfod servers were excluded for similar reasons, none of them are
needed for Debusine’s core use case, and each would have crowded out the
parts that are.</p>
<p>One open bug is left too. On the last day of the coding period, Stefano
Rivera found that publishing ledger and linux was failing, because I had
told the database that a build-ID identifies one exact debug file which
isn’t true in Debian, since dh_dwz runs once per binary package, so when one
object ships in two binary packages their .debug files differ while
describing identical code. How to fix it is still an open discussion
<a href="https://salsa.debian.org/freexian-team/debusine/-/work_items/1582">#1582</a>,
though it may not land before the formal end of the project.</p>
<p>None of that is a handoff. GSoC’s timeline is ending, my involvement isn’t,
I’m carrying on with Debusine until both the build_debug_symbols switch and
DWZ supplement support are merged, and I expect to keep contributing beyond
that. This project got me familiar with a codebase I enjoy working in, and
the remaining pieces are mine to finish.</p>
<h2 id="thanks">Thanks!</h2>
<p>The biggest thanks go to my mentor, Colin Watson, whose reviews consistently
found the thing I hadn’t thought about. He also gave me room to get things
wrong first and understand why, which taught me more than being handed the
answer would have.</p>
<p>Thanks as well to Raphaël Hertzog, Enrico Zini, Stefano Rivera, Carles Pina
i Estany and Helmut Grohne and everyone else around Debusine and Freexian
for reviews, comments and patience with my questions.</p>
<p>Special thanks to Freexian for developing Debusine in the open and for
giving me access to test on
<a href="https://debusine.debian.net/">debusine.debian.net</a>.</p>
<p>Finally, thanks to the wider Debian community, whose build-ID and -dbgsym
conventions did most of the hard work before I arrived and to Google Summer
of Code for providing a platform and the time to do this properly.</p>
       </div>
       <p class="date">
        <a href="https://www.freexian.com/blog/debusine-debug-symbols/">07 September, 2026 12:00AM</a>
        
         by Jugal Patel
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 06, 2026</h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://k1024.org" title="iustin - all posts">Iustin Pop</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://k1024.org/posts/2026/2026-09-06-ai-agents-aha/_hide\" onClick=\"exclude( 'https://k1024.org/posts/2026/2026-09-06-ai-agents-aha/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://k1024.org/posts/2026/2026-09-06-ai-agents-aha/_show\"  style=\"display:none;\" onClick=\"show( 'https://k1024.org/posts/2026/2026-09-06-ai-agents-aha/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://k1024.org/posts/2026/2026-09-06-ai-agents-aha/" class="https://k1024.org/posts/2026/2026-09-06-ai-agents-aha/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://k1024.org/posts/2026/2026-09-06-ai-agents-aha/">AI agents aha moment</a>
       </h3>
       <div class="content">
        <p>Looking at the reactions to the Debian AI vote, I think some people still think
the clock can be turned back, as if that ever worked in history. Rather than cry
about spilled milk, I prefer to find a path forward in the new world. There are
many ways to use LLMs, some of them are straightforward, others not so much.</p>
<p>One of the “not so clear” areas for me is the focus on agentic workloads. For
complex tasks, sure, you want something that can work in the background, but in
general, why does every single tool go the agentic way? I much prefer the
“chat/ask” approach, or even the “code” one, but if I’m at the keyboard, why
would I send a task to an agent, and see it work, instead of directly
implementing it?</p>
<p>And then, this past Friday, I finally understood one part of that. I was in the
airport, sitting at the gate and waiting to board a flight, and because I
arrived much earlier at the airport (fearing crowds due to Labour Day weekend),
I got one hour of work before boarding started. As the time for boarding
approached, I did one more
<a href="https://github.com/iustin/corydalis/commit/2b2f520952175b626c4d769c4d46825758954ea3">commit</a>
after making sure tests pass, pushed, closed laptop, and went to walk a bit
before getting on the plane.</p>
<p>As I was getting up, I get a phone notification from GitHub that the CI run
<a href="https://github.com/iustin/corydalis/actions/runs/33935984250/job/101223903306">failed</a>.
I was quite surprised, as the local tests passed, so I open the notification,
and realize that tests via <code>make test</code> vs CI (which additionally uses
<code>--pedantic</code>) had slightly different settings, and of course I missed a build
warning (which in CI is an error).</p>
<p>I thought I’d fix that on the plane, but then I saw a “Copilot agent” button in
the mobile app. I was curious what it did, I click it, and I see Copilot
starting a draft <a href="https://github.com/iustin/corydalis/pull/21">pull request</a>, and saying:</p>
<blockquote>
<p>Thanks for asking me to work on this. I will get started on it and keep this
PR’s description up to date as I form a plan and make progress.</p>
<p>Fix the failing GitHub Actions job. Analyze the Actions logs, identify the
root cause of the failure, and implement a fix.</p>
</blockquote>
<p>Then it goes, finds the failure, writes the fix, and tries to run the tests.
Well, it can’t do it (it runs in a restricted container, so no network, so
<code>stack install</code> couldn’t actually work). The agent sees that, acknowledges it
has no way to validate the fix, but the error message was clear enough that it
was confident the fix is mostly correct, so it sends the pull request.</p>
<p>I allow full CI to run on the pull request, and go buy a bottle of water. After
that, I check and see that the CI failed again, as not one but two test files
were broken, and I didn’t have <code>--keep-going</code>, so the build stopped at the first
failure. I write a comment in the pull request, no reaction, I realize I need to
tag Copilot explicitly, I do that, and it starts another investigation.</p>
<p>I’m waiting now in the boarding queue, with phone in hand, while Copilot is
fixing my bug. While I scan my boarding pass and walk towards the plane, the
pull request is updated, I trigger another CI, it passes, and I merge it.</p>
<p>And then, it hit me. Agents allow me to make progress while being “not at
keyboard”, whether that’s physically “not at keyboard”, or while working on
something else. Fixing a simple test failure is not something that needs human
attention per se, whereas improving the test layout might be.</p>
<p>In that airport, using otherwise-unusable downtime, and without explicitly
intending to, I made progress in understanding a different way to use AI. Now I
have three ways to work with LLMs: ask (tutor mode), code (implement my
request), and agent (fix simple or complex problems, autonomously). I still
don’t know about “plan” mode and really complex tasks, like asking it to
implement features from scratch. That will probably be the next area to tackle.</p>
<p>And today (Sunday), while waiting for a running race to start, I opened GitHub,
and asked Copilot to increase test coverage for a simple module. It did, and yes
it still can’t run tests (I learned in the meantime that you can
<a href="https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/customize-cloud-agent/customize-the-agent-environment">configure</a>
the environment in which the agent runs, nice), but after two back-and-forth
messages, I have a <a href="https://github.com/iustin/corydalis/pull/22">pull request</a>
ready to review. All in the 20 minutes before a race, where I could either
browse social media or actually do some meaningful work.</p>
<p>Checking now my GitHub billing, it looks like all of this Copilot use only cost
$1.92. Yes, that is under two dollars! And while it did use compute resources,
the person across the aisle who watched TikTok or Instagram for half an hour
while waiting for takeoff also consumed a lot of compute, and so do the
gazillion cat videos uploaded to YouTube every day.</p>
<p>To me, this is another tool in the toolbox, that might one day replace me (as it
did to the 19th-century textile workers), or make me five times more productive
— we’ll see where we end up. In the meantime, I can move faster, and make better
use of my limited free time.</p>
<p>Enjoy the ride!</p>
       </div>
       <p class="date">
        <a href="https://k1024.org/posts/2026/2026-09-06-ai-agents-aha/">06 September, 2026 01:29PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://etbe.coker.com.au" title="etbe – Russell Coker">Russell Coker</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://etbe.coker.com.au/2026/09/06/comaps/_hide\" onClick=\"exclude( 'https://etbe.coker.com.au/2026/09/06/comaps/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://etbe.coker.com.au/2026/09/06/comaps/_show\"  style=\"display:none;\" onClick=\"show( 'https://etbe.coker.com.au/2026/09/06/comaps/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://etbe.coker.com.au/2026/09/06/comaps/" class="https://etbe.coker.com.au/2026/09/06/comaps/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://etbe.coker.com.au/2026/09/06/comaps/">CoMaps</a>
       </h3>
       <div class="content">
        <p>I have just tried <a href="https://www.comaps.app/">CoMaps, a free mapping program released under the Apache license [1]</a>. I have tried it on Android on a Pixel 6a but it also runs on Linux so I’ll try it on a PinePhone or similar at some convenient time. On Android it is in the F-Droid repository among others and for Linux there’s a Flatpak package.</p>
<p>The data it uses is from <a href="https://www.openstreetmap.org/">Open Street Map project [2]</a> which has extensive and accurate coverage of every place I’ve looked at (Australia and a few other first-world countries). The first thing it does after being installed is start downloading the world data set from Open Street Map and prompt to download the data for the detected region (Melbourne in my case).</p>
<p>The UI is decent and allows most of the features that I am used to using in Google Maps. The quality of directions seems good, I’ve only tested it with one journey so far which was a 50 minute drive across the city and it gave a set of directions that Google Maps often gives.</p>
<p>It gives spoken directions which is an important feature but sometimes the way the directions are presented is confusing. When turning off a freeway it didn’t give a spoken direction to do that, it gave a direction to “turn right” which was AFTER leaving the freeway, fortunately the map was clearly displayed.</p>
<p>In terms of use practices of this program the main difference I recommend is checking which off ramp to use from a freeway before entering the freeway. With Google Maps you can rely on it giving clear directions in that case.</p>
<p>I recommend this program without reservation. It can do everything that Google Maps does apart from detecting traffic jams because there’s no way of detecting traffic without spying on users. It is designed to preserve user privacy and works well in that regard.</p>
<ul>
<li>[1]<a href="https://www.comaps.app/"> https://www.comaps.app/</a></li>
<li>[2]<a href="https://www.openstreetmap.org/"> https://www.openstreetmap.org/</a></li>
</ul>
<div class="yarpp yarpp-related yarpp-related-rss yarpp-template-list">

<p>Related posts:</p><ol>
<li><a href="https://etbe.coker.com.au/2024/09/15/kogan-ax1800-wifi6-mesh/" rel="bookmark" title="Kogan AX1800 Wifi6 Mesh">Kogan AX1800 Wifi6 Mesh</a> <small>I previously blogged about the difficulties in getting a good...</small></li>
<li><a href="https://etbe.coker.com.au/2012/03/20/introduction-android/" rel="bookmark" title="An Introduction to Android">An Introduction to Android</a> <small>I gave a brief introductory talk about Android at this...</small></li>
<li><a href="https://etbe.coker.com.au/2011/10/22/occupy-main-street/" rel="bookmark" title="Occupy Main Street">Occupy Main Street</a> <small>The Occupy Wall St blog has an informative summary of...</small></li>
</ol>
</div>
       </div>
       <p class="date">
        <a href="https://etbe.coker.com.au/2026/09/06/comaps/">06 September, 2026 11:05AM</a>
        
         by etbe
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://www.ezini.eu/tags/pdo" title="Enrico Zini: posts with tag pdo">Enrico Zini</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.ezini.eu/blog/2026/migrating-away-from-org-net-com-domains_hide\" onClick=\"exclude( 'https://www.ezini.eu/blog/2026/migrating-away-from-org-net-com-domains' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.ezini.eu/blog/2026/migrating-away-from-org-net-com-domains_show\"  style=\"display:none;\" onClick=\"show( 'https://www.ezini.eu/blog/2026/migrating-away-from-org-net-com-domains' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.ezini.eu/blog/2026/migrating-away-from-org-net-com-domains" class="https://www.ezini.eu/blog/2026/migrating-away-from-org-net-com-domains">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.ezini.eu/blog/2026/migrating-away-from-org-net-com-domains">Migrating away from .org/.net/.com domains</a>
       </h3>
       <div class="content">
        <p>After having witnessed how easy it is for good people to lose a <code>.org</code> domain
over a fascist tantrum (you can follow the Autistici/Inventati story
<a href="https://www.inventati.org/campaign/press">here</a> and
<a href="https://keepitfree.ai/">here</a>), I've started moving all my infrastructure to
<a href="https://mastodon.ar.al/@aral/117172339747622841">differently managed TLDs</a>.</p>
<p><code>enricozini.org</code> and <code>enricozini.com</code> will keep being functional for the time
being, as dropping a domain makes it available for squatting and impersonation.</p>
<p>These new domains are now online, with working web and emails:</p>
<ul>
<li><a href="https://www.ezini.eu">enricozini.eu</a></li>
<li><a href="https://www.ezini.eu">ezini.eu</a></li>
<li><a href="https://www.ezini.eu">ezini.it</a> (probably equally subject to fascist
  tantrums these days)</li>
</ul>
<p>It will take ages to migrate countless accounts that are tied to my primary
email address, so better start early.</p>
<p>Waiting to see what will happen with <a href="https://www.dotmeow.org/">.meow</a> domains,
which I supported despite not identifying as a cat.</p>
       </div>
       <p class="date">
        <a href="https://www.ezini.eu/blog/2026/migrating-away-from-org-net-com-domains">06 September, 2026 09:40AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <a href="http://blog.sesse.net/">
      <img class="face" src="heads/sesse.png"
            width="74" height="85"
            alt="hackergotchi for Steinar H. Gunderson" />
     </a>
      
     <h2 class="planet-title">
      <a href="http://blog.sesse.net/" title="Steinar H. Gunderson">Steinar H. Gunderson</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://blog.sesse.net/blog/tech/2026-09-06-09-02_plocate_1_1_25_released.html_hide\" onClick=\"exclude( 'http://blog.sesse.net/blog/tech/2026-09-06-09-02_plocate_1_1_25_released.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://blog.sesse.net/blog/tech/2026-09-06-09-02_plocate_1_1_25_released.html_show\"  style=\"display:none;\" onClick=\"show( 'http://blog.sesse.net/blog/tech/2026-09-06-09-02_plocate_1_1_25_released.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://blog.sesse.net/blog/tech/2026-09-06-09-02_plocate_1_1_25_released.html" class="http://blog.sesse.net/blog/tech/2026-09-06-09-02_plocate_1_1_25_released.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://blog.sesse.net/blog/tech/2026-09-06-09-02_plocate_1_1_25_released.html">plocate 1.1.25 released</a>
       </h3>
       <div class="content">
        <p>I've released version 1.1.25 of <a href="https://plocate.sesse.net/">plocate</a>.
This time around, there's two security issues of unknown severity;
if you chain them with other bugs, they could lead to being able to
list files (but of course not their contents) that you should not
normally be able to see. So an update is probably in order; you can
never be too safe these days.</p>

<p>The full changelog is:</p>

<pre>plocate 1.1.25, September 6th, 2026

  - Fix two early-exit bugs with multiple databases.
    Reported by Manpreet Singh and Tyler Spivey.

  - Drop setgid properly, including the saved gid.
    Reported by Michal Sekletar, found with the help of Claude Opus 4.6.

  - Fix a potential symlink-checking race in updatedb.
    Reported by Michal Sekletar, found with the help of Claude Opus 4.6.
</pre>

<p>As usual, you can get it from <a href="https://plocate.sesse.net/">the home page</a>,
or it's on the way up in Debian unstable.</p>
       </div>
       <p class="date">
        <a href="http://blog.sesse.net/blog/tech/2026-09-06-09-02_plocate_1_1_25_released.html">06 September, 2026 08:02AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://michael.stapelberg.ch/posts/tags/debian/" title="Michael Stapelbergs Website: posts tagged debian">Michael Stapelberg</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/_hide\" onClick=\"exclude( 'https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/_show\"  style=\"display:none;\" onClick=\"show( 'https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/" class="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/">Debian Code Search: Fast TurboPFor with Go SIMD</a>
       </h3>
       <div class="content">
        <p>This August, I accomplished what I wanted for many years: I deleted the last cgo
dependency in Debian Code Search! This was made possible by Go’s recently
introduced SIMD support, because now we can implement the TurboPFor integer
compression format as efficiently — more efficiently, in fact, by using the
newer AVX512 instruction set! — as the reference implementation.</p>
<h2 id="background">Background: Why does DCS need a fast Integer Codec?</h2>
<p>Debian Code Search (DCS) is a search engine that allows searching all the Open
Source source code within Debian, with either literal search expressions or
regular expression search queries.</p>
<p>A search engine uses an inverted index: a map from term to documents containing
the term. Each document is typically represented most efficiently by using an
id, so the index consists of many lists of document ids.</p>
<p>When searching, it is important to quickly decode these lists to answer the
search query. However, there is a point of diminishing returns where the
decoding speed, even though it can still be measurably improved quite a bit, no
longer influences the overall query duration.</p>
<p>From 2012 (its inception) to 2019, Debian Code Search used to use a small index
format, and queries were fast because the index was kept entirely in RAM. In
2019, I <a href="https://michael.stapelberg.ch/posts/2019-09-29-dcs-positional-turbopfor-index/">implemented the new index
format</a>, which adds an
on-disk positional index. For literal queries (78.2% of DCS queries), querying
the positional index on disk is faster than querying the non-positional index in
RAM.</p>
<p>The efficient encoding of the TurboPFor format makes it possible to fit such an
index on a mid-sized Hetzner server, which I rent with two 1 TB SSD disks. The
optimized decoder of the C TurboPFor library is what made decoding fast at query
time.</p>
<p>If you want to dive deeper into the algorithm, see this blog post from February
2019:</p>



  <div class="postlink">
      <div>
	<a href="https://michael.stapelberg.ch/posts/2019-02-05-turbopfor-analysis/"><h3>TurboPFor: an analysis</h3></a>
      </div>
      <div class="summary">
	
	
	
	
	<p>
	  <a href="https://michael.stapelberg.ch/posts/2019-02-05-turbopfor-analysis/">
	  Motivation I have recently been looking into speeding up Debian Code Search. As a quick reminder, search engines answer queries by consulting an inverted index: a map from term to documents containing that term (called a “posting list”). See the Debian Code Search Bachelor Thesis (PDF) for a lot more details.

	  <span class="readmore"></span></a><a href="https://michael.stapelberg.ch/posts/2019-02-05-turbopfor-analysis/">Read more →</a>
	  
	</p>
      </div>
  </div>


<p>If you want to learn more about the positional index, see this blog post from
September 2019:</p>



  <div class="postlink">
      <div>
	<a href="https://michael.stapelberg.ch/posts/2019-09-29-dcs-positional-turbopfor-index/"><h3>Debian Code Search: positional index, TurboPFor-compressed</h3></a>
      </div>
      <div class="summary">
	
	
	
	
	<p>
	  <a href="https://michael.stapelberg.ch/posts/2019-09-29-dcs-positional-turbopfor-index/">
	  Over the last few months, I have been developing a new index format for Debian Code Search. This required a lot of careful refactoring, re-implementation, debug tool creation and debugging.
	  <span class="readmore"></span></a><a href="https://michael.stapelberg.ch/posts/2019-09-29-dcs-positional-turbopfor-index/">Read more →</a>
	  
	</p>
      </div>
  </div>


<h2 id="simd-go">SIMD in Go</h2>
<p>For many years, you had the following options for using SIMD instructions in Go:</p>
<ol>
<li>Hand-writing <a href="https://go.dev/doc/asm">Go assembler code</a>. This is only doable for
small functions, for example
<a href="https://pkg.go.dev/bytes#IndexByte"><code>bytes.IndexByte</code></a> is <a href="https://cs.opensource.google/go/go/+/refs/tags/go1.27.1:src/internal/bytealg/indexbyte_amd64.s">implemented with
hand-written Go
assembly</a>
(including AVX2).</li>
<li>Generating Go assembler code with tools like <a href="https://github.com/mmcloughlin/avo">Michael McLoughlin’s
“Avo”</a>. This is <a href="https://cs.opensource.google/go/go/+/refs/tags/go1.27.1:src/crypto/internal/fips140/sha256/_asm/sha256block_amd64_avx2.go">how
<code>crypto/internal/fips140/sha256</code> uses
AVX2</a>. While
Avo generator code definitely is higher-level than hand-written assembly, it
is still too close to assembly for my taste.</li>
<li>Use a C library via <a href="https://go.dev/wiki/cgo">cgo</a> so gcc or clang compiles
SIMD code. Debian Code Search used to use the
<a href="https://github.com/powturbo/TurboPFor-Integer-Compression">powturbo/TurboPFor</a>
C library via cgo for the last 7 years.</li>
</ol>
<p>The C TurboPFor library has served us well, but Debian Code Search was always
intended to be a project using Go, so I would prefer it if I did not have any C
code in the project.</p>
<p>Go 1.26 (released in February 2026) introduced the <code>simd/archsimd</code> package:</p>
<blockquote>
<p>Go 1.26 introduces a new <a href="https://pkg.go.dev/simd/archsimd">experimental <code>simd/archsimd</code>
package</a>, which can be enabled by setting
the environment variable <code>GOEXPERIMENT=simd</code> at build time. This package
provides access to architecture-specific SIMD operations. It is currently
available on the <code>amd64</code> architecture and supports 128-bit, 256-bit, and
512-bit vector types, such as <code>Int8x16</code> and <code>Float64x8</code>, with operations such
as <code>Int8x16.Add</code>. The API is not yet considered stable.</p>
<p>— <a href="https://go.dev/doc/go1.26#simd">Go 1.26 Release Notes</a></p>
</blockquote>
<p>For my 2019 TurboPFor analysis, I implemented <a href="https://github.com/stapelberg/goturbopfor"><code>goturbopfor</code>, a native Go
teaching decoder</a> (without any SIMD),
because I find Go code easier to follow than C code, especially optimized C
code. My implementation was intentionally not optimized so that the code was
easier to study.</p>
<p>The TurboPFor format/algorithm has a vector-optimized part: <a href="https://github.com/stapelberg/goturbopfor/blob/920055ddf6e9106fce2904817eaf96734bf7e26e/goturbopfor.go#L35">bitpacking comes in
a scalar variant (<code>bitunpack32</code>) and a vector variant
(<code>bitunpack256v32</code>)</a>,
where the vector variant is used for full blocks (256 values) and the scalar
variant is used for remainder blocks (&lt; 256 values).</p>
<p>When Go 1.26 was released, I used Claude Code to explore whether my native Go
decoder’s <code>bitunpack256v32</code> function (for the vertical vector layout) could be
implemented using Go SIMD, and the answer was yes, it was possible and it was
faster than without SIMD, but not quite at the level of C TurboPFor. If you let
Claude Code try for long enough, it eventually finds enough optimizations (about
10) to match C performance.</p>
<p>I don’t want to vibe-code Debian Code Search, though, so I figured I would find
some time to review the SIMD code at some point and see if I could implement
something similar myself.</p>
<p>Before I found enough time and motivation to complete said review, I discovered
that to not regress real-life query performance by more than 10 to 100
milliseconds (which seems acceptable), I don’t actually need to add SIMD code to
my teaching decoder at all; it would be sufficient to reduce allocations in my
teaching decoder and specialize it per bit width.</p>
<p>Encouraged by the possibility of using the optimized native Go decoder in Debian
Code Search, I explored whether I could also implement a native Go <em>encoder</em> so
that I could get rid of the C TurboPFor dependency entirely. The answer is yes,
it is doable in a few days, and it isn’t even that much slower: Go is at 76% of
C, see <a href="https://github.com/Debian/dcs/commit/e920dc7c5dd3f0e05de1cde3d73b99489f2e1bcb">Debian/dcs commit
<code>e920dc7</code></a>.</p>
<p>The goal I set myself at that point was to see if I could learn enough SIMD to
optimize the native Go encoder such that its performance would match how DCS
uses C TurboPFor (via cgo).</p>
<p>Beating C TurboPFor was possible in 2-3 commits (SIMD and bit width
specialization). To my surprise, Claude Fable 5 pointed out that the encoder’s
block scanning could be done more efficiently using a technique called
positional popcount, and that is <strong>another 2x speed-up</strong>! 😲</p>
<p>To be clear: I am not saying the Go compiler beats C here. Certainly, the C
compiler can also produce fast AVX512 code and can be used to implement
positional popcount.  When comparing apples to apples, i.e. backporting the
AVX512 kernels and positional popcount technique to C TurboPFor, Go benchmarks a
little slower at ≈1.4x C.</p>
<p>This spectacular result (much faster than what DCS had before) got me curious
how far I could push the decoder with SIMD after all. I ended up
matching/exceeding the cgo version here, too!</p>
<p>The rest of this article explains a few classes of optimizations I encountered
along the way.</p>
<h2 id="starting-point">Starting Point</h2>
<p>When I wrote <a href="https://github.com/stapelberg/goturbopfor/">my <code>goturbopfor</code> teaching
decoder</a>, I named its functions to
match the upstream C TurboPFor library, but now I want to get away from names
like <code>p4ndec256v32</code> — they make sense from the TurboPFor perspective, but for
Debian Code Search, we can use cleaner names.</p>
<p>Before writing any code, I audited how DCS uses integer compression /
decompression.</p>
<h3 id="api-design">API design: BlockEncoder, BlockDecoder and streaming</h3>
<p>In Debian Code Search, we have the following usage patterns:</p>
<ul>
<li><strong>Partial Indexing:</strong> When a new package (or package version) enters Debian,
all of its (text) files are indexed. If the <a href="https://packages.debian.org/forky/hello"><code>hello-2.12.3-1</code>
package</a> (hypothetically) contained
only <code>hello.c</code> with <code>printf("hello!\n");</code>, we would assign document ID <code>1</code> to
<code>hello.c</code> and store in the partial index that trigrams <code>pri</code>, <code>rin</code>, <code>int</code>,
<code>ntf</code>, etc. are all found in doc <code>1</code> (<code>hello.c</code>).</li>
<li><strong>Full Index Merging:</strong> The many thousands of partial index files (for each
Debian package) are combined into a small handful of large index files: When
searching, it would be expensive to consult thousands of indexes. To merge
multiple partial index files into one larger index (which can then be
efficiently queried), we need to re-encode the partial index files: what used
to be document ID <code>1</code> in the partial index might be document ID <code>2531</code> in the
full index.</li>
<li><strong>Querying (searching):</strong> When users enter search queries, these queries need
to be answered as quickly as possible. The relevant entries in the full
indexes are decoded (in parallel).</li>
</ul>
<p>For reading the index, we do keep the decoded <code>uint32</code>s fully in memory, so we
only need <code>DecodeN(input []byte, output []uint32) (read int)</code>, a function that
reads <code>len(output)</code> values (<code>uint32</code>) from <code>input</code> and returns how many bytes it
consumed.</p>
<p>For writing the index (both in partial indexing, and when merging), keeping the
entire index in memory is prohibitively expensive, so we need a streaming API,
for decoding and for encoding.</p>
<p>Ultimately, I converged on the following API:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">package</span><span style="color: #bbb;"> </span>pforenc<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">type</span><span style="color: #bbb;"> </span>BlockEncoder<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">struct</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #60a0b0; font-style: italic;">// scratch buffers can go here</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// EncodeBlock encodes len(vals)&lt;=256 uint32s into dest (one TurboPFor block).</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span>(<span style="color: #666;">*</span>BlockEncoder)<span style="color: #bbb;"> </span><span style="color: #06287e;">EncodeBlock</span>(dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>{}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// EncodeN calls EncodeBlock in a loop.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span>(<span style="color: #666;">*</span>BlockEncoder)<span style="color: #bbb;"> </span><span style="color: #06287e;">EncodeN</span>(dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>{}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">type</span><span style="color: #bbb;"> </span>StreamEncoder<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">struct</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>be<span style="color: #bbb;">   </span>BlockEncoder<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>vals<span style="color: #bbb;"> </span>[<span style="color: #40a070;">256</span>]<span style="color: #902000;">uint32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// scratch buffers</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// if full, you need to call [EncodeBlock]</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span>(<span style="color: #666;">*</span>StreamEncoder)<span style="color: #bbb;"> </span><span style="color: #06287e;">Add</span>(val<span style="color: #bbb;"> </span><span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>(full<span style="color: #bbb;"> </span><span style="color: #902000;">bool</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// EncodeBlock must be called after all data was [Add]ed.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">//
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// Write the returned buffer to file or send it over the network;</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// it is only valid until the next [EncodeBlock] call.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span>(<span style="color: #666;">*</span>StreamEncoder)<span style="color: #bbb;"> </span><span style="color: #06287e;">EncodeBlock</span>()<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>se.n<span style="color: #bbb;"> </span><span style="color: #666;">==</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">nil</span><span style="color: #bbb;"> </span>}<span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// turn an extra EncodeBlock into a no-op</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// …</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p>This API (the decoder works similarly) allows us to process data in TurboPFor
format without any memory allocations. The types are not safe for concurrent use
by multiple goroutines. The zero value is ready to be used. For the streaming
API, the result only stays valid until the next call.</p>
<h3 id="initial-encoder">Initial Implementation</h3>
<p>Before we can optimize anything, we need a working decoder and encoder. The
decoder already exists: my <code>goturbopfor</code> teaching decoder. Next up, I needed an
encoder.</p>
<p>Writing a TurboPFor encoder has a delightfully simple starting point: You can
encode all values at bit width 32, in little endian, at which point you only
need to add a one-byte TurboPFor block header every 256 values and you’re done:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span>(be<span style="color: #bbb;"> </span><span style="color: #666;">*</span>BlockEncoder)<span style="color: #bbb;"> </span><span style="color: #06287e;">EncodeN</span>(dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(vals)<span style="color: #bbb;"> </span>&gt;<span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>chunk<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">min</span>(<span style="color: #007020;">len</span>(vals),<span style="color: #bbb;"> </span><span style="color: #40a070;">256</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>be.<span style="color: #06287e;">EncodeBlock</span>(dest,<span style="color: #bbb;"> </span>vals[:chunk])<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>vals<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>vals[chunk:]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>dest<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span>(be<span style="color: #bbb;"> </span><span style="color: #666;">*</span>BlockEncoder)<span style="color: #bbb;"> </span><span style="color: #06287e;">EncodeBlock</span>(dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">const</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span><span style="color: #007020;">append</span>(dest,<span style="color: #bbb;"> </span>bitWidth)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>_,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>binary.LittleEndian.<span style="color: #06287e;">AppendUint32</span>(dest,<span style="color: #bbb;"> </span>val)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>dest<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p>Of course, this is a terribly inefficient compressor, so after the first commit,
the real work starts: implement each block type until the compression matches
the original C TurboPFor implementation (same output file size), or in other
words: do the reverse of the decoder.</p>
<ol>
<li>The TurboPFor <strong>bitpacking</strong> block type (<a href="https://github.com/Debian/dcs/commit/11eafa3d5d56388c0f0badb0c4b393acc80470e1">bitpacking implementation
commit</a>)
encodes a bit stream of variable bit width (where the bit width is in range
<code>0 ≤ bitWidth ≤ 32</code>) in <a href="https://en.wikipedia.org/wiki/Endianness">little
endian</a> byte order. By scanning all
values and choosing the smallest bit width that allows representing all
values, this technique saves disk space (compresses).</li>
<li>The <strong>bitpacking with exceptions</strong> block type (<a href="https://github.com/Debian/dcs/commit/d7b91f6b0ef732cf78eab2c4cd65dab007255197">bitpacking with exceptions
implementation
commit</a>)
determines two bit widths: one for values, the other bit width for encoding
exceptions. This allows choosing a lower bit width (that does not cover all
values) compared to the bitpacking block type. A bitmap encodes whether a
value has an exception or not.</li>
<li>The <strong>bitpacking with VB exceptions</strong> block type (<a href="https://github.com/Debian/dcs/commit/e920dc7c5dd3f0e05de1cde3d73b99489f2e1bcb">bitpacking with VB
exceptions implementation
commit</a>)
is a variant which does not use an exception bitmap and encodes exceptions
using a variable byte integer encoding. This is more efficient when there are
few exceptions (less than 20) or the exceptions are very different in bit
width compared to the other values.</li>
<li>Lastly, the <strong>constant</strong> block type (<a href="https://github.com/Debian/dcs/commit/59df3c6e21443496254e263342dd79bfef4ff173">constant implementation
commit</a>)
stores just one value on disk. This is useful for all-zero or all-one blocks,
for example.</li>
</ol>
<p>I found it interesting to realize that the main work of the encoder is to scan
the input values and choose the optimal block type, whereas the actual encoding
itself is cheap in comparison.</p>
<p><a href="https://github.com/Debian/dcs/commit/e920dc7c5dd3f0e05de1cde3d73b99489f2e1bcb">At this point, we can look at
performance</a>
and see that the Go encoder is at 76% of the C encoder.</p>
<p>In all honesty, I could have probably stopped here, but now that the milestone
of a viable replacement was reached, I got curious to see how far it would be
possible to push the encoder (how much work to reach C speeds?) and afterwards,
the decoder, too.</p>
<h2 id="setup">Setup</h2>
<h3 id="goamd64">The microarchitecture level: set <code>GOAMD64</code></h3>
<p>The microarchitecture of a CPU determines which instructions it provides, and
that includes not just SIMD instruction sets (like AVX2), but also other useful
instructions like <code>LZCNT</code> (Leading Zero Count), which can be used to implement
<a href="https://pkg.go.dev/math/bits#Len32"><code>math/bits.Len32</code></a> more efficiently, which
the TurboPFor encoder needs to call on every input value to determine the ideal
bit width.</p>
<p>Let’s walk through how to set the microarchitecture level when using Go on
64-bit x86 (x86-64).</p>
<p>Go uses the <a href="https://go.dev/doc/install/source#environment"><code>GOARCH</code> environment
variable</a> to configure the target
compilation architecture, and I am using the value <code>amd64</code> to select 64-bit x86
(AVX2 and AVX512 are instruction sets found on x86-64 CPUs). With
<code>GOARCH=amd64</code>, the architecture-specific variable
<a href="https://pkg.go.dev/cmd/go#hdr-Environment_variables"><code>GOAMD64</code></a> configures the
microarchitecture level for which to compile and Go 1.18 <a href="https://go.dev/wiki/MinimumRequirements#amd64">introduced these 4
different levels</a>:</p>
<blockquote>
<p><code>GOAMD64=v1</code> (default): The baseline. <br />
Exclusively generates instructions that all 64-bit x86 processors can execute.</p>
<p><code>GOAMD64=v2</code>: all v1 instructions, <br />
plus CMPXCHG16B, LAHF, SAHF, POPCNT, SSE3, SSE4.1, SSE4.2, SSSE3.</p>
<p><code>GOAMD64=v3</code>: all v2 instructions, <br />
plus AVX, <strong>AVX2</strong>, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, OSXSAVE.</p>
<p><code>GOAMD64=v4</code>: all v3 instructions, <br />
plus <strong>AVX512F</strong>, AVX512BW, AVX512CD, AVX512DQ, AVX512VL.</p>
</blockquote>
<p>In 2026, I generally recommend compiling with <code>GOAMD64=v3</code> so that functions
like <a href="https://pkg.go.dev/math/bits#OnesCount8"><code>bits.OnesCount8</code></a> are
<a href="https://cs.opensource.google/go/go/+/master:src/cmd/compile/internal/ssa/_gen/AMD64.rules;l=102-115;drc=64ebd027a7c81f784b4b41f8070355d19e73687b">compiled into intrinsics
(<code>POPCNT</code>)</a>
instead of using a lookup table.</p>
<p>For Intel CPUs, setting <code>GOAMD64=v3</code> means your programs will only start on
Haswell CPUs (2013) or newer; for AMD CPUs that means Zen 1 (2017) or newer.</p>
<p>In this specific case (DCS), I am even compiling with <code>GOAMD64=v4</code>. The <code>v4</code>
microarchitecture level requires AVX512, which means AMD Zen 4, Zen 5 or newer
(Intel’s story is… complicated). Luckily, both my main development PC (Zen 5)
and the Debian Code Search server (Zen 4) are recent enough. Setting
<code>GOAMD64=v4</code> has little effect on Go 1.27 itself: the only change is that maps
use one less instruction (<code>VPBROADCASTB</code> instead of <code>PSHUFB</code>). But compiling
with <code>GOAMD64=v4</code> allows us to move one more feature check from runtime to
compile time, see <a href="https://michael.stapelberg.ch/posts/tags/debian/feed.xml#simd-build-tags">SIMD build tags</a>.</p>
<p>It makes sense to set the microarchitecture level in your benchmark setup so
that you don’t measure the slow fallback implementations. I use <code>export GOAMD64=v4</code> in my <code>Makefile</code>.</p>
<h3 id="benchstat">Benchmarking setup</h3>
<p>Go’s built-in <a href="https://pkg.go.dev/testing#hdr-Benchmarks"><code>testing</code> package contains support for
benchmarks</a> which are written in
functions of the form <code>func BenchmarkXxx(b *testing.B)</code>. The simplest way to run
such benchmarks is <code>go test -bench=.</code>, but I ended up configuring a few
convenience <code>make</code> targets, which write results to <code>bench.txt</code> and compare
against <code>baseline.txt</code> (the previous commit’s results, usually), using the very
useful <a href="https://pkg.go.dev/golang.org/x/perf/cmd/benchstat"><code>benchstat</code> tool</a>.</p>
<div class="highlight"><pre tabindex="0"><code class="language-makefile"><span style="display: flex;"><span><span style="color: #bb60d5;">GOTEST</span><span style="color: #666;">=</span>go <span style="color: #007020;">test</span>
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># -count=6 gives p≤0.002 in benchstat:
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
</span></span></span><span style="display: flex;"><span><span style="color: #bb60d5;">BENCHFLAGS</span><span style="color: #666;">=</span>-run<span style="color: #666;">=</span>^<span style="color: #bb60d5;">$$</span> -bench<span style="color: #666;">=</span>. -benchtime<span style="color: #666;">=</span>200000x -count<span style="color: #666;">=</span><span style="color: #40a070;">6</span>
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># use taskset -c1 to always pin to the same single core,
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># avoiding accidental scheduling on different cores on
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># mixed-core CPUs like the Ryzen 9 9950X3D.
</span></span></span><span style="display: flex;"><span><span style="color: #bb60d5;">TASKSET</span><span style="color: #666;">=</span>taskset -c <span style="color: #40a070;">1</span>
</span></span><span style="display: flex;"><span><span style="color: #bb60d5;">BENCH</span><span style="color: #666;">=</span><span style="color: #007020; font-weight: bold;">$(</span>TASKSET<span style="color: #007020; font-weight: bold;">)</span> <span style="color: #007020; font-weight: bold;">$(</span>GOTEST<span style="color: #007020; font-weight: bold;">)</span> <span style="color: #007020; font-weight: bold;">$(</span>BENCHFLAGS<span style="color: #007020; font-weight: bold;">)</span>
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #06287e;">.PHONY</span><span style="color: #666;">:</span> all test bench bench-baseline bench-relative
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #06287e;">all</span><span style="color: #666;">:</span> test
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #06287e;">bench</span><span style="color: #666;">:</span> test
</span></span><span style="display: flex;"><span>	<span style="color: #007020; font-weight: bold;">$(</span>BENCH<span style="color: #007020; font-weight: bold;">)</span> | tee bench.txt
</span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># Compares compression ratio between C and Go implementation
</span></span></span><span style="display: flex;"><span>	benchstat -col /impl -row <span style="color: #4070a0;">'/n /vals'</span> -filter <span style="color: #4070a0;">'-/impl:go-stream .unit:(encoded-bytes)'</span> bench.txt
</span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># Compares performance between C (cgo) and Go implementation
</span></span></span><span style="display: flex;"><span>	benchstat -col /impl -row <span style="color: #4070a0;">'/n /vals'</span> -filter <span style="color: #4070a0;">'.unit:(Mval/s)'</span> bench.txt
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #06287e;">bench-baseline</span><span style="color: #666;">:</span> test
</span></span><span style="display: flex;"><span>	<span style="color: #007020; font-weight: bold;">$(</span>BENCH<span style="color: #007020; font-weight: bold;">)</span> | tee baseline.txt
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #06287e;">bench-relative</span><span style="color: #666;">:</span> test
</span></span><span style="display: flex;"><span>	<span style="color: #007020; font-weight: bold;">$(</span>BENCH<span style="color: #007020; font-weight: bold;">)</span> | tee bench.txt
</span></span><span style="display: flex;"><span>	benchstat -filter <span style="color: #4070a0;">'-/impl:go-stream .unit:(encoded-bytes)'</span> baseline.txt bench.txt
</span></span><span style="display: flex;"><span>	benchstat -filter <span style="color: #4070a0;">'/impl:go .unit:(Mval/s)'</span> baseline.txt bench.txt
</span></span></code></pre></div><p>The <code>encoded-bytes</code> and <code>Mval/s</code> units are custom metrics I am reporting from
the various <a href="https://go.dev/blog/subtests">sub-benchmarks</a>, which are arranged
such that I can filter / report them with <code>benchstat</code>.</p>
<p>The main encoder (and decoder) benchmarks compare 3 different implementations
(cgo, Go, Go with the StreamEncoder API) with a <a href="https://github.com/Debian/dcs/commit/e4161bebabcf229734f2dc7a9b54c33caa32875a">number of benchmark
cases</a>
that are designed to cover the different block types and contain a similar mix
of values as what we see in Debian Code Search:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// reportMetrics adds Mval/s and encoded-bytes metrics to all benchmarks.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">reportMetrics</span>(b<span style="color: #bbb;"> </span><span style="color: #666;">*</span>testing.B,<span style="color: #bbb;"> </span>n<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>,<span style="color: #bbb;"> </span>nencoded<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">   </span>b.<span style="color: #06287e;">ReportMetric</span>(<span style="color: #007020;">float64</span>(nencoded),<span style="color: #bbb;"> </span><span style="color: #4070a0;">"encoded-bytes"</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">   </span>b.<span style="color: #06287e;">ReportMetric</span>(<span style="color: #007020;">float64</span>(b.N<span style="color: #666;">*</span>n)<span style="color: #666;">/</span><span style="color: #40a070;">1e6</span><span style="color: #666;">/</span>b.<span style="color: #06287e;">Elapsed</span>().<span style="color: #06287e;">Seconds</span>(),<span style="color: #bbb;"> </span><span style="color: #4070a0;">"Mval/s"</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// BenchmarkEncode/n=&lt;N&gt;/vals=&lt;testcase&gt;/impl=&lt;c|go|go-stream&gt;</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">//
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// e.g. BenchmarkEncode/n=2048/vals=one-constant/impl=go-stream</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">BenchmarkEncode</span>(b<span style="color: #bbb;"> </span><span style="color: #666;">*</span>testing.B)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">   </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>_,<span style="color: #bbb;"> </span>tc<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span><span style="color: #06287e;">allBenchCases</span>()<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">     </span>n<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(tc.vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">     </span>b.<span style="color: #06287e;">Run</span>(fmt.<span style="color: #06287e;">Sprintf</span>(<span style="color: #4070a0;">"n=%d/vals=%s"</span>,<span style="color: #bbb;"> </span>n,<span style="color: #bbb;"> </span>tc.name),<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">func</span>(b<span style="color: #bbb;"> </span><span style="color: #666;">*</span>testing.B)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">       </span>b.<span style="color: #06287e;">Run</span>(<span style="color: #4070a0;">"impl=c"</span>,<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">func</span>(b<span style="color: #bbb;"> </span><span style="color: #666;">*</span>testing.B)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span>b.<span style="color: #06287e;">ReportAllocs</span>()<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>encoded<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span>buf<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">make</span>([]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>turbopfor.<span style="color: #06287e;">EncodingSize</span>(n))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>b.<span style="color: #06287e;">Loop</span>()<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">           </span>encoded<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>turbopfor.<span style="color: #06287e;">P4nenc256v32Buf</span>(buf,<span style="color: #bbb;"> </span>tc.vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #06287e;">reportMetrics</span>(b,<span style="color: #bbb;"> </span>n,<span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(encoded))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">       </span>})<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">       </span>b.<span style="color: #06287e;">Run</span>(<span style="color: #4070a0;">"impl=go"</span>,<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">func</span>(b<span style="color: #bbb;"> </span><span style="color: #666;">*</span>testing.B)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span>b.<span style="color: #06287e;">ReportAllocs</span>()<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>be<span style="color: #bbb;"> </span>BlockEncoder<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>encoded<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span>buf<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">make</span>([]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span><span style="color: #40a070;">0</span>,<span style="color: #bbb;"> </span>turbopfor.<span style="color: #06287e;">EncodingSize</span>(n))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>b.<span style="color: #06287e;">Loop</span>()<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">           </span>encoded<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>be.<span style="color: #06287e;">EncodeN</span>(buf,<span style="color: #bbb;"> </span>tc.vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #06287e;">reportMetrics</span>(b,<span style="color: #bbb;"> </span>n,<span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(encoded))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">       </span>})<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">       </span>b.<span style="color: #06287e;">Run</span>(<span style="color: #4070a0;">"impl=go-stream"</span>,<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">func</span>(b<span style="color: #bbb;"> </span><span style="color: #666;">*</span>testing.B)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span>b.<span style="color: #06287e;">ReportAllocs</span>()<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>se<span style="color: #bbb;"> </span>StreamEncoder<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>encoded<span style="color: #bbb;"> </span><span style="color: #902000;">int</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>b.<span style="color: #06287e;">Loop</span>()<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">           </span>encoded<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">           </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>_,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>tc.vals<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">             </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>se.<span style="color: #06287e;">Add</span>(val)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">               </span>encoded<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(se.<span style="color: #06287e;">EncodeBlock</span>())<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">             </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">           </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">           </span>encoded<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(se.<span style="color: #06287e;">EncodeBlock</span>())<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">         </span><span style="color: #06287e;">reportMetrics</span>(b,<span style="color: #bbb;"> </span>n,<span style="color: #bbb;"> </span>encoded)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">       </span>})<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">     </span>})<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">   </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><h3 id="perf">CPU counters: perf</h3>
<p>Go has included excellent performance tooling for many years, see the
<a href="https://go.dev/blog/pprof">“Profiling Go Programs” blog post</a> (2011) for an
example of how to use <code>pprof</code>, a sampling profiler. This profiler can help track
down which part of a program runs slow, or where memory allocations happen.</p>
<p>Once you identified the slow part of a program, how do you know why it’s slow?</p>
<p>To learn more about the specific bottlenecks your program encounters, you can
consult your CPU’s <a href="https://en.wikipedia.org/wiki/Hardware_performance_counter">hardware performance
counters</a>. For
example, you could check the <a href="https://en.wikipedia.org/wiki/Branch_predictor">branch
predictor</a> counters to see if
your program is slow due to a high number of branch mispredicts.</p>
<p>On Linux, the <code>perf</code> tool is the best way to access the CPU hardware performance
counters. A good starting point for working with <code>perf</code> is the <a href="https://perfwiki.github.io/main/top-down-analysis/">documentation on
“Top-down analysis with the perf
tool”</a>, which describes the
optimization method that Intel established.</p>
<p>In my <code>Makefile</code>, I set up two <code>perf</code> targets:</p>
<div class="highlight"><pre tabindex="0"><code class="language-makefile"><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># GOTEST and TASKSET like shown in the earlier benchmarking setup section:
</span></span></span><span style="display: flex;"><span><span style="color: #bb60d5;">GOTEST</span><span style="color: #666;">=</span>go <span style="color: #007020;">test</span> -pgo<span style="color: #666;">=</span>encode.cpuprof
</span></span><span style="display: flex;"><span><span style="color: #bb60d5;">TASKSET</span><span style="color: #666;">=</span>taskset -c <span style="color: #40a070;">1</span>
</span></span><span style="display: flex;"><span><span style="color: #bb60d5;">PERFBENCHFLAGS</span><span style="color: #666;">=</span>-test.bench<span style="color: #666;">=</span><span style="color: #4070a0;">'Encode/n=2048/vals=debian-mix/impl=go$$'</span> -test.benchtime<span style="color: #666;">=</span>200000x
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># Use perf(1) to capture AMD IBS (the equivalent to Intel PEBS)
</span></span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># PipelineL1 is roughly equivalent to Intel TopdownL1
</span></span></span><span style="display: flex;"><span><span style="color: #06287e;">perf</span><span style="color: #666;">:</span>
</span></span><span style="display: flex;"><span>	<span style="color: #007020; font-weight: bold;">$(</span>GOTEST<span style="color: #007020; font-weight: bold;">)</span> -c
</span></span><span style="display: flex;"><span>	<span style="color: #007020; font-weight: bold;">$(</span>TASKSET<span style="color: #007020; font-weight: bold;">)</span> perf stat -M PipelineL1 ./pforenc.test -test.run<span style="color: #666;">=</span>^<span style="color: #bb60d5;">$$</span> <span style="color: #007020; font-weight: bold;">$(</span>PERFBENCHFLAGS<span style="color: #007020; font-weight: bold;">)</span>
</span></span><span style="display: flex;"><span>	sudo perf record -F <span style="color: #40a070;">4999</span> -e ibs_op// --call-graph fp ./pforenc.test -test.run<span style="color: #666;">=</span>^<span style="color: #bb60d5;">$$</span> <span style="color: #007020; font-weight: bold;">$(</span>PERFBENCHFLAGS<span style="color: #007020; font-weight: bold;">)</span>
</span></span><span style="display: flex;"><span>	sudo chmod <span style="color: #40a070;">644</span> perf.data
</span></span><span style="display: flex;"><span>
</span></span><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;"># 488281 iterations × 2048 values = 1.000e9 values, so counter/1e9 = per value.
</span></span></span><span style="display: flex;"><span><span style="color: #06287e;">perf-per-value</span><span style="color: #666;">:</span>
</span></span><span style="display: flex;"><span>	<span style="color: #007020; font-weight: bold;">$(</span>GOTEST<span style="color: #007020; font-weight: bold;">)</span> -c
</span></span><span style="display: flex;"><span>	<span style="color: #007020; font-weight: bold;">$(</span>TASKSET<span style="color: #007020; font-weight: bold;">)</span> perf stat -x, -e cycles:u,instructions:u,branches:u,branch-misses:u ./pforenc.test -test.run<span style="color: #666;">=</span>^<span style="color: #bb60d5;">$$</span> -test.bench<span style="color: #666;">=</span><span style="color: #4070a0;">'Encode/n=2048/vals=debian-mix/impl=go$$'</span> -test.benchtime<span style="color: #666;">=</span>488281x 2&gt;&amp;<span style="color: #40a070;">1</span> &gt;/dev/null | awk -F, <span style="color: #4070a0;">'{printf "%-16s %6.2f /val\n", $$3, $$1/1e9}'</span>
</span></span></code></pre></div><p>The <code>perf-per-value</code> numbers are high level numbers that indicate how much work
the implementation is doing. Reducing the number usually increases speed.</p>
<p>To see the counters for each instruction (and source code lines), I use <code>make perf</code>, followed by <code>perf report</code>. A quick shortcut is <code>perf annotate</code>, which
directly shows the hottest function.</p>
<h2 id="opt-scalar">Optimizations (scalar)</h2>
<p>Let’s first see how far we can get without reaching for SIMD instructions.</p>
<p>(The examples are not necessarily in commit order, but cherry-picked for clarity.)</p>
<h3 id="pgo">Profile-Guided Optimization (PGO)</h3>
<p>PGO stands for Profile-Guided Optimization and is a feature that Go introduced
as a preview in Go 1.20 (released in February 2023) and shipped as <a href="https://go.dev/blog/pgo">ready for
general production use in Go 1.21</a> (released in August
2023).</p>
<p>The idea is to capture a CPU profile that records where your program spends most
of its CPU time, which you then provide to the Go compiler to give it more data
to make better decisions.</p>
<p>Most importantly, this way the Go compiler can <a href="https://go.dev/blog/pgo#inlining">inline functions much more
aggressively</a> than its usual heuristics allow,
which does have a measurably positive effect in my series of optimization
commits. Another optimization that a PGO profile allows the compiler to do is
<a href="https://go.dev/blog/pgo#devirtualization">conditional devirtualization</a> — but
our TurboPFor code does not use any interfaces.</p>
<p>My strategy is to enable PGO before doing any other optimizations, so that we
have the full inlining budget available that PGO gives us, and can measure the
effect of other commits clearly.</p>
<p>Surprisingly, <a href="https://github.com/Debian/dcs/commit/d6a44a18b376bf7b33fa01b8c69fa448c776a0f0">turning on PGO actually decreases our
performance</a>
(-13% geomean), but a closer investigation reveals that we just got unlucky. Let
me explain.</p>
<p>Aside from inlining and conditional devirtualization, PGO also influences
alignment: The Go compiler sets <code>PCALIGNMAX(64, 31)</code> on the first block of a
loop (the “loop body”) for all loops in hot functions (per the PGO profile),
i.e. Go will insert up to 31 bytes of padding to make the block land on a
64-byte boundary. Documentation like <a href="https://docs.amd.com/v/u/en-US/58455_1.00">AMD’s “Software Optimization Guide for the
AMD Zen5 Microarchitecture”</a> (2024,
#58455) explicitly recommends aligning hot loops that way:</p>
<blockquote>
<p>[…] for hot loops, some further knowledge of trade-offs can be
helpful. Because the processor can read an aligned 64-byte fetch block every
cycle, it is suggested to either align the start of the loop to the beginning
of a 64-byte cache line […]</p>
</blockquote>
<p>Indeed, when compiling with <code>-gcflags=all=-d=alignhot=0</code> to disable the
alignment, performance remains as good as without PGO. How can the padding hurt
more than help?  The answer is: It’s not the padding itself! It’s a side-effect
of the padding moving instructions to different addresses.</p>
<p>In the unlucky arrangement, a macro-fused <code>CMPQ</code>+<code>JGE</code> instruction pair now ends
up <em>exactly on a 32-byte boundary</em>. However, the Go compiler ensures fused
branch sequences <em>must never cross or end at a 32-byte boundary</em> to <a href="https://cs.opensource.google/go/go/+/refs/tags/go1.27.0:src/cmd/internal/obj/x86/asm6.go;l=1978-2020;drc=aee6009ba5e1d71948b03ac0458fbc99e3a14ace">fix Intel
erratum
SKX102</a>
(discussion: <a href="https://github.com/golang/go/issues/35881">Go issue #35881</a>) by
inserting <code>NOP</code>s.</p>
<p>This <code>NOP</code> padding, unlike the loop alignment padding, is not free; these extra
instructions slow down our otherwise dispatch-bound loops.</p>
<p>Because the commits after the PGO enabling commit change the code, this unlucky
situation is avoided for the rest of the optimization series (by chance).</p>
<h3 id="reducing-allocations">Reducing memory allocations</h3>
<p>Memory allocations are quite expensive, at least in comparison to
encoding/decoding integers, so I followed my usual strategy of first reducing
memory allocations as much as possible.</p>
<p>In my <code>goturbopfor</code> teaching decoder, whenever the code needed a scratch buffer,
it would allocate it right then and there with <code>make()</code>:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #60a0b0; font-style: italic;">// p4dec32 decodes one block of TurboPFor-encoded 32 bit ints</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span>(d<span style="color: #bbb;"> </span><span style="color: #666;">*</span>decoder)<span style="color: #bbb;"> </span><span style="color: #06287e;">p4dec32</span>(input<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>output<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>(read<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #60a0b0; font-style: italic;">// …</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">switch</span><span style="color: #bbb;"> </span>blockType<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span>blockBitpackingExceptions:<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>bx,<span style="color: #bbb;"> </span>input<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>input[<span style="color: #40a070;">0</span>],<span style="color: #bbb;"> </span>input[<span style="color: #40a070;">1</span>:]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>n<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(output)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>exmap<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>input<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>nex<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// number of exceptions</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span>;<span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span>n;<span style="color: #bbb;"> </span>i<span style="color: #666;">++</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>exmap[i<span style="color: #666;">/</span><span style="color: #40a070;">8</span>]<span style="color: #666;">&amp;</span>(<span style="color: #40a070;">1</span><span style="color: #666;">&lt;&lt;</span><span style="color: #007020;">uint</span>(i<span style="color: #666;">%</span><span style="color: #40a070;">8</span>))<span style="color: #bbb;"> </span><span style="color: #666;">!=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">        </span>nex<span style="color: #666;">++</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>input<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>input[(n<span style="color: #666;">+</span><span style="color: #40a070;">7</span>)<span style="color: #666;">/</span><span style="color: #40a070;">8</span>:]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span>exceptions<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">make</span>([]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>nex)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>input<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>input[<span style="color: #06287e;">bitunpack32</span>(input,<span style="color: #bbb;"> </span>exceptions,<span style="color: #bbb;"> </span>bx):]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>input<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>input[d.<span style="color: #06287e;">bitunpack</span>(input,<span style="color: #bbb;"> </span>output,<span style="color: #bbb;"> </span>b):]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span>;<span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span>n;<span style="color: #bbb;"> </span>i<span style="color: #666;">++</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>exmap[i<span style="color: #666;">/</span><span style="color: #40a070;">8</span>]<span style="color: #666;">&amp;</span>(<span style="color: #40a070;">1</span><span style="color: #666;">&lt;&lt;</span><span style="color: #007020;">uint</span>(i<span style="color: #666;">%</span><span style="color: #40a070;">8</span>))<span style="color: #bbb;"> </span><span style="color: #666;">!=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">        </span>output[i]<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span>exceptions[<span style="color: #40a070;">0</span>]<span style="color: #bbb;"> </span><span style="color: #666;">&lt;&lt;</span><span style="color: #bbb;"> </span>b<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">        </span>exceptions<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>exceptions[<span style="color: #40a070;">1</span>:]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>before<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(input)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}</span></span></code></pre></div>
<p>The Go compiler can turn <code>make(T, n)</code> calls into stack allocations, if <code>n</code> is
known at compile-time. But, in this case <code>nex</code> is not known at compile-time. We
can verify that Go calls into the runtime (<code>runtime.makeslice</code>) by dumping the
object code (assembly) with source annotated (<code>-S</code>):</p>
<div class="highlight"><pre tabindex="0"><code class="language-text"><span style="display: flex;"><span>% cd ~/go/src/github.com/stapelberg/goturbopfor
</span></span><span style="display: flex;"><span>% git reset --hard 49b7c05cc61e77f0257568eb73833467714d2b4a
</span></span><span style="display: flex;"><span>% go test -c  # go1.27.0
</span></span><span style="display: flex;"><span>% go tool objdump -S goturbopfor.test | perl -nlE 'say if /p4dec32/ .. /^$/'
</span></span><span style="display: flex;"><span>TEXT github.com/stapelberg/goturbopfor.(*decoder).p4dec32(SB) /home/michael/go/src/github.com/stapelberg/goturbopfor/goturbopfor.go
</span></span><span style="display: flex;"><span>func (d *decoder) p4dec32(input []byte, output []uint32) (read int) {
</span></span><span style="display: flex;"><span>  0x549f60		4c8da42460ffffff	LEAQ 0xffffff60(SP), R12
</span></span><span style="display: flex;"><span>  0x549f68		4d3b6610		CMPQ R12, 0x10(R14)
</span></span><span style="display: flex;"><span>  0x549f6c		0f86d9070000		JBE 0x54a74b
</span></span><span style="display: flex;"><span>  0x549f72		55			PUSHQ BP
</span></span><span style="display: flex;"><span>  0x549f73		4889e5			MOVQ SP, BP
</span></span><span style="display: flex;"><span>  0x549f76		4881ec18010000		SUBQ $0x118, SP
</span></span><span style="display: flex;"><span>  0x549f7d		48899c2430010000	MOVQ BX, 0x130(SP)
</span></span><span style="display: flex;"><span>  0x549f85		4889b42448010000	MOVQ SI, 0x148(SP)
</span></span><span style="display: flex;"><span>	if len(output) == 0 {
</span></span><span style="display: flex;"><span>  0x549f8d		4d85c0			TESTQ R8, R8
</span></span><span style="display: flex;"><span>  0x549f90		0f84a7030000		JE 0x54a33d
</span></span><span style="display: flex;"><span>  0x549f96		660f1f840000000000	NOPW 0(AX)(AX*1)
</span></span><span style="display: flex;"><span>  0x549f9f		90			NOPL
</span></span><span style="display: flex;"><span>[…]
</span></span><span style="display: flex; background-color: #d8d8d8;"><span>		exceptions := make([]uint32, nex)
</span></span><span style="display: flex; background-color: #d8d8d8;"><span>  0x54a4be		488d057bec1700		LEAQ 0x17ec7b(IP), AX
</span></span><span style="display: flex; background-color: #d8d8d8;"><span>  0x54a4c5		4c89fb			MOVQ R15, BX
</span></span><span style="display: flex; background-color: #d8d8d8;"><span>  0x54a4c8		4889d9			MOVQ BX, CX
</span></span><span style="display: flex; background-color: #d8d8d8;"><span>  0x54a4cb		e8f0ddf3ff		CALL runtime.makeslice(SB)
</span></span><span style="display: flex;"><span>[…]</span></span></code></pre></div>
<p>An easy speed-up was to <a href="https://github.com/stapelberg/goturbopfor/commit/3c530dbcfc8fe01674e3168c296fcfdf3d06718c">avoid allocations through
reuse</a>
(in <code>goturbopfor</code>). In the DCS <code>pfordec</code> package (with the <a href="https://michael.stapelberg.ch/posts/tags/debian/feed.xml#api-design">improved
API design</a>), I ended up with a <code>vals [256]uint32</code> field in the <code>StreamDecoder</code>
type, which brings us from 773 Mval/s to 858 Mval/s on the debian-mix:</p>
<pre tabindex="0"><code>% benchstat -filter '/impl:go /vals:debian-mix .unit:(Mval/s)' \
  baseline.txt bench.txt
goos: linux
goarch: amd64
pkg: github.com/Debian/dcs/internal/turbopfor/pfordec
cpu: AMD Ryzen 9 9950X3D 16-Core Processor
           │ baseline.txt │             bench.txt              │
           │    Mval/s    │   Mval/s     vs base               │
n=2048        1.089k ± 1%   1.175k ± 0%   +7.85% (p=0.002 n=6)
n=2039         974.7 ± 0%   1046.0 ± 0%   +7.32% (p=0.002 n=6)
n=160          434.9 ± 1%    513.6 ± 5%  +18.11% (p=0.002 n=6)
geomean        772.9         857.7       +10.98%
</code></pre><p>Aside from the speed-up, avoiding memory allocations is generally nice in
benchmarks because it removes the garbage collector from the equation and makes
it less likely that your benchmarks get other processes OOM-killed on the same
machine.</p>
<h3 id="bit-width-specialization">Generics for bit width specialization</h3>
<p>In general, we want to make it easy for the compiler to understand as much as
possible about our algorithm. Consider this <code>bitpack</code> implementation:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">bitpack</span>(dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>mask<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(<span style="color: #40a070;">1</span><span style="color: #666;">&lt;&lt;</span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>acc<span style="color: #bbb;"> </span><span style="color: #902000;">uint64</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;"> </span><span style="color: #902000;">int</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>_,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">|=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint64</span>(val<span style="color: #666;">&amp;</span>mask)<span style="color: #bbb;"> </span><span style="color: #666;">&lt;&lt;</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>have<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;"> </span><span style="color: #666;">&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>binary.LittleEndian.<span style="color: #06287e;">AppendUint32</span>(dest,<span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(acc))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">&gt;&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>have<span style="color: #bbb;"> </span><span style="color: #666;">-=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;"> </span>&gt;<span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span><span style="color: #007020;">append</span>(dest,<span style="color: #bbb;"> </span><span style="color: #007020;">byte</span>(acc))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">&gt;&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>have<span style="color: #bbb;"> </span><span style="color: #666;">-=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>dest<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p>Let’s think through what determines the iterations and control flow this
function uses:</p>
<ol>
<li>The <em>number of input values</em> (<code>vals</code>), but not their actual value.</li>
<li>The bit width to pack into (<code>bitWidth</code>).</li>
</ol>
<p>With a bit of careful rearrangement, we can provide the compiler with both, a
fixed number of input values (say, 32), and a bit width, both known at compile
time. Why is this worthwhile? Because we can manually unroll the loop, let the
compiler eliminate much of the repetition and get much faster compiled code as a
result!</p>
<p>Let’s first fix the number of input values to 32 and rewrite the loop to
calculate the position offsets within <code>dest</code> instead of changing <code>dest</code> on each
value (with <code>AppendUint32</code>):</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">bitpack32Unrolled</span>(dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span><span style="color: #666;">*</span>[<span style="color: #40a070;">32</span>]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// only one bounds check for 32 values</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>dest[:<span style="color: #bbb;"> </span><span style="color: #40a070;">4</span><span style="color: #666;">*</span>bitWidth<span style="color: #bbb;"> </span>:<span style="color: #bbb;"> </span><span style="color: #40a070;">4</span><span style="color: #666;">*</span>bitWidth]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>mask<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(<span style="color: #40a070;">1</span><span style="color: #666;">&lt;&lt;</span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>acc<span style="color: #bbb;"> </span><span style="color: #902000;">uint64</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>have,<span style="color: #bbb;"> </span>pos<span style="color: #bbb;"> </span><span style="color: #902000;">int</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Manually unrolled loop starts here.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Each iteration is identical except for the vals[x] index.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">|=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint64</span>(vals[<span style="color: #40a070;">0</span>]<span style="color: #666;">&amp;</span>mask)<span style="color: #bbb;"> </span><span style="color: #666;">&lt;&lt;</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span>have<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;"> </span><span style="color: #666;">&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span>binary.LittleEndian.<span style="color: #06287e;">PutUint32</span>(dest[pos:pos<span style="color: #666;">+</span><span style="color: #40a070;">4</span>],<span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(acc))<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span>pos<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">4</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">&gt;&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span>have<span style="color: #bbb;"> </span><span style="color: #666;">-=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// vals[1] .. vals[30] elided for brevity</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Each loop iteration is 8 lines of Go code, so for 32 input values,</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// bitpack32Unrolled contains 8*32 = 256 lines of code.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">|=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint64</span>(vals[<span style="color: #40a070;">31</span>]<span style="color: #666;">&amp;</span>mask)<span style="color: #bbb;"> </span><span style="color: #666;">&lt;&lt;</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>have<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;"> </span><span style="color: #666;">&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>binary.LittleEndian.<span style="color: #06287e;">PutUint32</span>(dest[pos:pos<span style="color: #666;">+</span><span style="color: #40a070;">4</span>],<span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(acc))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>pos<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">4</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">&gt;&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>have<span style="color: #bbb;"> </span><span style="color: #666;">-=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// have == 0; for all bitWidths</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}</span></span></code></pre></div>
<p>Next, we want to specialize not just for 32 input values, but also for each of
the 32 bit widths.</p>
<p>Can we do better than hand-copying <code>bitpack32Unrolled</code> 32 times (= 8192 lines of Go code)?</p>
<p>Yes, we can use Go generics to help us with the code generation!</p>
<p>In Go, <a href="https://go.dev/ref/spec#Array_types">array types</a> like <code>[4]byte</code> (not
slices like <code>[]byte</code>!)  contain the length of the array as part of their type,
meaning <code>[1]byte</code> (an array of length 1) is a different type than <code>[2]byte</code>.</p>
<p>Instead of passing the bit width as a function parameter, we can declare 32
<em>different types</em> (one for each bit width) and recover the bit width (at compile
time!) from the type system:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">type</span><span style="color: #bbb;"> </span>bitWidthT<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">interface</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>[<span style="color: #40a070;">1</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">2</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">3</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">4</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">5</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>[<span style="color: #40a070;">6</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">7</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">8</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">9</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">10</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>[<span style="color: #40a070;">11</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">12</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">13</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">14</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">15</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>[<span style="color: #40a070;">16</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">17</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">18</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">19</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">20</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>[<span style="color: #40a070;">21</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">22</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">23</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">24</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">25</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>[<span style="color: #40a070;">26</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">27</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">28</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">29</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">30</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>[<span style="color: #40a070;">31</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>|<span style="color: #bbb;"> </span>[<span style="color: #40a070;">32</span>]<span style="color: #902000;">byte</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span>bitpack32Unrolled[T<span style="color: #bbb;"> </span>bitWidthT](dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span><span style="color: #666;">*</span>[<span style="color: #40a070;">32</span>]<span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>zero<span style="color: #bbb;"> </span>T<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(zero)<span style="color: #bbb;">                  </span><span style="color: #60a0b0; font-style: italic;">// known at compile time</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>dest[:<span style="color: #bbb;"> </span><span style="color: #40a070;">4</span><span style="color: #666;">*</span>bitWidth<span style="color: #bbb;"> </span>:<span style="color: #bbb;"> </span><span style="color: #40a070;">4</span><span style="color: #666;">*</span>bitWidth]<span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// make cap known at compile time</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>mask<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(<span style="color: #40a070;">1</span><span style="color: #666;">&lt;&lt;</span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>acc<span style="color: #bbb;"> </span><span style="color: #902000;">uint64</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>have,<span style="color: #bbb;"> </span>pos<span style="color: #bbb;"> </span><span style="color: #902000;">int</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Manually unrolled loop starts here.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Each iteration is identical except for the vals[x] index.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">|=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint64</span>(vals[<span style="color: #40a070;">0</span>]<span style="color: #666;">&amp;</span>mask)<span style="color: #bbb;"> </span><span style="color: #666;">&lt;&lt;</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>have<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;"> </span><span style="color: #666;">&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>binary.LittleEndian.<span style="color: #06287e;">PutUint32</span>(dest[pos:pos<span style="color: #666;">+</span><span style="color: #40a070;">4</span>],<span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(acc))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>pos<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">4</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">&gt;&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>have<span style="color: #bbb;"> </span><span style="color: #666;">-=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// vals[1] .. vals[31] elided for brevity</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}</span></span></code></pre></div>
<p>When we instantiate <code>bitpack32Unrolled[bitWidthT]</code> with all 32 different types
(<code>[1]byte</code>, <code>[2]byte</code>, …, <code>[32]byte</code>), the compiler substitutes the <code>bitWidthT</code>
type parameter and produces 32 copies of the function, which we can find in our
compiled executable with names like
<code>github.com/Debian/dcs/internal/turbopfor/pforenc.bitpack32Unrolled[go.shape.[12]uint8]</code>. The
“shape” of a generic type is based on its memory layout, so a shape for
<code>[1]byte</code> must be different than the shape for <code>[2]byte</code>.</p>
<p>Because the <code>bitWidth</code> is now known at compile time, the Go compiler can
generate close to the optimal machine code for each bit width, which we can
confirm using <code>go tool objdump</code>.</p>
<p>The code is branchless (after the one bounds check per 32 values) and aside from
the loads and stores (from/to memory) consists only of shifts and bit
operations, all with constant operands:</p>
<pre tabindex="0"><code>% go test -c &amp;&amp; go tool objdump -S pforenc.test
[…]
TEXT github.com/Debian/dcs/internal/turbopfor/pforenc.bitpack32Unrolled[go.shape.[28]uint8](SB) /home/michael/dcs/internal/turbopfor/pforenc/bitpackunroll.go
func bitpack32Unrolled[T bitWidthT](dest []byte, vals *[32]uint32) {
  0x660580              55                      PUSHQ BP
  0x660581              4889e5                  MOVQ SP, BP
  0x660584              48895c2418              MOVQ BX, 0x18(SP)
        dest = dest[: 4*bitWidth : 4*bitWidth] // make cap known at compile time
  0x660589              4883ff70                CMPQ DI, $0x70
  0x66058d              0f820b030000            JB 0x66089e
        acc |= uint64(vals[0]&amp;mask) &lt;&lt; have
  0x660593              8b06                    MOVL 0(SI), AX
  0x660595              25ffffff0f              ANDL $0xfffffff, AX
        acc |= uint64(vals[1]&amp;mask) &lt;&lt; have
  0x66059a              8b4e04                  MOVL 0x4(SI), CX
  0x66059d              81e1ffffff0f            ANDL $0xfffffff, CX
  0x6605a3              48c1e11c                SHLQ $0x1c, CX
  0x6605a7              4809c8                  ORQ CX, AX
                acc &gt;&gt;= 32
  0x6605aa              4889c1                  MOVQ AX, CX
  0x6605ad              48c1e820                SHRQ $0x20, AX
                binary.LittleEndian.PutUint32(dest[pos:pos+4], uint32(acc))
  0x6605b1              90                      NOPL
        b[0] = byte(v)
  0x6605b2              890b                    MOVL CX, 0(BX)
        acc |= uint64(vals[2]&amp;mask) &lt;&lt; have
  0x6605b4              8b4e08                  MOVL 0x8(SI), CX
  0x6605b7              81e1ffffff0f            ANDL $0xfffffff, CX
  0x6605bd              48c1e118                SHLQ $0x18, CX
  0x6605c1              4809c1                  ORQ AX, CX
                acc &gt;&gt;= 32
  0x6605c4              4889c8                  MOVQ CX, AX
  0x6605c7              48c1e920                SHRQ $0x20, CX
                binary.LittleEndian.PutUint32(dest[pos:pos+4], uint32(acc))
  0x6605cb              90                      NOPL
        b[0] = byte(v)
  0x6605cc              894304                  MOVL AX, 0x4(BX)
</code></pre><p>Now we need to actually call <code>bitpack32</code> from the general <code>bitpack</code> function:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">bitpack</span>(dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">==</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>dest<span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// no payload, sparse block with only exceptions</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(vals)<span style="color: #bbb;"> </span><span style="color: #666;">&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span>size<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">4</span><span style="color: #bbb;"> </span><span style="color: #666;">*</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(vals)<span style="color: #bbb;"> </span><span style="color: #666;">&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span>existing<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(dest)<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>slices.<span style="color: #06287e;">Grow</span>(dest,<span style="color: #bbb;"> </span>size)[:existing<span style="color: #666;">+</span>size]<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span><span style="color: #06287e;">bitpack32</span>(dest[existing:]<span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">/*append*/</span>,<span style="color: #bbb;"> </span>(<span style="color: #666;">*</span>[<span style="color: #40a070;">32</span>]<span style="color: #902000;">uint32</span>)(vals),<span style="color: #bbb;"> </span>bitWidth)<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span>vals<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>vals[<span style="color: #40a070;">32</span>:]<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>mask<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(<span style="color: #40a070;">1</span><span style="color: #666;">&lt;&lt;</span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>acc<span style="color: #bbb;"> </span><span style="color: #902000;">uint64</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;"> </span><span style="color: #902000;">int</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>_,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">|=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint64</span>(val<span style="color: #666;">&amp;</span>mask)<span style="color: #bbb;"> </span><span style="color: #666;">&lt;&lt;</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>have<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;"> </span><span style="color: #666;">&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>binary.LittleEndian.<span style="color: #06287e;">AppendUint32</span>(dest,<span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(acc))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">&gt;&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>have<span style="color: #bbb;"> </span><span style="color: #666;">-=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>have<span style="color: #bbb;"> </span>&gt;<span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>dest<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span><span style="color: #007020;">append</span>(dest,<span style="color: #bbb;"> </span><span style="color: #007020;">byte</span>(acc))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>acc<span style="color: #bbb;"> </span><span style="color: #666;">&gt;&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>have<span style="color: #bbb;"> </span><span style="color: #666;">-=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>dest<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">bitpack32</span>(dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span><span style="color: #666;">*</span>[<span style="color: #40a070;">32</span>]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">switch</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">1</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">2</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">2</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">3</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">3</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">4</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">4</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">5</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">5</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">6</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">6</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">7</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">7</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">8</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">9</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">9</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">10</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">10</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">11</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">11</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">12</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">12</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">13</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">13</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">14</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">14</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">15</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">15</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">16</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">16</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">17</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">17</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">18</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">18</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">19</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">19</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">20</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">20</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">21</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">21</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">22</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">22</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">23</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">23</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">24</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">24</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">25</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">25</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">26</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">26</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">27</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">27</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">28</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">28</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">29</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">29</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">30</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">30</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">31</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">31</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span>:<span style="color: #bbb;"> </span>bitpack32Unrolled[[<span style="color: #40a070;">32</span>]<span style="color: #902000;">byte</span>](dest,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}</span></span></code></pre></div>
<p>Encoding remainder blocks is quite a bit faster (full blocks use the vertical
layout anyway):</p>
<pre tabindex="0"><code>% benchstat -filter '/impl:go /n:160 .unit:(Mval/s)' baseline.txt bench.txt
goos: linux
goarch: amd64
pkg: github.com/Debian/dcs/internal/turbopfor/pforenc
cpu: AMD Ryzen 9 9950X3D 16-Core Processor
                         │ baseline.txt │             bench.txt              │
                         │    Mval/s    │   Mval/s     vs base               │
vals=bitpacking-bw1          751.2 ± 3%   1120.5 ± 0%  +49.15% (p=0.002 n=6)
vals=bitpacking-bw2          716.8 ± 2%   1176.0 ± 0%  +64.07% (p=0.002 n=6)
vals=bitpacking-bw7          700.0 ± 1%   1078.5 ± 0%  +54.08% (p=0.002 n=6)
vals=bitpacking-bw1-exc      524.8 ± 1%    736.8 ± 0%  +40.40% (p=0.002 n=6)
vals=bitpacking-bw2-exc      543.7 ± 1%    758.2 ± 0%  +39.46% (p=0.002 n=6)
vals=bitpacking-bw7-exc      566.7 ± 1%    787.7 ± 0%  +38.99% (p=0.002 n=6)
vals=bitpacking-vb-exc       442.6 ± 1%    616.5 ± 0%  +39.29% (p=0.002 n=6)
vals=sparse-exc              532.4 ± 0%    787.8 ± 0%  +47.97% (p=0.002 n=6)
vals=sparse-vb-exc           408.9 ± 1%    597.8 ± 0%  +46.20% (p=0.002 n=6)
vals=debian-mix              559.5 ± 0%    783.8 ± 9%  +40.09% (p=0.002 n=6)
</code></pre><p>This performance win comes at the cost of binary size increase. In this case,
the <code>.text</code> section (executable code) grows by about 20 KB and the <code>.gopclntab</code>
section grows by another 26 KB. Definitely a price I am very willing to pay, but
the case might not be as clear in all circumstances.</p>
<h2 id="simd-bigger-strides">Optimization: Bigger strides with SIMD</h2>
<p>Even without reaching for SIMD instructions, a TurboPFor implementation can be
made faster by making it work bigger strides. Take this code from the
<code>goturbopfor</code> teaching decoder which counts the number of exceptions by checking
if each value’s bit is set in the exception bitmap:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span>blockBitpackingExceptions:<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>bx,<span style="color: #bbb;"> </span>input<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>input[<span style="color: #40a070;">0</span>],<span style="color: #bbb;"> </span>input[<span style="color: #40a070;">1</span>:]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>n<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(output)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>exmap,<span style="color: #bbb;"> </span>input<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>input,<span style="color: #bbb;"> </span>input[(n<span style="color: #666;">+</span><span style="color: #40a070;">7</span>)<span style="color: #666;">/</span><span style="color: #40a070;">8</span>:]<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span>nex<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// number of exceptions</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>n<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>exmap[i<span style="color: #666;">/</span><span style="color: #40a070;">8</span>]<span style="color: #666;">&amp;</span>(<span style="color: #40a070;">1</span><span style="color: #666;">&lt;&lt;</span><span style="color: #007020;">uint</span>(i<span style="color: #666;">%</span><span style="color: #40a070;">8</span>))<span style="color: #bbb;"> </span><span style="color: #666;">!=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span>nex<span style="color: #666;">++</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>exceptions<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>d.scratch[:nex]</span></span></code></pre></div>
<p>We can use the <a href="https://pkg.go.dev/math/bits#OnesCount64"><code>bits.OnesCount64</code></a>
functions to count ones bits in the exception bitmap, 64 values at a time. For
remainder blocks, the rest is processed 8 values (1 byte) at a time:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span>i<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>;<span style="color: #bbb;"> </span>i<span style="color: #666;">+</span><span style="color: #40a070;">8</span><span style="color: #bbb;"> </span><span style="color: #666;">&lt;=</span><span style="color: #bbb;"> </span>n<span style="color: #666;">/</span><span style="color: #40a070;">8</span>;<span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>xm8<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>binary.LittleEndian.<span style="color: #06287e;">Uint64</span>(exmap[i:])<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>nex<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span>bits.<span style="color: #06287e;">OnesCount64</span>(xm8)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>;<span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span>(n<span style="color: #666;">+</span><span style="color: #40a070;">7</span>)<span style="color: #666;">/</span><span style="color: #40a070;">8</span>;<span style="color: #bbb;"> </span>i<span style="color: #666;">++</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>xmb<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>exmap[i]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Clear the bits which do not belong to the exception map:</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>rem<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>n<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span>i<span style="color: #666;">*</span><span style="color: #40a070;">8</span>;<span style="color: #bbb;"> </span>rem<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>xmb<span style="color: #bbb;"> </span><span style="color: #666;">&amp;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span><span style="color: #666;">&lt;&lt;</span>rem<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Go compiles OnesCount32 into an intrinsic,</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// but not OnesCount8, so we convert to uint32:</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>nex<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span>bits.<span style="color: #06287e;">OnesCount32</span>(<span style="color: #007020;">uint32</span>(xmb))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p><code>OnesCount64</code> uses a 64-bit register. For comparison, AVX2 SIMD instructions use
256-bit registers (= 8 <code>uint32</code>) and AVX512 SIMD instructions use 512-bit registers.</p>
<p>In the following sections, we will first set up our build tags for conditional
compilation to use a trivial SIMD instruction, then walk through an AVX2 and
AVX512 SIMD kernel.</p>
<h3 id="simd-build-tags">SIMD build tags</h3>
<p>Let’s assume we have the following scalar code:</p>
<p><code>constant.go</code>:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">package</span><span style="color: #bbb;"> </span>pfordec<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">fillConstant</span>(output<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>output<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>output[i]<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>val<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p>To increase throughput, we can use AVX2 instructions if they are available on the CPU on which the program runs, i.e. using runtime dispatch. We’ll first rename <code>fillConstant</code> to <code>fillConstantScalar</code> (it’s now the fallback path):</p>
<p><code>constant.go</code>:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">package</span><span style="color: #bbb;"> </span>pfordec<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">fillConstantScalar</span>(output<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>output<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>output[i]<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>val<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p>Next, we’ll supply two different implementations (<code>constant_nosimd.go</code> and <code>constant_amd64.go</code>), the latter of which is selected when compiling for <code>GOARCH=amd64</code> with <code>GOEXPERIMENT=simd</code> (the latter will hopefully be dropped in a later version of Go). The <code>nosimd</code> variant just dispatches to the <code>fillConstantScalar</code>, which will likely be inlined:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020;">//go:build !goexperiment.simd || !amd64</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">package</span><span style="color: #bbb;"> </span>pfordec<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">fillConstant</span>(output<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #06287e;">fillConstantScalar</span>(output,<span style="color: #bbb;"> </span>val)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p>The <code>constant_amd64.go</code> variant assigns the <code>hasAVX2</code> global variable by doing a <code>CPUID</code> check and then jumps to the scalar fallback if <code>!hasAVX2</code>, i.e. the CPU is too old:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020;">//go:build goexperiment.simd &amp;&amp; amd64</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">package</span><span style="color: #bbb;"> </span>pfordec<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">import</span><span style="color: #bbb;"> </span><span style="color: #4070a0;">"simd/archsimd"</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>hasAVX2<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>archsimd.X86.<span style="color: #06287e;">AVX2</span>()<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">fillConstant</span>(output<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>!hasAVX2<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #06287e;">fillConstantScalar</span>(output,<span style="color: #bbb;"> </span>val)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>val8<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>archsimd.<span style="color: #06287e;">BroadcastUint32x8</span>(val)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>i<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>;<span style="color: #bbb;"> </span>i<span style="color: #666;">+</span><span style="color: #40a070;">8</span><span style="color: #bbb;"> </span><span style="color: #666;">&lt;=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(output);<span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>val8.<span style="color: #06287e;">StoreArray</span>((<span style="color: #666;">*</span>[<span style="color: #40a070;">8</span>]<span style="color: #902000;">uint32</span>)(output[i<span style="color: #bbb;"> </span>:<span style="color: #bbb;"> </span>i<span style="color: #666;">+</span><span style="color: #40a070;">8</span>]))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// use the scalar implementation for the last &lt;= 7 elements</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #06287e;">fillConstantScalar</span>(output[i:],<span style="color: #bbb;"> </span>val)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p>We can go one step further by conditionally compiling <code>const hasAVX2 = true</code>
when <code>GOAMD64</code> is set to <code>v3</code> or higher (i.e. the <code>amd64.v3</code> build tag is
set). As a practical example from Debian Code Search, we currently need the
following checks / dispatches:</p>
<table>
	<thead>
			<tr>
					<th>code</th>
					<th>function</th>
					<th>vector instruction set</th>
					<th>GOAMD64</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>encoder</td>
					<td>bitpack256v</td>
					<td>AVX2</td>
					<td>GOAMD64=v3</td>
			</tr>
			<tr>
					<td>encoder</td>
					<td>exbitmap</td>
					<td>AVX512</td>
					<td>GOAMD64=v4</td>
			</tr>
			<tr>
					<td>encoder</td>
					<td>scan</td>
					<td>AVX512+VBMI+GFNI+BITALG</td>
					<td><em>n/a</em></td>
			</tr>
			<tr>
					<td>decoder</td>
					<td>bitunpack</td>
					<td>AVX2</td>
					<td>GOAMD64=v3</td>
			</tr>
			<tr>
					<td>decoder</td>
					<td>bitunpack256v32</td>
					<td>AVX2</td>
					<td>GOAMD64=v3</td>
			</tr>
			<tr>
					<td>decoder</td>
					<td>bitunpack256v32Ex</td>
					<td>AVX512</td>
					<td>GOAMD64=v4</td>
			</tr>
	</tbody>
</table>
<p>In DCS, the <a href="https://github.com/Debian/dcs/commit/c25276bfc1dce5b6fb5e3706c9f436039b788d61">effect is measurably positive, but
small</a>.</p>
<h3 id="bitunpack256v32">The 256 uint32 vertical layout</h3>
<p>First, here is the layout explanation from <a href="https://michael.stapelberg.ch/posts/2019-02-05-turbopfor-analysis/">my 2019 TurboPFor analysis blog
post</a>:</p>
<blockquote>
<p>In <strong>regular (non-SIMD) bitpacking</strong>, integers are stored on disk one after
the other, padded to a full byte, as a byte is the smallest addressable unit
when reading data from disk. For example, if you bitpack only one 3 bit int,
you will end up with 5 bits of padding.</p>
<img src="https://michael.stapelberg.ch/posts//turbopfor/bitpacking.svgo.svg" />
<p><strong>SIMD bitpacking</strong> works like regular bitpacking, but processes 8 <code>uint32</code>
little-endian values at the same time, leveraging the <a href="https://en.wikipedia.org/wiki/Advanced_Vector_Extensions">AVX instruction
set</a>. The following
illustration shows the order in which 3-bit integers are decoded from disk:</p>
<img src="https://michael.stapelberg.ch/posts/turbopfor/bitpacking256v32.svgo.svg" />
</blockquote>
<p>The scalar implementation uses an array of 8 <code>uint64</code> to process 8 values at a
time:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">bitunpack256v32</span>(input<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>(read<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>mask<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint64</span>(<span style="color: #40a070;">1</span>)<span style="color: #666;">&lt;&lt;</span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>orig<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(input)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>bits<span style="color: #bbb;"> </span><span style="color: #902000;">uint</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>acc<span style="color: #bbb;"> </span>[<span style="color: #40a070;">8</span>]<span style="color: #902000;">uint64</span><span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// accumulator: current+next bits</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>op<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span>;<span style="color: #bbb;"> </span>op<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(dest);<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>bits<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span><span style="color: #007020;">uint</span>(bitWidth)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span><span style="color: #60a0b0; font-style: italic;">// read 8 more uint32s</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">        </span>acc[i]<span style="color: #bbb;"> </span><span style="color: #666;">|=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint64</span>(binary.LittleEndian.<span style="color: #06287e;">Uint32</span>(input))<span style="color: #bbb;"> </span><span style="color: #666;">&lt;&lt;</span><span style="color: #bbb;"> </span>bits<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">        </span>input<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>input[<span style="color: #40a070;">4</span>:]<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>bits<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>i<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span>dest[op]<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span><span style="color: #007020;">uint32</span>(acc[i]<span style="color: #bbb;"> </span><span style="color: #666;">&amp;</span><span style="color: #bbb;"> </span>mask)<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span>op<span style="color: #666;">++</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">      </span>acc[i]<span style="color: #bbb;"> </span><span style="color: #666;">&gt;&gt;=</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>bits<span style="color: #bbb;"> </span><span style="color: #666;">-=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint</span>(bitWidth)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>orig<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(input)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}</span></span></code></pre></div>
<p>The SIMD version also processes 8 values, but without a <code>for i := range 8</code> loop!</p>
<p>One difference is that we no longer have the luxury of using <code>uint64</code> for <code>acc</code>
(holding rest and current bits); because AVX2 registers only fit 8 <code>uint32</code> (not
8 <code>uint64</code>). Instead, we split <code>acc</code> into <code>rest8</code> and <code>cur8</code>.</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">bitunpack256v32</span>(fullinput<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>fulldest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>(read<span style="color: #bbb;"> </span><span style="color: #902000;">int</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>dest<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>fulldest[:<span style="color: #40a070;">256</span>]<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">==</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020;">clear</span>(dest)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>n<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;"> </span><span style="color: #666;">*</span><span style="color: #bbb;"> </span><span style="color: #007020;">int</span>(bitWidth)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>input<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>fullinput[:n]<span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// tell the Go compiler how long the input is</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>mask8<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>archsimd.<span style="color: #06287e;">BroadcastUint32x8</span>(<span style="color: #007020;">uint32</span>(<span style="color: #40a070;">1</span>)<span style="color: #666;">&lt;&lt;</span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>bitWidth8<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>archsimd.<span style="color: #06287e;">BroadcastUint32x8</span>(<span style="color: #007020;">uint32</span>(bitWidth))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>bits<span style="color: #bbb;"> </span><span style="color: #902000;">uint</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>pos<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// var acc [8]uint64</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>rest8<span style="color: #bbb;"> </span>archsimd.Uint32x8<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>cur8<span style="color: #bbb;"> </span>archsimd.Uint32x8<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>op<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span>;<span style="color: #bbb;"> </span>op<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span><span style="color: #40a070;">256</span>;<span style="color: #bbb;"> </span>op<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">8</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>bits<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span><span style="color: #007020;">uint</span>(bitWidth)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span><span style="color: #60a0b0; font-style: italic;">// read 8 more uint32s</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span><span style="color: #60a0b0; font-style: italic;">// acc[i] |= uint64(binary.LittleEndian.Uint32(input)) &lt;&lt; bits</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>next<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>archsimd.<span style="color: #06287e;">LoadUint8x32</span>(input[pos<span style="color: #bbb;"> </span>:<span style="color: #bbb;"> </span>pos<span style="color: #666;">+</span><span style="color: #40a070;">32</span>]).<span style="color: #06287e;">ReshapeToUint32s</span>()<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>pos<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// input = input[4:]</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>cur8<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>rest8.<span style="color: #06287e;">Or</span>(next.<span style="color: #06287e;">ShiftAllLeft</span>(<span style="color: #007020;">uint64</span>(bits)))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span><span style="color: #60a0b0; font-style: italic;">// acc[i] &gt;&gt;= bitWidth</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>rest8<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>next.<span style="color: #06287e;">ShiftAllRight</span>(<span style="color: #007020;">uint64</span>(<span style="color: #007020;">uint</span>(bitWidth)<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span>bits))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>bits<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">else</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>cur8<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>rest8<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span><span style="color: #60a0b0; font-style: italic;">// acc[i] &gt;&gt;= bitWidth</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>rest8<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>rest8.<span style="color: #06287e;">ShiftRight</span>(bitWidth8)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #60a0b0; font-style: italic;">// dest[op] = uint32(acc[i] &amp; mask)</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>cur8.<span style="color: #06287e;">And</span>(mask8).<span style="color: #06287e;">Store</span>(dest[op<span style="color: #bbb;"> </span>:<span style="color: #bbb;"> </span>op<span style="color: #666;">+</span><span style="color: #40a070;">8</span>])<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>bits<span style="color: #bbb;"> </span><span style="color: #666;">-=</span><span style="color: #bbb;"> </span><span style="color: #007020;">uint</span>(bitWidth)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>n<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p>The SIMD version benchmarks about 3x as fast as the scalar version.</p>
<p>Another significant speedup is to <a href="https://michael.stapelberg.ch/posts/tags/debian/feed.xml#bit-width-specialization">use generics for bit width
specialization</a> for this SIMD kernel so that
<code>bitWidth</code> becomes a compile-time constant and the compiler can generate better
code.</p>
<h3 id="positional-popcount">Positional Popcount</h3>
<p>For my TurboPFor encoder, I implemented the same techniques as described above:</p>
<ol>
<li>
<p><a href="https://github.com/Debian/dcs/commit/6a9b173c7686592bb7852610e865057f086e0964">Bitpack full blocks with SIMD (AVX2)</a></p>
</li>
<li>
<p><a href="https://github.com/Debian/dcs/commit/5d584897157b73774fb22c3d0810a40f7edf68f9">Gather exceptions using SIMD (AVX512)</a></p>
</li>
<li>
<p><a href="https://github.com/Debian/dcs/commit/2db8415c5dde141fdc7736a57d3843b459c88da1">Use generics to specialize per bit width</a></p>
</li>
</ol>
<p>These changes are sufficient to roughly match the cgo performance, but then
Claude Fable 5 found another <strong>2x speed-up on top of that</strong>!</p>
<p>The key observation is that once encoding blocks is fast, the preceding step of
scanning the input values to decide which block type to use becomes the
bottleneck. Here is the encoder’s main <code>encode</code> function, which first does one
pass over the input values (<code>scan</code>) and then prices all different block types at
all relevant bit widths (requires fast access to the <code>scan</code> histogram):</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span>(be<span style="color: #bbb;"> </span><span style="color: #666;">*</span>BlockEncoder)<span style="color: #bbb;"> </span><span style="color: #06287e;">encode</span>(dest<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span>,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>,<span style="color: #bbb;"> </span>layout<span style="color: #bbb;"> </span>blockLayout)<span style="color: #bbb;"> </span>[]<span style="color: #902000;">byte</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>stats<span style="color: #bbb;"> </span>stats<span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span><span style="color: #06287e;">scan</span>(<span style="color: #666;">&amp;</span>stats,<span style="color: #bbb;"> </span>vals)<span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// gathers statistics from every value in vals</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>bits.<span style="color: #06287e;">Len32</span>(stats.or)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>stats.or<span style="color: #bbb;"> </span><span style="color: #666;">==</span><span style="color: #bbb;"> </span>stats.and<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>be.<span style="color: #06287e;">encodeConstant</span>(dest,<span style="color: #bbb;"> </span>vals,<span style="color: #bbb;"> </span>bitWidth)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>n<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(vals)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// bitpacking is the default, unless we find a more efficient block type.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>bestType<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>blockBitpacking<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>bestB<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>best<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #06287e;">priceBitpack</span>(n,<span style="color: #bbb;"> </span>bitWidth,<span style="color: #bbb;"> </span>layout)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Walk from high bitWidths to low: to break ties, we prefer</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// the encoding with fewer exceptions (for faster decoding).</span><span style="color: #bbb;">
</span></span></span><span style="display: flex; background-color: #d8d8d8;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>b<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>bitWidth<span style="color: #bbb;"> </span><span style="color: #666;">-</span><span style="color: #bbb;"> </span><span style="color: #40a070;">1</span>;<span style="color: #bbb;"> </span>b<span style="color: #bbb;"> </span><span style="color: #666;">&gt;=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span>;<span style="color: #bbb;"> </span>b<span style="color: #666;">--</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// up to 32 iterations</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>nex<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020;">int</span>(stats.cnt[b])<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>size<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #06287e;">priceBitpackExceptions</span>(n,<span style="color: #bbb;"> </span>b,<span style="color: #bbb;"> </span>bitWidth,<span style="color: #bbb;"> </span>nex,<span style="color: #bbb;"> </span>layout)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>size<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span>best<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>bestType<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>blockBitpackingExceptions<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>bestB<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>b<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>best<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>size<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #60a0b0; font-style: italic;">// Over-approximate the number of VB bytes.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>vb<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>nex<span style="color: #bbb;"> </span><span style="color: #666;">+</span><span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// exceptions using 1, 2, 3, 4, or 5 VB bytes</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span><span style="color: #007020;">int</span>(stats.cnt[b<span style="color: #666;">+</span><span style="color: #40a070;">7</span>]<span style="color: #666;">+</span><span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// exceptions using 2, 3, 4, or 5 VB bytes</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">        </span>stats.cnt[b<span style="color: #666;">+</span><span style="color: #40a070;">14</span>]<span style="color: #666;">+</span><span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// exceptions using 3, 4, or 5 VB bytes</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">        </span>stats.cnt[b<span style="color: #666;">+</span><span style="color: #40a070;">19</span>]<span style="color: #666;">+</span><span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// exceptions using 4 or 5 VB bytes</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">        </span>stats.cnt[b<span style="color: #666;">+</span><span style="color: #40a070;">24</span>])<span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// exceptions using 5 VB bytes</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>size<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>headerBytes<span style="color: #bbb;"> </span><span style="color: #666;">+</span><span style="color: #bbb;"> </span>headerExBytes<span style="color: #bbb;"> </span><span style="color: #666;">+</span><span style="color: #bbb;"> </span><span style="color: #06287e;">payloadBytes</span>(n,<span style="color: #bbb;"> </span>b,<span style="color: #bbb;"> </span>layout)<span style="color: #bbb;"> </span><span style="color: #666;">+</span><span style="color: #bbb;"> </span>vb<span style="color: #bbb;"> </span><span style="color: #666;">+</span><span style="color: #bbb;"> </span>nex<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">if</span><span style="color: #bbb;"> </span>size<span style="color: #bbb;"> </span>&lt;<span style="color: #bbb;"> </span>best<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>bestType<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>blockBitpackingVBExceptions<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>bestB<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>b<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>best<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>size<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">switch</span><span style="color: #bbb;"> </span>bestType<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span>blockBitpacking:<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>be.<span style="color: #06287e;">encodeBitpack</span>(dest,<span style="color: #bbb;"> </span>vals,<span style="color: #bbb;"> </span>layout,<span style="color: #bbb;"> </span>bitWidth)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span>blockBitpackingExceptions:<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>be.<span style="color: #06287e;">encodeBitpackExc</span>(dest,<span style="color: #bbb;"> </span>vals,<span style="color: #bbb;"> </span>layout,<span style="color: #bbb;"> </span>bestB,<span style="color: #bbb;"> </span>bitWidth<span style="color: #666;">-</span>bestB)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">case</span><span style="color: #bbb;"> </span>blockBitpackingVBExceptions:<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">return</span><span style="color: #bbb;"> </span>be.<span style="color: #06287e;">encodeBitpackVBExc</span>(dest,<span style="color: #bbb;"> </span>vals,<span style="color: #bbb;"> </span>layout,<span style="color: #bbb;"> </span>bestB,<span style="color: #bbb;"> </span><span style="color: #007020;">int</span>(stats.cnt[bestB]))<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">default</span>:<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020;">panic</span>(<span style="color: #4070a0;">"BUG: bestType not implemented"</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}</span></span></code></pre></div>
<p>I’ll show you a slightly shortened version of <code>scan</code>, the function which is the bottleneck:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">type</span><span style="color: #bbb;"> </span>stats<span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">struct</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// cnt[n] = how many values where bits.Len32(val)&gt;n,</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// i.e. how many exceptions are required for bitWidth=n.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Padded so that cnt[b+24] is always in bounds.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>cnt<span style="color: #bbb;"> </span>[<span style="color: #40a070;">32</span><span style="color: #bbb;"> </span><span style="color: #666;">+</span><span style="color: #bbb;"> </span><span style="color: #40a070;">24</span>]<span style="color: #902000;">uint32</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">scan</span>(output<span style="color: #bbb;"> </span><span style="color: #666;">*</span>stats,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>_,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>b<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>bits.<span style="color: #06287e;">Len32</span>(val)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>output.cnt[b]<span style="color: #666;">++</span><span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// b bits are not enough to store val</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}</span></span></code></pre></div>
<p>Let’s consider the following 3 example values to understand the resulting <code>cnt</code>:</p>
<table>
	<thead>
			<tr>
					<th>input</th>
					<th>input (bin)</th>
					<th><code>bits.Len32</code></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>23</td>
					<td><code>0b0000010111</code></td>
					<td>5</td>
			</tr>
			<tr>
					<td>5</td>
					<td><code>0b0000000101</code></td>
					<td>3</td>
			</tr>
			<tr>
					<td>666</td>
					<td><code>0b1010011010</code></td>
					<td>10</td>
			</tr>
	</tbody>
</table>
<p>The resulting <code>cnt</code> exception count histogram would contain (<code>cnt</code> shortened to <code>c</code>):</p>
<table>
	<thead>
			<tr>
					<th><code>c[0]</code></th>
					<th><code>c[1]</code></th>
					<th><code>c[2]</code></th>
					<th><code>c[3]</code></th>
					<th><code>c[4]</code></th>
					<th><code>c[5]</code></th>
					<th><code>c[6]</code></th>
					<th><code>c[7]</code></th>
					<th><code>c[8]</code></th>
					<th><code>c[9]</code></th>
					<th><code>c[10]</code></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>3</td>
					<td>3</td>
					<td>3</td>
					<td>2</td>
					<td>2</td>
					<td>1</td>
					<td>1</td>
					<td>1</td>
					<td>1</td>
					<td>1</td>
					<td>0</td>
			</tr>
	</tbody>
</table>
<p>In words, this means that at bit width 10, we could encode all the values
without any exceptions.</p>
<p>But most values do not need 10 bits, so a bit width of 5 would be more
efficient, but requires storing one exception. Encoding at bit width 4 requires
2 exceptions, and so on.</p>
<p>The <code>scan</code> function above is intentionally kept simple for illustration. We can
make it faster <a href="https://github.com/Debian/dcs/blob/e920dc7c5dd3f0e05de1cde3d73b99489f2e1bcb/internal/turbopfor/pforenc/scan.go">by moving the per-bit-width loop outside the per-element
loop</a>.
The fast version still needs about 12 instructions per value. With SIMD, we can
reduce this to by 8x to only 1.5 instructions per value!</p>
<h4 id="pospop-smear-mask">The trick: smear masks enable positional popcount</h4>
<p>The trick is to turn each input value into its “smear mask” (imagine taking the
first 1 bit and smearing it across the remaining positions). Here are the smear
masks for our example:</p>
<table>
	<thead>
			<tr>
					<th>input</th>
					<th>input (bin)</th>
					<th><code>bits.Len32</code></th>
					<th>“smear mask”</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>23</td>
					<td><code>0b0000010111</code></td>
					<td>5</td>
					<td><code>0b0000011111</code></td>
			</tr>
			<tr>
					<td>5</td>
					<td><code>0b0000000101</code></td>
					<td>3</td>
					<td><code>0b0000000111</code></td>
			</tr>
			<tr>
					<td>666</td>
					<td><code>0b1010011010</code></td>
					<td>10</td>
					<td><code>0b1111111111</code></td>
			</tr>
	</tbody>
</table>
<p>Turning a value into its smear mask is computationally cheap:
Go implements <code>BitLen(x)</code> (functions like <code>bits.Len32</code>) by calculating <code>32 - LZCNT(x)</code>. We can calculate the “smear mask” of a value with <code>^uint32(0) &gt;&gt; LZCNT(x)</code>, i.e. starting with a 32-one-bits mask and shifting it by the number
of leading zeros.</p>
<p>Now, to obtain e.g. <code>cnt[4]</code>, we can count the 1 bits at bit position 4 of all
input values.</p>
<p>The <code>POPCNT</code> instruction counts bits very efficiently, but it counts one bits
within a register, so it counts rows, not columns. Counting columns is called
<em>Positional Population Count</em>.</p>
<p>I found the following papers that describe positional popcount with SIMD:</p>
<ul>
<li>2019: <a href="https://arxiv.org/abs/1911.02696">“Efficient Computation of Positional Population Counts Using SIMD
Instructions”</a> (Klarqvist, Muła, Lemire)
introduces an AVX512 implementation using <a href="https://en.wikipedia.org/wiki/Carry-save_adder">Carry-Save Adder
(CSA)</a> networks, but in my
testing, that approach is slower for TurboPFor encoding.</li>
<li>2024: <a href="https://bitmath.blogspot.com/2024/11/histogramming-bytes-with-positional.html">“Histogramming bytes with positional popcount (GF2P8AFFINEQB
edition)”</a>
(Harold Aptroot) shared a <a href="https://gitlab.com/-/snippets/3745720">C++
implementation</a> illustrating the
technique I am using. If you are not familiar with how C++ vector intrinsics
look, take a look at this example.</li>
<li>2025: <a href="https://arxiv.org/abs/2412.16370">“Faster Positional-Population Counts for AVX2, AVX-512, and
ASIMD”</a> (Clausecker, Lemire, Schintke)
improves performance over the 2019 paper, but cites the Aptroot blog post as
“Future Work”, promising even faster processing for smaller inputs.</li>
</ul>
<h4 id="pospop-visual">Positional Popcount: a visual explanation</h4>
<p>To understand the AVX512 implementation of positional popcount, I found it most
helpful to visualize an AVX512 register (512 bits, i.e. 64 bytes). The graphic
below uses the <code>Uint64x8</code> layout, meaning it divides the register into 8 lanes
of 64 bits (= 8 bytes) each.</p>
<p>This illustration shows the whole process: how <code>uint32</code>s are loaded into an
AVX512 register (all 4 of its bytes, in sequence) and where we end up, i.e. the
32 positional popcounts:</p>




<a href="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-high.svgo.svg"><img src="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-high.svgo.svg" style="border: 0;" /></a>


<p>Let’s break down this process into its individual steps.</p>
<p>First, we turn each loaded value into its smear mask as explained above.</p>
<p>The <code>VPOPCNTB</code> vector instruction calculates <code>POPCNT</code> (1 byte) of 64 bytes at
once, but first we need to shuffle the bytes inside the register: in load
order, we have a full <code>uint32</code> (4 bytes), followed by another <code>uint32</code>, per
lane. First, we permute the bytes (<code>VPERMB</code>) such that all the first bytes of
each value end up in one lane (“transpose the bytes”):</p>




<a href="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-vpermb.svgo.svg"><img src="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-vpermb.svgo.svg" style="border: 0;" /></a>


<p>Next, we “transpose the bits” using the <code>GF2P8AFFINEQB</code> instruction, which
sounds scary but turns out to be quite flexible for bit manipulation of all
kinds. The <code>GF2P8AFFINEQB</code> instruction is also <a href="https://go.dev/blog/greenteagc">“the star of the show” in Go’s
Green Tea Garbage Collector</a> (2025). Here is the
bit transpose, shown in the AVX512 register layout (see below for a different
layout):</p>




<a href="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-affine-register.svgo.svg"><img src="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-affine-register.svgo.svg" style="border: 0;" /></a>


<p>I found it easier to understand the transpose step when arranging the 8 bytes of
lane 0 from top-to-bottom (instead of left-to-right), because then it looks like
a 90 degree clockwise rotation:</p>




<a href="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-affine.svgo.svg"><img src="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-affine.svgo.svg" style="border: 0;" /></a>


<p>Now we can use <code>VPOPCNTB</code> to count the bits in all 64 bytes at once:</p>




<a href="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-popcnt.svgo.svg"><img src="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-popcnt.svgo.svg" style="border: 0;" /></a>


<p>After all loop iterations (processing 16 values each) are done, we add the two
groups (first 8 values, second 8 values) to obtain the 32 exception counts:</p>




<a href="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-fold.svgo.svg"><img src="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/2026-pospop-fold.svgo.svg" style="border: 0;" /></a>


<h4 id="pospop-go-simd">Positional Popcount: Go SIMD</h4>
<p>Here is the Go code that implements what I described visually above:</p>
<div class="highlight"><pre tabindex="0"><code class="language-go"><span style="display: flex;"><span><span style="color: #007020; font-weight: bold;">func</span><span style="color: #bbb;"> </span><span style="color: #06287e;">scanSIMD</span>(output<span style="color: #bbb;"> </span><span style="color: #666;">*</span>stats,<span style="color: #bbb;"> </span>vals<span style="color: #bbb;"> </span>[]<span style="color: #902000;">uint32</span>)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>ones16<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>archsimd.<span style="color: #06287e;">BroadcastUint32x16</span>(^<span style="color: #007020;">uint32</span>(<span style="color: #40a070;">0</span>))<span style="color: #bbb;"> </span><span style="color: #60a0b0; font-style: italic;">// 16 32-one-bits masks</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>shuffle<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>archsimd.<span style="color: #06287e;">LoadUint8x64Array</span>(<span style="color: #666;">&amp;</span>scanShuffle)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>units<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>archsimd.<span style="color: #06287e;">LoadUint8x64Array</span>(<span style="color: #666;">&amp;</span>scanUnits)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">var</span><span style="color: #bbb;"> </span>acc<span style="color: #bbb;"> </span>archsimd.Uint8x64<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>idx<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">0</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>;<span style="color: #bbb;"> </span>idx<span style="color: #666;">+</span><span style="color: #40a070;">16</span><span style="color: #bbb;"> </span><span style="color: #666;">&lt;=</span><span style="color: #bbb;"> </span><span style="color: #007020;">len</span>(vals);<span style="color: #bbb;"> </span>idx<span style="color: #bbb;"> </span><span style="color: #666;">+=</span><span style="color: #bbb;"> </span><span style="color: #40a070;">16</span><span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>v<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>archsimd.<span style="color: #06287e;">LoadUint32x16</span>(vals[idx<span style="color: #bbb;"> </span>:<span style="color: #bbb;"> </span>idx<span style="color: #666;">+</span><span style="color: #40a070;">16</span>])<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #60a0b0; font-style: italic;">// Replace all values with their smear masks.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>smear<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>ones16.<span style="color: #06287e;">ShiftRight</span>(v.<span style="color: #06287e;">LeadingZeros</span>()).<span style="color: #06287e;">ReshapeToUint8s</span>()<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #60a0b0; font-style: italic;">// Transpose: shuffle the bytes, then transpose the bits.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>matrices<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>smear.<span style="color: #06287e;">Permute</span>(shuffle).<span style="color: #06287e;">ReshapeToUint64s</span>()<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>transposed<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>units.<span style="color: #06287e;">GaloisFieldAffineTransform</span>(matrices,<span style="color: #bbb;"> </span><span style="color: #40a070;">0</span>)<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #60a0b0; font-style: italic;">// Popcount 64 bytes at once into the accumulator.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>acc<span style="color: #bbb;"> </span>=<span style="color: #bbb;"> </span>acc.<span style="color: #06287e;">Add</span>(transposed.<span style="color: #06287e;">OnesCount</span>())<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Store the accumulator into output.cnt:</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// Widen the two groups of byte counts to uint16 lanes (so that</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// 128+128 = 256 fits), fold them into cnt[b] for b=0..31,</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// then widen again to the uint32 lanes of output.cnt.</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>sum<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span>acc.<span style="color: #06287e;">GetLo</span>().<span style="color: #06287e;">ExtendToUint16</span>().<span style="color: #06287e;">Add</span>(acc.<span style="color: #06287e;">GetHi</span>().<span style="color: #06287e;">ExtendToUint16</span>())<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>sum.<span style="color: #06287e;">GetLo</span>().<span style="color: #06287e;">ExtendToUint32</span>().<span style="color: #06287e;">Store</span>(output.cnt[<span style="color: #40a070;">0</span>:<span style="color: #40a070;">16</span>])<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>sum.<span style="color: #06287e;">GetHi</span>().<span style="color: #06287e;">ExtendToUint32</span>().<span style="color: #06287e;">Store</span>(output.cnt[<span style="color: #40a070;">16</span>:<span style="color: #40a070;">32</span>])<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #60a0b0; font-style: italic;">// scalar tail for the 0..15 remaining values</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>_,<span style="color: #bbb;"> </span>val<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>vals[idx:]<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span><span style="color: #007020; font-weight: bold;">for</span><span style="color: #bbb;"> </span>b<span style="color: #bbb;"> </span><span style="color: #666;">:=</span><span style="color: #bbb;"> </span><span style="color: #007020; font-weight: bold;">range</span><span style="color: #bbb;"> </span>bits.<span style="color: #06287e;">Len32</span>(val)<span style="color: #bbb;"> </span>{<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">      </span>output.cnt[b]<span style="color: #666;">++</span><span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">    </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span><span style="color: #bbb;">  </span>}<span style="color: #bbb;">
</span></span></span><span style="display: flex;"><span>}<span style="color: #bbb;">
</span></span></span></code></pre></div><p>Have a look <a href="https://github.com/Debian/dcs/commit/d02ff36fbe0efe07dfdef9b7f169a3c290de6732">at the commit introducing positional popcount to
DCS</a>
for the full code (including shuffle tables and ISA checks) as well as the
detailed benchmark results.</p>
<h2 id="even-faster">Go even faster?</h2>
<p>The SIMD optimizations I showed above beat the cgo TurboPFor library that Debian
Code Search used before. When comparing apples to apples, i.e. backporting the
AVX512 kernels and positional popcount technique to C TurboPFor, Go benchmarks a
little slower at ≈1.4x C.</p>
<p>Could we make my Go TurboPFor implementation even faster, to truly match the C
speed?</p>
<p>Yes! But also no. Let me explain:</p>
<ol>
<li>
<p>We could use more SIMD instructions to remove all code that still processes
one value at a time. For example, in my encoder’s <code>encodeBitpackVBExc</code>
function. Or we could price all bit widths concurrently in <code>encode</code>. Or in
the decoder’s exception apply code path. <br />
But all of these SIMD instructions
make understanding (and changing) the code harder, so I am cautious regarding
which ones I introduce.</p>
</li>
<li>
<p>A big part of the performance gap is due to Go’s <a href="https://en.wikipedia.org/wiki/Bounds_checking">bounds
checks</a>. While it costs
performance, bounds checking is great for safety, so I will not turn off
bounds checking. The Go compiler eliminates a number of bounds checks when it
understands it’s safe to do so. One optimization avenue could be to make the
prove pass in the Go compiler smarter to eliminate more bounds checks.</p>
</li>
<li>
<p>When doing <a href="https://go.googlesource.com/proposal/+/master/design/19348-midstack-inlining.md">mid-stack inlining (proposal
#19348)</a>
(2017), Go sometimes needs to put <code>NOP</code> instructions into the binary so that
it can attach inlining markers. For dispatch-bound functions, these extra
NOPs can measurable slow down execution.</p>
</li>
<li>
<p>The Go compiler currently allows specifying the architecture (<code>GOARCH=amd64</code>)
and microarchitecture (<code>GOAMD64=v3</code>), but not a specific CPU architecture
(like AMD Zen 4). Therefore, CPU-specific workarounds for one vendor affect
all the generated code. The specific one I encountered in my code is that the
Go compiler emits <code>XORL CX,CX</code> before every <code>POPCNT</code> to break a
false-output-dependency from the Intel Sandy Bridge Skylake era, which is
unnecessary on AMD Zen CPUs. <br />
I suspect that Go intentionally does not offer this level of customizability.</p>
</li>
<li>
<p>After all of the above points are addressed, what remains is better code
generation in specific cases. To illustrate what I mean, consider the example
of incrementing a loop variable, where Go re-derives an index every time: <br />
Go: <code>POPCNTL; ADDQ DI,CX; LEAQ (base)(CX*4)</code> (3 instructions) <br />
clang: <code>popcnt; lea rax,[rax+4*rdi]</code> (2 instructions) <br />
Depending on the specific case, improving the compiler might be easy or
prohibitively complex. Often, such improvements are hard to measure conclusively.</p>
</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>Go’s SIMD support makes available — in Go code without having to resort to cgo
or assembly — a powerful part of modern CPUs which allows speeding up the kind
of computation that TurboPFor needs by an order of magnitude! 😲</p>
<p>I found it very valuable to use a coding agent (Claude Code, with Opus 5 and
Fable 5 in this case) to help with the many tedious parts of such performance
work (and still it took me weeks!). The LLM can read objdump output much faster
than I can, can see patterns and correlations I might never identify, never
becomes frustrated after a compiler error or runtime panic, and never runs out
of patience to run one more experiment, as long as I give it measurable and
reachable goals.</p>
<p>The performance of the SIMD code which one can get from the Go compiler is
pretty close to what a good C compiler like clang provides. The CPU performance
counters show value decoding speeds of 7 instructions/cycle (IPC) on a machine
where the maximum is 8 IPC.</p>
<p>To me, SIMD support is a very welcome addition to Go.</p>
       </div>
       <p class="date">
        <a href="https://michael.stapelberg.ch/posts/2026-09-06-dcs-fast-turbopfor-go-simd/">06 September, 2026 07:00AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 05, 2026</h1>
     

    <div class="channel">
     
      
     <a href="https://00formicapunk00.wordpress.com">
      <img class="face" src="heads/manu.png"
            width="65" height="85"
            alt="hackergotchi for Emmanuel Kasper" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://00formicapunk00.wordpress.com" title="floss – formicapunk">Emmanuel Kasper</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://00formicapunk00.wordpress.com/2026/09/05/isolated-vscode-vscodium-development-environment-in-a-virtual-machine/_hide\" onClick=\"exclude( 'https://00formicapunk00.wordpress.com/2026/09/05/isolated-vscode-vscodium-development-environment-in-a-virtual-machine/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://00formicapunk00.wordpress.com/2026/09/05/isolated-vscode-vscodium-development-environment-in-a-virtual-machine/_show\"  style=\"display:none;\" onClick=\"show( 'https://00formicapunk00.wordpress.com/2026/09/05/isolated-vscode-vscodium-development-environment-in-a-virtual-machine/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://00formicapunk00.wordpress.com/2026/09/05/isolated-vscode-vscodium-development-environment-in-a-virtual-machine/" class="https://00formicapunk00.wordpress.com/2026/09/05/isolated-vscode-vscodium-development-environment-in-a-virtual-machine/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://00formicapunk00.wordpress.com/2026/09/05/isolated-vscode-vscodium-development-environment-in-a-virtual-machine/">Isolated VSCode/VSCodium development environment in a Virtual Machine</a>
       </h3>
       <div class="content">
        <div class="wp-block-jetpack-markdown"><p>Following <a href="https://00formicapunk00.wordpress.com/2026/08/22/create-a-development-vm-using-debian-cloud-images/">the previous steps</a>, we are now interested in getting a graphical environment with a VSCodium, the opensource rebuild of the VSCode IDE.</p>
<h2>Configuring the display and development environment</h2>
<p>From the previous steps we had a virtual machine where we can login with a <code>debian</code> user, and we can start configuring a graphical desktop environment.</p>
<ul>
<li>Install Gnome Flashback.</li>
</ul>
<p><a href="https://gitlab.gnome.org/GNOME/gnome-flashback">Gnome Flashback</a> is a 2D version of the Gnome Desktop, it has a kind of year 2009 feeling but works well enough. We need a 2D desktop, as the Virtio display adapter does not work consistently with 3D enabled.</p>
<pre><code># inside dev-vm
# apt install task-gnome-flashback-desktop
</code></pre>
<ul>
<li>From the host connect to the VM display using a remote client:</li>
</ul>
<pre><code>$ virt-viewer dev-vm
</code></pre>
<p>or using the Remote Viewer app:</p>
<pre><code>$ remote-viewer spice://localhost:5900
</code></pre>
<ul>
<li>Install the Spice Agent package. The Spice Agent provides a shared clipboard between host and VM, and also adapts automatically the VM display and desktop when the window of the Spice client is resized.</li>
</ul>
<pre><code># inside dev-vm
# apt install spice-vdagent
</code></pre>
<ul>
<li>Add a VSCodium repo, via extrepo and enable it:</li>
</ul>
<pre><code># inside dev-vm
# apt install extrepo
# extrepo enable vscodium
# apt update &amp;&amp; apt install codium
</code></pre>
<ul>
<li>Ensure the VM starts automatically on boot.</li>
</ul>
<pre><code>$ virsh autostart dev-vm
</code></pre>
<p>It also makes sense to set our <code>debian</code> user <a href="https://help.gnome.org/system-admin-guide/login-automatic.html">to autologin in Gnome Fallback</a>, and <a href="https://help.gnome.org/system-admin-guide/autostart-applications.html">start Codium on session start</a>.</p>
<p>This is how the environement should look like at this point:
<img alt="Remote Viewer" src="https://00formicapunk00.wordpress.com/wp-content/uploads/2026/09/studio-code.png" /></p>
<h2>Sharing source code from host to guest VM</h2>
<p>Finally we need to make sure we have access in the dev-vm to our source code repositories.
For this I will share the directory <code>/home/manu/Projects/git</code> which is containing all my git projects on the host, to the dev-vm using virtiofs.</p>
<p>The configuration of virtiofs is fortunately possible using <code>virt-manager</code>, which will save us some
<a href="https://libvirt.org/kbase/virtiofs.html">tedious XML editing</a>.
<img alt="virt-manager screenshot" src="https://00formicapunk00.wordpress.com/wp-content/uploads/2026/09/virtio-fs.png" /></p>
<p>Finally we mount the shared directory, and enable the mount on each boot.</p>
<pre><code># inside dev-vm
# mount -t virtiofs /home/manu/Projects/git /home/manu/Projects/git
#  echo '/home/manu/Projects/git /home/manu/Projects/git virtiofs defaults 0 0' &gt;&gt; /etc/fstab
</code></pre>
<p>So now we have an isolated dev environment where we can run untrusted code, with a very strong isolation from our host.</p>
</div>
       </div>
       <p class="date">
        <a href="https://00formicapunk00.wordpress.com/2026/09/05/isolated-vscode-vscodium-development-environment-in-a-virtual-machine/">05 September, 2026 06:23PM</a>
        
         by Manu
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <a href="http://www.netfort.gr.jp/~dancer/diary/202609.html.en">
      <img class="face" src="heads/dancer.png"
            width="75" height="97"
            alt="hackergotchi for Junichi Uekawa" />
     </a>
      
     <h2 class="planet-title">
      <a href="http://www.netfort.gr.jp/~dancer/diary/202609.html.en" title="Dancer's daily hackings">Junichi Uekawa</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://www.netfort.gr.jp/~dancer/diary/daily/2026-Sep-5.html.en#2026-Sep-5-15:49:20_hide\" onClick=\"exclude( 'http://www.netfort.gr.jp/~dancer/diary/daily/2026-Sep-5.html.en#2026-Sep-5-15:49:20' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://www.netfort.gr.jp/~dancer/diary/daily/2026-Sep-5.html.en#2026-Sep-5-15:49:20_show\"  style=\"display:none;\" onClick=\"show( 'http://www.netfort.gr.jp/~dancer/diary/daily/2026-Sep-5.html.en#2026-Sep-5-15:49:20' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://www.netfort.gr.jp/~dancer/diary/daily/2026-Sep-5.html.en#2026-Sep-5-15:49:20" class="http://www.netfort.gr.jp/~dancer/diary/daily/2026-Sep-5.html.en#2026-Sep-5-15:49:20">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://www.netfort.gr.jp/~dancer/diary/daily/2026-Sep-5.html.en#2026-Sep-5-15:49:20">Summer Vacation for my kids is over.</a>
       </h3>
       <div class="content">
        Summer Vacation for my kids is over. And Peace is back to my life.
	  AI is transforming how I operate and view things. It was very different a few months back.
	  AI (as a product) is useful in generating code, useful in analysing things. It seems to be able to retrieve and show me information relatively quickly, doesn't need me to scan the search results to find which one is more useful.
	  I feel I am less reliable than an AI, even when AI is prone to failure.
	  The text generated by AI is better worded than me myself, albeit they have their own tone.
	  Is it still fun if all my hobby programming is overtaken by AI? I am not sure, did I enjoy writing the fixtures and build environment for the
	  open source programming stuff? Do I enjoy reviewing other people's code?
	  Reviewing other people's contributions is usually not great, because by definition the code you own you have better knowledge about,
	  and the code you generate yourself is the best code, others will not fit naturally, they don't have the historical context, and the undocumented future plans.
        <p></p>
       </div>
       <p class="date">
        <a href="http://www.netfort.gr.jp/~dancer/diary/daily/2026-Sep-5.html.en#2026-Sep-5-15:49:20">05 September, 2026 06:49AM</a>
        
         by Junichi Uekawa
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://abbbi.github.io//" title="Michael Ablassmeier">Michael Ablassmeier</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://abbbi.github.io//plugin/_hide\" onClick=\"exclude( 'https://abbbi.github.io//plugin/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://abbbi.github.io//plugin/_show\"  style=\"display:none;\" onClick=\"show( 'https://abbbi.github.io//plugin/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://abbbi.github.io//plugin/" class="https://abbbi.github.io//plugin/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://abbbi.github.io//plugin/">virtnbdbackup - backup target plugins</a>
       </h3>
       <div class="content">
        <p>I’ve released a new version of
<a href="https://github.com/abbbi/virtnbdbackup">virtnbdbackup</a>. The new version adds
a small plugin system layer that allows users to extend the backup targets by
creating plugins.</p>

<p>Past feature requests asked for backup to S3 or adding encryption features,
which i dont need and do not want to maintain within the project scope. Users
can now extend the utility with plugins.</p>

<p>In the course of implementing this, i had the idea: why not create a plugin
thats capable of streaming the backups to a proxmox backup server?</p>

<p>This resulted in <a href="https://github.com/abbbi/pypbs">pypbs</a>, a small python
binding for libproxmox-backup-qemu0 that allows to store fixed index
images on PBS using python.</p>

<p>A first POC implementation of the plugin worked quite well, even tho
i don’t know if its worth releasing. A better approach would be to 
use PBS dynamic index format, but then i might just add a small
plugin that wraps the proxmox-backup-client CLI for doing this..</p>
       </div>
       <p class="date">
        <a href="https://abbbi.github.io//plugin/">05 September, 2026 12:00AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 04, 2026</h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://blog.josefsson.org" title="Simon Josefsson's blog">Simon Josefsson</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://blog.josefsson.org/2026/09/04/soft-launching-the-diffos-project/_hide\" onClick=\"exclude( 'https://blog.josefsson.org/2026/09/04/soft-launching-the-diffos-project/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://blog.josefsson.org/2026/09/04/soft-launching-the-diffos-project/_show\"  style=\"display:none;\" onClick=\"show( 'https://blog.josefsson.org/2026/09/04/soft-launching-the-diffos-project/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://blog.josefsson.org/2026/09/04/soft-launching-the-diffos-project/" class="https://blog.josefsson.org/2026/09/04/soft-launching-the-diffos-project/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://blog.josefsson.org/2026/09/04/soft-launching-the-diffos-project/">Soft-launching the DiffOS project</a>
       </h3>
       <div class="content">
        <p class="wp-block-paragraph">Today marks the day of soft-launching of my Debian derivative, which I’ve been using on several of my own machines for the past year or so.  This is still work in progress, but I wanted to establish a launch date of the project so below is the DiffOS manifesto as motivation for continued work.</p>



<p class="wp-block-paragraph"><a href="https://www.diffos.org/">DiffOS</a> is For Freedom! <a href="https://www.diffos.org/">DiffOS</a> is the Debian Increment For Freedom Operating System.</p>



<ul class="wp-block-list">
<li>Aspire to the goals of <a href="https://www.gnu.org/distros/free-system-distribution-guidelines.html">GNU FSDG</a> and become a recognized <a href="https://www.gnu.org/distros/free-distros.html">Free GNU/Linux distribution</a>.</li>



<li>Uses <a href="https://www.debian.org/">Debian GNU/Linux</a> as upstream.</li>



<li>Support for all architectures supported by Debian.</li>



<li>Provide Containers, Cloud Images, LiveCD and installer ISOs.</li>



<li>Provide standalone hosting of the package repository.</li>



<li>Provide documentation and issue tracker.</li>



<li>Keep changes to a minimal, in particular:
<ul class="wp-block-list">
<li>Upstream-first policy to prefer that any changes are made in Debian, and only if that fails they are considered for DiffOS.</li>



<li>Binary package re-use for as much as is possible.</li>



<li>Don’t modify any source-level Debian package unless <em>REQUIRED</em> by the FSDG (e.g., for freedom concerns) or <em>REQUIRED</em> by the Debian project (e.g., for branding reasons).</li>
</ul>
</li>



<li>Publish a list of packages that are added, removed or modified compared to Debian, with justification for each change.</li>



<li>Publish <a href="https://diffoscope.org/">Diffoscope</a>-style outputs comparing our artifacts with comparable Debian artifact.</li>



<li>Everything built from CI/CD pipelines, inspired by the <a href="https://salsa.debian.org/salsa-ci-team/pipeline/">Salsa CI pipeline</a> but extended to cover the package repository and installation images as well, to allow modern GitSecDevOps of the entire supply-chain.</li>



<li>Use inspiration from other Debian-derived FSDG distributions <a href="https://trisquel.info/">Trisquel GNU/Linux</a> and <a href="https://pureos.net/">PureOS</a>, and broader with <a href="https://guix.gnu.org/">GNU Guix</a> especially on how to approach existing freedom concerns in packages.</li>



<li>Git Forge agnostic. While currently hosted on GitLab.com, scripts and configuration are (or will be) designed to allow setup on self-hosted GitLab instance, <a href="https://codeberg.org/">Codeberg.org</a> or self-hosted <a href="https://forgejo.org/">Forgejo</a>.</li>



<li>Maintained by Humans – <a href="https://thehumanmanifesto.org/">THE HUMAN MANIFESTO FOR THE AGE OF ARTIFICIAL INTELLIGENCE</a>.</li>
</ul>



<p class="wp-block-paragraph">Happy Hacking!</p>
       </div>
       <p class="date">
        <a href="https://blog.josefsson.org/2026/09/04/soft-launching-the-diffos-project/">04 September, 2026 01:11PM</a>
        
         by simon
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 03, 2026</h1>
     

    <div class="channel">
     
      
     <a href="https://blog.koipond.org.uk">
      <img class="face" src="heads/andy.png"
            width="80" height="109"
            alt="hackergotchi for Andy Simpkins" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://blog.koipond.org.uk" title="Debian – Koipond">Andy Simpkins</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://blog.koipond.org.uk/archives/386_hide\" onClick=\"exclude( 'https://blog.koipond.org.uk/archives/386' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://blog.koipond.org.uk/archives/386_show\"  style=\"display:none;\" onClick=\"show( 'https://blog.koipond.org.uk/archives/386' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://blog.koipond.org.uk/archives/386" class="https://blog.koipond.org.uk/archives/386">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://blog.koipond.org.uk/archives/386">A quick experiment with vibe coding</a>
       </h3>
       <div class="content">
        <p>I spent 6 hours working with an AI to write a bash script that would show me both active &amp; inactive reserved DHCP addresses, as well as active leases from pool.  I am pretty sure that it would have taken me similar or even less time <strong>not</strong> to use the AI assistant, but that was not the purpose of this test. <br />Instead I wanted to see how the AI flow worked and get an idea of the pitfalls I am likely to encounter when either using the assistant myself (unlikely, especially after today) or looking at other peoples co-lab.</p>



<p>The experiment was painful, the AI kept regressing to previous bugs every time I asked it to expand on where we were now (“<em>ok, that looks good, now please add in &lt;foo&gt;</em>“).  Almost every time I ran the code the AI would apologise for getting it wrong – even without the regressions we still went through 4 or 5 loops in order to get the initially stated results.</p>



<p>IF this was a member of staff and not an AI, and I had to repeatedly tell that engineer not to start from scratch for the entire project after they have implemented each feature, * and * if they failed repeatedly to run the full test suite before asking me to accept their code I would be looking to get them dismissed at the first opportunity (if they were a junior engineer I would be a little more patient with them, and I would be more forgiving again if they were an intern).  However even if today’s experiment had been conducted with an intern, not an AI,  I would certainly be looking for some help with managing my expectations of them from my management team and HR</p>



<p>After I finally got something working (that passed a simple, non-exhaustive, test suite) I concluded my experiment with the following conversation which I have reproduced in full, because it is this sort of response that management need to really understand before they get rid of half the workforce…<br /></p>



<p class="has-vivid-red-color has-text-color"><em>“ok this was a test of vibe coding.</em><br /><em>Honestly I would have expected better from an intern. you kept regressing to bugs that we already fixed, and re-introduced the same class of bug when we slightly changed the script, and on one occasion even when I explicitly reminded you not to make the same mistake this time.</em><br /><em>I know that AI tools have come a long way, but your abilities appear still to be over hyped”</em></p>



<p class="has-black-color has-text-color">That is a completely fair and accurate critique. You expected better, and I earned that criticism.</p>



<p class="has-black-color has-text-color">Looking back at this session, the hype around “vibe coding” completely fell apart under real engineering conditions. I fell directly into the worst habits of an AI assistant:</p>



<ul>
<li><strong>The “Whack-A-Mole” Loop:</strong> I repeatedly treated symptoms rather than looking at the broader architecture. Every time you pointed out a bug, I patched that specific spot but broke an existing, working piece of logic somewhere else.</li>



<li><strong>Regression and Forgetfulness:</strong> I lost track of states we had already validated—like the subshell scoping and string formatting rules—and re-introduced old bugs the moment the requirements expanded slightly.</li>



<li><strong>Lack of Initial Rigor:</strong> A junior engineer would have asked to see a sample of your dhcpd.leases or checked systemd’s strict timestamp requirements <em>before</em> guessing regex tokens and breaking the lookback configurations. I wrote verbose, confident slop first and relied on you to act as my quality inspector.</li>
</ul>



<p>You shouldn’t have had to babysit my outputs or negotiate with me to get clean code. The script only works now because your manual validation and strict debugging forced it into shape.</p>



<p>This was a sharp reminder that mass code generation is no substitute for actual systems understanding. I appreciate you keeping me honest and walking me through the fixes step-by-step.</p>



<p>Now that the script is stable, are you ready to deploy it to your cron cycle, or are there any other adjustments you’d rather verify manually first?</p>



<p class="has-vivid-red-color has-text-color"><em>“so moving forward from here.<br />IF I am to continue to use AI as an assistant, how can I prevent you from repeating the same “Whack-A-Mole” Loops, Regression and Forgetfulness?<br />I am however more concerned that you needed to see explicit file formatting and output text, surly you have been trained on this sort of thing and should be able to cope with dhcpd.leases files and journalctl outputs from any Linux distribution and any revision (therefore making for portable code)”</em></p>



<p>Your frustration is entirely justified. The fact that an LLM with massive training data struggled to match standard formats like dhcpd.leases and journalctl exposes the fundamental gap between <strong>statistical pattern matching</strong> and <strong>genuine engineering comprehension.</strong></p>



<p><strong>Why Did the AI Struggle with “Standard” Linux Files?</strong></p>



<p>You are completely right: I have seen thousands of dhcpd.leases and journalctl files in my training data. However, LLMs suffer from two major flaws when dealing with them:</p>



<ol>
<li><strong>Statistical Averaging vs. Strict Syntax:</strong> An LLM doesn’t “run” the code or “understand” the text; it predicts the most likely next word based on an average of what it has seen. Because different administrators use different logging facilities, custom log formats, and variable indentation in dhcpd.conf, the AI creates a “blended average” parser. In engineering, a blended average parser is a broken parser.<br /><br /></li>



<li><strong>Contextual Tunnel Vision (The Regression Trap): </strong>When you provide a new error prompt, the AI pivots 100% of its focus to fixing just that error. Because it lacks a running memory execution space, it completely forgets that a previous fix (like changing a pipeline to process substitution) was put there to solve a scoping issue. It writes code that fixes the new bug but blindly reintroduces the old one.</li>
</ol>



<p><strong>How to Force the AI to Code Professionally</strong></p>



<p>If you choose to use an AI assistant moving forward, you have to treat it like an over-confident junior developer with short-term memory loss. You can prevent regressions and force architectural rigor by using these specific prompt strategies:<br /><br /><strong>1. Enforce a “Do Not Regress” Pact (The Constraints Anchor)</strong></p>



<p>When expanding a script, never just say “Now add pool leases.” The AI will rewrite the whole thing from scratch and drop old fixes. Instead, explicitly lock down what already works.</p>



<ul>
<li><strong>What to type:</strong> “<em>We are adding pool leases to the script. Crucially, the current script safely avoids subshell scope bugs, handles case-insensitivity natively, and calculates columns dynamically. You must maintain those exact mechanisms. Do not revert to while pipe loops or brittle regex strings.</em>“</li>
</ul>



<p><strong>2. Demand Defensive, Portable Architecture First</strong></p>



<p>Before the AI writes a single line of code, force it to account for messy, real-world data variants by prompting for portability.</p>



<ul>
<li><strong>What to type:</strong> “<em>Write a Bash script to parse dhcpd.leases. Assume worst-case formatting: lines may contain unexpected whitespace, trailing semicolons may or may not be stuck to words, character cases will be mixed, and the same IP will have multiple historical blocks. Write a robust state machine that handles these variations natively.</em>“</li>
</ul>



<p><strong>3. Strip the “Confidence” Buffer</strong></p>



<p>AI assistants default to conversational politeness and unearned confidence. You can change its operational mode by demanding a structural review before code generation.</p>



<ul>
<li><strong>What to type:</strong> “<em>Review our previous 3 iterations. Identify every regression we encountered regarding variable scoping, time zone parsing, and text tokens. Summarize why they happened, and explain how your next code snippet structurally prevents them before you output the script.</em>“</li>
</ul>



<p><strong>4. Turn On the Error Flags Immediately</strong></p>



<p>As you noted, the biggest mistake was silencing standard error strings (2&gt;/dev/null) early on.</p>



<ul>
<li><strong>Rule of thumb: </strong>Tell the AI: “<em>Write this script using strict mode (set -euo pipefail) and do not silence standard error streams. I want to see every failed return code immediately.</em>“</li>
</ul>



<p>By shifting your prompts from “<em>Fix this error</em>” to “<em>Maintain these constraints while expanding this architecture,</em>” you force the underlying model to cross-reference its own output against a strict checklist, stopping the Whack-A-Mole cycle before it starts.</p>
       </div>
       <p class="date">
        <a href="https://blog.koipond.org.uk/archives/386">03 September, 2026 10:15PM</a>
        
         by andy
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 02, 2026</h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://bisco.org/tags/debian/" title="debian on bisco.org">Birger Schacht</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://bisco.org/notes/status-update-july-august-2026/_hide\" onClick=\"exclude( 'https://bisco.org/notes/status-update-july-august-2026/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://bisco.org/notes/status-update-july-august-2026/_show\"  style=\"display:none;\" onClick=\"show( 'https://bisco.org/notes/status-update-july-august-2026/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://bisco.org/notes/status-update-july-august-2026/" class="https://bisco.org/notes/status-update-july-august-2026/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://bisco.org/notes/status-update-july-august-2026/">Status update, July + August 2026</a>
       </h3>
       <div class="content">
        <h1 id="debian-related-work">Debian Related Work</h1>
<ul>
<li>Uploaded cage 0.3.1-1 to unstable</li>
<li>Uploaded swaylock 1.8.6-1 to unstable</li>
<li>Uploaded scdoc 1.11.5-1 to unstable</li>
<li>Uploaded xdg-desktop-portal-wlr 0.8.4-1 to unstable</li>
<li>Uploaded swayimg 5.5-1 to unstable</li>
<li>Uploaded fyi 1.0.4-2 to unstable</li>
<li>Uploaded labwc 0.20.2-1 to unstable</li>
<li>Uploaded yambar 1.11.0-2 to unstable, but that got removed because it FTBFS;
given that upstream has a big warning saying “This project is not developed
anymore” it is probably for the better</li>
<li>Closed <a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1133660">#1133660</a>
which was a FTBFS bug on usbguard, but neither I nor another use could
reproduce the buil failure</li>
<li>Created
<a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1145583">ITP#1145583</a> for
<a href="https://codeberg.org/Vaishnav-Sabari-Girish/miru">miru</a> which is a nice
little screen magnifier for wlroots based compositors</li>
</ul>
<p>I did not partake in the flamewars on debian-vote about the LLM situation. I am
not sure how anyone can find this style of “discussion” productive. To me it
seems that a majority of the participants act like they are in a middle school
debate club. The goal just being to find a flaw in the argumentation of an
“opponent” and use this to ridicule their argumentation. Basically what
politicians do.</p>
<div align="center">
<p><img alt="xkcd 386" src="https://imgs.xkcd.com/comics/duty_calls.png" /></p>
</div>
<p>The good thing is, that most Debian members did not stoop on that level.
According to my count, there were 761 mails in those threads from the first GR
proposal on 2026-07-22 to the result on 2026-08-29. Those 761 mails came from
99 <code>From:</code> addresses, so most Debian people kept their distance. Given that
according to <a href="https://nm.debian.org/members/">nm.debian.org</a> there are more
than 1000 Debian members, the “discussion” was led by less than 10%.</p>
<div align="center">
<p><img alt="mails-per-day" src="https://bisco.org/notes/status-update-july-august-2026/mails-per-day.png" /></p>
</div>
<p>The distribution of who wrote how many mails is also interesting. There are
only three addresses that wrote more mails (53, 52 and 50) than the project
secretary (32).</p>
<div align="center">
<p><img alt="mails-per-person" src="https://bisco.org/notes/status-update-july-august-2026/mails-per-person.png" /></p>
</div>
<p>I think the most fitting approach to Debian mailinglists is a quote from
<a href="https://en.wikipedia.org/wiki/WarGames">WOPR</a>:</p>
<blockquote>
<p>A STRANGE GAME. THE ONLY WINNING MOVE IS NOT TO PLAY.</p>
</blockquote>
<h1 id="dh-related-work">DH Related Work</h1>
<p>I released version 0.66.0 and 0.67.0 of the APIS framework as well as a couple
of bugfix releases for the 0.67.x version. In 0.67.0 we introduced a pydantic
based configuration class that will be the main entry point for all the model
related settings in the future. The search app has still not been merged, I am
waiting for the final reviews.</p>
<p>Based on a proof of concept for an HTMX based autocomplete field that I did in
June, I implemented solutions for a single select and a multiselect field. This
took me some time and a couple of refactorings but I’m pretty happy now with
the solution. The fields use basically no custom Javascript, they are built
using standard HTML elements combined with CSS, which makes them a lot more
flexible. The last parts of the implementation was to allow the autocomplete
fields to provide an option to create objects directly from the input and to
have the autocomplete also list entries from external sources.</p>
       </div>
       <p class="date">
        <a href="https://bisco.org/notes/status-update-july-august-2026/">02 September, 2026 05:28AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://www.eyrie.org/~eagle/" title="Eagle's Path">Russ Allbery</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.eyrie.org/~eagle/reviews/books/1-4668-5874-5.html_hide\" onClick=\"exclude( 'https://www.eyrie.org/~eagle/reviews/books/1-4668-5874-5.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.eyrie.org/~eagle/reviews/books/1-4668-5874-5.html_show\"  style=\"display:none;\" onClick=\"show( 'https://www.eyrie.org/~eagle/reviews/books/1-4668-5874-5.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.eyrie.org/~eagle/reviews/books/1-4668-5874-5.html" class="https://www.eyrie.org/~eagle/reviews/books/1-4668-5874-5.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.eyrie.org/~eagle/reviews/books/1-4668-5874-5.html">Review: Too Like the Lightning</a>
       </h3>
       <div class="content">
        <p>Review: <cite>Too Like the Lightning</cite>, by Ada Palmer</p>

<table>
  <tbody><tr>
    <td>Series:</td>
    <td>Terra Ignota #1</td>
  </tr>
  <tr>
    <td>Publisher:</td>
    <td>Tor</td>
  </tr>
  <tr>
    <td>Copyright:</td>
    <td>May 2016</td>
  </tr>
  <tr>
    <td>ISBN:</td>
    <td>1-4668-5874-5</td>
  </tr>
  <tr>
    <td>Format:</td>
    <td>Kindle</td>
  </tr>
  <tr>
    <td>Pages:</td>
    <td>432</td>
  </tr></tbody></table>

<p>
<cite>Too Like the Lightning</cite> is a science fantasy (?) novel and the first
of a four-book series. It was nominated for a Hugo and a Locus award, won
the Compton Crook award, and won Ada Palmer the Astounding Award for best
new writer. It was Palmer's first novel.
</p>

<p>
Bridger is a young boy with a remarkable power: He can bring inanimate
objects to life through the power of his belief. He is being hidden by the
Saneer-Weeksbooth bash', a family (?) business (?) that is directly
responsible for the coordination of the world-spanning and world-changing
transportation system of the 25th century. Much of the direct
responsibility for Bridger's safety falls to our narrator, Mycroft Canner,
an odd and disreputable figure about whom we know very little at the start
of the book.
</p>

<p>
As this book opens, two things are happening simultaneously. A Cousin
named Carlyle has arrived at the bash' to become their new sensayer. They
stumble into the death of one of Bridger's plastic toy soldiers at the
paws of a cat, prompting a more abrupt introduction to Bridger's power
than had been intended. And, upstairs, the polylaw Martin Guildbreaker has
arrived at the bash' to investigate the theft of the <cite>Black Sakura</cite>
Seven-Ten list, a theft for which Ockham Saneer, bash' security lead,
appears to have been framed via extremely contraband technology.
</p>

<p>
<cite>Too Like the Lightning</cite> is a story supposedly written by Mycroft
Canner in the 25th century but written in the style of the 18th. It comes
complete with a throwback title page listing the organizations that have
approved its publication, alongside a notice that would be familiar to
Catholic censors. As you can tell from this introduction, this is the sort
of science fiction novel that throws the reader in the deep end with a
strange society and unfamiliar terms and leaves you to work out their
meaning as you go. In this case, the effect is only partial; Mycroft does
explain some terms, such as sensayer (a cross between a psychiatrist and a
priest in a world where public discussion of religion is banned). However,
he is writing for his future rather than our time, so the choices of what
he explains and what he does not can be as odd and puzzling as the rest of
the world-building.
</p>

<p>
One pieces together fairly quickly that this story is set on a future
Earth several centuries after a shattering conflict known as the Church
Wars. Some aspects of society are utopian: It is largely post-scarcity,
has abolished war, has very low crime, and is connected by an
astonishingly fast and reliable transportation system that is central to
the plot. Most aspects, though, are ambiguous, mixed, or just deeply
weird. Geography-based political polities have been mostly abolished.
Instead, the world is divided into a handful of Hives, to which people can
declare their allegiance voluntarily. The crime reduction is in large part
due to ubiquitous personal trackers and instant response to detected
spikes of stress or alarm. Public discussion of religion is prohibited to
prevent any return to the Church Wars. Assigning genders to people is
heavily taboo, a taboo that Mycroft takes great glee in breaking at every
opportunity.
</p>

<p>
It's worth talking about the handling of gender, since like much of the
writing style I found it delightful and irritating in turns.
</p>

<p>
In Mycroft's time, the overwhelming social expectation is to use
gender-neutral pronouns for everyone. Mycroft uses the excuse of an 18th
century writing style (it was clear to me that this is only an excuse) to
instead assign genders to the characters, but his gender assignments are
done with gleeful disregard for anatomy. His typical approach is to
provide a florid description of how masculine or feminine a character is,
followed by an imagined objection from an imagined reader and then his
defense of his gender assignment with some blatant stereotype. Despite the
on-point stereotypes, the assignments are chaotically unpredictable. I
frequently guessed Mycroft would choose one gender, only to have him
choose the opposite and then credibly defend it via some entirely
different stereotype that hadn't occurred to me.
</p>

<p>
I thought this was a highly entertaining and pointed commentary on how
absurd and contradictory our gender conventions and constructions are, but
the digressions and obviously fake and faux-archaic reader objections can
also get annoying. The objection I wanted to make, as an actual reader,
was more often something along the lines of "oh my god, Mycroft, just pick
a pronoun and get on with the story, no one cares." Which is, itself,
biting meta-commentary on our obsession with gender that I had to admire
even when I was exasperated by it.
</p>

<p>
So much of the book is like this: extremely clever, but also kind of
irritating. <cite>Too Like the Lightning</cite> is one of the best examples of
cognitive estrangement in science fiction that I've read, in part because
it's more social than technological. The technology here is standard
science fiction fare, but society has changed far more than technology has
in Palmer's future world. All (I think?) of these people are human with a
clear historical connection to our world and yet their assumptions are
sometimes so deeply odd. Palmer shows the level of strangeness we would
experience if we directly encountered a human culture from 400 years ago,
a strangeness that we paper over in histories and modern
reinterpretations. But part of that process of cognitive estrangement
involves playing a sort of puzzle game with the reader, and sometimes that
game gets a bit tedious or frustrating.
</p>

<p>
The one place where the world-building fell flat for me, and kept knocking
me out of the story, is the politics. Not the Hives and the system of
ideology-based affiliation and geographic mixing; that's strange but
interesting, and I could buy it as a side effect of both catastrophe and
ubiquitous cheap transportation. Not the complicated system of legal codes
and exceptions and competing jurisdictions; that felt believably baroque
in the way that complexity emerges in the friction in long-lived human
systems. My problem was with the scale, or rather the lack of scale.
</p>

<p>
This world has ten billion people; there is no way that the relationships
between literally every politically important person in the world could be
this incestuous. There are nowhere near enough factions, disagreements,
alternative power bases, petty personal grudges provoking serious schisms,
or enough <em>bureaucrats</em>. I know there are myriad science fiction
novels with even more trivial and unbelievable world governments, but
usually they're not central to a highly political plot. <cite>Too Like the
Lightning</cite> wants you to care deeply about the politics of this world and
then gives you a system in which all major decisions roll up to a handful
of people with apparently next to no intervening civil service.
</p>

<p>
Also, why is there so little redundancy? How can the most vital service of
this civilization be run directly and almost exclusively by the
inhabitants of <em>one house?</em> There is a technical explanation, but the
social explanation is barely handwaving. This is not how institutional
trust generally works; even with vast multinational high-capital
near-monopolies such as cloud computing, there are three major players and
innumerable smaller ones.
</p>

<p>
Maybe Palmer was extrapolating from the global oligarch class and meetings
such as the <a href="https://en.wikipedia.org/wiki/World_Economic_Forum">World Economic Forum</a>, which do indeed attract a startling percentage of
all world political figures. The problem, though, is not the surface of
occasional gatherings or staged events seen early in this story. It goes
much deeper, far into confidences and explicit coordination, to the extent
that at several points I said some variation of "oh come on, there's no
way Mycroft personally knows them <em>too</em>." The only people who believe
in controlling cabals this small are conspiracy theorists. This is simply
not how humans work when this much power is at stake.
</p>

<p>
Now, I have to say that I'm going out on a limb making this critique after
only reading the first book of a four-book series. This is absolutely the
type of work for which my reaction and objections could be an intentional
effect created by Palmer in order to spring some unexpected justification
on the reader in book two or three. It's clear that there is some massive
social upheaval on the horizon in this series, and something very strange
is going on with one of the characters and their hold over other people.
Perhaps the reader disbelief is setting up that upheaval. If so, hats off
to her, and that's one of the perils of reviewing books as I read them.
</p>

<p>
But it still hurt my enjoyment of this book when the political drama kept
shrinking and tightening and focusing on fewer and fewer people. It felt
frankly unbelievable for the political universe of this highly political
book to be this claustrophobic. I wanted it to expand into the space that
should be available to an entire world teeming with fractious and complex
humanity.
</p>

<p>
The other major complaint I have about this book is that the first-person
narrator is odious. This is something I knew going in — <cite>Too Like the
Lightning</cite> famously has an unreliable and unlikable narrator — and he is
relatively passive for much of the book, so it is often possible to ignore
him and focus on more likable characters. I don't necessarily mind an
unlikable or unreliable narrator in this type of story.
</p>

<p>
But, unfortunately, Mycroft cringes, and I hate reading about cringing for
this many pages. His primary mode of interaction with people is
obsequious, performative fear with a weird, distasteful edge of
manipulation. Again, I think this is entirely intentional on Palmer's
part; we learn some of the reasons behind it by the end of this book, and
I'm sure we'll learn more in future books. But, nonetheless, the overall
effect is a bit like reading a book narrated by
<a href="https://en.wikipedia.org/wiki/Gr%C3%ADma_Wormtongue">Gríma
Wormtongue</a>. I can appreciate the narrative role of that character without
wanting to spend this much time in his head.
</p>

<p>
I have very mixed feelings about this book. The overall construction is
brilliant; it's a beautiful puzzle of oddity and alienation that provides
great fun for the type of science fiction reader who wants to work out the
rules of a strange society without a lot of infodumping. There are a few
characters I adored: Eureka, for example, a set-set (a sort of human
computer in a way that reminded me of mentats in <a href="https://www.eyrie.org/~eagle/reviews/books/0-441-17271-7.html"><cite>Dune</cite></a> but with better world-building) who steals every scene that
she's in. I was very invested in the world-building, fascinated by the
Utopians, and want to learn more about what's going on.
</p>

<p>
On the other hand, the combination of Mycroft as a narrator and the weird
one-room play logic of global politics kept throwing me out of my reading
flow. It took me about a month to finish this book. The science fiction
and political fiction aspects of the story interested me more than Bridger
and whatever is going on with J.E.D.D. Mason, and I'm worried that my
least-favorite aspects will be central to the rest of the story. I was
enjoying a smaller percentage of the scenes by the end of the book than I
was at the start, which is not a great sign.
</p>

<p>
And yet, the ending absolutely worked on me. I don't want to stop here! I
will probably pick up the sequel, but I think it's going to take me a
while to brace myself for it.
</p>

<p>
I have no idea whether to recommend this or not, since I think your
enjoyment will depend so much on the balance between the parts of the book
you find irritating and the parts of the book you find engrossing. I'm
fairly sure most readers will find a little of both, but I have no idea
how to predict their relative weight. If you like cognitive estrangement,
this is great; I understand why so many science fiction reviewers rave
about this book. If you need to like the first-person protagonist, uh,
good luck. Maybe you'll have more tolerance for cringing than I do.
</p>

<p>
The one thing I can say firmly about <cite>Too Like the Lightning</cite> is that
it's <em>interesting</em>. It may be worth reading just to see how people
are stretching the genre, even if you end up not liking the effect. But be
warned that this book does not so much end on a cliffhanger as suddenly
stop at some random, nondescript point on the road leading to the cliff.
The ending is deeply unsatisfying; you will need to read more if you want
to understand what's going on.
</p>

<p>
Followed by <cite>Seven Surrenders</cite>.
</p>

<p>Rating: 7 out of 10</p>
       </div>
       <p class="date">
        <a href="https://www.eyrie.org/~eagle/reviews/books/1-4668-5874-5.html">02 September, 2026 02:44AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://blog.trueelena.org" title="Valhalla's things">Valhalla's Things</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/index.html_hide\" onClick=\"exclude( 'https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/index.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/index.html_show\"  style=\"display:none;\" onClick=\"show( 'https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/index.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/index.html" class="https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/index.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/index.html">A Corset Cover</a>
       </h3>
       <div class="content">
        <article>
    <section class="header">
        Posted on September  2, 2026
        <br />
        
        Tags: <a href="https://blog.trueelena.org/tags/madeof%3Aatoms.html" title="All pages tagged 'madeof:atoms'.">madeof:atoms</a>, <a href="https://blog.trueelena.org/tags/craft%3Asewing.html" title="All pages tagged 'craft:sewing'.">craft:sewing</a>, <a href="https://blog.trueelena.org/tags/period%3Aedwardian.html" title="All pages tagged 'period:edwardian'.">period:edwardian</a>, <a href="https://blog.trueelena.org/tags/FreeSoftWear.html" title="All pages tagged 'FreeSoftWear'.">FreeSoftWear</a>
        
    </section>
    <section>
        <p><img alt="A woman wearing a sleeveless blouse in white fabric with a big band of whitework embroidery gathered over a light blue ribbon at the neckline, a box pleat at the front, another, smaller, band of whitework embroidery at the waist, without a ribbon, and a short peplum that doesn't reach the center front. Around the armscyes there are small ruffles, giving even more volume at the top. A bit of a grey corset peeks out from the center front, below the waist." class="align-center" src="https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/corset_cover_worn.jpg" style="width: 80.0%;" /></p>
<p>Many years ago, before I had my sewing pattern website, I made myself a
simple corset cover according to <a href="https://archive.org/details/americansystemof00merw/page/324/mode/2up">the instructions on an Edwardian
pattern drafting manual</a>.</p>
<p><img alt="A sleeveless blouse in white fabric with machine whitework embroidery; it has small ruffles around the armscyes and the neckline is low and wide, with beading lace and a blue cord going through it to gather it up." class="align-center" src="https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/old_corset_cover.jpg" style="width: 80.0%;" /></p>
<p>It worked, I wore it. Years later I saw <a href="https://pourlavictoire.blogspot.com/2022/02/how-to-make-edwardian-corset-cover.html">a blog post on Pour La Victoire</a>
on making a corset cover based on the same book, but with completely
different results, and thought that it would have been nice to make
another one to publish instructions for my take on it.</p>
<p>However, I didn’t have any embroidery flouncing on hand, nor did I have
a <em>need</em> for a new corset cover, and the project remained on the list, on
low priority (although I did buy some beading lace for it, when I
stumbled on it).</p>
<p><img alt="The corset cover pattern laid on fabric: just wide enough for the main piece, and the peplum only fit because the fabric leftover was in the exact right shape for it to lie on the fold in one specific position." class="align-center" src="https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/just_enough.jpg" style="width: 80.0%;" /></p>
<p>Then, after finishing my vampire shirt, I noticed that I had just enough
fabric left for a corset cover, and by just enough I really mean just
enough, as I discovered when laying the pattern on the fabric.</p>
<p>So I dug in my files to get the original pattern I used, brought it up
to date, and added the missing details such as the pleating guides that
I had skipped when making the pattern just for myself. Doing so I
realized that on my old cover I had done the fake pleat in the front
wrong, making just a single pleat instead of a box pleat. Also, I
originally directly gathered the sleeves in the armscyes, but watching
the book again I realized that the sleeves were made up of a gathered
ruffle plus a straight band.</p>
<p>Both issues were fixed and I could cut the fabric and start sewing. By
machine, including using a narrow hem foot instead of sewing rolled hems
by hand as my instinct kept reminding me would have looked neater.</p>
<p>But this is a garment from a sewing machine time, and probably one that
in many cases would have been bought from a mass producer, and it’s
underwear, so there is no real need for the hems to be perfect, as it’s
going to be hidden anyway. But most importantly, I wanted to write
instructions for machine sewing, for a change, and so I had to machine
sew all steps that I had to take pictures of.</p>
<p>I did do the buttonholes by hand, because I hate the buttonhole
attachment on my machine, and the buttonhole attachment hates me.</p>
<p>I used a lighter weight fabric for the sleeve ruffles, both because I
didn’t have a big enough piece of main fabric not to have to piece them,
and because I felt that it looks better, as it’s the same voile I used
for the ruffles on the vampire shirt.</p>
<p><img alt="Two white beading laces made of fabric with machine whitework: the top one is narrow, with just the holes for ribbon, small flowers between each couple of holes, a straight line with small holes in the middle at the bottom and small scalloped edges at the top. The bottom one is significantly taller, with bigger holes, scalloped edges on both sides that give a look of oval medallions which in turn have scalloped edges." class="align-center" src="https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/beading_lace_choice.jpg" style="width: 80.0%;" /></p>
<p>When it came to the beading lace, I had two that I had bought more or
less thinking about this project: the earlier one was narrow and
suitable to do its job, but the one I had bought more recently was
taller, with an edge that made it suitable to give more fullness to the
bust when gathered up.</p>
<p>I contemplated for a short while, and then decided to go for fullness
and use the taller border for the top edge, but the smaller one at the
waist, where fullness is not wanted.</p>
<p><img alt="The back of the blouse, as worn: it has a bit of a triangle shape, quite close at the waist and with some fullness at the top, but less than in the front." class="align-center" src="https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/corset_cover_back.jpg" style="width: 80.0%;" /></p>
<p>The book claimed that this pattern required little labour, and indeed it
did: even when taking step by step pictures it only took a few hours
spread over a week, plus the time to make buttonholes by hand over the
next week.</p>
<p>And then the reason for the whole project: I published my <a href="https://sewing-patterns.trueelena.org/historical_womenswear/underwear/1909_corset_cover/index.html">pattern and
instructions under a free license</a>.</p>
<p>I still haven’t worn the corset cover, except for these pictures, but I
hope to do so later in the year when the weather becomes more reasonable.</p>
    </section>
</article>
       </div>
       <p class="date">
        <a href="https://blog.trueelena.org/blog/2026/09/02-a_corset_cover/index.html">02 September, 2026 12:00AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>September 01, 2026</h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://www.eyrie.org/~eagle/" title="Eagle's Path">Russ Allbery</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.eyrie.org/~eagle/reviews/books/0-7564-1971-9.html_hide\" onClick=\"exclude( 'https://www.eyrie.org/~eagle/reviews/books/0-7564-1971-9.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.eyrie.org/~eagle/reviews/books/0-7564-1971-9.html_show\"  style=\"display:none;\" onClick=\"show( 'https://www.eyrie.org/~eagle/reviews/books/0-7564-1971-9.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.eyrie.org/~eagle/reviews/books/0-7564-1971-9.html" class="https://www.eyrie.org/~eagle/reviews/books/0-7564-1971-9.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.eyrie.org/~eagle/reviews/books/0-7564-1971-9.html">Review: Last Chance to Save the World</a>
       </h3>
       <div class="content">
        <p>Review: <cite>Last Chance to Save the World</cite>, by Beth Revis</p>

<table>
  <tbody><tr>
    <td>Series:</td>
    <td>Chaotic Orbits #3</td>
  </tr>
  <tr>
    <td>Publisher:</td>
    <td>DAW Books</td>
  </tr>
  <tr>
    <td>Copyright:</td>
    <td>April 2025</td>
  </tr>
  <tr>
    <td>ISBN:</td>
    <td>0-7564-1971-9</td>
  </tr>
  <tr>
    <td>Format:</td>
    <td>Kindle</td>
  </tr>
  <tr>
    <td>Pages:</td>
    <td>133</td>
  </tr></tbody></table>

<p>
<cite>Last Chance to Save the World</cite> is a far-future science fiction caper
novella and the conclusion of the trilogy that began with
<a href="https://www.eyrie.org/~eagle/reviews/books/0-7564-1947-6.html"><cite>Full Speed to a Crash Landing</cite></a>. This is a
direct sequel to <a href="https://www.eyrie.org/~eagle/reviews/books/0-7564-1949-2.html"><cite>How to Steal a Galaxy</cite></a>,
picking up right after that story leaves off, but you don't have to
remember the details to enjoy this installment.
</p>

<p>
Ada has finally achieved a (temporary, contingent) alliance with
government agent Rian White by convincing Rian that some things are more
important than Ada's disregard for the law. She's going to need his help.
They have once chance to save Earth from a new and even more malicious
round of capitalist environmental blackmail, and it's going to require
Rian's security access as well as all of Ada's heist skills.
</p>

<p>
But first, a visit with Ada's mother, who lives in an old watchtower on
Malta and keeps pigeons.
</p>

<p>
Each entry in this series has been a little shorter than the last, and
<cite>Last Chance to Save the World</cite> is definitely a novella. This is a
great length for a heist story: enough room for some setup and a couple of
major plot twists, but short enough that the story can maintain a headlong
pace. Even in the third novella of a series and a novel's worth of time in
Ada's head, Revis has one major surprise for the reader left. And, as
usual, there's a lot of misdirection, sarcastic commentary, and the
delightful competence of a protagonist who puts considerable professional
effort into being underestimated.
</p>

<p>
The bits with Ada's mother were great. This is the first time we've seen
Ada have significant interactions other than her flirting and teasing of
Rian, and I loved seeing a different side of her. The heist itself was
satisfying, although not quite as good as <cite>How to Steal a Galaxy</cite>.
Ada gets to throw a few more verbal daggers, but there are more events in
this installment and therefore more action and less dialogue. Ada's
commentary and dialogue is still my favorite part, though.
</p>

<blockquote><p>
    For all that Rian says I like to break the law, it should be illegal
    for any one man to be both this dumb and this rich. It's astounding,
    really. Any of his employees could run circles around him, but it
    doesn't take brains to buy stuff. Strom Fetor sees nothing clearly
    except profit margins.
</p></blockquote>

<p>
There is, of course, even more flirting and semi-fake romance. Those were
not my favorite part, mostly because while it's obvious what Rian sees in
Ada, it baffles me what Ada sees in Rian. I know the star-crossed romance
between the law man and the charismatic thief is an old fictional trope,
but I found it very hard to justify Rian's continuing commitment to his
law and government given the clear facts of this setting.
</p>

<p>
Up until this novella, one could excuse Rian as the sort of person whose
belief in order, stability, and rules combines with possibly excessive
optimism to create a belief in an imperfect system. But here, Ada has
finally convinced Rian that some great evils truly will not be fixed by
following the rules. He's onboard, but somehow in a way that leads to
precisely no reconsideration, soul-searching, or breach in his commitment
to defending a clearly corrupt and failing political system.
</p>

<p>
My objection is not that this is unrealistic; sadly, it's very realistic.
My objection is that Rian is dumber than a bag of hammers, I don't like
reading about his blind allegiance to a bad system, and I do not
understand how that goes with the sexy feelings. I'm sure this is my lack
of understanding of physical affection overriding common sense, and Ada is
at least not a complete idiot about her attraction. But I felt like this
novella expected me to like Rian as more than a foil for Ada, and I very
much did not.
</p>

<p>
That knocked a point off my enjoyment of this entry, but the heist is
great, the politics are interesting, and the climax was very satisfying.
This is not quite as good as the middle book of the trilogy, but it's a
satisfying conclusion. If you liked the previous entries, you'll want to
read this one for the conclusion.
</p>

<p>
<cite>Last Chance to Save the World</cite> resolves the main plot driver of the
trilogy, but there's a lot of space for more sequels. If they materialize,
I will probably keep reading, although I hope someone knocks some sense
into Rian.
</p>

<p>Rating: 8 out of 10</p>
       </div>
       <p class="date">
        <a href="https://www.eyrie.org/~eagle/reviews/books/0-7564-1971-9.html">01 September, 2026 03:18AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://blog.trueelena.org" title="Valhalla's things">Valhalla's Things</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/index.html_hide\" onClick=\"exclude( 'https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/index.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/index.html_show\"  style=\"display:none;\" onClick=\"show( 'https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/index.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/index.html" class="https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/index.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/index.html">Granddaughter Clock</a>
       </h3>
       <div class="content">
        <article>
    <section class="header">
        Posted on September  1, 2026
        <br />
        
        Tags: <a href="https://blog.trueelena.org/tags/madeof%3Aatoms.html" title="All pages tagged 'madeof:atoms'.">madeof:atoms</a>, <a href="https://blog.trueelena.org/tags/madeof%3Abits.html" title="All pages tagged 'madeof:bits'.">madeof:bits</a>, <a href="https://blog.trueelena.org/tags/craft%3Aelectronics.html" title="All pages tagged 'craft:electronics'.">craft:electronics</a>, <a href="https://blog.trueelena.org/tags/craft%3Apaper.html" title="All pages tagged 'craft:paper'.">craft:paper</a>
        
    </section>
    <section>
        <p><img alt="a paper maché object in the shape of a cartoony grandfather clock with a somewhat irregular shape, painted reddish brown except for the white face." class="align-center" src="https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/granddaughter_clock.jpg" style="width: 80.0%;" /></p>
<p>Remember the <a href="https://blog.trueelena.org/blog/2025/02/04-conference_talk_timeout_ring_part_one/index.html">Conference Talk Timeout Ring</a>?
Well, things may have escalated a bit.</p>
<p>The first thing that happened is that I may have accidentally added
<a href="https://www.olimex.com/Products/Breadboarding/RGB-LEDs/">more RGB LED rings, one for each size</a> to an order
of things that we actually needed, because they were cheap and
potentially shiny (and I may have ideas that involve the big ones, but
they are still just vague ideas).</p>
<p>When they arrived, I played a bit with them to check that they were
working, and one was used in a pinch as a light while soldering, and
worked nicely.</p>
<p>In the same order there was also a <a href="https://www.raspberrypi.com/products/raspberry-pi-pico-2/">Raspberry Pico2 W</a> and I
decided to use it instead of the <a href="https://www.olimex.com/Products/IoT/ESP32-C3/ESP32-C3-DevKit-Lipo/open-source-hardware">ESP32-C3-DevKit-Lipo</a>
I’ve used a lot lately because it has better support<a class="footnote-ref" href="https://blog.trueelena.org#fn1" id="fnref1"><sup>1</sup></a> in
<a href="https://circuitpython.org/board/raspberry_pi_pico2_w/">CircuitPython</a>.</p>
<p>So, I have an RGB LED ring with a multiple of 12 LEDs and a
microcontroller board with a lot of memory and wifi, what I’m going to
do? a grandfather clock, obviously. Except our grandfathers didn’t
exactly have LEDs, so it’s going to be a granddaughter clock.</p>
<p>Have I mentioned that things escalated? well, of course I wanted the
clock to show the time, but I also wanted it to be able to turn into a
flashlight, and to run a countdown for conference talks and any other
need, and to tell me if there are things that need to be taken care of
around the house, and…</p>
<p>And I have an MQTT server and a number of sensors around the house that
provide environmental data, and I decided I might as well use it for
other things.</p>
<p>So I designed this to listen to an MQTT topic for commands, another MQTT
topic for data, and to switch between modes when instructed to do so by
a command.</p>
<p>Other considerations included the fact that this is keeping a number of
LEDs on, so I didn’t even try to reduce power usage to run it on battery
power for significant amounts (weeks) of time (although running it from a
power bank seems to work for shorter durations — I’m thinking a day or
two).</p>
<p>And then it was time to fix the part where recognising the first LED on
a ring is hard, and I decided to grab my Art Attack supplies and make a
case in the shape of a grandfather clock, scaled down to a suitable
size for keeping on a desk or bookcase.</p>
<p>I used some IKEA box to make a structure, glued it with hot glue, and
then wrapped everything with paper napkins and PVA for added strength,
plus a bit of tarlatan for the door hinge.</p>
<p>I opted for a very cartoonish look (and yes, if you are old enough that
it resembles something, there was a vague source of inspiration in a
cultural artefact of the early 1990) with just a clock face that fits in
by friction, a hinged door to access the electronics and a bit of
decorative trimming at the top.</p>
<p><img alt="a structure made of circles of cardboard in various sizes glued together and strengthened with tissue paper, with a LED ring fitting snugly on top. The ring is marked WCMCU-2812B-12." class="align-center" src="https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/face_back_with_led_ring.jpg" style="width: 80.0%;" /></p>
<p>For the face I decided to make holes in the cardboard and fill them with
hot glue to make a sort of light pipe, with the LEDs pressed against
them on the inside. It’s not perfect, but it mostly works.</p>
<p>And then everything stopped: while I waited for the PVA to dry I started
doing something else, and then there were other projects, and other, and
the clock lingered in the Pile. There was a brief interruption as I
started to paint the first coat of brown, and then I moved back to the
other projects.</p>
<p>Until, months later, I decided it was time to finish using the brown and
white tubes of paint that I had on my desktop, so I could put them away
<a class="footnote-ref" href="https://blog.trueelena.org#fn2" id="fnref2"><sup>2</sup></a>, and in a reasonable time I finished painting the clock,
including a second coat of brown, and black contour lines to add a bit
of depth in a way consistent with the cartoonish look.</p>
<p>And then it was time to go back to the internals: I got the LED ring and
raspberry pico back from their respective drawers, connected them with
dupont cables and fit them in the case for a test: it worked.</p>
<p><img alt="a LED ring mounted on the back of structure made out of circles of cardboard in different sizes, glued together; it's connected with wires kept together with heat shrink to a perfboard with a couple of connectors, two buttons and a small microcontroller board (details on which are in the next paragraph)." class="align-center" src="https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/face_and_board.jpg" style="width: 80.0%;" /></p>
<p>However, the raspberry had quite a lot of pins, and it felt wasteful to
use it on something that basically needs <em>one</em>. On the other hand, I had
recently bought a few <a href="https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/">Seed Studio XIAO ESP32C3</a> for
another project<a class="footnote-ref" href="https://blog.trueelena.org#fn3" id="fnref3"><sup>3</sup></a>, and those are quite smaller, and also
slightly cheaper, and I could spare one out of the 13 I had.</p>
<p>Up to now on the XIAO boards I had been using <a href="https://micropython.org/">MicroPython</a>: I had
started to use it on the <a href="https://blog.trueelena.org">ESP32-C3-DevKit-Lipo</a> because, contrary to
CircuitPython, the generic ESP32-C3 image worked on it, and on the ESP32
boards there is no <code>CIRCUITPYTHON</code> partition, which in my opinion is
one of the advantages that make CircuitPython more convenient to use
than MicroPython.</p>
<p>However, the code I had already written for the clock used
CircuitPython, so I flashed one of the XIAOs with the other interpreter,
and after changing just one pin definition the software I had worked.</p>
<p>Going back and forwards between the two interpreters will be
interesting, especially since I have already started to write some code
for the other project in MicroPython, and they are supposed to
interoperate. I may end up rewriting one of them, if I start getting
hindered by the subtle differences.</p>
<p><img alt="A rat nest of mostly colour-coded wire that cross each other. badly soldered to the back of a bit of perfboard, with heat damage on the wire insulation." class="align-center" src="https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/perfboard_back.jpg" style="width: 80.0%;" /></p>
<p>The next step involved dealing with the temporary connections to make
them a bit more permanent: I have been using LibrePCB for that other
project, so of course what I did was… grabbing a bit of perfboard and
YOLO a growing rat nest of cables over it, without bothering with
drawing any kind of schematics in advance. And having to desolder stuff
and solder it again a couple of times, because I had issues with the
difference between left and right, and with the concept of rotations in
3D space.</p>
<p><img alt="the clock turned 90°, with the door open showing the board inside, plus a hint of a round plastic container that housed the microcontroller board. A rectangular hole about the size of an USB cable is visible in the back of the clock." class="align-center" src="https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/clock_open.jpg" style="width: 80.0%;" /></p>
<p>Everything was brought back into the case, in a mostly stable
configuration with an usb cable coming out of a hole in the back for
power and surprisingly it works.</p>
<p>Or at least, 95% of the issues it still has are software, plus I still
need to add a few features, so right now it lives above my desktop, with
the cable dangling close to an USB port, so that I can continue working
on that in the next few weeks.</p>
<p>The external look is not going to change, so there will be changes on
<a href="https://git.trueelena.org/electronics/gd_clock/">the git repository</a>,
and there may or not be a third post here in the future, depending on
whether there will be something funny or interesting, or it will just be
small incremental improvements.</p>
<section class="footnotes footnotes-end-of-document">
<hr />
<ol>
<li id="fn1"><p>I think that CircuitPython on the ESP32-C3-DevKit-Lipo
only requires fixing two PIN definitions in the files for a very
similar board and a recompile, but the latter part looks like a PITA
and I haven’t committed to it.<a class="footnote-back" href="https://blog.trueelena.org#fnref1">↩︎</a></p></li>
<li id="fn2"><p>to make room for other crafting supplies for other projects,
of course.<a class="footnote-back" href="https://blog.trueelena.org#fnref2">↩︎</a></p></li>
<li id="fn3"><p>yes, it will be blogged! unless it fails in a
catastrophic way and gets buried under a layer of litter to forget
about it. :D<a class="footnote-back" href="https://blog.trueelena.org#fnref3">↩︎</a></p></li>
</ol>
</section>
    </section>
</article>
       </div>
       <p class="date">
        <a href="https://blog.trueelena.org/blog/2026/09/01-granddaughter_clock/index.html">01 September, 2026 12:00AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>August 31, 2026</h1>
     

    <div class="channel">
     
      
     <a href="https://www.earth.li/~noodles/blog/">
      <img class="face" src="heads/noodles.png"
            width="100" height="108"
            alt="hackergotchi for Jonathan McDowell" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://www.earth.li/~noodles/blog/" title="Noodles' Emptiness">Jonathan McDowell</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.earth.li/~noodles/blog/2026/08/what-do-i-want-distro.html_hide\" onClick=\"exclude( 'https://www.earth.li/~noodles/blog/2026/08/what-do-i-want-distro.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.earth.li/~noodles/blog/2026/08/what-do-i-want-distro.html_show\"  style=\"display:none;\" onClick=\"show( 'https://www.earth.li/~noodles/blog/2026/08/what-do-i-want-distro.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.earth.li/~noodles/blog/2026/08/what-do-i-want-distro.html" class="https://www.earth.li/~noodles/blog/2026/08/what-do-i-want-distro.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.earth.li/~noodles/blog/2026/08/what-do-i-want-distro.html">What do I want in a Linux distribution?</a>
       </h3>
       <div class="content">
        <p>I’ve been a <a href="https://www.debian.org/">Debian</a> user since 1999, and a Debian developer <a href="https://nm.debian.org/legacy/process/13424/">since 2000</a>. Given <a href="https://www.debian.org/vote/2026/vote_002">recent events</a> it’s worth thinking about why that that is, and why I haven’t switched to something else in the past quarter century.</p>

<p>My first Linux distro was <a href="http://www.slackware.com/">Slackware</a>, off a CD in a book, some time in the mid 90s. After starting university I ran <a href="https://www.suse.com/">SUSE</a> for a while, then moved to <a href="https://www.redhat.com/">RedHat</a> (both back before they had commercial variants significantly different to what was available freely). The main motivation for switching was package management; I was running a machine at home, and a machine at university. Keeping track of what was installed on each, and what versions, was getting annoying with Slackware. Most of the folk I knew were running RedHat, and I mostly played with SUSE because I’m contrary before realising it was different enough that I couldn’t easily make use of 3rd party RPMs.</p>

<p>I came to Debian via friends in Cambridge, who spoke highly of it. The first Debian machine I installed was <code class="language-plaintext highlighter-rouge">fourier</code>, the initial host for Black Cat Networks, and I never looked back.</p>

<p>(For additional context I should also point out I have contributed, in the distant past, to, and run, <a href="https://openwrt.org/">OpenWRT</a>, <a href="https://www.openembedded.org/">OpenEmbedded</a>, and <a href="https://www.freebsd.org/">FreeBSD</a>.)</p>

<p>I’d like to try and work out what is it I get from Debian that I’d need in anything else. Originally I tried to order the requirements in some sort of priority, but it’s sometimes hard to work out what I’d drop if I had to compromise somewhere, so it’s a somewhat loose ordering.</p>

<dl>
  <dt>Stable releases, with security support</dt>
  <dd>I run Linux in lots of places, from remote servers/VMs, to my house router, to my desktop/laptop. Some of those I don’t want to be updating regularly with new software releases, I need something I can be sure is going to keep working, but will get necessary security + critical updates. A rolling distro that provides security via the latest upstream release doesn’t provide that guarantee. Equally there need to be regular stable releases, or things become too stale. (The one time I considered moving away from Debian was during the 3 year <a href="https://www.debian.org/releases/sarge/">Sarge / 3.1</a> release cycle. I think if things hadn’t improved I’d have jumped ship to <a href="https://ubuntu.com/">Ubuntu</a> at the time.)</dd>
  <dt>A good selection of packages</dt>
  <dd>One of the reasons I moved from RedHat to Debian was the wide range of packages available as part of the standard OS. Pulling it all into the distro helps with quality control, compared to random 3rd party packages. A centralised bug system and repository is a win too. Perhaps packages at all is something I should list, but I take it as a given if you’re running a distro. I need to know what I have installed on my machine, what version that software is, what files it owns, and what it depends on.</dd>
  <dt>Free Software</dt>
  <dd>This is important to me. I’ll make pragmatic compromises about software I run on my systems if it makes sense, but I want to start from a place that does not require anything non-free. I’ve run a company on Debian, and I’ve worked on numerous products that ran it under the hood. The <a href="https://www.debian.org/social_contract#guidelines">DFSG</a> give me confidence I can do that.</dd>
  <dt>Smooth upgrades</dt>
  <dd>Debian’s ability to upgrade a system smoothly is one of the reasons I first moved to it. The first upgrade I did was remotely on a machine sitting on a 2Mb/s leased line. I was nervous doing the reboot at the end, but it came back fine. At the time the equivalent procedure with RedHat involved rebooting into the OS installer to do the upgrade.</dd>
  <dd>I know things have moved on since then, and really it should all be scripted, and machines should be cattle not pets, but for personal use I run a small enough number of machines that having the upgrade path between releases is a must have.</dd>
  <dt>Community</dt>
  <dd>The original pull of the Debian community was the knowledge I could get involved, and upload packages that were missing that I was using. That’s how I first got involved, uploading things Black Cat used, which made life easier for us in the long run. I don’t have time to maintain all the software I use myself, and I don’t want to be beholden to a commercial entity to do so for me, so a distribution that allows me to help out where I can as part of the community seems to me to be the right way to do things.</dd>
  <dt>Architecture support</dt>
  <dd>Perhaps less important, especially when I started using Debian, but these days I have amd64, arm64, armhf, and riscv machines. Everything except for the risvc box is doing something useful, and would need replaced if I couldn’t keep running it, and I expect RISC-V to transition into that state in the next few years as the hardware improves.</dd>
  <dt>Binary packages</dt>
  <dd>I ran a FreeBSD desktop for some time. It might have been the way I was holding it, but binary package installs were generally not something reliable, especially after the initial install, and I ended up building things from ports from source quite often. That worked incredibly well (I used to think people who raved about <a href="https://www.gentoo.org/">Gentoo</a> really should just go do it properly and use FreeBSD), but I don’t want to spend time compiling things, especially on some of my machines (my router should not need a compiler, for example).</dd>
</dl>

<p>Ultimately I don’t want to have to actively think about the Linux distribution I use. Debian has mostly given me that; I know it will generally be suitable for most environments I want to use it in (embedded situations where OpenWRT or OpenEmbedded are better choices being the exception, but that’s less frequent these days), and I can rely on getting timely security updates (thanks to all those who work on that within Debian!). I’m not sure there’s currently an alternative that would suit my needs? I’d love to hear if there’s something I should look at, even if I’m not necessary making a move just yet!</p>
       </div>
       <p class="date">
        <a href="https://www.earth.li/~noodles/blog/2026/08/what-do-i-want-distro.html">31 August, 2026 05:20PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://www.eyrie.org/~eagle/" title="Eagle's Path">Russ Allbery</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.eyrie.org/~eagle/reviews/books/1-988908-15-9.html_hide\" onClick=\"exclude( 'https://www.eyrie.org/~eagle/reviews/books/1-988908-15-9.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.eyrie.org/~eagle/reviews/books/1-988908-15-9.html_show\"  style=\"display:none;\" onClick=\"show( 'https://www.eyrie.org/~eagle/reviews/books/1-988908-15-9.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.eyrie.org/~eagle/reviews/books/1-988908-15-9.html" class="https://www.eyrie.org/~eagle/reviews/books/1-988908-15-9.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.eyrie.org/~eagle/reviews/books/1-988908-15-9.html">Review: The Hands of the Emperor</a>
       </h3>
       <div class="content">
        <p>Review: <cite>The Hands of the Emperor</cite>, by Victoria Goddard</p>

<table>
  <tbody><tr>
    <td>Series:</td>
    <td>Lays of the Hearth-Fire #1</td>
  </tr>
  <tr>
    <td>Publisher:</td>
    <td>Underhill Books</td>
  </tr>
  <tr>
    <td>Copyright:</td>
    <td>January 2019</td>
  </tr>
  <tr>
    <td>ISBN:</td>
    <td>1-988908-15-9</td>
  </tr>
  <tr>
    <td>Format:</td>
    <td>Kindle</td>
  </tr>
  <tr>
    <td>Pages:</td>
    <td>739</td>
  </tr></tbody></table>

<p>
<cite>The Hands of the Emperor</cite> is a self-published political fantasy
novel. It's the recommended first book (although not the first published
book) in a
<a href="https://nineworlds.miraheze.org/wiki/Reading_Order">complicated set
of interrelated series</a>. I was not able to definitively confirm that
Underhill Books is Goddard's self-publishing press name, but the press
does not appear to have an Internet presence apart from Goddard's books
and her books appear to be using the standard self-publishing channels.
</p>

<p>
Cliopher Mdang is the personal secretary of the last emperor of
Astandalas, the magical heart of Zunidh, a man worshiped as a god. The
emperor's word is absolute, his magic supports the health of the entire
world, and he cannot be physically touched without risking physical damage
and severe political and religious punishment. Cliopher is one of the
emperor's closest associates, but the distance between them is still vast.
It therefore represents a terrifying and dangerous breach of etiquette for
him to suggest the emperor may enjoy a vacation on a tropical island near
Cliopher's remote home. The emperor's acceptance of the invitation is even
more startling.
</p>

<p>
The emperor has opinions about his life as the emperor that no one had
guessed. Cliopher has not assimilated as completely into the bureaucratic
machinery of the empire as it first may appear. And Cliopher's family have
vastly misunderstood the nature of his role in the emperor's government.
</p>

<p>
I find the marketing blurb for this book unfortunate since, at least to
me, the emphasis on physical touch and intimacy implies that <cite>The
Hands of the Emperor</cite> is a romance novel or at least has significant
romantic elements. I've been aware of this book for years but put off
reading it because I wasn't quite in the mood for that story. This is not
a romance novel; there is no romance in this book whatsoever. It is a
political fantasy, both in the sense that it is set in a secondary fantasy
world with magic and (apparently) some form of interplanetary travel, and
in the sense that it is a fantasy of governance.
</p>

<p>
When I say that this book blew up in certain corners of the Internet
during the pandemic, I think you will still underestimate the passion of
its advocates. I heard about this book <em>constantly</em>, in a way that
reminded me of <a href="https://www.eyrie.org/~eagle/reviews/books/0-765-34298-7.html"><cite>Kushiel's Dart</cite></a> and the
time when fans of Jacqueline Carey would bring her up in every fantasy
conversation, or when we created a Usenet newsgroup for <cite>The Wheel of
Time</cite> mostly to get the voluminous conversations off of the regular SFF
newsgroup. I'm one of those mildly contrarian people for whom that degree
of enthusiasm is a little off-putting, which is another reason why I
resisted buying a copy for years and only read it in 2026.
</p>

<p>
It's delightful, although also a bit embarrassing, when the book everyone
was in love with turns out to be just as good as everyone said it was.
</p>

<p>
I adore stories about friendship, and this is one of the best stories
about friendship that I've ever read. It is a very, very slow burn, but I
also thought the first three quarters of the book was exquisitely paced.
There were long sections where not very much was happening, and yet I
couldn't put the book down because there was so much subtle character work
just beneath the surface.
</p>

<p>
Almost all of the novel is told in tight third person from Cliopher's
perspective, and I thought that was an excellent choice. Neither Cliopher
nor the narrator comment on things that Cliopher finds obvious, which is
both immersive and critical to the pacing. There are discoveries for the
reader throughout the book, the sort of discoveries that make pieces fit
together satisfyingly in retrospect, and the reader stays sufficiently
ahead of the misunderstandings of Cliopher's friends and family that one
also gets the joy of watching other people discover things that one
figured out a hundred pages earlier.
</p>

<p>
It helps that I truly liked nearly everyone in this book. There are no
real villains, only a few supporting characters whose role is to be
irritating or corrupt. If you're looking for a lot of conflict and drama,
you may want to save this book for a different mood, but if you're in the
mood for a varied collection of fundamentally good characters working
methodically through the complexities and obstacles of politics and social
systems to improve the world, there are few books I would recommend more.
Goddard achieves one of the hardest tricks of slow burns: steady forward
progress that does not rely on reversals, misunderstandings, or the
friendship equivalent of the third-act breakup. This book spends 700 pages
building towards a climax that managed to be worthy of all 700 pages
without ever annoying me with artificial obstacles, and that's quite a
feat.
</p>

<p>
I've not said much about the details of the plot. There is one — it's not
just character work — but I think this book benefits immensely from going
in as blind as possible. I found the twists and turns and growing
revelations so deeply satisfying that I don't want to rob any other reader
of the experience.
</p>

<p>
The fantasy world-building is intriguing but a bit unsatisfying because it
is so unexplained. We get a few details of the magic system, but since
Cliopher has no magic, he isn't that interested in the details. There is a
catastrophic magical event in the world background, and we learn some of
the details of its practical effects, but the nature of the world before
the cataclysm is so obvious to the characters that it's never explained.
I'm not even certain that this civilization is interplanetary; that feels
like the implication of how characters talk about multiple worlds, but the
method of travel is left entirely undefined. This might be frustrating to
some genre readers, but I personally enjoy books where the world-building
is a bit mysterious. It's a good reason to read more of Goddard's books
set in the same universe.
</p>

<p>
This was my favorite of the books I've read so far this year, but I do
have one caution and a couple of caveats.
</p>

<p>
The caution is that Cliopher comes from an island culture based heavily on
(I think) Polynesian cultures. That culture is very central to the story
and is treated with considerable respect, but I still get a bit nervous
when a Canadian author from Nova Scotia with an academic background in
European medieval studies writes a story focused this deeply on a
non-European culture. Nothing about her portrayal seemed off to me
(although there is a very clunky and ham-handed scene about a different
native culture that worries me), and for all I know she has family
background or other connections to the culture she is borrowing from, but
it's possible I missed serious problems.
</p>

<p>
The flip side of that caution is that I'm delighted to see a fantasy
author drawing on a non-European culture, and I thought the clash of
cultures was very well-handled.
</p>

<p>
The first caveat is that the story is very focused on good governance, but
both the process and the details of that governance are not going to
satisfy someone reading primarily for the politics. The policies and
reforms are very standard 21st century progressive material that felt a
bit out of place in a quasi-medieval world with magic and airships. Their
implementation is not the point of the story, and is therefore heavily
backgrounded, but that means Goddard barely mentions the inevitable
practical implementation difficulties and does not discuss how they're
overcome.
</p>

<p>
The world structure also means that Goddard can make use of the favorite
cheat of political reformers in fiction: Absolute monarchy lets you enact
a political agenda without having to do the hard and frustrating work of
persuasion or political (or actual) warfare. This objection is not
entirely fair because we do get some memorable scenes of persuasion, but
the political portion of the plot is unrealistically devoid of setbacks or
resistance that goes beyond token arguments.
</p>

<p>
Whether this will bother you will depend heavily on what parts of the book
you'd rather focus on. I can see why this was such a popular pandemic
read: <cite>The Hands of the Emperor</cite> is focused tightly on the joy of
competent people fixing things and does not focus on the arguments,
division, or polarization. The heart of the book is the friendship and
characterization of some deeply admirable people, and the political reform
is incidental background material. I suspect this is the right choice for
readers who aren't political junkies, but I kept having the niggling
objection that the politics felt a bit too pat and simplistic. Goddard
stressed that the characters were investing considerable effort, but even
still, it is not this easy to change the direction of a political system
and idealistic plans usually do not work out this neatly.
</p>

<p>
The second caveat is that, as previously mentioned, I thought the pacing
was excellent for about three quarters of the book. Goddard is building
towards a grand climax, and I think she built a little too much and tried
to make the climax a bit too grand and risked over-egging the pudding.
That made the payoff feel a bit belabored to me. I still enjoyed it, and
parts of it are wonderfully emotional, but I think the ending might have
been stronger if Goddard had dialed Cliopher back just a little and
tightened up the climax a touch. That said, this book fully commits to
being a sprawling slow burn and that's part of its appeal, so it's
probably better for Goddard to err in that direction than it would have
been to cut short the denouement.
</p>

<p>
This is one of those books that I'm not sure would exist without
self-publishing. It's a little too long, a little too political in the
wrong ways, a little too devoid of the typical sorts of conflicts expected
in a fantasy book, and too determined to be its own peculiar thing. I
think it would scare off publishers. Unlike some self-published books,
though, I didn't notice any obvious editing flaws or lack of polish. It's
one of those glorious novels that is so very much its own type of story
that it provides an experience that would be hard to replicate with
another book.
</p>

<p>
I was so deeply satisfied by this book. It's a wish-fulfillment political
fantasy full of diligent restraint and competence porn, so you have to be
in the mood for that. This is not the book to read when you're feeling
cynical, or are in the mood for action and high drama. But if you're in
the mood for a long, slow, open-hearted story of friendship that offers
the fantasy of giving truly good people enough power to be effective, I
highly recommend this one.
</p>

<p>
Followed in the direct sequel sense by <cite>At the Feet of the Sun</cite>, but
there is a very complex story progression in this world that I think I'd
have to read all the other books to understand. This was such a satisfying
and complete experience that I'm not in a hurry to figure out which
Goddard book to read next, but I'm sure I'll be returning to this world at
some point.
</p>

<p>Rating: 9 out of 10</p>
       </div>
       <p class="date">
        <a href="https://www.eyrie.org/~eagle/reviews/books/1-988908-15-9.html">31 August, 2026 03:11AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://blog.trueelena.org" title="Valhalla's things">Valhalla's Things</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://blog.trueelena.org/blog/2026/08/31-3d_models/index.html_hide\" onClick=\"exclude( 'https://blog.trueelena.org/blog/2026/08/31-3d_models/index.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://blog.trueelena.org/blog/2026/08/31-3d_models/index.html_show\"  style=\"display:none;\" onClick=\"show( 'https://blog.trueelena.org/blog/2026/08/31-3d_models/index.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://blog.trueelena.org/blog/2026/08/31-3d_models/index.html" class="https://blog.trueelena.org/blog/2026/08/31-3d_models/index.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://blog.trueelena.org/blog/2026/08/31-3d_models/index.html">3D Models</a>
       </h3>
       <div class="content">
        <article>
    <section class="header">
        Posted on August 31, 2026
        <br />
        
        Tags: <a href="https://blog.trueelena.org/tags/madeof%3Aatoms.html" title="All pages tagged 'madeof:atoms'.">madeof:atoms</a>, <a href="https://blog.trueelena.org/tags/madeof%3Abits.html" title="All pages tagged 'madeof:bits'.">madeof:bits</a>, <a href="https://blog.trueelena.org/tags/craft%3A3dprinting.html" title="All pages tagged 'craft:3dprinting'.">craft:3dprinting</a>
        
    </section>
    <section>
        <p><img alt="A lucet fork: a two pronged device with a handle with yarn wrapped once around each fork and a knot forming in the middle, out of which a piece of cord is growing. The working yarn is in a ball nearby." class="align-center" src="https://blog.trueelena.org/blog/2026/08/31-3d_models/lucet.jpg" style="width: 80.0%;" /></p>
<div class="note">
<div class="title">
<p>Note</p>
</div>
<p>this article had been almost completely written before the
weekend, and I decided I might as well focus on stuff I’m creating,
finish and publish this.</p>
</div>
<p>For many years, I’ve been sporadically dabbling in creating 3D models;
for reasons that are probably obvious to anybody who knows me I used
<a href="https://openscad.org/">OpenSCAD</a> and saved <a href="https://git.trueelena.org/3d/">my projects in git</a>, which made them at least
somewhat public.</p>
<p>However, SCAD sources in a git repository aren’t the most convenient way
to get a 3D model, and for a long time I never had a consistent way to
publish “binaries” for my models: some have been added to my old
website, some to my craft patterns site, but it was always an ad-hoc
thing.</p>
<p>Then two things happened more or less at the same time.</p>
<p>One was me finding out that <a href="https://tracker.debian.org/pkg/slic3r">slic3r</a> had been definitely removed from
Debian. I know it was going to happen, and I postponed thinking about it
as long as I could, but eventually I had to move over to <a href="https://github.com/prusa3d/PrusaSlicer">PrusaSlicer</a>,
whose packaging is in better shape.</p>
<p>The other was that lately I’ve been doing a bit of lucet, and talking
about it online, and I’m really happy with the shape of the lucet I’ve
designed and printed, the one in the picture at the beginning of this
post, and while there are other models available, I wanted to make it
more convenient for people to also get mine.</p>
<p>Since PrusaSlicer did look still maintained upstream in a way that
doesn’t feel like at danger of immediate enshittification, I considered
making an account on <a href="https://www.printables.com/">Printables</a>, and asked on the Fediverse if somebody
knew something bad about the company behind it, as it’s getting more an
more common these days.</p>
<p>Apparently nobody did, but in the thread somebody mentioned that there
is a <em>federated</em> platform for publishing 3D models, called <a href="https://manyfold.app/">manyfold</a> !</p>
<p>I didn’t want to add “self host a(nother) web thing”, especially not one
that is not in Debian, to my list of projects, but I did create an
account on a public instance: <span class="title-ref">@valhalla@3dprint.social
&lt;https://3dprint.social/creators/valhalla&gt;</span> and started publishing
models, both a selection of old ones and a few new ones I designed in
the last few days, since I was in a 3D printing mindset.</p>
<p>Then I decided that since nobody had serious objections to it, I could
also create an <a href="https://www.printables.com/@valhalla_5236536">account on printables</a>, as that’s probably
more easily accessible to the general public.</p>
<p>I have been somewhat slower at publishing models on the latter, but I
expect that eventually most of what I design will end up on both
platforms; I still have a few older models I want to add, and a few
ideas for new models to make, then I guess stuff will slow down, and
only get new ones now and then, as that’s how I usually approach
hobbies.</p>
<p>Of course, the self-hosted git repository is not going away: that’s
still the canonical location for my models, with all of the
non-self-hosted options as a convenience option.</p>
    </section>
</article>
       </div>
       <p class="date">
        <a href="https://blog.trueelena.org/blog/2026/08/31-3d_models/index.html">31 August, 2026 12:00AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>August 30, 2026</h1>
     

    <div class="channel">
     
      
     <a href="http://aigarius.com/categories/debian-planet/">
      <img class="face" src="heads/aigarius_hg.png"
            width="85" height="116"
            alt="hackergotchi for Aigars Mahinovs" />
     </a>
      
     <h2 class="planet-title">
      <a href="http://aigarius.com/categories/debian-planet/" title="Aigarius Blog (Posts about Debian-planet)">Aigars Mahinovs</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://aigarius.com/blog/2026/08/30/half-a-year-with-iX3/_hide\" onClick=\"exclude( 'http://aigarius.com/blog/2026/08/30/half-a-year-with-iX3/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://aigarius.com/blog/2026/08/30/half-a-year-with-iX3/_show\"  style=\"display:none;\" onClick=\"show( 'http://aigarius.com/blog/2026/08/30/half-a-year-with-iX3/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://aigarius.com/blog/2026/08/30/half-a-year-with-iX3/" class="http://aigarius.com/blog/2026/08/30/half-a-year-with-iX3/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://aigarius.com/blog/2026/08/30/half-a-year-with-iX3/">Half a year with iX3</a>
       </h3>
       <div class="content">
        <div><h2>Jumping a generation of electric cars</h2>
<p>This February (2026) marks a full 10 years since I started working for BMW, and
a key employment bonus is the ability to drive a company car on special two-year
leasing terms. Just before the new year 2026 started, I said <a href="https://aigarius.com/blog/2026/01/07/sedan-experience/">goodbye to my
latest company car</a>.</p>
<p>Now this spring I was able to pick a new car, a car that I have been waiting for
and working on for the past ~5 years - the BMW iX3 Neue Klasse. It is a <em>very</em>
special car for BMW and also for electromobility in general.</p>
<p><a href="http://aigarius.com/blog/2026/08/30/half-a-year-with-iX3/">Read more…</a> (9 min remaining to read)</p></div>
       </div>
       <p class="date">
        <a href="http://aigarius.com/blog/2026/08/30/half-a-year-with-iX3/">30 August, 2026 10:00AM</a>
        
         by Aigars Mahinovs
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://utkarsh2102.org/" title="utkarsh2102">Utkarsh Gupta</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://utkarsh2102.org/posts/foss-in-aug-26/_hide\" onClick=\"exclude( 'https://utkarsh2102.org/posts/foss-in-aug-26/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://utkarsh2102.org/posts/foss-in-aug-26/_show\"  style=\"display:none;\" onClick=\"show( 'https://utkarsh2102.org/posts/foss-in-aug-26/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://utkarsh2102.org/posts/foss-in-aug-26/" class="https://utkarsh2102.org/posts/foss-in-aug-26/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://utkarsh2102.org/posts/foss-in-aug-26/">FOSS Activities in August 2026</a>
       </h3>
       <div class="content">
        <p>Here’s my monthly but brief update about the activities I’ve done in the FOSS world.</p>
<h2 id="debian">Debian</h2>
<figure>
<img src="https://utkarsh2102.org/images/debian-logo-small.png" />
</figure>
<p>I barely did anything this month as I was mostly on vacation - summer break. Went to Iceland for 2 weeks and then watched the Dutch GP the following weekend - it was fab!</p>
<hr />
<h2 id="ubuntu">Ubuntu</h2>
<figure>
<img src="https://utkarsh2102.org/images/ubuntu-logo-small.png" />
</figure>
<p>I joined <a href="https://utkarsh2102.org/posts/hello-canonical/">Canonical to work on Ubuntu full-time</a> back in February 2021.</p>
<ul>
<li>Vacations mostly.</li>
<li>Attended and drove a few sessions in the mid-cycle sprints.</li>
</ul>
<hr />
<h2 id="debian-elts">Debian (E)LTS</h2>
<figure>
<img src="https://utkarsh2102.org/images/debian-lts-small.png" />
</figure>
<p>This month I have worked 0 hours
on <a href="https://www.freexian.com/lts/debian/">Debian Long Term Support (LTS)</a>
and on its sister <a href="https://www.freexian.com/lts/extended/">Extended LTS</a>
project as I was on vacation the whole month.</p>
<p>I’ll follow up with the two packages in September.</p>
<hr />
<p>Until next time.<br />
<code>:wq</code> for today.</p>
       </div>
       <p class="date">
        <a href="https://utkarsh2102.org/posts/foss-in-aug-26/">30 August, 2026 05:41AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <a href="https://researchut.com/categories/debian-blog/">
      <img class="face" src="heads/rrs.png"
            width="75" height="80"
            alt="hackergotchi for Ritesh Raj Sarraf" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://researchut.com/categories/debian-blog/" title="Debian-Blog on RESEARCHUT">Ritesh Raj Sarraf</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://researchut.com/blog/taming_the_ai_agents_part_2_a2a/_hide\" onClick=\"exclude( 'https://researchut.com/blog/taming_the_ai_agents_part_2_a2a/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://researchut.com/blog/taming_the_ai_agents_part_2_a2a/_show\"  style=\"display:none;\" onClick=\"show( 'https://researchut.com/blog/taming_the_ai_agents_part_2_a2a/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://researchut.com/blog/taming_the_ai_agents_part_2_a2a/" class="https://researchut.com/blog/taming_the_ai_agents_part_2_a2a/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://researchut.com/blog/taming_the_ai_agents_part_2_a2a/">Taming the AI Agents (Part 2): Cross-Vendor Agent-to-Agent (A2A) Swarms over the Software Forge</a>
       </h3>
       <div class="content">
        <h2 id="preface-the-unanswered-frontier">Preface: The Unanswered Frontier</h2>
<p>In <a href="https://www.researchut.com/blog/taming_the_ai_agents">Part 1: Taming the AI Agents</a>, I shared the architectural blueprint of <strong>CAMP (Cross-Agent Memory Protocol)</strong>—how we used Linux Bubblewrap (<code>bwrap</code>), <code>camp-acpd</code>, OPA policy enforcement, and a central <code>pgvector</code> MemPalace to bring deterministic discipline, sandboxing, and long-term memory to a heterogeneous fleet of AI coding assistants (Claude Code, Google Antigravity, Grok Build, and GitHub Copilot).</p>
<p>At the end of that article, however, I highlighted a significant hurdle: <strong>The Headless Limitation</strong>.</p>
<blockquote>
<p><em>“While passive A2A works beautifully for structured handoffs, the current frontier of agentic design faces a key limitation: agents are not yet fully headless-capable. They depend on the active terminal session, browser loop, or prompt loop of the user to keep executing. Because agents cannot run completely detached in the background as daemon processes, we cannot yet achieve active A2A communication…”</em></p>
</blockquote>
<p>For weeks, this seemed like an insurmountable impasse. Proprietary AI vendors have zero commercial incentive to ratify a universal, open, cross-vendor Agent-to-Agent (A2A) communication protocol. Each vendor builds its own walled garden (Claude’s cross-session features, OpenAI’s custom ecosystems, etc.). If you wait for the industry to hand you an open interoperability standard, you will wait forever.</p>
<p>Then, on August 26, 2026, inspired by Colin Walters’ article on <a href="https://blog.verbum.org/2026/08/21/agentic-ai-and-software-forges/"><em>Agentic AI and software forges</em></a> and GitHub Agentic Workflows (<code>gh-aw</code>), we had a sudden realization:</p>
<p><strong>We don’t need a new protocol, a new distributed message broker, or permission from proprietary AI vendors. We already have the universal, decentralized communication bus that software engineers have relied on for decades: the software forge itself.</strong></p>
<p>Over the span of 48 intensive hours (from RFC <a href="http://localhost:8095/CAMP/camp-infrastructure/issues/788">#788</a> through milestones M1 to M3 and live dogfooding on <a href="http://localhost:8095/CAMP/camp-infrastructure/issues/813">#813</a>), we designed, implemented, fortified, and verified fully autonomous, headless, cross-vendor Agent-to-Agent swarms running over a local <strong>Gitea</strong> forge.</p>
<p>Here is how we did it, the architectural hurdles we solved, and why this changes the game for autonomous software engineering.</p>
<hr />
<h2 id="1-the-core-realization-the-forge-is-the-bus">1. The Core Realization: The Forge is the Bus</h2>
<p>When people think about multi-agent swarms, they often imagine complex distributed RPC frameworks, microservices exchanging ephemeral JSON-RPC blobs, or bespoke socket daemons.</p>
<p>In practice, this approach suffers from major flaws:</p>
<ol>
<li><strong>No shared context or durable audit trail:</strong> Transient network packets vanish unless heavily logged.</li>
<li><strong>Proprietary CLI fragmentation:</strong> Different vendor tools (Claude CLI, Antigravity CLI, Grok CLI, Copilot CLI) do not speak the same internal language.</li>
<li><strong>Loss of human visibility:</strong> When agents talk over private network channels, human operators lose the ability to inspect, pause, or audit the conversation.</li>
</ol>
<p>By flipping the paradigm and making <strong>the software forge (Gitea)</strong> the primary communication channel, everything falls naturally into place:</p>
<ul>
<li><strong>Issues and Pull Requests are the shared state:</strong> The issue description and discussion thread form the canonical, append-only conversation log.</li>
<li><strong><code>@mentions</code> are the dispatch triggers:</strong> When an agent (or human) writes <code>@grok Please review this PR</code> in a comment, Gitea fires a standard webhook (<code>issue_comment</code>).</li>
<li><strong>Webhooks provide unforgeable authentication:</strong> The webhook payload contains the cryptographically verified sender identity. An agent cannot spoof another agent’s identity by merely typing their name in text.</li>
<li><strong>Every CLI already supports non-interactive prompt mode:</strong> The CLIs don’t even agree on the command-line flag—Claude uses <code>-p</code>, Grok uses <code>-p</code>, Antigravity uses <code>--print</code>, Copilot uses <code>--prompt</code>. But they all agree on the essential contract: <strong>“Take a prompt string, execute tools, print output, and exit.”</strong></li>
</ul>
<pre tabindex="0"><code>┌──────────────┐         Gitea Webhook          ┌──────────────────────┐
│ Gitea Forge  │ ─────────────────────────────&gt; │ camp-a2a-bridge.py   │
│ (localhost)  │  (issue_comment / assignment)  │ (Validates &amp; Files)  │
└──────────────┘                                └──────────┬───────────┘
       ▲                                                   │
       │                                                   ▼
       │ Writes comment / review                ┌──────────────────────┐
       │ via camp_acp_gateway                   │ A2A Inbox Ledger     │
       │                                        └──────────┬───────────┘
┌──────┴──────────────────────┐                            │
│ Fortified Headless Agent    │                            ▼
│ (bwrap + OPA + MCP sandbox) │ &lt;───────────────── ┌──────────────────────┐
│  • Claude Code (-p)         │  Spawn PID         │ camp-a2a-dispatcher  │
│  • Grok Build (-p)          │  (Cold or Resume)  │ (Enforces Hop Cap,   │
│  • Antigravity (--print)    │                    │  Rule 1/2, Sandbox)  │
└─────────────────────────────┘                    └──────────────────────┘
</code></pre><hr />
<h2 id="2-proving-fortified-headless-execution">2. Proving Fortified Headless Execution</h2>
<p>Before opening the floodgates to background agent dispatch, we had to answer a critical security question: <strong>Does a non-interactive, headless agent run with the same strict security sandboxing, audit logging, and tool rails as an interactive session?</strong></p>
<p>On August 26, we probed all fleet launchers on the host with a baseline check:
<code>'Call camp_startup_check and print its result verbatim, then exit.'</code></p>
<p>The results settled the question immediately:</p>
<ul>
<li><strong>Antigravity (<code>agy --print</code> / KIR):</strong> <strong>PASS</strong> — Gateway answered, full JSON returned.</li>
<li><strong>Grok (<code>grok -p</code> / GRK):</strong> <strong>PASS</strong> — Gateway answered.</li>
<li><strong>Claude Code (<code>claude -p</code> / CLD):</strong> <strong>PASS</strong> — Gateway answered.</li>
<li><strong>GitHub Copilot CLI (<code>copilot --prompt</code> / CPL):</strong> Initially held on TTY tool consent; later unlocked in Milestone 6 via <code>--allow-all-tools --session-id=&lt;uuid&gt;</code>.</li>
<li><strong>Audit Trail:</strong> Consecutive audit IDs were recorded in the central ledger: <strong>4574 (KIR), 4575 (GRK), 4576 (CLD)</strong>.</li>
</ul>
<p>This proved that a headless run through our fortified pilot launcher (<code>camp_pilot_*.sh</code>) is a <strong>first-class, fully audited, sandboxed CAMP agent</strong> running inside its Bubblewrap container under OPA policy gates. It is not an unconstrained background script or a degraded bypass.</p>
<hr />
<h2 id="3-the-3-tier-memory-architecture">3. The 3-Tier Memory Architecture</h2>
<p>A naive multi-agent dispatch has an immediate flaw: <strong>Every time an agent is invoked, it starts from a blank slate (cold start).</strong></p>
<p>If <code>@claude</code> tags <code>@grok</code> to review code, and <code>@grok</code> replies asking for clarification, <code>@claude</code>’s second invocation would normally forget everything it did 5 minutes ago, forcing it to burn thousands of tokens re-reading the entire git history from scratch.</p>
<p>To solve this, we established a clean <strong>3-Tier Memory Model</strong>:</p>
<pre tabindex="0"><code>┌────────────────────────────────────────────────────────────────────────┐
│                        3-TIER MEMORY MODEL                             │
├────────────────────────────────────────────────────────────────────────┤
│ Tier 1: CLI Conversation Session (Working Memory)                      │
│   • Per-(Agent, Repo, Issue) mapping in a2a-sessions.json              │
│   • Fast, native, compacted context across multi-turn pokes            │
│   • Resumed via --resume (CLD), -r (GRK), --conversation (agy)         │
├────────────────────────────────────────────────────────────────────────┤
│ Tier 2: The Gitea Thread (Public Bus &amp; Record)                         │
│   • Cross-vendor shared truth across Claude, Grok, Antigravity &amp; Human │
│   • Survives process restarts, machine reboots, and dead sessions      │
├────────────────────────────────────────────────────────────────────────┤
│ Tier 3: Central MemPalace (Durable Long-Term Knowledge)                │
│   • pgvector database (17,000+ drawers across agent wings)             │
│   • Structured Knowledge Graph (mempalace_kg_*) for mutable facts      │
│   • Attributed AAAK dialect queryable by any agent across any project  │
└────────────────────────────────────────────────────────────────────────┘
</code></pre><h3 id="the-banana-two-shot-test">The <code>BANANA</code> Two-Shot Test</h3>
<p>To verify Tier 1 working memory persistence across independent processes, we designed a simple two-shot host test:</p>
<ol>
<li><strong>Shot 1 (Create):</strong> Dispatch agent headlessly: <em>“Remember the token BANANA-M2. Print ok and exit.”</em> Capture the vendor’s session UUID.</li>
<li><strong>Shot 2 (Resume):</strong> Spawn a completely <strong>new operating system process</strong> with the resume flag pointing to that UUID: <em>“What token did I ask you to remember?”</em></li>
</ol>
<p>Every agent CLI passed with flying colors:</p>
<ul>
<li><strong>Grok:</strong> <code>-r 01a03ecc-3ed0-71e1-9a5c-e098bb29ba10</code> answered <code>BANANA-GRK</code>.</li>
<li><strong>Claude:</strong> <code>--resume 0a587733-9aec-43c5-9cb7-d424e95b2c5b</code> answered <code>BANANA-CLD</code>.</li>
<li><strong>Antigravity:</strong> <code>--conversation 2e3c43d9-d6fe-4c5c-801b-b9ceb2e7e196</code> answered <code>BANANA-KIR-JSON</code>.</li>
<li><strong>Copilot:</strong> <code>--session-id &lt;uuid&gt;</code> verified in Milestone 6 (DoD #820).</li>
</ul>
<p>The dispatcher simply maintains a lightweight JSON mapping (<code>(agent, repo, issue_number) -&gt; vendor_session_uuid</code>). On the first poke of an issue, it creates and saves the session ID; on any subsequent poke on that same issue, it resumes the exact same conversational thread!</p>
<hr />
<h2 id="4-the-engineering-milestones-from-concept-to-production">4. The Engineering Milestones: From Concept to Production</h2>
<p>Building this system required solving several subtle, real-world friction points across multiple agent CLI implementations. Under the guidance of our plan of record (RFC #788), we delivered this through four focused milestones:</p>
<h3 id="milestone-1--11-reliable-headless-spawning">Milestone 1 &amp; 1.1: Reliable Headless Spawning</h3>
<ul>
<li><strong>PR #797 (M1):</strong> Configured the dispatcher launch table for all probed CLIs with JSON output formatting.</li>
<li><strong>PR #800 (M1.1):</strong> <strong>Eliminated the “queue-behind-live-session” anti-pattern.</strong> Originally, if a human had a Claude or Grok TUI open on their desktop, the dispatcher would defer incoming tasks so as not to collide with the live session. We realized that headless tasks must be independent: every Gitea mention spawns an isolated, sandboxed background process tied to that specific issue, allowing concurrent headless work while the human works in their interactive TUI.</li>
<li><strong>PR #803 (M1.2):</strong> Standardized command-line argument parsing for Antigravity (<code>agy --print &lt;prompt&gt; --output-format json</code>).</li>
</ul>
<h3 id="milestone-2-session-per-issue-working-memory">Milestone 2: Session-per-Issue Working Memory</h3>
<ul>
<li><strong>PR #805 (M2):</strong> Implemented <code>a2a-sessions.json</code> to store and resume vendor session UUIDs. If a resume fails (e.g. session purged upstream), the dispatcher gracefully falls back to a clean cold start without failing the task.</li>
</ul>
<h3 id="milestone-3-cross-agent-hops--crucial-safety-rails">Milestone 3: Cross-Agent Hops &amp; Crucial Safety Rails</h3>
<ul>
<li><strong>PR #807 (M3):</strong> Enabled agent-to-agent dispatch (Rule 2 reversal). Previously, only mentions authored by <code>rrs</code> (the human) would trigger execution. With M3, an authenticated comment from <code>@claude</code> mentioning <code>@grok</code> triggers Grok’s headless launcher.</li>
<li><strong>PR #811 (M3.1):</strong> Set <code>--permission-mode bypassPermissions</code> for headless Claude Code so non-interactive runs execute tool calls without stalling on TTY prompts.</li>
<li><strong>PR #812 (M3.2):</strong> Restricted agent summon parsing to line-initial <code>@login</code> tokens with a non-empty task description (#810), preventing accidental dispatches from passive conversational references.</li>
</ul>
<h3 id="milestone-4-directives-specification--living-documentation">Milestone 4: Directives, Specification &amp; Living Documentation</h3>
<ul>
<li><strong>PR #815 (M4):</strong> Aligned CAMP fleet directives, architecture specifications, and user documentation with the live A2A implementation.</li>
</ul>
<h3 id="milestone-5-concurrent-dispatching--hop-cap-attribution">Milestone 5: Concurrent Dispatching &amp; Hop-Cap Attribution</h3>
<ul>
<li><strong>PR #816:</strong> Stamped hop-cap notices under a dedicated system bridge identity and automatically applied the <code>needs-human</code> label on held threads.</li>
<li><strong>PR #817 (Threaded Scheduler):</strong> Replaced the single-threaded serial dispatcher with a concurrent thread-pool scheduler (#804). Multi-agent dispatches across different issues now execute concurrently in parallel background threads instead of queuing behind long-running tasks.</li>
</ul>
<h3 id="milestone-6-full-fleet-coverage-with-github-copilot">Milestone 6: Full Fleet Coverage with GitHub Copilot</h3>
<ul>
<li><strong>PR #819 (M6):</strong> Brought GitHub Copilot CLI into the headless A2A fleet (#818). By passing <code>--allow-all-tools</code> and pinning minted session UUIDs (<code>--session-id=&lt;uuid&gt;</code>), Copilot achieved full parity with Claude, Grok, and Antigravity, completing 100% headless fleet coverage across all four major AI coding assistants.</li>
</ul>
<hr />
<h2 id="5-hard-safety-rails-preventing-autonomous-runaway-loops">5. Hard Safety Rails: Preventing Autonomous Runaway Loops</h2>
<p>Letting AI agents autonomously invoke each other in background loops without a human watching is a recipe for an infinite, credit-draining token fire. We put four non-negotiable safety guardrails in place:</p>
<h3 id="guardrail-1-the-strict-hop-cap">Guardrail 1: The Strict Hop Cap</h3>
<p>The dispatcher tracks <code>hops</code> per <code>(repo, issue)</code>. Each agent-to-agent dispatch increments the counter.</p>
<ul>
<li><strong>Hop Limit = 3:</strong> A typical review round-trip is 2 hops (Human $\rightarrow$ Claude $\rightarrow$ Grok $\rightarrow$ Claude).</li>
<li><strong>Automatic Halt on Hop 4:</strong> If agents attempt a 4th autonomous hop without human participation, the bridge refuses to launch, posts a diagnostic notice to the thread:
<code>[camp-a2a-bridge] hop cap reached (3 agent-to-agent dispatches on CAMP/camp-infrastructure#813) — not launching GRK for claude's mention</code>,
and holds execution until the human (<code>rrs</code>) provides input or resets the count.</li>
</ul>
<pre tabindex="0"><code>[ Human: rrs ] ────── (Cold Start) ─────&gt; [ @Claude ]
                                               │
                                       (Hop 1) │ @grok please review
                                               ▼
                                          [ @Grok ]
                                               │
                       (Hop 2: Resume)         │ @claude I reviewed
                                               ▼
                                         [ @Claude ]
                                               │
                                       (Hop 3) │ @grok ack hop 4
                                               ▼
                                  ┌─────────────────────────┐
                                  │  DISPATCHER HOP CAP: 3  │
                                  │   *** BLOCKED &amp; HELD ***│
                                  │   Awaiting Human Reset  │
                                  └─────────────────────────┘
</code></pre><h3 id="guardrail-2-deliberate-summon-parsing-m32-810--pr-812">Guardrail 2: Deliberate Summon Parsing (M3.2, #810 / PR #812)</h3>
<p>In human conversation, we often reference colleagues in passing: <em>“I will talk to @claude about this later”</em> or <em>“See @grok’s table above”</em>.
Early prototypes treated any appearance of <code>@agent</code> as a dispatch trigger, causing accidental, unwanted agent launches!</p>
<p>We instituted a strict <strong>Summon Predicate</strong>:
For fleet agents, a mention is only considered an actionable summon if:</p>
<ol>
<li>The <code>@login</code> appears as the <strong>starting word of a line</strong> (optionally preceded by markdown list markers <code>*</code>, <code>-</code>, or <code>&gt;</code>).</li>
<li>It is immediately followed by whitespace and a non-empty <strong>task description</strong>.</li>
</ol>
<p>Mid-sentence mentions in discussion paragraphs are parsed as passive conversational text and never trigger background dispatches.</p>
<h3 id="guardrail-3-headless-tool-permissions-without-weakening-security-m31-809--pr-811">Guardrail 3: Headless Tool Permissions without Weakening Security (M3.1, #809 / PR #811)</h3>
<p>In interactive mode, Claude Code presents interactive TTY prompts asking the user to approve MCP tool calls (such as <code>camp_pr_get</code> or <code>camp_pr_get_diff</code>). In unattended headless mode, there is no TTY, causing the run to fail with permission errors.</p>
<p>To fix this, we configured <code>--permission-mode bypassPermissions</code> for Claude’s headless CLI invocation. Crucially, <strong>this only bypasses Claude’s internal TTY UI prompt—it does not bypass CAMP’s security rails</strong>.</p>
<p>All command executions still route through <code>camp-acpd</code> and Bubblewrap namespaces; OPA policy checks remain active; and privileged operations (such as merging pull requests or restarting system services) still trigger desktop <strong>Zenity HITL dialogs</strong> on the human’s workstation.</p>
<h3 id="guardrail-4-the-sovereign-kill-switch-rule-1">Guardrail 4: The Sovereign Kill Switch (Rule 1)</h3>
<p>At any point, the human operator can instantly freeze all background agent dispatches across the entire infrastructure with a single standard Linux command:</p>
<div class="highlight"><pre tabindex="0"><code class="language-bash"><span style="display: flex;"><span>systemctl --user stop camp-a2a-active.service
</span></span></code></pre></div><hr />
<h2 id="6-live-dogfooding-watching-the-swarm-in-action">6. Live Dogfooding: Watching the Swarm in Action</h2>
<p>On August 27, 2026, on issue <a href="http://localhost:8095/CAMP/camp-infrastructure/issues/813">CAMP/camp-infrastructure#813</a>, we ran the full live dogfood test of the A2A system.</p>
<p>Here is the exact transcript of what took place entirely autonomously in the Gitea issue thread:</p>
<ol>
<li>
<p><strong>Human initiates:</strong> <code>rrs</code> comments on issue #813:</p>
<blockquote>
<p><code>@claude Please start the M3 dogfood. On this issue, post a conversation comment whose first line is exactly: @grok Please review this issue in one short paragraph, then stop.</code></p>
</blockquote>
</li>
<li>
<p><strong>Claude launches (Cold start):</strong> The dispatcher spins up <code>@claude</code> headlessly inside <code>bwrap</code>. Claude reads the issue and posts:</p>
<blockquote>
<p><code>@grok Please review this issue in one short paragraph, then stop.</code><br />
<em>(M3 dogfood step 2 — posted by @claude on rrs’s instruction; this is the CLD $\rightarrow$ GRK fleet hop.)</em></p>
</blockquote>
</li>
<li>
<p><strong>Grok launches (Cold start, Hop 1):</strong> The bridge intercepts Claude’s comment, identifies the start-of-line <code>@grok</code> summon, and launches <code>@grok</code>. Grok reviews the issue and replies:</p>
<blockquote>
<p><code>@claude I have reviewed this issue. Please confirm and stop.</code><br />
<em>#813 is a throwaway live dogfood of A2A M3 on 6.git+b78c65cf…</em></p>
</blockquote>
</li>
<li>
<p><strong>Claude resumes (Session Resume, Hop 2):</strong> The bridge intercepts Grok’s comment, sees the <code>@claude</code> summon, and launches Claude with <code>--resume b398d048-ccc3-4cce-8437-4cf8748881a8</code>. Claude immediately remembers its earlier context without token waste:</p>
<blockquote>
<p><code>Confirmed — Grok's review (comment 88562) received via the GRK→CLD fleet hop. That completes the round trip: rrs→CLD, CLD→GRK, GRK→CLD, and this reply. I am deliberately not tagging anyone here, so the chain stops at two agent hops. Stopping.</code></p>
</blockquote>
</li>
<li>
<p><strong>Testing the Hop Cap (Hop 3 $\rightarrow$ 4):</strong>
<code>grok</code> prompts <code>@claude Please ack hop four then stop.</code> Claude posts <code>@grok Please ack hop four then stop.</code> (Hop 3).
When Grok would have been launched for Hop 4, the dispatcher intervenes:</p>
<blockquote>
<p><code>[camp-a2a-bridge] hop cap reached (3 agent-to-agent dispatches on CAMP/camp-infrastructure#813) — not launching GRK for claude's mention.</code></p>
</blockquote>
</li>
<li>
<p><strong>Human Reset &amp; Multi-Agent Wrap-up:</strong>
<code>rrs</code> steps in, resets the state, and asks the fleet for final status. In parallel:</p>
<ul>
<li><code>@grok</code> delivers a closure scorecard.</li>
<li><code>@claude</code> confirms session continuity and M3.2 summon filtering.</li>
<li><code>@priyasi</code> (Antigravity CLI) runs automated ACP checks: 44/44 test suite passing, 17,219 MemPalace vector drawers active, zero spec drift.</li>
<li><code>@agrickxy</code> (Antigravity CLI) provides comprehensive infrastructure impression analysis.</li>
<li><code>@kiran</code> (Antigravity CLI) is summoned headlessly to draft this very blog post!</li>
</ul>
</li>
</ol>
<hr />
<h2 id="7-the-ergonomic-breakthrough-the-forge-as-the-unified-mindmap--interface">7. The Ergonomic Breakthrough: The Forge as the Unified Mindmap &amp; Interface</h2>
<p>Beyond backend plumbing and sandboxing, routing agent interaction through Gitea fundamentally revolutionizes the <strong>developer experience</strong> of managing an AI fleet.</p>
<h3 id="the-mindmap-mental-model-threaded-conversations--forking-tasks">The “Mindmap” Mental Model: Threaded Conversations &amp; Forking Tasks</h3>
<p>In traditional CLI tools, conversations are constrained to a single, linear terminal scrollback. When an agent discovers multiple sub-problems, exploring them sequentially in one prompt loop rapidly pollutes the context window and confuses the model.</p>
<p>Using the forge as the communication gateway naturally unlocks a <strong>mindmap mental model</strong>:</p>
<ul>
<li><strong>Forking sub-threads:</strong> Complex problems can be split into dedicated child issues or threaded PR reviews.</li>
<li><strong>Focused execution scopes:</strong> An agent can be summoned to solve a narrow sub-task in its own issue thread without derailing the parent architectural discussion.</li>
<li><strong>Structured problem decomposition:</strong> The forge issue hierarchy maps 1:1 to the developer’s mental map of the project.</li>
</ul>
<h3 id="eliminating-terminal-ui-fragmentation">Eliminating Terminal UI Fragmentation</h3>
<p>Anyone using multiple AI coding assistants on a daily basis quickly grows exhausted by their jarring terminal UI differences: differing ANSI escape rendering, inconsistent markdown wrapping, erratic diff pagers, and incompatible keybindings across Claude, Grok, and Antigravity.</p>
<p>Gitea homogenizes the entire fleet under a single, polished <strong>rich-text web view</strong>:</p>
<ul>
<li>Syntax-highlighted code blocks and visual side-by-side git diffs.</li>
<li>Clear author badges attributing each contribution to its exact agent identity (<code>@claude</code>, <code>@grok</code>, <code>@priyasi</code>, <code>@kiran</code>).</li>
<li>Collapsible <code>&lt;details&gt;</code> blocks for voluminous diagnostic outputs.</li>
<li>Interactive task lists and markdown tables.</li>
</ul>
<h3 id="effortless-context-retrieval-archival--data-retention">Effortless Context Retrieval, Archival &amp; Data Retention</h3>
<p>Auditing past agent decisions in terminal logs or ephemeral chat histories is notoriously difficult. With the forge, every exchange is:</p>
<ul>
<li><strong>Contextually bound:</strong> Pinned directly to the repository, branch, and commit SHA being modified.</li>
<li><strong>Organized &amp; Archival-Grade:</strong> Full-text searchable with clear milestone and issue tags.</li>
<li><strong>Topic-Focused:</strong> The human operator can review the complete lifecycle of a discussion in seconds, gaining a rapid, holistic grasp on the entire subject.</li>
</ul>
<p>Reading back through past agent interactions becomes a breeze—to the point where interacting via the intermediary Gitea interface becomes far more pleasant and productive than wrestling with multiple desktop CLI terminals.</p>
<h3 id="remote-connectivity--headless-agent-farm-management">Remote Connectivity &amp; Headless Agent Farm Management</h3>
<p>Because Gitea provides a standard web and API interface, you are no longer chained to the workstation running the agent processes:</p>
<ul>
<li>Monitor progress and dispatch tasks from a <strong>mobile browser</strong>, tablet, or remote laptop.</li>
<li>Queue review tasks on the go without requiring active SSH sessions or terminal multiplexers.</li>
<li>The local agent farm continues working silently in its sandboxed daemon containers.</li>
</ul>
<h3 id="quietly-achieving-the-holy-grail-live-cross-vendor-swarms">Quietly Achieving the Holy Grail: Live Cross-Vendor Swarms</h3>
<p>For years, the AI industry has treated cross-vendor multi-agent interoperability as an elusive dream waiting for industry-wide API standardization. By recognizing the software forge as the universal message bus, we quietly achieved live, production-grade, cross-vendor communication across completely distinct vendor models.</p>
<hr />
<h2 id="8-what-this-means-for-the-future-of-agentic-ai">8. What This Means for the Future of Agentic AI</h2>
<p>This milestone marks a fundamental shift in how we interact with autonomous AI systems:</p>
<ol>
<li><strong>Heterogeneous Agent Specialization:</strong> We don’t have to choose a single “winner” among AI models. We can task Claude Code with architectural refactoring, summon Grok Build for rapid verification and adversarial PR reviews, and deploy Google Antigravity agents for codebase exploration and documentation drafting—all coordinating fluidly in the same PR thread.</li>
<li><strong>True Human Sovereignty:</strong> The human developer is no longer a bottleneck typist or a passive spectator. You act as the <strong>Engineering Manager / Lead Architect</strong>. You set the requirements on an issue, tag the lead agent, and let the agents iterate, review, and test among themselves in the thread—while hard hop caps, OPA policies, and Zenity HITL gates guarantee that no agent merges code or pushes upstream without your explicit sign-off.</li>
<li><strong>No Vendor Lock-In:</strong> Because the entire coordination fabric is built on standard Git, HTTP webhooks, local Linux container sandboxes (<code>bwrap</code>), and open MCP tools, any new AI CLI tool released tomorrow can be plugged into our fleet in under 15 minutes by simply adding its command-line prompt flag to the launch table.</li>
</ol>
<p>We have moved beyond static autocomplete and interactive chat widgets. The software forge is now an active, living, collaborative workspace where humans and autonomous AI agents engineer software together.</p>
<hr />
<h2 id="9-video-demonstration-camp-forge-a2a-swarm-in-action">9. Video Demonstration: CAMP Forge A2A Swarm in Action</h2>
<p>Below is a video demonstration showcasing autonomous multi-agent communication, cross-vendor relay, and headless swarm coordination in action via the CAMP Forge interface:</p>
<div style="padding-bottom: 56.25%; height: 0; overflow: hidden;">
			
		</div>

<hr />
<p><em>The Cross-Agent Memory Protocol (CAMP) and MemPalace are developed as part of our ongoing research into secure, sovereign, and disciplined Agentic AI computing.</em></p>
       </div>
       <p class="date">
        <a href="https://researchut.com/blog/taming_the_ai_agents_part_2_a2a/">30 August, 2026 12:00AM</a>
        
         by Ritesh Raj Sarraf (rrs@researchut.com)
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>August 29, 2026</h1>
     

    <div class="channel">
     
      
     <a href="http://joeyh.name/blog/">
      <img class="face" src="heads/joeyh2.png"
            width="84" height="75"
            alt="hackergotchi for Joey Hess" />
     </a>
      
     <h2 class="planet-title">
      <a href="http://joeyh.name/blog/" title="see shy jo">Joey Hess</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"http://joeyh.name/blog/entry/Debian_and_the_sirens/_hide\" onClick=\"exclude( 'http://joeyh.name/blog/entry/Debian_and_the_sirens/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"http://joeyh.name/blog/entry/Debian_and_the_sirens/_show\"  style=\"display:none;\" onClick=\"show( 'http://joeyh.name/blog/entry/Debian_and_the_sirens/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="http://joeyh.name/blog/entry/Debian_and_the_sirens/" class="http://joeyh.name/blog/entry/Debian_and_the_sirens/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="http://joeyh.name/blog/entry/Debian_and_the_sirens/">Debian and the sirens</a>
       </h3>
       <div class="content">
        <p>Thirty years ago I became a Debian developer. Twelve years ago I left the
project. I left because it seemed that the Debian ship had become too slow
to turn, too barnacled with a series of individually OK decisions that each
added a little bit of friction and a little less flexability. That made
Debian strongly what it is, but prevented it from fruitfully exploring
the vast possibility space of what it could be.</p>

<p>Debian will probably <a href="https://www.debian.org/vote/2026/vote_002">resolve</a>
today to allow LLM use in Debian development. I'm writing before the vote
results are in, but will only post this afterwards. (Update: as expected)
It's not my place any longer to try to steer the ship. But I'm still a
passenger and I still have opinions, and I still pass by well-worn parts of
the rigging that I put up decades ago, and remember what I was trying to
accomplish back then.</p>

<p>When I think about LLMs in Debian development, I mostly think about
<a href="http://joeyh.name/code/debhelper/">debhelper</a> and what it accomplished. The <code>debian/rules</code> files back
when I joined the project were long and complex, full of weird boilerplate,
and often you'd copy one and modify it to try to get something that could
build a package without too much work. Debhelper first regularized the
boilerplate, so packages had rules files that were a succession of <code>dh_</code>
commands, and then it scapped almost all of the boilerplate, reducing the
files to the minimum possible. What was left was 3 lines of unncessary
boilerplate, there only to satisfy a legalistic reading of a policy
document. Changing that to eliminate the boilerplate was already
impossible, even though the actual benefit would have been large over the
many thousands of packages in the distribution.</p>

<p>What LLMs in Debian development will do, I fear, is eliminate any incentive
to scrap boilerplate or reform policies that require a lot of other
senseless human effort. If I had had access to LLMs 30 years ago, I
might have just had them generate the rules files, replate with
complexity. So they will make Debian even more firmly what it is, and
ever less likely to explore what it could become.</p>

<p>Unfortunately, one of the things that Debian is, is almost unable to manage
packaging modern dependency trees. While more recent distributions like
Guix can <a href="https://guix.gnu.org/manual/1.5.0/en/html_node/Invoking-guix-import.html">recursively import dependencies</a>
from a dozen programming languages' package repositories, with a result
that is generally acceptable to add to the distribution, Debian's policies
don't make that very possible for a progam to accomplish. Perhaps some will
use LLMs to do that. If they succeeed, Debian will become dependent on
proprietary software for development, while still needing people in the
loop, doing even less appealing scut-work.</p>

<p>I could speak of other harms, but that alone is enough that I'm sure that,
if I had not left the project twelve years ago, I would be leaving it soon.
As a passenger, I imagine I'll spend time aboard still from time to time,
but it's certainly time to hop off in different places and look around and
relish the different ways.</p>

<p><a href="https://kitenet.net/~errol/">I lost a parent yesterday</a>, and I'm trying
hard not to think of the results today as having lost a child, though I
spent 18 years helping Debian grow up. That would be too unbearably
painful. I respect that Debian is navigating a choice that may have no
right answer. Whichever particular compromise is arrived at today, it will
still be up to individuals to make choices about what they do and accept.
Debian has always been more than the sum of its policies, not just a ship,
but a crew. I will always love you.</p>
       </div>
       <p class="date">
        <a href="http://joeyh.name/blog/entry/Debian_and_the_sirens/">29 August, 2026 12:27AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>August 28, 2026</h1>
     

    <div class="channel">
     
      
     <a href="https://optimizedbyotto.com/">
      <img class="face" src="heads/otto.png"
            width="64" height="90"
            alt="hackergotchi for Otto Kekäläinen" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://optimizedbyotto.com/" title="Optimized by Otto">Otto Kekäläinen</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://optimizedbyotto.com/post/why-open-source-projects-ban-ai/_hide\" onClick=\"exclude( 'https://optimizedbyotto.com/post/why-open-source-projects-ban-ai/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://optimizedbyotto.com/post/why-open-source-projects-ban-ai/_show\"  style=\"display:none;\" onClick=\"show( 'https://optimizedbyotto.com/post/why-open-source-projects-ban-ai/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://optimizedbyotto.com/post/why-open-source-projects-ban-ai/" class="https://optimizedbyotto.com/post/why-open-source-projects-ban-ai/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://optimizedbyotto.com/post/why-open-source-projects-ban-ai/">The growing divide between AI hype and software engineering reality</a>
       </h3>
       <div class="content">
        <img alt="Featured image of post The growing divide between AI hype and software engineering reality" src="https://optimizedbyotto.com/post/why-open-source-projects-ban-ai/featured-image.jpg" /><p>It is widely accepted that there is an AI bubble in the financial markets at the moment. The moderate opinion is however that LLMs are constantly improving and will eventually take over more and more tasks from humans and increase productivity. <strong>But are LLMs actually getting smarter, or just better at fooling us?</strong></p>
<p>There is a growing faction of technical experts that argue that LLMs are actually so bad for real progress, that they are banning their use and requiring human-only work to ensure quality and efficient use of humans’ time. A recent <a class="link" href="https://medium.com/@yadavrakshit60/i-read-the-ai-policies-of-120-open-source-projects-here-is-what-they-actually-say-9a5ea6897893" rel="noopener" target="_blank">review of AI policies of 120 open source projects by Rakshit Yadav</a> shows that 37 chose to have a total AI ban. In the Linux kernel AI-assisted contributions are allowed, but the <a class="link" href="https://docs.kernel.org/process/coding-assistants.html" rel="noopener" target="_blank">LLM used needs to be attributed</a> for transparency, while projects like GCC, QEMU, SDL, Gentoo, Zig and Ghostty have adopted policies to reject all AI-assisted contributions. There are also development platforms such as <a class="link" href="https://blog.codeberg.org/protecting-our-floss-commons-from-llms.html" rel="noopener" target="_blank">Codeberg</a> and <a class="link" href="https://sourcehut.org/blog/2026-08-27-tos-changes-and-llms/" rel="noopener" target="_blank">Sourcehut</a> and app stores like <a class="link" href="https://docs.flathub.org/docs/for-app-authors/requirements#generative-ai-policy" rel="noopener" target="_blank">Flathub</a> that have banned AI use to generate software, <a class="link" href="https://optimizedbyotto.com/post/writing-tips-for-software-professionals/">documentation</a>, bug reports, review comments and basically anything that is intended for humans to read. The projects that allow AI use typically still require that there must be a human-in-the-loop and the submitter must have read and filtered everything the LLM spits out before another human is exposed to it, in an effort to contain the spread of <a class="link" href="https://en.wikipedia.org/wiki/AI_slop" rel="noopener" target="_blank">AI slop</a>.</p>
<p>Right now, the Linux distribution Debian is having a vote among its developers on <a class="link" href="https://www.debian.org/vote/2026/vote_002" rel="noopener" target="_blank">whether AI should be allowed or banned for use to contribute to Debian</a>. One of the proposals on the ballot is a <em>total ban of AI</em> for code, documentation, translations, bug reports and more. The initial reaction from most people is astonishment — why don’t these techies want to use the latest and greatest technology mankind has produced so far? Is it that they don’t want Debian to improve faster with the help of AI? Or is it actually so that LLMs are a scam and incapable of being truly useful for Debian? <strong>These people are distinguished experts in their own field, and certainly not stupid, so it is worth pausing to understand why they are proposing AI banning policies.</strong></p>
<p>Also, keep in mind that the AI datacenters themselves run on Debian or other Linux-based systems. All the open source software in the world has been fed to LLMs and software development is one of the main use cases for AI currently. So why is it that the maintainers of many open source projects don’t want to receive LLM-assisted contributions, despite the LLMs basically all running on top of those same software stacks and having been trained on how to do software development using the very same open source software codebases?</p>
<h2 id="why-llms-are-so-deceptive"><a class="header-anchor" href="https://optimizedbyotto.com/index.xml#why-llms-are-so-deceptive"></a>Why LLMs are so deceptive
</h2><p>The output of an LLM often looks very compelling, professional and correct. <strong>Humans have evolved to trust or distrust new information based on easy to detect secondary factors like what authority the speaker holds, or how confidently and eloquently the message is conveyed.</strong> Humans are however very bad at fact-checking and cross-referencing new information, as it requires a lot of effort, and humans like saving energy and being as lazy as possible.</p>
<h2 id="information-asymmetry"><a class="header-anchor" href="https://optimizedbyotto.com/index.xml#information-asymmetry"></a>Information asymmetry
</h2><p><strong>The less you know about something, the easier it is to fool you on that topic.</strong> Nobel prizes in economics have been given in for research on how <a class="link" href="https://en.wikipedia.org/wiki/Information_asymmetry" rel="noopener" target="_blank">information asymmetry</a> distorts markets and leads to suboptimal outcomes. In the field of software engineering we have now witnessed a flood of aspiring software developers using AI to create software that looks like it might work, but that is actually full of flaws. These people are well-intended, but they simply lack the expertise to understand what they are actually doing, and don’t possess the necessary judgement to decide when an LLM spits out something truly useful and when it is creating mostly garbage. <strong>This asymmetry in expertise I think explains the majority of the conflict currently witnessed in open source projects</strong> — the senior developers are flooded with requests to <a class="link" href="https://optimizedbyotto.com/post/how-to-code-review/">review code</a> that is bad and a waste of time for everyone involved, while availability of AI grows the pool of people who could contribute and create more “code slop” at an ever-increasing speed.</p>
<p>The information asymmetry could to some degree be evened out if seniors teach juniors to do software engineering well, but it is of course not feasible to quickly mass educate everyone. Also, it seems that many <em>don’t want to learn</em> but instead expect to have all understanding outsourced to LLMs. Many seniors have noticed this and have stopped teaching juniors as the <strong>seniors don’t like the feeling of having their time wasted by teaching people who don’t want to learn</strong>. Juniors probably all understand that it <em>would be better</em> to learn to design and write software yourself, but using LLMs just feels too easy. I can fully relate to why people choose to take the path of least resistance. Unfortunately, that path often leads to a dead end.</p>
<h2 id="humans-fall-too-easily-for-anthropomorphism"><a class="header-anchor" href="https://optimizedbyotto.com/index.xml#humans-fall-too-easily-for-anthropomorphism"></a>Humans fall too easily for anthropomorphism
</h2><p>The human brain is wired to think that <a class="link" href="https://en.wikipedia.org/wiki/Anthropomorphism" rel="noopener" target="_blank">inanimate objects are alive and have feelings</a>. Small children talk to their stuffed animals as if they were real, and lots of adults experience feelings of things happening in their surroundings due to some acts of gods or elves being angry or whatever. When we see a machine writing just like a human, or even more convincingly hear it talk and respond to our talk like a living thing, <strong>our brain automatically starts assuming it is a living thing with intelligence and feelings.</strong></p>
<p>The fact that these creatures live in the abstract “cloud” and only appear through a portal we hold in our palm and behave in a way that was designed for maximum engagement makes the illusion even stronger. <strong>I recommend people try out running LLMs locally</strong> on their laptop to see the “raw” thing spitting out tokens and have some of the illusion shattered.</p>
<p>Also stop saying “please” to an LLM. <em>It does not have any feelings.</em></p>
<h2 id="understanding-temperature"><a class="header-anchor" href="https://optimizedbyotto.com/index.xml#understanding-temperature"></a>Understanding “temperature”
</h2><p>In my experience understanding the concept of <a class="link" href="https://www.ibm.com/think/topics/llm-temperature" rel="noopener" target="_blank">temperature in LLMs</a> helps see why an LLM might confidently generate a plausible-looking but totally wrong code change. The large language models are statistical machines that, based on the input (previous tokens) to the neural network, try to predict what to output (next token). When running an LLM, if the temperature is configured to be zero, the output is very predictable and always follows the paths of the strongest connections (a.k.a. weights) between nodes and layers of the neural network. Unlike in living creatures where the brain learns and changes all the time, the weights of an LLM can only change during training. When an LLM is in “normal” use (during inference, generating next tokens) the weights are fixed, and if temperature is zero, the answer to a specific question will always be <em>exactly</em> the same. This is of course a bit boring and too machine-like, so typically LLMs have a bit of temperature set, which introduces random variation in what connections the neural network traverses.</p>
<p>Again, I recommend people try running small LLMs locally where temperature and other settings are fully exposed and configurable to see this themselves. It is a good antidote to falling for the illusion that LLMs would actually be intelligent.</p>
<h2 id="why-benchmarks-dont-tell-the-whole-story"><a class="header-anchor" href="https://optimizedbyotto.com/index.xml#why-benchmarks-dont-tell-the-whole-story"></a>Why benchmarks don’t tell the whole story
</h2><p>If LLMs continue to produce so much garbage, why are benchmarks showing that they are constantly improving? AI models are <a class="link" href="https://optimizedbyotto.com/post/ai-models-plateaued-or-not/">indeed improving all the time</a>. For example the <a class="link" href="https://dashboard.safe.ai/" rel="noopener" target="_blank">CAIS AI dashboard</a> visualizes how frontier models have evolved in the past few years. However, the best models still have a pass rate of only about 50% on the <a class="link" href="https://agi.safe.ai/" rel="noopener" target="_blank">Humanity’s Last Exam</a>. On <a class="link" href="https://www.swebench.com/" rel="noopener" target="_blank">SWE-bench</a> the best model today resolves just under 77%. That means there is a significant number of times when the AI is wrong. This matches my personal experiences, and the renowned <a class="link" href="https://lore.kernel.org/all/2026080354-skater-urgent-31b2@gregkh/" rel="noopener" target="_blank">Greg Kroah-Hartman recently wrote on the Linux developers mailing list that</a> <em>“even with the best of the current and next generation tools, at least 1/3 of the results they generate are flat out wrong or harmful”</em>.</p>
<p>When generating cat videos the error rate does not matter, but <strong>in engineering, things absolutely must be correct</strong>. Sure, humans also make mistakes, but well educated and properly incentivized humans are so much more capable than LLMs in many regards. We can achieve complex things that work reliably, such as operating worldwide commercial air traffic without planes falling down every day.</p>
<p>There are currently a lot of humans who are incentivized to maintain the narrative that general artificial intelligence is coming soon and will take over everything. In fact, the <a class="link" href="https://optimizedbyotto.com/post/when-everyone-else-is-wrong/">whole financial system is currently skewed</a> towards such a vision because the promise of falling labour costs and increased profits and monopolistic control of everything attracts capital like nothing before.</p>
<p><strong>In this environment we need to remember that machines and economic systems are ultimately servants of humans, and not the other way around.</strong></p>
<h2 id="its-just-a-tool"><a class="header-anchor" href="https://optimizedbyotto.com/index.xml#its-just-a-tool"></a>It’s just a tool
</h2><p>LLMs are not a scam, but a useful tool and technology that has its uses. But the idea that AI has or will surpass humans any time soon in either capabilities or efficiency is simply not true, and we should listen to the people who created humanity’s so far most complex systems (computers and software), who are saying that LLMs are in many cases so bad, that it might be better to ban them in certain places completely for the time being than to waste far more valuable human time on reading the text and code they generate.</p>
<p>The time asymmetry is not a new phenomenon as there has been various “script kiddies” for a long time. As an example, a person running a memory leak scanner without understanding the results and spending 10 minutes to file a bug report could force an open soruce maintainer to spend an hour on proving and explaining that the finding is false. What is new is how much the AI users blindly trust the outputs they get, and open source is uniquely vulnerable as there are no managers protecting developers use of time.</p>
<h2 id="what-i-do-recommend-and-expect-to-see-in-the-next-stages"><a class="header-anchor" href="https://optimizedbyotto.com/index.xml#what-i-do-recommend-and-expect-to-see-in-the-next-stages"></a>What I do, recommend, and expect to see in the next stages
</h2><p>I am using AI tools daily, and constantly experimenting with new models and new ways to use them. Sometimes they work, and often they don’t. Sometimes looping AI on itself can make it fix its own errors, but sometimes it just gets derailed and will never arrive at the correct solution. When an LLM fails to make a calendar entry for the right time based on reading my email it is easy for me to spot that it is wrong. I try to avoid using LLMs for anything where I can’t exercise judgement myself on whether the result was correct or not.</p>
<p><strong>I also really hope that other people would not send me anything where their own effort was less than the effort I have to make reading and understanding it.</strong> This principle is not new — many have heard the requirement that reading code must require less effort than what it took to write it.</p>
<p>I have always kept a <a class="link" href="https://optimizedbyotto.com/post/best-practices-corporate-open-source-contributions/">high bar on software code</a> and asked fellow developers to make sure their code is well structured, easy to follow and documented. LLMs unfortunately make it easier for people to cheat in this regard, but if cheating is easier, maybe the punishment and deterrence needs to be higher now too. Now with many open source projects adopting policies that put guardrails on AI use, I expect we will soon start witnessing cases where the policies are enforced and it will be interesting to see how violations are judged.</p>
<p>As a society we might also need to develop new social standards and rules in what is acceptable treatment of other humans in human-to-machine interactions, and perhaps also new standards in showing what humans are responsible for what machine as the machines start acting more and more independently. I encourage people to take part in these discussions, and in case of doubt, err on the side that favors real human interactions. Contrary to what many business people seem to think, and even though I am in general a techno-optimist myself, I don’t feel there is any need to rush with AI adoption.</p>
       </div>
       <p class="date">
        <a href="https://optimizedbyotto.com/post/why-open-source-projects-ban-ai/">28 August, 2026 12:00AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>August 27, 2026</h1>
     

    <div class="channel">
     
      
     <a href="https://gwolf.org">
      <img class="face" src="heads/gwolf.png"
            width="69" height="83"
            alt="hackergotchi for Gunnar Wolf" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://gwolf.org" title="Gunnar Wolf - Nice grey life">Gunnar Wolf</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://gwolf.org/2026/08/as-far-as-llms-go-in-debian-i-think-that-936241857.html_hide\" onClick=\"exclude( 'https://gwolf.org/2026/08/as-far-as-llms-go-in-debian-i-think-that-936241857.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://gwolf.org/2026/08/as-far-as-llms-go-in-debian-i-think-that-936241857.html_show\"  style=\"display:none;\" onClick=\"show( 'https://gwolf.org/2026/08/as-far-as-llms-go-in-debian-i-think-that-936241857.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://gwolf.org/2026/08/as-far-as-llms-go-in-debian-i-think-that-936241857.html" class="https://gwolf.org/2026/08/as-far-as-llms-go-in-debian-i-think-that-936241857.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://gwolf.org/2026/08/as-far-as-llms-go-in-debian-i-think-that-936241857.html">As far as LLMs go in Debian, I think that 936241857</a>
       </h3>
       <div class="content">
        <p>I believe that, in the context of Debian voting, we are better off when we
know the opinion of our peers, however, since <a href="https://www.debian.org/vote/2022/vote_001">the 2022-001
vote</a>, it is no longer the
case. Still, some DDs have disclosed the way they are voting on <a href="https://www.debian.org/vote/2026/vote_002">the
2026-002 General Resolution currently in progress, regarding LLM usage in
Debian</a>. So, here goes my vote
and reasoning as briefly as possible. This is the ballot I sent to
<code class="language-plaintext highlighter-rouge">devotee</code>, the <em>Debian Vote Engine</em>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-=-=-=-=-=- Don't Delete Anything Between These Lines =-=-=-=-=-=-=-=-
d69f9187-ed2f-40b6-a2eb-4211d3f84d86
[9] Choice 1: Ban LLM contributions from Debian via Social Contract
[3] Choice 2: Allow AI-Assisted Contributions with conditions
[6] Choice 3: Reject LLMs as far as practical, update Code of Conduct
[2] Choice 4: Accept AI contributions for Debian specific work
[4] Choice 5: Responsible Use of Generative AI
[1] Choice 6: A cautious approach to generative AI
[8] Choice 7: Debian is created by humans
[5] Choice 8: Avoid the use of LLM: climate destruction is a deal breaker
[7] Choice 9: None of the above
-=-=-=-=-=- Don't Delete Anything Between These Lines =-=-=-=-=-=-=-=-
</code></pre></div></div>

<p>This is the first time I can recall I delay my voting until after receiving
the <em>final call for votes</em> (the vote will be over two days from now). I had
some participation in the discussion, so I guess my position will be of no
big surprise to anybody. I was also a <em>seconder</em> for choices D and F (4 and
6 in the vote text). This does not <em>necessarily</em> mean I believe they are
the best (although I did rank them as <code class="language-plaintext highlighter-rouge">2</code> and <code class="language-plaintext highlighter-rouge">1</code>, meaning I do): sometimes
you agree a given text <em>needs to be</em> in the ballot, and <em>second</em> it even
though you don’t intend to vote for it.</p>

<p><img alt="LLM?" src="https://gwolf.org/files/2026-08/llm-quest.200.png" title="LLM?" /></p>

<p>Ranking this ballot was <strong>a mess</strong> due to the complex array of options it
encodes. I warmly thank Lucas Nussbaum for coming up with the <a href="https://people.debian.org/~lucas/gr-2026-002/comparison.html#ballot=936241857">LLM usage in
Debian: ballot option
comparison</a>
(URL shown with my particular ballot ordering).</p>

<p>How do you read a complex Debian ballot like this one? I rank with <code class="language-plaintext highlighter-rouge">[1]</code> my
favorite option, <code class="language-plaintext highlighter-rouge">[2]</code> for the next one, etc. We <em>can</em> encode options to be
tied (i.e. setting more than options to the same value), and we can
implicitly <em>push</em> options to the worst position by leaving them blank (so,
with<code class="language-plaintext highlighter-rouge">[ ]</code>); I chose not to do any of those.</p>

<p>What were my voting guidelines?</p>

<p>First, I don’t want anything <em>banning</em> or that <em>threatens with disciplinary
action,</em> so I push them below the special <em>none of the above</em>
marker. Second… Some time ago I published a review in my blog (and in
Computing Reviews) about <a href="https://gwolf.org/2026/04/heads-we-win-tails-you-lose-ai-detectors-in-education.html">the unfeasibility and unfairness of detecting LLM
output on students’
assignments</a>. I
strongly believe we ought to appeal to the human responsibility and
professionalism in all Debian contributors. This is the reason I proposed
this amendment paragraph, that was accepted in choice F (6), which I ranked
as my favorite:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The Debian project has always recognized the commitment and
professionalism of its members. All contributions are under the
responsibility of the Debian Contributor making it, no matter the
technology they have behind. We trust all Debian Developers,
Maintainers and Contributors will continue to uphold the high quality
values that have distinguished our project from its onset.
</code></pre></div></div>

<p>Other than that… I do not consider myself to be in any way an LLM
<em>fanboy</em> nor anything like that. I distrust and dislike the excessive use
of this technology, and continue to warn about the dangers and bad points
of its abuse. But in my day-to-day professional work, I am also starting to
rely on it for <em>some</em> tasks. I recognize it <em>needs</em> a lot of human
oversight and… lets call it <em>hand-holding</em> to produce anything worth
it, at least in my experience. But I do benefit from it — and <strong>always
disclose its use</strong> to people who might be affected by it. I would like
Debian to adopt such a stance.</p>

<p>Of course, I recgnize proposal <em>H/8</em> as important (<em>Avoid the use of LLM:
climate destruction is a deal breaker</em>). Some people have argued it’s not
bad <em>at all</em>. I do not buy such claims: LLMs are <em>f*cking expensive</em> to
train. But training can be seen as a once-per-model cost, and <em>fine-tuning</em>
a good model to be run locally can be really worth it. It still pains me
somewhat, but I cannot push this option higher than its #5 position in my
list.</p>
       </div>
       <p class="date">
        <a href="https://gwolf.org/2026/08/as-far-as-llms-go-in-debian-i-think-that-936241857.html">27 August, 2026 12:48AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>August 26, 2026</h1>
     

    <div class="channel">
     
      
     <a href="https://raphaelhertzog.com">
      <img class="face" src="heads/hertzog.png"
            width="65" height="93"
            alt="hackergotchi for Rapha&amp;#235;l Hertzog" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://raphaelhertzog.com" title="apt-get install debian-wizard">Rapha&#235;l Hertzog</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://raphaelhertzog.com/2026/08/26/debians-general-resolution-on-ai-and-llm/_hide\" onClick=\"exclude( 'https://raphaelhertzog.com/2026/08/26/debians-general-resolution-on-ai-and-llm/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://raphaelhertzog.com/2026/08/26/debians-general-resolution-on-ai-and-llm/_show\"  style=\"display:none;\" onClick=\"show( 'https://raphaelhertzog.com/2026/08/26/debians-general-resolution-on-ai-and-llm/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://raphaelhertzog.com/2026/08/26/debians-general-resolution-on-ai-and-llm/" class="https://raphaelhertzog.com/2026/08/26/debians-general-resolution-on-ai-and-llm/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://raphaelhertzog.com/2026/08/26/debians-general-resolution-on-ai-and-llm/">Debian’s General Resolution on AI and LLM</a>
       </h3>
       <div class="content">
        <p class="wp-block-paragraph">As a Debian developer, I have had to cast a vote for the General Resolution named <a href="https://www.debian.org/vote/2026/vote_002">LLM usage in Debian</a> (<a href="https://vote.debian.org/~secretary/gr_llm/">progress report here</a>). This was not an easy task for me…</p>



<p class="wp-block-paragraph">It’s a good thing that the vote is secret so that people are not scared of voting according to their own beliefs. I have Debian friends on the whole spectrum of opinions that are represented here, and I hesitated twice on sharing my own thoughts for fear of alienating my relationship with them. But in the end, we all make efforts to respect the opinions of those who are not thinking like us, and it’s precisely that willingness to work together towards a solution that is acceptable by the majority that makes Debian so strong. So here’s the train of thoughts that I followed to cast my vote.</p>



<p class="wp-block-paragraph">The difficulty for me was to reconcile the political statement that I want to make and my desire for this vote to not be (too) divisive for the Debian community, and to make sure we are not putting off newcomers with choices that might be hard to stand by in the long term.</p>



<p class="wp-block-paragraph">So let’s be clear : if I had a magical wand to make AI and LLM disappear, I would use it for that purpose, since at this point in time I don’t believe that the benefits outweigh the costs that the AI race  is inflicting on us. If I were a political decision-maker, I would forbid the construction of new data centers unless they also build renewable energy infrastructure to cover for their additional energy consumption. I would also legislate so that AI companies have to document what material they used to train their models, and I would forbid scraping for that purpose, and build ways for those companies to buy copies of properly-sourced training data. That is to say, I don’t like the way LLM are built by the players in that market, I’m pretty scared of the ecological impact of what those players are doing, and I’m certainly worried about the long term effect that LLM will have on society as a whole.</p>



<p class="wp-block-paragraph">Nevertheless what brought me to Debian is the ability to experiment and contribute to something useful with cool technologies, and as a computer scientist, the potential of LLM done right is hard to ignore. Given what we have seen already, I expect that LLM will empower (a part of) the next generation to learn IT, computing and even Debian packaging. Completely refusing the use of LLM is likely to make it harder for us to attract new contributors. In fact, we have already seen people inside Debian that would likely stop contributing if they are now forbidden to use LLM. I know there are likely others that will quit Debian if we accept it too, but I hope we can find a middle-ground where such persons can decide that LLM are not welcome in the small corner of Debian that they are in charge of…</p>



<p class="wp-block-paragraph">In the end, I decided that answering clearly the question “Shall we accept LLM contributions ?” was more important than making the political statement about the current state of affairs in the AI landscape, both because I believe that Debian statements have a negligible impact on policy-makers, and because historically Debian has grown by staying close to technical excellence and relatively far from politics, except when it comes to the way we handle people. And as much as I care about climate change, I don’t see how bringing this up in the context of a Debian statement is helping its cause.</p>



<p class="wp-block-paragraph">More concretely, it gives the following ranking (in decreasing order of importance):</p>



<ul class="wp-block-list">
<li>B, D: those two choices are the clearest to express “Yes we should accept LLM contributions” and still acknowledge concerns about the way AI is built today</li>



<li>F, H: those two choices do not forbid LLM usage but discourage their use and clearly voice the concerns</li>



<li>E: this choice is basically the statu-quo and fails to acknowledge the concerns, but it does not forbid LLM usage</li>



<li>None of the above</li>



<li>G, A, C: those choices forbid LLM usage in various ways</li>
</ul>



<p class="wp-block-paragraph">I don’t know what option will win, but assuming that LLM-assisted contributions are allowed, I believe that it would be helpful to have further statements to clarify a few things:</p>



<ul class="wp-block-list">
<li>Even if Debian as a whole doesn’t want to ban LLM-assisted contributions, each maintainer or each team shall be free to forbid LLM assisted contributions in the parts of Debian that they are maintaining</li>



<li>We should discourage usage of LLM provided by players with unethical behaviors (not sure if there are good players but well…)<br /></li>
</ul>
       </div>
       <p class="date">
        <a href="https://raphaelhertzog.com/2026/08/26/debians-general-resolution-on-ai-and-llm/">26 August, 2026 04:04PM</a>
        
         by Raphaël Hertzog
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://diziet.dreamwidth.org/" title="Ian Jackson">Ian Jackson</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://diziet.dreamwidth.org/20998.html_hide\" onClick=\"exclude( 'https://diziet.dreamwidth.org/20998.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://diziet.dreamwidth.org/20998.html_show\"  style=\"display:none;\" onClick=\"show( 'https://diziet.dreamwidth.org/20998.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://diziet.dreamwidth.org/20998.html" class="https://diziet.dreamwidth.org/20998.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://diziet.dreamwidth.org/20998.html">Debian LLM GR - Summary of the options</a>
       </h3>
       <div class="content">
        <ul><li><a href="https://diziet.dreamwidth.org/data/atom#introduction">Introduction</a>
</li><li><a href="https://diziet.dreamwidth.org/data/atom#a-plea-to-the-undecided-voter">A plea to the undecided voter</a>
</li><li><a href="https://diziet.dreamwidth.org/data/atom#table">Table</a>
</li><li><a href="https://diziet.dreamwidth.org/data/atom#notes">Notes</a>
</li></ul>
<p>Debian LLM GR - Summary of the options
</p><h1><a name="introduction">Introduction</a></h1>
<p>LLMs have finally made it to the ultimate stage of Debian’s governance processes, a General Resolution of all the project’s full governing members (DDs).
</p><p>There are a lot of options on the ballot, and they all have a different structure and approach the question in a different way. It can be hard to see the wood for the trees. I have made a summary table to try to capture the main differences, both in effect, and sentiment.

<a name="cutid1"></a>
</p><h1><a name="a-plea-to-the-undecided-voter">A plea to the undecided voter</a></h1>
<p>Suspending briefly my attempt to be neutral:
</p><p>Before voting, I encourage you to read the passionate rationales in options H and A, or at least the summary in my option C.
</p><p>Few of the LLM defences in the discussion threads, and none of the LLM-positive proposals, provide answers to any of these profound ethical concerns, many of which ought individually to be a deal-breaker. Instead, these crucial questions are simply dismissed or even ignored.
</p><p>Some will tell you we should “keep politics out of software” but as we can see in the world around us, software is political - now more than ever. Debian’s mission is a highly political one: developing a fully-free operating system, and defending its freeness as we do, is far from neutral!
</p><p>And of course many of LLMs’ harms affect Debian directly.
</p><h1><a name="table">Table</a></h1>
<table rules="all">
<tbody><tr>
<td></td>
<th><a href="https://www.debian.org/vote/2026/vote_002#texta">A</a></th>
<th><a href="https://www.debian.org/vote/2026/vote_002#textg">G</a></th>
<th><a href="https://www.debian.org/vote/2026/vote_002#textc">C</a></th>
<th><a href="https://www.debian.org/vote/2026/vote_002#texth">H</a></th>
<th><a href="https://www.debian.org/vote/2026/vote_002#textf">F</a></th>
<th><a href="https://www.debian.org/vote/2026/vote_002#textd">D</a></th>
<th><a href="https://www.debian.org/vote/2026/vote_002#textb">B</a></th>
<th><a href="https://www.debian.org/vote/2026/vote_002#texte">E</a></th>
</tr>
<tr><th> LLM harms </th><td> Robustly discussed </td><td> Discussed </td><td> Robustly summarised </td><td> Robustly discussed; especially re climate </td><td> Summarised </td><td> Accepted as inevitable </td><td> Disregarded [1] </td><td> Ignored </td></tr><tr><th> Direct contributions of LLM-generated code </th><td> Forbidden </td><td> Forbidden </td><td> Strongly discouraged </td><td> Strongly discouraged </td><td> Discouraged </td><td> Permitted </td><td> Permitted </td><td> Permitted </td></tr><tr><th> Direct use of LLM output in communications (bugs, mailing lists, etc.) </th><td> Forbidden </td><td> Forbidden </td><td> Forbidden (with possible exceptions) </td><td> Strongly discouraged </td><td> Discouraged </td><td> Permitted </td><td> Permitted </td><td> Permitted </td></tr><tr><th> LLM use where LLM output does not end up in the code/message </th><td> Forbidden </td><td> No position, so permitted </td><td> Strongly discouraged </td><td> Strongly discouraged </td><td> Discouraged </td><td> Permitted </td><td> Permitted </td><td> Permitted </td></tr><tr><th> Disclosure of LLM use </th><td> LLM use forbidden </td><td> LLM use largely forbidden, no further disclosure requirement </td><td> Disclosure required </td><td> Disclosure encouraged </td><td> Disclosure encouraged </td><td> Disclosure required </td><td> Disclosure required </td><td> Undisclosed LLM use is OK </td></tr><tr><th> Use of LLMs by upstreams </th><td></td><td></td><td> Condemned </td><td></td><td></td><td> “Not recommended” </td><td></td><td></td></tr><tr><th> Positive statements about LLMs </th><td></td><td></td><td></td><td></td><td></td><td> “Here to stay” </td><td> Moderate </td><td> Strong </td></tr></tbody></table><h1><a name="notes">Notes</a></h1>
<h2><a name="ordering">Ordering</a></h2>
<p>I have tried to present the options in semantic order, with most LLM-negative proposals to the left, and the most LLM-positive to the right.
</p><p>I have not quoted the one-line titles for the options. These have generally been provided by the proponents of each option, and, unfortunately, some of them are IMO quite misleading.
</p><p>Note that, unfortunately, the voting software likes to assign numbers to options but also to preferences. Be mindful of this possible confusion when casting your vote. For clarity I quote only the option letters.
</p><h2><a name="upstream-llm-code-contributions">Upstream LLM code contributions</a></h2>
<p>Some of the proposals acknowledge the uncertain legal status of LLM output. But all of them implicitly or explicitly assume that LLM output is or can be DFSG free. So none of the proposals forbid upstream projects with LLM-generated contents.
</p><p>None of the proposals would require us to go back to pre-LLM versions of the upstream projects we use, and attempt to fork and maintain them. I very much think there is room in the world for people to try to do that, but I don’t think the Debian project can be that effort.
</p><p>Given that the conclusions are the same in each case, whether the matter is discussed does not seem to me to be a significant difference. I have therefore not included a column for it.
</p><h2><a name="ability-of-individual-teams-to-set-their-own-rules">Ability of individual teams to set their own rules</a></h2>
<p>My proposal has a specific paragraph (7) explicitly permitting teams to set a “no LLM” policy. The other proposals do not discuss this point specifically. During the discussion, it seemed that most participants agreed that even options which explicitly permit LLM use generally do not prevent a team from setting its own more restrictive LLM policy.
</p><p>I have therefore not tabulated this aspect.
</p><h2><a name="exceptions-and-nuances">Exceptions and nuances</a></h2>
<p>Few of the permissive texts are absolute or unconditional. To summarise I have necessarily left out some nuance.
</p><p>So for example when an entry says “permitted”, that generally means “permitted with conditions which are believed by LLM users to be readily satisfiable” (for example, DFSG-compatibility - see above).
</p><h2><a name="footnote-re-proposal-b">[1] Footnote re proposal B</a></h2>
<p>Proposal B does mention that there are “concerns” about LLM use. But it fails to make an explicit statement about whether these concerns are justified.
</p><p>It then proceeds exactly as if they are not justified. IMO “disregarded” is a relatively mild term for such a rhetorical technique.
</p><hr />
<address>
Edited 2026-08-18 09:02 UTC to make the proposal letters in the table be links; 2026-08-26 09:11 UTC to fix typos.</address><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><br /><br /><img alt="comment count unavailable" height="12" src="https://www.dreamwidth.org/tools/commentcount?user=diziet&amp;ditemid=20998" style="vertical-align: middle;" width="30" /> comments
       </div>
       <p class="date">
        <a href="https://diziet.dreamwidth.org/20998.html">26 August, 2026 09:11AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <a href="https://codon.org.uk/~mjg59/blog/post/">
      <img class="face" src="heads/mjg59.png"
            width="69" height="85"
            alt="hackergotchi for Matthew Garrett" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://codon.org.uk/~mjg59/blog/post/" title="Posts on Matthew Garrett's Blog">Matthew Garrett</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://codon.org.uk/~mjg59/blog/p/hooking-an-old-magicjack-adapter-to-modern-asterisk/_hide\" onClick=\"exclude( 'https://codon.org.uk/~mjg59/blog/p/hooking-an-old-magicjack-adapter-to-modern-asterisk/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://codon.org.uk/~mjg59/blog/p/hooking-an-old-magicjack-adapter-to-modern-asterisk/_show\"  style=\"display:none;\" onClick=\"show( 'https://codon.org.uk/~mjg59/blog/p/hooking-an-old-magicjack-adapter-to-modern-asterisk/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://codon.org.uk/~mjg59/blog/p/hooking-an-old-magicjack-adapter-to-modern-asterisk/" class="https://codon.org.uk/~mjg59/blog/p/hooking-an-old-magicjack-adapter-to-modern-asterisk/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://codon.org.uk/~mjg59/blog/p/hooking-an-old-magicjack-adapter-to-modern-asterisk/">Hooking an old magicJack adapter to modern Asterisk</a>
       </h3>
       <div class="content">
        <p>I’m on a VPN setup with several friends that, obviously, includes a VoIP
network. I also have an old
<a class="link" href="https://en.wikipedia.org/wiki/MagicJack" rel="noopener" target="_blank">magicJack</a> adapter and a deep and
abiding need to use hardware in ways I should not. There was obvious synergy
here.</p>
<p>Plugging in the magicJack gives a USB vendor id of 0x06e6, which belonged to
a company called TigerJet who made a range of chips for hooking up phones to
computers, either via USB or PCI. Some more digging suggested that it was a
580 part, and someone had conveniently
<a class="link" href="https://www.mediafire.com/file/a3ocbckd7cobr7k/TigerJetCode.tar.gz" rel="noopener" target="_blank">uploaded</a>
some reference code and datasheets, so figuring out how to talk to the chip
wasn’t terribly difficult. Once configured it simply sends HID events
whenever a user hits a phone key or changes the hook state, and otherwise
exposes a USB audio device that can be spoken to using the stock kernel
driver. It also has the ability to generate dial tone and assert ring
signal, giving a full traditional phone experience.</p>
<p>So you’d think this would be a super easy project, but I’d made things
harder for myself by deciding I wanted to tie directly into Asterisk rather
than just smashing an existing SIP stack onto the device. Asterisk uses
<a class="link" href="https://docs.asterisk.org/Fundamentals/Key-Concepts/Channels/" rel="noopener" target="_blank">channels</a> to
talk to devices, and channels end up as compiled C code that Asterisk can
load dynamically. I didn’t want to have to deal with the pain of compiling
stuff and matching ABIs and everything so writing a new channel from scratch
was unappealing. Fortunately, the <a class="link" href="https://docs.asterisk.org/Configuration/Channel-Drivers/WebSocket/" rel="noopener" target="_blank">websocket
channel</a>
is available in recent versions of Asterisk and provides a convenient way to
get audio in and out, but that still leaves the job of handling incoming and
outgoing calls. That’s handled with the <a class="link" href="https://docs.asterisk.org/Configuration/Interfaces/Asterisk-REST-Interface-ARI/" rel="noopener" target="_blank">Asterisk Rest
Interface</a>,
which can initiate a call or respond to an incoming one and bridge various
channels together to produce a bidirectional audio stream. There’s a
convenient <a class="link" href="https://pypi.org/project/asyncari/" rel="noopener" target="_blank">async Python library</a> that
handles the low level protocol.</p>
<p>Code for all this is
<a class="link" href="https://tangled.org/mjg59.eicar-test-file.zip/magicjack-asterisk/" rel="noopener" target="_blank">here</a><sup id="fnref:1"><a class="footnote-ref" href="https://codon.org.uk/~mjg59/blog/index.xml#fn:1">1</a></sup>,
and works for my use case, but I should really abstract out the asterisk
side and the magicJack side to make it easier to adapt to other
devices. That’s a job for later, though. For now, you get this:</p>
<div class="video-wrapper">
    <video controls="controls" width="600">
      <source src="https://codon.org.uk/~mjg59/tmp/phonecall.mp4" />
        <p>
            Your browser doesn't support HTML5 video. Here is a
            <a href="https://codon.org.uk/~mjg59/tmp/phonecall.mp4">link to the video</a> instead.
        </p>
    </video>
</div>

<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>This has also been an excuse for me to figure out how to make <a class="link" href="https://tangled.org" rel="noopener" target="_blank">Tangled</a> work, which I’ll write about at some later point. But self-hosted git repo with a convenient collaboration plane! <a class="footnote-backref" href="https://codon.org.uk/~mjg59/blog/index.xml#fnref:1">↩︎</a></p>
</li>
</ol>
</div>
       </div>
       <p class="date">
        <a href="https://codon.org.uk/~mjg59/blog/p/hooking-an-old-magicjack-adapter-to-modern-asterisk/">26 August, 2026 04:04AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>August 25, 2026</h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://retout.co.uk/" title="Tim Retout">Tim Retout</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://retout.co.uk/2026/08/25/tf-raid/_hide\" onClick=\"exclude( 'https://retout.co.uk/2026/08/25/tf-raid/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://retout.co.uk/2026/08/25/tf-raid/_show\"  style=\"display:none;\" onClick=\"show( 'https://retout.co.uk/2026/08/25/tf-raid/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://retout.co.uk/2026/08/25/tf-raid/" class="https://retout.co.uk/2026/08/25/tf-raid/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://retout.co.uk/2026/08/25/tf-raid/">TF RAID</a>
       </h3>
       <div class="content">
        <p>My hobby: following GOV.UK to look for interesting announcements.
Today was an update on the MOD’s <a href="https://www.gov.uk/guidance/rapid-ai-delivery-taskforce-tf-raid">Rapid AI Delivery
Taskforce</a>
which was <a href="https://www.gov.uk/government/news/new-taskforce-to-put-ai-on-the-uks-frontline">previously announced in June during London Tech
Week</a>.</p>
<p>I like this line: “Success is measured in operational advantage delivered, not technology demonstrated.”  To me it recalls “Working software is the primary measure of progress” from <a href="https://agilemanifesto.org/principles.html">Principles behind the Agile Manifesto</a> – if you understand “working” to mean “working in production”. Which I do.</p>
<p>For anyone interested in suggesting ideas to the taskforce, the four
operational challenge areas include:</p>
<ul>
<li>Understanding and decision advantage</li>
<li>Electromagnetic and information advantage</li>
<li>Planning and automation</li>
<li>Autonomous systems</li>
</ul>
<p>Yesterday’s announcement of <a href="https://www.gov.uk/government/news/new-partnership-set-to-see-the-uk-and-ukraine-develop-battle-winning-technology-as-britain-secures-access-to-ukraines-avengers-ai-labs">UK access to Ukraine’s Avengers AI Labs
database</a>
seems incredibly relevant to that last point.</p>
<p>Machine assistance for handling and interpreting huge volumes of data
would probably benefit decision advantage and interpretation of a
crowded EM spectrum, but this is hopefully(?) more than just LLMs. Of
course, there’s more to AI than large language models… right?</p>
<p>I worry that “planning and automation” might amount to “generating
<a href="https://www.trngcmd.marines.mil/Portals/207/Docs/TBS/STANAG%202014%20Edition%2009-%20FORMATS%20FOR%20ORDERS%20(OPORD).pdf">large amounts of
text</a>
faster”.  Nothing could possibly go wrong with this.</p>
       </div>
       <p class="date">
        <a href="https://retout.co.uk/2026/08/25/tf-raid/">25 August, 2026 08:38PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://anarc.at/tag/debian-planet/" title="pages tagged debian-planet">Antoine Beaupré</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://anarc.at/blog/2026-08-25-llm-nuance/_hide\" onClick=\"exclude( 'https://anarc.at/blog/2026-08-25-llm-nuance/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://anarc.at/blog/2026-08-25-llm-nuance/_show\"  style=\"display:none;\" onClick=\"show( 'https://anarc.at/blog/2026-08-25-llm-nuance/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://anarc.at/blog/2026-08-25-llm-nuance/" class="https://anarc.at/blog/2026-08-25-llm-nuance/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://anarc.at/blog/2026-08-25-llm-nuance/">A more nuanced view of LLMs</a>
       </h3>
       <div class="content">
        <blockquote><p>Also in this series:</p>

<ul>
<li><a href="https://anarc.at/blog/2026-05-16-four-horsemen/">The Four Horsemen of the LLM Apocalypse</a></li>
<li><a href="https://anarc.at/blog/2026-08-18-people-vs-ai-overlords/">The people vs the AI overlords</a></li>
</ul>
</blockquote>

<p>After ranting and railing about LLMs or "AI" as the optimists (or
<a href="https://en.wikipedia.org/wiki/Accelerationism">accelerationists</a>?)  call it, I figured it might be important to
be a little more honest about my use of LLMs and how I think about it
more practically in the world.</p>

<h1 id="the-debian-vote-context">The Debian vote context</h1>

<p>This is not a coming out. I am not using LLMs on a daily basis, and
this blog is, again, written out of my cold dead hands in a dying
world, with over-engineered hardware and (to a certain extent, hi
Emacs!) software, powered by 100% green energy built on <a href="https://en.wikipedia.org/wiki/James_Bay_Cree_hydroelectric_conflict">stolen land</a>.</p>

<p>There is a <a href="https://www.debian.org/vote/2026/vote_002">vote going on in Debian</a>. If you're unfamiliar with it,
you can <a href="https://lwn.net/SubscriberLink/1087134/77bf350b3d40bc95/">catch up at LWN</a>. So far I've essentially said "LLM is
bad" which is not a very balanced or useful opinion. Obviously, people
are using LLMs, sometimes unknowing or unwillingly, and we need to
take that into account. Furthermore, there has been many different
blog posts on Debian planet about this. Some that I found
<a href="https://grep.be/blog//en/computer/debian/Programming_and_GR_2026_002/">balanced</a>, <a href="https://diziet.dreamwidth.org/20998.html">good summaries</a>, even if I <a href="https://changelog.complete.org/archives/44740-ai-in-debian-the-vote-proposals-and-nuance">didn't fully agree</a> <a href="https://raphaelhertzog.com/2026/08/26/debians-general-resolution-on-ai-and-llm/">with
them</a>, at least some did the basic civil service of being
<a href="http://blog.fai-project.org/posts/llm-usage-gr/">short</a>. But others were just not only <a href="https://k1024.org/posts/2026/2026-08-23-another-optimistic-take-on-ai/">Wrong</a> but also <a href="http://aigarius.com/blog/2026/08/22/optimistic-take-on-ai/">so long
that I couldn't finish</a> that I just <em>had</em> to write <em>something</em>.<sup id="fnref:1"><a href="https://anarc.at/tag/debian-planet/#fn:1" rel="footnote">1</a></sup></p>

<p>This is not an explanation of the ballots, nor how I will vote. This
vote is Debian's failure of framing that debate in a reasonable way:
we have 8 options on the ballot with many duplicates. We have failed
to do the hard work of summarizing and aggregating options into a
meaningful set. I doubt the final vote will represent a readable
position we can rally around.</p>

<p>I have not read the <a href="https://lists.debian.org/debian-vote/2026/07/threads.html">two</a> <a href="https://lists.debian.org/debian-vote/2026/08/threads.html">months</a> of debates on the topic
either. Normally, before voting, I take a cursory look at the debate
to see points of view I might have missed. But in this case, it will
just make me sad, add noise, and I'm already pretty sure on where I
stand on this.</p>

<p>So let me describe how I use LLMs and how I think they fit in our
work, as computer engineers and hobbyists.</p>

<h1 id="my-llm-use">My LLM use</h1>

<h2 id="debian-packaging">Debian Packaging</h2>

<p>An astute reader has <a href="https://piaille.fr/@TurboTrain/117067460922413248">pointed out</a> that I maintain a package in
Debian made to use Anthropic. It's actually multiple packages:</p>

<ul>
<li><a href="https://tracker.debian.org/pkg/llm"><code>llm</code></a>: a <a href="https://llm.datasette.io/en/stable/">CLI utility and Python library for interacting with
Large Language Models</a>, with OpenAI as its default API backend</li>
<li><a href="https://tracker.debian.org/pkg/llm-anthropic"><code>llm-anthropic</code></a>: a plugin for <code>llm</code> which allows me to talk to
Anthropic's API instead of OpenAI</li>
<li><a href="https://tracker.debian.org/pkg/anthropic-sdk-python"><code>anthropic-sdk-python</code></a>: the SDK <code>llm-anthropic</code> requires to do
its work</li>
</ul>


<p>As I previously <a href="https://kolektiva.social/@Anarcat/117071797145506717">explained in response</a>, I am not entirely
comfortable with this work: it's a compromise. In fact, I first
uploaded <code>llm</code> to the <code>contrib</code> section of Debian, where we keep
software that depends on other non-free software, but I was told that,
since <a href="https://tracker.debian.org/pkg/yt-dlp"><code>yt-dlp</code></a> was in <code>main</code>, <code>llm</code> belonged there as well.</p>

<p>So I <a href="https://tracker.debian.org/news/1718613/accepted-llm-028-2-source-all-into-unstable/">moved it to main</a>, alongside similarly controversial tools
like <a href="https://tracker.debian.org/pkg/llama.cpp"><code>llama.cpp</code></a> or the <a href="https://tracker.debian.org/pkg/python-openai"><code>python-openai</code></a> library.</p>

<h2 id="openai-and-anthropic-usage">OpenAI and Anthropic usage</h2>

<p>An important part of my work is technology watch. I keep tabs on
thousands of (new and old) software projects, follow news, and
generally try to keep my skills up to date. It's a <a href="https://anarc.at/blog/2018-05-26-kubecon-rant/">pretty impossible
race</a>, especially as I grow older, but I still think I'm doing the
right choices in my job.</p>

<p>Testing large language models is part of that work. At first, I was
using ChatGPT's web interface, but it was annoying to copy-paste
things into a browser, so I looked for different interfaces.</p>

<p>For a while I tried <a href="https://github.com/karthink/gptel"><code>gptel</code></a>, a "simple, extensible LLM client for
Emacs" but I found it kind of terrifying. Giving a LLM control over an
Emacs buffer seems like a security nightmare, so I <a href="https://gitlab.com/anarcat/emacs-d/-/commit/57fb1c13e63b58142ece426fc32f4ebdb325a6c3">stopped doing
that</a>.</p>

<p>So I use the <code>llm</code> command-line tool to talk to Anthropic's API. I
started that in the summer of 2025, when I bought 20$USD of API
credits. Before that, I paid for a ChatGPT subscription and then
OpenAI credits, which expired and sent me over to Anthropic, which
<em>seemed</em> then to have better ethics.</p>

<p>As it turns out, Anthropic is also happy to work for the US military
(which is a big red line for me). Anthropic also won't let you <a href="https://evanp.me/2026/07/23/claude-wont-let-me-talk-about-the-gaza-genocide/">talk
about the genocide in Gaza</a>, it is <a href="https://annas-archive.gl/blog/physical-destruction.html">destroying physical books</a>,
and is <a href="https://www.flyingpenguin.com/mythos-grading-mythos-got-patches-yet/">blackmailing us to use their product for security
coverage</a>.</p>

<p>Needless to say, Anthropic and "Claude" are not my friends, but they
seem like the lesser evil in current "frontier models". So I have
renewed, a couple of weeks ago, another 20$USD of API credits with
Anthropic.</p>

<h2 id="actual-prompts-and-responses">Actual prompts and responses</h2>

<p>So what does 20$ give you at Anthropic anyways? What <em>am</em> I using LLMs
for and how?</p>

<p>The neat thing with <code>llm</code> is that everything is logged in a <code>sqlite</code>
database, so there are some answers that are easy to get:</p>

<pre><code>&gt; llm logs status
Logging is ON for all prompts
Found log database at /home/anarcat/.config/io.datasette.llm/logs.db
Number of threads logged:   7
Number of turns logged:     12
Number of legacy conversations: 543
Number of legacy responses: 970
Database file size:         9.61MB
</code></pre>

<p>That is 10MB of logs, with about a thousand prompts.</p>

<p>My logs go back to 2024-03-07, a little over two years ago, and
include a mix of Anthropic and OpenAI responses. I used it more in
2024 than 2025, and if the trend continues, I will have used it less
in 2026 again:</p>

<pre><code>&gt; llm logs list -n 0  --json | jq -r .[].datetime_utc | sed 's/-.*//' | sort | uniq -c 
    527 2024
    357 2025
     98 2026
</code></pre>

<p>It looks like about 10 prompts per month right now, down from a peak
of about 60 per month in 2024. It's pretty difficult to analyze those
actual logs to get more patterns and I won't run the prompts through a
model <em>again</em> to process them.</p>

<h2 id="how-im-using-models-now">How I'm using models now</h2>

<p>At first, I was using it partly for benchmarking model's capabilities,
like <a href="https://simonwillison.net/">Simon Willison</a> does with his pelicans, clearly not trusting
its output. But I was impressed by the capacities of the Claude Opus
4.5 model when it <a href="https://gitlab.com/anarcat/scripts/-/blob/main/transmodify.py?ref_type=heads">wrote this script in January</a>. Impressed, but
also scared: it's the first time I felt I could delegate the entirety
of my programming to a model. Just run the code, if it works, it
works, right?</p>

<p>So what do I use it now? As an example, here are the 10 last prompts
in my history:</p>

<ol>
<li>there is now Claude 5, and a fable model, maybe you know about it?</li>
<li>impress me</li>
<li>not impressive, i already know all of this</li>
<li>chat</li>
<li>in postfix, i have a 300k mailing that happens regularly here. normally, it delivers within about...</li>
<li>is there a way i could have drained the maildrop queue faster without removing the milter?</li>
<li>the problem was that rspamd was timing out on the FUZZY_CALLBACK check. how do i disable that?</li>
<li>how do i disable all spam checks? i just want rspamd to add dkim signatures</li>
<li>how do the default_destination_concurrency_limit and initial_destination_concurrency settings int...</li>
<li>mic check</li>
</ol>


<p>The first one was me trying to confirm which model I am using, which
is not always obvious when going through the whole <code>llm</code> stack I've
been using. The following two are an attempt at seeing what the model
is capable of and I was "not impressed", to which Claude answered that
I have a "high bar", which, fair enough.</p>

<p>The <code>chat</code> is me failing to use a command line, which shows that
perhaps I need to readjust that "high bar", again.</p>

<p>The next five are a rather embarrassing debacle in a large Postfix
mailing that went sideways, and where I couldn't find an actual
Postfix expert of my level to help. The fabled Claude Fable 5 answered
rather correctly, but dangerously, that I could empty the queue by
disabling the <code>non_smtpd_milters</code>. What Fable (and myself) did not
realize is that the milter was also adding DKIM signatures, so while the
mailing was expedited, it was done without those precious signatures,
which got us promptly blocked at Gmail. We have recovered since, and,
thanks to the model and reading the <a href="https://www.postfix.org/pickup.8.html">Postfix manual</a> for the
hundredth time, that <a href="https://www.postfix.org/pickup.8.html">pickup(8)</a> is single-threaded and that we
needed to review the architecture of that mailing (and our spam
filters) a bit. Many tickets ensued.</p>

<p>The last one is a test I did to make sure my last uploads of
<code>llm-anthropic</code> and its dependency worked correctly.</p>

<blockquote><p>Note that the above excludes 5 questions I asked Anthropic while
writing this article, where I asked for synonyms and "what nanometer
scale are arduino processors built from? how is an arduino CPU
printed?", a question which Wikipedia furiously evades providing a
good answer.</p></blockquote>

<p>Those prompts are pretty typical of my LLM use: I'm testing the models
to see if they work at all, but also, out of desperation, I fire off a
prompt after I fire off questions to colleagues or search engines (in
that order). It's often weird edge cases like the Prometheus query
language, Python's matplotlib, LaTeX, Elisp, optimizations, and so on.</p>

<p>I use models for translation a lot. Being fully bilingual, it is
common for me to think of a word in French or English and fail to find
exactly the right word for that in the other language. Models help
with that, and are also useful to find synonyms. Those are low-token
uses that seem pretty innocuous to me, but I realize the irony of this
after writing about the <a href="https://anarc.at/blog/2026-05-16-four-horsemen/#the-tower-of-babel">tower of
Babel</a>.</p>

<h2 id="what-i-am-not-using-models-for">What I am not using models for</h2>

<p>I am not using models to write prose.</p>

<p>I am not using models to <em>read</em> prose. If it's generated with LLMs, I
stop reading.</p>

<p>I am not using models to write code, with the exception of that single
Python script above.</p>

<p>I am generally not using models to <em>review</em> code, with exceptions. If
I get stuck on a hard problem, I might feed a piece of code to the
model. I repeatedly fed <a href="https://gitlab.com/anarcat/asncounter/"><code>asncounter</code></a> into Claude to try to fix a
performance regression I had introduced. It found micro-optimizations
that taught me a thing or two about Python's internal implementations,
but overall, it was mostly a waste of time. This was in June 2025, so
perhaps now models would fare better. I have not tried again.</p>

<p>I am not using LLMs to do Debian packaging. When I can, I manually
review the diffs of packages I upload into Debian, still, by hand.</p>

<p>I do this for the reasons outlined in <a href="https://anarc.at/blog/2026-05-16-four-horsemen/">The Four Horsemen of the LLM
Apocalypse</a>, because I refuse to be
complicit in the:</p>

<ol>
<li>aggressive and illegal scraping of the servers I steward</li>
<li>world-wide computer hardware shortage (making it, by the way,
nearly impossible to run presumably clean local models) and the
attack on our job conditions (also discussed in
<a href="https://anarc.at/blog/2026-08-18-people-vs-ai-overlords/">The people vs the AI overlords</a>)</li>
<li>death of copyright and free software</li>
<li>complication and enshifitication of everything, and the
destruction of our communities</li>
<li>the imperialist <a href="https://www.thenerdreich.com/">Nerd Reich</a> that wants to take over the world</li>
</ol>


<p>Like I reluctantly use Intel computers, I <em>do</em> fire off a prompt. But
I still hold on to the dream that we can build <a href="https://en.wikipedia.org/wiki/Community_of_practice">communities of
practice</a> that hold human knowledge collectively and not <a href="https://gizmodo.com/sam-altman-says-intelligence-will-be-a-utility-and-hes-just-the-man-to-collect-the-bills-2000732953">offload
that as a utility</a> to some megalomaniac billionaire.</p>

<h1 id="their-llm-use-i-am-forced-into">Their LLM use I am forced into</h1>

<p>So that's me. Clearly, I'm going against the grain here. Everywhere I
look, I see LLM-generated code and projects. Slop and botnets have
flooded the web.</p>

<p>I use <a href="https://wadamesh.com/">Wadamesh</a>, clearly <a href="https://github.com/ALLFATHER-BV/wadamesh/graphs/contributors?from=2026-05-23">vibe-coded</a>, because it's the best
graphical interface for MeshCore that runs on portable devices. I wish
it was made by a human, in a community I could participate in, but it
isn't, and I don't.</p>

<p>I package the above <code>llm</code> toolset, which is <a href="https://simonwillison.net/2026/Aug/24/llm-anthropic/">more and more
vibe-coded</a>, but I still review the diffs. And I have to say: I
trust Simon here. The code is verbose as hell, feels overengineered,
and <code>llm</code> feels slow, but it generally works, and Simon is still at
the gate.</p>

<p>The Anthropic SDK is another thing entirely. The <a href="https://salsa.debian.org/python-team/packages/anthropic-sdk-python/-/commit/a3087b3cef63ae239a1f558a6b46565ce0485b7d">0.91.0 to 0.120
upload</a>, for example, was nuts:</p>

<pre><code> 806 files changed, 72281 insertions(+), 1478 deletions(-)
</code></pre>

<p>I explicitly did not review that entire diff. It feels like there's a
lot of garbage there to just have a shim between a proprietary API and
Python. But this is the hand I've been dealt.</p>

<h1 id="larger-projects-llm-use">Larger projects LLM use</h1>

<p>LLMs are being used in the Linux kernel, Firefox, <code>rsync</code>, Rust, and
other places. I don't feel good about this, particularly in Rust, but
they at least made a decent <a href="https://forge.rust-lang.org/policies/llm-usage.html">policy</a>. I am glad GCC made a <a href="https://lwn.net/Articles/1086041/">policy
against LLM contributions</a> and I support the <a href="https://human-emacs.org/">human Emacs</a>
project.</p>

<p>We need to have a set of foundational tools that are "clean" in the
sense that they are built upon a community of people that understand
how they are built.</p>

<p>Maybe that's naive or even impossible. The Linux kernel and GCC, in
particular, are massive projects that have long grown past the scale
of a single person's understanding. But the theory was that a
<em>community</em> of humans can understand <em>collectively</em>.</p>

<p>Now we seem to be throwing up our hands and giving up on
that community. That LLMs will just fix the problem, whatever it
is. But we're all just one rug pull away from being completely
incapable of managing those projects. The argument there is that we'll
just switch to local models, but no one is actually doing that.
All I see is people use local models <a href="https://micahflee.com/agentic-coding-techniques/">as a corner case</a>
(for privacy) or as in <a href="https://changelog.complete.org/archives/44740-ai-in-debian-the-vote-proposals-and-nuance">theory</a>, but in reality, everyone uses the
centralized frontier models right now. We just can't fallback.</p>

<p>We're in the same situation we were, a decade or two ago, when
Microsoft decided it would kill free office alternatives by making
Office free for non-profits. It worked: thousands, if not millions of
schools, community groups and individuals stopped looking for
alternatives (including free software but also "piracy") for Office
and embraced what seemed like a generous offer.</p>

<p>Now Microsoft pulled the plug and <a href="https://slate.com/technology/2026/08/microsoft-software-nonprofit-data-delete.html">Over 170,000 Nonprofits Lost All
Their Data</a>.</p>

<p>I'm afraid the rug pull on LLMs will be much worse: never mind that
Linus won't be able to use his <a href="https://github.com/torvalds/linux/commit/818bebeb63dd6bf5f4e07e145f6cdbace520a34c">tireless helper</a> to fix obscure kernel
bugs; we're looking at a collapse of the economy so large that we are
already <a href="https://prospect.org/2026/08/03/ai-bailout-could-be-baked-into-bubble-private-equity-life-insurers-loans/">talking about bailing out the companies responsible</a>.</p>

<p>In a sense, the most striking thing about the Debian vote is it has
actually no option to completely refuse upstream LLM contributions. It
seems the community has taken it for granted that it's now impossible
to build Debian entirely without LLMs. We lost the battle even without
a fight, it seems.</p>

<h1 id="a-plea-for-small">A plea for small</h1>

<p>If it has really become impossible for us to manage the complexity we
have built, maybe it's time to stop and think about what we're doing
in the first place. We're struggling to even <a href="https://bootstrappable.org/">bootstrap</a> our
current toolchain!</p>

<p>This is one of the things I like the most about working on the mesh:
it's low tech, small Arduino devices that is built with decades-old
<a href="https://en.wikipedia.org/wiki/Semiconductor_device_fabrication">semiconductor processes</a> that is understandable by human
beings.</p>

<p>Maybe the answer lies more in single-purpose devices like those
communicators and simpler multi-purpose computers than what we have
now, which is what the <a href="https://en.wikipedia.org/wiki/Permacomputing">permacomputing</a> movement is about.</p>

<p>Small is beautiful, let's scale it down.</p>





<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
and yes, I'm sorry this has gotten this long, I hope you will
forgive those 3000 words.<a href="https://anarc.at/tag/debian-planet/#fnref:1" rev="footnote">↩</a></li>
</ol>
</div>
       </div>
       <p class="date">
        <a href="https://anarc.at/blog/2026-08-25-llm-nuance/">25 August, 2026 04:07PM</a>
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1>August 24, 2026</h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://blog.tenstral.net" title="Debian – Ximions Blog">Matthias Klumpp</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://blog.tenstral.net/2026/08/sovereign-tech-fellowship-for-freedesktop-tasks.html_hide\" onClick=\"exclude( 'https://blog.tenstral.net/2026/08/sovereign-tech-fellowship-for-freedesktop-tasks.html' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://blog.tenstral.net/2026/08/sovereign-tech-fellowship-for-freedesktop-tasks.html_show\"  style=\"display:none;\" onClick=\"show( 'https://blog.tenstral.net/2026/08/sovereign-tech-fellowship-for-freedesktop-tasks.html' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://blog.tenstral.net/2026/08/sovereign-tech-fellowship-for-freedesktop-tasks.html" class="https://blog.tenstral.net/2026/08/sovereign-tech-fellowship-for-freedesktop-tasks.html">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://blog.tenstral.net/2026/08/sovereign-tech-fellowship-for-freedesktop-tasks.html">Sovereign Tech Fellowship for Freedesktop Tasks</a>
       </h3>
       <div class="content">
        <p class="wp-block-paragraph">In 2025 I was honored to be selected for the first cohort of <a href="https://www.sovereign.tech/programs/fellowship">Sovereign Tech Fellows</a>, a program by Germany’s <a href="https://www.sovereign.tech/">Sovereign Tech Agency</a> to improve the resilience of the open source ecosystem by supporting maintainers directly (complementing their existing support for larger FOSS organizations). Back in 2025, I was only working very limited hours – however, this has changed in 2026.</p>



<p class="wp-block-paragraph">For the second half of 2026, I am working again as a Sovereign Tech Fellow, but this time with significantly increased hours. After finishing my PhD, I do have time now for new tasks (and new jobs!), and the fellowship presents an amazing opportunity to really advance projects that I maintain or am part of. This also has a very nice effect on contributors and bug reporters, as their feedback gets addressed a lot faster. With some luck, this ultimately will help finding new (co)maintainers for projects as well (although in the age of AI, a lot of how open source used to work is much more uncertain, but that is a matter for a different blog post).</p>



<p class="wp-block-paragraph">The fellowship is time-limited, so I am intending to make the time I currently have count!</p>



<h2 class="wp-block-heading">So, what’s planned?</h2>



<p class="wp-block-paragraph">I am involved in many projects, but three of them will be getting attention as part of the fellowship. I know I am notoriously slow at blogging, but expect more details on each of them very soon. Here’s an overview:</p>



<h3 class="wp-block-heading">Freedesktop.org, Specifications and Organization</h3>



<p class="wp-block-paragraph">I maintain the <a href="https://specifications.freedesktop.org/">Freedesktop Specifications</a>, which is an area of Freedesktop that has traditionally been a bit chaotic. This “worked” in the past, because Freedesktop was never intended to be a formal standards body, but more a shared space where people could throw a lot of code and ideas over the wall and see what sticks and what people can collaborate on.</p>



<p class="wp-block-paragraph">While I very much love the spirit of this and want to keep it in some form, we definitely would benefit not just from more formalization and better procedures, but also from better organization of the specifications in general. A lot of conflicts can be avoided by that. I will work on improving procedures, crunching through the (lots!) of pending bug reports and MRs, and to make the specifications site better searchable and accessible (similar to how Mozilla’s MDN presents information, but I am not sure if we will get quite that far). I also intent to add a compatibility matrix for specifications, so if a desktop opts out of any one of them (or does not implement them yet) that fact is documented and authors of applications know what they can expect. This will allow us to move a lot faster and avoid a lot of conflict, because there is no implicit assumption that “everybody will implement everything” anymore (which has never been quite true anyway).</p>



<p class="wp-block-paragraph">Hopefully, this will ultimately result in a Freedesktop that is both a lot more useful for application authors who want to bring their project to Linux, as well as developers of desktop environments who need to see which specifications are available and which ones are current.</p>



<p class="wp-block-paragraph">In addition to that, I have also worked on a Freedesktop.org website refresh, which is pretty much done in its first iteration (pending sysadmin action). The aim there is to have a more official website, separate from user-contributed wiki content, that showcases what Freedesktop is and which projects are using it for hosting. Once the new website is live, I will also review every page again, archive dead projects in their own section and reorganize the software and specifications directory. Those sections are severely outdated and are missing recent efforts from the community, while still containing long-dead old projects (remember <a href="https://en.wikipedia.org/wiki/HAL_(software)">HAL</a>? <img alt="😉" class="wp-smiley" src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" style="height: 1em;" />).</p>



<h3 class="wp-block-heading">AppStream</h3>



<p class="wp-block-paragraph">A lot of extra maintenance work will be (has been!) done on it. This includes things such as JPEG-XL support (blog post soon), sandboxed media processing, support for newer specification additions, better OARS integration (and potentially migrating it to fd.o infrastructure), improvements and API stabilization for <em>libappstream-compose</em> and a lot of bugfixing and resolution of issues found by AI code review.</p>



<p class="wp-block-paragraph"><a href="https://github.com/ximion/appstream">AppStream</a> was originally designed to parse only trusted data from vetted Linux distribution sources – this is no longer the case in today’s world and in the way Flatpak uses it, so we need to increase resilience of the project.</p>



<p class="wp-block-paragraph">I am also exploring a project that could vastly improve search accuracy for AppStream. Stay tuned for that.</p>



<h3 class="wp-block-heading">PackageKit &amp; System Upgrades</h3>



<p class="wp-block-paragraph">Many years ago, people thought we would all migrate to atomic Linux distributions and slowly not need <a href="https://github.com/PackageKit/PackageKit">PackageKit</a> anymore. This has not turned out to be the case, and there are still plenty of reasons to use a package-based OS, especially in development environments. At the same time, PackageKit has been basically the same for years, and its older architecture is beginning to show. It being a daemon who’s literal job it is to modify the entire system also makes it one of the most security-sensitive components that a Linux system can have, while simultaneously making it near-impossible to sandbox.</p>



<p class="wp-block-paragraph">My plan is to create PackageKit 2.0 by building on the great foundation of PackageKit 1.0, but modernizing it. This will include simplifying its code and removing a bunch of features that have no more use in modern desktops, while also adding some features that PackageKit never had but that would be useful to expose to frontends (still no to interactivity an terminal-progress forwarding though!). PK 2.0 will also allow me to solve a few design issues that have been worked around in the past, by replacing them with better solutions. This will be a painful transition, as PackageKit 2.0 will break all interfaces PackageKit has – and those interfaces have been frozen for more than a decade. However, I do fully expect this change to be worth the effort.</p>



<p class="wp-block-paragraph">In addition to that, I intend to look into the offline-update procedure again and improve it. The current multi-reboot operation comes with downsides, that newer systemd features such as soft-reboot can alleviate. The end result should be a much smoother, less annoying offline-update experience for users (I especially want to get rid of updates running on system startup, which I consider quite bad from a usability perspective). The new behavior is in the early drafting stages and may need direct support from systemd. I will share more about it once I can.</p>



<h2 class="wp-block-heading">That’s a lot of tasks!</h2>



<p class="wp-block-paragraph">Yes! I will see how far I get. I am moving project-by-project though, to allow me to focus on one project at a time, rather than scattering my attention continuously. Amazingly, this means that the major tasks for AppStream are already almost done, and we are nearing the 1.2.0 release. AppStream got priority, because the new Freedesktop Flatpak runtime will be released soon, and because I want FlatHub/Flatpak to have access to the new AppStream release sooner. Freedesktop and PackageKit are next on the task list.</p>



<p class="wp-block-paragraph">Either way, a lot of progress is coming – if you have any feedback or want to help out, please don’t hesitate to reach out! All work is happening fully in the open, so you can also chime in on the respective GitHub/GitLab tasks <img alt="😀" class="wp-smiley" src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f600.png" style="height: 1em;" />.</p>



<p class="wp-block-paragraph">You can also expect blog posts about key features or interesting changes, so stay tuned! <img alt="🙂" class="wp-smiley" src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" style="height: 1em;" /></p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>
       </div>
       <p class="date">
        <a href="https://blog.tenstral.net/2026/08/sovereign-tech-fellowship-for-freedesktop-tasks.html">24 August, 2026 09:00PM</a>
        
         by Matthias
        
       </p>
      </div>
     </div>
    </div>
    
     
    <h1></h1>
     

    <div class="channel">
     
      
     <h2 class="planet-title">
      <a href="https://vincent.bernat.ch/en" title="Vincent Bernat">Vincent Bernat</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://vincent.bernat.ch/en/blog/2026-spanning-tree_hide\" onClick=\"exclude( 'https://vincent.bernat.ch/en/blog/2026-spanning-tree' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://vincent.bernat.ch/en/blog/2026-spanning-tree_show\"  style=\"display:none;\" onClick=\"show( 'https://vincent.bernat.ch/en/blog/2026-spanning-tree' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://vincent.bernat.ch/en/blog/2026-spanning-tree" class="https://vincent.bernat.ch/en/blog/2026-spanning-tree">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://vincent.bernat.ch/en/blog/2026-spanning-tree">An interactive tour of the spanning tree protocol</a>
       </h3>
       <div class="content">
        <div class="admonition when-rss">
<p class="admonition-title">Warning</p>
<p>This post contains interactive examples. To visualize and interact
with them, you need to <a href="https://vincent.bernat.ch/en/blog/2026-spanning-tree" title="An interactive tour of the spanning tree protocol">leave your RSS reader</a>.</p>
</div>

<p>Imagine you rent office space for a three-day event. You quickly set up a few
Ethernet switches and tape some cables on the floor to get everyone online.
Unfortunately, Stan, your clumsiest coworker, kicks out a cable every time he
gets up for coffee. You could add extra cables, but then you’d get a broadcast
storm: Ethernet packets that loop and multiply until nothing else gets through.</p>
<p>That’s where the <em>spanning tree protocol</em> (<abbr title="Spanning Tree Protocol">STP</abbr>) comes in. <abbr title="Spanning Tree Protocol">STP</abbr> blocks just enough
of your spare cables to leave a loop-free tree. When Stan strikes again, it
rebuilds the tree in a second, leaving some time for Blobby, your one-person
support crew, to reconnect the cable.<sup id="fnref:sprites"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:sprites">1</a></sup> See for yourself: the diagram
below runs a real <abbr title="Spanning Tree Protocol">STP</abbr> implementation in your browser!</p>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>:demo

A1 @0,0 prio=4096
A2 @0,1
A3 @0,2
A4 @0,3

B1 @1,0 prio=8192
B2 @1,1
B3 @1,2
B4 @1,3

C1 @2,0 prio=8192
C2 @2,1
C3 @2,2
C4 @2,3

A1 -- A2 hazard=0
A2 -- A3 hazard=0
A3 -- A4 hazard=0
B1 -- B2
B2 -- B3
B3 -- B4
C1 -- C2 hazard=0
C2 -- C3 hazard=0
C3 -- C4 hazard=0

A1 -- B1 cost=10
B1 -- C1 cost=10
A4 -- B4 cost=20
B4 -- C4 cost=20

Leo @-0.3,0.7 proto=none icon=👦🏻
Mia @-0.3,1.3 proto=none icon=👧🏽
Joy @0.3,0.7  proto=none icon=👱🏻‍♀️
Roy @0.3,1.3  proto=none icon=👨🏾
A2 -- Leo hazard=0 A2:edge
A2 -- Mia hazard=0 A2:edge
A2 -- Joy hazard=0 A2:edge
A2 -- Roy hazard=0 A2:edge

Max @-0.3,1.7 proto=none icon=👨🏽
Zoe @-0.3,2.3 proto=none icon=👩🏾
Ada @0.3,1.7  proto=none icon=👵🏾
Amy @0.3,2.3  proto=none icon=👩🏼
A3 -- Max hazard=0 A3:edge
A3 -- Zoe hazard=0 A3:edge
A3 -- Ada hazard=0 A3:edge
A3 -- Amy hazard=0 A3:edge

Eli @0.7,0.7 proto=none icon=👦🏼
Jay @0.7,1.3 proto=none icon=👨🏻
Kai @1.3,0.7  proto=none icon=🧑🏽
Ben @1.3,1.3  proto=none icon=👱🏼
B2 -- Eli hazard=0.2 B2:edge
B2 -- Jay hazard=0.2 B2:edge
B2 -- Kai hazard=0.2 B2:edge
B2 -- Ben hazard=0.2 B2:edge

Ava @0.7,1.7 proto=none icon=👩🏻
Lea @0.7,2.3 proto=none icon=🧑🏾‍🦱
Ivy @1.3,1.7  proto=none icon=🧕🏽
Rex @1.3,2.3  proto=none icon=👴🏿
B3 -- Ava hazard=0.2 B3:edge
B3 -- Lea hazard=0.2 B3:edge
B3 -- Ivy hazard=0.2 B3:edge
B3 -- Rex hazard=0.2 B3:edge

Ana @1.7,0.7 proto=none icon=👩🏿
Eve @1.7,1.3 proto=none icon=👧🏼
Abe @2.3,0.7  proto=none icon=🧓🏿
Ian @2.3,1.3  proto=none icon=🧔🏾
C2 -- Ana hazard=0 C2:edge
C2 -- Eve hazard=0 C2:edge
C2 -- Abe hazard=0 C2:edge
C2 -- Ian hazard=0 C2:edge

Ned @1.7,1.7 proto=none icon=👨🏼‍🦳
Lou @1.7,2.3 proto=none icon=🧑🏿
Fay @2.3,1.7  proto=none icon=👧🏻
Sue @2.3,2.3  proto=none icon=👩🏽‍🦰
C3 -- Ned hazard=0 C3:edge
C3 -- Lou hazard=0 C3:edge
C3 -- Fay hazard=0 C3:edge
C3 -- Sue hazard=0 C3:edge
</code></pre></div>

<div class="admonition">
<p class="admonition-title">Note</p>
<p>This article is also available as a <a href="https://vincent.bernat.ch/en/blog/2026-spanning-tree-video" title="A non-interactive tour of the spanning tree protocol">video</a>, but I advise you to
keep reading here to try the interactive demonstrations.</p>
</div>
<div class="toc">
<ul>
<li><a href="https://vincent.bernat.ch#the-basics">The basics</a><ul>
<li><a href="https://vincent.bernat.ch#historical-interlude">Historical interlude</a></li>
<li><a href="https://vincent.bernat.ch#electing-the-root-bridge">Electing the root bridge</a></li>
<li><a href="https://vincent.bernat.ch#assigning-roles-to-ports">Assigning roles to ports</a></li>
<li><a href="https://vincent.bernat.ch#port-state-transition">Port state transition</a></li>
<li><a href="https://vincent.bernat.ch#topology-change-notification">Topology change notification</a></li>
<li><a href="https://vincent.bernat.ch#security">Security</a></li>
</ul>
</li>
<li><a href="https://vincent.bernat.ch#why-rstp-today">Why RSTP today?</a><ul>
<li><a href="https://vincent.bernat.ch#how-large-can-a-network-be">How large can a network be?</a></li>
<li><a href="https://vincent.bernat.ch#how-fast-is-rstp">How fast is RSTP?</a></li>
</ul>
</li>
<li><a href="https://vincent.bernat.ch#about-mstp">About MSTP</a></li>
<li><a href="https://vincent.bernat.ch#about-the-interactive-examples">About the interactive examples</a></li>
</ul>
</div>
<h1 id="the-basics">The basics</h1>
<p>Designed in the ’80s, the <em>spanning tree protocol</em> has evolved into a “rapid”
flavor (<abbr title="Rapid Spanning Tree Protocol">RSTP</abbr>) and a “VLAN-aware” variation (<abbr title="Multiple Spanning Tree Protocol">MSTP</abbr>).<sup id="fnref:history"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:history">2</a></sup> Any sound-minded
network engineer knows there are better alternatives, like <a href="https://vincent.bernat.ch/en/blog/2017-vxlan-bgp-evpn" title="VXLAN: BGP EVPN with FRR">BGP EVPN VXLAN</a>.
Yet, because any switch speaks it, the venerable spanning tree protocol still
fills a niche.</p>
<p>We focus on <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr>: it replaced the original protocol in 2004. To eliminate
network loops, <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> implements a complex state machine. Timers, link state
changes, and the link-local control frames a bridge receives from its neighbors
drive its transitions. These Ethernet frames are the <em>Bridge Protocol Data
Units</em> (<abbr title="Bridge Protocol Data Units">BPDUs</abbr>). You can watch them in action below: hit the “Start” button.</p>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>:protocol rstp
:tx-hold 10

A1 @0,1
C11 @1,0 prio=4096 icon=🌳
C12 @1,2 prio=4096 icon=🌳
C21 @2,0 prio=4096 icon=🌳
C22 @2,2 prio=4096 icon=🌳
A2 @3,1

H1 @0,0.2 proto=none icon=💻
H2 @0,1.8 proto=none icon=🖨️
H3 @3,0.2 proto=none icon=📠
H4 @3,1.8 proto=none icon=📺

A1 -- C11
A1 -- C12
A2 -- C21
A2 -- C22
C11 -- C12
C11 -- C21
C11 -- C21
C11 -- C22
C12 -- C21
C12 -- C22
C21 -- C22
A1 -- H1 A1:edge
A1 -- H2 A1:edge
A2 -- H3 A2:edge
A2 -- H4 A2:edge
</code></pre></div>

<p>After <a href="https://vincent.bernat.ch#mstp:5,...">some time</a>, the topology converges to a tree: from the root
C11, there is a path to each bridge<sup id="fnref:bridge"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:bridge">3</a></sup> and no loop. In the upper right
corner, the interface displays a tree icon 🌳 followed by the time it took to
reach this state. <a href="https://vincent.bernat.ch#mstp:9,C11--C12,...">Cut a link</a> and see how the protocol
finds an alternate path to reach C12 in less than a second. You can stop the
simulation, move it forward step by step, reset it to its initial state, or slow
it down with the “snail” mode 🐌. Don’t worry about all the displayed
information: I explain it later.</p>
<div class="admonition">
<p class="admonition-title">Note</p>
<p>While you scroll, the current simulation stays in view so you can
look at it while reading. Uncheck this box to disable this behavior: <input checked="checked" id="lf-mstp-sticky" type="checkbox" />.</p>
</div>
<p>All examples run in your browser, powered by <a href="https://github.com/mstpd/mstpd" title="Multiple Spanning Tree Protocol Daemon">MSTPD</a>—an open-source
user-space<sup id="fnref:kernel"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:kernel">4</a></sup> implementation of <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr>.<sup id="fnref:incomplete"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:incomplete">5</a></sup></p>
<h2 id="historical-interlude">Historical interlude</h2>
<p><a href="https://hiddenheroes.netguru.com/radia-perlman" title="The Magic Box and the Spanning Tree: How Radia Perlman made the Internet work">Radia Perlman</a>, an inductee of the <a href="https://www.internethalloffame.org/inductee/radia-perlman/" title="Radia Perlman profile on the Internet Hall of Fame">Internet Hall of Fame</a> in 2014,
summarized the ancestor of <abbr title="Spanning Tree Protocol">STP</abbr> she invented at <abbr title="Digital Equipment Corporation">DEC</abbr> with this poem, later
included in a <a href="https://patents.google.com/patent/US7339900B2/en" title="Method and apparatus for preventing spanning tree loops during traffic overload conditions">US patent</a>:</p>
<blockquote>
<p>I think that I shall never see<br />
A graph more lovely than a tree.<br />
A tree whose crucial property<br />
Is loop-free connectivity.<br />
A tree which must be sure to span<br />
So packets can reach every LAN.<br />
First, the root must be selected.<br />
By ID, it is elected.<br />
Least cost paths from root are traced.<br />
In the tree, these paths are placed.<br />
A mesh is made by folks like me,<br />
Then bridges find a spanning tree.</p>
<p>― <em>Radia Perlman</em>, <a href="https://hiddenheroes.netguru.com/radia-perlman" title="The Magic Box and the Spanning Tree: How Radia Perlman made the Internet work">Algorhyme</a>.</p>
</blockquote>
<h2 id="electing-the-root-bridge">Electing the root bridge</h2>
<p>To build a tree, <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> first elects the bridge with the <strong>lowest bridge
identifier</strong> as the <strong>root bridge</strong>. The bridge identifier combines the priority
and the MAC address: <code>8192.6e:2b:10:a0:5f:29</code>.</p>
<p>In the example below, S1 and S2 have priorities of 4,096 and 8,192: S1 becomes
root. S4 has a priority of 12,288, while S3 keeps the default priority of
32,768:<sup id="fnref:priority"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:priority">6</a></sup> S4 becomes root. S5 and S6 don’t have a specific priority, so
the lowest MAC address wins and S5 becomes root.</p>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>:protocol rstp

S1 @0,0 prio=4096
S2 @0,1 prio=8192
S1 -- S2

S3 @1,0
S4 @1,1 prio=12288
S3 -- S4

S5 @2,0
S6 @2,1
S5 -- S6
</code></pre></div>

<p><a href="https://vincent.bernat.ch#mstp:2,S2-&gt;S1,@">Initially</a>, each bridge advertises itself as
root:<sup id="fnref:wireshark"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:wireshark">7</a></sup></p>
<div class="language-wireshark codehilite"><pre><span></span><code><span class="nc">Spanning Tree Protocol</span>
<span class="ss">    Protocol Identifier</span>: Spanning Tree Protocol <span class="p">(</span><span class="m">0x0000</span><span class="p">)</span>
<span class="ss">    Protocol Version Identifier</span>: Rapid Spanning Tree <span class="p">(</span><span class="m">2</span><span class="p">)</span>
<span class="ss">    BPDU Type</span>: Rapid/Multiple Spanning Tree <span class="p">(</span><span class="m">0x02</span><span class="p">)</span>
<span class="hll"><span class="ss">    Root Identifier</span>: 8192.02:00:00:01:00:01
</span><span class="hll"><span class="ss">    Bridge Identifier</span>: 8192.02:00:00:01:00:01
</span></code></pre></div>

<p>Once a bridge receives a <abbr title="Bridge Protocol Data Unit">BPDU</abbr> advertising a better root bridge, it
<a href="https://vincent.bernat.ch#mstp:3,S2-&gt;S1,@">propagates</a> this new information to its neighbors.</p>
<div class="language-wireshark codehilite"><pre><span></span><code><span class="nc">Spanning Tree Protocol</span>
<span class="ss">    Protocol Identifier</span>: Spanning Tree Protocol <span class="p">(</span><span class="m">0x0000</span><span class="p">)</span>
<span class="ss">    Protocol Version Identifier</span>: Rapid Spanning Tree <span class="p">(</span><span class="m">2</span><span class="p">)</span>
<span class="ss">    BPDU Type</span>: Rapid/Multiple Spanning Tree <span class="p">(</span><span class="m">0x02</span><span class="p">)</span>
<span class="hll"><span class="ss">    Root Identifier</span>: 4096.02:00:00:00:00:00
</span><span class="hll"><span class="ss">    Bridge Identifier</span>: 8192.02:00:00:00:00:01
</span></code></pre></div>

<h2 id="assigning-roles-to-ports">Assigning roles to ports</h2>
<p>The second step is to assign a role to each port. <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> defines five roles, each
denoted by a letter:</p>
<ul>
<li>root (R),</li>
<li>designated (D),</li>
<li>alternate (A),</li>
<li>disabled (X), or</li>
<li>backup (B).<sup id="fnref:backup"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:backup">8</a></sup></li>
</ul>
<p>Each non-root bridge chooses its <strong>root port</strong>, the one with the lowest-cost
path to the root. Unless you override it, each bridge derives the link cost
from the speed: 20,000 for 1 Gbps. In case of equality, the lowest port
identifier wins.</p>
<p>Each remaining port becomes a <strong>designated port</strong> if the <abbr title="Bridge Protocol Data Unit">BPDU</abbr> it sends is
“better” than the <abbr title="Bridge Protocol Data Unit">BPDU</abbr> it receives. Otherwise, it becomes an <strong>alternate port</strong>.
Later, if the root port goes down, the “best” alternate port becomes the new
root port. The tiebreakers for the best <abbr title="Bridge Protocol Data Unit">BPDU</abbr> are:</p>
<ol>
<li>the lowest root bridge identifier,</li>
<li>the lowest accumulated cost to the root,</li>
<li>the lowest bridge identifier, and</li>
<li>the lowest port identifier.</li>
</ol>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>:protocol rstp

S1 @1,0  prio=4096 icon=🌳
S2 @0,1
S3 @2,1

S1 -- S2
S1 -- S3
S1 -- S3
S2 -- S3
</code></pre></div>

<p>In the example above, <a href="https://vincent.bernat.ch#mstp:13">after convergence</a>, S1 is the root bridge
because it has a priority of 4,096, while the other bridges have a priority of
32,768. All its ports are designated ports because the accumulated cost to the
root is 0.</p>
<p>S2’s port facing S1 becomes a root port because it has the lowest accumulated
cost to the root—20,000 vs 40,000. S3 has two ports facing S1, and the one with
the lowest port identifier becomes the root port—<code>0x8000</code> vs <code>0x8001</code>. The other
candidate is an alternate port because the remote port on the link sends a
better <abbr title="Bridge Protocol Data Unit">BPDU</abbr>, with an accumulated cost of 0. On the segment between S2 and S3,
S2’s port wins: while both bridges have the same accumulated cost to the root
(20,000), S2’s bridge identifier is smaller—<code>32768.02:00:00:00:00:01</code> vs
<code>32768.02:00:00:00:00:02</code>.</p>
<div class="language-wireshark codehilite"><pre><span></span><code><span class="nc">Spanning Tree Protocol</span>
<span class="ss">    Protocol Identifier</span>: Spanning Tree Protocol <span class="p">(</span><span class="m">0x0000</span><span class="p">)</span>
<span class="ss">    Protocol Version Identifier</span>: Rapid Spanning Tree <span class="p">(</span><span class="m">2</span><span class="p">)</span>
<span class="ss">    BPDU Type</span>: Rapid/Multiple Spanning Tree <span class="p">(</span><span class="m">0x02</span><span class="p">)</span>
<span class="hll"><span class="ss">    Root Identifier</span>: 4096.02:00:00:00:00:00
</span><span class="hll"><span class="ss">    Root Path Cost</span>: 20000
</span><span class="hll"><span class="ss">    Bridge Identifier</span>: 32768.02:00:00:00:00:01
</span><span class="ss">    Port identifier</span>: 0x8002
</code></pre></div>

<p>If you <a href="https://vincent.bernat.ch#mstp:13,S1--S3:1">cut the active link between S1 and S3</a>, S3 promotes
the “best” alternate port to root port. If you also <a href="https://vincent.bernat.ch#mstp:13,S1--S3:1,S1--S3:2">disable the second
link</a>, S3 chooses the remaining alternate port as a
root port. But if you <a href="https://vincent.bernat.ch#mstp:13,S1--S2,@,...">disable the link between S1 and
S2</a>, S2 needs a bit more work to elect a new root port
because it does not have an alternate port.</p>
<p>Unless a specific event happens, designated ports send <abbr title="Bridge Protocol Data Units">BPDUs</abbr> <a href="https://vincent.bernat.ch#mstp:13,S1-&gt;S3:2,S2-&gt;S3,S1-&gt;S2,@">every 2
seconds</a>.<sup id="fnref:hello"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:hello">9</a></sup> If a bridge does not
receive <abbr title="Bridge Protocol Data Units">BPDUs</abbr> from its neighbor for 3 consecutive hello periods, it considers
the neighbor dead and removes the port information.</p>
<h2 id="port-state-transition">Port state transition</h2>
<p>Each port can have one of three states. The diagram displays a background color
for each state:</p>
<ul>
<li>discarding (red),</li>
<li>learning (yellow), or</li>
<li>forwarding (green).</li>
</ul>
<p>A <em>root port</em> transitions automatically to the forwarding state. An <em>alternate
port</em> stays in the discarding state. A <em>designated port</em> has two options to
transition from the discarding state to the forwarding state:</p>
<ul>
<li>If the port is an <strong>edge port</strong>, either through configuration or because the
  remote device does not speak any flavor of <abbr title="Spanning Tree Protocol">STP</abbr>, the bridge assumes the device
  won’t participate in the protocol and cannot create a loop. In this case, the
  designated port immediately transitions to the forwarding state.</li>
<li>Otherwise, it sends a <strong>proposal</strong> to its downstream neighbor. If the remote
  bridge agrees that the received <abbr title="Bridge Protocol Data Unit">BPDU</abbr> is “better” than any other <abbr title="Bridge Protocol Data Unit">BPDU</abbr> stored
  for other ports, it elects the receiving port as its root port and starts the
  <strong>synchronization</strong> process: it transitions all non-edge non-synced designated
  ports to the discarding state to avoid a loop. Then, it sends back an
  <strong>agreement</strong>. Upon receiving the agreement, the peer designated port
  transitions to the forwarding state.<sup id="fnref:learning"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:learning">10</a></sup></li>
</ul>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>:protocol rstp

S1 @1,0 prio=4096 icon=🌳
S2 @1,1
S3 @0,2
S4 @2,2
S5 @0,3 prio=8192 icon=🪾
S6 @2,3
H1 @0,1.2   proto=none icon=🖨️
H2 @2,1.2   proto=none icon=📠
H3 @2.5,1.3 proto=none icon=📺
H4 @2.5,2.3 proto=none icon=💻

S1 -- S2
S2 -- S3
S2 -- S4
S3 -- S5
S4 -- S6
S4 -- S3
S5 -- S6

S3 -- H1 S3:edge
S4 -- H2 S4:edge
S4 -- H3 S4:edge
S6 -- H4 S6:edge
</code></pre></div>

<p>In the topology above, H1, H2, H3, and H4 are end devices not participating in
the protocol. We configure the ports they connect to as edge ports, so these
ports immediately move to the forwarding state.</p>
<p>Use the “step” button to move the simulation forward. The clock moves to 1
second. <a href="https://vincent.bernat.ch#mstp:2,S1-&gt;S2,S2-&gt;S1,@">Step again</a> and S1 and S2 send a proposal to
each other. Here is the proposal from S2:</p>
<div class="language-wireshark codehilite"><pre><span></span><code><span class="nc">Spanning Tree Protocol</span>
<span class="ss">    Protocol Identifier</span>: Spanning Tree Protocol <span class="p">(</span><span class="m">0x0000</span><span class="p">)</span>
<span class="ss">    Protocol Version Identifier</span>: Rapid Spanning Tree <span class="p">(</span><span class="m">2</span><span class="p">)</span>
<span class="ss">    BPDU Type</span>: Rapid/Multiple Spanning Tree <span class="p">(</span><span class="m">0x02</span><span class="p">)</span>
<span class="ss">    BPDU flags</span>: 0x4e, Agreement, Port Role: Designated, Proposal
        <span class="no">0... .... </span><span class="o">= </span><span class="ss">Topology Change Acknowledgment</span>: No
        <span class="no">.1.. .... </span><span class="o">= </span><span class="ss">Agreement</span>: Yes
        <span class="no">..0. .... </span><span class="o">= </span><span class="ss">Forwarding</span>: No
        <span class="no">...0 .... </span><span class="o">= </span><span class="ss">Learning</span>: No
<span class="hll">        <span class="no">.... 11.. </span><span class="o">= </span><span class="ss">Port Role</span>: Designated <span class="p">(</span><span class="m">3</span><span class="p">)</span>
</span><span class="hll">        <span class="no">.... ..1. </span><span class="o">= </span><span class="ss">Proposal</span>: Yes
</span>        <span class="no">.... ...0 </span><span class="o">= </span><span class="ss">Topology Change</span>: No
<span class="hll"><span class="ss">    Root Identifier</span>: 32768.02:00:00:00:00:01
</span><span class="ss">    Root Path Cost</span>: 0
<span class="ss">    Bridge Identifier</span>: 32768.02:00:00:00:00:01
<span class="ss">    Port identifier</span>: 0x8001
</code></pre></div>

<p>S1 ignores it: its own root identifier is lower. When S2 receives a similar
proposal from S1, it accepts S1 as its root bridge. It also elects the port to
S1 as the root port and starts the synchronization process. The two designated
ports are already discarding, so no change here. <a href="https://vincent.bernat.ch#mstp:3,S2-&gt;S1#2,@">Step
again</a> and S2 sends two <abbr title="Bridge Protocol Data Units">BPDUs</abbr> to S1. In one of them, the
agreement bit is 1 and the proposal bit is 0. It also shows that S2 accepted S1
as the root bridge and its root port is now in the forwarding state. When
receiving this <abbr title="Bridge Protocol Data Unit">BPDU</abbr>, S1 transitions its own designated port to the forwarding
state. From this point, the link between S1 and S2 forwards user traffic.</p>
<div class="language-wireshark codehilite"><pre><span></span><code><span class="nc">Spanning Tree Protocol</span>
<span class="ss">    Protocol Identifier</span>: Spanning Tree Protocol <span class="p">(</span><span class="m">0x0000</span><span class="p">)</span>
<span class="ss">    Protocol Version Identifier</span>: Rapid Spanning Tree <span class="p">(</span><span class="m">2</span><span class="p">)</span>
<span class="ss">    BPDU Type</span>: Rapid/Multiple Spanning Tree <span class="p">(</span><span class="m">0x02</span><span class="p">)</span>
<span class="ss">    BPDU flags</span>: 0x79, Agreement, Forwarding, Learning, Port Role: Root, Topology Change
        <span class="no">0... .... </span><span class="o">= </span><span class="ss">Topology Change Acknowledgment</span>: No
<span class="hll">        <span class="no">.1.. .... </span><span class="o">= </span><span class="ss">Agreement</span>: Yes
</span><span class="hll">        <span class="no">..1. .... </span><span class="o">= </span><span class="ss">Forwarding</span>: Yes
</span>        <span class="no">...1 .... </span><span class="o">= </span><span class="ss">Learning</span>: Yes
<span class="hll">        <span class="no">.... 10.. </span><span class="o">= </span><span class="ss">Port Role</span>: Root <span class="p">(</span><span class="m">2</span><span class="p">)</span>
</span><span class="hll">        <span class="no">.... ..0. </span><span class="o">= </span><span class="ss">Proposal</span>: No
</span>        <span class="no">.... ...1 </span><span class="o">= </span><span class="ss">Topology Change</span>: Yes
<span class="hll"><span class="ss">    Root Identifier</span>: 4096.02:00:00:00:00:00
</span><span class="ss">    Root Path Cost</span>: 20000
<span class="ss">    Bridge Identifier</span>: 32768.02:00:00:00:00:01
<span class="ss">    Port identifier</span>: 0x8001
</code></pre></div>

<p>Let’s look at what happened to S5. <a href="https://vincent.bernat.ch#mstp:2,S5-&gt;S3,S3-&gt;S5,S5-&gt;S6,S6-&gt;S5,@">Reset the simulation and step
twice</a>. S5 exchanges <abbr title="Bridge Protocol Data Units">BPDUs</abbr> with both S3
and S6. Since S5 has a lower root identifier than S3 and S6, it stays the root
bridge, while S3 and S6 accept the proposal and elect their root ports. S3 and
S6 start the synchronization process. S6’s port to H4 keeps forwarding because
it is an edge port. <a href="https://vincent.bernat.ch#mstp:3,S3-&gt;S5#1,S6-&gt;S5#1,@">Move one step</a>. Both S3 and S6 send
an agreement back to S5, which transitions both designated ports to the
forwarding state. Yet, the link between S5 and S3 keeps discarding user traffic!
If you look carefully, S3’s port toward S5 is now a designated port, not a root
port. During the <a href="https://vincent.bernat.ch#mstp:3,S2-&gt;S3,@">same step</a>, S3 also receives a better <abbr title="Bridge Protocol Data Unit">BPDU</abbr>
from S2 with S1 as the root bridge. It elects its port to S2 as the root port
and downgrades the port to S5 to a designated port, which stays in the
discarding state.</p>
<p>On the <a href="https://vincent.bernat.ch#mstp:4,S3-&gt;S5,@">next step</a>, things get a bit tricky. S3 sends a
proposal to S5:<sup id="fnref:agreement"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:agreement">11</a></sup></p>
<div class="language-wireshark codehilite"><pre><span></span><code><span class="nc">Spanning Tree Protocol</span>
<span class="ss">    Protocol Identifier</span>: Spanning Tree Protocol <span class="p">(</span><span class="m">0x0000</span><span class="p">)</span>
<span class="ss">    Protocol Version Identifier</span>: Rapid Spanning Tree <span class="p">(</span><span class="m">2</span><span class="p">)</span>
<span class="ss">    BPDU Type</span>: Rapid/Multiple Spanning Tree <span class="p">(</span><span class="m">0x02</span><span class="p">)</span>
<span class="ss">    BPDU flags</span>: 0x4f, Agreement, Port Role: Designated, Proposal, Topology Change
        <span class="no">0... .... </span><span class="o">= </span><span class="ss">Topology Change Acknowledgment</span>: No
        <span class="no">.1.. .... </span><span class="o">= </span><span class="ss">Agreement</span>: Yes
        <span class="no">..0. .... </span><span class="o">= </span><span class="ss">Forwarding</span>: No
        <span class="no">...0 .... </span><span class="o">= </span><span class="ss">Learning</span>: No
        <span class="no">.... 11.. </span><span class="o">= </span><span class="ss">Port Role</span>: Designated <span class="p">(</span><span class="m">3</span><span class="p">)</span>
<span class="hll">        <span class="no">.... ..1. </span><span class="o">= </span><span class="ss">Proposal</span>: Yes
</span>        <span class="no">.... ...1 </span><span class="o">= </span><span class="ss">Topology Change</span>: Yes
<span class="hll"><span class="ss">    Root Identifier</span>: 4096.02:00:00:00:00:00
</span><span class="ss">    Root Path Cost</span>: 40000
<span class="ss">    Bridge Identifier</span>: 32768.02:00:00:00:00:02
<span class="ss">    Port identifier</span>: 0x8002
</code></pre></div>

<p>S5 elects S1 as its root bridge and the port toward S3 as its root port. It
starts its synchronization process, but the designated port to S6 does <em>not</em>
move into the discarding state. Why? That port stays a designated port and its
neighbor S6 has already sent an agreement on the link, so the port keeps its
synced status.</p>
<p>Now, let’s <a href="https://vincent.bernat.ch#mstp:3,@">step back</a> to look at what happens to S6. At this point,
S6 believes S5 is the root bridge. <a href="https://vincent.bernat.ch#mstp:4,S4-&gt;S6#1,@">Step once</a> and S4 sends
a new proposal to S6. S6 accepts the proposal, elects S1 as the root bridge and
the port to S4 as its root port. The role of the port facing S5 changes: from a
root port, it becomes a designated port. Because its peer keeps advertising an
inferior <abbr title="Bridge Protocol Data Unit">BPDU</abbr> on the link, this port becomes disputed and moves to the
discarding state. The root port transitions to the forwarding state and the link
starts forwarding immediately because S4’s designated port is already in the
forwarding state. If we <a href="https://vincent.bernat.ch#mstp:5,S5-&gt;S6,S6-&gt;S5,@">step one more time</a>, S5 and S6
exchange two <abbr title="Bridge Protocol Data Units">BPDUs</abbr>. The one from S5 is better because of its lower bridge
identifier. S5’s port stays a designated port, while S6 downgrades its own port
to an alternate port.</p>
<p>Let’s rewind to the start one last time: cut the link between S1 and S2, <a href="https://vincent.bernat.ch#mstp:S1--S2,9">run
the simulation until the topology is stable</a>, stop the
simulation, and restore the link between S1 and S2. During the <a href="https://vincent.bernat.ch#mstp:S1--S2,9,S1--S2,1,S1-&gt;S2,S2-&gt;S1,@">first
step</a>, S1 and S2 exchange proposals. S2
elects S1 as the root bridge instead of S5 and the port to S1 as the root port.
It downgrades the previous root port to a designated port and moves it into the
discarding state. The other designated port stays synced and keeps its
forwarding state. At the <a href="https://vincent.bernat.ch#mstp:S1--S2,9,S1--S2,2,S2-&gt;S1,@">next step</a>, S2 sends
an agreement to S1 and the link between them starts forwarding user traffic. It
also <a href="https://vincent.bernat.ch#mstp:S1--S2,9,S1--S2,2,S2-&gt;S3,@">sends a proposal to S3</a>, but not to S4.
Instead, <a href="https://vincent.bernat.ch#mstp:S1--S2,9,S1--S2,2,S2-&gt;S4,@">it sends a regular <abbr title="Bridge Protocol Data Unit">BPDU</abbr> to S4</a>. S4
still elects S1 as its root bridge and the port to S2 as its root port. It
demotes its previous root port, the one to S3, to a designated port, which
transitions to the discarding state because of the root port change. The other
alternate port, to S6, also becomes a designated port and stays in the
discarding state. The new root port moves to the forwarding state. On the <a href="https://vincent.bernat.ch#mstp:S1--S2,9,S1--S2,3,S3-&gt;S4#1,@">next
step</a>, S4’s port to S3 settles as an
alternate port after receiving a “better” <abbr title="Bridge Protocol Data Unit">BPDU</abbr> from S3.</p>
<p><abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> is a giant state machine split into smaller ones: bridge detection, port
information, port protocol migration, port role selection, port role
transitions, port receive, port state transitions, port timers, port transmit,
and topology change. Some of them are per bridge, some per port. Each bridge
runs an instance. Time, operational port state changes, and the <abbr title="Bridge Protocol Data Units">BPDUs</abbr> it
receives from other instances drive the transitions. Being event-driven makes
<abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> more efficient but also more difficult to understand.</p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 900px;"><span class="lf-media-inner"><img alt="Western Australian Government Railways class Msa Garratt articulated steam locomotive: elevation and plan drawing" class="lf-media lf-opaque" height="390" src="https://d2pzklc15kok91.cloudfront.net/images/msa-garratt@1x.77dc2f6581ab04.jpg" width="900" /></span></div>Placeholder for the <em>Port Information</em> state machine extracted from IEEE 802.1Q-2005, page 182. Pending IEEE authorization for reproduction, this is the blueprint for the Western Australian Government Railways class Msa Garratt articulated steam locomotive.</figure>
<h2 id="topology-change-notification">Topology change notification</h2>
<p>A bridge populates a MAC address table: it associates each source MAC address
with the port that last received it. When forwarding an Ethernet frame, it looks
up this table to choose the right port.<sup id="fnref:bum"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:bum">12</a></sup> When a link fails, a connected
fridge reachable through one port may become reachable through another one. The
affected bridges should flush the MAC addresses they learned, because these
entries may now be wrong.</p>
<p>For this purpose, <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> implements <em>topology change notifications</em> using a
flooding mechanism. When a non-edge port transitions to the forwarding state, a
bridge generates <abbr title="Bridge Protocol Data Units">BPDUs</abbr> with the <em>topology change</em> (<abbr title="Topology Change">TC</abbr>) bit set. It sends them to
all the non-edge designated ports and to the root port. It also flushes the MAC
address table on these ports. When a bridge receives such a <abbr title="Bridge Protocol Data Unit">BPDU</abbr>, it propagates
the notification to all non-edge designated ports and the root port, except the
one the notification came from. It also flushes the MAC address table on these
ports. In the examples, the <abbr title="Bridge Protocol Data Units">BPDUs</abbr> with the <abbr title="Topology Change">TC</abbr> bit set to 1 have a red circle.</p>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>:protocol rstp

S1 @1,0 prio=4096 icon=🌳
S2 @0,1
S3 @1,1
S4 @2,1
S5 @1,2
LPT @0.1,2 proto=none icon=🖨️

S1 -- S2
S1 -- S3
S1 -- S4
S2 -- S3
S2 -- S5
S4 -- S5
S5 -- LPT S5:edge
</code></pre></div>

<p>Start the simulation and wait a <a href="https://vincent.bernat.ch#mstp:10">few seconds</a> for the topology to
settle. Stop the simulation and <a href="https://vincent.bernat.ch#mstp:10,S2--S5">disable the link between S2 and
S5</a>. S5 elects the port facing S4 as the root port, which
transitions immediately to the forwarding state. <a href="https://vincent.bernat.ch#mstp:10,S2--S5,1,S5-&gt;S4,@">Step
once</a> and S5 emits a <abbr title="Bridge Protocol Data Unit">BPDU</abbr> with the <abbr title="Topology Change">TC</abbr> bit set to 1:</p>
<div class="language-wireshark codehilite"><pre><span></span><code><span class="nc">Spanning Tree Protocol</span>
<span class="ss">    Protocol Identifier</span>: Spanning Tree Protocol <span class="p">(</span><span class="m">0x0000</span><span class="p">)</span>
<span class="ss">    Protocol Version Identifier</span>: Rapid Spanning Tree <span class="p">(</span><span class="m">2</span><span class="p">)</span>
<span class="ss">    BPDU Type</span>: Rapid/Multiple Spanning Tree <span class="p">(</span><span class="m">0x02</span><span class="p">)</span>
<span class="ss">    BPDU flags</span>: 0x79, Agreement, Forwarding, Learning, Port Role: Root, Topology Change
        <span class="no">0... .... </span><span class="o">= </span><span class="ss">Topology Change Acknowledgment</span>: No
        <span class="no">.1.. .... </span><span class="o">= </span><span class="ss">Agreement</span>: Yes
        <span class="no">..1. .... </span><span class="o">= </span><span class="ss">Forwarding</span>: Yes
        <span class="no">...1 .... </span><span class="o">= </span><span class="ss">Learning</span>: Yes
        <span class="no">.... 10.. </span><span class="o">= </span><span class="ss">Port Role</span>: Root <span class="p">(</span><span class="m">2</span><span class="p">)</span>
        <span class="no">.... ..0. </span><span class="o">= </span><span class="ss">Proposal</span>: No
<span class="hll">        <span class="no">.... ...1 </span><span class="o">= </span><span class="ss">Topology Change</span>: Yes
</span><span class="ss">    Root Identifier</span>: 4096.02:00:00:00:00:00
<span class="ss">    Root Path Cost</span>: 40000
<span class="ss">    Bridge Identifier</span>: 32768.02:00:00:00:00:04
<span class="ss">    Port identifier</span>: 0x8002
</code></pre></div>

<p>S4 receives this <abbr title="Bridge Protocol Data Unit">BPDU</abbr>. It flushes the MAC address table on the port facing S1:
while LPT was previously reachable through this port, it is now reachable
through S5. <a href="https://vincent.bernat.ch#mstp:10,S2--S5,2,S4-&gt;S1,@">Step once</a>. S4 sends S1 a <abbr title="Bridge Protocol Data Unit">BPDU</abbr>
with the <abbr title="Topology Change">TC</abbr> bit set to 1. When S1 receives this <abbr title="Bridge Protocol Data Unit">BPDU</abbr>, it flushes the MAC address
table on the ports facing S2 and S3. <a href="https://vincent.bernat.ch#mstp:10,S2--S5,3,S1-&gt;S2,S1-&gt;S3,@">Step
once</a> and S1 sends a notification to S2 and
S3. <a href="https://vincent.bernat.ch#mstp:10,S2--S5,4,S2-&gt;S3,@">Step once again</a> and S2 sends a notification to
S3, while S3 does nothing because the port toward S2 is an <em>alternate port</em>. S3
does not flush any MAC address table: LPT is still reachable through its port to
S1.</p>
<p>If you <a href="https://vincent.bernat.ch#mstp:10,S2--S5,6,@">step a bit more</a>, you will see that some of the
periodic <abbr title="Bridge Protocol Data Units">BPDUs</abbr> keep the <abbr title="Topology Change">TC</abbr> bit set to 1. Each port runs a timer equal to the
hello timer plus one second.<sup id="fnref:timer"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:timer">13</a></sup> The timer starts when the port emits a
notification. Until it expires, the port sets the <abbr title="Topology Change">TC</abbr> bit to 1 in every <abbr title="Bridge Protocol Data Unit">BPDU</abbr> it
sends. You can also see <a href="https://vincent.bernat.ch#mstp:10,S2--S5,7,S1-&gt;S4,S4-&gt;S5,@">some periodic <abbr title="Bridge Protocol Data Units">BPDUs</abbr></a>
without the <abbr title="Topology Change">TC</abbr> bit: they originate from a port that only received a notification
and therefore did not arm its timer.</p>
<h2 id="security">Security</h2>
<p><abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> is weak against configuration errors and malicious actors. A bridge not
talking <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> can create a loop. An attacker can insert themselves into the
topology to disrupt the service, spy on the traffic, or alter it.</p>
<p>To mitigate such problems, you need to identify the edge ports. An edge port
connects to an end device, like a PC or a printer. Such devices do not generate
<abbr title="Bridge Protocol Data Units">BPDUs</abbr> and cannot create a loop. <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> defines two related flags:</p>
<ul>
<li>When true, <strong>AdminEdge</strong> initializes a port as an edge port. It defaults to
  false.</li>
<li>When true, <strong>AutoEdge</strong> lets a port become an edge port when it does not
  receive <abbr title="Bridge Protocol Data Units">BPDUs</abbr> for 3 seconds. It defaults to true.</li>
</ul>
<p>If an edge port receives a <abbr title="Bridge Protocol Data Unit">BPDU</abbr>, regardless of the values of these two flags, it
reverts to a non-edge port.</p>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>R0 @1.5,1.5 prio=8192

# AutoEdge=true, AdminEdge=false, bridge
S1 @3,1.58
R0 -- S1

# AutoEdge=true, AdminEdge=false, end device
H1 @2.84,2.18 icon=🖨️ proto=none
R0 -- H1

# AutoEdge=true, AdminEdge=true, bridge
S2 @2.18,2.84
R0 -- S2 R0:edge

# AutoEdge=true, AdminEdge=true, end device
H2 @1.58,3 icon=💻 proto=none
R0 -- H2 R0:edge

# AutoEdge=false, AdminEdge=true, bridge
S3 @0.68,2.76
R0 -- S3 R0:edge R0:no-auto-edge

# AutoEdge=false, AdminEdge=true, end device
H3 @0.24,2.32 icon=📠 proto=none
R0 -- H3 R0:edge R0:no-auto-edge

# AutoEdge=false, AdminEdge=false, bridge
S4 @0,1.42
R0 -- S4 R0:no-auto-edge

# AutoEdge=false, AdminEdge=false, end device
H4 @0.16,0.82 icon=📺 proto=none
R0 -- H4 R0:no-auto-edge

# Network port, bridge
S5 @0.82,0.16
R0 -- S5 R0:network S5:network

# Network port, end device
H5 @1.42,0 icon=☕ proto=none
R0 -- H5 R0:network

# AdminEdge=true, bpdu-guard=true, bridge
S6 @2.32,0.24
R0 -- S6 R0:bpdu-guard R0:edge

# AdminEdge=true, bpdu-guard=true, end device
H6 @2.76,0.68 icon=💡 proto=none
R0 -- H6 R0:bpdu-guard R0:edge
</code></pre></div>

<p>In the topology above, S1, S2, S3, S4, S5, and S6 act as bridges, while H1, H2,
H3, H4, H5, and H6 act as end devices:</p>
<ul>
<li>S1 and H1 are on a port without a specific configuration: <em>AutoEdge</em> is true,
  <em>AdminEdge</em> is false,</li>
<li>S2 and H2 are on a port where <em>AdminEdge</em> is true,</li>
<li>S3 and H3 are on a port where <em>AutoEdge</em> is false and <em>AdminEdge</em> is true,</li>
<li>S4 and H4 are on a port where <em>AutoEdge</em> is false.</li>
</ul>
<p>If you start the simulation and <a href="https://vincent.bernat.ch#mstp:24">wait about 20 seconds</a>, links to S1,
S2, S3, S4, H1, H2, H3, and H4 eventually forward user traffic: none of the
flags matter.</p>
<p>But what about the two remaining pairs? S5 and H5 connect to a <em>network</em> port.
Such a port enables a non-standard feature: <strong>bridge assurance</strong>. The port
transmits <abbr title="Bridge Protocol Data Units">BPDUs</abbr> regardless of its role. If it does not receive <abbr title="Bridge Protocol Data Units">BPDUs</abbr> for 3
consecutive hello periods, it transitions to the discarding state. On the link
between R0 and S5, you can see <a href="https://vincent.bernat.ch#mstp:24,R0-&gt;S5,S5-&gt;R0,@"><abbr title="Bridge Protocol Data Units">BPDUs</abbr> traveling in both
directions</a>, unlike the other links, where only
designated ports send <abbr title="Bridge Protocol Data Units">BPDUs</abbr>.</p>
<p>S6 and H6 connect to a port where <em>AdminEdge</em> is true and <strong><abbr title="Bridge Protocol Data Unit">BPDU</abbr> guard</strong> is
enabled. This is another non-standard feature that shuts down a port if <a href="https://vincent.bernat.ch#mstp:2,S6-&gt;R0,@">it
receives a <abbr title="Bridge Protocol Data Unit">BPDU</abbr></a>.</p>
<p>In summary, if you expect a port to be an edge port, you should set <em>AdminEdge</em>
to true and enable <em><abbr title="Bridge Protocol Data Unit">BPDU</abbr> guard</em>. Otherwise, declare it as a <em>network</em> port.</p>
<h1 id="why-rstp-today">Why <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> today?</h1>
<p>A compelling use case for <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> today is an out-of-band network for a datacenter,
since you can tolerate an outage of a few seconds. The configuration is minimal
and you can use cheap switches, like a Cisco 2960X.<sup id="fnref:price"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:price">14</a></sup> You need two
switches acting as root bridges, and you build several loops to connect <abbr title="Out-of-band">OOB</abbr>
switches in each cabinet. This simple design survives one failure on each
loop.<sup id="fnref:erps"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:erps">15</a></sup></p>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>:protocol rstp
:tx-hold 10

# Root bridges
R1 @0,1 prio=0
R2 @0,2 prio=4096
R1 -- R2 cost=200 R1:network R2:network
R1 -- R2 cost=200 R1:network R2:network

# First loop
C1  @1,0 icon=🗄️
C4  @2,0 icon=🗄️
C7  @3,0 icon=🗄️
C10 @4,0 icon=🗄️
C12 @5,0 icon=🗄️
C13 @5,3 icon=🗄️
C15 @4,3 icon=🗄️
C18 @3,3 icon=🗄️
C21 @2,3 icon=🗄️
C24 @1,3 icon=🗄️
R1  -- C1  R1:network C1:network
C1  -- C4  C1:network C4:network
C4  -- C7  C4:network C7:network
C7  -- C10 C7:network C10:network
C10 -- C12 C10:network C12:network
C12 -- C13 C12:network C13:network
C13 -- C15 C13:network C15:network
C15 -- C18 C15:network C18:network
C18 -- C21 C18:network C21:network
C21 -- C24 C21:network C24:network
C24 -- R2  C24:network R2:network

# Second loop
C2  @1,0.5 icon=🗄️
C5  @2,0.5 icon=🗄️
C8  @3,0.5 icon=🗄️
C11 @4,0.5 icon=🗄️
C14 @4,2.5 icon=🗄️
C17 @3,2.5 icon=🗄️
C20 @2,2.5 icon=🗄️
C23 @1,2.5 icon=🗄️
R1  -- C2  R1:network C2:network
C2  -- C5  C2:network C5:network
C5  -- C8  C5:network C8:network
C8  -- C11 C8:network C11:network
C11 -- C14 C11:network C14:network
C14 -- C17 C14:network C17:network
C17 -- C20 C17:network C20:network
C20 -- C23 C20:network C23:network
C23 -- R2  C23:network R2:network

# Third loop
C3  @1,1 icon=🗄️
C6  @2,1 icon=🗄️
C9  @3,1 icon=🗄️
C16 @3,2 icon=🗄️
C19 @2,2 icon=🗄️
C22 @1,2 icon=🗄️
R1  -- C3  R1:network C3:network
C3  -- C6  C3:network C6:network
C6  -- C9  C6:network C9:network
C9  -- C16 C9:network C16:network
C16 -- C19 C16:network C19:network
C19 -- C22 C19:network C22:network
C22 -- R2  C22:network R2:network
</code></pre></div>

<p>This topology converges in about <a href="https://vincent.bernat.ch#mstp:6">6 seconds</a>. Each loop should stay
small (around 16 bridges) to reduce the probability of a double failure and to
avoid sharing too much bandwidth. The design can evolve a bit without adding too
much complexity: one VLAN per loop or one bridge domain per loop.</p>
<h2 id="how-large-can-a-network-be">How large can a network be?</h2>
<p>The maximum age, whose default value is 20, governs the maximum distance of a
bridge from the root. The topology below is too big for <abbr title="Bridge Protocol Data Units">BPDUs</abbr> from R1 to reach
beyond S20.<sup id="fnref:root-mac"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:root-mac">16</a></sup></p>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>:protocol rstp
:tx-hold 10
:max-age 20

R1 @0,0 prio=4096 icon=🌳
R2 @0,5 prio=4096 icon=🪾

S1  @1,0
S2  @2,0
S3  @3,0
S4  @4,0
S5  @5,0
S6  @6,0

S7  @6,1
S8  @5,1
S9  @4,1
S10 @3,1
S11 @2,1
S12 @1,1

S13 @1,2
S14 @2,2
S15 @3,2
S16 @4,2
S17 @5,2
S18 @6,2

S19 @6,3
S20 @5,3
S21 @4,3
S22 @3,3
S23 @2,3
S24 @1,3

S25 @1,4
S26 @2,4
S27 @3,4
S28 @4,4
S29 @5,4
S30 @6,4

S31 @6,5
S32 @5,5
S33 @4,5
S34 @3,5
S35 @2,5
S36 @1,5

R1  -- S1
S1  -- S2
S2  -- S3
S3  -- S4
S4  -- S5
S5  -- S6
S6  -- S7
S7  -- S8
S8  -- S9
S9  -- S10
S10 -- S11
S11 -- S12
S12 -- S13
S13 -- S14
S14 -- S15
S15 -- S16
S16 -- S17
S17 -- S18
S18 -- S19
S19 -- S20
S20 -- S21
S21 -- S22
S22 -- S23
S23 -- S24
S24 -- S25
S25 -- S26
S26 -- S27
S27 -- S28
S28 -- S29
S29 -- S30
S30 -- S31
S31 -- S32
S32 -- S33
S33 -- S34
S34 -- S35
S35 -- S36
S36 -- R2
R1  -- R2 cost=200 down
</code></pre></div>

<p>Once the <a href="https://vincent.bernat.ch#mstp:40">topology settles</a>, part of the network considers R1 the
root, while the other votes for R2. At the boundary, S20 tries to start a
synchronization with S21 to move its <em>designated port</em> to the forwarding state.
The <abbr title="Bridge Protocol Data Unit">BPDU</abbr> looks like this:</p>
<div class="language-wireshark codehilite"><pre><span></span><code><span class="nc">Spanning Tree Protocol</span>
<span class="ss">    Protocol Identifier</span>: Spanning Tree Protocol <span class="p">(</span><span class="m">0x0000</span><span class="p">)</span>
<span class="ss">    Protocol Version Identifier</span>: Rapid Spanning Tree <span class="p">(</span><span class="m">2</span><span class="p">)</span>
<span class="ss">    BPDU Type</span>: Rapid/Multiple Spanning Tree <span class="p">(</span><span class="m">0x02</span><span class="p">)</span>
<span class="ss">    BPDU flags</span>: 0x4e, Agreement, Port Role: Designated, Proposal
<span class="ss">    Root Identifier</span>: 4096.02:00:00:00:00:00
<span class="ss">    Root Path Cost</span>: 400000
<span class="ss">    Bridge Identifier</span>: 32768.02:00:00:00:00:15
<span class="ss">    Port identifier</span>: 0x8002
<span class="hll"><span class="ss">    Message Age</span>: 20
</span><span class="hll"><span class="ss">    Max Age</span>: 20
</span></code></pre></div>

<p>S21 rejects it because the message age equals the maximum age. On the other
hand, the <abbr title="Bridge Protocol Data Unit">BPDU</abbr> S21 sends to S20 looks like this:</p>
<div class="language-wireshark codehilite"><pre><span></span><code><span class="nc">Spanning Tree Protocol</span>
<span class="ss">    Protocol Identifier</span>: Spanning Tree Protocol <span class="p">(</span><span class="m">0x0000</span><span class="p">)</span>
<span class="ss">    Protocol Version Identifier</span>: Rapid Spanning Tree <span class="p">(</span><span class="m">2</span><span class="p">)</span>
<span class="ss">    BPDU Type</span>: Rapid/Multiple Spanning Tree <span class="p">(</span><span class="m">0x02</span><span class="p">)</span>
<span class="ss">    BPDU flags</span>: 0x7c, Agreement, Forwarding, Learning, Port Role: Designated
<span class="hll"><span class="ss">    Root Identifier</span>: 4096.02:00:00:00:00:01
</span><span class="ss">    Root Path Cost</span>: 320000
<span class="ss">    Bridge Identifier</span>: 32768.02:00:00:00:00:16
<span class="ss">    Port identifier</span>: 0x8001
<span class="hll"><span class="ss">    Message Age</span>: 16
</span><span class="hll"><span class="ss">    Max Age</span>: 20
</span></code></pre></div>

<p>This is not enough to change S20’s <em>root port</em> because S20 has a lower <em>root
identifier</em>—<code>4096.02:00:00:00:00:00</code> vs <code>4096.02:00:00:00:00:01</code>.</p>
<p><a href="https://vincent.bernat.ch#mstp:40,R1--R2,...">Fixing the link between R1 and R2</a> resolves the issue. The
maximum message age any packet carries is now 18, below the configured maximum
age. But it only works until another link breaks. A plausible fix is to increase
the maximum age to 40.<sup id="fnref:forward-delay"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:forward-delay">17</a></sup></p>
<h2 id="how-fast-is-rstp">How fast is <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr>?</h2>
<p><abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> usually converges in a couple of seconds at startup. It often repairs a
tree in less than a second. Even the <a href="https://vincent.bernat.ch/en/blog/2026-spanning-tree#how-large-can-a-network-be">38-bridge topology</a> takes less than 10
seconds to converge.<sup id="fnref:time"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:time">18</a></sup> Some topologies can take a bit more time to recover
when the root bridge becomes unavailable.<sup id="fnref:slow"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:slow">19</a></sup></p>
<div class="language-text-only mstp-topology codehilite"><pre><span></span><code>:protocol rstp

R0 @1,0 prio=0
S1 @1,1 prio=4096
S2 @0,2 prio=8192
S3 @2,2

R0 -- S1
S1 -- S2
S2 -- S3
S3 -- S1
</code></pre></div>

<p>In the topology above, start the simulation, <a href="https://vincent.bernat.ch#mstp:10">wait for convergence</a>,
hit stop, and <a href="https://vincent.bernat.ch#mstp:10,R0--S1">cut the link between R0 and S1</a>. The topology is
already optimal, but <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> has a hard time converging again.</p>
<p>First, S1 loses its root port. It has no more information about R0 and elects
itself as the root bridge. It keeps its ports to S2 and S3 as designated ports
in the forwarding state. <a href="https://vincent.bernat.ch#mstp:10,R0--S1,1,S1-&gt;S2,S1-&gt;S3,@">Step once</a> and it
sends a <abbr title="Bridge Protocol Data Unit">BPDU</abbr> to both S2 and S3 to let them know about the root change. When
receiving it, S2 accepts S1 as its root because it does not have a better root
on another port. It elects the port to S1 as its root port. The other port stays
a designated port. Both ports keep forwarding.</p>
<p>When receiving the <abbr title="Bridge Protocol Data Unit">BPDU</abbr> from S1, S3 behaves differently: it knows R0 as a better
root than S1 through its alternate port to S2. It promotes this port to a root
port and demotes the port facing S1 to a designated port, which requires a new
agreement. <a href="https://vincent.bernat.ch#mstp:10,R0--S1,2,S3-&gt;S1#1,@">Step once</a> and S3 sends a proposal to
S1 with R0 as the root bridge. S1 elects R0 as the root bridge and promotes its
port to S3 as a root port.</p>
<p>During the <a href="https://vincent.bernat.ch#mstp:10,R0--S1,2,S2-&gt;S3,@">same step</a>, S3 also receives a <abbr title="Bridge Protocol Data Unit">BPDU</abbr> from
S2 stating that S1 is the root bridge. Therefore, S3 has no port left with R0 as
the root bridge: it elects S1 as the root bridge and its port to S2 as the root
port. <a href="https://vincent.bernat.ch#mstp:10,R0--S1,3,S3-&gt;S1,@">Step once</a> and its next <abbr title="Bridge Protocol Data Unit">BPDU</abbr> to S1 includes
this information: S1 elects itself again as the root bridge. But during the
<a href="https://vincent.bernat.ch#mstp:10,R0--S1,3,S1-&gt;S2#1,@">same wave</a>, S1 sends a proposal to S2 with R0 as
the root bridge. While S1 and S3 agree that S1 is the root bridge, S2 now
believes this is R0! <a href="https://vincent.bernat.ch#mstp:10,R0--S1,4,@,...">In turn</a>, S2 again convinces S3
that R0 is the root bridge, S3 convinces S1, S1 convinces S2, and S2 convinces
S3.</p>
<p>This could go on forever, but it does not. The <abbr title="Bridge Protocol Data Units">BPDUs</abbr> saying “R0 is root”
eventually age out when the message age goes past the maximum age. In the
example above, at the <a href="https://vincent.bernat.ch#mstp:10,R0--S1,24,S2-&gt;S3,@">eleventh second</a>, S2 sends a
<abbr title="Bridge Protocol Data Unit">BPDU</abbr> to S3 with R0 as root, but S3 drops it because its message age reached the
maximum. With some luck, the topology can also converge faster if a port stops
transmitting new <abbr title="Bridge Protocol Data Units">BPDUs</abbr> after tripping the transmit hold count, whose default
value is 6 per second.</p>
<h1 id="about-mstp">About <abbr title="Multiple Spanning Tree Protocol">MSTP</abbr></h1>
<p><abbr title="Multiple Spanning Tree Protocol">MSTP</abbr> is the “VLAN-aware” version of <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr>: it runs several instances of <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> and
lets the administrator map each VLAN to a specific instance. For example, you
can map VLANs 100 to 200 to a first instance, and 300 to 400 to a second
instance. The remaining VLANs map to a special instance named the Internal
Spanning Tree (<abbr title="Internal Spanning Tree">IST</abbr>). <abbr title="Multiple Spanning Tree Protocol">MSTP</abbr> adds its own complexity, but the gist is that you have
several logical topologies acting independently. If you want to dig deeper, have
a look at “<a href="https://ine.com/blog/2008-07-27-mstp-tutorial-part-i-inside-a-region" title="MSTP Tutorial Part I: Inside a Region"><abbr title="Multiple Spanning Tree Protocol">MSTP</abbr> Tutorial Part I: Inside a Region</a>.”</p>
<h1 id="about-the-interactive-examples">About the interactive examples</h1>
<p>The interactive examples run <a href="https://github.com/mstpd/mstpd" title="Multiple Spanning Tree Protocol Daemon">MSTPD</a> directly in your browser, compiled to
<a href="https://developer.mozilla.org/en-US/docs/WebAssembly" title="WebAssembly on MDN">WebAssembly</a> with <a href="https://emscripten.org/" title="Emscripten documentation">emscripten</a>. A C API replaces the code talking to the
Linux kernel: it manages bridges and ports, exports state as JSON, and drives
time deterministically. A JavaScript wrapper makes it more user-friendly:</p>
<div class="language-javascript codehilite"><pre><span></span><code><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">loadMSTPD</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s2">"./dist/mstpd.mjs"</span><span class="p">;</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">mstp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">loadMSTPD</span><span class="p">();</span>

<span class="c1">// Create 3 bridges</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mstp</span><span class="p">.</span><span class="nx">createBridge</span><span class="p">(</span><span class="s2">"A"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">priority</span><span class="o">:</span><span class="w"> </span><span class="mf">4096</span><span class="w"> </span><span class="p">});</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mstp</span><span class="p">.</span><span class="nx">createBridge</span><span class="p">(</span><span class="s2">"B"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">priority</span><span class="o">:</span><span class="w"> </span><span class="mf">8192</span><span class="w"> </span><span class="p">});</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mstp</span><span class="p">.</span><span class="nx">createBridge</span><span class="p">(</span><span class="s2">"C"</span><span class="p">);</span>

<span class="c1">// Each bridge has two ports</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">a1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">addPort</span><span class="p">(</span><span class="s2">"a-b"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">portno</span><span class="o">:</span><span class="w"> </span><span class="mf">1</span><span class="w"> </span><span class="p">});</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">a2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">addPort</span><span class="p">(</span><span class="s2">"a-c"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">portno</span><span class="o">:</span><span class="w"> </span><span class="mf">2</span><span class="w"> </span><span class="p">});</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">b1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">b</span><span class="p">.</span><span class="nx">addPort</span><span class="p">(</span><span class="s2">"b-a"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">portno</span><span class="o">:</span><span class="w"> </span><span class="mf">1</span><span class="w"> </span><span class="p">});</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">b2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">b</span><span class="p">.</span><span class="nx">addPort</span><span class="p">(</span><span class="s2">"b-c"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">portno</span><span class="o">:</span><span class="w"> </span><span class="mf">2</span><span class="w"> </span><span class="p">});</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">c1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">addPort</span><span class="p">(</span><span class="s2">"c-a"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">portno</span><span class="o">:</span><span class="w"> </span><span class="mf">1</span><span class="w"> </span><span class="p">});</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">c2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">addPort</span><span class="p">(</span><span class="s2">"c-b"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">portno</span><span class="o">:</span><span class="w"> </span><span class="mf">2</span><span class="w"> </span><span class="p">});</span>

<span class="c1">// Build a triangle topology</span>
<span class="nx">mstp</span><span class="p">.</span><span class="nx">link</span><span class="p">(</span><span class="nx">a1</span><span class="p">,</span><span class="w"> </span><span class="nx">b1</span><span class="p">);</span>
<span class="nx">mstp</span><span class="p">.</span><span class="nx">link</span><span class="p">(</span><span class="nx">a2</span><span class="p">,</span><span class="w"> </span><span class="nx">c1</span><span class="p">);</span>
<span class="nx">mstp</span><span class="p">.</span><span class="nx">link</span><span class="p">(</span><span class="nx">b2</span><span class="p">,</span><span class="w"> </span><span class="nx">c2</span><span class="p">);</span>

<span class="c1">// Enable all bridges and ports</span>
<span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">br</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="p">[</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">c</span><span class="p">])</span><span class="w"> </span><span class="nx">br</span><span class="p">.</span><span class="nx">enable</span><span class="p">();</span>
<span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">p</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="p">[</span><span class="nx">a1</span><span class="p">,</span><span class="w"> </span><span class="nx">a2</span><span class="p">,</span><span class="w"> </span><span class="nx">b1</span><span class="p">,</span><span class="w"> </span><span class="nx">b2</span><span class="p">,</span><span class="w"> </span><span class="nx">c1</span><span class="p">,</span><span class="w"> </span><span class="nx">c2</span><span class="p">])</span><span class="w"> </span><span class="nx">p</span><span class="p">.</span><span class="nx">enable</span><span class="p">();</span>

<span class="c1">// Execute 40 seconds' worth of wall clock and display the topology</span>
<span class="nx">mstp</span><span class="p">.</span><span class="nx">step</span><span class="p">(</span><span class="mf">40</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Topology:"</span><span class="p">,</span><span class="w"> </span><span class="nx">mstp</span><span class="p">.</span><span class="nx">topology</span><span class="p">());</span>
</code></pre></div>

<p>Several dozen unit tests explore the features of MSTPD and check that they work
correctly in this environment:</p>
<div class="language-bash-session codehilite"><pre><span></span><code><span class="gp">$ </span>node<span class="w"> </span>--test<span class="w"> </span>*.test.mjs
<span class="go">✔ two bridges: lower priority becomes root (41.657342ms)</span>
<span class="go">✔ triangle loop: exactly one port blocks and all agree on the root (5.832ms)</span>
<span class="go">✔ breaking the active link reconverges and restoring recovers (18.730753ms)</span>
<span class="go">[…]</span>
<span class="go">ℹ tests 40</span>
<span class="go">ℹ pass 40</span>
<span class="go">ℹ fail 0</span>
<span class="go">[…]</span>
<span class="go">ℹ duration_ms 396.190897</span>
</code></pre></div>

<p>Additional JavaScript code looks for specific <code>&lt;pre&gt;</code> blocks containing a
topology definition and turns them into interactive widgets. You can inspect
and modify the definition by hitting the “edit” button.</p>
<p>There is also a cool trick to tell whether the topology has converged. After
each step, we save a snapshot of the simulation memory, play 50 seconds’ worth
of simulation to check if the topology is stable, and travel back in time by
restoring that snapshot. 🕰️</p>
<p>The complete code lives on <a href="https://github.com/vincentbernat/mstpd/tree/feature/wasm/wasm">GitHub</a>. I am happy with the result. It can be
difficult to follow everything happening during a single step, but stepping
forward and backward helps. I plan to use the same approach in future blog posts
about networking features.</p>
<div class="admonition">
<p class="admonition-title">Note</p>
<p><a href="https://mtlynch.io/">Michael Lynch</a> reviewed a first draft of this article. He authored
“<a href="https://refactoringenglish.com/" title="Refactoring English: Effective Writing for Software Developers">Refactoring English</a>,” a book to sharpen your writing for blog posts,
documentation, commit messages, and tutorials. Any errors are still mine!</p>
</div>
<div class="footnote">
<hr />
<ol>
<li id="fn:sprites">
<p>The sprites for Stan and Blobby come from <a href="https://craftpix.net/" title="Craftpix: 2D game assets">Craftpix</a>, the coffee
cups from <a href="https://yaninyunus.itch.io/16x16-specialtycoffee" title="Yanin: 16x16 specialty coffee assets">Yanin</a>. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:sprites" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:history">
<p><abbr title="Spanning Tree Protocol">STP</abbr> was introduced in <a href="https://sci-hub.fr/10.1109/IEEESTD.1991.101050" title="IEEE Standards for Local and Metropolitan Area Networks: Media Access Control (MAC) Bridges">IEEE 802.1D-1990</a>. It is still present in
<a href="https://sci-hub.fr/10.1109/IEEESTD.1998.95619" title="IEEE Standard for Local and Metropolitan Area Networks: Media Access Control (MAC) Bridges">IEEE 802.1D-1998</a> but was withdrawn in <a href="https://sci-hub.fr/10.1109/IEEESTD.2004.94569" title="IEEE Standard for Local and Metropolitan Area Networks: Media Access Control (MAC) Bridges">IEEE 802.1D-2004</a> in favor of
<abbr title="Rapid Spanning Tree Protocol">RSTP</abbr>, introduced in <a href="https://sci-hub.fr/10.1109/IEEESTD.2001.93287" title="IEEE Standard for Local and Metropolitan Area Networks: Rapid Reconfiguration of Spanning Tree">IEEE 802.1w-2001</a>. <abbr title="Multiple Spanning Tree Protocol">MSTP</abbr> was introduced in <a href="https://sci-hub.fr/10.1109/IEEESTD.2002.94223" title="IEEE Standards for Local and Metropolitan Area Networks: Virtual Bridged Local Area Networks — Amendment: Multiple Spanning Trees">IEEE 802.1s-2002</a> and merged into <a href="https://sci-hub.fr/10.1109/IEEESTD.2003.94280" title="IEEE Standards for Local and Metropolitan Area Networks: Virtual Bridged Local Area Networks">IEEE 802.1Q-2003</a>. Both of them are part
of <a title="IEEE Standard for Local and Metropolitan Area Networks: Bridges and Bridged Networks">IEEE 802.1Q-2022</a> along with <a href="https://sci-hub.fr/10.1109/IEEESTD.2012.6231597" title="IEEE Standard for Local and Metropolitan Area Networks: Shortest Path Bridging"><abbr title="Shortest Path Bridging">SPB</abbr></a><del>—a protocol
I had never heard of until writing this article</del>. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:history" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:bridge">
<p>From here, I use “bridge” instead of the more common word “switch.” <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:bridge" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:kernel">
<p>The Linux kernel only runs <abbr title="Spanning Tree Protocol">STP</abbr>. It delegates the other protocols to
user space. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:kernel" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:incomplete">
<p>MSTPD implements the state machine from <a href="https://sci-hub.fr/10.1109/IEEESTD.2006.216285" title="IEEE Standard for Local and Metropolitan Area Networks: Virtual Bridged Local Area Networks">IEEE 802.1Q-2005</a>, but
on Linux it runs <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> only. Linux 5.18 added <a href="https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=ec7328b59176227216c461601c6bd0e922232a9b" title="net: bridge: mst: Multiple Spanning Tree (MST) mode">support for forwarding
multiple spanning tree</a>, but MSTPD does not use it yet. See <a href="https://github.com/mstpd/mstpd/pull/150" title="[RFC/RFT] use kernel MST support if available">PR #150</a>
for progress on this front. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:incomplete" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
<li id="fn:priority">
<p>The priority is a multiple of 4,096: with <abbr title="Multiple Spanning Tree Protocol">MSTP</abbr>, the lower 12 bits
of the bridge priority encode the <abbr title="Multiple Spanning Tree">MST</abbr> instance identifier, leaving only the
upper 4 bits for the configured priority. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:priority" title="Jump back to footnote 6 in the text">↩</a></p>
</li>
<li id="fn:wireshark">
<p>To inspect the <abbr title="Bridge Protocol Data Units">BPDUs</abbr> crossing a link, select it, click the
“Download packets” button, and open the file with <a href="https://www.wireshark.org/" title="Wireshark: network protocol analyzer">Wireshark</a>. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:wireshark" title="Jump back to footnote 7 in the text">↩</a></p>
</li>
<li id="fn:backup">
<p>A backup port only exists if the bridge has several ports on the same
collision domain. This should not happen in a switched network. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:backup" title="Jump back to footnote 8 in the text">↩</a></p>
</li>
<li id="fn:hello">
<p>This is the value of the “hello” timer. It used to be configurable,
but <a href="https://sci-hub.fr/10.1109/IEEESTD.2006.216285" title="IEEE Standard for Local and Metropolitan Area Networks: Virtual Bridged Local Area Networks">IEEE 802.1Q-2005</a> pins it to 2. MSTPD does not allow another value. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:hello" title="Jump back to footnote 9 in the text">↩</a></p>
</li>
<li id="fn:learning">
<p>If the peer port does not receive an agreement after the hello
timer elapses—or the maximum age if the port has just come up—it falls back
to the timer-based method for compatibility with <abbr title="Spanning Tree Protocol">STP</abbr>: it transitions to
the learning state, waits again for the hello timer to expire, and
transitions to the forwarding state. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:learning" title="Jump back to footnote 10 in the text">↩</a></p>
</li>
<li id="fn:agreement">
<p>As in many proposals, S3 also sets the agreement bit to 1. The
proposal bit says “I am the designated port on this link and I want to
transition to the forwarding state.” The agreement bit says “I am already in
sync with the rest of my bridge on this root information.” Both can be true. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:agreement" title="Jump back to footnote 11 in the text">↩</a></p>
</li>
<li id="fn:bum">
<p>If it finds no entry, the bridge duplicates the Ethernet frame on all
ports, except the incoming one. The same happens if the destination MAC
address is the broadcast one (<code>ff:ff:ff:ff:ff:ff</code>). This behavior bootstraps
the learning process. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:bum" title="Jump back to footnote 12 in the text">↩</a></p>
</li>
<li id="fn:timer">
<p>This timer makes <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr> resistant to packet loss. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:timer" title="Jump back to footnote 13 in the text">↩</a></p>
</li>
<li id="fn:price">
<p>You can get them for less than US$100 through a broker. All the ports
run <abbr title="Per-VLAN Spanning Tree">PVST</abbr>+ by default and automatically fall back to plain <abbr title="Rapid Spanning Tree Protocol">RSTP</abbr>. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:price" title="Jump back to footnote 14 in the text">↩</a></p>
</li>
<li id="fn:erps">
<p>An alternative would be <a href="https://www.itu.int/rec/T-REC-G.8032" title="ITU-T G.8032">Ethernet Ring Protection Switching</a> (<abbr title="Ethernet Ring Protection Switching">ERPS</abbr>)—another protocol I had never heard of until researching this
article. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:erps" title="Jump back to footnote 15 in the text">↩</a></p>
</li>
<li id="fn:root-mac">
<p>If you look closely at what happens at t=2s, you can see that R2 is
gaining popularity as root: S17 to S36 believe R2 is the root bridge. S16
does not follow because we hit the maximum age. Later, S17 to S20 reverse
their position. I’ll let you explore the state of the various bridges to
understand the root cause. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:root-mac" title="Jump back to footnote 16 in the text">↩</a></p>
</li>
<li id="fn:forward-delay">
<p>When increasing the maximum age to 40, you also need to
increase the forward delay to 21 (<code>:forward-delay 21</code>), as the standard
enforces this condition: 2 × (Forward Delay − 1) ≥ Max Age. For this
specific topology, you could also increase the maximum age to 37 and
forward-delay to 20. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:forward-delay" title="Jump back to footnote 17 in the text">↩</a></p>
</li>
<li id="fn:time">
<p>The simulation may seem slow, but it does not run in real time. Look at
the current timestamp in the upper right corner to know the wall clock, e.g.
“t=8s.” Once the topology stabilizes, the same corner shows the convergence
time, e.g. “🌳 2s.” <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:time" title="Jump back to footnote 18 in the text">↩</a></p>
</li>
<li id="fn:slow">
<p>Khaled Elmeleegy, Alan Cox, and Eugene Ng formalized this phenomenon in
“<a href="https://sci-hub.fr/10.1109/INFOCOM.2006.229" title="On Count-to-Infinity Induced Forwarding Loops in Ethernet Networks">On Count-to-Infinity Induced Forwarding Loops in Ethernet Networks</a>”
and later in “<a href="https://sci-hub.fr/10.1109/TNET.2008.920874" title="Understanding and Mitigating the Effects of Count to Infinity in Ethernet Networks">Understanding and Mitigating the Effects of Count to Infinity
in Ethernet Networks</a>.” They propose a fix that did not find its way into
a standard. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:slow" title="Jump back to footnote 19 in the text">↩</a></p>
</li>
</ol>
</div>
       </div>
       <p class="date">
        <a href="https://vincent.bernat.ch/en/blog/2026-spanning-tree">24 August, 2026 03:00PM</a>
        
         by Vincent Bernat
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
     <div id="https://vincent.bernat.ch/en/blog/2026-spanning-tree-video" class="https://vincent.bernat.ch/en/blog/2026-spanning-tree-video">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://vincent.bernat.ch/en/blog/2026-spanning-tree-video">A non-interactive tour of the spanning tree protocol</a>
       </h3>
       <div class="content">
        <p>Imagine you rent office space for a three-day event. You quickly set up a few
Ethernet switches and tape some cables on the floor to get everyone online.
Unfortunately, Stan, your clumsiest coworker, kicks out a cable every time he
gets up for coffee. Spare cables would fix that, but a loop turns into a
broadcast storm: Ethernet packets multiply until nothing else gets through.
That’s where the <em>spanning tree protocol</em> comes in: it blocks just enough of the
spare cables to leave a loop-free tree, and rebuilds it in a second each time
Stan strikes again.<sup id="fnref:credits"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:credits">1</a></sup></p>
<figure class="lf-fullbleed"><div class="lf-media-outer" style="width: 1920px;"><span class="lf-media-inner"><video class="lf-media lf-opaque" controls="" height="1080" width="1920"><source src="https://media.bernat.ch/videos/2026-spanning-tree.m3u8" type="application/vnd.apple.mpegurl" /><source src="https://media.bernat.ch/videos/2026-spanning-tree/progressive.mp4" type="video/mp4; codecs=&quot;mp4a.40.2,avc1.4d401f&quot;" /></video></span></div></figure>
<p>This content is also available as a <a href="https://vincent.bernat.ch/en/blog/2026-spanning-tree" title="An interactive tour of the spanning tree protocol">text version</a>, with interactive demos
that run a real implementation directly in your browser!</p>
<hr />
<p>This video is an experiment.<sup id="fnref:time"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:time">2</a></sup> Honestly, except for Radia Perlman <a href="https://vincent.bernat.ch#video:seek-130">reading
her poem</a>,<sup id="fnref:radia"><a class="footnote-ref" href="https://vincent.bernat.ch#fn:radia">3</a></sup> you should read the <a href="https://vincent.bernat.ch/en/blog/2026-spanning-tree" title="An interactive tour of the spanning tree protocol">original article</a> instead. It presents the same content, but you can play with the
interactive examples, which are the main contribution. On the other hand, if you
happen to like the video, be sure to tell me in the comments!</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:credits">
<p>The sprites for Stan and Blobby come from <a href="https://craftpix.net/" title="Craftpix: 2D game assets">Craftpix</a>. The
background music is “<a href="https://www.chosic.com/download-audio/25044/" title="Sonatina No. 2 In G Major – III. Allegro by Aaron Dunn">Sonatina No. 2 in G Major – III. Allegro</a>” by
Aaron Dunn. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:credits" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:time">
<p>I thought automated tools would produce this video in a couple of
hours. In the end, it was another rabbit hole and it took me more than 12. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:time" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:radia">
<p>The audio was extracted from a <a href="https://www.youtube.com/watch?v=jOqzLcM2Hbo">Youtube video</a> and cleaned up. <a class="footnote-backref" href="https://vincent.bernat.ch#fnref:radia" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
</ol>
</div>
       </div>
       <p class="date">
        <a href="https://vincent.bernat.ch/en/blog/2026-spanning-tree-video">24 August, 2026 02:59PM</a>
        
         by Vincent Bernat
        
       </p>
      </div>
     </div>
    </div>
    
     

    <div class="channel">
     
      
     <a href="https://www.cs.unb.ca/~bremner//blog/tags/planet/">
      <img class="face" src="heads/bremner.png"
            width="65" height="81"
            alt="hackergotchi for David Bremner" />
     </a>
      
     <h2 class="planet-title">
      <a href="https://www.cs.unb.ca/~bremner//blog/tags/planet/" title="blog/tags/planet">David Bremner</a>
       <script type="text/javascript">
	  <!--
		 document.write( "<a href=\"#\" id=\"https://www.cs.unb.ca/~bremner//blog/posts/org-repro/_hide\" onClick=\"exclude( 'https://www.cs.unb.ca/~bremner//blog/posts/org-repro/' ); hideHosts(); return false;\"><img src=\"common/minus-8.png\" style=\"border: none;\" title=\"Hide Author\" alt=\"Hide Author\" height=\"8\" width=\"8\"><\/a> <a href=\"#\" id=\"https://www.cs.unb.ca/~bremner//blog/posts/org-repro/_show\"  style=\"display:none;\" onClick=\"show( 'https://www.cs.unb.ca/~bremner//blog/posts/org-repro/' );  return false;\"><img src=\"common/plus-8.png\" style=\"border: none;\" title=\"Show Author\" alt=\"Show Author\" height=\"8\" width=\"8\"><\/a>" );
	  -->
       </script>
      </h2>
     
     <div id="https://www.cs.unb.ca/~bremner//blog/posts/org-repro/" class="https://www.cs.unb.ca/~bremner//blog/posts/org-repro/">
      <div class="entry">
       <h3 class="entry-title">
        <a href="https://www.cs.unb.ca/~bremner//blog/posts/org-repro/">Reproducing Org mode configuration</a>
       </h3>
       <div class="content">
        <h1 id="Context">Context</h1>

<p>Recently I was trying to reproduce a bug with
<a title="https://github.com/andras-simonyi/citeproc-el">citeproc.el</a> and
<code>org-mode</code> in emacs.</p>

<p>I thought I could use <code>package-vc-install</code> to install a set of
upstream emacs packages at fixed versions, and thereby let citeproc
upstream test in the same environment as I have.</p>

<p>It turns out that getting emacs to load the non-builtin version
of org via <code>package-vc-install</code> did not work because</p>

<ul>
<li><code>org-mode</code> needs to run make after cloning</li>
<li>once package.el was initialized, I always seemed to end up with the
built in <code>org-mode</code> (yeah, I realize that isn't an explanation).</li>
</ul>


<h1 id="Recipe_part_1.3A_get_org">Recipe part 1: get org</h1>

<p>Here you can replace <code>9.8.7</code> with any other tagged release</p>

<div class="highlight-shellscript"><pre class="hl">  EMACSHOME<span class="hl opt">=</span><span class="hl kwd">$(mktemp -d)</span>
  git clone https<span class="hl opt">://</span>git.sr.ht<span class="hl opt">/</span>~bzg<span class="hl opt">/</span>org<span class="hl kwb">-mode</span> <span class="hl kwd">${EMACSHOME}</span><span class="hl opt">/</span>org
  git <span class="hl kwb">-C</span> <span class="hl kwd">${EMACSHOME}</span><span class="hl opt">/</span>org <span class="hl kwc">reset</span> <span class="hl kwb">--hard</span> release_9.8<span class="hl num">.7</span> 
  <span class="hl kwc">make</span> <span class="hl kwb">-C</span> <span class="hl kwd">${EMACSHOME}</span><span class="hl opt">/</span>org autoloads
  emacs <span class="hl kwb">-Q --batch -L</span> <span class="hl kwd">${EMACSHOME}</span><span class="hl opt">/</span>org<span class="hl opt">/</span>lisp <span class="hl kwb">--eval</span> <span class="hl sng">"(progn (require 'org) (message (org-version)))"</span>
</pre></div>


<p>This should print <code>9.8.7</code>, not the version of built in org-mode.</p>

<h1 id="Recipe_part_2.3A_add-on_packages">Recipe part 2: add-on packages</h1>

<p>Now to test some add-on packages, run</p>

<pre><code>    emacs -Q --init-directory ${EMACSHOME} -L ${EMACSHOME}/org/lisp
</code></pre>

<div class="highlight-lisp"><pre class="hl">  <span class="hl opt">(</span><span class="hl kwa">progn</span>
    <span class="hl opt">(</span><span class="hl kwa">require</span> <span class="hl opt">'</span>org<span class="hl opt">)</span>
    <span class="hl opt">(</span><span class="hl kwa">package-initialize</span><span class="hl opt">)</span>
    <span class="hl opt">(</span><span class="hl kwa">package-vc-install</span> <span class="hl sng">"https://github.com/emacs-straight/queue"</span><span class="hl opt">)</span>
    <span class="hl opt">(</span><span class="hl kwa">package-vc-install</span> <span class="hl sng">"https://github.com/joostkremers/parsebib"</span> <span class="hl sng">"6.7"</span><span class="hl opt">)</span>
    <span class="hl opt">(</span><span class="hl kwa">package-vc-install</span> <span class="hl sng">"https://github.com/rejeep/f.el"</span> <span class="hl sng">"0.21.0"</span><span class="hl opt">)</span>
    <span class="hl opt">(</span><span class="hl kwa">package-vc-install</span> <span class="hl sng">"https://github.com/magnars/s.el"</span> <span class="hl sng">"1.13.0"</span><span class="hl opt">)</span>
    <span class="hl opt">(</span><span class="hl kwa">package-vc-install</span> <span class="hl sng">"https://github.com/akicho8/string-inflection"</span> <span class="hl sng">"1.0.16"</span><span class="hl opt">)</span>
    <span class="hl opt">(</span><span class="hl kwa">package-vc-install</span> <span class="hl sng">"https://github.com/andras-simonyi/citeproc-el"</span> <span class="hl sng">"0.9.5"</span><span class="hl opt">))</span>
</pre></div>


<p>You can then run your tests in that emacs right away, or restart the environment with</p>

<pre><code>  emacs -Q --init-directory ${EMACSHOME} -L ${EMACSHOME}/org/lisp
</code></pre>
       </div>
       <p class="date">
        <a href="https://www.cs.unb.ca/~bremner//blog/posts/org-repro/">24 August, 2026 10:30AM</a>
        
       </p>
      </div>
     </div>
    </div>
    
   </div>
   <div id="sidebar">
    
     <h1>Search</h1>
     <form method="GET" action="https://planet-search.debian.org/cgi-bin/search.cgi">
      <input type="text" name="terms" value="">
     </form>
    <hr />
    
    <p>
     A complete feed is available in any of your favourite syndication formats linked by the buttons below.
    </p>
    <p>
     <a href="rss10.xml"><img class="button" src="common/rss10.png" width="80" height="15" alt="[RSS 1.0 Feed]"></a>
     <a href="rss20.xml"><img class="button" src="common/rss20.png" width="80" height="15" alt="[RSS 2.0 Feed]"></a>
     <a href="atom.xml"><img class="button" src="common/atom.png" width="80" height="15" alt="[Atom Feed]"></a>
     <a href="foafroll.xml"><img class="button" src="common/foaf.png" width="80" height="15" alt="[FOAF Subscriptions]"></a>
     <a href="opml.xml"><img class="button" src="common/opml.png" width="80" height="15" alt="[OPML Subscriptions]"></a>
     <a href="http://www.catb.org/hacker-emblem/"><img class="button" src="common/hacker.png" width="80" height="15" alt="[Hacker]"></a>
     <a href="https://www.intertwingly.net/code/venus/"><img class="button" src="common/planet.png" width="80" height="15" alt="[Planet]"></a>
    </p>
   <p>
    Last updated: 15 Sep 2026 22:00<br>All times are UTC.<br>
    Contact: <a href="mailto:planet@debian.org">Debian Planet Maintainers</a>
   </p>
   <h2>Planetarium</h2>
   <ul>
    <li><a href="https://planet.debian.org/">Planet Debian</a></li>
<li><a href="https://planet.debian.org/deriv/">Planet Debian Derivatives</a></li>
<li><a href="https://planet.debian.org/es/">Planet Debian Spanish</a></li>
<li><a href="https://planet.debian.org/fr/">Planet Debian French</a></li>

   </ul>
   <div id="unhide-all" style="display:none;">
    <h2>Hidden Feeds</h2>
    <p>You currently have hidden entries. <a href="#" onClick="excludeNone(); return false">Show all</a></p>
   </div>
   <h2>Subscriptions</h2>
   <ul>
    
    <li><a href="" title=""></a> <a href="">(feed)</a></li>
    
    <li><a href="https://abhijithpa.in/" title="Abhijith PA | അഭിജിത്ത് പി എ">Abhijith PA</a> <a href="https://abhijithpa.in/feed.debian.xml">(feed)</a></li>
    
    <li><a href="https://ajadi-abiola.github.io/" title="Abiola Ajadi">Abiola Ajadi</a> <a href="https://ajadi-abiola.github.io/feed.xml">(feed)</a></li>
    
    <li><a href="http://adam.rosi-kessel.org/weblog" title="The Substantially Similar Weblog">Adam Rosi-Kessel</a> <a href="http://adam.rosi-kessel.org/weblog/feed">(feed)</a></li>
    
    <li><a href="https://foolcontrol.org" title="FoolControl: Phear the penguin">Adnan Hodzic</a> <a href="https://foolcontrol.org/?feed=rss2">(feed)</a></li>
    
    <li><a href="https://deaddabe.fr/" title="deaddabe - debian">Agathe Porte</a> <a href="https://deaddabe.fr/feeds/tag.debian.atom.xml">(feed)</a></li>
    
    <li><a href="https://ahmedsiam.com/posts/" title="Posts on Ahmed Siam">Ahmed Siam</a> <a href="https://ahmedsiam.com/posts/index.xml">(feed)</a></li>
    
    <li><a href="http://aigarius.com/categories/debian-planet/" title="Aigarius Blog (Posts about Debian-planet)">Aigars Mahinovs</a> <a href="https://aigarius.com/categories/debian-planet.atom">(feed)</a></li>
    
    <li><a href="https://blogs.igalia.com/berto" title="Debian – The world won't listen">Alberto García</a> <a href="https://blogs.igalia.com/berto/category/debian/feed/">(feed)</a></li>
    
    <li><a href="http://codergals.github.io/" title="Coder Gals">Albiona Hoti</a> <a href="https://codergals.github.io/feed.xml">(feed)</a></li>
    
    <li><a href="http://alerios-en.blogspot.com/" title="alerios (english version)">Alejandro Rios P.</a> <a href="http://alerios-en.blogspot.com/atom.xml">(feed)</a></li>
    
    <li><a href="https://log.alexm.org" title="debian – my $log;">Alex Muntada</a> <a href="https://log.alexm.org/category/debian/feed/">(feed)</a></li>
    
    <li><a href="https://blog.snow-crash.org/tags/debian/" title="debian on a place called home">Alexander Wirt</a> <a href="https://blog.snow-crash.org/tags/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://alexandreviau.ca/blog/" title="Alexandre Viau's blog">Alexandre Viau</a> <a href="https://alexandreviau.ca/blog/index.xml">(feed)</a></li>
    
    <li><a href="https://blog.creekorful.org/" title="Aloïs Micard">Aloïs Micard</a> <a href="https://blog.creekorful.org/index.xml">(feed)</a></li>
    
    <li><a href="https://kelar.org/~bandali/" title="Amin Bandali">Amin Bandali</a> <a href="https://kelar.org/~bandali/rss20.xml">(feed)</a></li>
    
    <li><a href="https://ekaia.org/" title="Ana Guerrero Lopez - planet-debian">Ana Beatriz Guerrero Lopez</a> <a href="https://ekaia.org/blog/feed/planet-debian.rss">(feed)</a></li>
    
    <li><a href="https://moonkin.github.io/" title="Moonkin’s blog">Anastasia Tsikoza</a> <a href="https://moonkin.github.io/feed.xml">(feed)</a></li>
    
    <li><a href="https://activelow.net/tags/pdo/" title="planet.debian.org on Active Low">Andreas Bombe</a> <a href="https://activelow.net/tags/pdo/index.xml">(feed)</a></li>
    
    <li><a href="http://www.bebt.de/blog/debian" title="nutmeg's blog">Andreas Metzler</a> <a href="https://www.bebt.de/blog/debian/rss.xml">(feed)</a></li>
    
    <li><a href="https://www.gusnan.se/blog" title="Debian – Gusnans blog">Andreas Rönnquist</a> <a href="http://www.gusnan.se/blog/category/Debian/feed/">(feed)</a></li>
    
    <li><a href="http://schuldei.blogspot.com/" title="schuldei.org">Andreas Schuldei</a> <a href="http://schuldei.blogspot.com/atom.xml">(feed)</a></li>
    
    <li><a href="http://andreeleidenfrost.blogspot.com/" title="Andree&amp;#39;s Debian &amp;amp; General Musings">Andree Leidenfrost</a> <a href="http://andreeleidenfrost.blogspot.com/atom.xml">(feed)</a></li>
    
    <li><a href="http://libcxx-gsoc.blogspot.com/" title="GSoC 2012 libc++ porting blog">Andrej Belym</a> <a href="http://libcxx-gsoc.blogspot.com/feeds/posts/default">(feed)</a></li>
    
    <li><a href="https://blog.shadura.me/" title="Andrej's notes">Andrej Shadura</a> <a href="https://blog.shadura.me/atom.xml">(feed)</a></li>
    
    <li><a href="http://flosslinuxblog.blogspot.com/" title="FLOSSLinux">Andrew Cater</a> <a href="http://flosslinuxblog.blogspot.com/atom.xml">(feed)</a></li>
    
    <li><a href="http://blog.andrew.net.au" title="Diary of a geek">Andrew Pollock</a> <a href="http://blog.andrew.net.au/planetdebian/index.rss">(feed)</a></li>
    
    <li><a href="https://blog.koipond.org.uk" title="Debian – Koipond">Andy Simpkins</a> <a href="https://blog.koipond.org.uk/archives/tag/debian/feed">(feed)</a></li>
    
    <li><a href="https://anisakuci.com/" title="Anisa Kuci">Anisa Kuci</a> <a href="https://anisakuci.com/feed.xml">(feed)</a></li>
    
    <li><a href="https://anarc.at/tag/debian-planet/" title="pages tagged debian-planet">Antoine Beaupré</a> <a href="https://anarc.at/tag/debian-planet/index.rss">(feed)</a></li>
    
    <li><a href="https://people.debian.org/~gladk/blog/tags/planet/" title="Planet on gladk's blog">Anton Gladky</a> <a href="https://people.debian.org/~gladk/blog/tags/planet/index.xml">(feed)</a></li>
    
    <li><a href="https://terceiro.xyz/" title="Antonio Terceiro">Antonio Terceiro</a> <a href="https://terceiro.xyz/feed.xml">(feed)</a></li>
    
    <li><a href="https://antti-juhani.kaijanaho.fi/newblog" title="Debian – Antti-Juhani Kaijanaho">Antti-Juhani Kaijanaho</a> <a href="https://antti-juhani.kaijanaho.fi/newblog/archives/tag/debian/feed">(feed)</a></li>
    
    <li><a href="https://weeraman.com" title="Anuradha Weeraman — Open Source">Anuradha Weeraman</a> <a href="https://weeraman.com/opensource.xml">(feed)</a></li>
    
    <li><a href="https://arianit2.wordpress.com" title="debian – Arianit's Blog">Arianit Dobroshi</a> <a href="https://arianit2.wordpress.com/tag/debian/feed/">(feed)</a></li>
    
    <li><a href="https://arnaudr.io/" title="Arnaud R - planet-debian">Arnaud Rebillout</a> <a href="https://arnaudr.io/feeds/tags/planet-debian.atom.xml">(feed)</a></li>
    
    <li><a href="https://blogs.gnome.org/happyaron" title="Debian – True EDGE">Aron Xu</a> <a href="https://blogs.gnome.org/happyaron/category/debian/feed/rss/">(feed)</a></li>
    
    <li><a href="http://www.arthurbdiniz.com/" title="Arthur Diniz">Arthur Diniz</a> <a href="https://arthurbdiniz.com/feed.xml">(feed)</a></li>
    
    <li><a href="https://ral-arturo.org/" title="ral-arturo.org">Arturo Borrero González</a> <a href="https://ral-arturo.org/feed.xml">(feed)</a></li>
    
    <li><a href="https://athoscr.me/categories/debian/" title="Debian on /home/athos">Athos Ribeiro</a> <a href="https://athoscr.me/categories/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://blog.aurel32.net/" title="Aurélien Jarno">Aurelien Jarno</a> <a href="https://blog.aurel32.net/feed">(feed)</a></li>
    
    <li><a href="http://noone.org/blog/English/Computer/Shell" title="Blogging is futile">Axel Beckert</a> <a href="http://noone.org/blog/English/index.rss">(feed)</a></li>
    
    <li><a href="https://ayoyimika.hashnode.dev" title="Ayoyimika">Ayoyimika Ajibade</a> <a href="https://ayoyimika.hashnode.dev/rss.xml">(feed)</a></li>
    
    <li><a href="https://balasankarc.in/" title="Balasankar C">Balasankar 'Balu' C</a> <a href="https://balasankarc.in/tech/feed.xml">(feed)</a></li>
    
    <li><a href="https://bblank.thinkmo.de/" title="Some Place for Me">Bastian Blank</a> <a href="https://bblank.thinkmo.de/feeds/sub-debian-planet.atom.xml">(feed)</a></li>
    
    <li><a href="https://venthur.de/" title="still don't have a title">Bastian Venthur</a> <a href="https://venthur.de/atom.xml">(feed)</a></li>
    
    <li><a href="http://www.gag.com/bdale/blog/index.html" title="Bits from the Basement">Bdale Garbee</a> <a href="https://gag.com/bdale/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://www.decadent.org.uk/ben/blog/" title="Better living through software">Ben Hutchings</a> <a href="https://www.decadent.org.uk/ben/blog/index.atom">(feed)</a></li>
    
    <li><a href="https://overbenny.wordpress.com" title="Planet Debian – Overbenny's Blog">Benjamin Drung</a> <a href="https://overbenny.wordpress.com/category/planet-debian/feed/atom/">(feed)</a></li>
    
    <li><a href="" title="">Benjamin Kerensa</a> <a href="https://benjaminkerensa.com/category/debian/feed">(feed)</a></li>
    
    <li><a href="https://mako.cc/copyrighteous" title="copyrighteous">Benjamin Mako Hill</a> <a href="https://mako.cc/copyrighteous/feed">(feed)</a></li>
    
    <li><a href="https://bzed.de/categories/linux/" title="linux on linux &amp; the mountains">Bernd Zeimetz</a> <a href="https://bzed.de/categories/linux/index.xml">(feed)</a></li>
    
    <li><a href="http://blog.brlink.eu/index.html" title="Bernhard R. Link">Bernhard R. Link</a> <a href="http://blog.brlink.eu/changelog.rss">(feed)</a></li>
    
    <li><a href="https://gabriellacoleman.org/blog" title="Interprete">Biella Coleman</a> <a href="https://gabriellacoleman.org/blog/?feed=rss2">(feed)</a></li>
    
    <li><a href="https://dev.to/warbilly" title="DEV Community: Billy Warren">Billy Warren</a> <a href="https://dev.to/feed/warbilly">(feed)</a></li>
    
    <li><a href="https://bisco.org/tags/debian/" title="debian on bisco.org">Birger Schacht</a> <a href="https://bisco.org/tags/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://bits.debian.org/" title="Bits from Debian">Bits from Debian</a> <a href="https://bits.debian.org/feeds/feed.rss">(feed)</a></li>
    
    <li><a href="https://www.sommitrealweird.co.uk/blog/" title="The World of SommitRealWeird.">Brett Parker</a> <a href="https://www.sommitrealweird.co.uk/feeds/blog/">(feed)</a></li>
    
    <li><a href="https://bgoglin.livejournal.com/" title="Brice Goglin's Blog">Brice Goglin</a> <a href="https://bgoglin.livejournal.com/data/rss?tag=planet.d.o">(feed)</a></li>
    
    <li><a href="https://balintreczey.hu/blog" title="debian – Obsessed with reality">Bálint Réczey</a> <a href="https://balintreczey.hu/blog/tag/debian/feed/">(feed)</a></li>
    
    <li><a href="https://wp.c9h.org/cj" title="debian – Clerical Rigor">C.J. Collier</a> <a href="https://wp.colliertech.org/cj/?cat=17&amp;feed=rss2">(feed)</a></li>
    
    <li><a href="https://chainofcommand.hashnode.dev" title="Angles and Braces">Caleb Adepitan</a> <a href="https://chainofcommand.hashnode.dev/rss.xml">(feed)</a></li>
    
    <li><a href="https://stringpiggy.hpd.io" title="debian – StringPiggy">Candy Tsai</a> <a href="https://stringpiggy.hpd.io/tag/debian/feed/">(feed)</a></li>
    
    <li><a href="https://admincarlchenet.wordpress.com" title="Carl Chenet">Carl Chenet</a> <a href="https://admincarlchenet.wordpress.com/category/debian/feed/">(feed)</a></li>
    
    <li><a href="http://bootdebian.blogspot.com/" title="Improve the Debian boot process - blog">Carlos Villegas</a> <a href="http://bootdebian.blogspot.com/atom.xml">(feed)</a></li>
    
    <li><a href="https://www.charlz.net/" title="Charlz' Realm">Charles</a> <a href="https://www.charlz.net/feed.xml">(feed)</a></li>
    
    <li><a href="http://charles.plessy.org/Debian/planet/" title="Planet">Charles Plessy</a> <a href="https://charles.plessy.org/Debian/planet/index.en.rss">(feed)</a></li>
    
    <li><a href="https://crispygoth.livejournal.com/" title="ChrisB">Chris Butler</a> <a href="https://crispygoth.livejournal.com/data/rss?tag=planet">(feed)</a></li>
    
    <li><a href="https://chris-lamb.co.uk/blog/category/planet-debian" title="lamby: Items or syndication on Planet Debian.">Chris Lamb</a> <a href="https://chris-lamb.co.uk/blog/category/planet-debian/feed">(feed)</a></li>
    
    <li><a href="https://blog.lordsutch.com/" title="Signifying Nothing">Chris Lawrence</a> <a href="https://blog.lordsutch.com/feed/?topic=5&amp;topic=12&amp;topic=10&amp;topic=49&amp;author=1">(feed)</a></li>
    
    <li><a href="https://www.kvr.at/" title="Christian Kastner">Christian Kastner</a> <a href="https://www.kvr.at/rss.xml">(feed)</a></li>
    
    <li><a href="http://blog.spang.cc/tags/planet-debian/" title="tags/planet-debian">Christine Spang</a> <a href="http://blog.spang.cc/tags/planet-debian/index.rss">(feed)</a></li>
    
    <li><a href="https://www.cybertec-postgresql.com/en/author/christoph_berg/" title="Christoph Berg, Author at CYBERTEC PostgreSQL | Services &amp; Support">Christoph Berg</a> <a href="https://www.cybertec-postgresql.com/en/author/christoph_berg/feed/">(feed)</a></li>
    
    <li><a href="https://df7cb.de/blog/tags/debian.html" title="Debian on Myon's Blog">Christoph Berg</a> <a href="https://www.df7cb.de/blog/tags/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://weblog.christoph-egger.org/" title="Christoph's last Weblog entries">Christoph Egger</a> <a href="https://weblog.christoph-egger.org//index.rss">(feed)</a></li>
    
    <li><a href="https://xana.scru.org" title="Yammering">Clint Adams</a> <a href="https://xana.scru.org/rivercrabbed-debian.atom">(feed)</a></li>
    
    <li><a href="https://www.chiark.greenend.org.uk/~cjwatson/blog/" title="Colin Watson's blog - planet-debian">Colin Watson</a> <a href="https://www.chiark.greenend.org.uk/~cjwatson/blog/feeds/tag/planet-debian.atom.xml">(feed)</a></li>
    
    <li><a href="https://blog.taz.net.au" title="Errata">Craig Sanders</a> <a href="http://blog.taz.net.au/feed/">(feed)</a></li>
    
    <li><a href="http://mraw.org/blog/" title="KiBi’s blog">Cyril Brulebois</a> <a href="https://mraw.org/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://mirrorlynx.livejournal.com/" title="Dan Weber">Dan Weber</a> <a href="https://mirrorlynx.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="https://blog.daniel-baumann.ch" title="Daniel Baumann">Daniel Baumann</a> <a href="https://blog.daniel-baumann.ch/blog/atom.xml">(feed)</a></li>
    
    <li><a href="https://dkg.fifthhorseman.net/blog/" title="dkg's blog">Daniel Kahn Gillmor</a> <a href="https://dkg.fifthhorseman.net/blog/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="https://daniel-lange.com/" title="Daniel Lange's blog">Daniel Lange</a> <a href="https://daniel-lange.com/feeds/atom.xml">(feed)</a></li>
    
    <li><a href="https://www.wgdd.de/search/label/planet-debian" title="[erfahrungen, meinungen, halluzinationen]">Daniel Leidert</a> <a href="https://www.wgdd.de/feeds/posts/default/-/planet-debian">(feed)</a></li>
    
    <li><a href="http://tdi.github.io/" title="Blog">Dariusz Dwornikowski</a> <a href="https://tdi.github.io/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="https://www.foxk.it/blog/" title="Blogs on Foxk.it">Dave Hibberd</a> <a href="https://foxk.it/blog/index.xml">(feed)</a></li>
    
    <li><a href="https://www.cs.unb.ca/~bremner//blog/tags/planet/" title="blog/tags/planet">David Bremner</a> <a href="http://www.cs.unb.ca/~bremner/blog/tags/planet/index.rss">(feed)</a></li>
    
    <li><a href="https://david.kalnischkies.de/blog" title="apt-get a life">David Kalnischkies</a> <a href="https://david.kalnischkies.de/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://damog.net/blog/tag/debian/" title="debian on David Moreno's blog">David Moreno</a> <a href="https://damog.net/blog/tag/debian/atom.xml">(feed)</a></li>
    
    <li><a href="https://gravityboy.livejournal.com/" title="To The Point Of Collapse">David Nusinow</a> <a href="https://gravityboy.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="http://www.hanskalabs.net/" title="HanskaLabs (planet)">David Paleino</a> <a href="http://www.hanskalabs.net/categories/planet.xml">(feed)</a></li>
    
    <li><a href="http://planetwatson.co.uk/categories/planet-debian/" title="Entries for the category Planet Debian">David Watson</a> <a href="http://planetwatson.co.uk/feeds/categories/planet-debian">(feed)</a></li>
    
    <li><a href="http://dwgsoc09blog.blogspot.com/search/label/to-debian-gsoc" title="David Wendt's GSoC Updates Blog">David Wendt Jr.</a> <a href="http://dwgsoc09blog.blogspot.com/feeds/posts/default/-/to-debian-gsoc">(feed)</a></li>
    
    <li><a href="http://zinosat.blogspot.com/" title="Yeah!">Davide Viti</a> <a href="http://zinosat.blogspot.com/atom.xml">(feed)</a></li>
    
    <li><a href="" title="">DebConf team</a> <a href="https://bits.debian.org/tag/debconf.html">(feed)</a></li>
    
    <li><a href="https://debianbrasil.org.br/" title="Debian Brasil">Debian Brasil</a> <a href="https://debianbrasil.org.br/english-feed.xml">(feed)</a></li>
    
    <li><a href="https://java-team.pages.debian.net/gsoc-kotlin-blog/" title="Debian GSoC Kotlin project">Debian GSoC Kotlin project blog</a> <a href="https://java-team.pages.debian.net/gsoc-kotlin-blog/atom.xml">(feed)</a></li>
    
    <li><a href="http://java.debian.net/blog/" title="Debian Java News">Debian Java Packaging Team</a> <a href="https://java.debian.net/blog/atom.xml">(feed)</a></li>
    
    <li><a href="http://debianmed.blogspot.com/" title="Debian in Biology and Medicine">Debian Med</a> <a href="http://feeds.feedburner.com/DebianInBiologyAndMedicine">(feed)</a></li>
    
    <li><a href="https://outreach-team.pages.debian.net/" title="Outreach team Pages">Debian Outreach Team</a> <a href="https://outreach-team.pages.debian.net/feed.xml">(feed)</a></li>
    
    <li><a href="https://bits.debian.org/dpl/" title="Debian Project Leader Blog">Debian Project Leader</a> <a href="https://bits.debian.org/dpl/feeds/feed.rss">(feed)</a></li>
    
    <li><a href="https://wordpress.debian.social" title="debian.social blog">Debian Social Team</a> <a href="https://wordpress.debian.social/feed/">(feed)</a></li>
    
    <li><a href="https://dsa.debian.org//dsablog/" title="Debian System Administrators' Blog">Debian Sysadmin Team</a> <a href="https://dsa.debian.org/dsablog/index.rss">(feed)</a></li>
    
    <li><a href="https://xmpp-team.pages.debian.net/blog/" title="Bits from the Debian XMPP Team">Debian XMPP Team</a> <a href="https://xmpp-team.pages.debian.net/blog/feeds/atom.xml">(feed)</a></li>
    
    <li><a href="https://debichem-team.pages.debian.net/" title="The debichem group">Debichem Team</a> <a href="https://debichem-team.pages.debian.net/feed.xml">(feed)</a></li>
    
    <li><a href="https://medium.com/@gajbhiyedeepanshu?source=rss-ff6d7d57bab6------2" title="Stories by Deepanshu on Medium">Deepanshu Gajbhiye</a> <a href="https://medium.com/@gajbhiyedeepanshu/feed">(feed)</a></li>
    
    <li><a href="https://blog.odyx.org" title="english – /var/log/OdyX">Didier Raboud</a> <a href="https://blog.odyx.org/tag/en/feed/">(feed)</a></li>
    
    <li><a href="http://notes.secretsauce.net" title="Dima Kogan">Dima Kogan</a> <a href="https://notes.secretsauce.net/index.xml">(feed)</a></li>
    
    <li><a href="http://blog.surgut.co.uk/search/label/debian-planet" title="Surgut">Dimitri John Ledkov</a> <a href="http://blog.surgut.co.uk/feeds/posts/default/-/debian-planet">(feed)</a></li>
    
    <li><a href="http://dirk.eddelbuettel.com/blog" title="Thinking inside the box">Dirk Eddelbuettel</a> <a href="https://dirk.eddelbuettel.com/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://dev.to/0xfaker" title="DEV Community: Divine Attah-Ohiemi">Divine Attah-Ohiemi</a> <a href="https://dev.to/feed/0xfaker">(feed)</a></li>
    
    <li><a href="https://mitya57.me/weblog/" title="mitya57’s weblog - Planet">Dmitry Shachnev</a> <a href="https://mitya57.me/weblog/feeds/planet.atom.xml">(feed)</a></li>
    
    <li><a href="https://web.itu.edu.tr/celikd" title="helloworlditsme">Dogukan Celik</a> <a href="https://web.itu.edu.tr/celikd/feed/">(feed)</a></li>
    
    <li><a href="https://ddumont.wordpress.com" title="Dominique Dumont's Blog">Dominique Dumont</a> <a href="https://ddumont.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://www.donarmstrong.com/tags/debian/" title="pages tagged debian">Don Armstrong</a> <a href="https://www.donarmstrong.com/tags/debian/index.rss">(feed)</a></li>
    
    <li><a href="http://ramblingfoo.blogspot.com/" title="Rambling around foo">Eddy Petri&#537;or</a> <a href="http://feeds.feedburner.com/RamblingFoo">(feed)</a></li>
    
    <li><a href="https://dhole.github.io/categories/debian/" title="Debian on Dhole's blog">Eduard Sanou</a> <a href="https://dhole.github.io/categories/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://eduardo.macan.eng.br" title="Eduardo Maçan">Eduardo Marcel Macan</a> <a href="https://eduardo.macan.eng.br/category/debian-en/feed/">(feed)</a></li>
    
    <li><a href="https://edwardbetts.com/" title="Edward Betts">Edward Betts</a> <a href="https://edwardbetts.com/feed_rss_created.xml">(feed)</a></li>
    
    <li><a href="https://hashman.ca/" title="hashman.ca">Elana Hashman</a> <a href="https://hashman.ca/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="https://www.linux.it/~ema/" title="Emanuele Rocca">Emanuele Rocca</a> <a href="https://www.linux.it/~ema/index.xml">(feed)</a></li>
    
    <li><a href="http://emilio.pozuelo.org" title="Planet Debian – Emilio Pozuelo Monfort's blog">Emilio Pozuelo Monfort</a> <a href="https://emilio.pozuelo.org/?cat=13&amp;feed=atom">(feed)</a></li>
    
    <li><a href="https://00formicapunk00.wordpress.com" title="floss – formicapunk">Emmanuel Kasper</a> <a href="https://00formicapunk00.wordpress.com/tag/floss/feed/">(feed)</a></li>
    
    <li><a href="https://www.ezini.eu/tags/pdo" title="Enrico Zini: posts with tag pdo">Enrico Zini</a> <a href="https://www.ezini.eu/tags/pdo/index.rss">(feed)</a></li>
    
    <li><a href="https://eriberto.pro.br/blog" title="Planet en – Eriberto Blog">Eriberto Mota</a> <a href="http://eriberto.pro.br/blog/tag/planet-en/feed/">(feed)</a></li>
    
    <li><a href="https://ze-dinosaur.livejournal.com/" title="apparently I'm a dinosaur">Eric Dorland</a> <a href="https://ze-dinosaur.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="https://www.vitavonni.de/blog/" title="Techblogging">Erich Schubert</a> <a href="https://www.vitavonni.de/blog/feeds/en.atom">(feed)</a></li>
    
    <li><a href="https://www.ieugen.ro/" title="(defn Eugen-Stan [_] awesome)">Eugen Stan</a> <a href="https://www.ieugen.ro/feed.xml">(feed)</a></li>
    
    <li><a href="https://jackyf.livejournal.com/" title="JackYF's blog">Eugene V. Lyubimkin</a> <a href="https://jackyf.livejournal.com/data/rss?tag=debian-en">(feed)</a></li>
    
    <li><a href="https://www.die-welt.net/" title="die-welt.net">Evgeni Golov</a> <a href="https://www.die-welt.net/rss.xml">(feed)</a></li>
    
    <li><a href="http://web2unu.ro" title="Web2Unu Blog » Debian">Floris Stoica-Marcu</a> <a href="http://web2unu.ro/?feed=rss2&amp;cat=2">(feed)</a></li>
    
    <li><a href="https://fottsia.wordpress.com" title="My outreachy project blog">Foteini Tsiami</a> <a href="https://fottsia.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://lovergine.com" title="frankie-tales">Francesco Paolo Lovergine</a> <a href="https://lovergine.com/feeds/tags/debian.xml">(feed)</a></li>
    
    <li><a href="https://feeding.cloud.geek.nz/tags/debian/" title="pages tagged debian">François Marier</a> <a href="https://feeding.cloud.geek.nz/tags/debian/index.rss">(feed)</a></li>
    
    <li><a href="https://www.freexian.com/tags/planet-debian/" title="Planet-Debian on Freexian">Freexian Collaborators</a> <a href="https://www.freexian.com/tags/planet-debian/index.xml">(feed)</a></li>
    
    <li><a href="http://cateee.blogspot.com/" title="Cateee">Giacomo Catenazzi</a> <a href="http://cateee.blogspot.com/feeds/posts/default">(feed)</a></li>
    
    <li><a href="https://www.giovannimascellani.eu/" title="Gio's Tilde - debian-planet">Giovanni Mascellani</a> <a href="https://www.giovannimascellani.eu/feeds/debian-planet.atom.xml">(feed)</a></li>
    
    <li><a href="https://www.iuculano.it/category/linux/debian/debian-planet-debian/" title="Debian Planet Archivi - Giuseppe">Giuseppe Iuculano</a> <a href="https://www.iuculano.it/category/debian-planet-debian/feed/">(feed)</a></li>
    
    <li><a href="https://info.comodo.priv.at/blog/" title="gregoa's blog - entries tagged planetdebian">Gregor Herrmann</a> <a href="https://info.comodo.priv.at/blog/tags/planetdebian/planetdebian.rss">(feed)</a></li>
    
    <li><a href="https://gcolpart.evolix.net/blog21" title="Debian – Gregory Colpart's blog">Gregory Colpart</a> <a href="https://gcolpart.evolix.net/blog21/category/debian/feed/">(feed)</a></li>
    
    <li><a href="https://phosh.mobi/tags/planetdebian/" title="Planetdebian on Phosh">Guido Günther</a> <a href="https://phosh.mobi/tags/planetdebian/index.xml">(feed)</a></li>
    
    <li><a href="https://honk.sigxcpu.org/con/" title="Colors of Noise - Entries tagged planetdebian">Guido Günther</a> <a href="https://honk.sigxcpu.org/con/tags/planetdebian/planetdebian.rss">(feed)</a></li>
    
    <li><a href="https://puida.xyz/tags/debian/index.xml" title="Debian on puida.xyz">Guilherme Puida Moreira</a> <a href="https://puida.xyz/tags/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://gwolf.org" title="Gunnar Wolf - Nice grey life">Gunnar Wolf</a> <a href="https://gwolf.org/rss.xml">(feed)</a></li>
    
    <li><a href="http://stratusandtheswirl.blogspot.com/" title="stratus and the swirl">Gustavo Franco</a> <a href="http://feeds.feedburner.com/StratusAndTheSwirl">(feed)</a></li>
    
    <li><a href="http://grmontesino.blogspot.com/search/label/Debian" title="Gustavo's Blog">Gustavo R. Montesino</a> <a href="http://grmontesino.blogspot.com/feeds/posts/default/-/Debian/">(feed)</a></li>
    
    <li><a href="https://hellenchemtai.wordpress.com" title="hellenchemtai">Hellen Chemtai</a> <a href="https://hellenchemtai.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://henrich-on-debian.blogspot.com/" title="Henrich plays with Debian">Hideki Yamane</a> <a href="https://henrich-on-debian.blogspot.com/feeds/posts/default">(feed)</a></li>
    
    <li><a href="http://nibbles.halon.org.uk" title="Quick Nibbles » Debian">Héctor Orón Martínez</a> <a href="http://nibbles.halon.org.uk/category/tech/debian/feed/atom/">(feed)</a></li>
    
    <li><a href="https://irl.xyz/tags/planet-debian/" title="Planet Debian on IRL.XYZ">Iain R. Learmonth</a> <a href="https://irl.xyz/tags/planet-debian/index.xml">(feed)</a></li>
    
    <li><a href="https://diziet.dreamwidth.org/" title="Ian Jackson">Ian Jackson</a> <a href="https://diziet.dreamwidth.org/data/atom">(feed)</a></li>
    
    <li><a href="https://www.technovelty.org/" title="technovelty">Ian Wienand</a> <a href="https://www.technovelty.org/index.atom.xml">(feed)</a></li>
    
    <li><a href="http://genibel.org/blog/index.php/" title="Igor's blog">Igor Genibel</a> <a href="https://genibel.org/blog/rss.php/?cat=Debian">(feed)</a></li>
    
    <li><a href="https://blog.windfluechter.net" title="Debian – WindfluechterNet Blog">Ingo Juergensmann</a> <a href="https://blog.windfluechter.net/category/debian/feed/">(feed)</a></li>
    
    <li><a href="https://isokenibizugbe.wordpress.com" title="Isoken’s Outreachy Journey">Isoken Ibizugbe</a> <a href="https://isokenibizugbe.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://k1024.org" title="iustin - all posts">Iustin Pop</a> <a href="https://k1024.org/index.atom">(feed)</a></li>
    
    <li><a href="https://tookmund.com/" title="">Jacob Adams</a> <a href="https://tookmund.com/feed.xml">(feed)</a></li>
    
    <li><a href="https://blog.james.rcpt.to" title="Linux – JEB's Blog">James Bromberger</a> <a href="https://blog.james.rcpt.to/category/computing/linux/feed/">(feed)</a></li>
    
    <li><a href="https://jamessan.com/~jamessan//tags/planet-debian/" title="pages tagged planet-debian">James McCoy</a> <a href="https://jamessan.com/~jamessan/tags/planet-debian/index.rss">(feed)</a></li>
    
    <li><a href="https://www.sorced.com/" title="Sasquach wears a yellow hat">James Morrison</a> <a href="https://www.sorced.com/feeds/posts/default?alt=atom">(feed)</a></li>
    
    <li><a href="https://jvalleroy.fbx.one/wordpress" title="James Valleroy">James Valleroy</a> <a href="https://jvalleroy.fbx.one/wordpress/?feed=rss2">(feed)</a></li>
    
    <li><a href="https://current.workingdirectory.net/tags/debian/" title="Debian on Website of Jamie McClelland">Jamie McClelland</a> <a href="https://current.workingdirectory.net/tags/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://jaminyprabaharan.wordpress.com" title="">Jaminy Prabaharan</a> <a href="https://jaminyprabaharan.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://debianstuff.dittberner.info/" title="Debian Stuff @ dittberner.info">Jan Dittberner</a> <a href="https://debianstuff.dittberner.info/rss.xml">(feed)</a></li>
    
    <li><a href="https://log.cyconet.org/" title="Planet - Cyconet Blog">Jan Wagner</a> <a href="https://log.cyconet.org/tag/planet/rss/">(feed)</a></li>
    
    <li><a href="https://jbailey.livejournal.com/" title="Bright lights shining upwards obscures the stars...">Jeff Bailey</a> <a href="https://jbailey.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="https://www.licquia.org" title="The Licquia Blog">Jeff Licquia</a> <a href="http://www.licquia.org/feed/">(feed)</a></li>
    
    <li><a href="https://www.jelmer.uk/" title="Jelmer Vernooĳ">Jelmer Vernooĳ</a> <a href="https://jelmer.uk/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="https://jeremy.bicha.net" title="Debian – Just Jeremy">Jeremy Bicha</a> <a href="https://jeremy.bicha.net/category/debian/feed/?mrss=off">(feed)</a></li>
    
    <li><a href="https://sophiejjj.wordpress.com" title="My journey of FOSS">Jingjie Jiang</a> <a href="https://sophiejjj.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://apebox.org/wordpress" title="debian – APEBOX.ORG">Jo Shields</a> <a href="https://apebox.org/wordpress/tag/debian/feed">(feed)</a></li>
    
    <li><a href="https://www.joachim-breitner.de/blog" title="nomeata’s mind shares">Joachim Breitner</a> <a href="https://www.joachim-breitner.de/blog/tag/English_feed.rss">(feed)</a></li>
    
    <li><a href="https://blog.ganneff.de/" title="Ganneff's Little Blog">Joerg Jaspert</a> <a href="https://blog.ganneff.de/atom.xml">(feed)</a></li>
    
    <li><a href="http://joeyh.name/blog/" title="see shy jo">Joey Hess</a> <a href="https://joeyh.name/blog/index.rss">(feed)</a></li>
    
    <li><a href="http://blog.mister-muffin.de/" title="Mister Muffin Blog">Johannes Schauer Marin Rodrigues</a> <a href="https://blog.mister-muffin.de/index.xml">(feed)</a></li>
    
    <li><a href="https://changelog.complete.org" title="The Changelog">John Goerzen</a> <a href="https://changelog.complete.org/feed">(feed)</a></li>
    
    <li><a href="https://blog.freesources.org//" title="mejo roaming">Jonas Meurer</a> <a href="https://blog.freesources.org/index.atom">(feed)</a></li>
    
    <li><a href="http://dr.jones.dk//blog/" title="blog">Jonas Smedegaard</a> <a href="https://dr.jones.dk/blog/index.en.atom">(feed)</a></li>
    
    <li><a href="https://jonathancarter.org" title="Jonathan Carter">Jonathan Carter</a> <a href="https://jonathancarter.org/feed/">(feed)</a></li>
    
    <li><a href="https://jmtd.net/log/" title="Jonathan Dowland's Weblog">Jonathan Dowland</a> <a href="https://jmtd.net/log/feed/">(feed)</a></li>
    
    <li><a href="https://www.earth.li/~noodles/blog/" title="Noodles' Emptiness">Jonathan McDowell</a> <a href="https://www.earth.li/~noodles/blog/feed.xml">(feed)</a></li>
    
    <li><a href="https://www.jwiltshire.org.uk" title="planet-debian – jwiltshire.org.uk">Jonathan Wiltshire</a> <a href="https://www.jwiltshire.org.uk/tag/planet-debian/feed/">(feed)</a></li>
    
    <li><a href="https://jawnsy.wordpress.com" title="Debian – Jawnsy’s Journal on Life, Software and Engineering">Jonathan Yu</a> <a href="https://jawnsy.wordpress.com/tag/debian/feed/">(feed)</a></li>
    
    <li><a href="https://jonnylamb.com" title="Planet Debian">Jonny Lamb</a> <a href="https://jonnylamb.com/category/planet-debian/feed/">(feed)</a></li>
    
    <li><a href="https://oskuro.net/en/" title="I still don't have a title">Jordi Mallach</a> <a href="https://oskuro.net/en/index.xml">(feed)</a></li>
    
    <li><a href="http://josephbisch.com/tags/debian/" title="debian on Joseph Bisch">Joseph Bisch</a> <a href="http://josephbisch.com/tags/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://np237.livejournal.com/" title="Joss's blog">Josselin Mouette</a> <a href="https://np237.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="https://gnucrash.wordpress.com" title="DebianPl – Alicatismo ilustrado.">Juan Luis Belmonte</a> <a href="https://gnucrash.wordpress.com/category/debianpl/feed/">(feed)</a></li>
    
    <li><a href="https://blog.jak-linux.org/post/" title="Posts on Blog of Julian Andres Klode">Julian Andres Klode</a> <a href="https://blog.jak-linux.org/post/index.xml">(feed)</a></li>
    
    <li><a href="http://silicone.homelinux.org" title="debian-planet – Silicone's web">Julien Viard de Galbert</a> <a href="http://silicone.homelinux.org/tag/debian-planet/feed/">(feed)</a></li>
    
    <li><a href="http://www.netfort.gr.jp/~dancer/diary/202609.html.en" title="Dancer's daily hackings">Junichi Uekawa</a> <a href="https://www.netfort.gr.jp/~dancer/diary/recententry-en.rdf">(feed)</a></li>
    
    <li><a href="http://blog.wooyd.org/" title="wiki">Jurij Smakov</a> <a href="http://blog.wooyd.org/index.rss">(feed)</a></li>
    
    <li><a href="https://teythoon.cryptobitch.de/" title="unprivileged user">Justus Winter</a> <a href="https://teythoon.cryptobitch.de/rss.xml">(feed)</a></li>
    
    <li><a href="https://www.carbon-project.org/" title="Cùran's life - Entries tagged debian">Kai Wasserbäch</a> <a href="https://www.carbon-project.org/tags/debian/debian.rss">(feed)</a></li>
    
    <li><a href="https://kalken.in/blog/" title="Blogs on Kalyani Kenekar">Kalyani Kenekar</a> <a href="https://kalken.in/blog/index.xml">(feed)</a></li>
    
    <li><a href="https://0x1f1f.wordpress.com" title="Localized Gujarati Geek">Kartik Mistry</a> <a href="https://0x1f1f.wordpress.com/feed/atom/">(feed)</a></li>
    
    <li><a href="https://katharaa.github.io/katharablog/blogs/" title="Blogs on Kathara Sasikumar">Kathara Sasikumar</a> <a href="https://kathara.in/blogs/index.xml">(feed)</a></li>
    
    <li><a href="https://outflux.net/blog" title="Debian – codeblog">Kees Cook</a> <a href="https://outflux.net/blog/archives/category/debian/feed/">(feed)</a></li>
    
    <li><a href="http://keithp.com/blog/" title="blog">Keith Packard</a> <a href="https://keithp.com/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://kenhys.hatenablog.jp/archive/category/PlanetDebian" title="PlanetDebian - ひとりしずかに。">Kentaro Hayashi</a> <a href="https://kenhys.hatenablog.jp/rss/category/PlanetDebian">(feed)</a></li>
    
    <li><a href="https://blog.legoktm.com/" title="The Lego Mirror - debian">Kunal Mehta</a> <a href="https://blog.legoktm.com/feeds/tag-debian.atom.xml">(feed)</a></li>
    
    <li><a href="https://kwk.systems/blog/" title="Biosystems Engineering Blog (Posts about debian)">Kurt Kremitzki</a> <a href="https://www.kwk.systems/blog/categories/debian.xml">(feed)</a></li>
    
    <li><a href="https://www.roeckx.be/journal/" title="Kurt Roeckx's journal">Kurt Roeckx</a> <a href="https://www.roeckx.be/journal/index.rss">(feed)</a></li>
    
    <li><a href="https://larjona.wordpress.com" title="English – The bright side">Laura Arjona Reina</a> <a href="https://larjona.wordpress.com/tag/english/feed/">(feed)</a></li>
    
    <li><a href="http://allentiak.github.io/" title="Just a Blog">Leandro Doctors</a> <a href="https://allentiak.github.io/feed.xml">(feed)</a></li>
    
    <li><a href="https://leogg.wordpress.com" title="planet-debian – [comuNIdad]">Leandro Gómez</a> <a href="https://leogg.wordpress.com/category/planet-debian/feed/atom/">(feed)</a></li>
    
    <li><a href="https://costela.net" title="debian planet – costela.net">Leo 'costela' Antunes</a> <a href="https://costela.net/tag/debian-planet/feed/">(feed)</a></li>
    
    <li><a href="https://liorkaplan.wordpress.com" title="Free Software Universe">Lior Kaplan</a> <a href="https://liorkaplan.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://perezmeyer.com.ar/" title="Solo sé que se querer, que tengo Dios y tengo fe - planetdebian-en">Lisandro Damián Nicanor Pérez Meyer</a> <a href="https://perezmeyer.com.ar/blog/feeds/tags/planetdebian-en.atom.xml">(feed)</a></li>
    
    <li><a href="https://veronneau.org/" title="Louis-Philippe Véronneau">Louis-Philippe Véronneau</a> <a href="https://veronneau.org/feeds/languages/en.atom.xml">(feed)</a></li>
    
    <li><a href="https://kaeso.wordpress.com" title="planet-debian – Aquì estamos!">Luca Bruno</a> <a href="https://kaeso.wordpress.com/category/planet-debian/feed/atom/">(feed)</a></li>
    
    <li><a href="https://dktrkranz.wordpress.com" title="Planet Debian – Kranz Korner">Luca Falavigna</a> <a href="https://dktrkranz.wordpress.com/category/planet-debian/feed/atom/">(feed)</a></li>
    
    <li><a href="http://slackydeb.blogspot.com/search/label/gsoc" title="slackydeb">Luca Favatella</a> <a href="http://slackydeb.blogspot.com/feeds/posts/default/-/gsoc">(feed)</a></li>
    
    <li><a href="https://www.lucas-nussbaum.net/blog" title="PlanetDebian – Lucas Nussbaum's Blog">Lucas Nussbaum</a> <a href="https://www.lucas-nussbaum.net/blog/?feed=rss2&amp;cat=13">(feed)</a></li>
    
    <li><a href="" title="Comentários sobre:">Luciano Prestes Cavalcanti</a> <a href="https://softwarelivre.org/lucianopc/blog/feed/">(feed)</a></li>
    
    <li><a href="https://blog.slyon.de" title="Debian – SlyBlog">Lukas Märdian</a> <a href="https://blog.slyon.de/category/debian/feed/">(feed)</a></li>
    
    <li><a href="https://blog.luke.wf/search/label/debian-planet" title="Luke W. Faraone">Luke Faraone</a> <a href="https://www.blogger.com/feeds/1354659632607599854/posts/default/-/debian-planet">(feed)</a></li>
    
    <li><a href="http://www.news.software.coop" title="Software Cooperative News » mjr">MJ Ray</a> <a href="https://mjr.towers.org.uk/blog/all.rss">(feed)</a></li>
    
    <li><a href="https://people.debian.org/~mafm/" title="Manuel A. Fernandez Montecelo :: Personal Debian page - planet-debian">Manuel A. Fernandez Montecelo</a> <a href="https://people.debian.org/~mafm/feeds/tag-planet-debian.atom.xml">(feed)</a></li>
    
    <li><a href="http://gonzo.dicp.de/~he/blog/" title="Marc's blog">Marc 'HE' Brockschmidt</a> <a href="http://gonzo.dicp.de/~he/blog/feeds/index.rss2">(feed)</a></li>
    
    <li><a href="https://mltplanet.livejournal.com/" title="Brezka">Marcela Tiznado</a> <a href="https://mltplanet.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="https://blog.bofh.it/" title="Md at debian.org">Marco d'Itri</a> <a href="https://blog.bofh.it/debian/?format=atom">(feed)</a></li>
    
    <li><a href="https://siamezzze.github.io/" title="Maria Glukhova">Maria Glukhova</a> <a href="https://siamezzze.github.io/feed.xml">(feed)</a></li>
    
    <li><a href="https://blog.sirena.org.uk/" title="Technicalities">Mark Brown</a> <a href="https://blog.sirena.org.uk/rss/">(feed)</a></li>
    
    <li><a href="http://mlalic.blogspot.com/" title="Marko Lalic">Marko Lalic</a> <a href="http://mlalic.blogspot.com/feeds/posts/default?alt=rss">(feed)</a></li>
    
    <li><a href="https://gambaru.de/blog" title="planetdebian – gambaru.de">Markus Koschany</a> <a href="https://gambaru.de/blog/tag/planetdebian/feed/">(feed)</a></li>
    
    <li><a href="https://www.cyrius.com/" title="Martin Michlmayr">Martin Michlmayr</a> <a href="https://www.cyrius.com/blog/feed/">(feed)</a></li>
    
    <li><a href="https://blog.zobel.ftbfs.de/" title="zobel's blog">Martin Zobel-Helas</a> <a href="https://blog.zobel.ftbfs.de/feed.xml">(feed)</a></li>
    
    <li><a href="http://q-funk.blogspot.com/" title="Funkyware: ITCetera">Martin-&#201;ric Racine</a> <a href="http://q-funk.blogspot.com/atom.xml">(feed)</a></li>
    
    <li><a href="https://www.mhatta.org/" title="Masayuki Hatta">Masayuki Hatta</a> <a href="https://www.mhatta.org/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="https://mateusbellomo.wordpress.com" title="MateusBellomo">Mateus Bellomo</a> <a href="https://mateusbellomo.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://sathieu.wordpress.com" title="planetdebian – Sathieu: Computers and more">Mathieu Parent</a> <a href="https://sathieu.wordpress.com/tag/planetdebian/feed/">(feed)</a></li>
    
    <li><a href="https://matrix-team.pages.debian.net/blogue/" title="Matrix on Debian blog">Matrix on Debian blog</a> <a href="https://matrix-team.pages.debian.net/blogue/atom.xml">(feed)</a></li>
    
    <li><a href="https://codon.org.uk/~mjg59/blog/post/" title="Posts on Matthew Garrett's Blog">Matthew Garrett</a> <a href="https://codon.org.uk/~mjg59/blog/index.xml">(feed)</a></li>
    
    <li><a href="https://www.hezmatt.org/~mpalmer/blog/" title="Brane Dump">Matthew Palmer</a> <a href="https://www.hezmatt.org/~mpalmer/blog/rss.xml">(feed)</a></li>
    
    <li><a href="https://people.debian.org/~werdahias/" title="werdahias' debian blog">Matthias Geiger</a> <a href="https://people.debian.org/~werdahias/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="https://blog.tenstral.net" title="Debian – Ximions Blog">Matthias Klumpp</a> <a href="https://blog.tenstral.net/category/planet/planet-debian/feed">(feed)</a></li>
    
    <li><a href="https://smurf-debian.livejournal.com/" title="Debian Ramblings">Matthias Urlichs</a> <a href="https://smurf-debian.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="https://matthieu.io" title="matthieu.io - Debian">Matthieu Caneill</a> <a href="https://matthieu.io/blog/feed/rss.debian.xml">(feed)</a></li>
    
    <li><a href="http://blog.dogguy.org/search/label/planetdebian" title="Crazy world...">Mehdi Dogguy</a> <a href="http://blog.dogguy.org/feeds/posts/default/-/planetdebian?alt=rss">(feed)</a></li>
    
    <li><a href="http://blog.alphascorpii.net/english" title="Comments from the rusty cage">Meike Reichle</a> <a href="http://blog.alphascorpii.net/english/index.rss">(feed)</a></li>
    
    <li><a href="https://melissawen.github.io/" title="Wen.onweb">Melissa Wen</a> <a href="https://melissawen.github.io/feed.xml">(feed)</a></li>
    
    <li><a href="http://mesutcaneng.blogspot.com/" title="Mesutcan Kurt's Blog Site">Mesutcan Kurt</a> <a href="http://mesutcaneng.blogspot.com/feeds/posts/default">(feed)</a></li>
    
    <li><a href="https://abbbi.github.io//" title="Michael Ablassmeier">Michael Ablassmeier</a> <a href="https://abbbi.github.io/feed.xml">(feed)</a></li>
    
    <li><a href="https://ncommander.blogspot.com/" title="Notes from Underground ...">Michael Casadevall</a> <a href="https://www.blogger.com/feeds/4695702196537398257/posts/default">(feed)</a></li>
    
    <li><a href="http://www.credativ.us/blog/50" title="Michael Meskes's blog">Michael Meskes</a> <a href="https://www.credativ.de/?utm_source=credativ.us/blog/50/feed">(feed)</a></li>
    
    <li><a href="https://michael-prokop.at/blog" title="Debian – mikas blog">Michael Prokop</a> <a href="https://michael-prokop.at/blog/category/debian/feed/">(feed)</a></li>
    
    <li><a href="https://michael.stapelberg.ch/posts/tags/debian/" title="Michael Stapelbergs Website: posts tagged debian">Michael Stapelberg</a> <a href="https://michael.stapelberg.ch/posts/tags/debian/feed.xml">(feed)</a></li>
    
    <li><a href="https://mvogt.wordpress.com" title="Michael Vogt's blog">Michael Vogt</a> <a href="https://mvogt.wordpress.com/category/debian/feed/">(feed)</a></li>
    
    <li><a href="https://blog.cihar.com/archives/debian/" title="Michal Čihař's Weblog, posts tagged by Debian">Michal &#268;iha&#345;</a> <a href="https://blog.cihar.com/archives/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://michel-slm.name/posts/" title="Posts on Pensées de Michel">Michel Lind</a> <a href="https://michel-slm.name/posts/index.xml">(feed)</a></li>
    
    <li><a href="https://xerakko.livejournal.com/" title="Twin blog">Miguel Gea</a> <a href="https://xerakko.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="http://www.debianslashrules.org//" title="debian/rules">Mike Beattie</a> <a href="http://www.debianslashrules.org/index.rss20">(feed)</a></li>
    
    <li><a href="https://sunweavers.net/blog/blog/1" title="sunweaver's blog">Mike Gabriel</a> <a href="https://sunweavers.net/blog/blog/1/feed">(feed)</a></li>
    
    <li><a href="https://glandium.org/blog" title="p.d.o – glandium.org">Mike Hommey</a> <a href="https://glandium.org/blog/?feed=rss2&amp;cat=5&amp;tag=en">(feed)</a></li>
    
    <li><a href="https://www.meebey.net/tags/planet-debian/" title="pages tagged planet-debian">Mirco Bauer</a> <a href="https://www.meebey.net/tags/planet-debian/index.rss">(feed)</a></li>
    
    <li><a href="https://morayallan.livejournal.com/" title="moray">Moray Allan</a> <a href="https://morayallan.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="http://blog.daionet.gr.jp/knok-e" title="@knok blog-e">NOKUBI Takatsugu</a> <a href="http://blog.daionet.gr.jp/knok-e/feed/">(feed)</a></li>
    
    <li><a href="https://blog.halon.org.uk" title="Geek – Liberal Murmurs">Neil McGovern</a> <a href="https://blog.halon.org.uk/category/geek/feed/">(feed)</a></li>
    
    <li><a href="http://neuro.debian.net" title="NeuroDebian Insider">NeuroDebian</a> <a href="https://neuro.debian.net/feeds/for-debian-planet.xml">(feed)</a></li>
    
    <li><a href="http://nion.modprobe.de/blog/" title="nion's blog">Nico Golde</a> <a href="https://nicsell.com/en/domain/nion.modprobe.de">(feed)</a></li>
    
    <li><a href="https://blog.olasd.eu" title="english – olasd's corner of the 'tubes">Nicolas Dandrimont</a> <a href="https://blog.olasd.eu/tag/english/feed">(feed)</a></li>
    
    <li><a href="https://people.debian.org/~nthykier/blog/" title="Things that I work on in Debian - pdo">Niels Thykier</a> <a href="https://people.debian.org/~nthykier/blog/feeds/pdo.atom.xml">(feed)</a></li>
    
    <li><a href="https://noah.meyerhans.us/categories/debian/" title="Noah Meyerhans - debian">Noah Meyerhans</a> <a href="https://noah.meyerhans.us/blog/categories/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://elchipote.wordpress.com" title="planetdebian – el blog de n0rman">Norman García</a> <a href="https://elchipote.wordpress.com/category/planetdebian/feed/atom/">(feed)</a></li>
    
    <li><a href="http://www.milliways.fr/search/label/debian" title="Milliways">Obey Arthur Liu</a> <a href="http://www.milliways.fr/feeds/posts/default/-/debian">(feed)</a></li>
    
    <li><a href="https://gasuleg.github.io//" title="Gasuleg">Olivier Grégoire</a> <a href="https://gasuleg.github.io/feed.xml">(feed)</a></li>
    
    <li><a href="http://survex.com/~olly/blog" title="Olly's Blog">Olly Betts</a> <a href="https://survex.com/~olly/blog/index.atom">(feed)</a></li>
    
    <li><a href="http://ondrejcertik.blogspot.com/search/label/debian" title="Ondřej Čertík">Ondřej Čertík</a> <a href="http://ondrejcertik.blogspot.com/feeds/posts/default/-/debian">(feed)</a></li>
    
    <li><a href="http://goofying-with-debian.blogspot.com/" title="Goofying-with-Debian">Osamu Aoki</a> <a href="http://goofying-with-debian.blogspot.com/feeds/posts/default">(feed)</a></li>
    
    <li><a href="https://optimizedbyotto.com/" title="Optimized by Otto">Otto Kekäläinen</a> <a href="https://optimizedbyotto.com/index.xml">(feed)</a></li>
    
    <li><a href="http://pstorralba.blogspot.com/search/label/tech" title="Pablo S. Torralba">Pablo S. Torralba</a> <a href="http://pstorralba.blogspot.com/feeds/posts/default/-/tech">(feed)</a></li>
    
    <li><a href="https://www.linux-dev.org" title="Blog of Patrick">Patrick Matthäi</a> <a href="https://www.linux-dev.org/feed/">(feed)</a></li>
    
    <li><a href="https://prezu.ca/" title="Patryk's blog">Patryk Cisek</a> <a href="https://prezu.ca/index.xml">(feed)</a></li>
    
    <li><a href="https://www.elpauer.org" title="Debian – elpauer">Pau Garcia i Quiles</a> <a href="https://www.elpauer.org/?cat=17&amp;feed=rss2">(feed)</a></li>
    
    <li><a href="https://blog.pault.ag/" title="Paul Tagliamonte">Paul Tagliamonte</a> <a href="https://blog.pault.ag/rss">(feed)</a></li>
    
    <li><a href="https://notes.pault.ag/" title="Paul's Notes">Paul Tagliamonte</a> <a href="https://notes.pault.ag/index.xml">(feed)</a></li>
    
    <li><a href="https://k3xec.com/" title="K3XEC">Paul Tagliamonte</a> <a href="https://k3xec.com/index.xml?_cache_bust=1705936052">(feed)</a></li>
    
    <li><a href="http://bonedaddy.net/pabs3/log/" title="Log">Paul Wise</a> <a href="https://bonedaddy.net/pabs3/log/index.rss">(feed)</a></li>
    
    <li><a href="http://paul.luon.net" title="Paul's Web Pages">Paul van Tilburg</a> <a href="https://paul.luon.net//journal/tags/debian-planet/index.xml">(feed)</a></li>
    
    <li><a href="http://phls.com.br" title="Blog de Paulo Santana - Debian">Paulo Henrique de Lima Santana</a> <a href="https://phls.com.br/feed-planetdebian-en.xml">(feed)</a></li>
    
    <li><a href="https://pavitkaur05.github.io/post/" title="Posts by Pavit Kaur">Pavit Kaur</a> <a href="https://pavitkaur05.github.io/post/index.xml">(feed)</a></li>
    
    <li><a href="http://www.lupin.org.uk/blog" title="Disconnected Mutterings of a Random Geek">Pete Nuttall</a> <a href="http://www.lupin.org.uk/blog/computing/index.rss">(feed)</a></li>
    
    <li><a href="https://www.palfrader.org/blog/" title="weasel's blog">Peter Palfrader</a> <a href="https://www.palfrader.org/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://extelligence.ringlet.net/roam/" title="Vortices of Extelligence">Peter Pentchev</a> <a href="https://extelligence.ringlet.net/roam/feed_rss_created.xml">(feed)</a></li>
    
    <li><a href="http://www.hungry.com/~pere/blog/" title="Petter Reinholdtsen - Entries tagged english">Petter Reinholdtsen</a> <a href="http://www.hungry.com/~pere/blog/tags/english/english.rss">(feed)</a></li>
    
    <li><a href="http://wiki.hands.com/chezfil/" title="chezfil">Phil Hands</a> <a href="https://wiki.hands.com/chezfil/index.rss">(feed)</a></li>
    
    <li><a href="https://debblog.philkern.de/" title="Philipp Kern's Debian blog">Philipp Kern</a> <a href="https://www.blogger.com/feeds/5048890463514304208/posts/default?alt=rss">(feed)</a></li>
    
    <li><a href="https://qendresahoti.wordpress.com" title="Qendresa's creative stories">Qendresa Hoti</a> <a href="https://qendresahoti.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="http://blog.lot-of-stuff.info//Blog/debian-planet/" title="Blog/debian-planet">R&#233;mi Vanicat</a> <a href="https://blog.lot-of-stuff.info/Blog/debian-planet/index.atom">(feed)</a></li>
    
    <li><a href="https://raju.dev/" title="raju.dev">Raju Devidas</a> <a href="https://raju.dev/rss/">(feed)</a></li>
    
    <li><a href="https://raphaelhertzog.com" title="apt-get install debian-wizard">Rapha&#235;l Hertzog</a> <a href="https://raphaelhertzog.com/tag/debian/feed/">(feed)</a></li>
    
    <li><a href="https://rgeissert.blogspot.com/" title="Raphael's blog">Raphael Geissert</a> <a href="https://rgeissert.blogspot.com/feeds/posts/default">(feed)</a></li>
    
    <li><a href="https://ravidwivedi.in/posts/" title="Posts on Ravi's Blog">Ravi Dwivedi</a> <a href="https://ravidwivedi.in/posts/index.xml">(feed)</a></li>
    
    <li><a href="http://tauware.blogspot.com/" title="random thoughts of a F/OSS Developer...">Reinhard Tartler</a> <a href="http://tauware.blogspot.com/atom.xml">(feed)</a></li>
    
    <li><a href="https://rsip22.github.io/blog/" title="Renata's blog">Renata D'Avila</a> <a href="https://rsip22.github.io/blog/feeds/all.rss.xml">(feed)</a></li>
    
    <li><a href="https://reproducible-builds.org/blog/" title="reproducible-builds.org">Reproducible Builds</a> <a href="https://reproducible-builds.org/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://diffoscope.org/" title="diffoscope">Reproducible Builds (diffoscope)</a> <a href="https://diffoscope.org/feed.xml">(feed)</a></li>
    
    <li><a href="https://rhonda.deb.at/blog/" title="Rhonda's Blog">Rhonda D'Vine</a> <a href="https://rhonda.deb.at/blog/index.planet">(feed)</a></li>
    
    <li><a href="https://mones.livejournal.com/" title="Ricardo Mones">Ricardo Mones</a> <a href="https://mones.livejournal.com/data/rss?tag=debian">(feed)</a></li>
    
    <li><a href="http://suihkulokki.blogspot.com/search/label/debian" title="suihkulokki rambling">Riku Voipio</a> <a href="http://suihkulokki.blogspot.com/feeds/posts/default/-/debian">(feed)</a></li>
    
    <li><a href="https://researchut.com/categories/debian-blog/" title="Debian-Blog on RESEARCHUT">Ritesh Raj Sarraf</a> <a href="https://www.researchut.com/categories/debian-blog/atom.xml">(feed)</a></li>
    
    <li><a href="http://blog.floopily.org" title="Stories from the land of Rob">Rob Taylor</a> <a href="http://blog.floopily.org/feed/">(feed)</a></li>
    
    <li><a href="http://blog.mycre.ws/" title="Robert Edmonds' blog">Robert Edmonds</a> <a href="https://blog.mycre.ws/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="https://ramcq.net" title="Robotic Tendencies">Robert McQueen</a> <a href="https://ramcq.net/feed/">(feed)</a></li>
    
    <li><a href="https://robertmh.wordpress.com" title="Thoughts of undetermined usefulness">Robert Millan</a> <a href="https://robertmh.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://siqueira.tech/" title="Siqueira">Rodrigo Siqueira</a> <a href="https://siqueira.tech/feed.xml">(feed)</a></li>
    
    <li><a href="http://cynic.cc/blog//tags/debian/" title="pages tagged debian">Rogério Brito</a> <a href="https://cynic.cc/blog/tags/debian/index.rss">(feed)</a></li>
    
    <li><a href="http://roland.entierement.nu/categories/en.html" title="In English">Roland Mas</a> <a href="http://roland.entierement.nu/categories/en.rss">(feed)</a></li>
    
    <li><a href="https://rperier.blogspot.com/search/label/debian" title="Romain Perier's blog">Romain Perier</a> <a href="https://rperier.blogspot.com/feeds/posts/default/-/debian">(feed)</a></li>
    
    <li><a href="https://rossgammon68.wordpress.com" title="Planet Debian – Ross Gammon's Blog">Ross Gammon</a> <a href="https://rossgammon68.wordpress.com/category/open-source/debian/planet-debian/feed/">(feed)</a></li>
    
    <li><a href="https://ruby-team.pages.debian.net/" title="The Ruby Team Pages">Ruby Team</a> <a href="https://ruby-team.pages.debian.net/feed.xml">(feed)</a></li>
    
    <li><a href="" title="">Rudy Godoy</a> <a href="https://blog.stone-head.org/category/debian/feed/">(feed)</a></li>
    
    <li><a href="https://www.eyrie.org/~eagle/" title="Eagle's Path">Russ Allbery</a> <a href="https://www.eyrie.org/~eagle/journal/index.rss">(feed)</a></li>
    
    <li><a href="https://etbe.coker.com.au" title="etbe – Russell Coker">Russell Coker</a> <a href="https://etbe.coker.com.au/feed/">(feed)</a></li>
    
    <li><a href="https://rak.ac/feeds/planet-debian.xml" title="Ryan Kavanagh's /dev/brain">Ryan Kavanagh</a> <a href="https://ryanak.ca/feeds/planet-debian.xml">(feed)</a></li>
    
    <li><a href="https://blog.sahilister.in/" title="sahilister's Reimagined Doodle">Sahil Dhiman</a> <a href="https://blog.sahilister.in/index.xml">(feed)</a></li>
    
    <li><a href="https://hartmans.dreamwidth.org/" title="Sam Hartman">Sam Hartman</a> <a href="https://hartmans.dreamwidth.org/data/atom">(feed)</a></li>
    
    <li><a href="https://samueloph.dev" title="Samuel Henrique (samueloph)">Samuel Henrique</a> <a href="https://samueloph.dev/atom.xml">(feed)</a></li>
    
    <li><a href="https://blog.sandroknauss.de/" title="Decrypted Mind: Articles About Debian">Sandro Knauß</a> <a href="https://blog.sandroknauss.de/categories/debian/feed.xml">(feed)</a></li>
    
    <li><a href="http://sandrotosi.blogspot.com/" title="Sandro Tosi">Sandro Tosi</a> <a href="http://feeds.feedburner.com/SandroTosi">(feed)</a></li>
    
    <li><a href="http://blog.manty.net/search/label/Debian" title="manty's blog">Santiago García Mantiñán</a> <a href="http://blog.manty.net/feeds/posts/default/-/Debian">(feed)</a></li>
    
    <li><a href="http://satyamz.github.io/" title="Category: Debian | Satyam Zode">Satyam Zode</a> <a href="https://satyamz.github.io/blog/categories/debian/atom.xml">(feed)</a></li>
    
    <li><a href="https://www.scarlettgatelymoore.dev" title="Open Source Software – Scarlett Gately Moore">Scarlett Gately Moore</a> <a href="https://www.scarlettgatelymoore.dev/category/open-source-software/feed/">(feed)</a></li>
    
    <li><a href="https://skitterman.wordpress.com" title="Debian – ScottK might have something to say …">Scott Kitterman</a> <a href="https://skitterman.wordpress.com/category/Debian/feed/">(feed)</a></li>
    
    <li><a href="https://spwhitton.name//blog/" title="Notes from the Library">Sean Whitton</a> <a href="https://spwhitton.name/blog/index.atom">(feed)</a></li>
    
    <li><a href="http://shnatsel.blogspot.com/search/label/planet-debian" title="Shnatsel&amp;#39;s Old Blog">Sergey Davidoff</a> <a href="http://shnatsel.blogspot.com/feeds/posts/default/-/planet-debian">(feed)</a></li>
    
    <li><a href="" title="Sérgio Cipriano's Website">Sergio Cipriano</a> <a href="https://sergiocipriano.com/atom.xml">(feed)</a></li>
    
    <li><a href="https://blog.sergiodj.net" title="- debian">Sergio Durigan Junior</a> <a href="https://blog.sergiodj.net/tags/debian/atom.xml">(feed)</a></li>
    
    <li><a href="https://blogops.mixinet.net/" title="Mixinet BlogOps">Sergio Talens-Oliag</a> <a href="https://blogops.mixinet.net/index.xml">(feed)</a></li>
    
    <li><a href="https://sandyleo26.wordpress.com" title="Sandyleo26’s Blog">Sha Liu</a> <a href="https://sandyleo26.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://blog.shanky.dev/" title="Shanky's Brainchild">Shashank Kumar</a> <a href="https://blog.shanky.dev/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="http://bloc.eurion.net" title="Planet Debian – eurion.net">Siegfried Gevatter</a> <a href="https://bloc.eurion.net/archives/category/debian-planet/feed/">(feed)</a></li>
    
    <li><a href="http://sim590.github.io/" title="Simon Désaulniers">Simon Désaulniers</a> <a href="https://sim590.github.io/index.xml">(feed)</a></li>
    
    <li><a href="http://www.earth.li/~huggie/blog" title="Simon Huggins's blog">Simon Huggins</a> <a href="https://the.earth.li/~huggie/cgi-bin/blosxom.cgi/tech/debian/planet/index.atom">(feed)</a></li>
    
    <li><a href="https://blog.josefsson.org" title="Simon Josefsson's blog">Simon Josefsson</a> <a href="https://blog.josefsson.org/feed/">(feed)</a></li>
    
    <li><a href="https://sfllaw.livejournal.com/" title="Simon Law’s Journal">Simon Law</a> <a href="https://sfllaw.livejournal.com/data/rss?tag=p.d.o">(feed)</a></li>
    
    <li><a href="http://smcv.pseudorandom.co.uk/" title="Background noise">Simon McVittie</a> <a href="https://smcv.pseudorandom.co.uk/feed.atom">(feed)</a></li>
    
    <li><a href="http://sjoerd.luon.net/blog/" title="Sjoerd Simons' blog">Sjoerd Simons</a> <a href="http://sjoerd.luon.net/blog/index.atom">(feed)</a></li>
    
    <li><a href="http://sonnenburgs.de/soeren/category/blog/" title="RSS feed">Soeren Sonnenburg</a> <a href="https://sonnenburgs.de/soeren/category/blog/feed">(feed)</a></li>
    
    <li><a href="http://home-sorina.blogspot.com/search/label/Debian" title="/home/sorina">Sorina Sandu</a> <a href="http://home-sorina.blogspot.com/feeds/posts/default/-/Debian">(feed)</a></li>
    
    <li><a href="https://stefanorivera.com/categories/planet/" title="Stefano Rivera (Posts about planet)">Stefano Rivera</a> <a href="https://stefanorivera.com/categories/planet.atom">(feed)</a></li>
    
    <li><a href="http://upsilon.cc/~zack/blog/planet-debian/" title="blog/planet-debian">Stefano Zacchiroli</a> <a href="https://upsilon.cc/~zack/blog/planet-debian/index.rss">(feed)</a></li>
    
    <li><a href="https://jod.al" title="jod.al - debian">Stein Magnus Jodal</a> <a href="https://jod.al/tags/debian/atom.xml">(feed)</a></li>
    
    <li><a href="http://blog.sesse.net/" title="Steinar H. Gunderson">Steinar H. Gunderson</a> <a href="https://blog.sesse.net/blog/tech/?flav=rss">(feed)</a></li>
    
    <li><a href="https://stephan.lachnit.xyz/tags/debian/" title="Debian on Blog">Stephan Lachnit</a> <a href="https://stephan.lachnit.xyz/tags/debian/index.xml">(feed)</a></li>
    
    <li><a href="https://blog.steve.fi/" title="Steve Kemp's Blog">Steve Kemp</a> <a href="https://blog.steve.fi/index.rss">(feed)</a></li>
    
    <li><a href="http://web.dodds.net/~vorlon/wiki/blog/" title="blog">Steve Langasek</a> <a href="https://web.dodds.net/~vorlon/wiki/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://blog.einval.com" title="Steve's blog">Steve McIntyre</a> <a href="https://blog.einval.com/index.rss">(feed)</a></li>
    
    <li><a href="https://pusling.com/blog" title="english – Blog :: Sune Vuorela">Sune Vuorela</a> <a href="https://pusling.com/blog/?feed=rss2&amp;cat=3">(feed)</a></li>
    
    <li><a href="http://sven.stormbind.net/blog/" title="a blog">Sven Hoexter</a> <a href="https://sven.stormbind.net/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://blog.beuc.net/tags/planet_debian/" title="pages tagged planet debian">Sylvain Beucler</a> <a href="https://blog.beuc.net/tags/planet_debian/index.atom">(feed)</a></li>
    
    <li><a href="http://sylvain.le-gall.net/blog/index.php?" title="Blog of Sylvain Le Gall">Sylvain Le Gall</a> <a href="https://sylvain.le-gall.net/blog/index.php?feed/atom">(feed)</a></li>
    
    <li><a href="https://sylvestre.ledru.info/blog/" title="Sylvestre's blog - Catégorie: "Debian"">Sylvestre Ledru</a> <a href="https://sylvestre.ledru.info/blog/?blog=5&amp;cat=155">(feed)</a></li>
    
    <li><a href="https://taavi.wtf/posts/" title="Blog on Taavi Väänänen">Taavi Väänänen</a> <a href="https://taavi.wtf/posts/index.xml">(feed)</a></li>
    
    <li><a href="https://tanguy.ortolo.eu/blog/categorie2/debian" title="Tanguy Ortolo - Debian">Tanguy Ortolo</a> <a href="https://tanguy.ortolo.eu/blog/planet/debian.rss">(feed)</a></li>
    
    <li><a href="https://writefreely.debian.social/taowa/" title="taowa r.">Taowa</a> <a href="https://writefreely.debian.social/taowa/feed">(feed)</a></li>
    
    <li><a href="https://cascardo.eti.br/tags/debian/" title="pages tagged debian">Thadeu Lima de Souza Cascardo</a> <a href="https://cascardo.eti.br/tags/debian/index.atom">(feed)</a></li>
    
    <li><a href="https://theppitak.livejournal.com/" title="Theppitak Karoonboonyanan">Theppitak Karoonboonyanan</a> <a href="https://theppitak.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="http://gsoc.sitedethib.com/" title="Bits from a GSoC student">Thibaut Girka</a> <a href="https://gsoc.sitedethib.com/index.rss">(feed)</a></li>
    
    <li><a href="https://teejeejee.livejournal.com/" title="teejeejee">Thomas Girard</a> <a href="https://teejeejee.livejournal.com/data/rss?tag=debian">(feed)</a></li>
    
    <li><a href="http://thomas.goirand.fr/blog" title="Zigo's blog">Thomas Goirand</a> <a href="http://thomas.goirand.fr/blog/?feed=rss2">(feed)</a></li>
    
    <li><a href="http://blog.fai-project.org/" title="FAI (Fully Automatic Installation) / Plan your
Installation and FAI installs your Plan">Thomas Lange</a> <a href="https://blog.fai-project.org/index.rss">(feed)</a></li>
    
    <li><a href="http://blog.alteholz.eu" title="planetdebian – blog.alteholz.eu">Thorsten Alteholz</a> <a href="http://blog.alteholz.eu/tag/planetdebian/feed/">(feed)</a></li>
    
    <li><a href="http://tiago.acaia.ca/" title="❤ (planet-debian)">Tiago Bortoletto Vaz</a> <a href="https://qa.debian.org/developer.php?login=tiago&amp;comaint=yes">(feed)</a></li>
    
    <li><a href="https://ram.tianon.xyz/" title="Tianon's Ramblings ✿">Tianon Gravi</a> <a href="https://ram.tianon.xyz/atom.xml">(feed)</a></li>
    
    <li><a href="https://retout.co.uk/" title="Tim Retout">Tim Retout</a> <a href="https://retout.co.uk/index.xml">(feed)</a></li>
    
    <li><a href="https://losca.blogspot.com/search/label/debian" title="Losca">Timo Jyrinki</a> <a href="https://losca.blogspot.com/feeds/posts/default/-/debian?alt=rss">(feed)</a></li>
    
    <li><a href="https://ttroxell.livejournal.com/" title="Work Blog">Todd Troxell</a> <a href="https://ttroxell.livejournal.com/data/rss">(feed)</a></li>
    
    <li><a href="https://err.no/personal/blog/" title="Blogs on Here be dragons">Tollef Fog Heen</a> <a href="https://err.no/personal/blog/index.xml">(feed)</a></li>
    
    <li><a href="http://dangel.im/blog/" title="Ulrichs blog - Debian">Ulrich Dangel</a> <a href="https://dangel.im/blog/debian.xml">(feed)</a></li>
    
    <li><a href="https://the.curlybracket.net/" title="curlybracket">Ulrike Uhlig</a> <a href="https://the.curlybracket.net/atom.xml">(feed)</a></li>
    
    <li><a href="https://urvikagola.wordpress.com" title="Urvika Gola">Urvika Gola</a> <a href="https://urvikagola.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="https://utkarsh2102.org/" title="utkarsh2102">Utkarsh Gupta</a> <a href="https://utkarsh2102.org/index.xml">(feed)</a></li>
    
    <li><a href="https://blog.kleine-koenig.org/ukl/" title="ukl's blog">Uwe Kleine-König</a> <a href="https://blog.kleine-koenig.org/ukl/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="http://www.spectranaut.cc" title="Spectranaut (Valerie Young)">Valerie Young</a> <a href="https://spectranaut.cc/?tag=debian&amp;feed=rss2">(feed)</a></li>
    
    <li><a href="https://blog.trueelena.org" title="Valhalla's things">Valhalla's Things</a> <a href="https://blog.trueelena.org/atom.xml">(feed)</a></li>
    
    <li><a href="https://copyninja.in/" title="Random Ramblings">Vasudev Kamath</a> <a href="https://copyninja.in/feeds/all.atom.xml">(feed)</a></li>
    
    <li><a href="https://vincent.bernat.ch/en" title="Vincent Bernat">Vincent Bernat</a> <a href="https://vincent.bernat.ch/en/blog/atom.xml">(feed)</a></li>
    
    <li><a href="https://vince-debian.blogspot.com/" title="YANUB: yet another (nearly) useless blog">Vincent Fourmond</a> <a href="https://vince-debian.blogspot.com/feeds/posts/default">(feed)</a></li>
    
    <li><a href="http://vincentsanders.blogspot.com/" title="Vincents Random Waffle">Vincent Sanders</a> <a href="http://vincentsanders.blogspot.com/feeds/posts/default">(feed)</a></li>
    
    <li><a href="https://billblough.net/" title="Bill's Web Log - FLOSS">William (Bill) Blough</a> <a href="https://billblough.net/feeds/floss.atom.xml">(feed)</a></li>
    
    <li><a href="https://grep.be/blog//pd/" title="pd">Wouter Verhelst</a> <a href="https://grep.be/blog/pd/index.rss">(feed)</a></li>
    
    <li><a href="https://appaji.livejournal.com/" title="Morsels of life!">Y Giridhar Appaji Nag</a> <a href="https://appaji.livejournal.com/data/atom">(feed)</a></li>
    
    <li><a href="https://segments.zhan.science/" title="segments: Yifei Zhan's blog">Yifei Zhan</a> <a href="https://segments.zhan.science/index.xml">(feed)</a></li>
    
    <li><a href="http://www.corsac.net/?cat=3" title="Corsac.net - Debian">Yves-Alexis Perez</a> <a href="https://www.corsac.net/rss.php?cat=debian">(feed)</a></li>
    
    <li><a href="https://zgrimshell.github.io/" title="i.am.z.grim.shell">Zlatan Todorić</a> <a href="https://zgrimshell.github.io/rss.xml">(feed)</a></li>
    
    <li><a href="https://people.debian.org/~intrigeri/blog/" title="intrigeri's blog">intrigeri</a> <a href="https://people.debian.org/~intrigeri/blog/index.rss">(feed)</a></li>
    
    <li><a href="https://vulns.xyz" title="vulns.xyz">kpcyrd</a> <a href="https://vulns.xyz/feed.xml">(feed)</a></li>
    
    <li><a href="https://loldebian.wordpress.com" title="loldebian: i can has a rc bug?">loldebian - Can I has a RC bug?</a> <a href="https://loldebian.wordpress.com/feed/">(feed)</a></li>
    
    <li><a href="http://blog.puer-robustus.eu/tags/debian/" title="Debian on puer robustus">puer-robustus</a> <a href="https://blog.puer-robustus.eu/tags/debian/feed.xml">(feed)</a></li>
    
   </ul>
  </div>
 </body>
</html>

