<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://steventang.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://steventang.net/" rel="alternate" type="text/html" /><updated>2026-03-12T07:39:15+00:00</updated><id>https://steventang.net/feed.xml</id><title type="html">Steven Tang</title><subtitle>Steven Tang</subtitle><author><name>Steven Tang</name></author><entry><title type="html">Ram disk</title><link href="https://steventang.net/blog/2024/ramdisk" rel="alternate" type="text/html" title="Ram disk" /><published>2024-07-05T12:28:00+00:00</published><updated>2024-07-05T12:28:00+00:00</updated><id>https://steventang.net/blog/2024/ramdisk</id><content type="html" xml:base="https://steventang.net/blog/2024/ramdisk"><![CDATA[<p>Not long ago, I was running multiple Docker containers in parallel to
score code submissions.</p>

<p>This involved compiling the code, and running a common set of tests
on the compiled executable to determine the score.</p>

<p>When examining the output,
I noticed that some submissions were failing to compile.</p>

<h2 id="diagnosis">Diagnosis</h2>

<p>Though there was a 30s timeout on the compile step
to prevent broken submissions from running infinitely,
I didn’t expect submissions to actually time out
given the relatively small scale of the project.</p>

<p>After performing a couple of runs, I determined that this issue was
intermittent, and occurring for different submissions each time.
I also noticed that my reference submission which was written
in Rust was failing more frequently.</p>

<p>I tried to compile some of these submissions outside of Docker,
but was unable to reproduce the issue.</p>

<p>By chance, I then found that invoking <code class="language-plaintext highlighter-rouge">clang</code> (with no arguments)
after restarting the machine was taking more than 20 seconds.
Given that it was slow on the first run but not subsequent invocations,
I thought to caching.</p>

<p>Perhaps there was an issue or limit with Disk I/O?</p>

<p>I filed a ticket with the provider.</p>

<h2 id="workaround-1">Workaround 1</h2>

<p>After reading documentation from previous years,
I noticed that the documented setup had Docker on an external volume
rather than the boot volume.</p>

<p>I added an external volume for Docker, and the timeouts went away.</p>

<p>Given that I was no longer experiencing the issue,
I replied to the email from the provider with my solution
and they closed the ticket.</p>

<h2 id="workaround-2">Workaround 2</h2>

<p>A few weeks later, I was scoring a new set of submissions for a new project.</p>

<p>When examining the output, I found that Rust submissions with external
crates were often failing to compile,
and many submission were failing on a test
which indirectly required reading many small files from disk.</p>

<p>Another observation from watching <code class="language-plaintext highlighter-rouge">docker ps</code> was that though I requested
<code class="language-plaintext highlighter-rouge">parallel(1)</code> to run 16 submissions at once,
it was never running the full 16,
and even dropped to 4 at one point.</p>

<p>Since it was a weekend, I didn’t immediately create another ticket and looked for
alternative solutions.</p>

<p>The first thing that I tried was setting up Software RAID 0,
and mounting Docker on the array.
While this seemed to have improved results somewhat,
some tests were still failing unexpectedly.</p>

<p>After some thinking, it occurred to me that since Disk I/O latency may be the issue,
it might be worthwhile to try putting <code class="language-plaintext highlighter-rouge">/var/lib/docker</code> in memory instead.</p>

<p>This can be achieved with a ram disk, <a href="https://unix.stackexchange.com/questions/66329" target="_blank" rel="noopener">such as</a>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mount <span class="nt">-o</span> <span class="nv">size</span><span class="o">=</span>16G <span class="nt">-t</span> tmpfs none /var/lib/docker
</code></pre></div></div>

<p>Surprisingly, this worked on the first attempt.
The parallelism increased, and tests were now passing consistently.</p>

<p>Since the RAM allocation for the VM was 64 GB,
this solution was feasible for my use case
and I was able to complete the testing,
albeit with the minor inconvenience of recreating the ram disk
after every restart.</p>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[Not long ago, I was running multiple Docker containers in parallel to score code submissions.]]></summary></entry><entry><title type="html">Reproducing Firefox Bug 1888751</title><link href="https://steventang.net/blog/2024/firefox-1888751" rel="alternate" type="text/html" title="Reproducing Firefox Bug 1888751" /><published>2024-06-29T13:30:00+00:00</published><updated>2024-06-29T13:30:00+00:00</updated><id>https://steventang.net/blog/2024/firefox-1888751</id><content type="html" xml:base="https://steventang.net/blog/2024/firefox-1888751"><![CDATA[<p>Last year, after clearing my Firefox cache on my laptop,
I found that I could no longer login to the Bitwarden browser extension
against my self-hosted
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank" rel="noopener">Vaultwarden</a>
configured with a client certificate.</p>

<p>The issue was that the client certificate popup no longer appeared when
interacting with the extension.</p>

<p>I confirmed that visiting the URL of the Vaultwarden instance directly
(i.e. in a browser tab) still triggers the prompt.
But this, alongside reinstalls, restarts and clearing the cache had no effect.</p>

<p>After configuring the instance URL for login,
the extension will try to reach the <code class="language-plaintext highlighter-rouge">config</code> endpoint
(which returns information about server configuration)
and immediately fail with 403 Forbidden without (the browser) prompting the user to select
the client certificate.</p>

<p>As the laptop isn’t my primary device, and the Chromium version of the extension
still worked, I tolerated the occasional annoyance of having to switch
between browsers to copy passwords.</p>

<h2 id="a-workaround">A workaround</h2>

<p>At some point, possibly because it also stopped working on my main device,
this became a much bigger annoyance.</p>

<p>After repeating the basic troubleshooting,
I eventually tried to downgrade Firefox to an older version from the package cache.</p>

<p>Given that newer profiles cannot be used on older versions,
I needed to reimport the certificate and reinstall the extension.
This time, the prompt popped up and I was able to log in.</p>

<p>I then upgraded the browser to the latest stable version,
and it continued to work in this logged in state with (what I assume to be)
the certificate selection cached.</p>

<h2 id="reproduced">Reproduced</h2>

<p>I ended up taking a closer look at the issue in December last year,
with the aim of gathering more evidence to create a bug report.</p>

<p>I was able to reproduce the issue consistently on Firefox 120 and 121,
while showing that the extension was working on Firefox 118.</p>

<p>I also created a simple test extension, which fetches a
page requiring client authentication when visiting <code class="language-plaintext highlighter-rouge">example.com</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"manifest_version"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Client Cert test"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Test that client cert works properly in extension."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"content_scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"matches"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"https://example.com/*"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"js"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"test.js"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"permissions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"&lt;all_urls&gt;"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// test.js</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">border</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">5px solid red</span><span class="dl">"</span><span class="p">;</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">test</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://&lt;redacted&gt;</span><span class="dl">"</span><span class="p">,</span>
        <span class="p">{</span> <span class="na">credentials</span><span class="p">:</span> <span class="dl">'</span><span class="s1">include</span><span class="dl">'</span> <span class="p">});</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">x</span><span class="p">);</span>
<span class="p">}</span>
<span class="nf">test</span><span class="p">();</span>
</code></pre></div></div>

<p>Since the certificate prompt always pops up for this test extension when
visiting <code class="language-plaintext highlighter-rouge">example.com</code>,
I thought that perhaps a browser API has changed and that this somehow caused
the extension to break in newer versions.</p>

<p>Believing that this was a bug with the Bitwarden extension,
I created
<a href="https://github.com/bitwarden/clients/issues/7077" target="_blank" rel="noopener">bitwarden/clients Issue #7077</a>.</p>

<p>After trying to edit the code of the extension and failing to fix the issue,
I set it aside and hoped that there was enough information for someone else to take a look.</p>

<h2 id="browser-bug">Browser Bug</h2>

<p>Unfortunately, the issue remained unsolved for a few months.
Having this on my todo list for a while, I decided to investigate further
on a free weekend.</p>

<p>After digging around in the code again, and having no luck, I browsed to
the extension manifest.</p>

<p>Reading through, I found that there was a <code class="language-plaintext highlighter-rouge">background</code> key.
After looking this up on <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background" target="_blank" rel="noopener">MDN</a>,
it struck me as something which could
make a difference, given that it is used to define background scripts and pages.</p>

<p>Since the extension can be used independently of a particular page or tab,
perhaps it will be loaded differently compared to an extension
which is activated on a page?</p>

<p>After extending the test app with a background page and adding the <code class="language-plaintext highlighter-rouge">background</code> key to the manifest:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">background</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">page</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">background.html</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">persistent</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">},</span>
</code></pre></div></div>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;html&gt;</span>
  <span class="nt">&lt;head&gt;</span>
    <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/head&gt;</span>
  <span class="nt">&lt;body&gt;&lt;/body&gt;</span>
  <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"test.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p>I was able to reproduce the bug, but this time with the test extension.</p>

<p>This meant that it wasn’t an extension bug like I had previously thought,
but a browser bug instead!</p>

<p>Having this minimal reproducible example and after confirming that it
was still working on older versions, I reported
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1888751" target="_blank" rel="noopener">Firefox Bug 1888751</a>.</p>

<h2 id="confirmed">Confirmed</h2>
<p>After providing a script to create a client certificate setup (upon request),
the bug was confirmed by the triager.</p>

<p>I didn’t follow too closely on the discussion or the fix that followed,
being busy with the teaching semester.</p>

<p>From reading the comments and code,
my understanding is that the dialog failed to pop up because
the variable holding the dialog box <code class="language-plaintext highlighter-rouge">browserWindow.gDialogBox</code> was <code class="language-plaintext highlighter-rouge">undefined</code>,
where <code class="language-plaintext highlighter-rouge">browserWindow</code> is the “most recent main window”.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">browserWindow</span><span class="p">?.</span><span class="nx">gDialogBox</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">browserWindow</span> <span class="o">=</span> <span class="nx">Services</span><span class="p">.</span><span class="nx">wm</span><span class="p">.</span><span class="nf">getMostRecentBrowserWindow</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">if </span><span class="p">(</span><span class="nx">browserWindow</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">browserWindow</span><span class="p">.</span><span class="nx">gDialogBox</span>
        <span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="nx">clientAuthAskURI</span><span class="p">,</span> <span class="p">{</span> <span class="nx">hostname</span><span class="p">,</span> <span class="nx">certArray</span><span class="p">,</span> <span class="nx">retVals</span> <span class="p">})</span>
        <span class="p">...;</span>
    <span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then, the fix involved adding a further fallback of opening a new window
on top of the most recent window, with the client certificate prompt as a modal.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">clientAuthAskURI</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">chrome://pippki/content/clientauthask.xhtml</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// if cannot find window with browserWindow.gDialogBox...</span>
<span class="kd">let</span> <span class="nx">mostRecentWindow</span> <span class="o">=</span> <span class="nx">Services</span><span class="p">.</span><span class="nx">wm</span><span class="p">.</span><span class="nf">getMostRecentWindow</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
<span class="nx">Services</span><span class="p">.</span><span class="nx">ww</span><span class="p">.</span><span class="nf">openWindow</span><span class="p">(</span><span class="nx">mostRecentWindow</span><span class="p">,</span> <span class="nx">clientAuthAskURI</span><span class="p">,</span> <span class="dl">"</span><span class="s2">_blank</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">centerscreen,chrome,modal,titlebar</span><span class="dl">"</span><span class="p">,</span> <span class="nx">args</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>
<p>The fix from the Mozilla team made it to Firefox 126 Beta,
and then Firefox 126 Stable on May the 14th, 2024.</p>

<p>After confirming that the fix applied to my workflow,
I closed the GitHub issue on the Bitwarden clients repository.</p>

<p>Many thanks to those who were involved!</p>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[Last year, after clearing my Firefox cache on my laptop, I found that I could no longer login to the Bitwarden browser extension against my self-hosted Vaultwarden configured with a client certificate.]]></summary></entry><entry><title type="html">Notes &amp;amp; Experiments with Linux x64 assembly</title><link href="https://steventang.net/blog/2023/assembly-notes" rel="alternate" type="text/html" title="Notes &amp;amp; Experiments with Linux x64 assembly" /><published>2023-12-29T10:25:00+00:00</published><updated>2023-12-29T10:25:00+00:00</updated><id>https://steventang.net/blog/2023/assembly-notes</id><content type="html" xml:base="https://steventang.net/blog/2023/assembly-notes"><![CDATA[<h2 id="compilation">Compilation</h2>
<p>To compile using <code class="language-plaintext highlighter-rouge">nasm</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nasm <span class="nt">-felf64</span> &lt;fn&gt;.asm <span class="o">&amp;&amp;</span> ld &lt;fn&gt;.o <span class="nt">-o</span> &lt;fn&gt;
</code></pre></div></div>

<h2 id="hello-world">Hello World</h2>
<p>A hello world program, which:</p>
<ol>
  <li>Writes out the message.</li>
  <li>Exits with status code 0.</li>
</ol>

<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="c1">; Mark _start symbol as global</span>
<span class="nf">global</span> <span class="nv">_start</span>

<span class="nf">section</span> <span class="nv">.rodata</span>
    <span class="nl">message:</span>        <span class="kd">db</span> <span class="s">"Hello World!"</span><span class="p">,</span> <span class="mh">0x0A</span>
    <span class="nl">messageLen:</span>     <span class="nf">equ</span> <span class="kc">$</span><span class="o">-</span><span class="nv">message</span>

<span class="nf">section</span> <span class="nv">.text</span>

<span class="nl">_start:</span>
    <span class="c1">; Write to stdout message of length messageLen</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; write syscall for x64</span>
    <span class="nf">mov</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; stdout</span>
    <span class="nf">mov</span>         <span class="nb">rsi</span><span class="p">,</span> <span class="nv">message</span>
    <span class="nf">mov</span>         <span class="nb">rdx</span><span class="p">,</span> <span class="nv">messageLen</span>
    <span class="nf">syscall</span>

    <span class="c1">; exit 0</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="mi">60</span>         <span class="c1">; exit</span>
    <span class="nf">xor</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="nb">rdi</span>        <span class="c1">; status code 0</span>
    <span class="nf">syscall</span></code></pre></figure>

<p>The syscall table for x64: <a href="https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md" target="_blank" rel="noopener">https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md</a></p>

<h2 id="conditionals">Conditionals</h2>
<p>A small program which prints out whether the number of CLI arguments is even or odd:</p>
<ol>
  <li>Pop argc into register.</li>
  <li>Bitwise AND the value with 1.</li>
  <li>Compare the result with 0, and jump to the corresponding label.</li>
  <li>Print the relevant message and exit.</li>
</ol>

<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="c1">; Mark _start symbol as global</span>
<span class="nf">global</span> <span class="nv">_start</span>

<span class="nf">section</span> <span class="nv">.rodata</span>
    <span class="nl">oddMessage:</span>        <span class="kd">db</span> <span class="err">"</span><span class="nv">argc</span> <span class="nv">indicates</span> <span class="nv">odd</span> <span class="nv">number</span> <span class="nv">of</span> <span class="nv">arguments</span><span class="err">"</span><span class="p">,</span> <span class="mh">0x0A</span>
    <span class="nl">oddMessageLen:</span>     <span class="nf">equ</span> <span class="kc">$</span><span class="o">-</span><span class="nv">oddMessage</span>
    <span class="nl">evenMessage:</span>       <span class="kd">db</span> <span class="err">"</span><span class="nv">argc</span> <span class="nv">indicates</span> <span class="nv">even</span> <span class="nv">number</span> <span class="nv">of</span> <span class="nv">arguments</span><span class="err">"</span><span class="p">,</span> <span class="mh">0x0A</span>
    <span class="nl">evenMessageLen:</span>    <span class="nf">equ</span> <span class="kc">$</span><span class="o">-</span><span class="nv">evenMessage</span>

<span class="nf">section</span> <span class="nv">.text</span>

<span class="nl">_start:</span>
    <span class="c1">; Pop top of stack (argc) to r8</span>
    <span class="nf">pop</span>     <span class="nv">r8</span>

    <span class="c1">; Bitwise AND of r8 and 1</span>
    <span class="nf">mov</span>     <span class="nb">rax</span><span class="p">,</span> <span class="mi">1</span>
    <span class="nf">and</span>     <span class="nb">rax</span><span class="p">,</span> <span class="nv">r8</span>

    <span class="c1">; If rax == 0, even number, jump to even</span>
    <span class="nf">cmp</span>     <span class="nb">rax</span><span class="p">,</span> <span class="mi">0</span>
    <span class="nf">je</span>      <span class="nv">even</span>

<span class="nl">odd:</span>
    <span class="c1">; Prepare write of odd message</span>
    <span class="nf">mov</span>         <span class="nb">rsi</span><span class="p">,</span> <span class="nv">oddMessage</span>
    <span class="nf">mov</span>         <span class="nb">rdx</span><span class="p">,</span> <span class="nv">oddMessageLen</span>
    <span class="nf">jmp</span>         <span class="nv">end</span>

<span class="nl">even:</span>
    <span class="c1">; Prepare write of even message</span>
    <span class="nf">mov</span>         <span class="nb">rsi</span><span class="p">,</span> <span class="nv">evenMessage</span>
    <span class="nf">mov</span>         <span class="nb">rdx</span><span class="p">,</span> <span class="nv">evenMessageLen</span>

<span class="nl">end:</span>
    <span class="c1">; Write message</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; write</span>
    <span class="nf">mov</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; stdout</span>
    <span class="nf">syscall</span>

    <span class="c1">; exit 0</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="mi">60</span>         <span class="c1">; exit</span>
    <span class="nf">xor</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="nb">rdi</span>        <span class="c1">; status code 0</span>
    <span class="nf">syscall</span></code></pre></figure>

<h2 id="iterations">Iterations</h2>
<p>Loops can be achieved through a conditional and some jumps.</p>

<p>Here’s FizzBuzz, compiled with:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nasm <span class="nt">-felf64</span> fizzbuzz.asm <span class="o">&amp;&amp;</span> gcc fizzbuzz.o <span class="nt">-o</span> fizzbuzz
</code></pre></div></div>

<p>In <code class="language-plaintext highlighter-rouge">main</code>:</p>
<ol>
  <li>The loop variable is initialised to 1.</li>
  <li>Evalulate the condition, and jump to <code class="language-plaintext highlighter-rouge">exit</code> if the loop variable becomes greater than 100.</li>
  <li>Call the <code class="language-plaintext highlighter-rouge">fizzbuzz</code> function (The body of the loop).</li>
  <li>Increment the loop variable.</li>
  <li>Jump back to the condition.</li>
</ol>

<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">global</span> <span class="nv">main</span>
<span class="nf">extern</span> <span class="nv">printf</span>
<span class="nf">extern</span> <span class="nv">fflush</span>

<span class="nf">section</span> <span class="nv">.rodata</span>
    <span class="nl">fizz:</span>       <span class="kd">db</span> <span class="s">"Fizz"</span>
    <span class="nl">fizzLen:</span>    <span class="nf">equ</span> <span class="kc">$</span><span class="o">-</span><span class="nv">fizz</span>
    <span class="nl">buzz:</span>       <span class="kd">db</span> <span class="s">"Buzz"</span>
    <span class="nl">buzzLen:</span>    <span class="nf">equ</span> <span class="kc">$</span><span class="o">-</span><span class="nv">buzz</span>
    <span class="nl">nl:</span>         <span class="kd">db</span> <span class="mh">0x0A</span>
    <span class="nl">printNum:</span>   <span class="kd">db</span> <span class="s">"%d"</span><span class="p">,</span> <span class="mh">0x0</span>

<span class="nf">section</span> <span class="nv">.text</span>

<span class="nl">main:</span>
    <span class="c1">; Initialise loop variable to 1</span>
    <span class="c1">; Note that r12 is callee-saved</span>
    <span class="nf">mov</span>         <span class="nv">r12</span><span class="p">,</span> <span class="mi">1</span>

<span class="nl">condition:</span>
    <span class="c1">; Break if &gt; 100</span>
    <span class="nf">cmp</span>         <span class="nv">r12</span><span class="p">,</span> <span class="mi">100</span>
    <span class="nf">jg</span>          <span class="nv">exit</span>

    <span class="c1">; Call function</span>
    <span class="c1">; If register for loop variable is caller-saved,</span>
    <span class="c1">; need to save value before call</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="nv">r12</span>
    <span class="nf">call</span>        <span class="nv">fizzbuzz</span>

    <span class="c1">; Increment</span>
    <span class="nf">add</span>         <span class="nv">r12</span><span class="p">,</span> <span class="mi">1</span>
    <span class="c1">; Jump back to condition</span>
    <span class="nf">jmp</span>         <span class="nv">condition</span>

<span class="nl">exit:</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="mi">60</span>         <span class="c1">; exit</span>
    <span class="nf">xor</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="nb">rdi</span>        <span class="c1">; status code 0</span>
    <span class="nf">syscall</span>

<span class="c1">; FizzBuzz function</span>
<span class="nl">fizzbuzz:</span>
    <span class="c1">; copy rax</span>
    <span class="nf">mov</span>         <span class="nv">r11</span><span class="p">,</span> <span class="nb">rax</span>

    <span class="c1">; Divide by 3</span>
    <span class="nf">xor</span>         <span class="nb">rdx</span><span class="p">,</span> <span class="nb">rdx</span>
    <span class="nf">mov</span>         <span class="nv">r9</span><span class="p">,</span> <span class="mi">3</span>           <span class="c1">; divisor to r9</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="nv">r11</span>        <span class="c1">; dividend to rax</span>
    <span class="nf">idiv</span>        <span class="nv">r9</span>
    <span class="nf">mov</span>         <span class="nv">r9</span><span class="p">,</span> <span class="nb">rdx</span>         <span class="c1">; remainder to r9</span>

    <span class="c1">; Divide by 5</span>
    <span class="nf">xor</span>         <span class="nb">rdx</span><span class="p">,</span> <span class="nb">rdx</span>
    <span class="nf">mov</span>         <span class="nv">r10</span><span class="p">,</span> <span class="mi">5</span>          <span class="c1">; divisor to r10</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="nv">r11</span>        <span class="c1">; dividend to rax</span>
    <span class="nf">idiv</span>        <span class="nv">r10</span>
    <span class="nf">mov</span>         <span class="nv">r10</span><span class="p">,</span> <span class="nb">rdx</span>        <span class="c1">; remainder to r10</span>

    <span class="c1">; Flag to indicate no printing</span>
    <span class="nf">xor</span>         <span class="nv">r8</span><span class="p">,</span> <span class="nv">r8</span>

<span class="c1">; Divisible by 3?</span>
<span class="nl">div3:</span>
    <span class="nf">cmp</span>         <span class="nv">r9</span><span class="p">,</span> <span class="mi">0</span>
    <span class="nf">jne</span>         <span class="nb">di</span><span class="nv">v5</span>

    <span class="c1">; Print Fizz</span>
    <span class="nf">or</span>          <span class="nv">r8</span><span class="p">,</span> <span class="mi">1</span>           <span class="c1">; set flag</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; write</span>
    <span class="nf">mov</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; stdout</span>
    <span class="nf">mov</span>         <span class="nb">rsi</span><span class="p">,</span> <span class="nv">fizz</span>
    <span class="nf">mov</span>         <span class="nb">rdx</span><span class="p">,</span> <span class="nv">fizzLen</span>
    <span class="nf">syscall</span>

<span class="c1">; Divisible by 5?</span>
<span class="nl">div5:</span>
    <span class="nf">cmp</span>         <span class="nv">r10</span><span class="p">,</span> <span class="mi">0</span>
    <span class="nf">jne</span>         <span class="nv">printnum</span>

    <span class="c1">; Print Buzz</span>
    <span class="nf">or</span>          <span class="nv">r8</span><span class="p">,</span> <span class="mi">1</span>           <span class="c1">; set flag</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; write</span>
    <span class="nf">mov</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; stdout</span>
    <span class="nf">mov</span>         <span class="nb">rsi</span><span class="p">,</span> <span class="nv">buzz</span>
    <span class="nf">mov</span>         <span class="nb">rdx</span><span class="p">,</span> <span class="nv">buzzLen</span>
    <span class="nf">syscall</span>

<span class="nl">printnum:</span>
    <span class="c1">; If number is not multiple of 3/5</span>
    <span class="nf">cmp</span>         <span class="nv">r8</span><span class="p">,</span> <span class="mi">0</span>
    <span class="nf">jne</span>         <span class="nv">printline</span>

    <span class="c1">; call printf</span>
    <span class="nf">lea</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="p">[</span><span class="nv">rel</span> <span class="nv">printNum</span><span class="p">]</span>
    <span class="nf">mov</span>         <span class="nb">rsi</span><span class="p">,</span> <span class="nv">r11</span>
    <span class="nf">xor</span>         <span class="nb">eax</span><span class="p">,</span> <span class="nb">eax</span>
    <span class="nf">call</span>        <span class="p">[</span><span class="nv">rel</span> <span class="nv">printf</span> <span class="ow">wrt</span> <span class="nv">..got</span><span class="p">]</span>

    <span class="c1">; fflush</span>
    <span class="nf">xor</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="nb">rdi</span>
    <span class="nf">call</span>        <span class="p">[</span><span class="nv">rel</span> <span class="nv">fflush</span> <span class="ow">wrt</span> <span class="nv">..got</span><span class="p">]</span>

    <span class="c1">; Print '\n'</span>
<span class="nl">printline:</span>
    <span class="nf">mov</span>         <span class="nb">rax</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; write</span>
    <span class="nf">mov</span>         <span class="nb">rdi</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; stdout</span>
    <span class="nf">mov</span>         <span class="nb">rsi</span><span class="p">,</span> <span class="nv">nl</span>
    <span class="nf">mov</span>         <span class="nb">rdx</span><span class="p">,</span> <span class="mi">1</span>
    <span class="nf">syscall</span>

<span class="nf">ret</span></code></pre></figure>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[Compilation To compile using nasm:]]></summary></entry><entry><title type="html">No such interface org.freedesktop.portal.OpenURI on Arch Linux xfce</title><link href="https://steventang.net/blog/2023/openuri" rel="alternate" type="text/html" title="No such interface org.freedesktop.portal.OpenURI on Arch Linux xfce" /><published>2023-11-09T12:00:00+00:00</published><updated>2023-11-09T12:00:00+00:00</updated><id>https://steventang.net/blog/2023/openuri</id><content type="html" xml:base="https://steventang.net/blog/2023/openuri"><![CDATA[<p>A while ago, my <a href="https://gitlab.com/news-flash/news_flash_gtk" target="_blank" rel="noopener">NewsFlash</a> started to throw the following error when trying to open links:<br />
<code class="language-plaintext highlighter-rouge">Failed to open URL: ZBus Error: org.freedesktop.DBus.Error.UnknownMethod: No such interface "org.freedesktop.portal.OpenURI" on object at path /org/freedesktop/portal/desktop</code>.</p>

<p>While I think I’ve experienced a similar issue with opening links before,
I didn’t really want to take the ‘wait it out and hope for someone else to fix it’ approach this time.</p>

<p>Yesterday, I finally set out to identify the root cause and (hopefully) fix this issue.</p>

<p>Initially, I tried to compile the latest version of NewsFlash.<br />
Unfortunately, it had the same issue.</p>

<p>I then cloned NewsFlash to search for how the <code class="language-plaintext highlighter-rouge">Open Browser</code> menu item is handled,
tracing it to the following code:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">ashpd</span><span class="p">::</span><span class="nn">desktop</span><span class="p">::</span><span class="nn">open_uri</span><span class="p">::</span><span class="n">OpenFileRequest</span><span class="p">;</span>
<span class="o">...</span>

<span class="k">let</span> <span class="n">ctx</span> <span class="o">=</span> <span class="nn">glib</span><span class="p">::</span><span class="nn">MainContext</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
<span class="n">ctx</span><span class="nf">.spawn_local</span><span class="p">(</span><span class="k">async</span> <span class="k">move</span> <span class="p">{</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Err</span><span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="o">=</span> <span class="nn">OpenFileRequest</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.ask</span><span class="p">(</span><span class="n">ask</span><span class="p">)</span><span class="nf">.send_uri</span><span class="p">(</span><span class="o">&amp;</span><span class="n">url</span><span class="p">)</span><span class="k">.await</span> <span class="p">{</span>
        <span class="nn">App</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.in_app_notifiaction</span><span class="p">(</span><span class="o">&amp;</span><span class="nf">i18n_f</span><span class="p">(</span><span class="s">"Failed read URL: {}"</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">[</span><span class="o">&amp;</span><span class="n">error</span><span class="nf">.to_string</span><span class="p">()]));</span>
    <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>I tried to reproduce this usage of <code class="language-plaintext highlighter-rouge">ashpd</code> for opening links externally:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">ashpd</span><span class="p">::{</span><span class="nn">desktop</span><span class="p">::</span><span class="nn">open_uri</span><span class="p">::</span><span class="n">OpenFileRequest</span><span class="p">,</span> <span class="nn">url</span><span class="p">::</span><span class="n">Url</span><span class="p">};</span>

<span class="nd">#[tokio::main]</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">url</span> <span class="o">=</span> <span class="s">"https://example.com"</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">url</span> <span class="o">=</span> <span class="nn">Url</span><span class="p">::</span><span class="nf">parse</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Err</span><span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="o">=</span> <span class="nn">OpenFileRequest</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.ask</span><span class="p">(</span><span class="k">true</span><span class="p">)</span><span class="nf">.send_uri</span><span class="p">(</span><span class="o">&amp;</span><span class="n">url</span><span class="p">)</span><span class="k">.await</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Unfortunately (or fortunately), it produced the same error.<br />
This meant that it’s not a bug in <code class="language-plaintext highlighter-rouge">NewsFlash</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">Zbus</span><span class="p">(</span><span class="nf">MethodError</span><span class="p">(</span><span class="nf">OwnedErrorName</span><span class="p">(</span><span class="nf">ErrorName</span><span class="p">(</span><span class="nf">Str</span><span class="p">(</span><span class="nf">Owned</span><span class="p">(</span><span class="s">"org.freedesktop.DBus.Error.UnknownMethod"</span><span class="p">)))),</span> <span class="nf">Some</span><span class="p">(</span><span class="s">"No such interface “org.freedesktop.portal.OpenURI” on object at path /org/freedesktop/portal/desktop"</span><span class="p">),</span> <span class="n">Msg</span> <span class="p">{</span> <span class="k">type</span><span class="p">:</span> <span class="n">Error</span><span class="p">,</span> <span class="n">sender</span><span class="p">:</span> <span class="nf">UniqueName</span><span class="p">(</span><span class="nf">Str</span><span class="p">(</span><span class="nf">Borrowed</span><span class="p">(</span><span class="s">":1.44"</span><span class="p">))),</span> <span class="n">reply</span><span class="o">-</span><span class="n">serial</span><span class="p">:</span> <span class="mi">15</span><span class="p">,</span> <span class="n">body</span><span class="p">:</span> <span class="nf">Signature</span><span class="p">(</span><span class="s">"s"</span><span class="p">)</span> <span class="p">}))</span>
</code></pre></div></div>

<p>So I cloned <code class="language-plaintext highlighter-rouge">ashpd</code>.
After struggling to produce a dev build of <code class="language-plaintext highlighter-rouge">ashpd-demo</code>, I eventually gave up and installed it using Flathub.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flatpak <span class="nb">install </span>com.belmoussaoui.ashpd.demo
flatpak run com.belmoussaoui.ashpd.demo
</code></pre></div></div>

<p>I clicked on the first couple of demos out of curiosity, and they all failed…</p>

<p>Unsurprisingly, the Open URI demo fails also.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR ashpd_demo::portals::desktop::open_uri: Failed to open URI: ZBus Error: org.freedesktop.DBus.Error.UnknownMethod: No such interface “org.freedesktop.portal.OpenURI” on object at path /org/freedesktop/portal/desktop
</code></pre></div></div>

<p>At this stage, I performed a search on the Issues of <code class="language-plaintext highlighter-rouge">ashpd</code> and found no relevant results.
A dead end…
Perhaps it’s not an issue with <code class="language-plaintext highlighter-rouge">ashpd</code> either?</p>

<p>I went back to searching and eventually found <a href="https://bbs.archlinux.org/viewtopic.php?id=289405" target="_blank" rel="noopener">a forum post</a>,
which links to a GitHub issue <a href="https://github.com/flatpak/xdg-desktop-portal/issues/1077" target="_blank" rel="noopener">flatpak/xdg-desktop-portal #1077 No more OpenURI when using XDG_CURRENT_DESKTOP=sway and gtk portal</a>.</p>

<p>This is great!</p>

<p>With some more trial and error, I settled on:</p>

<p><code class="language-plaintext highlighter-rouge">~/.config/xdg-desktop-portal/portals.conf</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[preferred]
default=*
</code></pre></div></div>

<p>From one of the linked PRs.</p>

<p>After restarting, the links are working again! (for now…)</p>

<p>Edit: Note that this requires the package <code class="language-plaintext highlighter-rouge">xdg-desktop-portal-gtk</code>.</p>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[A while ago, my NewsFlash started to throw the following error when trying to open links: Failed to open URL: ZBus Error: org.freedesktop.DBus.Error.UnknownMethod: No such interface "org.freedesktop.portal.OpenURI" on object at path /org/freedesktop/portal/desktop.]]></summary></entry><entry><title type="html">Windows 11 with kvm/virt-manager 3.2.0</title><link href="https://steventang.net/blog/2021/kvm-win11" rel="alternate" type="text/html" title="Windows 11 with kvm/virt-manager 3.2.0" /><published>2021-10-22T02:50:00+00:00</published><updated>2021-10-22T02:50:00+00:00</updated><id>https://steventang.net/blog/2021/kvm-win11</id><content type="html" xml:base="https://steventang.net/blog/2021/kvm-win11"><![CDATA[<p>Today, I encountered ‘This PC can’t run Windows 11’ while setting up a Windows 11 virtual machine with <code class="language-plaintext highlighter-rouge">virt-manager</code>.
Quite annoying when the installer doesn’t specify which requirements weren’t met…</p>

<p>Here are the steps which I used to bypass the error:</p>

<ol>
  <li>Install <code class="language-plaintext highlighter-rouge">edk2-ovmf</code> for UEFI and <code class="language-plaintext highlighter-rouge">swtpm</code> for Software TPM.</li>
  <li>Select Windows 11 ISO. <code class="language-plaintext highlighter-rouge">virt-manager</code> 3.2.0 still detects it as Windows 10.</li>
  <li>Ensure allocation of at least 4 GB of RAM, 2 CPU cores, and 64 GB of disk.</li>
  <li>Tick ‘Customize configuration before install’.</li>
  <li>Add Hardware, TPM, TIS, Emulated device, 2.0.</li>
  <li>Overview, Firmware to UEFI x86_64 with secure boot, Apply (important, or this will get reset!).</li>
  <li>Boot options, tick CDROM, increase priority (to top), Apply again.</li>
  <li>Begin installation.</li>
</ol>

<p>Possibly unnecessary:<br />
I encountered this strange problem where the screen was black after booting, so I changed video to VGA.<br />
After installation, I installed <code class="language-plaintext highlighter-rouge">spice-guest-tools</code> and changed it back to QXL for auto-resizing.</p>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[Today, I encountered ‘This PC can’t run Windows 11’ while setting up a Windows 11 virtual machine with virt-manager. Quite annoying when the installer doesn’t specify which requirements weren’t met…]]></summary></entry><entry><title type="html">TOTP algorithm: by example</title><link href="https://steventang.net/blog/2021/totp" rel="alternate" type="text/html" title="TOTP algorithm: by example" /><published>2021-09-29T23:00:00+00:00</published><updated>2021-09-29T23:00:00+00:00</updated><id>https://steventang.net/blog/2021/totp</id><content type="html" xml:base="https://steventang.net/blog/2021/totp"><![CDATA[<hr />

<p>Note: A version of this post was previously published as part of coursework
for the subject SCIE90012 (Science Communication).</p>

<hr />

<p>At some stage, you may have entered a 6-digit code from a 2FA app like
Google Authenticator. But where did this code come from?</p>

<p>The answer lies within the QR code and the inner workings of the Time-based
One-time Password algorithm (TOTP).</p>

<p>Here is an example of the algorithm in action.
<!-- I will attempt to show you an example of the algorithm in action
(or at least as much as I feasibly can). --></p>

<h2 id="the-preparations">The preparations</h2>
<p>To prepare, I performed a MFA reset on my university account.</p>

<p>This was the QR code:</p>

<p><img src="/assets/blog/scie90012/setup.png" alt="A 2FA QR from unimelb SSO" title="A 2FA QR from unimelb SSO" /></p>

<p>Then, I enrolled the QR code into my 2FA app and took the following screenshot on the 27th of September,
at 9:21:19 pm UTC+10 Australia/Melbourne time:</p>

<p><img src="/assets/blog/scie90012/2fa.png" alt="My 2FA app" title="My 2FA app" style="max-width: 270px" /></p>

<p>With this, we have all the information necessary to recompute and verify the code.</p>

<h2 id="decode-the-qr">Decode the QR</h2>

<p>First, we must decode and make sense of the QR.</p>

<!-- It is possible to do this [by hand](https://www.youtube.com/watch?v=KA8hDldvfv0){:target="_blank" rel="noopener"}. -->
<!-- However, as our QR is much denser (in terms of information) and it will likely be quite tedious, -->

<p>I’ve used a decoder to find the following URI:</p>

<p><code class="language-plaintext highlighter-rouge">otpauth://totp/sso.unimelb.edu.au:stevent2%40student.unimelb.edu.au?secret=XE7ZREYZTLXYK444&amp;issuer=sso.unimelb.edu.au</code></p>

<p>From the URI, we see that the secret is <code class="language-plaintext highlighter-rouge">XE7ZREYZTLXYK444</code>.</p>

<h2 id="the-time">The time?</h2>
<p>Time undoubtedly plays an important role in the <strong>T</strong>OTP algorithm, which is defined as
<code class="language-plaintext highlighter-rouge">TOTP = HOTP(K, T)</code>, where <code class="language-plaintext highlighter-rouge">K</code> is the secret key and
<code class="language-plaintext highlighter-rouge">T = (Current Unix Time - T0) / X</code>,
with floor division being applied.</p>

<p>Current Unix Time is an integer counting the number of seconds (excluding
leap seconds) since the unix epoch (January 1st, 1970).
The date above converts to <code class="language-plaintext highlighter-rouge">1632741679</code>
(18897 days, (21 - 10) hours, 21 minutes, 19 seconds).</p>

<p><code class="language-plaintext highlighter-rouge">T0</code> is the unix time from which counting starts and <code class="language-plaintext highlighter-rouge">X</code> is the time step in seconds, which controls how often the code changes.
By default, T0 is the unix epoch (the value 0) and <code class="language-plaintext highlighter-rouge">X</code> is 30 seconds (if not specified).</p>

<p>This, along with the use of floor division means that <code class="language-plaintext highlighter-rouge">T</code> will stay the same across every 30 second period for this example.</p>

<p>Substituting in these values, we get the counter value <code class="language-plaintext highlighter-rouge">T = (1632741679 - 0) / 30 = 54424722</code>.</p>

<h2 id="hotp">HOTP</h2>
<p>From before, we have <code class="language-plaintext highlighter-rouge">K = XE7ZREYZTLXYK444</code> and <code class="language-plaintext highlighter-rouge">T = 54424722</code>.
By default, HOTP uses HMAC-SHA1. Taking <code class="language-plaintext highlighter-rouge">K</code> and <code class="language-plaintext highlighter-rouge">T</code>, <code class="language-plaintext highlighter-rouge">HMAC-SHA1(K, T)</code>
produces an irreversible fixed-size value (a hash) from the input.</p>

<h3 id="convert-the-secret">Convert the secret</h3>
<p>The secret is in Base32, which uses the digits A-Z and 2-7 to represent the
values 0-25 to 26-31 respectively.</p>

<ol>
  <li>Convert from Base32 digits to decimal (e.g. using a lookup table).
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>X  E  7  Z  R  E  Y  Z  T  L  X  Y  K  4  4  4
23 04 31 25 17 04 24 25 19 11 23 24 10 28 28 28
</code></pre></div>    </div>
  </li>
  <li>Convert decimal to binary (each will be less or equal to 5 bits, because
values 0-31 are represented).
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>10111 00100 11111 11001 10001 00100 11000 11001
10011 01011 10111 11000 01010 11100 11100 11100
</code></pre></div>    </div>
  </li>
  <li>Rewrite this into groups of 4 bits, and convert binary to hexadecimal
using the trick that 4 binary digits is one hexadecimal digit.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1011 1001 0011 1111 1001 1000 1001 0011 0001 1001
B    9    3    F    9    8    9    3    1    9
1001 1010 1110 1111 1000 0101 0111 0011 1001 1100
9    A    E    F    8    5    7    3    9    C
</code></pre></div>    </div>
  </li>
</ol>

<h3 id="convert-the-counter-value">Convert the counter value</h3>
<ol>
  <li>Convert to binary and then hexadecimal.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>54424722 =
0011 0011 1110 0111 0100 1001 0010
3    3    e    7    4    9    2
</code></pre></div>    </div>
  </li>
  <li>Pad this number to 8 bytes (16 hexadecimal digits) by adding leading
zeros, per the RFC.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000033e7492
</code></pre></div>    </div>
  </li>
</ol>

<h3 id="hmac--truncation">HMAC &amp; Truncation</h3>

<p>With the converted values, we can calculate the <code class="language-plaintext highlighter-rouge">SHA1-HMAC</code>.</p>

<p>The values from above produces the following hash:
<code class="language-plaintext highlighter-rouge">38056c8282a236a5ad624ff292b984d20138aba8</code>.</p>

<p>Next, we follow a dynamic truncation algorithm to extract a section of the
hash:</p>

<ol>
  <li>
    <p>Given the 20 byte SHA1 hash String, extract the “low-order 4 bits of <code class="language-plaintext highlighter-rouge">String[19]</code>” to use as Offset.<br />
When splitting the hash into 20 segments, <code class="language-plaintext highlighter-rouge">String[19]</code> is the last segment
(since we start from index 0). This is <code class="language-plaintext highlighter-rouge">a8</code>.<br />
Then, because one hexadecimal digit represent 4 binary bits, and the
least significant digit is the rightmost digit, it is <code class="language-plaintext highlighter-rouge">8</code> in this example.</p>
  </li>
  <li>
    <p>Convert the 4 bits into a number.<br />
8 in hexadecimal is the same in decimal (base 10), and so we move on.</p>
  </li>
  <li>
    <p>Return the last 31 bits of <code class="language-plaintext highlighter-rouge">String[Offset]...String[Offset+3]</code>.<br />
Substituting in 8, this becomes <code class="language-plaintext highlighter-rouge">String[8]...String[11]</code>.</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>38 05 6c 82 82 a2 36 a5 ad 62 4f f2 92 b9 84 d2 01 38 ab a8
0  1  2  3  4  5  6  7  ^  ^  ^  ^  12 13 14 15 16 17 18 19
</code></pre></div>    </div>

    <p>We can then convert this into binary, by replacing each hexadecimal
 digit with its 4-digit binary equivalent.</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a    d    6    2    4    f    f    2
1010 1101 0110 0010 0100 1111 1111 0010
</code></pre></div>    </div>

    <p>Since it’s the last 31 bits, we unset the first bit.</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0010 1101 0110 0010 0100 1111 1111 0010
</code></pre></div>    </div>
  </li>
</ol>

<p>The next step involves converting this 31-bit binary number into decimal.</p>

<p>2<sup>29</sup> + 2<sup>27</sup> + 2<sup>26</sup> + 2<sup>24</sup> + 2<sup>22</sup> + 2<sup>21</sup> + 2<sup>17</sup> + 2<sup>14</sup> + 2<sup>11</sup> + 2<sup>10</sup> + 2<sup>9</sup> + 2<sup>8</sup> + 2<sup>7</sup> + 2<sup>6</sup> + 2<sup>5</sup> + 2<sup>4</sup> + 2<sup>1</sup> = 761417714</p>

<p>When not specified in the URI, 6-digit codes are used by default.
Hence, we can (in the final step) write down the last six digits of the number.</p>

<p>And voila, <code class="language-plaintext highlighter-rouge">417714</code>!</p>

<h2 id="references">References</h2>
<ul>
  <li><a href="https://datatracker.ietf.org/doc/html/rfc4226" target="_blank" rel="noopener">RFC 4226: HOTP</a></li>
  <li><a href="https://datatracker.ietf.org/doc/html/rfc6238" target="_blank" rel="noopener">RFC 6238: TOTP</a></li>
  <li><a href="https://github.com/google/google-authenticator/wiki/Key-Uri-Format" target="_blank" rel="noopener">Key URI format</a></li>
</ul>

<p>Verified with:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oathtool <span class="nt">--totp</span> <span class="nt">--verbose</span> <span class="nt">--now</span> <span class="s2">"2021-09-27 11:21:19 UTC"</span> <span class="nt">--base32</span> XE7ZREYZTLXYK444
Hex secret: b93f9893199aef85739c
Base32 secret: XE7ZREYZTLXYK444
Digits: 6
Window size: 0
TOTP mode: SHA1
Step size <span class="o">(</span>seconds<span class="o">)</span>: 30
Start <span class="nb">time</span>: 1970-01-01 00:00:00 UTC <span class="o">(</span>0<span class="o">)</span>
Current <span class="nb">time</span>: 2021-09-27 11:21:19 UTC <span class="o">(</span>1632741679<span class="o">)</span>
Counter: 0x33E7492 <span class="o">(</span>54424722<span class="o">)</span>

417714
</code></pre></div></div>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Building Android on self-hosted Linux GitHub Actions runners</title><link href="https://steventang.net/blog/2021/github-android" rel="alternate" type="text/html" title="Building Android on self-hosted Linux GitHub Actions runners" /><published>2021-07-07T06:35:00+00:00</published><updated>2021-07-07T06:35:00+00:00</updated><id>https://steventang.net/blog/2021/github-android</id><content type="html" xml:base="https://steventang.net/blog/2021/github-android"><![CDATA[<p>Note that this guide was last updated on 07/07/21.</p>

<p>Last year, I had some trouble setting up Android builds on self-hosted GitHub
Actions runners for <a href="https://github.com/comp90018-2020/smart-broccoli" target="_blank" rel="noopener">smart-brocolli</a>.
This post serves to document this process.</p>

<ol>
  <li>Provision a Linux VM. I will be using Ubuntu 20.04 on Azure.</li>
  <li>Install Flutter SDK.<br />
<a href="https://flutter.dev/docs/get-started/install/linux#install-flutter-manually" target="_blank" rel="noopener">https://flutter.dev/docs/get-started/install/linux#install-flutter-manually</a></li>
  <li>Add flutter to <code class="language-plaintext highlighter-rouge">$PATH</code> and run <code class="language-plaintext highlighter-rouge">$ flutter doctor</code>.</li>
  <li>Download Android command-line tools.<br />
<a href="https://developer.android.com/studio#command-tools" target="_blank" rel="noopener">https://developer.android.com/studio#command-tools</a></li>
  <li>Extract tools:
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>unzip <span class="o">&amp;&amp;</span> unzip commandlinetools-linux-<span class="k">*</span>.zip
</code></pre></div>    </div>
  </li>
  <li>Set up Android directory:
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">mkdir </span>android-sdk <span class="o">&amp;&amp;</span> <span class="nb">mkdir </span>android-sdk/cmdline-tools <span class="o">&amp;&amp;</span> <span class="se">\</span>
 <span class="nb">mv </span>cmdline-tools/ android-sdk/cmdline-tools/latest
</code></pre></div>    </div>
  </li>
  <li>Add <code class="language-plaintext highlighter-rouge">android-sdk/cmdline-tools/latest/bin/</code> to <code class="language-plaintext highlighter-rouge">$PATH</code>, set <code class="language-plaintext highlighter-rouge">$ANDROID_HOME</code>.<br />
e.g.
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">ANDROID_HOME</span><span class="o">=</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>/android-sdk
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">:</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span><span class="s2">/flutter/bin:</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span><span class="s2">/android-sdk/cmdline-tools/latest/bin"</span>
</code></pre></div>    </div>
  </li>
  <li>Install Java:
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>openjdk-8-jdk
</code></pre></div>    </div>
  </li>
  <li>Install Platform SDK and build-tools using <code class="language-plaintext highlighter-rouge">sdkmanager</code>, e.g.: 
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>sdkmanager <span class="nt">--install</span> <span class="s2">"platforms;android-30"</span>
<span class="nv">$ </span>sdkmanager <span class="nt">--install</span> <span class="s2">"build-tools;30.0.2"</span>
</code></pre></div>    </div>
  </li>
  <li>Review licenses:
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>flutter doctor <span class="nt">--android-licenses</span>
</code></pre></div>    </div>
  </li>
  <li>Run <code class="language-plaintext highlighter-rouge">$ flutter doctor</code> again.<br />
Ensure that a tick is given for Android Toolchain.</li>
  <li>Finally, add an appropriate GitHub Actions workflow.</li>
</ol>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[Note that this guide was last updated on 07/07/21.]]></summary></entry><entry><title type="html">OSS 2021</title><link href="https://steventang.net/blog/2021/oss" rel="alternate" type="text/html" title="OSS 2021" /><published>2021-07-03T04:45:00+00:00</published><updated>2021-07-03T04:45:00+00:00</updated><id>https://steventang.net/blog/2021/oss</id><content type="html" xml:base="https://steventang.net/blog/2021/oss"><![CDATA[<p>An ongoing post about my OSS contributions in 2021.</p>

<h3 id="mongoose">mongoose</h3>
<ul>
  <li>PR <a href="https://github.com/Automattic/mongoose/pull/9760" target="_blank" rel="noopener noreferrer">#9760</a>
  is a followup PR to #9688. It configures Actions to trigger on pull requests and changes the README badge from travis to Actions.</li>
  <li>PR <a href="https://github.com/Automattic/mongoose/pull/10393" target="_blank" rel="noopener noreferrer">#10393</a>
  replaces <code class="language-plaintext highlighter-rouge">mongodb-topology-manager</code> with an adapted implementation (for future CI upgrades).</li>
  <li>PR <a href="https://github.com/Automattic/mongoose/pull/10679" target="_blank" rel="noopener noreferrer">#10679</a>
  adds test for latest Ubuntu LTS alongside pre-existing older version through <code class="language-plaintext highlighter-rouge">strategy.matrix</code>.</li>
</ul>

<h3 id="wakapi">wakapi</h3>
<ul>
  <li>PR <a href="https://github.com/muety/wakapi/pull/85" target="_blank" rel="noopener noreferrer">#85</a>
  reduces minimum username length to 1.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/86" target="_blank" rel="noopener noreferrer">#86</a>
  reduces docker image layers by moving some steps to the build stage.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/95" target="_blank" rel="noopener noreferrer">#95</a>
  allows static assets to be embedded using <code class="language-plaintext highlighter-rouge">pkger</code>.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/97" target="_blank" rel="noopener noreferrer">#97</a>
  is a followup PR to #95, and fixes CI releases.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/107" target="_blank" rel="noopener noreferrer">#107</a>
  removes legacy configuration code.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/110" target="_blank" rel="noopener noreferrer">#111</a>
  adds GitHub Action to publish docker image to Docker Hub on release.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/130" target="_blank" rel="noopener noreferrer">#130</a>
  is a simple fix for a migration which fails for sqlite.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/167" target="_blank" rel="noopener noreferrer">#167</a>
  switches from <code class="language-plaintext highlighter-rouge">pkger</code> to golang’s <code class="language-plaintext highlighter-rouge">embed</code>.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/217" target="_blank" rel="noopener noreferrer">#217</a>
  adds Alpine image.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/220" target="_blank" rel="noopener noreferrer">#220</a>
  adds removal of unix socket on start.</li>
  <li>PR <a href="https://github.com/muety/wakapi/pull/233" target="_blank" rel="noopener noreferrer">#233</a>
  fixes some javascript errors caused by conditional display of summary page (has data vs. no data).</li>
</ul>

<p>And <a href="https://github.com/muety/wakapi/pulls?q=is%3Apr+is%3Aclosed+author%3AYC">more</a>…</p>

<h3 id="misc">Misc</h3>
<ul>
  <li>PR <a href="https://github.com/cesanta/docker_auth/pull/311" target="_blank" rel="noopener noreferrer">#311</a> <code class="language-plaintext highlighter-rouge">docker_auth</code>
  fixes arm64 Docker builds.</li>
  <li>PR <a href="https://github.com/AURIN/comp90024/pull/6" target="_blank" rel="noopener noreferrer">#6</a> <code class="language-plaintext highlighter-rouge">comp90024</code>
  fixes a dead link and some typos.</li>
  <li>PR <a href="https://github.com/katmail-xyz/mail.katmail.xyz/pull/1" target="_blank" rel="noopener noreferrer">#1</a> <code class="language-plaintext highlighter-rouge">mail.katmail.xyz</code>
  fixes TLSA script typos.</li>
  <li>MR <a href="https://gitlab.com/news-flash/news_flash_gtk/-/merge_requests/59" target="_blank" rel="noopener noreferrer">#59</a> <code class="language-plaintext highlighter-rouge">newsflash-gtk</code>
  fixes a meson build issue.</li>
</ul>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[An ongoing post about my OSS contributions in 2021.]]></summary></entry><entry><title type="html">OSS 2020</title><link href="https://steventang.net/blog/2020/oss" rel="alternate" type="text/html" title="OSS 2020" /><published>2020-12-27T10:50:00+00:00</published><updated>2020-12-27T10:50:00+00:00</updated><id>https://steventang.net/blog/2020/oss</id><content type="html" xml:base="https://steventang.net/blog/2020/oss"><![CDATA[<p>As 2020 comes to an end, I thought that it might be interesting to document
and comment on the OSS contributions which I made in the past year.</p>

<h3 id="mongodb--mongoose">MongoDB &amp; mongoose</h3>
<ul>
  <li>PR <a href="https://github.com/vkarpov15/mongo-sanitize/pull/6">#6</a> <code class="language-plaintext highlighter-rouge">mongo-sanitize</code> adds recursive object sanitisation,
  to mitigate mongoose/MongoDB injection attacks (which can occur on e.g. unsanitised JSON input).</li>
  <li>Issue <a href="https://github.com/Automattic/mongoose/issues/8521" target="_blank" rel="noopener noreferrer">#8521</a> <code class="language-plaintext highlighter-rouge">mongoose</code>
  was created after I found that calling <code class="language-plaintext highlighter-rouge">syncIndex()</code> after updating the collation index for a model (for case-insensitive username matching)
  had no effect.</li>
  <li>PR <a href="https://github.com/Automattic/mongoose/pull/9688" target="_blank" rel="noopener noreferrer">#9688</a> <code class="language-plaintext highlighter-rouge">mongoose</code>
  adds GitHub Actions CI configuration (after travis.org announcement).
  This was somewhat challenging due to environment (os, node, and dependency) differences and timing issues which exists in the test suite.
  In hindsight, it probably would have been easier if I tested it using Docker first.</li>
  <li>PR <a href="https://github.com/Automattic/mongoose/pull/9696" target="_blank" rel="noopener noreferrer">#9696</a> <code class="language-plaintext highlighter-rouge">mongoose</code>
   adds single document populate to the new internal type definitions.</li>
  <li>PR <a href="https://github.com/Automattic/mongoose/pull/9705" target="_blank" rel="noopener noreferrer">#9705</a> <code class="language-plaintext highlighter-rouge">mongoose</code>
   is a small CSS responsive design fix for documentation.</li>
  <li>PR <a href="https://github.com/jdesboeufs/connect-mongo/pull/374" target="_blank" rel="noopener noreferrer">#374</a> <code class="language-plaintext highlighter-rouge">connect-mongo</code> (not yet merged) fixes type definitions errors caused by upstream type definition updates.</li>
</ul>

<h3 id="couchdb">CouchDB</h3>
<p>For <a href="https://github.com/pancak3/City-Analytics-On-the-Cloud" target="_blank" rel="noopener noreferrer">project 2</a>
of <a href="https://handbook.unimelb.edu.au/2020/subjects/comp90024" target="_blank" rel="noopener noreferrer">COMP90024</a>,
we were required to use CouchDB and its MapReduce functions to analyze harvested Twitter data.
One of the tasks is to use <code class="language-plaintext highlighter-rouge">ansible</code> to automate the deployment of fresh nodes.
Though Docker was optional, I decided to use it to reduce complexity in making changes.
During this process, I ran into several non-obvious details and issues.</p>

<ul>
  <li>PR <a href="https://github.com/apache/couchdb-docker/pull/185" target="_blank" rel="noopener noreferrer">#185</a> <code class="language-plaintext highlighter-rouge">couchdb-docker</code>
  adds documentation about the default ports which are exposed by <a href="https://github.com/apache/couchdb-docker/blob/main/3.1.1/vm.args">vm.args</a>
  and the Dockerfile.</li>
  <li>PR <a href="https://github.com/apache/couchdb-docker/pull/186" target="_blank" rel="noopener noreferrer">#186</a> <code class="language-plaintext highlighter-rouge">couchdb-docker</code> (not merged)
  adds a sample docker compose file.</li>
  <li>
    <p>PR <a href="https://github.com/apache/couchdb/pull/3036" target="_blank" rel="noopener noreferrer">#3036</a> <code class="language-plaintext highlighter-rouge">couchdb</code> fixes an issue which causes
  the <code class="language-plaintext highlighter-rouge">finish_cluster</code> API call to fail when executed on a fresh CouchDB node with no UUID.
  I helped by describing and reproducing the issue (using scripts and 2 networked VMs) and submitting the final PR.</p>
  </li>
  <li>PR <a href="https://github.com/apache/couchdb-nano/pull/225" target="_blank" rel="noopener noreferrer">#225</a> <code class="language-plaintext highlighter-rouge">couchdb-nano</code>
  fixes typescript definitions for generic user-defined requests (<code class="language-plaintext highlighter-rouge">request</code>, <code class="language-plaintext highlighter-rouge">relax</code>, <code class="language-plaintext highlighter-rouge">dinosaur</code> functions).
  I discovered this issue when attempting to
  <a href="https://github.com/pancak3/City-Analytics-On-the-Cloud/blob/master/app/backend/info.ts#L10" target="_blank" rel="noopener noreferrer">use</a>
  the <code class="language-plaintext highlighter-rouge">request</code> function to get metadata about the instance using the <code class="language-plaintext highlighter-rouge">/</code> route.</li>
  <li>PR <a href="https://github.com/apache/couchdb-nano/pull/226" target="_blank" rel="noopener noreferrer">#226</a> <code class="language-plaintext highlighter-rouge">couchdb-nano</code>
  adds <code class="language-plaintext highlighter-rouge">nano.info()</code>, a method specifically used to retrieve the metadata.
  This PR came about because I was expecting a method to access the <code class="language-plaintext highlighter-rouge">/</code> route, but didn’t find any.
  As I didn’t have enough time before the project was due,
  I used <code class="language-plaintext highlighter-rouge">nano.request()</code> for the project and submitted this PR during the winter break.</li>
</ul>

<h3 id="tokei">tokei</h3>
<ul>
  <li>PR <a href="https://github.com/XAMPPRocky/tokei/pull/576" target="_blank" rel="noopener noreferrer">#576</a> (my first Rust PR!)
  was a small fix to remove a duplicated occurrence of <code class="language-plaintext highlighter-rouge">json</code> in <code class="language-plaintext highlighter-rouge">tokei --help</code>.
  I came across the attached issue by chance and thought that it might be a fun problem to tackle.</li>
  <li>PR <a href="https://github.com/XAMPPRocky/tokei/pull/580" target="_blank" rel="noopener noreferrer">#580</a>
  adds summary information (i.e. the Total) to output formats.
  Some minor refactoring was done to accommodate this change.</li>
</ul>

<h3 id="misc-self-hosted-web-apps">Misc self-hosted web apps</h3>
<ul>
  <li>PR <a href="https://github.com/muety/wakapi/pull/35" target="_blank" rel="noopener noreferrer">#35</a> <code class="language-plaintext highlighter-rouge">wakapi</code>
  adds <code class="language-plaintext highlighter-rouge">BASE_PATH</code> as an environment variable.</li>
  <li>PR <a href="https://github.com/zadam/trilium/pull/1495" target="_blank" rel="noopener noreferrer">#1495</a> <code class="language-plaintext highlighter-rouge">trilium</code>
  was a small change to rename the session cookie from the default.
  This PR was inspired by a clash of <code class="language-plaintext highlighter-rouge">express-session</code> cookies which occurred on one of my subdomains.</li>
  <li>PR <a href="https://github.com/andreimarcu/linx-server/pull/252" target="_blank" rel="noopener noreferrer">#252</a> <code class="language-plaintext highlighter-rouge">linx-server</code>
  removes <code class="language-plaintext highlighter-rouge">entrypoint</code> from the sample <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> file suggested by the documentation.
  This prevents the default <code class="language-plaintext highlighter-rouge">ENTRYPOINT</code> from the <code class="language-plaintext highlighter-rouge">Dockerfile</code> from being overridden.</li>
</ul>

<h3 id="rose-compiler">Rose compiler</h3>
<ul>
  <li>Issue <a href="https://github.com/rose-compiler/rose/issues/141" target="_blank" rel="noopener noreferrer">#141</a>
  reports a compilation error.</li>
</ul>

<h2 id="summary">Summary</h2>
<p>2020 is the first year where I have started to look at and dive more deeply into the code of
the software and packages that I use.
Though I have mostly focused on small bugs and minor issues, I have found the process to be highly rewarding
and something worthwhile to repeat for the future.</p>

<p>Though I doubt that many people (if there are any at all) read my blog, I wish you all the best for 2021!</p>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[As 2020 comes to an end, I thought that it might be interesting to document and comment on the OSS contributions which I made in the past year.]]></summary></entry><entry><title type="html">Migrating VMs</title><link href="https://steventang.net/blog/2020/vm-migration" rel="alternate" type="text/html" title="Migrating VMs" /><published>2020-06-20T10:00:00+00:00</published><updated>2020-06-20T10:00:00+00:00</updated><id>https://steventang.net/blog/2020/vm-migration</id><content type="html" xml:base="https://steventang.net/blog/2020/vm-migration"><![CDATA[<!-- This is a brief and informal post on the steps that I took yesterday to migrate
from an 18.04 to 20.04 VM on Oracle Cloud. -->

<p>After finishing my exams, I started to look at the miscellaneous tasks which were on my todo list.
While messing around on my VM, I thought that it was probably a good time to upgrade to Ubuntu 20.04.</p>

<p>I first checked <code class="language-plaintext highlighter-rouge">do-release-upgrade</code>.
For some reason, it listed <code class="language-plaintext highlighter-rouge">eoan</code> instead of <code class="language-plaintext highlighter-rouge">focal</code> as the release to upgrade to.
Because I didn’t have many services running on the VM (only Gitea and ZNC), I
thought that it wouldn’t be a bad idea to start fresh on a fresh VM.</p>

<p>After stopping all the running services,
I moved all the files which weren’t on the attached block storage volume
to the volume (misc files in <code class="language-plaintext highlighter-rouge">/home</code> like <code class="language-plaintext highlighter-rouge">/home/ubuntu/.znc</code>,
configs from <code class="language-plaintext highlighter-rouge">/etc/nginx</code>, letsencrypt certificates from <code class="language-plaintext highlighter-rouge">/etc/letsencrypt</code> etc).</p>

<p>Then, I swapped this volume from the old machine to the new one, and started
installing <code class="language-plaintext highlighter-rouge">docker</code> and <code class="language-plaintext highlighter-rouge">docker-compose</code>. This went quite smoothly now
that the <code class="language-plaintext highlighter-rouge">focal</code> source respository for <code class="language-plaintext highlighter-rouge">docker</code> is present.
After that, I disabled the <code class="language-plaintext highlighter-rouge">snap</code> and Oracle related services which were on the
machine.</p>

<p>Because my domains pointed to the IP address of the original VM,
I reassigned the reserved IP of the old VM and to the new one.
This turned out to be a good decision, as it became possible to ProxyJump from
the new machine to the old machine using its internal IP address, and I used
this to double check some configuration files (e.g. <code class="language-plaintext highlighter-rouge">iptables</code>).</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">ssh <span class="nt">-J</span> vm ubuntu@10.0.0.7</code></pre></figure>

<p>Then, it was just a matter of reconfiguring <code class="language-plaintext highlighter-rouge">ZNC</code>, <code class="language-plaintext highlighter-rouge">gitea</code> and the other
relevant pieces (e.g. <code class="language-plaintext highlighter-rouge">nginx</code>, <code class="language-plaintext highlighter-rouge">iptables</code>, <code class="language-plaintext highlighter-rouge">sshd_config</code>, <code class="language-plaintext highlighter-rouge">fail2ban</code>).</p>

<h3 id="containerising-znc">Containerising ZNC</h3>
<p>I use <code class="language-plaintext highlighter-rouge">znc</code> as an IRC bouncer for <code class="language-plaintext highlighter-rouge">freenode</code>.
Because I configured the old instance in a rush,
I didn’t bother investigating whether it had a docker image or not
and just let it run on the machine.
This time round, I thought that it would be a good idea to run it as a
container, if only to make the migration process easier the next time.</p>

<p>I referred to the reference for the official <a href="https://hub.docker.com/_/znc" target="_blank" rel="noopener">docker image</a>
and the compose file from <a href="https://hub.docker.com/r/linuxserver/znc/" target="_blank" rel="noopener">linuxserver</a>
and adapted it into the following docker-compose file.
Note that 6667 is the port which I originally chose.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nt">---</span>
version: <span class="s2">"2.1"</span>
services:
  znc:
    image: znc:latest
    container_name: znc
    environment:
      - <span class="nv">PUID</span><span class="o">=</span>1000
      - <span class="nv">PGID</span><span class="o">=</span>1000
      - <span class="nv">TZ</span><span class="o">=</span>Australia/Melbourne
    volumes:
      - ./data:/znc-data
    ports:
      - 6667:6667
    restart: unless-stopped</code></pre></figure>

<p>I renamed the original <code class="language-plaintext highlighter-rouge">.znc</code> folder to <code class="language-plaintext highlighter-rouge">data</code> and ran <code class="language-plaintext highlighter-rouge">docker-compose start</code>,
immediately running into the following error:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Unable to locate pem file: /home/ubuntu/.znc/znc.pem</code></pre></figure>

<p>After playing around with the configuration files, I eventually realised that
<code class="language-plaintext highlighter-rouge">znc.conf</code> contains a reference to the original location of the <code class="language-plaintext highlighter-rouge">pem</code> file.
Therefore, I changed the corresponding line to reflect the new location, and
all was well.</p>

<h3 id="gitea-with-ssh-passthrough">Gitea with SSH passthrough</h3>
<p>I use <code class="language-plaintext highlighter-rouge">gitea</code> for hosting some of my personal git repositories.
Because it is desirable to use SSH to perform operations on remote repositories,
it becomes necessary to use SSH passthrough to pass SSH from host to container.</p>

<p>Though the <a href="https://docs.gitea.io/en-us/install-with-docker/" target="_blank" rel="noopener">guide</a> on
Gitea provides a step by step process, it isn’t trivial to set it up correctly.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Received disconnect from &lt;IP&gt; port 22:2: Too many authentication failures
Disconnected from &lt;IP&gt; port 22
fatal: Could not <span class="nb">read </span>from remote repository.

Please make sure you have the correct access rights
and the repository exists.</code></pre></figure>

<p>Here are some strategies for debugging such issues:</p>

<ul>
  <li>Get the SSH client to output some logs when performing operations on remote
repositories. e.g.</li>
</ul>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">GIT_SSH_COMMAND</span><span class="o">=</span><span class="s2">"ssh -v"</span> git clone &lt;url&gt;</code></pre></figure>

<p>When having many SSH keys, use a <code class="language-plaintext highlighter-rouge">~/.ssh/config</code> file to specify the keys that should be used.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Host git.domain
    HostName git.domain
    IdentityFile ~/.ssh/&lt;private_key&gt;
    User git</code></pre></figure>

<ul>
  <li>Check the SSH logs (e.g. <code class="language-plaintext highlighter-rouge">/var/log/auth.log</code>).
Often, you’ll find something intuitive, e.g.</li>
</ul>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Jun 20 09:58:31 sshd[13579]: message repeated 5 <span class="nb">times</span>: <span class="o">[</span> Authentication refused: bad ownership or modes <span class="k">for </span>directory /volume/gitea/gitea]</code></pre></figure>

<ul>
  <li>Change to the git user (e.g. <code class="language-plaintext highlighter-rouge">sudo su git</code>) and see if the <code class="language-plaintext highlighter-rouge">authorized_keys</code> file is accessible.</li>
</ul>]]></content><author><name>Steven Tang</name></author><summary type="html"><![CDATA[]]></summary></entry></feed>