<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>eta.st</title>
    <description>eta&apos;s personal website &amp; blog
</description>
    <link>https://eta.st/</link>
    <atom:link href="https://eta.st/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 15 Dec 2025 23:45:01 +0000</pubDate>
    <lastBuildDate>Mon, 15 Dec 2025 23:45:01 +0000</lastBuildDate>
    <generator>Jekyll v4.3.4</generator>
    
      <item>
        <title>Reversing UK mobile rail tickets</title>
        <description>&lt;p&gt;The UK has used small credit-card sized tickets to pay for train travel for years and years, since long before I was born — originally the
&lt;a href=&quot;https://en.wikipedia.org/wiki/APTIS_ticket_features&quot;&gt;APTIS ticket&lt;/a&gt;&lt;sup id=&quot;fnref:aptis&quot;&gt;&lt;a href=&quot;#fn:aptis&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;,
which later got replaced by a
&lt;a href=&quot;https://en.wikipedia.org/wiki/2014_National_Rail_ticket_features&quot;&gt;slightly easier to read version&lt;/a&gt; printed onto the same stock.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/ticket.jpg&quot; alt=&quot;a National Rail paper ticket&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Nowadays, the industry would very much like you to ditch your paper ticket in favour of a fancy mobile barcode one (or
an &lt;a href=&quot;https://en.wikipedia.org/wiki/ITSO_Ltd&quot;&gt;ITSO&lt;/a&gt; smartcard&lt;sup id=&quot;fnref:itso&quot;&gt;&lt;a href=&quot;#fn:itso&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;); not
only do they not have to spend money on printing tickets but they also gain the ability to more precisely track the ticket’s usage across the network
and minimise fraud.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/why-mobile.png&quot; alt=&quot;promotional material about the benefits of mobile ticketing&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are obvious benefits for the user too — I’m willing to bet most people use the mobile tickets anyway, since they’re just easier
if you’re booking your train travel in an app like &lt;a href=&quot;https://www.thetrainline.com/&quot;&gt;Trainline&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But what data is inside the barcode of a mobile ticket, and how do they work? Could people who aren’t ticket inspectors get the data out of them?
It turns out that the answer is a bit more interesting than I initially expected!&lt;/p&gt;

&lt;h2 id=&quot;initial-explorations&quot;&gt;Initial explorations&lt;/h2&gt;

&lt;p&gt;A mobile ticket is just an &lt;a href=&quot;https://en.wikipedia.org/wiki/Aztec_Code&quot;&gt;Aztec barcode&lt;/a&gt;, either displayed inside an app or on a PDF you can print out:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/initial-ticket.png&quot; alt=&quot;image of a Trainline ticket barcode, from Cathays (CYS) to Cardiff Queen St (CDQ), UTN TTDNQMCQF6S&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Googling around for prior work people had done decoding mobile tickets, I found
&lt;a href=&quot;https://de.wikipedia.org/w/index.php?title=Diskussion:Online-Ticket&amp;amp;oldid=198973306#Barcode&quot;&gt;a bunch of discussion&lt;/a&gt; (German-language link) about UIC 918.3,
a specification used by the German railway company Deutsche Bahn for their e-tickets. These also use the Aztec barcode format and looked superficially
similar — and some people had already written code to read them. Maybe this could work?&lt;/p&gt;

&lt;p&gt;(Why would I expect this to work? Well, the UIC is the international standards body for railways — in Europe at least —
so it’s reasonable to assume they might’ve used a standard format here.)&lt;/p&gt;

&lt;p&gt;However, the formats are sadly nothing alike — decoding our UK barcode using &lt;a href=&quot;https://github.com/zxing/zxing&quot;&gt;zxing&lt;/a&gt;, we get:&lt;/p&gt;

&lt;pre style=&quot;white-space: pre-wrap; overflow-wrap: anywhere;&quot;&gt;
06DNQL4XHVK00TTRCGPUQWNTHPGHWBPOUTKRWXAJKGHFBAPBCTOGUZQVTZTKKDEBQXPGRWZJRJBXJZPOHNJGIPDJWEGYWJXLVPGEEZBCUUELIJMOINPRZMSDQCZJGLIZLUTQHXMTPKWCMJISUXQLORAOVYXSOLGXXGMVUDXTMHAYMBLUTKPUPFCRNNTDBBDLNWSBPDUXYKSIMJSBYBURSCPUMFBZPEUTECHTIOXAH
&lt;/pre&gt;

&lt;p&gt;…which looks nothing like you might expect a UIC barcode to look, given the latter are supposed to start with “#UT”
(according to the discussion in German earlier).&lt;/p&gt;

&lt;p&gt;In fact, this is a custom standard that is only used inside the UK, as the “06” at the start of the data hints at; this is an “RSP-6” ticket
(as in RSP for &lt;a href=&quot;https://en.wikipedia.org/wiki/Rail_Settlement_Plan&quot;&gt;Rail Settlement Plan&lt;/a&gt;), which Google doesn’t seem to know much about.
It is possible to find a &lt;a href=&quot;https://www.whatdotheyknow.com/request/rsp_6_specification_for_barcodes_2&quot;&gt;Freedom of Information Act request&lt;/a&gt; someone made
asking for the spec that never got a response — sadly the Rail Delivery Group (RDG), technically a private company, doesn’t actually have to respond
to such requests, so I’d have to figure this out myself.&lt;/p&gt;

&lt;h2 id=&quot;a-friendly-wolf-comes-to-help&quot;&gt;A friendly wolf comes to help&lt;/h2&gt;

&lt;p&gt;At this point, I basically had no idea how to continue. Comparing multiple tickets, the data seemed to be mostly random apart from some fixed headers,
suggesting that it was probably encrypted in some way — I couldn’t just get lots of tickets and hope to find similarities between them.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/binwalk.png&quot; alt=&quot;photo of &apos;binwalk -W cdq-cys.bin pad-aml.bin&apos;, showing no shared data apart from a few random bytes and some headers&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;image: output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;binwalk -W cdq-cys.bin pad-aml.bin&lt;/code&gt;, which highlights similarities and differences between two tickets&lt;sup id=&quot;fnref:binwalk&quot;&gt;&lt;a href=&quot;#fn:binwalk&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My friend Harley (“&lt;a href=&quot;https://lobi.to/&quot;&gt;unlobito&lt;/a&gt;”) had noticed me complaining about tickets in a shared group chat and had a clue for me:
the word “masabi”, which turned out to be the name of &lt;a href=&quot;https://www.masabi.com/&quot;&gt;a ticketing company&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Masabi’s website has &lt;a href=&quot;https://www.masabi.com/justride-uk-rail/&quot;&gt;this lovely page&lt;/a&gt; where they explain all about how they invented mobile
ticketing in the UK in 2007 and how the RSP6 national standard was actually written by them! They also boast about how their
“JustRide Inspect” suite of apps can be used to decode these tickets.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/justride-web.png&quot; alt=&quot;Masabi promotional copy about their Inspect app&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We sadly can’t just get this app off the Play Store. However, after some googling around you can totally get it from one of those less
than official APK rehosting websites.&lt;/p&gt;

&lt;p&gt;With the APK in hand there are a number of things we can do. We can just install it on an Android device and see whether it’ll give up
anything interesting that way; we can also try to “decompile” it, to get a better idea of how the app (and the ticket parser) works.&lt;/p&gt;

&lt;h2 id=&quot;running-the-app&quot;&gt;Running the app&lt;/h2&gt;

&lt;p&gt;Since I didn’t have any spare throwaway Android phones and that the APK might be malware, my first step was to just run it
inside an 
&lt;a href=&quot;https://developer.android.com/studio/run/managing-avds&quot;&gt;Android Virtual Device&lt;/a&gt; (using the emulator in Android Studio).
I reckoned this would be a bit safer than just installing it on my main Android phone.&lt;sup id=&quot;fnref:avd&quot;&gt;&lt;a href=&quot;#fn:avd&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;After a bit of fiddling about, I had it doing something:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/app-screen1.png&quot; alt=&quot;Inspect app showing &amp;quot;Scan config barcode&amp;quot; screen&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, this wasn’t very useful — it wouldn’t scan standard ticket barcodes in this form. The “Login manually” button lets you
choose a Train Operating Company (TOC) to sign in as (some of the companies mentioned don’t even exist any more!), which was pretty
interesting but not useful for our goals:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/app-screen2.png&quot; alt=&quot;Inspect app showing list of TOCs&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I don’t work for a train operating company, so clearly it wasn’t going to be possible for me to proceed&lt;sup id=&quot;fnref:notoc&quot;&gt;&lt;a href=&quot;#fn:notoc&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.
Maybe examining the app another way could get us somewhere?&lt;/p&gt;

&lt;h2 id=&quot;decompiling-the-app&quot;&gt;Decompiling the app&lt;/h2&gt;

&lt;p&gt;You can point Android Studio at an APK and have it analyse what’s inside. Sort of.&lt;/p&gt;

&lt;p&gt;If you try this (via the 3-dot menu in the project chooser → “Profile or Debug APK”), you get something that’s not entirely useful:
a bunch of weird looking “smali” files.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/smalis.png&quot; alt=&quot;Android Studio showing debug view of the APK&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is because the APK only contains compiled bytecode, rather than any useful source code; as the yellow warning banner notes,
this is stored inside the APK in &lt;a href=&quot;https://source.android.com/docs/core/runtime/dex-format&quot;&gt;.dex format&lt;/a&gt;
(“Dalvik Executable”, where Dalvik is the name of the Android VM), and
&lt;a href=&quot;https://github.com/JesusFreke/smali&quot;&gt;smali&lt;/a&gt; is just a human-readable representation someone invented for this format (like Assembly).&lt;/p&gt;

&lt;p&gt;What we ideally need is something that can turn the bytecode all the way back into Java. Such a tool exists in the form of
&lt;a href=&quot;https://github.com/skylot/jadx&quot;&gt;jadx&lt;/a&gt;, a very handy tool that not only does just that, but can also do a bunch of other
cool stuff like outputting a project Android Studio can load (and theoretically compile)!&lt;/p&gt;

&lt;pre style=&quot;white-space: pre-wrap; overflow-wrap: anywhere;&quot;&gt;
$ jadx --deobf -e -d out ~/Downloads/justride-inspect.apk
INFO  - loading ...
INFO  - processing ...
INFO  - done                                                 
$ ls out/
app  build.gradle  settings.gradle
&lt;/pre&gt;

&lt;h2 id=&quot;making-sense-of-the-decompiled-output&quot;&gt;Making sense of the decompiled output&lt;/h2&gt;

&lt;p&gt;When I looked in jadx’s output directory I found a perfect copy of the original source code that Masabi had written,
and my job was then very easy. Wait, no, that’s a lie.&lt;/p&gt;

&lt;p&gt;Before shipping an APK to the end user, Android app developers usually run it through a number of steps to make it smaller
including “obfuscation” — turning long class and member names like “mContext” into the smallest string they can get away with, like “a”.
This means that the code jadx generated is rather hard to make sense of:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TicketInspectActivity&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseActivity&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InterfaceC2526a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

   &lt;span class=&quot;cm&quot;&gt;/* renamed from: c */&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ViewPager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f4615c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;cm&quot;&gt;/* JADX INFO: Access modifiers changed from: private */&lt;/span&gt;
   &lt;span class=&quot;cm&quot;&gt;/* renamed from: h */&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;C2496p&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;m1419h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;C2496p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;f4615c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// com.masabi.app.android.ticketcheck.activities.BaseActivity&lt;/span&gt;
   &lt;span class=&quot;cm&quot;&gt;/* renamed from: a */&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mo1410a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;mo1410a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isFinishing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
           &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;m1419h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;m1467a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;f4615c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCurrentItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Mmm yes, I knew exactly what they meant when they named their class &lt;code&gt;C2496p&lt;/code&gt;. Of course! We just need to trace the execution
of &lt;code&gt;void mo1410a()&lt;/code&gt; and then we’ll figure it all out!&lt;sup id=&quot;fnref:jadx&quot;&gt;&lt;a href=&quot;#fn:jadx&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Despite this looking daunting at first it’s actually quite okay with the tools Android Studio gives us. Not everything is completely obfuscated:
some class names need to be left deobfuscated, such as activities (like this &lt;code&gt;TicketInspectActivity&lt;/code&gt;). That lets us get some idea of
where to start. The code also occasionally contains error messages that give away what the classes and methods are supposed to be:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;cm&quot;&gt;/* renamed from: com.masabi.c.a */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* loaded from: classes.dex */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;C2666a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;cm&quot;&gt;/* renamed from: a */&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;m552a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Calendar&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;calendar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;calendar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
           &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;DateTimeUtils.packDate() ERROR - Attempt to pack a null date!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;C2668c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;m542a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;calendar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;C2667b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;m548a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;calendar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;65535&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In this case, the log line lets us instantly rename &lt;code&gt;C2666a&lt;/code&gt; → &lt;code&gt;DateTimeUtils&lt;/code&gt;, and &lt;code&gt;m552a&lt;/code&gt; → &lt;code&gt;packDate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Android Studio also has excellent support for doing renames across an entire codebase at once, so after a long afternoon
picking things apart it quickly started to take shape and our obfuscated code began to look something like the original source code
might have looked&lt;sup id=&quot;fnref:osource&quot;&gt;&lt;a href=&quot;#fn:osource&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/refactor-this.png&quot; alt=&quot;the Refactor → Rename menu in Android Studio&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;interesting-uses-of-rsa&quot;&gt;Interesting uses of RSA&lt;/h2&gt;

&lt;p&gt;My investigations into the app confirmed my suspicions that the data was indeed encrypted — well, not quite. Technically, the ticket data
is actually &lt;em&gt;signed&lt;/em&gt; with RSA and &lt;a href=&quot;https://en.wikipedia.org/wiki/PKCS_1&quot;&gt;PKCS#1&lt;/a&gt; (I think). Ticket issuers generate a payload containing the
ticket data, pad it a bit, and then use their RSA private key to create a signed message they put into the barcode. A ticket scanner has a set
of the issuers’ public keys on hand to verify the signature and read the original payload.&lt;/p&gt;

&lt;p&gt;As a more concrete example, some vague Rust code to do the verification and reading steps looks a bit like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// BigUint is an arbitrary size unsigned integer.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// The ticket is base26 encoded, so we need to undo that first:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BigUint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;base26_decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// this is doing “S^e mod N”;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// i.e. part of RSA signature verification&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.modpow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.public_exponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.modulus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// convert big integer into raw bytes (big-endian)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_bytes_be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// attempt to strip PKCS#1 padding; if it fails, the key is wrong&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unpadded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;strip_padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nd&quot;&gt;eprintln!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;[+] decrypt done: {:?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unpadded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I’m not a cryptographer, so this was all somewhat new to me! I was used to signatures being a &lt;a href=&quot;https://en.wikipedia.org/wiki/Hash_function&quot;&gt;hash&lt;/a&gt;
of the original message (i.e. you’d send the plaintext, and then &lt;code&gt;sign(hash(plaintext))&lt;/code&gt; along with it), which is usually done
so that you can &lt;a href=&quot;https://crypto.stackexchange.com/questions/9896/how-does-rsa-signature-verification-work&quot;&gt;sign messages longer than the size of your keys&lt;/a&gt;.
In this case, they’ve put the &lt;em&gt;whole&lt;/em&gt; message inside the signature to save space on the barcode, meaning you need the public keys to read the message at all.&lt;/p&gt;

&lt;p&gt;You also can’t make your own fraudulent tickets using this scheme; you’d need the RSA private key of one of the ticket issuers to do that,
or to have a custom public key added to the network of gate readers and ticket inspectors’ apps, neither of which seem easy to do.&lt;/p&gt;

&lt;details class=&quot;other-projects-details&quot;&gt;
&lt;summary class=&quot;other-projects-summary&quot;&gt;Some further details on the cryptography (click to expand)&lt;/summary&gt;
&lt;div&gt;
&lt;p&gt;How can you tell that the ticket payload was unwrapped correctly? The payload is padded in a way that I think corresponds to some of the
algorithms in PKCS#1 (see &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8017&quot;&gt;RFC 8017&lt;/a&gt;); it&apos;ll either be&lt;/p&gt;

&lt;p&gt;
Scheme 1: &lt;code&gt;padded = [0x00, 0x01, padding-string, 0x00, message]&lt;/code&gt;&lt;br /&gt;
(where &lt;code&gt;padding-string&lt;/code&gt; is a length of &lt;code&gt;0xFF&lt;/code&gt; octets)
&lt;/p&gt;

&lt;p&gt;
or
&lt;/p&gt;

&lt;p&gt;
Scheme 2: &lt;code&gt;padded = [0x00, 0x02, padding-string, 0x00, message]&lt;/code&gt;&lt;br /&gt;
(where &lt;code&gt;padding-string&lt;/code&gt; is a length of random non zero octets)
&lt;/p&gt;

&lt;p&gt;If the payload doesn&apos;t look like either of these, the RSA operation failed, so you probably have the wrong key and should try another one.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;So the public keys are required knowledge for actually being able to decode these tickets. Where do we get those from?&lt;/p&gt;

&lt;h2 id=&quot;obtaining-the-elusive-public-keys&quot;&gt;Obtaining the elusive public keys&lt;/h2&gt;

&lt;p&gt;The public keys aren’t really published anywhere obvious, and reversing the Masabi app seems to indicate that it downloads the keys from a
configuration server once you scan the config barcode mentioned earlier.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;Global&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSimpleName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;loadAllKeys() - Fetched &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;barcodeKeysList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; barcode keys from metadata&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;barcodeKeysList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nc&quot;&gt;AbstractJSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;barcodeKeysList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ExtendedGlobal2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCurrentTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Global&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;f4949d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;mo915a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;expiryDate&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
           &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decoder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;makeDecoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;issuerId&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ticketType&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modulus&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;exponent&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mQ&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;decoders&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addElement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You’d think this would be a dead end, since we don’t have any login credentials — but they also just left some keys inside the APK
as well. As far as I can tell no part of the app actually reads these; maybe it did in the past, or maybe they used them for testing and
forgot to take them out of the production version of the app.&lt;/p&gt;

&lt;pre&gt;
$ find . | grep &apos;keys&apos; | grep rsp6
./app/src/main/assets/keys/rsp6_rsa_ao.dat
./app/src/main/assets/keys/rsp6_rsa_ua.dat
./app/src/main/assets/keys/rsp6_rsa_tt-qa.dat
./app/src/main/assets/keys/rsp6_rsa_tt.dat
./app/src/main/assets/keys/rsp6_rsa_t3.dat
./app/src/main/assets/keys/rsp6_rsa_t2.dat
&lt;/pre&gt;

&lt;p&gt;The keys are split up by ticket issuer, a 2-character code that forms the first part of the ticket ID. This ticket from earlier was issued
by Trainline, who have issuer code &lt;strong&gt;TT&lt;/strong&gt;…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/initial-barcode.png&quot; alt=&quot;barcode of the earlier ticket&quot; /&gt;&lt;/p&gt;

&lt;p&gt;…and the keys to decode this ticket are in &lt;code&gt;rsp6_rsa_&lt;b&gt;tt&lt;/b&gt;.dat&lt;/code&gt;. Nice!&lt;sup id=&quot;fnref:datfile&quot;&gt;&lt;a href=&quot;#fn:datfile&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;This is only a subset of all the keys that are being used today though, as I quickly discovered when
&lt;a href=&quot;https://lobi.to/&quot;&gt;unlobito&lt;/a&gt; gave me an &lt;a href=&quot;https://www.avantiwestcoast.co.uk/&quot;&gt;Avanti West Coast&lt;/a&gt; ticket to decode.
The copy of the app I have is only from 2016 and a bunch more TOCs have started issuing mobile tickets since then!&lt;/p&gt;

&lt;h2 id=&quot;ttkmobile&quot;&gt;ttkMobile&lt;/h2&gt;

&lt;p&gt;Around when I was figuring all of this out &lt;a href=&quot;https://twitter.com/puckipedia&quot;&gt;puck&lt;/a&gt; pointed me towards the website for
&lt;a href=&quot;http://info.theticketkeeper.com/&quot;&gt;The Ticket Keeper&lt;/a&gt;, another firm who makes ticket validation and issuance tooling.
They have an iOS app for ticket inspectors called &lt;strong&gt;ttkMobile&lt;/strong&gt;, which you can just
&lt;a href=&quot;https://apps.apple.com/gb/app/ttkmobile/id921166994&quot;&gt;download straight off the App Store&lt;/a&gt; and use to start validating tickets at home!&lt;sup id=&quot;fnref:ttk&quot;&gt;&lt;a href=&quot;#fn:ttk&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rsp6/ttkmobile.jpg&quot; alt=&quot;screenshot of ttkMobile&quot; /&gt;&lt;/p&gt;

&lt;details class=&quot;other-projects-details&quot;&gt;
&lt;summary class=&quot;other-projects-summary&quot;&gt;Important note if you actually intend to use this app (click to expand)&lt;/summary&gt;
&lt;p&gt;Be warned!&lt;/p&gt;

&lt;p&gt;If you install this app, you can’t ever uninstall it and expect it to work after a reinstall.
On first load it registers your device UUID with some server and generates a random password that it stores in local storage.
Uninstalling the app removes the password, but your device UUID doesn’t change, so next time you reinstall, it won’t be able
to authenticate and it’ll be useless (since it needs to grab keys and stuff to work).&lt;/p&gt;

&lt;p&gt;(“But wait,” I hear you cry, “isn’t getting a persistent device ID exactly what Apple don’t want you to do?” And you’d be right! Technically, I believe
the device UUID actually &lt;i&gt;does&lt;/i&gt; change between installs, but they store a copy in the device keychain, which doesn’t get wiped
when you remove the app. This is stupid, and almost certainly a contravention of App Store policy.)&lt;/p&gt;
&lt;/details&gt;

&lt;p&gt;I don’t have an iPhone, but some of my friends do. unlobito and another friend,
Eva (“&lt;a href=&quot;https://muffinti.me/&quot;&gt;thejsa&lt;/a&gt;”), had a poke around and managed to get me a DRM-free&lt;sup id=&quot;fnref:nodrm&quot;&gt;&lt;a href=&quot;#fn:nodrm&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;10&lt;/a&gt;&lt;/sup&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/.ipa&quot;&gt;&lt;code&gt;.ipa&lt;/code&gt;&lt;/a&gt;
containing the app, which I could unwrap and decompile with the help of &lt;a href=&quot;https://ghidra-sre.org/&quot;&gt;Ghidra&lt;/a&gt;.
This let me figure out some of the pieces of the ticket that the Masabi app didn’t look at.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://muffinti.me/&quot;&gt;thejsa&lt;/a&gt; also spent some time running the app through a proxy in order to find out how it
communicates with the server&lt;sup id=&quot;fnref:thejsa&quot;&gt;&lt;a href=&quot;#fn:thejsa&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;, and it turns out there’s just an endpoint where you can get all of the public keys:&lt;/p&gt;

&lt;pre&gt;
$ curl &apos;https://device.theticketkeeper.com/download_keys?device_name=abc&apos; | jq .
{
  &quot;return_code&quot;: &quot;ok&quot;,
  &quot;message&quot;: null,
  &quot;keys&quot;: {
    &quot;AA&quot;: [
      {
        &quot;valid_from&quot;: &quot;20000101000000&quot;,
        &quot;valid_until&quot;: &quot;29991231000000&quot;,
        &quot;public_exponent_hex&quot;: &quot;10001&quot;,
        &quot;modulus_hex&quot;: &quot;9140AA61F7D9A2E943C0510BACA5FA9CA7D12D78E301A36D640F2D28D8C0AA4D6A7102555CECF138E467730B797509EC1AB5BBA77CA6384BC8F483F609B121E75AE42660EDFE15EF91ADD4DA68C355F830FAAC6FFB25FBCFE1E61C7AF37C4AE8C85E264C151BD9C9AA4DE41D2756A9E260C0CC89AE2ADDD19E452A675E88DA47&quot;,
        &quot;public_key_x509&quot;: null,
        &quot;test_only&quot;: &quot;N&quot;,
        &quot;updated&quot;: &quot;20200313175331&quot;
      },
[etc]
&lt;/pre&gt;

&lt;p&gt;As I mentioned earlier, this is crucial information to be able to decode tickets at all,
so thanks go to The Ticket Keeper developers for making it available so easily!&lt;/p&gt;

&lt;h3 id=&quot;brief-aside-on-freedom-of-information&quot;&gt;Brief aside on freedom of information&lt;/h3&gt;

&lt;p&gt;I don’t know whether people in the industry (e.g. the Rail Delivery Group)
will be upset with me publishing this information or not.
I hope they won’t be: I really think the public keys should be made available to the public, along with the official specifications for decoding.
The tickets are signed, so it’s not as if there’s any practical danger — people can’t use this to start forging tickets en masse, for example —
and there are lots of potential innovative uses for this data. Imagine for example a journey logger that used ticket scans to track where you’d
been automatically, or an expenses system that used the price information encoded in the ticket to automatically log expense requests!&lt;/p&gt;

&lt;p&gt;The railways might be run by a consortium of private companies, but they are in effect a public service owned and controlled
by the Government&lt;sup id=&quot;fnref:erma&quot;&gt;&lt;a href=&quot;#fn:erma&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;12&lt;/a&gt;&lt;/sup&gt; (as of Jan 2023), so they really should be subject to the same Freedom of Information Act provisions as other public bodies.&lt;/p&gt;

&lt;p&gt;Some people in the industry already have the right idea; in conversation with one of the Ticket Keeper developers over email,
I was made aware that the ttkMobile app being public along with some of this data is actually an intentional choice, which is really nice to see!&lt;/p&gt;

&lt;h3 id=&quot;bonus-etvd-logs&quot;&gt;Bonus: eTVD logs&lt;/h3&gt;

&lt;p&gt;The website also tells you how they have an electronic Ticket Validation Database (&lt;a href=&quot;http://info.theticketkeeper.com/services/etvd/&quot;&gt;eTVD&lt;/a&gt;)
that has a copy of all ticket scans at gatelines and by people using their app. This is the anti-fraud thing I mentioned at the very start; this sort
of data is presumably very useful to revenue protection staff trying to figure out systematic fare evasion, like short-faring&lt;sup id=&quot;fnref:shortfare&quot;&gt;&lt;a href=&quot;#fn:shortfare&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;13&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;What it doesn’t tell you, though, is that the app will also give you this information unauthenticated, with nothing more than a ticket’s ID (!).&lt;/p&gt;

&lt;div class=&quot;nowplaying&quot;&gt;
This was reported to the developers as a possible security issue / data leak on 2023-01-19. They confirmed it was intended behaviour, but
agreed that it would probably be a good idea to restrict it; I&apos;m told this will happen soon.
&lt;/div&gt;

&lt;p&gt;This information can be quite disturbingly detailed, even pinpointing the exact username of the inspector who scanned you,
where you were scanned, on what exact train service you were scanned, whether it succeeded, and a bunch more stuff.
Helpfully it’ll also sometimes give you the entire barcode data, and what the ticket server thinks it decodes as, too!&lt;/p&gt;

&lt;pre&gt;
# getting scan history information for ticket CBCZSCDPVFF
# (this is massively cut down; there are more fields in reality)
$ curl &apos;https://device.theticketkeeper.com/get_ticket_details?device_name=abc&amp;amp;utn=CBCZSCDPVFF&apos; | jq &apos;.[&quot;ticket_detail&quot;][&quot;scans&quot;]&apos;
[
  {
    &quot;event_time_iso&quot;: &quot;2022-06-10T18:30:47&quot;,
    &quot;created&quot;: &quot;2022-06-10T18:30:48&quot;,
    &quot;device_type&quot;: &quot;ttkMobile&quot;,
    &quot;device_id&quot;: 1001,
    &quot;device_name&quot;: &quot;f95396f5-da22-47f9-8e85-dff4b2294a5d&quot;,
    &quot;device_alias&quot;: &quot;2021-TK10212&quot;,
    &quot;username&quot;: &quot;JLazlo01&quot;,
    &quot;action_name&quot;: &quot;Accepted&quot;,
    &quot;rsp_action_code&quot;: 4001,
    &quot;event_trigger&quot;: &quot;scan&quot;,
    &quot;scan_mode&quot;: &quot;clip&quot;,
    &quot;scan_nlc&quot;: &quot;2728&quot;,
    &quot;validation_result&quot;: &quot;warning&quot;,
    &quot;message_displayed&quot;: &quot;16-25 Railcard&quot;,
    &quot;gate_id&quot;: &quot;OPN-3002i[021502]&quot;,
    &quot;latitude&quot;: 51.7824963,
    &quot;longitude&quot;: -0.2141781,
    &quot;device_scan_id&quot;: 74402,
    &quot;train_uid&quot;: &quot;L77572&quot;,
    &quot;departure_date&quot;: &quot;2022-06-10&quot;,
    &quot;barcode&quot;: &quot;06CZSCDPVFF00…&quot;,
    &quot;train_info&quot;: &quot;Fr1803 KGX-SKI 1D26/GR2600&quot;,
    # etc
  },
  # etc
]
&lt;/pre&gt;

&lt;p&gt;So yeah, your ticket barcode — or its ID, which is often written below the code in plain text —
might let someone access a surprising amount of detailed tracking information as to where you are and what trains you’re taking!
(Rather like
&lt;a href=&quot;https://mango.pdf.zone/finding-former-australian-prime-minister-tony-abbotts-passport-number-on-instagram&quot;&gt;the booking reference they send you when you book a flight&lt;/a&gt;.)&lt;/p&gt;

&lt;h2 id=&quot;trying-this-out-for-yourself&quot;&gt;Trying this out for yourself&lt;/h2&gt;

&lt;p&gt;With the decompiled Masabi app and the ttkMobile app together, it wasn’t too hard to work out a vague idea of what the ticket format
was like. I’ve put together &lt;a href=&quot;https://git.eta.st/eta/rsp6-decoder&quot;&gt;a small repository&lt;/a&gt; with a Rust tool to decode a ticket,
as well as a small spec with my best guess on what all the fields mean.&lt;/p&gt;

&lt;p&gt;There’s also a &lt;a href=&quot;https://eta.st/tickets/&quot;&gt;funky web tool&lt;/a&gt; I threw together in an evening or so that’ll let you point your phone at
a barcode (or upload a screenshot of one) and give you a relatively nice readout of what data’s inside.
&lt;a href=&quot;https://eta.st/tickets/&quot;&gt;Give it a try!&lt;/a&gt; (If you need a barcode, feel free to scroll up and use the one from this post!)&lt;/p&gt;

&lt;video controls=&quot;&quot; style=&quot;max-width: 275px;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/img/rsp6/demo.mp4&quot; type=&quot;video/mp4&quot; /&gt;

    Sorry, your browser doesn&apos;t support embedded videos.
&lt;/video&gt;

&lt;p&gt;Do feel free to &lt;a href=&quot;/#contact&quot;&gt;get in touch&lt;/a&gt; if you find anything interesting or need help understanding something about the format!&lt;/p&gt;

&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements&lt;/h2&gt;

&lt;p&gt;Thanks to &lt;a href=&quot;https://lobi.to/&quot;&gt;unlobito&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/puckipedia&quot;&gt;puck&lt;/a&gt;, and &lt;a href=&quot;https://muffinti.me/&quot;&gt;thejsa&lt;/a&gt; (and assorted others
in various chatrooms) for their help with all of this; go check those people out, too!&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h3&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:aptis&quot;&gt;
      &lt;p&gt;I still have fond memories of getting these as a child for local journeys to London Waterloo! &lt;a href=&quot;#fnref:aptis&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:itso&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://lobi.to/talks/papertickets/&quot;&gt;This talk&lt;/a&gt; is a decent overview of the mess that is ITSO, if you’re interested. &lt;a href=&quot;#fnref:itso&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:binwalk&quot;&gt;
      &lt;p&gt;I did try with more than just two tickets, but that’d make for an unwieldy image. &lt;a href=&quot;#fnref:binwalk&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:avd&quot;&gt;
      &lt;p&gt;A VM provides a &lt;em&gt;decent&lt;/em&gt; level of protection against running untrusted code, but it’s not the end-all and be-all;
    I’d imagine the Android emulator isn’t exactly hardened against people trying to break out, so this probably isn’t a
    bulletproof strategy for running shady apps in the general case. &lt;a href=&quot;#fnref:avd&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:notoc&quot;&gt;
      &lt;p&gt;It turns out that the app actually downloads a lot of the keys and data necessary to decode tickets using this login,
      so even if I could somehow glitch it into starting the main ticket scanner, it wouldn’t have worked. &lt;a href=&quot;#fnref:notoc&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:jadx&quot;&gt;
      &lt;p&gt;jadx has made things slightly easier for us by renaming some identifiers (“deobfuscation”): adding some numbers and adding
     prefixes like “C” for “class” that help us distinguish amongst the multitude of things called “a” or “b”. &lt;a href=&quot;#fnref:jadx&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:osource&quot;&gt;
      &lt;p&gt;It’s impossible to know without the real source what all the classes were actually called, but I can give them names that
        make sense to &lt;em&gt;me&lt;/em&gt;, and that’s all that matters really. puck actually did manage to find an app that used some classes from
        the Masabi SDK that was not obfuscated later on, which let me compare and see how accurate my invented names were against
        the real ones! &lt;a href=&quot;#fnref:osource&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:datfile&quot;&gt;
      &lt;p&gt;The format of these .dat files is a bit weird and nonstandard, and you have to reverse the RSA decryption code to figure out
        what it is. Or, you can be lazy like me, and just paste the decompiled code into a new Java project and treat it as a black box. &lt;a href=&quot;#fnref:datfile&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ttk&quot;&gt;
      &lt;p&gt;It does expect to report back completed ticket scans by default, so you have to futz around and listen to it complain a bit before it’ll work.
    Also, the “Ticket Issuing” button doesn’t work, if you were wondering; you need a login for that. &lt;a href=&quot;#fnref:ttk&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:nodrm&quot;&gt;
      &lt;p&gt;As I understand it, you usually need a jailbroken phone to do this. Otherwise, you could just copy paid apps between phones and
      share them with people who hadn’t paid for them. &lt;a href=&quot;#fnref:nodrm&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:thejsa&quot;&gt;
      &lt;p&gt;This is also how she discovered the reason why the app doesn’t work after being uninstalled. She even went so far as writing
       a jailbreak tweak to change the device ID to make it work again, which is pretty cool! &lt;a href=&quot;#fnref:thejsa&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:erma&quot;&gt;
      &lt;p&gt;Yes, our railway looks privatised on the surface, but COVID actually resulted in so-called
     &lt;a href=&quot;https://www.gov.uk/government/speeches/rail-update-emergency-recovery-measures-agreements&quot;&gt;Emergency Recovery Measures Agreements&lt;/a&gt;
     that forced the TOCs into effective state ownership and control (they’re now in effect contractors who run the service, rather than
     taking revenue risk). This means that the Government are actually responsible for things like the
     current union dispute over jobs and working conditions, despite what some ministers would have you think! &lt;a href=&quot;#fnref:erma&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:shortfare&quot;&gt;
      &lt;p&gt;This refers to the practice of buying a ticket that doesn’t actually cover your whole journey (e.g. skipping some stops at the start or
          end), and gambling that you’ll only get inspected between the stations where it’s actually valid. &lt;a href=&quot;#fnref:shortfare&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Tue, 31 Jan 2023 00:00:00 +0000</pubDate>
        <link>https://eta.st/2023/01/31/rail-tickets.html</link>
        <guid isPermaLink="true">https://eta.st/2023/01/31/rail-tickets.html</guid>
        
        
      </item>
    
      <item>
        <title>Pessimism is a bad defense mechanism</title>
        <description>&lt;div class=&quot;nowplaying&quot; style=&quot;background: #ffdada;&quot;&gt;
&lt;b&gt;Warning!&lt;/b&gt; This post is a mental health / feels post from a long time ago (in fact, &lt;a href=&quot;/nomenclature&quot;&gt;one gender ago&lt;/a&gt;).
It is kept up in case some people find it useful, but I don&apos;t necessarily endorse the content in here nowadays.
&lt;/div&gt;

&lt;p&gt;Why do some people have poor mental health? It’s hard to know – there’s an entire profession centred on answering this question! – but I feel like a part
of it has to do with doing things that sabotage oneself, without necessarily being aware of the fact that this is happening. Of course, the next question
is inevitably why people even begin to do such things; surely if sabotaging yourself leads to bad mental health, people would just not do it, and therefore
lead happier lives.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;When you’ve had a history of things not going too well, it can often be difficult to accept that a situation is actually pretty great – because doing so
involves accepting the idea that things might turn out to be not so great later on, which is painful, so doing so puts you in a vulnerable position.
Some might argue this is silly, and you should just accept the risk and enjoy the benefits of the situation – but this all depends on how things have gone
down in the past. If you’ve opened yourself up and had a bad time, you’re probably going to be inclined to avoid showing vulnerability in future.&lt;/p&gt;

&lt;p&gt;Taking the avoidance of vulnerability to its extremes often involves a heavy degree of pessimism. If you judge everything to be terrible even when it isn’t,
you can conveniently avoid feeling bad about stuff going wrong; you already mentally prepared yourself for this outcome anyway, so there’s no loss. And if
it goes well, that’s a bonus! The events in the past that hurt you because you opened yourself up and showed vulnerability can’t any more; there’s a far
smaller gap between your expectation and the reality if the reality does turn out to be bad.&lt;/p&gt;

&lt;p&gt;As the title of this post says, this is a defence mechanism. I can definitely recall times when something hurt me, often to such an extent that I felt a
need to burst out in tears (usually in some inopportune place, like on public transport) – but I didn’t; I clenched my teeth, and muttered something
under my breath about the person who had hurt me being terrible all along, or the situation being doomed from the start, or some other overly-negative
view that would manage to make the pain hurt less, somehow. Or, if it couldn’t do that, it would at least keep me from visibly crying in front of all
the other commuters on the train.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Side note: what even happens if you cry on a train, just in front of people? Do people look at you like some sort of insane person and just let you get
on with it in peace? Do complete strangers ask you if you’re alright? Maybe it isn’t actually too terrible – or maybe it’s really rude. Will I ever
find out?&lt;/p&gt;

&lt;p&gt;For one reason or another&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, I grew up with the idea that showing weakness is bad. To this day, it’s something that I find very difficult to do, and I
often criticize myself for having done it. We all need to show weakness eventually, though; sometimes things just get way too much to bear, and it all
comes flooding out. Unfortunately, this can perpetuate a vicious cycle: the only occasions where you open yourself up are hysterical disasters where
you end up &lt;a href=&quot;https://www.urbandictionary.com/define.php?term=Paragraphing&quot;&gt;paragraphing&lt;/a&gt; one of your friends about all of your problems at once, or having
a lengthy phone call where you’re the only one talking for a solid hour. This seems like a really imposing and terrible thing to do to people, so you
conclude that showing weakness is bad, so you don’t do it as much… meaning the outburst is even worse next time, because you can’t hold the feelings
back forever.&lt;/p&gt;

&lt;p&gt;But when you’re trapped in the above cycle, it’s incredibly tempting to just push the inevitable feelings outburst forward by a few days/weeks/months with the
liberal application of some pessimism.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Apart from this train-crying-reduction purpose, the other thing pessimism is very good at is making the actual causes of things far more vague and
nebulous. You can avoid thinking about why something went wrong if you chalk the whole thing up to it just being a bad situation; this is particularly
useful when you’re actually the one doing things to make a situation worse. Here’s an example&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;: maybe you’re sad about getting out of contact
with a friend you haven’t seen in a while. If you take a pessimistic angle, you can usefully assume this is because people just naturally drift apart given
space, which is unfortunate but makes the whole thing seem rather impersonal: it’s just a thing which is happening to you which you have no control over.&lt;/p&gt;

&lt;p&gt;This isn’t how it works, though – in the above situation, the reason why you don’t talk to the friend could actually be because &lt;em&gt;you&lt;/em&gt; don’t make much
of an effort any more with them, because of the pessimism (“we’re going to drift apart anyway”). Another example: maybe you’re upset about being fired,
apply for a new job, and don’t get it. In a lot of situations, you do indeed have no control over whether or not the company will hire you – but
if you thought you’d never get a job again after getting fired and didn’t bother to put in the effort for the application as a result, you’re actually
sabotaging yourself here.&lt;/p&gt;

&lt;p&gt;This ability of pessimism to confuse situations can start to ruin your life if you’re not careful. If everything is terrible, what’s the point in doing
anything? Opportunities to make things better pass you by without you doing anything (because you refuse to be brave enough to actually take things
into your own hands and maybe fail), and can then serve as further examples of why everything is terrible when you start feeling bad about what you’ve
missed out on, reinforcing the cycle. Because it’s not always obvious that &lt;em&gt;you&lt;/em&gt; were the one that made the situation bad, you’re left feeling like
you can’t do anything about it. Even when it’s clear that you made a mistake, you might view yourself as incontrovertibly bad – sure, it was your
fault, but you’re a terrible person, so you’d never have made the right decision anyway (!).&lt;/p&gt;

&lt;p&gt;Thinking like this leads to viewing life as this third-person experience where stuff just happens to you and you have to just sit there and take it –
that you have no control over events. (Some people call this &lt;a href=&quot;https://en.wikipedia.org/wiki/Learned_helplessness&quot;&gt;“learned helplessness”&lt;/a&gt;.)
And that’s, to put it mildly, not good; you can get very depressed&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; this way!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I would have liked to finish this blog post with some inspiring call to action about how to get out of this cycle and make all the things better in 3
simple steps. Unfortunately, I’m not actually qualified enough to do that.&lt;/p&gt;

&lt;p&gt;Knowing what a problem is is often half the battle, however, and it’s definitely possible to use this knowledge for good. The research on learned
helplessness actually suggests that it’s not helplessness people learn, it’s actually help&lt;em&gt;ful&lt;/em&gt;ness – the default opinion of the brain is (allegedly)
that you can’t do things to change your situation, and you have to &lt;em&gt;teach&lt;/em&gt; it that you do actually have some power after all.&lt;/p&gt;

&lt;p&gt;So, do some things, and pay attention to their effect. I bet at least in some cases, you have more ability to change things than you think.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Is going to a academically selective, all-male private school a reason? No, that couldn’t &lt;em&gt;possibly&lt;/em&gt; be related… &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Frequent readers of the blog will know that the “examples” are often sanitized versions of things that have recently happened. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;I think it’s actually a lot &lt;em&gt;more&lt;/em&gt; dangerous when you hold this belief underneath, but otherwise have what seems to be a relatively okay life.
  Every so often, something will happen that makes you sad for a while, and then you’ll get over it and act like nothing happened without realizing
  what’s going on – thereby ensuring that you’ll continue to become occasionally sad in perpetuity! &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Wed, 30 Jun 2021 00:00:00 +0000</pubDate>
        <link>https://eta.st/2021/06/30/self-sabotage.html</link>
        <guid isPermaLink="true">https://eta.st/2021/06/30/self-sabotage.html</guid>
        
        
      </item>
    
      <item>
        <title>Sending audio to LKV373 HDMI extenders</title>
        <description>&lt;p&gt;My &lt;a href=&quot;https://benjojo.co.uk/&quot;&gt;current flatmate&lt;/a&gt; has a bunch of these &lt;a href=&quot;https://blog.benjojo.co.uk/post/cheap-hdmi-capture-for-linux&quot;&gt;HDMI extender devices&lt;/a&gt;
(also apparently known as “LKV373” / “Lenkeng extenders” / “LK-3080-ACL”). They’re pretty nifty: HDMI goes in one end, they emit UDP over ethernet,
and then HDMI comes out on the receiver end with surprisingly minimal latency (around 100ms).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/extender-3.jpg&quot; alt=&quot;photo of an extender&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;image: the receiver end of a transmitter-receiver pair, taken by said flatmate&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A couple months ago we decided it’d be quite nifty to be able to play arbitrary audio out of the devices without having to plug a transmitter into someone’s
laptop – so that we could have BBC Radio 4 or something constantly playing whenever the sound system is turned on.&lt;/p&gt;

&lt;p&gt;My flatmate’s blog post, &lt;a href=&quot;https://blog.benjojo.co.uk/post/cheap-hdmi-capture-for-linux&quot;&gt;“Ludicrously cheap HDMI capture for Linux”&lt;/a&gt;, outlined a way to
&lt;em&gt;decode&lt;/em&gt; the signal the transmitters sent (using his own tool, 
&lt;a href=&quot;https://github.com/benjojo/de-ip-hdmi&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt;&lt;/a&gt;). But could we do the opposite,
and &lt;em&gt;encode&lt;/em&gt; signals the receivers would accept?&lt;/p&gt;

&lt;h2 id=&quot;testing-without-the-actual-devices&quot;&gt;Testing without the actual devices&lt;/h2&gt;

&lt;p&gt;I figured I’d start by looking at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt;’s source code to see how it
decoded the packets and then use it as a way to test the output of my
custom sender until it was able to decode what I was sending. It’s pretty
unlikely that I’d just be able to send audio without also sending a picture,
so I tried to figure out the picture format first.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/sharked-sequences.png&quot; alt=&quot;video picture packet format&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The video picture packets have a fairly simple format: two big-endian, 16-bit
integers (frame number, and sequence number), and then data, up to 1020 bytes
(making UDP packets with a 1024-byte payload, total).&lt;/p&gt;

&lt;p&gt;Extracting the output from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt; with a transmitter running
confirms that the data is just a JPEG:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ file test.mjpeg 
test.mjpeg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 1920x1080, components 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The frame number counts
monotonically up from 0, incrementing with each new frame. Since you’re not
going to fit a whole JPEG frame in 1020 bytes, the sequence number provides a way of
splitting the frame up into multiple chunks; you increment it for each chunk
you send within a frame, while keeping the frame number constant.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt; &lt;a href=&quot;https://github.com/benjojo/de-ip-hdmi/blob/0bdfab96c7fbe8819977846e402c56a0b7660b47/main.go#L117&quot;&gt;source code to decode&lt;/a&gt;
looks like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span class=&quot;c&quot;&gt;// taken from the de-ip-hdmi source code, in Go&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;FrameNumber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CurrentChunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApplicationData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;buf2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApplicationData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FrameNumber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buf2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CurrentChunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// does stuff with ApplicationData[4:]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Seems simple enough. I wrote a small Rust &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; that could chunk up a
slice of bytes into 1024-byte vectors that fit this format:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FrameChunker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// the data to send&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;frame_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// frame number&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;seq_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// sequence number&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FrameChunker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// constructor emitted for brevity&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// get a new 1024 (or less)-byte chunk to send&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;next_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;remaining&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.inner&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1020&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1020&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.inner&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// this is from the `byteorder` crate&apos;s `WriteBytesExt` trait&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.write_u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.frame_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.write_u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.seq_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;remaining&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.inner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;remaining&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.seq_no&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// returns true if we&apos;re done transmitting the current frame;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// used to know when to send the next one&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;is_empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.inner&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I then made a little 1920×1080 all white JPEG in GIMP, wrote it to a file, and
wrote some code to send out UDP multicast packets in the format the receiver
(and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt;) would expect:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// The magic IPv4 address for the multicast group the transmitter would send&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// to.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MAGIC_ADDRESS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Ipv4Addr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Ipv4Addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;226&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// load the test image data in at compile-time&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;include_bytes!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;../white.mjpeg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// make a UDP socket for sending&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// note: this requires creating an interface with this address&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mjpeg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;UdpSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;192.168.168.55:2068&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// destination address:port for video picture data&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mjpeg_dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SocketAddr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;226.2.2.2:2068&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// join the multicast group&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mjpeg&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.join_multicast_v4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MAGIC_ADDRESS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;192.168.168.55&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// current frame number&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame_no&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chonker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FrameChunker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chonker&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chonker&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.next_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;mjpeg&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.send_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mjpeg_dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;frame_no&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep_ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I added a suitable network interface for the sender to use (the actual flat networking
setup uses &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_LAN&quot;&gt;VLAN&lt;/a&gt;s for the HDMI
traffic, but I found out that if you just want to test then adding a loopback interface also
works):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo ip link add link enp5s0f3u1u1 name showtime0 type vlan id 1024
$ sudo ip link set dev showtime0 address 00:0b:78:00:60:01
$ sudo ip addr add 192.168.168.55/32 dev showtime0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With much anticipation I then ran the code for the first time, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt;
running in parallel – and got…nothing.&lt;/p&gt;

&lt;h3 id=&quot;adventures-in-bitshifting&quot;&gt;Adventures in bitshifting&lt;/h3&gt;

&lt;p&gt;Turns out I forgot something else in the packet format: a way to signal the
last chunk in a series of chunks for a frame. Going back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CurrentChunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;65534&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// Flush the frame to output&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// [code snipped]&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;CurrentPacket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;CurrentPacket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;CurrentPacket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FrameID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;CurrentPacket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LastChunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;    &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;What the heck is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint16(^(CurrentChunk &amp;gt;&amp;gt; 15)) == 65534&lt;/code&gt; doing? Let’s break
that down:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;it shifts &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CurrentChunk&lt;/code&gt; to the right by 15 bits, discarding all bits apart
from the most significant bit (MSB), which becomes the new least significant
bit (LSB).&lt;/li&gt;
  &lt;li&gt;unary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^&lt;/code&gt; (caret) is the Go “logical NOT” operator, so we flip all of the
bits in the result above&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;65534&lt;/code&gt; is a 16-bit integer with every bit set except the LSB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/bitshift.png&quot; alt=&quot;bit shift diagrammatic explanation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So… this is a &lt;a href=&quot;https://mobile.twitter.com/Benjojo12/status/1400616243300872193&quot;&gt;very long-winded&lt;/a&gt;
way of checking whether the MSB of
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CurrentChunk&lt;/code&gt; is equal to 1 (in Rust, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(number &amp;amp; 0b1000_0000_0000_0000) &amp;gt; 0&lt;/code&gt;).
Accordingly, we need to set the MSB to 1 to denote the end of a frame, so
let’s do that in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FrameChunker&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.write_u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.frame_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.write_u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.seq_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// it&apos;s big-endian, so ret[2] contains the most significant&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 8 bits of the 16-bit integer&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0b1000_0000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;partial-victory&quot;&gt;Partial victory!&lt;/h3&gt;

&lt;p&gt;With this change applied &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt; would decode our image! Yay!&lt;/p&gt;

&lt;video controls=&quot;&quot; style=&quot;max-width: 100%&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/img/showtime/deip.mp4&quot; type=&quot;video/mp4&quot; /&gt;

    Sorry, your browser doesn&apos;t support embedded videos.
&lt;/video&gt;

&lt;p&gt;However, the real transmitter wasn’t having any of it; it refused to display a
picture (behaving as if it couldn’t see any transmitters; it says “Searching
TX” when this happens).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/searching-tx.png&quot; alt=&quot;&amp;quot;Searching TX&amp;quot; display&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Hmm.&lt;/p&gt;

&lt;h2 id=&quot;lets-do-the-wireshark-again&quot;&gt;Let’s do the Wireshark again&lt;/h2&gt;

&lt;p&gt;I connected a real transmitter and dumped some of the packets it sends using
&lt;a href=&quot;https://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt;. Turns out there’s more stuff you need
to do.&lt;/p&gt;

&lt;h3 id=&quot;mysterious-packets-on-port-2067&quot;&gt;Mysterious packets on port 2067&lt;/h3&gt;

&lt;p&gt;Here’s a screenshot of some of the packets the real transmitter sends.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/tasty-packet.png&quot; alt=&quot;wireshark screenshot&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the (source port) 2065 → (destination port) 2066 packets are audio data.
We’ll get to those later!&lt;/li&gt;
  &lt;li&gt;the 2068 → 2068 packets are video picture data&lt;/li&gt;
  &lt;li&gt;…but what are these 2067 → 2067 packets?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/2067-1.png&quot; alt=&quot;2067 packet 1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Hmm, it’s all zeroes. What about the next one?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/2067-2.png&quot; alt=&quot;2067 packet 2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Aha, a byte went from 00 to 01!&lt;/p&gt;

&lt;p&gt;Turns out this pattern continues; before each picture frame is sent, an
additional packet is sent on port 2067 of length 20, with the 5th and 6th
bytes being a 16-bit big-endian integer, which is the same as the frame
number of the frame we just sent.&lt;/p&gt;

&lt;p&gt;I’m guessing this is a sort of &lt;a href=&quot;https://en.wikipedia.org/wiki/Vertical_blanking_interval&quot;&gt;“vsync” / “vblank” packet&lt;/a&gt;,
used to tell the
receiver to get ready for a new frame.
We should probably replicate it, so I wrote some code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;make_vsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.write_u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// code to send this to port 2066 before sending frame packets elided&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Alas, this was not enough. The receiver continued to say “Searching TX”.&lt;/p&gt;

&lt;h2 id=&quot;heartbeat-packets&quot;&gt;Heartbeat packets&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/heartbeat-list.png&quot; alt=&quot;heartbeat packet list&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are also some weird 512-byte payloads being sent on port 48689. These
are easy to miss because they’re only sent once every second (and so are
comparatively infrequent relative to the firehose of data the transmitters
send for pictures and video). However, you can notice them by looking at
the start of a packet capture since they get sent even when no picture
data is being sent.&lt;/p&gt;

&lt;p&gt;Let’s look at a dump of some of these packet payloads over time (using
“[right click] Follow → UDP Stream” in Wireshark):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/heartbeat.png&quot; alt=&quot;heartbeat packet diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After squinting at it for a while, you notice a few things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The first three packets are identical, apart from a small 2-byte region that
carries on going up with each new packet (marked in red), and a sequence
number incremented with each new packet (marked in pink).&lt;/li&gt;
  &lt;li&gt;Between the third and fourth packets, a large chunk of data (marked in blue)
changes to something else (marked in green), and then stays that way.&lt;/li&gt;
  &lt;li&gt;This change is also correlated with the video and audio starting.&lt;/li&gt;
  &lt;li&gt;After the change happens the packets stay identical apart from the counter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This must be some kind of heartbeat packet sent by the transmitter to tell
the receiver that it’s there. Let’s try and figure out the various bits
in the payload in more detail.&lt;/p&gt;

&lt;h3 id=&quot;incrementing-counter&quot;&gt;Incrementing counter&lt;/h3&gt;

&lt;p&gt;How is this counter being generated? Well, let’s assume that
it’s a 16-bit big-endian integer, since
everything else in this blog post has been that, and interpret the values
accordingly.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0458&lt;/code&gt; is 1112, sent at time &lt;em&gt;t&lt;/em&gt; = 0.0&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0842&lt;/code&gt; is 2114 (delta = 1002), sent at &lt;em&gt;t&lt;/em&gt; = 1.001&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0c2b&lt;/code&gt; is 3115 (delta = 1001), sent at &lt;em&gt;t&lt;/em&gt; = 2.002&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0ee5&lt;/code&gt; is 3813 (delta = 698), sent at &lt;em&gt;t&lt;/em&gt; = 2.700&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Hmm…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I think it’s pretty reasonable to assume this counter just tracks time since
the transmitter was powered on. &lt;em&gt;Why&lt;/em&gt; the receiver needs that information
isn’t exactly clear though (maybe for some synchronization purposes?).&lt;/p&gt;

&lt;p&gt;Anyway, we should probably do the same as the transmitter when sending our
own packets here so we’ll add a timer to the code.&lt;/p&gt;

&lt;h3 id=&quot;other-gubbins&quot;&gt;Other gubbins&lt;/h3&gt;

&lt;p&gt;We have two sets of weird data:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;00 03 07 80 04 38 02 57 07 80 04 38 00 78 03&lt;/code&gt; (when the HDMI port is active)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;00 10 00 00 00 00 00 00 00 00 00 00 00 78 00&lt;/code&gt; (when it isn’t)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;07 80&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;04 38&lt;/code&gt; are repeated multiple times. Let’s try decoding those
as 16-bit big-endian integers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0780&lt;/code&gt; is 1920&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0438&lt;/code&gt; is 1080&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That seems familiar; isn’t 1920×1080 the resolution for &lt;a href=&quot;https://en.wikipedia.org/wiki/1080p&quot;&gt;1080p&lt;/a&gt;?
This data is presumably used to initialize the HDMI port on the other end.&lt;/p&gt;

&lt;p&gt;What about when the HDMI is off? Well, it looks like most of the
fields are just zeroed out, which makes sense since it doesn’t have a resolution
or anything to send yet.
(This causes the message “Check Tx’s input signal” to appear on the receiver, which
is useful for debugging why the receiver isn’t displaying anything.)&lt;/p&gt;

&lt;p&gt;In the end, I just encoded all of this data as constants (interpreting it
as a set of 16-bit big endian integers), sending one set (the one with 1920s
in it) if we’re ready to send data, and another set (the zeroed out set)
if we aren’t.&lt;/p&gt;

&lt;h3 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/h3&gt;

&lt;p&gt;The code to make a heartbeat packet ended up looking like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;make_heartbeat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seq_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur_ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0x54&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x46&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x7a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x63&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.write_u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seq_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x03&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x03&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x03&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// woohoo, magic numbers&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1920&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1080&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;599&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1920&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1080&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;thing&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.write_u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;thing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.write_u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BigEndian&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur_ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x0a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;512&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;assert_eq!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Putting it all together and sending heartbeat packets, vsync packets, and
the actual picture data… got the receiver to initialize, but output a black rectangle
instead of the test image.&lt;/p&gt;

&lt;video controls=&quot;&quot; style=&quot;max-width: 100%&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/img/showtime/blackscreen.mp4&quot; type=&quot;video/mp4&quot; /&gt;

    Sorry, your browser doesn&apos;t support embedded videos.
&lt;/video&gt;

&lt;p&gt;After trying many other things, I eventually tried sending the original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test.mjpeg&lt;/code&gt; file
I’d dumped from the receiver when first trying to get this to work, instead of my own
JPEG that I made in GIMP. That actually produced some output!&lt;/p&gt;

&lt;video controls=&quot;&quot; style=&quot;max-width: 100%&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/img/showtime/functional.mp4&quot; type=&quot;video/mp4&quot; /&gt;

    Sorry, your browser doesn&apos;t support embedded videos.
&lt;/video&gt;

&lt;p&gt;Yay!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(the test image was produced by writing some text on a laptop, making it full-screen, and connecting it to the transmitter)&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;but-what-about-audio&quot;&gt;But what about audio?&lt;/h2&gt;

&lt;p&gt;While this is (somewhat) useful, it doesn’t do what we originally wanted; what about actually sending audio?&lt;/p&gt;

&lt;h3 id=&quot;how-does-digital-audio-work-anyway&quot;&gt;How does digital audio work, anyway?&lt;/h3&gt;

&lt;p&gt;Digital audio is represented as a series of &lt;a href=&quot;https://en.wikipedia.org/wiki/Sampling_(signal_processing)&quot;&gt;&lt;em&gt;samples&lt;/em&gt;&lt;/a&gt; – individual measurements
of what the voltage of your speaker should be at a given point in time (the “signal”), taken at a given &lt;em&gt;sampling rate&lt;/em&gt; that’s high enough to provide
a decent approximation of what the speaker should be doing to reproduce your favourite tunes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/sampling.png&quot; alt=&quot;sampling visualization&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;image: sourced from the above linked Wikipedia article, CC0&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are a number of important variables involved in this process that we need to know in order to send and receive audio in a given format:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;what the sample rate was (measured in Hertz, aka “times per second”)&lt;/li&gt;
  &lt;li&gt;how the samples are stored (as floating-point numbers? as signed integers? how many bits?)&lt;/li&gt;
  &lt;li&gt;what the “maximum” and “minimum” values are (for floats, we typically use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.0&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-1.0&lt;/code&gt;; integer formats use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INT_MAX&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INT_MIN&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;what-format-do-the-extenders-use&quot;&gt;What format do the extenders use?&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt; has already figured this out for us:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;audio&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ffmpeg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ffmpeg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mjpeg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-i&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uuidpath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;s32be&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-ac&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-ar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;44100&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-i&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pipe:0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;matroska&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-codec&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;copy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pipe:1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is a &lt;a href=&quot;https://www.ffmpeg.org/&quot;&gt;ffmpeg&lt;/a&gt; command line that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt; uses to wrap the data from the extenders in a &lt;a href=&quot;https://en.wikipedia.org/wiki/Matroska&quot;&gt;Matroska container&lt;/a&gt;
(a way of combining audio and images together into one video file). We can tell a few things from this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-f s32be&lt;/code&gt;: the samples are stored as signed 32-bit big-endian integers
    &lt;ul&gt;
      &lt;li&gt;this uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i32::MIN&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i32::MAX&lt;/code&gt; as minimum/maximum (i.e. -2147483648 and 2147483647)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-ac 2&lt;/code&gt;: there are 2 audio channels
    &lt;ul&gt;
      &lt;li&gt;the samples from each channel will be &lt;a href=&quot;https://en.wikipedia.org/wiki/Interleaving_(data)&quot;&gt;interleaved&lt;/a&gt;, since we just have one big audio stream&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-ar 44100&lt;/code&gt;: the sample rate is 44100 Hz&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It looks like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;de-ip-hdmi&lt;/code&gt; just treats everything after the 16th byte as a set of samples in the above format:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span class=&quot;c&quot;&gt;// Maybe there is some audio data on port 2066?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pkt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x08&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pkt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;37&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x12&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output_mkv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;audio&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;audiodis&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ApplicationData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Going back to our Wireshark dump, the first 16 bytes of every audio packet seem to be identical (and the packets are always 1008
bytes long):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/audiohdr.png&quot; alt=&quot;audio header in wireshark&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So, to send audio data we should in theory be able to write the header and then 992 (1008 - 16) bytes worth of samples into a packet
and send it as UDP from port 2065 to port 2066.&lt;/p&gt;

&lt;p&gt;However, how frequently should we send the packets?&lt;/p&gt;

&lt;h3 id=&quot;timing-is-everything&quot;&gt;Timing is everything&lt;/h3&gt;

&lt;p&gt;We need to provide &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;44100 × 2 = 88200&lt;/code&gt; samples each second, since we have 2 audio channels at 44100 Hz sample rate. Each packet of audio
contains 996 bytes of samples at 32 bits (= 4 bytes) per sample, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;992 / 4 = 248&lt;/code&gt; samples fit in a packet.&lt;/p&gt;

&lt;p&gt;That means we need to send a packet every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;248 / 88200&lt;/code&gt; seconds, or roughly &lt;strong&gt;2.812 milliseconds&lt;/strong&gt; (to 3 decimal places) – which seems to line
up with the frequency observed in the Wireshark dump:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/showtime/audiointerval.png&quot; alt=&quot;audio intervals in wireshark&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(e.g. 2.9222 s - 2.9195 s = 2.7ms, 2.9251s - 2.9222s = 2.9ms, etc)&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I wrote up some sample code that read samples from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stdin&lt;/code&gt; (fed in using ffmpeg) and sent a packet, then slept for 2812 nanoseconds, and then repeated.
This resulted in…complete silence!&lt;/p&gt;

&lt;p&gt;That is, until we changed the device connected to the HDMI receiver unit (a hifi system with HDMI passthrough) to another device (a projector).
The projector happily played the audio we were sending… but it sounded distorted (kinda “crunchy” sounding). What’s going on?&lt;/p&gt;

&lt;h3 id=&quot;jitter-is-unacceptable&quot;&gt;Jitter is unacceptable&lt;/h3&gt;

&lt;p&gt;A while ago, I wrote some audio code for a project called &lt;a href=&quot;https://git.eta.st/eta/sqa&quot;&gt;“SQA”&lt;/a&gt;. This taught me one of the ‘cardinal rules’
of audio programming: jitter is unacceptable. There’s a great blog post called 
&lt;a href=&quot;http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing&quot;&gt;“Real-time audio programming 101: time waits for nothing”&lt;/a&gt; that
explains this in more detail but to sum up: when supplying audio samples for playback, you have a fixed amount of time to fill a buffer of a certain size,
and you must &lt;em&gt;always&lt;/em&gt; fill the buffer in the time given to you (or less).&lt;/p&gt;

&lt;p&gt;If you leave the buffer half-filled the audio will “glitch”: it’ll have pops and clicks in it and/or sound distorted, because the new samples you’ve
only half written will stop halfway through the playback buffer and jump suddenly to a bunch of zeroes instead (or some other garbage data). This is called
a buffer under/overrun, or an &lt;em&gt;xrun&lt;/em&gt; for short.&lt;/p&gt;

&lt;p&gt;So, how do you make sure you never glitch? Well, doing anything that could block or take lots of time to complete (reading from a file, waiting on a mutex,
etc.) in the thread that handles audio is strictly forbidden; you’ve basically got to have something else put the audio samples in a &lt;a href=&quot;https://en.wikipedia.org/wiki/Circular_buffer&quot;&gt;ringbuffer&lt;/a&gt;
for you to copy out
in the audio thread.&lt;/p&gt;

&lt;h2 id=&quot;final-audio-thread-code&quot;&gt;Final audio thread code&lt;/h2&gt;

&lt;p&gt;Armed with the above knowledge, I completely rewrote the audio thread – and it actually works!&lt;/p&gt;

&lt;p&gt;It now uses the excellent &lt;a href=&quot;https://crates.io/crates/bounded_spsc_queue&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bounded_spsc_queue&lt;/code&gt;&lt;/a&gt; ringbuffer library to ship samples from the rest of the program
and the audio thread, and &lt;a href=&quot;https://en.wikipedia.org/wiki/Busy_waiting&quot;&gt;spin-waits&lt;/a&gt; (loops) until it needs to send the next packet.
(Doing the “proper” thing and using something like &lt;a href=&quot;https://linux.die.net/man/2/nanosleep&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nanosleep(2)&lt;/code&gt;&lt;/a&gt; would be more efficient – but was unreliable
compared to just spinning in my tests.)&lt;/p&gt;

&lt;p&gt;The full audio thread code is below:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;audio_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;udp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UdpSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SocketAddr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Consumer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pkt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1008&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Pre-write the header on to the packet.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0x55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pkt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Instant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Monotonic nanosecond counter.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur_nanos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;Instant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.duration_since&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.as_micros&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 2.812 milliseconds!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INTERVAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u128&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2812&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Next time (on the counter) we need to send a packet.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INTERVAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Current buffer pointer.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Consecutive Xrun counter.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_xruns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cur_nanos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Send the packet.&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;udp&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.send_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pkt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Check if we managed to fill the buf; if not, we&apos;ve xrun&apos;d.&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1008&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;num_xruns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// Reset the pointer.&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;num_xruns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Advance the deadline so it&apos;s 2.8ms after the previous one.&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// We skip over if we&apos;re already going to miss it.&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INTERVAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Okay, now time to fill the buffer a bit.&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1008&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_xruns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// Let&apos;s just fill it up with silence instead.&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// (otherwise you get a buzzing effect that&apos;s highly irritating)&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;pkt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;smpl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.try_pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;pkt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smpl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;yield_now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;

&lt;p&gt;The code is &lt;a href=&quot;https://git.eta.st/eta/showtime&quot;&gt;here&lt;/a&gt;. Don’t expect miracles.
(If you actually plan to use this, you might want to &lt;a href=&quot;/#contact&quot;&gt;get in touch&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;There are a number of ways to feed data into it, which you choose at compile time using &lt;a href=&quot;https://doc.rust-lang.org/cargo/reference/features.html&quot;&gt;Cargo features&lt;/a&gt;.
You can either feed in raw s32be samples from
either stdin or TCP (it’ll make a TCP server and you can connect and send stuff, but not with more than one client at the same time…).
I also ended up adding a &lt;a href=&quot;https://jackaudio.org/&quot;&gt;JACK&lt;/a&gt; backend (using my own &lt;a href=&quot;https://docs.rs/sqa-jack/0.6.1/sqa_jack/&quot;&gt;sqa-jack&lt;/a&gt; library), if that’s
your thing.&lt;/p&gt;
</description>
        <pubDate>Sun, 20 Jun 2021 00:00:00 +0000</pubDate>
        <link>https://eta.st/2021/06/20/showtime.html</link>
        <guid isPermaLink="true">https://eta.st/2021/06/20/showtime.html</guid>
        
        
      </item>
    
      <item>
        <title>Why asynchronous Rust doesn&apos;t work</title>
        <description>&lt;p&gt;In 2017, I said that &lt;a href=&quot;/2017/08/04/async-rust.html&quot;&gt;“asynchronous Rust programming is a disaster and a mess”&lt;/a&gt;. In 2021
a lot more of the Rust ecosystem has become asynchronous – such that it might be appropriate to just say that Rust
programming is now a disaster and a mess. As someone who used to really love Rust, this makes me quite sad.&lt;/p&gt;

&lt;p&gt;I’ve had a think about this, and I’m going to attempt to explain how we got here. Many people have explained the problems
with asynchronous programming – the famous &lt;a href=&quot;https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/&quot;&gt;what colour is your function&lt;/a&gt;
essay, for example.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; However, I think there are a number of things &lt;em&gt;specific to the design of Rust&lt;/em&gt; that make asynchronous
Rust particularly messy, &lt;em&gt;on top of&lt;/em&gt; the problems inherent to doing any sort of asynchronous programming.&lt;/p&gt;

&lt;p&gt;In particular,
I actually think the design of Rust is almost fundamentally incompatible with a lot of asynchronous paradigms. It’s not that
the people designing async were incompetent or bad at their jobs – they actually did a surprisingly good job given the
circumstances!&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; I just don’t think it was ever going to work out cleanly – and to see why, you’re going to have to read
a somewhat long blog post!&lt;/p&gt;

&lt;h2 id=&quot;a-study-in-async&quot;&gt;A study in async&lt;/h2&gt;

&lt;p&gt;I’d like to make a simple function that does some work in the background, and lets us know when it’s done by running &lt;em&gt;another&lt;/em&gt;
function with the results of said background work.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;cd&quot;&gt;/// Does some strenuous work &quot;asynchronously&quot;, and calls `func` with the&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// result of the work when done.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;do_work_and_then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;spawn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Figuring out the meaning of life...&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep_ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// gee, this takes time to do...&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ah, that&apos;s it!&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// let&apos;s call the `func` and tell it the good news...&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There’s this idea called “&lt;a href=&quot;https://en.wikipedia.org/wiki/First-class_function&quot;&gt;first-class functions&lt;/a&gt;” which says you
can pass around functions as if they were objects. This would be great to have in Rust, right?&lt;/p&gt;

&lt;p&gt;See that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func: fn(i32)&lt;/code&gt;? &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fn(i32)&lt;/code&gt; is the type of a function that takes in one singular &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i32&lt;/code&gt; and returns nothing.
Thanks to first-class functions, I can pass a function to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do_work_and_then&lt;/code&gt; specifying what should happen next after I’m
done with my work – like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;do_work_and_then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meaning_of_life&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;oh man, I found it: {}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;meaning_of_life&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// do other stuff&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep_ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do_work_and_then&lt;/code&gt; is &lt;em&gt;asynchronous&lt;/em&gt;, it returns immediately and does its thing in the background, so the control
flow of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; isn’t disrupted. I could do some other form of work, which would be nice (but here I just wait for 2 seconds,
because there’s nothing better to do). Meanwhile, when we do figure out the meaning of life, it gets printed out. Indeed,
if you run this program, you get:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;oh man, I found it: 42
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is really exciting; we could build whole web servers and network stuff and whatever out of this! Let’s
try a more advanced example: I have a database I want to store the meaning of life in when I find it, and then I can run
a web server in the foreground that enables people to get it once I’m done (and returns some error if I’m not done yet).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Database&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Database&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.data&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Database&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;do_work_and_then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meaning_of_life&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;oh man, I found it: {}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;meaning_of_life&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meaning_of_life&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// I&apos;d read from `db` here if I really were making a web server.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// But that&apos;s beside the point, so I&apos;m not going to.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// (also `db` would have to be wrapped in an `Arc&amp;lt;Mutex&amp;lt;T&amp;gt;&amp;gt;`)&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep_ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s run this…oh.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;error[E0308]: mismatched types
  --&amp;gt; src/main.rs:27:22
   |
27 |       do_work_and_then(|meaning_of_life| {
   |  ______________________^
28 | |         println!(&quot;oh man, I found it: {}&quot;, meaning_of_life);
29 | |         db.store(meaning_of_life);
30 | |     });
   | |_____^ expected fn pointer, found closure
   |
   = note: expected fn pointer `fn(i32)`
                 found closure `[closure@src/main.rs:27:22: 30:6]`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I see.&lt;/p&gt;

&lt;h2 id=&quot;hang-on-a-minute&quot;&gt;Hang on a minute…&lt;/h2&gt;

&lt;p&gt;So, this is actually quite complicated. Before, the function we passed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do_work_and_then&lt;/code&gt; was &lt;em&gt;pure&lt;/em&gt;: it didn’t have any
associated data, so you could just pass it around as a function pointer (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fn(i32)&lt;/code&gt;) and all was grand.
However, this new function in that last example is a &lt;em&gt;closure&lt;/em&gt;: a function object with a bit of data (a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut Database&lt;/code&gt;)
tacked onto it.&lt;/p&gt;

&lt;p&gt;Closures are kind of magic. We can’t actually name their type – as seen above, the Rust compiler called it a
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[closure@src/main.rs:27:22: 30:6]&lt;/code&gt;, but we can’t actually write that in valid Rust code. If you were to write it out
explicitly, a closure would look something like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Closure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Database&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Database&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Closure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There are a number of things to unpack here.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;an-aside-on-naming-types&quot;&gt;An aside on naming types&lt;/h3&gt;

&lt;p&gt;Being able to name types in Rust is quite important. With a regular old type, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u8&lt;/code&gt;, life is easy. I can write a function
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fn add_one(in: u8) -&amp;gt; u8&lt;/code&gt; that takes one and returns one without any hassle.&lt;/p&gt;

&lt;p&gt;If you can’t actually name a type, working with it becomes somewhat cumbersome. What you end up having to do instead is
refer to it using generics – for example, closures’ types can’t be named directly, but since they implement one of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fn&lt;/code&gt;
family of traits, I can write functions like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;closure_accepting_function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &amp;lt;-- look!&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* do stuff */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If I want to store them in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; or something, I’ll also need to do this dance with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; clause every time they’re
used. This is annoying and makes things harder for me, but it’s still vaguely workable. For now.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rust-async/msql-srv.png&quot; alt=&quot;msql-srv example code&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;[image: from the &lt;a href=&quot;https://github.com/jonhoo/msql-srv/blob/7bb7c06868c82b11dd736039523f95683d12b9d1/tests/main.rs#L26&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;msql-srv&lt;/code&gt;&lt;/a&gt; crate,
showing an example of many &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; clauses as a result of using closures]&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;an-aside-on-radioactive-types&quot;&gt;An aside on ‘radioactive’ types&lt;/h3&gt;

&lt;p&gt;The way Rust is designed tends to encourage certain patterns while discouraging others. Because of ownership and lifetimes,
having pieces of data that hold references to other pieces of data becomes a bit of a problem. If my type has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt; or a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut&lt;/code&gt;
reference to something, Rust makes me ensure that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the something in question &lt;em&gt;outlives&lt;/em&gt; my type; you can’t go and drop the thing I’m referring to if I
still have a reference to it, otherwise my reference will become invalid&lt;/li&gt;
  &lt;li&gt;the something in question doesn’t move while I have the reference&lt;/li&gt;
  &lt;li&gt;my reference to the something doesn’t conflict with other references to the something (e.g. I can’t have my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt; reference if
something else has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut&lt;/code&gt; reference)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So types with references in them are almost ‘radioactive’; you can keep them around for a bit (e.g. inside one particular
function), but attempting to make them long-lived is usually a bit of an issue (requiring advanced tricks such as the
&lt;a href=&quot;https://doc.rust-lang.org/std/pin/struct.Pin.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pin&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/a&gt;
type which didn’t even exist until a few Rust versions ago). Generally Rust doesn’t really like it when you use radioactive
types for too long – they make the borrow checker uneasy, because you’re borrowing something for an extended period of time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rust-async/rustviz.png&quot; alt=&quot;borrowing semantics visualization&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;[image: from the &lt;a href=&quot;https://arxiv.org/pdf/2011.09012.pdf&quot;&gt;RustViz&lt;/a&gt; paper, showing borrowing semantics]&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Closures can be pretty radioactive. Look at the thing we just wrote out: it has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&apos;a mut Database&lt;/code&gt; reference in it!
That means while we’re passing our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Closure&lt;/code&gt; object around, we have to be mindful of the three rules (outlives, doesn’t move,
no conflicting) – which makes things pretty hard. I can’t just hand off the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Closure&lt;/code&gt; to another function (for example, the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do_work_and_then&lt;/code&gt; function), because then I have to make all of those rules work, and that’s not necessarily easy all the time.&lt;/p&gt;

&lt;p&gt;(Not &lt;em&gt;all&lt;/em&gt; closures are radioactive: if you make them &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;move&lt;/code&gt; closures, they’ll take everything by value instead, and create
closure objects that own data instead of having radioactive references to data.
Slightly more of a pain to deal with, but you lose the blue radiation glow the things give out when you look at them.)&lt;/p&gt;

&lt;p&gt;Also, remember what I said about being able to name types? We’re not actually dealing with a nice, written-out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Closure&lt;/code&gt; object
here; we’re dealing with something the compiler generated for us that we can’t name, which is annoying. I also lied when
I said that it was as simple as making all of your functions take &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F, where F: Fn(i32)&lt;/code&gt; or something – there are actually
&lt;em&gt;three&lt;/em&gt; different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fn&lt;/code&gt;-style traits, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fn&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FnMut&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FnOnce&lt;/code&gt;. Do you know the difference between them?&lt;/p&gt;

&lt;p&gt;So. A closure is this magical, un-nameable type that the compiler makes for us whenever we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;|| {...}&lt;/code&gt; syntax, which implements
one of three traits (and it’s not immediately obvious which), and it also might be radioactive.
Try and use one of these, and the Rust compiler is probably going to be watching you &lt;em&gt;very&lt;/em&gt; carefully.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The thing I really want to try and get across here is that &lt;strong&gt;Rust is not a language where first-class functions are ergonomic&lt;/strong&gt;.
It’s a lot easier to make some data (a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;) with some functions attached (methods) than it is to make some functions with some
data attached (closures).&lt;/p&gt;

&lt;p&gt;Trying to use ordinary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;s is downright easy:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;they’re explicitly written out by the programmer with no funky business&lt;/li&gt;
  &lt;li&gt;you choose what traits and methods to implement on them and how to set them out / implement them&lt;/li&gt;
  &lt;li&gt;the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; can actually be referred to by other parts of the code by its type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trying to use closures is hard:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the compiler does some magic to make a closure type for you&lt;/li&gt;
  &lt;li&gt;it implements some obscure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fn&lt;/code&gt; trait (and it’s not immediately obvious which)&lt;/li&gt;
  &lt;li&gt;it might be radioactive (or force you to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;move&lt;/code&gt; and maybe insert a bunch of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clone()&lt;/code&gt; calls)&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;you can’t actually name their type anywhere or do things like return them from a function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Importantly, the restrictions applied to using closures &lt;strong&gt;infect&lt;/strong&gt; types that contain them – if you’re writing a type that contains
a closure, you’ll have to make it generic over some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fn&lt;/code&gt;-trait-implementing type parameter, and it’s going to be impossible for
people to name your type as a result.&lt;/p&gt;

&lt;p&gt;(Other languages, like Haskell, flip this upside down: functions are everywhere, you can pass them around with reckless abandon, etc.
Of course, these other languages usually have garbage collection to make it all work…)&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Bearing this in mind, it is &lt;em&gt;really quite hard&lt;/em&gt; to make a lot of asynchronous paradigms (like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await&lt;/code&gt;) work well in Rust.
As the &lt;a href=&quot;https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/&quot;&gt;what colour is your function&lt;/a&gt; post says, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await&lt;/code&gt;
(as well as things like promises, callbacks, and futures) are really a big abstraction over
&lt;a href=&quot;https://en.wikipedia.org/wiki/Continuation-passing_style&quot;&gt;continuation-passing style&lt;/a&gt; – an idea closely
related to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Scheme_(programming_language)&quot;&gt;Scheme&lt;/a&gt;
programming language. Basically, the idea is you take your normal, garden-variety function and smear it out into &lt;strong&gt;a bunch of closures&lt;/strong&gt;.
(Well, not quite. You can read the blue links for more; I’m not going to explain CPS here for the sake of brevity.)&lt;/p&gt;

&lt;p&gt;Hopefully by now you can see that making a bunch of closures is really not going to be a good idea (!)&lt;/p&gt;

&lt;h2 id=&quot;wibbly-wobbly-scene-transition&quot;&gt;*wibbly wobbly scene transition*&lt;/h2&gt;

&lt;p&gt;And then fast forward a few years and you have an entire &lt;em&gt;language ecosystem&lt;/em&gt; built on top of the idea of making these &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Future&lt;/code&gt; objects
that actually have a load of closures inside&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;, and all of the problems listed above
(hard to name, can contain references which make them radioactive, usually require using a generic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; clause, etc)
apply to them because of how “infectious” closures are.&lt;/p&gt;

&lt;p&gt;The language people have actually been hard at
work to solve &lt;em&gt;some&lt;/em&gt; (some!) of these problems by introducing features like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;impl Trait&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async fn&lt;/code&gt; that make dealing with these
not &lt;em&gt;immediately&lt;/em&gt; totally terrible, but trying to use other language features (like traits) soon makes it clear that the problems
aren’t really gone; just hidden.&lt;/p&gt;

&lt;p&gt;Oh, and all the problems from &lt;a href=&quot;https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/&quot;&gt;what colour is your function&lt;/a&gt;
are still there too, by the way – on &lt;em&gt;top&lt;/em&gt; of the Rust-specific ones.&lt;/p&gt;

&lt;p&gt;Beginner (and experienced) Rust programmers look at the state of the world as it is and try and build things on top of these shaky
abstractions, and end up running into obscure compiler errors, and using hacks like the &lt;a href=&quot;https://docs.rs/async-trait/0.1.42/async_trait/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async_trait&lt;/code&gt;&lt;/a&gt;
crate to glue things together, and end up with projects that depend on like 3 different versions of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tokio&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;futures&lt;/code&gt; (perhaps
some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async-std&lt;/code&gt; in there if you’re feeling spicy) because people have differing opinions on how to try and avoid the
&lt;em&gt;fundamentally unavoidable&lt;/em&gt; problems, and it’s all a bit frustrating, and ultimately, all a bit sad.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Did it really have to end this way? Was spinning up a bunch of OS threads not an acceptable
solution for the majority of situations? Could we have explored solutions more like Go, where a language-provided runtime makes blocking
more of an acceptable thing to do?&lt;/p&gt;

&lt;p&gt;Maybe we could just have kept Rust as it was circa 2016, and let the crazy non-blocking folks&lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; write
hand-crafted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epoll()&lt;/code&gt; loops like they do in C++. I honestly don’t know, and think it’s a difficult problem to solve.&lt;/p&gt;

&lt;p&gt;But as far as my money goes, I’m finding it difficult to justify starting new projects in Rust when the ecosystem is like this. And, as
I said at the start, that makes me kinda sad, because I do actually like Rust.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;(&lt;a href=&quot;https://common-lisp.net/&quot;&gt;Common Lisp&lt;/a&gt; is pretty nice, though. We have crazy macros and parentheses and a language ecosystem that is
older than I am and isn’t showing any signs of changing…)&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;This is really recommended reading if you aren’t already familiar with it (as you’ll soon see) &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Seriously – when I put out the last blog post, the actual async core team members commented saying how much they appreciated the
  feedback, and then they actually went and made futures 1.0 better as a result. Kudos! &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;You might be thinking “well, why don’t you just only use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;move&lt;/code&gt; closures then?” – but that’s beside the point; it’s often a lot
  harder to do so, because now you might have to wrap your data in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Arc&lt;/code&gt; or something, by which point the ergonomic gains of
  using the closure are outweighed by the borrow checker-induced pain. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;You &lt;em&gt;can&lt;/em&gt; actually manually implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Future&lt;/code&gt; on a regular old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;. If you do this, things suddenly become a lot simpler,
  but also you can’t easily perform more async operations inside that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;’s methods. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt;(sorry, I mean, the esteemed companies that deign to use Rust for their low-latency production services) &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Mon, 08 Mar 2021 00:00:00 +0000</pubDate>
        <link>https://eta.st/2021/03/08/async-rust-2.html</link>
        <guid isPermaLink="true">https://eta.st/2021/03/08/async-rust-2.html</guid>
        
        
      </item>
    
      <item>
        <title>Getting PIV-based SSH working on a YubiKey</title>
        <description>&lt;p&gt;I bought a &lt;a href=&quot;https://www.yubico.com/gb/product/yubikey-5c-nano/&quot;&gt;YubiKey 5C Nano&lt;/a&gt; recently. These devices are great – I’ve built a lot
of my (metaphorical) empire on top of them, seeing as they’re capable of acting as an SSH agent (store your SSH keys on them, securely!),
an OpenPGP smartcard (do encryption and decryption on the key!), FIDO U2F ‘security keys’ (use them as a 2-factor authentication method!),
and probably more.&lt;/p&gt;

&lt;p&gt;Getting the thing to work as an SSH agent was, however, not the easiest thing I’ve ever done. There are multiple options here – you
can use the OpenPGP applet and then configure GnuPG to work as an SSH agent, but that’s a brittle solution in my experience (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpg-agent&lt;/code&gt;
is quite flaky, and often requires restarting when it forgets about the YubiKey). Instead, I wanted to see whether I could use the YubiKey’s
PIV (&lt;a href=&quot;https://en.wikipedia.org/wiki/FIPS_201&quot;&gt;Personal Identity Verification&lt;/a&gt;) applet to get this working.&lt;/p&gt;

&lt;h2 id=&quot;procedure&quot;&gt;Procedure&lt;/h2&gt;

&lt;p&gt;For the SSH agent part, we’re going to use Filippo Valsorda’s &lt;a href=&quot;https://github.com/FiloSottile/yubikey-agent&quot;&gt;yubikey-agent&lt;/a&gt;, so you’ll
want to have that installed. In my testing, yubikey-agent’s built-in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yubikey-agent -setup&lt;/code&gt; command errored out, so we’ll configure the
PIV applet of the YubiKey manually. (This’ll also leave the door open for you to do other things with the PIV applet later if you like.)&lt;/p&gt;

&lt;p&gt;You’ll also need Yubico’s own &lt;a href=&quot;https://github.com/Yubico/yubikey-manager&quot;&gt;yubikey-manager&lt;/a&gt; (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ykman&lt;/code&gt; cli tool) installed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Updated on 2023-11-23 to reflect the command names being moved around.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Okay, here goes:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Ensure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pcscd&lt;/code&gt; (the PC/SC smartcard daemon) is installed and running. This might already have been done for you by your Linux distribution
but, if not:
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ sudo systemctl enable --now pcscd&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;(This is safe to run if it’s already been set up; the command will just do nothing).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;This exercise assumes a fresh YubiKey (i.e. one where you haven’t touched the PIV applet yet). If that’s not the case, and you want to
&lt;strong&gt;erase all of the PIV data and start afresh (losing all data encrypted with the PIV keys!)&lt;/strong&gt;, use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ykman piv reset&lt;/code&gt; command.&lt;/li&gt;
  &lt;li&gt;First, change the PIN from the default one (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;123456&lt;/code&gt;).
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ykman piv access change-pin&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;As the command’s help says: &lt;em&gt;The PIN must be between 6 and 8 characters long, and supports any type of alphanumeric characters. For
cross-platform compatibility, numeric digits are recommended.&lt;/em&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Then, change the PUK (‘personal unblocking key’). This is used to reset the PIN if you ever forget it.
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ykman piv access change-puk&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;The PUK has the same entry requirements as the PIN (i.e. also 6-8 ASCII characters).&lt;/li&gt;
      &lt;li&gt;It might be prudent to generate a random PUK and keep it safe (e.g. by writing it down and locking the paper away). If you lose both
the PIN and PUK, you will need to reset the PIV applet, losing all data encrypted with the PIV keys (and SSH access to hosts
you don’t otherwise have access to).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The YubiKey PIV applet by default has a well-known management key used to make changes to the PIV keys (etc.). It’s best practice to
change this to something else. We’ll use the option to generate a random one, store it in the YubiKey, and secure it with the PIN.
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ykman piv access change-management-key -pt&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p&lt;/code&gt;: &lt;em&gt;Store new management key on your YubiKey, protected by PIN. A random key will be used if no key is provided.&lt;/em&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-t&lt;/code&gt;: &lt;em&gt;Require touch on YubiKey when prompted for management key.&lt;/em&gt;&lt;/li&gt;
      &lt;li&gt;If you’re more paranoid than me, you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-g&lt;/code&gt; option instead, which will generate a key for you to note down and give back
later. However, the extent to which you can cause damage with a management key is limited (you can delete and regenerate keys, but
not decrypt data or anything), so this is arguably not worth the hassle – especially since &lt;em&gt;forgetting&lt;/em&gt; the management key
means you’d have to reset the PIV applet to make changes.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Generate a public/private keypair on the key, using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9a&lt;/code&gt; (“PIV Authentication”) key slot. We’ll use ECC (elliptic-curve cryptography)
because it’s fast, secure, and has vastly smaller key sizes, and configure the key to always require touches and require the PIN on
first use. You might want to use different settings here.
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ykman piv keys generate -a ECCP256 --touch-policy ALWAYS --pin-policy ONCE 9a ./yubikey-public.pem&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Note&lt;/strong&gt;: Some outdated SSH servers/implementations only support RSA keys. If this applies to you, leave off the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-a&lt;/code&gt; option to use
RSA 2048 instead.&lt;/li&gt;
      &lt;li&gt;See &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ykman piv keys generate -h&lt;/code&gt; for a full description of all available options.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Use the newly generated key to make a self-signed PKCS#11 certificate to act as our SSH identity.
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ykman piv certificates generate -s &apos;my-yubikey-ssh&apos; -d 365 9a ./yubikey-public.pem&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Modify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-s&lt;/code&gt; parameter to include a human-readable description of the key or the machine the key is installed in.&lt;/li&gt;
      &lt;li&gt;Modify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-d&lt;/code&gt; parameter to set how many days the key will be valid for. The value above specifices 1 year.&lt;/li&gt;
      &lt;li&gt;You can get rid of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yubikey-public.pem&lt;/code&gt; after this step.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Enable yubikey-agent.
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ systemctl --user enable --now yubikey-agent&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Note&lt;/strong&gt;: You might need to kill any running instances of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpg-agent&lt;/code&gt; if you had that running (and it decided to try and use your
YubiKey), and potentially restart &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pcscd&lt;/code&gt; after doing so.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Note&lt;/strong&gt;: Check that yubikey-agent started properly with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemctl --user status yubikey-agent&lt;/code&gt;.
        &lt;ul&gt;
          &lt;li&gt;On my machine, it failed to start because of namespacing issues.&lt;/li&gt;
          &lt;li&gt;If this happens, edit the unit file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemctl --user edit --full yubikey-agent&lt;/code&gt;, and remove all lines under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[Service]&lt;/code&gt;
apart from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExecStart&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExecReload&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Update &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSH_AUTH_SOCK&lt;/code&gt; to point to the running instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yubikey-agent&lt;/code&gt;.
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ export SSH_AUTH_SOCK=/run/user/1000/yubikey-agent/yubikey-agent.sock&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;The above command assumes bash. Other shells may vary.&lt;/li&gt;
      &lt;li&gt;If your user ID isn’t &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000&lt;/code&gt;, you’ll need to change the above command (you can find the right path by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ps aux | grep yubikey-agent&lt;/code&gt;).&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Note&lt;/strong&gt;: You’ll want to put the above command in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.bashrc&lt;/code&gt; or similar.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Get the key fingerprint for your newly generated SSH identity.
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ssh-add -L&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Copy the key fingerprint to your remote host(s), and put it in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh/authorized_keys&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;You should now be able to SSH using the YubiKey!
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ssh &amp;lt;host&amp;gt;&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;The first time you try this, it should pop up a window asking for the PIN, after which you’ll need to touch the flashing YubiKey.&lt;/li&gt;
      &lt;li&gt;Subsequent attempts will only require a touch.&lt;/li&gt;
      &lt;li&gt;If it didn’t work, check the notes under step 8. In particular, using OpenPGP and SSH simultaneously with the YubiKey usually requires
 you to kill either &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pcscd&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpg-agent&lt;/code&gt;, depending on what you want to do.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ensure you have a backup plan in place for when this YubiKey fails, or is lost.
    &lt;ul&gt;
      &lt;li&gt;The key we generated is local to the YubiKey, and cannot be exported. It can very easily be wiped by anyone running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ykman piv reset&lt;/code&gt;
 (which requires no authentication!), or you could lose the key.&lt;/li&gt;
      &lt;li&gt;Make sure you have another way of getting into all hosts you use this YubiKey with (encrypted regular SSH key, out-of-band console, etc.).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;You’re done!&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;Hopefully that worked. If it didn’t, well, there’s a comments thing below if you can be bothered, I guess?&lt;/p&gt;
</description>
        <pubDate>Sat, 06 Mar 2021 00:00:00 +0000</pubDate>
        <link>https://eta.st/2021/03/06/yubikey-5-piv.html</link>
        <guid isPermaLink="true">https://eta.st/2021/03/06/yubikey-5-piv.html</guid>
        
        
      </item>
    
      <item>
        <title>Setting the tone in a group is very important</title>
        <description>&lt;div class=&quot;nowplaying&quot; style=&quot;background: #ffdada;&quot;&gt;
&lt;b&gt;Warning!&lt;/b&gt; This post is a mental health / feels post from a long time ago (in fact, &lt;a href=&quot;/nomenclature&quot;&gt;one gender ago&lt;/a&gt;).
It is kept up in case some people find it useful, but I don&apos;t necessarily endorse the content in here nowadays.
&lt;/div&gt;

&lt;p&gt;Various people have various different opinions on how they should present themselves. Personally, I’d consider myself quite a sensitive person;
I’d like to think I try quite hard to take the feelings and circumstances of other people into account when talking to them, although doing so
is by no means easy or automatic (and I often fail at this, sometimes quite badly). Partially, this is because I often have a lot of feelings
and circumstances going in my life myself which I’d like other people to attempt to take into account – in fact, especially nowadays,
I’d argue that the overwhelming majority of people have some topics or areas that make them uncomfortable, or that it’d be possible to upset
them with a correctly targeted remark.&lt;/p&gt;

&lt;p&gt;It’s very hard to judge what might upset or offend someone, simply because there can be a &lt;em&gt;lot&lt;/em&gt; of stuff going on behind the scenes that people
just don’t tell others about (it’s almost as if having &lt;a href=&quot;https://en.wikipedia.org/wiki/United_Kingdom&quot;&gt;an entire country&lt;/a&gt; where not talking about
stuff is the norm could lead to significant problems!). That said, you can at least attempt to be &lt;em&gt;reactive&lt;/em&gt; – it’s sometimes possible to detect
that something you’ve said or done wasn’t received well, and try to do less of that thing in future.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;There are, of course, cultures and groups where this sort of thing is very much not the norm. Some people – and groups of people – attempt to
act in a sort of “macho” / “tough guy” sort of way, where one is supposed to pretend that one doesn’t really have feelings, or that one is immune
to whatever life might throw in one’s way. This, of course, is obviously false – everyone has feelings! – but it suits them to conduct themselves
in this manner, because, at the end of the day, talking about one’s feelings can be very hard.&lt;/p&gt;

&lt;p&gt;Maybe you weren’t brought up in an environment where people do that; maybe you never saw your parents, or your school friends, cry, or be angry,
or show feelings toward things, because they thought they had to be ‘strong’). Maybe you had people express &lt;em&gt;too much&lt;/em&gt; of their feelings in your past
and really got put off by it (as in, maybe &lt;em&gt;you&lt;/em&gt; were distressed by other people having issues and now have decided that ‘burdening’ others with
your feelings isn’t a good idea). Maybe you don’t have any friends you &lt;em&gt;really&lt;/em&gt; trust enough to be able to confide in, perhaps because they’re
all tough guys, or perhaps because you have issues trusting people for some other reason – especially in our modern society, that reason can
often be &lt;a href=&quot;https://en.wikipedia.org/wiki/Loneliness&quot;&gt;loneliness&lt;/a&gt;, an epidemic that people don’t actually realise the severity of.&lt;/p&gt;

&lt;p&gt;But anyway, you don’t want to talk about your feelings. I get that, because I don’t really want to either&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. Being in that position, while
it’s not really a good thing for you long-term, isn’t really &lt;em&gt;wrong&lt;/em&gt;; you aren’t hurting anyone except yourself there (although if you read the
previous paragraph and found it hit a bit too close to home, you should probably &lt;a href=&quot;https://www.youtube.com/watch?v=n3Xv_g3g-mA&quot;&gt;watch this video&lt;/a&gt;).&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;It’s not uncommon for large-ish (i.e. more than 3 or 4) groups of people to have leaders, whether explicitly or implicitly allocated. Usually
there are one or two people who do a lot of the talking – who appear to set the tone and the rules of engagement for the rest of the group.
This doesn’t always have to happen, of course, but what I’m saying is it probably happens more than you realise. (It can also become painfully
obvious when the leaders step out to go and do something else for a bit and you end up with a bunch of people who don’t really know what they
should be doing with themselves, which is always a fun scenario!)&lt;/p&gt;

&lt;p&gt;As you probably gathered from the title, what I really want to emphasise is the whole “setting the tone” aspect of being a leader. This
&lt;a href=&quot;http://jsomers.net/blog/it-turns-out&quot;&gt;turns out&lt;/a&gt; to be important in a bunch of ways that aren’t immediately obvious. I’d hypothesize that
a large part of the issues people who aren’t &lt;a href=&quot;https://en.wiktionary.org/wiki/cishet#English&quot;&gt;cishet&lt;/a&gt; white males face in STEM fields, and especially
programming / IT, are down to this factor; the groups people tend to hang out in are implicitly using a bunch of norms that probably aren’t
very inclusive (think people making slightly inappropriate cheeky comments about women they fancy, but also more subtle mannerisms and ways of
communicating that tend to only be shared by people from a certain background that make it harder for people not from that background to
communicate). A lot of ink has been spilled about this&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; in terms of how companies should try to avoid it when creating teams at work,
because it’s a real problem.&lt;/p&gt;

&lt;p&gt;The macho people from earlier can face real issues with this sort of thing. Often, their tough-guy behaviour implicitly sets the tone for the
groups of people they find themselves in (which usually end up being filled with people who don’t really care, or are also trying to be macho).
To avoid opening themselves up to the potential of insecurity, they often tend to do this more forcefully – terms like “pussy”, “wimp”, et al.
are often employed for this purpose, wherein such people attempt to claim that people who do have feelings, are afraid of things, etc. are
somehow ‘weaker’ than them.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The astute reader will think that I’m writing this because someone did that to me and I’m angry about it, and this blog post is my way of
rationalizing their behaviour and asserting that I’m actually a better person than them. And they’d be partially right!&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;It’s more nuanced than that, however. The thing that’s actually really sad about these sorts of situations is that the people responsible for
creating the harmful no-feelings-allowed environment are often the people most in need of a way to express their feelings (as implied earlier).
And what they’ve managed to do by creating such an environment is ensure they most likely won’t be able to do that thing with those people –
if they try, it could get awkward (since the others aren’t really happy having a more ‘deep’ conversation, and that’s why they’re in the group),
or they might find themselves met with a surprising lack of sympathy (because others actually did have problems and got humiliated for them).&lt;/p&gt;

&lt;p&gt;I don’t even think you can blame these people, either. They’ve just found themselves in a situation that most likely isn’t even their
fault, and they don’t really know what they should do to cope with it. If anything, it’s probably society that teaches them to behave in this way –
and that’s just a &lt;a href=&quot;https://www.youtube.com/watch?v=YL42GI-X5WA&quot;&gt;sad, sad situation&lt;/a&gt; that’s not exactly easy to fix&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;No, it suits me to write pseudo-intellectual blog posts that nobody reads that vaguely hint at a whole bunch of screwed up stuff going on. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Like a lot of the claims on this blog, this one is unsubstantiated. I think it’s true though… &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;I mean I really don’t post often, so someone has to have annoyed me for things to get this bad. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;That said, I’ve seen advertising campaigns that try! I think some biscuit company teamed up with some mental health charity to promote the idea of having a cuppa and a chat about your problems with your friends, which is absolutely a good thing (even if Big Biscuit ends up profiting from it) &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Fri, 15 Jan 2021 00:00:00 +0000</pubDate>
        <link>https://eta.st/2021/01/15/social-leadership.html</link>
        <guid isPermaLink="true">https://eta.st/2021/01/15/social-leadership.html</guid>
        
        
      </item>
    
      <item>
        <title>Strict COVID-19 restrictions in universities are irresponsible</title>
        <description>&lt;p&gt;&lt;em&gt;This post is about mostly personal circumstances / issues, as well as current affairs. If that’s not what you want, turn back now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At the start of the current &lt;a href=&quot;https://en.wikipedia.org/wiki/Covid-19&quot;&gt;coronavirus disease 2019 (COVID-19)&lt;/a&gt; pandemic,
we were told that “flattening the curve” was a good idea – i.e. attempting to limit the spread of the disease by
staying at home, wearing face coverings, etc. was a necessary step we should all take in order to prevent the national
health services from getting overwhelmed (leading to an excess of deaths of people who could otherwise be helped).&lt;/p&gt;

&lt;p&gt;A significant number of months have passed since March, and a new wave of unsuspecting secondary school graduates
have descended on the UK’s universities&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; – but, obviously, since there’s still a pandemic going on, things are
different from the way they used to be. Pretty much all universities have new precautions to limit the spread of the disease,
including things like&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;grouping students into (logical) “households”, and restricting interaction between said households&lt;/li&gt;
  &lt;li&gt;enforcing social distancing requirements&lt;/li&gt;
  &lt;li&gt;enforcing face covering usage&lt;/li&gt;
  &lt;li&gt;limiting the number of students that can be in the same place at one time (in line with the nationwide “&lt;a href=&quot;https://www.gov.uk/government/publications/coronavirus-covid-19-meeting-with-others-safely-social-distancing/coronavirus-covid-19-meeting-with-others-safely-social-distancing&quot;&gt;rule of six&lt;/a&gt;”)&lt;/li&gt;
  &lt;li&gt;getting rid of &lt;em&gt;all&lt;/em&gt; face-to-face tuition, and moving everything online&lt;/li&gt;
  &lt;li&gt;adding a curfew to, or closing, pubs and social spaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these precautions involve more sacrifices on the part of the students than others; wearing face coverings is relatively
zero-cost, and has been shown to &lt;a href=&quot;https://tinyurl.com/FAQ-aerosols&quot;&gt;limit the spread of the disease quite significantly&lt;/a&gt;&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.
However, the goal of the overwhelming majority of the restrictions is clear: &lt;em&gt;limit social interaction as far as practicable&lt;/em&gt;.
(This ‘makes sense’, because social interaction is how the virus is spread.)&lt;/p&gt;

&lt;p&gt;The point I want to express here is that having that as a goal in the context of universities is somewhat irresponsible, and seems to completely
ignores the mental health concerns of an entire year’s worth of students at university right now&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. Most students have left the
(hopefully relatively comfortable) environment of secondary school to come to university – sometimes in an entirely new city, or
indeed country. These students typically don’t have many people they can talk to once they arrive, having left the vast majority
of their friends behind from school; instead, they must somehow discover new people, usually by having a &lt;em&gt;lot&lt;/em&gt; of spontaneous
interactions until they’re able to bed in and start to establish some friendships.&lt;/p&gt;

&lt;p&gt;It doesn’t take a genius to realise that this process is not compatible with the above stated goal of not having much social interaction.&lt;/p&gt;

&lt;p&gt;However, what I think is particularly irresponsible is the lack of discussion surrounding the consequences of not letting this process
play out as normal. The need for students to socialize and make friends is invariant; &lt;a href=&quot;https://www.youtube.com/watch?v=n3Xv_g3g-mA&quot;&gt;the feeling of loneliness is inherent to being human&lt;/a&gt;
and isn’t going away any time soon, so people will (attempt to) socialize to feel less lonely, especially when placed in an unfriendly new environment.
Examples of consequences arising from a lack of social interactions among students include&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;greater incidences of mental health problems, as loneliness creates new or exacerbates existing issues&lt;/li&gt;
  &lt;li&gt;a reduced ability to even notice and help with such problems, as remote learning can mask all sorts of issues that are more easily recognizable in person&lt;/li&gt;
  &lt;li&gt;reduced academic performance and ability, due to previously mentioned mental health problems&lt;/li&gt;
  &lt;li&gt;a &lt;a href=&quot;https://www.theguardian.com/education/2020/sep/19/uk-universities-predict-record-student-dropout-rate&quot;&gt;greater dropout rate&lt;/a&gt;, leading to reduced
income for universities (some of which are already struggling to stay afloat)&lt;/li&gt;
  &lt;li&gt;in the extreme case, greater incidences of suicides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s also the case that not everyone is perfectly rule-abiding. While more meek students might follow restrictions and suffer the associated consequences,
others will flagrantly disobey them, a fact which has consequences of its own:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;instead of socializing in ‘controlled’ environments, under the purview of (e.g.) student wellbeing officers, students will socialize elsewhere
(e.g. a random park)
    &lt;ul&gt;
      &lt;li&gt;in these ‘uncontrolled’ environments, a greater prevalence of dangerous behaviours (excessive drinking, drug use, etc.) would be expected&lt;/li&gt;
      &lt;li&gt;…but since these are the only opportunities available to undersocialized students, more students might end up taking unwise risks than would otherwise&lt;/li&gt;
      &lt;li&gt;there is already evidence to suggest more students are taking drugs &lt;em&gt;and dying from it&lt;/em&gt; through precisely this mechanism&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;from the perspective of the virus, the replacements for the now-banned opportunities to socialize are likely a lot worse, &lt;em&gt;increasing&lt;/em&gt; net transmission
    &lt;ul&gt;
      &lt;li&gt;think people drinking from the same bottle, cramming themselves into places, not using any face coverings, … (because there’s no reason for them to)&lt;/li&gt;
      &lt;li&gt;also see e.g. &lt;a href=&quot;https://www.theguardian.com/education/2020/oct/06/manchester-students-organising-covid-positive-parties&quot;&gt;Manchester students organizing COVID-19 transmission parties&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lot of the problems here tie into greater issues with the discussion of the pandemic in the media and elsewhere; a lot of people seem to
think that the &lt;a href=&quot;https://coronavirus.data.gov.uk/&quot;&gt;worrying graph of growing cases&lt;/a&gt; is unquestionably something that must be dealt with immediately
(perhaps with a lockdown, which is even worse for students). Don’t get me wrong – COVID-19 is a deadly disease, and must not be underestimated.
Letting the disease run completely unchecked throughout the population, without any restrictions whatsoever, is a terrible idea and would kill many people
unnecessarily; a very contested document called the &lt;a href=&quot;https://gbdeclaration.org/&quot;&gt;Great Barrington declaration&lt;/a&gt; calls for something akin to that (albeit
with protections in place for vulnerable members of society).&lt;/p&gt;

&lt;p&gt;The reality is that it’s very difficult to come to a decision, and neither extremist view is correct; making everyone sit on their hands until a vaccine
is available is stupid, but so is letting the disease run wild. There’s much we don’t know about the impacts of the virus, including whether or not
it has long-term health implications for certain groups (and the conditions under which such long-term complications might arise) – but sensationalizing
(e.g. evocative news headlines that attempt to instil fear as to the deadliness of the disease) does not help us come to a reasoned conclusion about risk.&lt;/p&gt;

&lt;p&gt;To conclude, then, I believe the evidence to support &lt;em&gt;strict&lt;/em&gt; COVID-19 restrictions in UK universities is questionable, and a re-think
about the rationale for, and the consequences of, such strict restrictions is sorely required. It’s really unclear whether the benefits conferred by
severely limiting social interaction (at least, imposing rules that attempt to achieve such) are worth the consequences of doing so – heck, it’s even
unclear whether people even follow the rules enough to limit transmission at all (and the recent outbreaks in universities across the nation confirm that).&lt;/p&gt;

&lt;p&gt;A lack of humane thinking seems to be the case amongst those who impose said restrictions; the problem cannot be viewed as a simple mathematical calculation
of how to reduce cases (if reducing cases is even something worth attempting to do!), but one that leads to significant human suffering for those affected.
With the world being more divided and polarised than ever, it’s worth trying to be &lt;em&gt;empathetic&lt;/em&gt; – to both see the fear on the part of those pushing for a
lockdown and limitation of cases, and to recognize the crushing impact restrictions have on the restricted.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;I’m one of these, of course, which is why I’m writing this. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Even if you disagree with the evidence here, face coverings are still basically zero-cost – you really don’t sacrifice much by wearing one! &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;If you disagree with me, please read the whole article first before getting angry. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;I can’t find a citation for this, so take this claim with a pinch of salt. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sun, 11 Oct 2020 00:00:00 +0000</pubDate>
        <link>https://eta.st/2020/10/11/university-covid.html</link>
        <guid isPermaLink="true">https://eta.st/2020/10/11/university-covid.html</guid>
        
        
      </item>
    
      <item>
        <title>Writing as a form of relief</title>
        <description>&lt;div class=&quot;nowplaying&quot; style=&quot;background: #ffdada;&quot;&gt;
&lt;b&gt;Warning!&lt;/b&gt; This post is a mental health / feels post from a long time ago (in fact, &lt;a href=&quot;/nomenclature&quot;&gt;one gender ago&lt;/a&gt;).
It is kept up in case some people find it useful, but I don&apos;t necessarily endorse the content in here nowadays.
&lt;/div&gt;

&lt;p&gt;The content on this blog does not get updated very frequently.&lt;/p&gt;

&lt;p&gt;This is largely because, as a somewhat permanent and public sort of thing, I have to be quite careful about what stuff I write on here,
since it could come back to bite me later, right? We’ve all heard the stories about people getting turned down from job offers due to
some embarrassing stuff they posted on the social network &lt;em&gt;du jour&lt;/em&gt;&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, and I generally get the impression that being careful with
what you choose to write into your permanent online record is generally a good thing. (Well, when you phrase it like that…)&lt;/p&gt;

&lt;p&gt;What are blogs for, though? As far as I can tell, almost nobody reads this one; there are a few stragglers who come here through Google
because I posted about something like &lt;a href=&quot;/2018/02/07/microg.html&quot;&gt;microG&lt;/a&gt; or Rust programming (even though there are now &lt;em&gt;much&lt;/em&gt; better
resources to learn Rust out there, given the language has changed a whole load since I started learning it…).&lt;/p&gt;

&lt;p&gt;Furthermore, I don’t
consider myself the kind of person who’s happy to go and do lots of writing about technical topics (for the moment, at least).
Some people can sustain an entire blogging habit by packing things full of interesting technical content / deep dives / whatever.
This is great, because then the content is at least ‘useful’ to the person reading it (in some sense&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;), instead of being some poor sap
whining on about random other things happening in their life.&lt;/p&gt;

&lt;p&gt;Unfortunately, though, I’m not one of these people. So, either I go about my life and don’t write any part of it up on the blog, or
I do the converse, and end up spewing things I’ll probably regret later out into the web at large.&lt;/p&gt;

&lt;p&gt;There’s a &lt;a href=&quot;https://en.wikipedia.org/wiki/COVID-19_pandemic&quot;&gt;pandemic&lt;/a&gt; on. Everyone’s feeling miserable, a lot of people have very tragically
lost their lives to the COVID-19 virus, and people are beginning to question all sorts of things about the existence we led before this all
started.&lt;/p&gt;

&lt;p&gt;So, what the hell, let’s just get on with it then.&lt;/p&gt;

&lt;h2 id=&quot;the-utility-of-personal-content&quot;&gt;The utility of personal content&lt;/h2&gt;

&lt;p&gt;Some people might be of the opinion that personal content (like somewhat soppy blog posts) is not worth reading, and should perhaps be gotten rid
of entirely. However – and obviously I’m going to be biased here! – I don’t really think so. Okay, I think the sort of content where people
talk about whatever mundane things they’ve been getting up to (“so, this week, I washed my bike, went out for a run, tinkered with Node.js a bit…”)
is perhaps a bit of a waste of time – I’d call that ‘oversharing’, perhaps. But I do think it’s possible to read stuff about someone else’s problems
and gain some insight into how you might be able to solve your own, so I don’t really want to dismiss personal content entirely.&lt;/p&gt;

&lt;p&gt;I guess there’s a distinction between content that is purely &lt;em&gt;descriptive&lt;/em&gt; – explaining how much you hate yourself, or how annoying thing X is, or
whatever – and content that has an &lt;em&gt;analytical&lt;/em&gt; or &lt;em&gt;empathetic&lt;/em&gt; component as well – trying to figure out the reasons why this is the case and provide some advice
to people feeling the same way, or otherwise attempting to connect your own personal experience to what others may feel.
The former has no value to
the reader, really – oh, poor random internet commentator. How sad. Imagine an agony aunt column without the agony aunt’s responses. How awful would that be?
But the latter kind of stuff can definitely be of some value; I’ve read things on the web that have influenced the way I look at the world and respond to things –
most people probably have. So it’s not entirely worthless!&lt;/p&gt;

&lt;h2 id=&quot;the-title-of-this-post&quot;&gt;The title of this post&lt;/h2&gt;

&lt;p&gt;In fact, I think writing about things is a great way to process and deal with said things. I’m not just talking about personal or emotional matters – way back in 2016
when I wrote a short &lt;a href=&quot;/2016/03/12/learn-you-a-rust-for-great-good.html&quot;&gt;Rust tutorial series&lt;/a&gt;, the aim was as much to inform others as to force me to be honest about my
own Rust abilities; writing something up gets you to specify what exactly you mean in plain English, which can be great for identifying gaps in your knowledge, or areas
of flawed thinking.&lt;/p&gt;

&lt;p&gt;This is partially why, as it says in the title, writing can be a form of relief; there’s something about putting pen to paper that makes you feel just a bit better about
whatever it is you’re writing about, be that your frustrations learning a new programming language or something more personal.&lt;/p&gt;

&lt;p&gt;It’s also, in some ways, a lot lower friction than talking to someone about something. If you start calling up your friends and ranting to them about how much asynchronous
programming paradigms suck, you eventually lose most of your friends – whereas you aren’t going to annoy anyone, or take up anyone’s time, by writing about things&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.
(Unless you do something crazy like start sending your friends letters in the post containing your rants. This is also a good way to lose most of your friends.)&lt;/p&gt;

&lt;h2 id=&quot;mental-health-awareness&quot;&gt;‘Mental health awareness’&lt;/h2&gt;

&lt;p&gt;Now, of course, you don’t actually have to publish anything to get these benefits; simply writing something up should be enough. (This is the idea behind journaling, I think.)
In fact, as I discussed at the start, publishing things can be harmful to your career.&lt;/p&gt;

&lt;p&gt;However, I think it’s still worth doing: I’m a human being, and you are too. There are thousands of tech blogs that just talk about tech and don’t talk about anything personal
or human; there are thousands of people who only talk about technical topics on their website and never mention a thing about their private lives. I’m not saying they should –
but I do tend to think that seeing other people talk about their problems publicly can be a great motivator for you to do the same (for example, I’m a big fan of
&lt;a href=&quot;https://rachelbythebay.com/w/&quot;&gt;rachelbythebay.com&lt;/a&gt; and her occasional post about toxic Silicon Valley culture). To me, that’s what this concept of ‘mental health awareness’
is about (at least in part): recognizing that other people are people too, and trying to get people to talk more openly about their thoughts and feelings, instead of just keeping
them to themselves.&lt;/p&gt;

&lt;p&gt;So, yeah. Write (somewhat critically) about things that bother you, even if they aren’t technical. It’s helpful for you, and you never know what impact it’ll have on somebody else!&lt;/p&gt;

&lt;p&gt;Or, you know, just don’t, if you’re not into that sort of thing. But I’m going to give it a try.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Also, the hope is that just trying to get into a semi-regular pattern of writing about /anything/ without much of a filter will mean that more technical stuff seeps out as well.
We’ll see what happens!&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Well, people tell me this happens. I’m not, ehm, &lt;em&gt;experienced&lt;/em&gt; enough to actually have heard of this happening first-hand. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;On a related note, if you’re someone who might have the capability to make me a job offer, just… do me a solid and don’t read the blog, okay? &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:p&lt;/code&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;More on this later. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;You also are probably not going to annoy your friends by talking about personal issues if you really feel the need to talk to someone about them, since that’s what friends are for! However, it doesn’t feel too great having to do this a lot (where ‘a lot’ is subjectively defined) – in other words, even though your friends might not actually get annoyed, your fear of them getting annoyed (and perhaps not telling you) might be enough to make you not want to talk to them. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Thu, 21 May 2020 00:00:00 +0000</pubDate>
        <link>https://eta.st/2020/05/21/personal-stuff.html</link>
        <guid isPermaLink="true">https://eta.st/2020/05/21/personal-stuff.html</guid>
        
        
      </item>
    
      <item>
        <title>Somewhat contrived schema designing for a new chat system</title>
        <description>&lt;p&gt;&lt;em&gt;[This is post 5 about designing a new chat system. Have a look at &lt;a href=&quot;/2019/09/10/chat-systems.html&quot;&gt;the first post in the series&lt;/a&gt; for more context!]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This post follows on from the &lt;a href=&quot;/2019/12/18/nea-schema.html&quot;&gt;previous one in the series&lt;/a&gt;&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;,
wherein I had a shot at designing / specifying what &lt;em&gt;state&lt;/em&gt; – persistent information, shared amongst all
servers in a federated system – in group chats should look like. To summarize, we ended up with the group chat state containing
three important things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a set of &lt;em&gt;roles&lt;/em&gt;, which are a way of grouping together &lt;em&gt;capabilities&lt;/em&gt; available to users with said roles
    &lt;ul&gt;
      &lt;li&gt;Remember, &lt;em&gt;capabilities&lt;/em&gt; are simple keywords like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;speak&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;change-topic&lt;/code&gt; that represent actions users can take&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;a list of &lt;em&gt;memberships&lt;/em&gt; (users in the chat), together with the &lt;em&gt;role&lt;/em&gt; for each chat member&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;non-user-related state&lt;/em&gt;, like the chatroom topic, which sort of follows a key-value store
    &lt;ul&gt;
      &lt;li&gt;We figured out that allowing arbitrary stuff to be stored in a room’s state was a bad idea, so this just contains…some random fields we’ll specify more formally later&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, we’ll look into how this state will be represented in server databases, and spec out a &lt;em&gt;database schema&lt;/em&gt; for our 
server implementations to use&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h2 id=&quot;unpacking-our-group-chat-state-object&quot;&gt;Unpacking our group chat state object&lt;/h2&gt;

&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; just store the group chat state as a big JSON blob in the database (indeed, if we were using something like
&lt;a href=&quot;https://www.mongodb.com/&quot;&gt;MongoDB&lt;/a&gt;, that would be commonplace). However, this probably isn’t a good idea – for a number of reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;we’d have to retrieve the whole thing every time we wanted to access information about it, which is suboptimal
performance-wise&lt;/li&gt;
  &lt;li&gt;things in the blob could quietly become inconsistent with the rest of the database if we didn’t check it all the time&lt;/li&gt;
  &lt;li&gt;the database wouldn’t be able to enforce any schemas; we’d have to do that in our application code&lt;/li&gt;
  &lt;li&gt;unless we (ab)use something like PostgreSQL’s native JSON support, our group chat state would be completely opaque
from the database’s point of view – meaning it’d be hard to draw links between things in there (e.g. user IDs)
and the rest of the database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These concerns are similar to the concerns &lt;a href=&quot;https://en.wikipedia.org/wiki/Third_normal_form&quot;&gt;third-normal form (3NF)&lt;/a&gt;, a way of structuring database
schemas from 1971, hopes to address. Under 3NF, you store your data in a set of database tables representing various objects,
with each table having a &lt;a href=&quot;https://en.wikipedia.org/wiki/Primary_key&quot;&gt;primary key&lt;/a&gt; (an identifier or set of identifiers uniquely identifying that object).
3NF then states that other information in each table must &lt;em&gt;only&lt;/em&gt; tell you something about the primary key, and nothing else;
they aren’t allowed to depend on anything other than the value of the primary key.&lt;/p&gt;

&lt;p&gt;As a more concrete example, let’s say we have a User table representing a user of our chat system, where the primary key
is a combination of a username and the user’s home server. If I wanted to add a column describing what channels they’re in,
for example, that would be fine – but if I wanted to also add the most recent messages for each channel, say
(let’s imagine you’re designing a UI like WhatsApp’s, with a homescreen that shows this information), that wouldn’t be valid
under 3NF, because the most recent messages are a property of the channel, not the user. We could end up having two users
in the same channel, and forget to keep this ‘recent messages sent’ property consistent, which would lead to confusion!&lt;/p&gt;

&lt;p&gt;So, using 3NF seems like a pretty good idea – and that’s exactly what we’re going to do! Our group chat state object doesn’t
fit 3NF as one large blob, so we’re going to need to decompose it into a set of database tables that store the same information.&lt;/p&gt;

&lt;h2 id=&quot;lets-do-some-schema-designing&quot;&gt;Let’s do some schema designing!&lt;/h2&gt;

&lt;h3 id=&quot;groupchats-our-starting-point&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchats&lt;/code&gt;: our starting point&lt;/h3&gt;

&lt;p&gt;Of course, we need some object to represent a group chat. Since group chats are going to be shared across different servers,
we have to choose some identifier that’s always going to be unique – we can’t just give them textual names, otherwise they’d
be a possibility of them clashing. Instead, we’ll use a &lt;a href=&quot;https://en.wikipedia.org/wiki/Universally_unique_identifier&quot;&gt;universally unique identifier (UUID)&lt;/a&gt; – it does what it says on the
tin!&lt;/p&gt;

&lt;p&gt;Our set of group chat state (for now) is in the list below. I’ve also put 🚫 next to things we can’t put in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchats&lt;/code&gt;
table directly due to 3NF, and explained why.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;topic / subject
    &lt;ul&gt;
      &lt;li&gt;This is purely a property of the group chat itself, and it doesn’t depend on anything else.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;list of users 🚫
    &lt;ul&gt;
      &lt;li&gt;This one technically could be a property of the group chat, but making it one isn’t a great idea.&lt;/li&gt;
      &lt;li&gt;Firstly, that’d mean we’d have to use an array, which is generally frowned upon; it makes it harder to do things like
database table &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JOIN&lt;/code&gt;s when the users are stuck in an array attribute.&lt;/li&gt;
      &lt;li&gt;Also, we probably want to associate some information with a user’s membership, like their role. Doing that in the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchats&lt;/code&gt; table would be a big 3NF violation.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;list of defined roles 🚫
    &lt;ul&gt;
      &lt;li&gt;Roles have capabilities associated with them, so they should be their own thing.&lt;/li&gt;
      &lt;li&gt;Said otherwise, our primary key is the channel’s UUID, not (channel UUID, role), so storing capabilities (which
depend on those two things) would be a 3NF violation.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;mapping of users to what roles they have 🚫
    &lt;ul&gt;
      &lt;li&gt;Similarly to the last item, this mapping introduces a 3NF violation.&lt;/li&gt;
      &lt;li&gt;We’ll probably end up doing this one in a separate object, as discussed above.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;list of servers involved in this group chat, as well as whether they’re sponsoring or not 🚫
    &lt;ul&gt;
      &lt;li&gt;Ditto, really.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;current state version
    &lt;ul&gt;
      &lt;li&gt;We need to keep track of what state version we’re on (remember, the state version is a monotonically incrementing integer),
for the purposes of our consensus algorithm.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, now that that’s all clear, we’re left with group chat UUID, subject, and current state version. Here’s the &lt;a href=&quot;https://en.wikipedia.org/wiki/Data_definition_language&quot;&gt;SQL DDL&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchats&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;uuid&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UUID&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;state_ver&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- can be null, if a group chat is unnamed.&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(We’ll include the DDL for each table in our schema.)&lt;/p&gt;

&lt;h3 id=&quot;groupchat_roles-and-groupchat_role_capabilities-storing-group-chat-role-information&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchat_roles&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchat_role_capabilities&lt;/code&gt;: storing group chat role information&lt;/h3&gt;

&lt;p&gt;Before we can actually express user memberships, we need something to store group chat role information;
what roles exist, and what capabilities are associated with them. Behold:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchat_roles&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;role_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;SERIAL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;groupchat_uuid&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UUID&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;role_name&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;UNIQUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_uuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;role_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchat_role_capabilities&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;role_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchat_roles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;capability&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;UNIQUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capability&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A row in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchat_roles&lt;/code&gt; table represents a role name in a group chat. Role names are unique per group chat, so
the 2-tuple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(groupchat_uuid, role_name)&lt;/code&gt; is unique; the only bit of information associated with a role
is a list of capabilities, but we aren’t going to use arrays (q.v.), so the separate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchat_role_capabilities&lt;/code&gt;
table represents capabilities granted to users with a given role.&lt;/p&gt;

&lt;p&gt;We’ve given roles an internal integer ID
(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;role_id&lt;/code&gt;) just to make the primary key less annoying; the ‘real’ primary key should be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(groupchat_uuid, role_name)&lt;/code&gt;,
but that’d be a real pain to refer to in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchat_role_capabilities&lt;/code&gt; table (we’d have to store both the UUID
and the role name! So much wasted space!&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;), so we just use an integer instead.&lt;/p&gt;

&lt;h3 id=&quot;groupchat_memberships-associating-users-with-group-chats&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchat_memberships&lt;/code&gt;: associating users with group chats&lt;/h3&gt;

&lt;p&gt;Now that we’ve got a group chat table, and a way of expressing user roles,
we want a way to express the set of users that are in said group chats, along with the role they have.
This is very simple – we have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(groupchat, user)&lt;/code&gt; primary key, and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;role&lt;/code&gt; foreign key.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchat_memberships&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;groupchat_uuid&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UUID&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;role_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchat_roles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groupchat_uuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’m deliberately not going to mention what’s in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt; table yet; we’re going to discuss that in another blog post.&lt;/p&gt;

&lt;h3 id=&quot;groupchat_sponsoring_servers-associating-sponsoring-servers-with-group-chats&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchat_sponsoring_servers&lt;/code&gt;: associating sponsoring servers with group chats&lt;/h3&gt;

&lt;p&gt;We also need to associate servers with groups somehow. This &lt;em&gt;is&lt;/em&gt; done for us partially, in that users reside on servers,
and so naturally the set of servers associated with a given group chat are just the servers on which the members reside –
but that doesn’t take into account the fact that some of these servers might be &lt;em&gt;sponsoring servers&lt;/em&gt; for the purposes
of federation.&lt;/p&gt;

&lt;p&gt;Enter the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupchat_sponsoring_servers&lt;/code&gt; table:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchat_sponsoring_servers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;groupchat_uuid&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UUID&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupchats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;server_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;servers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groupchat_uuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Again, there’s this mysterious &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servers&lt;/code&gt; table we haven’t got to.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;

&lt;p&gt;We’ve now established what principles we’re going to use to design our schema (3NF), and we’ve got 5 lovely tables that
express all the group chat state stuff we’ve been jabbering on about for the last two blog posts in proper SQL DDL
that we could actually use!&lt;/p&gt;

&lt;p&gt;We now need to tackle the other two important parts of our schema; we’ve done group chats, but &lt;em&gt;messages&lt;/em&gt; and &lt;em&gt;users&lt;/em&gt;
are yet to be specified. We’ll need to discuss a few important points about how we design our protocol to fit both of these,
as there’s more to it than you might think! (For example, a user might seem simple – just someone on a server somewhere, right? –
but what about them having a profile picture, or a bit of ‘status’ text describing what they’re up to, or things like that?)&lt;/p&gt;

&lt;p&gt;All of that will come in the next blog post in the series, coming (hopefully quite) soon to a website near you!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Hey, those deadlines really are quite scary. Wouldn’t it be lovely if we had a &lt;a href=&quot;/blah/nea-roadmap.html&quot;&gt;roadmap&lt;/a&gt;, with
nice intermediate dates on it, so we could actually plan stuff?&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Reading this post might be somewhat confusing, if you haven’t read that one! &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;This blog series is more “broad brushstrokes” than “exhaustive details” – because otherwise both you and I would get horrifically bored. Don’t worry, though – the exhaustive details will turn up somewhere and be featured in the final thing… &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;Of course, this schema isn’t part of the specification. Server implementors can do whatever they want; there does, however, have to be &lt;em&gt;a&lt;/em&gt; reference implementation out there… &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;If we’re really wanted to save space, of course, we’d refer to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group chats&lt;/code&gt; table itself using an integer instead of a UUID (because a UUID is a blob of four integers, I think). I definitely draw the line at foreign composite primary keys, though… &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Thu, 26 Dec 2019 00:00:00 +0000</pubDate>
        <link>https://eta.st/2019/12/26/nea-schema-2.html</link>
        <guid isPermaLink="true">https://eta.st/2019/12/26/nea-schema-2.html</guid>
        
        
      </item>
    
      <item>
        <title>Designing group chat state for a new chat system</title>
        <description>&lt;p&gt;&lt;em&gt;[This is post 4 about designing a new chat system. Have a look at &lt;a href=&quot;/2019/09/10/chat-systems.html&quot;&gt;the first post in the series&lt;/a&gt; for more context!]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is potentially the point at which the previous few blog posts, which have talked in
vague, vacuous terms about the possibility of designing some new utopian chat system, start
to gradually become more about actually getting the work done – purely due to the fact that,
as previously mentioned, I have a deadline! It’s been a while&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; since I last said anything
about the chat systems project – so, let’s get things rolling again with a practical post about
our database schema, shall we?&lt;/p&gt;

&lt;h2 id=&quot;the-importance-of-a-good-schema&quot;&gt;The importance of a good schema&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Bad programmers worry about the code. Good programmers worry about data structures and their relationships.”&lt;/p&gt;

  &lt;p&gt;~ &lt;a href=&quot;https://lwn.net/Articles/193245/&quot;&gt;Linus Torvalds&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s been said, as you can see in the quote above, that thinking hard about your &lt;em&gt;data structures&lt;/em&gt; – the way you choose
to store whatever data it is that you’re processing – is vitally important to the success of whatever it is that you’re
building – so it’s worth having good ones, or else you’ll end up with hacky code that doesn’t quite work right&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.
If you have the right data structures, the code usually sort of flows in to fill the gaps and make everything work right – whereas
if you do the code first, it doesn’t usually work as nicely.&lt;/p&gt;

&lt;p&gt;Now, data structures come in all shapes and sizes – there are trees, hash tables, association lists, binary heaps, and all sorts of
other fun stuff that you’d probably find in some CS textbook somewhere. We &lt;em&gt;could&lt;/em&gt; use some of these for our new chat system – and,
in fact, we probably will use the odd hash table or two. However, given I want to keep things nice and boring, sticking to proven,
reliable technologies for this project&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, we’re probably just going to store everything in a set of &lt;a href=&quot;https://en.wikipedia.org/wiki/Relational_database&quot;&gt;relational database&lt;/a&gt; tables
(i.e. rows and columns!).&lt;/p&gt;

&lt;p&gt;And, a &lt;em&gt;schema&lt;/em&gt; is essentially a description of a set of what columns mean what, and what tables you’re going to have. Which is what
I’m going to write now, so, without further ado…&lt;/p&gt;

&lt;h2 id=&quot;what-do-we-actually-want&quot;&gt;What do we actually want?&lt;/h2&gt;

&lt;p&gt;It’s a good idea to start with a discussion of what data we have, and what we’re trying to get out of that data. We’ve said that we
want our new system to &lt;a href=&quot;/2019/10/10/nea-federation-design.html&quot;&gt;support our own funky blend of federation&lt;/a&gt;, so that’s going to need to be accounted for. Naturally,
a chat service will have a bunch of &lt;strong&gt;users&lt;/strong&gt;, each with their own usernames, passwords, emails, and other information that needs to be stored
about them. We’ll probably have some &lt;strong&gt;messages&lt;/strong&gt; as well, given users tend to make a lot of those when you give them the chance to.&lt;/p&gt;

&lt;p&gt;As we’ve &lt;a href=&quot;/2019/09/26/nea-federation.html#key-implementation-goals&quot;&gt;also discussed before&lt;/a&gt;, the primary function of our chat service is to convey messages from their source to their recipient,
and do so in a reliable manner. That implies further that, in addition to the messages themselves, we’d also benefit from storing information
about message &lt;strong&gt;delivery&lt;/strong&gt; – did the messages get through, in the end, or do we need to try again sometime?&lt;/p&gt;

&lt;p&gt;Since we’re supporting &lt;strong&gt;group chats&lt;/strong&gt;, those also need to have some information stored about them. Our federation protocol&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; requires us to store
multiple different ‘versions’ of group chat state (remember, ‘state’ refers to things like the list of members of the chat, who has admin rights,
and what the current topic is) – because it’s based on this whole funky consensus protocol stuff, we’ll need to keep track of what servers have
proposed changes, and which changes we decided to actually accept.&lt;/p&gt;

&lt;h2 id=&quot;a-model-for-group-chat-state&quot;&gt;A model for group chat state&lt;/h2&gt;

&lt;p&gt;The consensus algorithms we looked into previously allow us to get a bunch of trusted servers (‘sponsoring servers’) to agree on something, where
‘something’ is just some arbitrary value – usually with some kind of monotonically incrementing identifier or version number. It thus follows that
we need some model for what that ‘something’ will look like; how will servers communicate information about a room’s state to one another?&lt;/p&gt;

&lt;h3 id=&quot;users-administrators-and-moderation&quot;&gt;Users, administrators, and moderation&lt;/h3&gt;

&lt;p&gt;Actually, we haven’t even specified what we want this room state to look like yet – there are still some unresolved questions around things as simple as
how administrator rights / operator powers should work in group chats. Different platforms do this in different ways, after all:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;IRC has a system of ‘channel modes’, where users can be given flags such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+o&lt;/code&gt; (operator; can kick, ban, etc.) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+v&lt;/code&gt; (voice; can speak in muted channels).
    &lt;ul&gt;
      &lt;li&gt;Some servers then extend this, adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+h&lt;/code&gt; (‘half-operator’), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+a&lt;/code&gt; (‘admin’), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+q&lt;/code&gt; (‘owner’), and all sorts of other random gunk that confuses people
and makes it hard to determine who can do what.&lt;/li&gt;
      &lt;li&gt;Of course, server administrators can override all of this and just do what they want, on most implementations.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Matrix has ‘&lt;a href=&quot;https://matrix.org/docs/guides/moderation/#power-levels&quot;&gt;power levels&lt;/a&gt;’ - each user has an integer between 0 and 100 assigned to them, which determines what they’re able to do. A set of rules (stored in
the room state, alongside the power levels) specify what power levels map to what – for example, only people with power level &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;=&lt;/code&gt; 50 can set the topic, etc.
    &lt;ul&gt;
      &lt;li&gt;You’re not allowed to give people power levels higher than what you already have, and you can’t take away power from people who have more or the same amount of power as you.
This is kinda required to make the whole thing work.&lt;/li&gt;
      &lt;li&gt;Because Matrix is decentralised, it’s possible to get yourself into a state where everyone’s lost control of a chatroom and you can’t get it back. Of course, though,
this is quite easy to avoid, by making the software stop you from shooting yourself in the foot.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;WhatsApp has ‘group admins’ and…that’s pretty much it&lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;. You either have admin rights, in which case you can do everything, or you don’t and you can’t really do much.
    &lt;ul&gt;
      &lt;li&gt;This is very simple for users to understand.&lt;/li&gt;
      &lt;li&gt;However, it makes WhatsApp completely impractical for all sorts of use cases; anyone who’s found themselves kicked out of a WhatsApp chat after someone went crazy and banned everyone
after pleading for admin ‘in order to help keep things civil’ probably knows what I’m talking about.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://discordapp.com/&quot;&gt;Discord&lt;/a&gt; has ‘roles’ - there’s a bit in server settings where you can create sets of permissions, called ‘roles’, that you grant to different users in order to empower them
to do specific things&lt;sup id=&quot;fnref:6&quot;&gt;&lt;a href=&quot;#fn:6&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;.
    &lt;ul&gt;
      &lt;li&gt;This is partially why Discord can play host to &lt;em&gt;massive&lt;/em&gt; chatrooms for very popular games like PUBG and Fortnite: the system is very flexible, and suited to large-scale moderation.&lt;/li&gt;
      &lt;li&gt;However, it’s also perhaps quite confusing for some people, especially if you’re just using it for smaller-scale stuff like a private group chat.&lt;/li&gt;
      &lt;li&gt;Like Matrix, the roles are arranged in a kind of hierarchy; roles at the top of the hierarchy can make changes to roles below them, but not the other way round.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, hmm, it might seem like there’s a lot to choose from here – but, in fact, it’s a bit more simple than you’d think. It’s immediately
apparent that the more flexible Matrix/Discord systems can in fact be used to make more simple systems, like the IRC and WhatsApp ones; if all you want is group admin or not group admin,
you can make two Discord roles (one with all permissions, one with none), or have two power levels (100 and 0, say, with appropriate rules for each one), and you’ve essentially got the
more simple system using your complex one. (And you can do funky things with your user interface to hide away the added power, for those who don’t really need it.)&lt;/p&gt;

&lt;p&gt;Taking some inspiration from this idea, and the age-old concept of an &lt;a href=&quot;https://en.wikipedia.org/wiki/Access-control_list&quot;&gt;access-control list&lt;/a&gt;, here’s a proposed model. We’ll specify a set of &lt;em&gt;capabilities&lt;/em&gt; that describe what actions
can be taken – for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;speak&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;change-topic&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mute-user&lt;/code&gt;, and so on – and then a set of &lt;em&gt;roles&lt;/em&gt;, like the Discord roles, that are sets of capabilities. Each user gets
a role, which in turn describes what they’re able to do. (If we want to make things work nicely for ex-IRC people, the roles can optionally come with a small letters, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+o&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+v&lt;/code&gt;.)
Unlike Discord and Matrix, there won’t be any hierarchy to the roles. Roles can only be modified by someone holding a capability called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;change-roles&lt;/code&gt;, and that’ll be the end of it.
The sponsoring servers in our federation model will do this role check every time they receive a message or a request to change the state in some way, and refuse to apply the change if it’s
not permitted.&lt;/p&gt;

&lt;p&gt;The list of capabilities will eventually be written in some spec document somewhere, and thereby standardised across different server implementations.
Essentially, they’ll work like &lt;a href=&quot;https://ircv3.net/specs/core/capability-negotiation.html#vendor-specific-capabilities&quot;&gt;IRCv3 capabilities&lt;/a&gt;, where vendors can make their own capabilities up if they want to (prefixing them with a valid domain name
for whatever it is that they built).&lt;/p&gt;

&lt;p&gt;To make things easier, the &lt;em&gt;special capability&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt; allows a user to make any change.&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;https://www.json.org/json-en.html&quot;&gt;JSON&lt;/a&gt;-esque syntax&lt;sup id=&quot;fnref:7&quot;&gt;&lt;a href=&quot;#fn:7&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;, this would look a bit like:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;roles&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;normals&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;speak&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;change-topic&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;voiced&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;speak&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;speak-in-muted-channel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;change-topic&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;theta.eu.org/special-extension-power&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;admins&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;memberships&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;server.org/user1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;normals&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;server.org/adminuser1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;admins&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;non-user-related-state&quot;&gt;Non-user-related state&lt;/h3&gt;

&lt;p&gt;Of course, there’s also state that doesn’t directly relate to users – the current group chat subject, whether or not guest users are allowed in, etc.
Some state may have more complicated structure – for example, the Matrix people have a &lt;a href=&quot;https://matrix.org/docs/guides/moderation/#banning-servers-from-rooms-server-acls&quot;&gt;server ACL&lt;/a&gt; state event that lets you ban specific servers from taking part in rooms, which
is pretty much its own little object with embedded arrays and booleans – which means we can’t just model this state as a simple stringly-typed key-value store, i.e.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;state&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;subject&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;other_state_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;other_value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;etc&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The question is, though, how extensible we want things to be: can users (or servers, for that matter) store arbitrary objects in our group chat state, or do they all
have to follow some predetermined schema? Matrix takes the approach of letting users chuck in whatever they like (assuming they have the necessary power level) – essentially
treating each group chat as a mini database – while pretty much every other platform restricts room state to a set of predetermined things.&lt;/p&gt;

&lt;h4 id=&quot;capability-negotiation&quot;&gt;Capability negotiation&lt;/h4&gt;

&lt;p&gt;I’m not entirely sure allowing &lt;em&gt;arbitrary&lt;/em&gt; extensibility, in the way that Matrix does, is such a good idea – for two reasons&lt;sup id=&quot;fnref:8&quot;&gt;&lt;a href=&quot;#fn:8&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Allowing people to just store arbitrary data in your group chat seems a bit too much like an abuse vector. It’s a chat system, not a distributed database; allowing people
to just chuck whatever stuff they want in there is a bit much!
    &lt;ul&gt;
      &lt;li&gt;How would you display these arbitrary state events, given you don’t know anything about them?&lt;/li&gt;
      &lt;li&gt;You’d need some limit on size, to prevent people just using you as free storage?&lt;/li&gt;
      &lt;li&gt;In many jurisdictions, you’re responsible for content that gets put on your server. What if someone uploads stuff you’re not comfortable hosting, and stashes it away
in some event your client doesn’t implement (and therefore doesn’t show)?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Usually, things in the group chat state are there for a reason, and it doesn’t make sense for servers to just ignore them.
    &lt;ul&gt;
      &lt;li&gt;For example, consider when the Matrix folks rolled out the server ACL feature: servers not on the latest version of their software would just completely ignore it,
which is pretty bad (because then it had no effect, as malicious actors could get into the room via the unpatched servers).
        &lt;ul&gt;
          &lt;li&gt;They ‘solved’ this by polling all the servers in a room for their current version number, and checking to see which ones still needed updating (which let them badger
the server owners until it got updated).&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead, it’s probably better to actually have some system – like the previously-mentioned &lt;a href=&quot;https://ircv3.net/specs/core/capability-negotiation&quot;&gt;IRCv3 capability negotiation&lt;/a&gt;&lt;sup id=&quot;fnref:9&quot;&gt;&lt;a href=&quot;#fn:9&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;9&lt;/a&gt;&lt;/sup&gt; – where servers can say “yes, I support
features X, Y, and Z”, thus enabling those features to be used only if all of the sponsoring servers in a group chat actually support them. This solves the issue of extensibility
quite nicely: non-user related state is governed by a strict schema, with optional extensions for servers that have managed to negotiate that.&lt;/p&gt;

&lt;h2 id=&quot;group-chat-state-summary&quot;&gt;Group Chat state summary&lt;/h2&gt;

&lt;p&gt;So, to sum up: we’ve managed to get a better idea of what the blob of &lt;em&gt;group chat state&lt;/em&gt;, shared across all servers participating in a group chat and agreed upon via the federation
consensus protocol, should look like: it’ll contain a &lt;em&gt;capability&lt;/em&gt;-based system for regulating what users are allowed to do what, and it’ll contain a set of other pieces of strictly-typed
non-user-related state, like the group chat subject! This might all seem a bit abstract for now, but it’ll hopefully become clearer once actual code starts getting written.
On that note…&lt;/p&gt;

&lt;h2 id=&quot;a-note-about-timings&quot;&gt;A note about timings&lt;/h2&gt;

&lt;p&gt;We managed to roughly sketch out group chat state over the course of around ~2,500 words (!), but there’s still all the other stuff to spec out: users, messages, and reliable delivery
mechanisms. In addition, there are also a number of less conceptual things to sketch out, like how we’re going to ensure server-to-server transport happens securely (SSL? Encryption?&lt;sup id=&quot;fnref:10&quot;&gt;&lt;a href=&quot;#fn:10&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;)
and things like that.&lt;/p&gt;

&lt;p&gt;And this all has to be done by March, at the absolute latest. &lt;em&gt;Yay!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On a more hopeful note, we do &lt;a href=&quot;https://git.theta.eu.org/nea-2019.git/&quot;&gt;actually have some code&lt;/a&gt; for this whole project – currently, it does the registration flow part of a standard
IRC server (basically, the plan is to piggyback off of IRC for the client&amp;lt;-&amp;gt;server stuff, to get things going), and has a very untested implementation of the Paxos consensus protocol.
We’re using &lt;a href=&quot;https://common-lisp.net/&quot;&gt;Common Lisp&lt;/a&gt; as our main programming language, which is fun&lt;sup id=&quot;fnref:11&quot;&gt;&lt;a href=&quot;#fn:11&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;!&lt;/p&gt;

&lt;p&gt;Anyway, the rest of the spec stuff will follow in the next blogpost (hopefully quite quickly after this one…!) – if anyone actually has the tenacity to read my ~2,500 words about the annals
of random chat protocol stuff, that is! We will also actually show how all of this translates into a database schema, which was sort of the point. Oops.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;To be continued…&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Err, two months. I was very busy applying to universities, okay! &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;A large part of my &lt;a href=&quot;/2019/09/10/chat-systems.html#matrix&quot;&gt;criticism of Matrix&lt;/a&gt; centered around the fact that they did something funky – and, in my view, perhaps unnecessary – with theirs. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;Using some fancy NoSQL database has &lt;a href=&quot;http://www.sarahmei.com/blog/2013/11/11/why-you-should-never-use-mongodb/&quot;&gt;undone many a project&lt;/a&gt; in the past; SQL databases have had a lot of engineering and research effort put into them over the decades to make them work reliably and quickly, and that shouldn’t be given up lightly! &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;Read the ‘funky blend of federation’ blog post linked earlier, if you need a refresher! &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt;I think they recently added some sort of limited permissions system for “who can change the topic: admins or all group members?” and things like that, but this is the gist of it. &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:6&quot;&gt;
      &lt;p&gt;They also serve a vanity / novelty purpose; look at me, I’ve got a colourful name! &lt;a href=&quot;#fnref:6&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:7&quot;&gt;
      &lt;p&gt;Even though we might not be actually &lt;em&gt;using&lt;/em&gt; JSON at the end of the day, it’s a pretty well-understood way to describe things. &lt;a href=&quot;#fnref:7&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:8&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://github.com/ircv3/ircv3-specifications/issues/333&quot;&gt;This IRCv3 issue&lt;/a&gt; is about a very similar problem (allowing arbitrary client-only message tags on IRC), and is also worth a read! &lt;a href=&quot;#fnref:8&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:9&quot;&gt;
      &lt;p&gt;These capabilities are, unfortunately, nothing to do with the user/administrator capabilities discussed earlier… &lt;a href=&quot;#fnref:9&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:10&quot;&gt;
      &lt;p&gt;The Matrix people went and used &lt;a href=&quot;https://perspectivessecurity.wordpress.com/&quot;&gt;Perspectives&lt;/a&gt; instead of regular SSL PKI for their stuff, and they eventually had to move away from it. It’s worth learning from that mistake! &lt;a href=&quot;#fnref:10&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:11&quot;&gt;
      &lt;p&gt;It was going to be Rust, but then I started doing Lisp stuff and figured this would be more ‘interesting’… &lt;a href=&quot;#fnref:11&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Wed, 18 Dec 2019 00:00:00 +0000</pubDate>
        <link>https://eta.st/2019/12/18/nea-schema.html</link>
        <guid isPermaLink="true">https://eta.st/2019/12/18/nea-schema.html</guid>
        
        
      </item>
    
  </channel>
</rss>
