Jon Williams2026-02-17T16:07:05+00:00https://jonwillia.msJon Williams[email protected]Cilanto Corn Black Bean Salad2024-04-09T00:00:00+00:00https://jonwillia.ms/2024/04/09/cilanto-corn-black-bean-salad
<p><span class="marginnote">
<img src="/assets/images/2024-04-09-cilanto-corn-black-bean-salad/salad.jpg" alt="salad" />
I <em>know</em> the photo doesn’t have the <em>tortilla strips</em>; deal with it.
</span></p>
<p>I promise I haven’t been hacked and turned into a recipe SEO blog: here’s a salad recipe that I designed in 2023. I’m not much of a chef, but this is a big hit at the beach or summer parties. I usually just wing the proportions and it turns out fine – it’s not rocket science. Don’t stress!</p>
<h2 id="ingredients">Ingredients</h2>
<ul>
<li>One or two big bundles of <em>cilantro</em>. This is your base green; you want it to approximate the volume of beans & corn (together). Don’t be fooled by parsley!</li>
<li>Some (4-6) <em>corn tortillas</em>. I like white corn tortillas, but yellow can work too I suppose.</li>
<li>4 <em>limes</em>. You may want more if you’re planning on keeping the salad in the fridge for a couple days.</li>
<li>2 <em>avocados</em> or more if you can afford it. I’ve left these out before; it was fine I guess.</li>
<li>A wheel of <em>cotija cheese</em>. You could get it ground, I suppose. Usuing a whole wheel might be overkill, I end up using half to two thirds depending on the volume of cilantro, beans and corn.</li>
<li><em>Dry black beans</em>. Canned beans work but it’s not the same without the <em>cumin</em>.</li>
<li><em>Greek yogurt</em>. I use Fage, the firmer texture is really superior.</li>
<li>A big ass <em>red onion</em>.</li>
<li>4 to 8 <em>jalapeños</em>. Depends on how spicy you like it.</li>
<li><em>Olive oil</em> or some other kind of oil for frying the tortillas.</li>
<li>A cup of <em>sugar</em>.</li>
<li>2 or 3 ears of <em>corn</em>. I’ve used frozen corn and it threw the whole taste off; beware.</li>
</ul>
<h3 id="spices">Spices</h3>
<ul>
<li><em>Cumin</em>.</li>
<li><em>Sea salt</em>.</li>
<li><em>Chili powder</em>; I like the dark chili powder.</li>
</ul>
<h3 id="optional-ingredients">Optional Ingredients</h3>
<ul>
<li><em>Radishes</em>, 6 to 12. These look nice in photos.</li>
</ul>
<h2 id="tortilla-strips">Tortilla strips</h2>
<ol>
<li>Cut the <em>tortillas</em> into strips. They should be about half the length of your pinky & a little wider.</li>
<li>Put the tortillas in a pan with enough <em>oil</em> to cover them. I always feel like I’m wasting oil but you want to fry them until they’re super crispy.</li>
<li>Fry them on medium high heat for a couple minutes. Some brown & black areas is ideal. You may need to do two batches.</li>
<li>Place the tortilla strips in a container or a paper bag. Toss with 1 tablespoon <em>chili powder</em> and one tablespoon <em>sea salt</em>.</li>
</ol>
<h2 id="beans">Beans</h2>
<ol>
<li>Search the internet for how to cook <em>black beans</em>. I usually just presoak them overnight and then throw them on for 90 minutes or so.</li>
<li>Throw a generous amount of <em>cumin</em> in. Maybe you don’t like cumin; do something else.</li>
</ol>
<h2 id="corn">Corn</h2>
<ol>
<li>Shuck the <em>corn</em>.</li>
<li>Start boiling some water. When it boils, throw the <em>sugar</em> in.</li>
<li>Boil for about 5 minutes.</li>
<li>Use a knife to cut the kernels off the cob.</li>
</ol>
<h2 id="assembly">Assembly</h2>
<ol>
<li>Rough cut the <em>cilantro</em>, throw into bowl.</li>
<li>Add the <em>beans</em> and <em>corn</em>.</li>
<li>Cut up the <em>jalapeños</em> and add them. Don’t touch your eyes.</li>
<li>Add the juice of 4 <em>limes</em>. This will help the salad keep in the fridge for a couple days.</li>
<li>Cut up the <em>red onion</em> and add it.</li>
<li>Toss a bit.</li>
<li>Add the <em>cotija cheese</em>.</li>
<li>(Optional) cut up the <em>radishes</em> and throw them in.</li>
<li>Toss more.</li>
</ol>
<h2 id="serving">Serving</h2>
<ol>
<li>Cut the <em>avocados</em> length wise into thin slivers.</li>
<li>Cut remaining <em>limes</em> into wedges.</li>
<li>Let your guests put <em>tortilla strips</em>, <em>avocado</em>, <em>lime juice</em>, <em>yogurt</em> and optional drizzle of olive oil on themselves.</li>
</ol>
Quick tools.go invocation2023-07-03T00:00:00+00:00https://jonwillia.ms/2023/07/03/tools.go
<p><a href="https://www.jvt.me/posts/2022/06/15/go-tools-dependency-management/">Many</a> <a href="https://www.alexedwards.net/blog/using-go-run-to-manage-tool-dependencies">places</a> (<a href="https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module">including the wiki</a>) <a href="https://marcofranssen.nl/manage-go-tools-via-go-modules">recommend</a> managing your CLI tool dependencies with a <code class="language-plaintext highlighter-rouge">tools.go</code> file.
I’ve found this helpful as well. My contribution to this is a bash/zsh function (<code class="language-plaintext highlighter-rouge">gt</code>) that allows a shorthand invocation of a vendored tool.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>gt <span class="o">{</span>
<span class="nv">arg</span><span class="o">=</span><span class="nv">$1</span><span class="p">;</span>
<span class="nb">shift </span>2> /dev/null
<span class="o">[[</span> <span class="nv">$?</span> <span class="o">==</span> 1 <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"Usage: gt tool"</span> <span class="o">&&</span> <span class="k">return </span>1
<span class="nv">root</span><span class="o">=</span><span class="si">$(</span><span class="nb">dirname</span> <span class="s2">"</span><span class="si">$(</span>go <span class="nb">env </span>GOMOD<span class="si">)</span><span class="s2">"</span><span class="si">)</span>
<span class="nb">cd</span> <span class="s2">"</span><span class="nv">$root</span><span class="s2">"</span> <span class="o">||</span> <span class="k">return </span>1
<span class="nv">cmd</span><span class="o">=</span><span class="si">$(</span>go list <span class="nt">-f</span> <span class="s1">'{{ join .Imports "\n" }}'</span> <span class="nt">-tags</span> tools tools.go | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s2">"</span><span class="se">\/</span><span class="nv">$arg</span><span class="se">\$</span><span class="s2">"</span> <span class="si">)</span>
<span class="k">if</span> <span class="o">[[</span> <span class="nv">$cmd</span> <span class="o">==</span> <span class="s2">""</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo </span>cmd <span class="se">\"</span><span class="s2">"</span><span class="nv">$arg</span><span class="s2">"</span><span class="se">\"</span> not <span class="k">in </span>tools.go
<span class="k">else
</span>go run <span class="s2">"</span><span class="nv">$cmd</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
<span class="k">fi</span><span class="p">;</span>
<span class="nb">cd</span> - 1> /dev/null <span class="o">||</span> <span class="k">return </span>1
<span class="o">}</span>
</code></pre></div></div>
<p>So now instead of invoking <code class="language-plaintext highlighter-rouge">go run github.com/bufbuild/buf/cmd/buf lint</code>, you can run <code class="language-plaintext highlighter-rouge">gt buf lint</code>.<label for="sn-caveat" class="margin-toggle sidenote-number"></label>
<input id="sn-caveat" class="margin-toggle" type="checkbox" />
<span class="sidenote">
This will only work within a Go project where a <code class="language-plaintext highlighter-rouge">tools.go</code> file exists.
</span></p>
<p>If you’re using zsh, here’s tab completion:</p>
<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>_gt <span class="o">{</span>
<span class="k">if</span> <span class="o">((</span>CURRENT <span class="o">==</span> 2<span class="o">))</span><span class="p">;</span> <span class="k">then
</span><span class="nv">root</span><span class="o">=</span><span class="si">$(</span><span class="nb">dirname</span> <span class="s2">"</span><span class="si">$(</span>go <span class="nb">env </span>GOMOD<span class="si">)</span><span class="s2">"</span><span class="si">)</span>
<span class="nb">cd</span> <span class="s2">"</span><span class="nv">$root</span><span class="s2">"</span> <span class="o">||</span> <span class="k">return </span>1
compadd <span class="si">$(</span>go list <span class="nt">-f</span> <span class="s1">'{{ join .Imports "\n" }}'</span> <span class="nt">-tags</span> tools tools.go | <span class="nb">sed</span> <span class="nt">-e</span> <span class="s2">"s#.*/##"</span> <span class="si">)</span>
<span class="nb">cd</span> - 1> /dev/null <span class="o">||</span> <span class="k">return </span>1
<span class="k">elif</span> <span class="o">((</span>CURRENT <span class="o">></span> 2<span class="o">))</span><span class="p">;</span> <span class="k">then
</span><span class="nb">shift </span>words
<span class="o">((</span>CURRENT--<span class="o">))</span>
_normal <span class="nt">-p</span> mycmd
<span class="k">fi</span>
<span class="o">}</span>
compdef _gt gt
</code></pre></div></div>
<p>If you haven’t seen <code class="language-plaintext highlighter-rouge">tools.go</code> before, the <strong>TL;DR</strong> is:</p>
<ol>
<li>Invoke cli utilities – specifically Go commands (<code class="language-plaintext highlighter-rouge">package main</code>) in other modules – via <code class="language-plaintext highlighter-rouge">go run</code>. For example <code class="language-plaintext highlighter-rouge">go run github.com/bufbuild/buf/cmd/buf lint</code>.</li>
<li>Reference the cli utilities in a stub <code class="language-plaintext highlighter-rouge">tools.go</code> file in the root of your projects.</li>
</ol>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">//go:build tools</span>
<span class="c">// +build tools</span>
<span class="k">package</span> <span class="n">foobar</span>
<span class="k">import</span> <span class="p">(</span>
<span class="n">_</span> <span class="s">"github.com/bufbuild/buf/cmd/buf"</span>
<span class="n">_</span> <span class="s">"github.com/golangci/golangci-lint/cmd/golangci-lint"</span>
<span class="n">_</span> <span class="s">"github.com/mfridman/tparse"</span>
<span class="n">_</span> <span class="s">"github.com/twitchtv/twirp/protoc-gen-twirp"</span>
<span class="n">_</span> <span class="s">"golang.org/x/vuln/cmd/govulncheck"</span>
<span class="n">_</span> <span class="s">"google.golang.org/protobuf/cmd/protoc-gen-go"</span>
<span class="p">)</span>
</code></pre></div></div>
<p>Your command-line utilities will be versioned like everything else in your project & will benefit from the ecosystem around that (Dependabot, <code class="language-plaintext highlighter-rouge">govulncheck</code>, etc.).</p>
Launch OpenBSD vmd Guests on Demand from SSH2023-03-20T00:00:00+00:00https://jonwillia.ms/2023/03/20/vmctl-ssh
<p>I was annoyed that <label for="sn-electron" class="margin-toggle sidenote-number">Electron apps</label>
<input id="sn-electron" class="margin-toggle" type="checkbox" />
<span class="sidenote">
Other people are annoyed that Signal doesn’t exist and <a href="https://icyphox.sh/blog/signal-vmm/">wrote helpful writeups</a>.
</span>
don’t run on my OpenBSD laptop so I decided to run them inside a virtual
machine. Unfortunately, this laptop is underpowered by 2023 standards
& persistent Linux virtual machines would be competing with all the other
memory <label for="sn-hogs" class="margin-toggle sidenote-number">hogs</label>.
<input id="sn-hogs" class="margin-toggle" type="checkbox" />
<span class="sidenote">
<small><a href="https://pkg.go.dev/golang.org/x/tools/gopls">gopls</a></small>
</span></p>
<p>I’d been kicking around the idea of using an ssh <code class="language-plaintext highlighter-rouge">ProxyCommand</code> to launch
transient EC2 instances connected to long-lived EBS volumes so I figured –
why not implement this for <code class="language-plaintext highlighter-rouge">vmd</code> hosts? I could forward Linux X11 apps to my
desktop & be able to use Signal<label for="sn-hogs2" class="margin-toggle sidenote-number"></label>.
<input id="sn-hogs2" class="margin-toggle" type="checkbox" />
<span class="sidenote">
But not Visual Studio Code; there is no way that is usable with 4GB of memory.
I tried!
</span></p>
<h2 id="how-does-this-work">How does this work?</h2>
<p>OpenSSH has the concept of session multiplexing] over a single connection.
By wrapping the master connection with a <code class="language-plaintext highlighter-rouge">ProxyCommand</code>, I tie the master
connection to the lifetime of the virtual machine. When it starts, the VM
starts; when it exits the VM exits.</p>
<p><code class="language-plaintext highlighter-rouge">vmctl-ssh-wrapper.sh</code>, when invoked by OpenSSH, parses the output of
<a href="https://man.openbsd.org/vmctl"><code class="language-plaintext highlighter-rouge">vmctl</code></a> to determine if the vm is already
running. If it isn’t, we try to bring it up, and schedule it for eventual
shutdown – we will not shutdown vms that were manually started.</p>
<p>We determine the guest’s IP address by parsing the output of <code class="language-plaintext highlighter-rouge">ifconfig tap</code>
and looking for interfaces with a “description” field matching the name of
our requested virtual machine. By convention, the guest’s DHCP-assigned ip
is immediately above the address assigned to the host’s <code class="language-plaintext highlighter-rouge">tap</code> interface.</p>
<p>Once we have the IP, we poll until the guest’s ssh server comes up. When it
does, the socket is connected to our ssh client. When the script exits, we
(may) invoke <code class="language-plaintext highlighter-rouge">vmctl</code> once again to schedule a shutdown.</p>
<h2 id="vm-setup">VM Setup</h2>
<p><a href="https://man.openbsd.org/vmd"><code class="language-plaintext highlighter-rouge">vmd</code></a> is the OpenBSD virtualization daemon.
There’s plenty of tutorials on how to install Linux over the fake serial port,
including <a href="https://icyphox.sh/blog/signal-vmm/">the one I linked
earlier</a>.</p>
<p>A fresh wrinkle is that newer Linux kernels <label for="sn-noboot" class="margin-toggle sidenote-number">will not boot</label>.
<input id="sn-noboot" class="margin-toggle" type="checkbox" />
<span class="sidenote">
“<a href="https://marc.info/?l=openbsd-bugs&m=167932237609527&w=2">MMIO
is unfinished in vmd(8).”</a>
</span>
So stick to an OS release known to be working, and don’t blindly jump to the
next major release.</p>
<p><a href="https://man.openbsd.org/vm.conf"><code class="language-plaintext highlighter-rouge">/etc/vm.conf</code></a></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vm "ubuntu" {
memory 2G
# boot device cdrom
cdrom "/home/jon/vm/mini.iso"
disk "/home/jon/vm/ubuntu.img"
interfaces 1
local interface tap
owner jon
disable
}
</code></pre></div></div>
<h2 id="ssh-client-configuration">SSH Client Configuration</h2>
<p>I added a block to my ssh configuration so that requests to <code class="language-plaintext highlighter-rouge">ubuntu.vmctl.host</code>
are be serviced by the virtual machine defined as <code class="language-plaintext highlighter-rouge">ubuntu</code>. The
<code class="language-plaintext highlighter-rouge">ControlPersist</code> block allows a 10 minutes idle period (no active X or ssh
clients) before shutting down.</p>
<p><a href="https://man.openbsd.org/ssh_config"><code class="language-plaintext highlighter-rouge">~/.ssh/config</code></a>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host *.vmctl.host
ProxyCommand ~/.skel/bin/vmctl-ssh-wrapper.sh %h %p
ControlMaster auto
ControlPersist 10m
ForwardX11 yes
</code></pre></div></div>
<h2 id="just-let-me-install-it-already">Just Let Me Install It, Already!</h2>
<p><a href="https://github.com/wizardishungry/vmctl-ssh-wrapper">Source here</a>. This is my first
shell script in a while, so gentle feedback is welcome.</p>
<p>This <a href="https://deskto.ps/u/wizardishungry/d/8eqfrb">works pretty well for Signal</a>! Who knows, maybe Slack or VSCode might even
be possible on a nicer laptop.</p>
Isolating problematic Cgo code2022-03-09T00:00:00+00:00https://jonwillia.ms/2022/03/09/isolating-problematic-cgo-code
<p><span class="marginnote">
<img src="/assets/images/2022-03-09-isolating-problematic-cgo-code/FNFL-4ZWYAEpIoF.png" alt="Typical image" />
</span></p>
<h2 id="introduction">Introduction</h2>
<p><a href="/2022/03/08/introducing-kctv_bot">KCTV_bot</a> watches an
<a href="https://en.wikipedia.org/wiki/HTTP_Live_Streaming">HLS</a> video stream and posts
screengrabs to Twitter. Because the video source (North Korean state television)
is not regularly available, some image processing must be performed to recognize when the channel is live.</p>
<p>Although the code is written in <a href="https://go.dev/">Go</a>,
the native options for decoding segments of video to get at individual frames
are underwhelming. Fortunately, there exist <a href="https://pkg.go.dev/cmd/cgo">Cgo</a>
<label for="sn-goav" class="margin-toggle sidenote-number">wrappers</label><input id="sn-goav" class="margin-toggle" type="checkbox" /><span class="sidenote"><a href="https://github.com/charlestamz/goav">charlestamz/goav</a></span> for the popular audio/video library <a href="https://ffmpeg.org/">ffmpeg</a>.</p>
<p>The process of decoding an MPEG video segment to iterate over each individual
video frame is a bit involved<label for="sn-decode" class="margin-toggle sidenote-number"></label>.
<input id="sn-decode" class="margin-toggle" type="checkbox" />
<span class="sidenote">
To get an idea of the complexity, begin reading <a href="https://github.com/WIZARDISHUNGRY/hls-await/blob/blog-post/internal/segment/goav.go#106"><code class="language-plaintext highlighter-rouge">HandleSegment</code></a> at the call to <code class="language-plaintext highlighter-rouge">AvformatAllocContext()</code>.
</span>
Although the program was stable over short time intervals, it frequently
crashed while running unsupervised. Furthermore, I would often
return to my computer to see that the memory usage had ballooned
to many <label for="sn-gigabytes" class="margin-toggle sidenote-number">gigabytes</label>.
<input id="sn-gigabytes" class="margin-toggle" type="checkbox" />
<span class="sidenote">
Typical memory usage is around a gigabyte, with roughly half of that being
accounted for by memory allocated inside Go — as reported by
<a href="https://pkg.go.dev/runtime#ReadMemStats"><code class="language-plaintext highlighter-rouge">ReadMemStats()</code></a>.
</span>
After a few attempts at <a href="https://kirshatrov.com/posts/finding-memory-leak-in-cgo/">tracing memory leaks</a>
in Cgo proved mostly fruitless, I decided to try separating the program into two processes.</p>
<h3 id="what-is-hls-http-live-streaming">What is HLS (HTTP Live Streaming)?</h3>
<p>Briefly, <a href="https://en.wikipedia.org/wiki/HTTP_Live_Streaming">HLS</a> is a popular format for streaming content.
A piece of content, be it live or on-demand, is represented as a series of short (~10 second) media files
contained in a playlist. A live playlist will be repeatedly fetched by a player to discover new media segments.</p>
<h2 id="architecture">Architecture</h2>
<p><span class="marginnote">
<img src="/assets/images/2022-03-09-isolating-problematic-cgo-code/FM4Au54X0A0E9FX.png" alt="Color bars w/ station id" />
<img src="/assets/images/2022-03-09-isolating-problematic-cgo-code/FM5uoddXsAAMnHf.png" alt="Test pattern" />
</span></p>
<ul>
<li>Parent process, directly invoked from the command line to handle the majority of the tasks:
<ul>
<li>Downloading the playlist to check for new segments. <em>Every <a href="https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-07#section-4.4.3.1">Target Duration</a>, download the playlist and look for new segments.</em></li>
<li>Fetching segments</li>
<li>Image pattern recognition <em>Is this an image of color bars or a test pattern? Is it a black screen? Is the image moving?</em></li>
<li>Maintenance of a state machine <em>Has the image been mostly moving for the last 30 seconds? If so, begin storing images to post.</em></li>
<li>Posting tweets.</li>
</ul>
</li>
<li>Child process, invoked by the parent process.
<ul>
<li>An rpc service that, when provided with a raw segment (a bunch of MPEG bytes), returns a slice of frames (<code class="language-plaintext highlighter-rouge">[]image.Image</code>) to the caller.</li>
<li>The child process is completely stateless — there is no dependency on previous rpc calls; a freshly restarted instance of the child process is ready to serve requests.</li>
</ul>
</li>
</ul>
<h2 id="implementation">Implementation</h2>
<p><em>Some of the code below has been edited for clarity.</em></p>
<h3 id="starting-the-child-process">Starting the child process</h3>
<p>Spawning the child process is handled by <code class="language-plaintext highlighter-rouge">spawnChild</code> within <a href="https://github.com/WIZARDISHUNGRY/hls-await/blob/blog-post/internal/worker/parent.go#L82"><code class="language-plaintext highlighter-rouge">internal/worker/parent.go</code></a>.</p>
<p>First, the parent process listens on a <a href="https://en.wikipedia.org/wiki/Unix_domain_socket">Unix domain socket</a><label for="sn-fuzz" class="margin-toggle sidenote-number"></label>
and retrieve an <code class="language-plaintext highlighter-rouge">os.File</code> struct corresponding to this socket. This struct contains the file descriptor of the socket.
<input id="sn-fuzz" class="margin-toggle" type="checkbox" />
<span class="sidenote">
It may be surprising to see an empty <code class="language-plaintext highlighter-rouge">UnixAddr</code> passed into <code class="language-plaintext highlighter-rouge">ListenUnix</code> instead of a path to a file.
This is a Linuxism that allows us to <a href="https://www.toptip.ca/2013/01/unix-domain-socket-with-abstract-socket.html">use a Unix socket on a read-only
file system</a>.
</span></p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ul</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">ListenUnix</span><span class="p">(</span><span class="s">"unix"</span><span class="p">,</span>
<span class="o">&</span><span class="n">net</span><span class="o">.</span><span class="n">UnixAddr</span><span class="p">{})</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">err</span>
<span class="p">}</span>
<span class="n">f</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">ul</span><span class="o">.</span><span class="n">File</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">err</span>
<span class="p">}</span>
</code></pre></div></div>
<p>A special flag is appended to a slice of arguments to the child process<label for="sn-fuzz" class="margin-toggle sidenote-number"></label>.
<input id="sn-fuzz" class="margin-toggle" type="checkbox" />
<span class="sidenote">
The go1.18+ fuzzing system is <a href="https://jayconrod.com/posts/123/internals-of-go-s-new-fuzzing-system">very similar</a> to our approach.
</span>
We prepare the child process for execution and add our socket to <code class="language-plaintext highlighter-rouge">ExtraFiles</code> slice on the <code class="language-plaintext highlighter-rouge">exec.Cmd</code> struct:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">args</span> <span class="o">:=</span> <span class="nb">append</span><span class="p">([]</span><span class="kt">string</span><span class="p">{},</span> <span class="n">os</span><span class="o">.</span><span class="n">Args</span><span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="p">]</span><span class="o">...</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="s">"-worker"</span><span class="p">)</span>
<span class="n">cmd</span> <span class="o">:=</span> <span class="n">exec</span><span class="o">.</span><span class="n">CommandContext</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">Args</span><span class="p">[</span><span class="m">0</span><span class="p">],</span> <span class="n">args</span><span class="o">...</span><span class="p">)</span>
<span class="n">cmd</span><span class="o">.</span><span class="n">ExtraFiles</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">cmd</span><span class="o">.</span><span class="n">ExtraFiles</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span>
</code></pre></div></div>
<p>Passing the listening socket’s FD via <code class="language-plaintext highlighter-rouge">ExtraFiles</code> allows the child process to <code class="language-plaintext highlighter-rouge">Accept()</code> connections<label for="sn-pass-fd" class="margin-toggle sidenote-number"></label>.
<input id="sn-pass-fd" class="margin-toggle" type="checkbox" />
<span class="sidenote">
While it is possible for the child process to call <code class="language-plaintext highlighter-rouge">ListenUnix</code> directly & avoid passing <code class="language-plaintext highlighter-rouge">ExtraFiles</code>, the parent’s <code class="language-plaintext highlighter-rouge">DialUnix</code> call may occur
before the child process has begun listening.
</span></p>
<p>The parent process next dials two<label for="sn-two" class="margin-toggle sidenote-number"></label> connections
and creates an <a href="https://pkg.go.dev/net/rpc">net/rpc</a> client.
<input id="sn-two" class="margin-toggle" type="checkbox" />
<span class="sidenote">
One handles rpc request/responses, and the second passes newly opened file descriptors to the child process.
See <a href="#why-two-client-connections"><em>Why two client connections?</em></a> below.
</span></p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">connRPC</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">DialUnix</span><span class="p">(</span><span class="s">"unix"</span><span class="p">,</span> <span class="no">nil</span><span class="p">,</span>
<span class="n">ul</span><span class="o">.</span><span class="n">Addr</span><span class="p">()</span><span class="o">.</span><span class="p">(</span><span class="o">*</span><span class="n">net</span><span class="o">.</span><span class="n">UnixAddr</span><span class="p">))</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">err</span>
<span class="p">}</span>
<span class="n">p</span><span class="o">.</span><span class="n">conn</span> <span class="o">=</span> <span class="n">conn</span>
<span class="n">connFD</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">DialUnix</span><span class="p">(</span><span class="s">"unix"</span><span class="p">,</span> <span class="no">nil</span><span class="p">,</span>
<span class="n">ul</span><span class="o">.</span><span class="n">Addr</span><span class="p">()</span><span class="o">.</span><span class="p">(</span><span class="o">*</span><span class="n">net</span><span class="o">.</span><span class="n">UnixAddr</span><span class="p">))</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">err</span>
<span class="p">}</span>
<span class="n">client</span> <span class="o">:=</span> <span class="n">rpc</span><span class="o">.</span><span class="n">NewClient</span><span class="p">(</span><span class="n">connRPC</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="child-startup">Child startup</h3>
<p>The child process next translates the file descriptor passed via <code class="language-plaintext highlighter-rouge">ExtraFiles</code> back into a <code class="language-plaintext highlighter-rouge">Listener</code>.
The first 3 file descriptors are reversed for standard i/o , so <code class="language-plaintext highlighter-rouge">ExtraFiles[0]</code> corresponds to an FD of 3.</p>
<p>Simplified from <a href="https://github.com/WIZARDISHUNGRY/hls-await/blob/blog-post/internal/worker/child.go"><code class="language-plaintext highlighter-rouge">internal/worker/child.go</code></a>:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">NewFile</span><span class="p">(</span><span class="m">3</span><span class="p">,</span> <span class="s">"unix"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">f</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"nil for fd %d"</span><span class="p">,</span> <span class="n">fd</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">listener</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">FileListener</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"net.FileListener: %w"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">Accept</code> loop for the child process is contained in <code class="language-plaintext highlighter-rouge">runWorker</code> (<a href="https://github.com/WIZARDISHUNGRY/hls-await/blob/blog-post/internal/worker/child.go#L152"><code class="language-plaintext highlighter-rouge">internal/worker/child.go</code></a>).</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">server</span> <span class="o">:=</span> <span class="n">rpc</span><span class="o">.</span><span class="n">NewServer</span><span class="p">()</span>
<span class="c">// pointer to a struct holding the goav code</span>
<span class="n">server</span><span class="o">.</span><span class="n">Register</span><span class="p">(</span><span class="n">segApi</span><span class="p">)</span>
<span class="n">conn</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">listener</span><span class="o">.</span><span class="n">Accept</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">errors</span><span class="o">.</span><span class="n">Wrap</span><span class="p">(</span><span class="n">err</span><span class="p">,</span> <span class="s">"listener.Accept"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">server</span><span class="o">.</span><span class="n">ServeConn</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
</code></pre></div></div>
<p>The child has now attached an RPC server to the Unix socket connection from the parent.
<code class="language-plaintext highlighter-rouge">segApi</code>’s methods<label for="sn-methods" class="margin-toggle sidenote-number"></label> may now be invoked from the parent process.
<input id="sn-methods" class="margin-toggle" type="checkbox" />
<span class="sidenote">
Specifically <a href="https://github.com/WIZARDISHUNGRY/hls-await/blob/blog-post/internal/segment/goav.go#106"><code class="language-plaintext highlighter-rouge">HandleSegment</code></a>
</span></p>
<h3 id="making-calls-to-the-child-process">Making calls to the child process</h3>
<p>Again, the <a href="https://pkg.go.dev/net/rpc">net/rpc</a> documentation may be helpful.
The parent process is able to make calls to child as such:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="n">client</span><span class="o">.</span><span class="n">Call</span><span class="p">(</span><span class="s">"GoAV.HandleSegment"</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">resp</span><span class="p">)</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">request</code> and <code class="language-plaintext highlighter-rouge">response</code> are pointers to:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">Request</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">FD</span> <span class="kt">uintptr</span>
<span class="p">}</span>
<span class="k">type</span> <span class="n">Response</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">RawImages</span> <span class="p">[]</span><span class="n">image</span><span class="o">.</span><span class="n">Image</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="why-are-you-passing-file-descriptors-between-processes">Why are you passing file descriptors between processes?</h3>
<p><code class="language-plaintext highlighter-rouge">Request</code> contains an integer file descriptor for each segment.
I explored passing the segment to <code class="language-plaintext highlighter-rouge">goav</code> in a number of ways.</p>
<ol>
<li>A path to a temporary file.
<ul>
<li>This had the disadvantage of disk i/o, and could leave files around when the program crashed.</li>
<li><code class="language-plaintext highlighter-rouge">goav</code> would not wait for the rest of the data to download once the end of a partially downloaded file was reached.</li>
</ul>
</li>
<li>A path to a temporary <a href="https://en.wikipedia.org/wiki/Named_pipe">FIFO</a>.
<ul>
<li>This allows forward progress on a partially downloaded file.</li>
<li>Same problems as temporary files and a bit complex.</li>
</ul>
</li>
<li>Passing http <code class="language-plaintext highlighter-rouge">Body()</code> - an instance of the <a href="https://pkg.go.dev/io#ReadCloser"><code class="language-plaintext highlighter-rouge">io.ReadCloser</code></a> interface.
<ul>
<li>It wasn’t obvious to me how to call <code class="language-plaintext highlighter-rouge">goav</code> on data already in memory.</li>
<li>This does not work across process boundaries.</li>
</ul>
</li>
<li>The serialized byte slice of the entire segment (~10 mb for our stream).
<ul>
<li><code class="language-plaintext highlighter-rouge">goav</code> cannot begin decoding the segment until it has been fully read from the http response.</li>
<li>It wasn’t obvious to me how to call <code class="language-plaintext highlighter-rouge">goav</code> on data already in memory.</li>
<li>When moving to process separation, this added additional copies
<ol>
<li>Copy from <code class="language-plaintext highlighter-rouge">Body</code> to a <code class="language-plaintext highlighter-rouge">[]byte</code></li>
<li>Serialize each <code class="language-plaintext highlighter-rouge">Request</code> using <code class="language-plaintext highlighter-rouge">encoding/gob</code>.</li>
<li>Unserialize the <code class="language-plaintext highlighter-rouge">Request</code> using <code class="language-plaintext highlighter-rouge">encoding/gob</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
<p>Instead of any of these approaches, I ended up passing segments’ file descriptors to the child process
in along with the RPC call. Previous code examples were simplified to hide this complexity.
The implementation of sending and receiving file descriptors can be found in
<a href="https://github.com/WIZARDISHUNGRY/hls-await/blob/blog-post/pkg/unixmsg/send_fd.go"><code class="language-plaintext highlighter-rouge">pkg/unixmsg/send_fd.go</code></a>.</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">SendFd</span><span class="p">(</span><span class="n">conn</span> <span class="o">*</span><span class="n">net</span><span class="o">.</span><span class="n">UnixConn</span><span class="p">,</span> <span class="n">fd</span> <span class="kt">uintptr</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="n">rights</span> <span class="o">:=</span> <span class="n">syscall</span><span class="o">.</span><span class="n">UnixRights</span><span class="p">(</span><span class="kt">int</span><span class="p">(</span><span class="n">fd</span><span class="p">))</span>
<span class="n">dummy</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">(</span><span class="s">"x"</span><span class="p">)</span>
<span class="n">n</span><span class="p">,</span> <span class="n">oobn</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">conn</span><span class="o">.</span><span class="n">WriteMsgUnix</span><span class="p">(</span><span class="n">dummy</span><span class="p">,</span> <span class="n">rights</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"err %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">n</span> <span class="o">!=</span> <span class="nb">len</span><span class="p">(</span><span class="n">dummy</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"short write %v"</span><span class="p">,</span> <span class="n">conn</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">oobn</span> <span class="o">!=</span> <span class="nb">len</span><span class="p">(</span><span class="n">rights</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"short oob write %v"</span><span class="p">,</span> <span class="n">conn</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="no">nil</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">RecvFd</span><span class="p">(</span><span class="n">conn</span> <span class="o">*</span><span class="n">net</span><span class="o">.</span><span class="n">UnixConn</span><span class="p">)</span> <span class="p">(</span><span class="kt">uintptr</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">buf</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="m">32</span><span class="p">)</span>
<span class="n">oob</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="m">32</span><span class="p">)</span>
<span class="n">_</span><span class="p">,</span> <span class="n">oobn</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">conn</span><span class="o">.</span><span class="n">ReadMsgUnix</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">oob</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="m">0</span><span class="p">,</span> <span class="n">err</span>
<span class="p">}</span>
<span class="n">scms</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">syscall</span><span class="o">.</span><span class="n">ParseSocketControlMessage</span><span class="p">(</span><span class="n">oob</span><span class="p">[</span><span class="o">:</span><span class="n">oobn</span><span class="p">])</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="m">0</span><span class="p">,</span> <span class="n">err</span>
<span class="p">}</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">scms</span><span class="p">)</span> <span class="o">!=</span> <span class="m">1</span> <span class="p">{</span>
<span class="k">return</span> <span class="m">0</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"count not 1: %v"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">scms</span><span class="p">))</span>
<span class="p">}</span>
<span class="n">scm</span> <span class="o">:=</span> <span class="n">scms</span><span class="p">[</span><span class="m">0</span><span class="p">]</span>
<span class="n">fds</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">syscall</span><span class="o">.</span><span class="n">ParseUnixRights</span><span class="p">(</span><span class="o">&</span><span class="n">scm</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="m">0</span><span class="p">,</span> <span class="n">err</span>
<span class="p">}</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">fds</span><span class="p">)</span> <span class="o">!=</span> <span class="m">1</span> <span class="p">{</span>
<span class="k">return</span> <span class="m">0</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"fd count not 1: %v"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">fds</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kt">uintptr</span><span class="p">(</span><span class="n">fds</span><span class="p">[</span><span class="m">0</span><span class="p">]),</span> <span class="no">nil</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Under the hood, this is calling the <code class="language-plaintext highlighter-rouge">I_SENDFD</code> <a href="https://linux.die.net/man/3/ioctl">ioctl</a>
on one of the Unix socket connections the parent process stood up earlier.
Because file descriptors are scoped to a process, the receiving process must read a structure
out of the connection to determine the integer value of the file descriptor it has been passed.
The values passed from the parent will not be the same as the values received in the child,
despite corresponding to the same resource.</p>
<p>The FD passed to the child process corresponds to a
<code class="language-plaintext highlighter-rouge">PipeReader</code> returned from <a href="https://pkg.go.dev/io#Pipe">io.Pipe()</a>.
The HTTP body is streamed to the <code class="language-plaintext highlighter-rouge">PipeWriter</code> as it downloads; enabling the <code class="language-plaintext highlighter-rouge">goav</code> calls to begin decoding without
waiting for the entire response to be read into memory.</p>
<p>Simplified version of handling a segment body from
<a href="https://github.com/WIZARDISHUNGRY/hls-await/blob/blog-post/internal/stream/seg_consumer.go#L84"><code class="language-plaintext highlighter-rouge">internal/stream/seg_consumer.go</code></a>:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">s</span><span class="o">.</span><span class="n">httpGet</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">errors</span><span class="o">.</span><span class="n">Wrap</span><span class="p">(</span><span class="n">err</span><span class="p">,</span> <span class="s">"httpGet"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="n">r</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Pipe</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">errors</span><span class="o">.</span><span class="n">Wrap</span><span class="p">(</span><span class="n">err</span><span class="p">,</span> <span class="s">"os.Pipe"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">r</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="k">defer</span> <span class="n">w</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="k">go</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">io</span><span class="o">.</span><span class="n">Copy</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">WithError</span><span class="p">(</span><span class="n">err</span><span class="p">)</span><span class="o">.</span><span class="n">Warn</span><span class="p">(</span><span class="s">"io.Copy"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">w</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="p">}()</span>
<span class="n">request</span> <span class="o">:=</span> <span class="o">&</span><span class="n">segment</span><span class="o">.</span><span class="n">Request</span><span class="p">{</span><span class="n">FD</span><span class="o">:</span> <span class="n">r</span><span class="o">.</span><span class="n">Fd</span><span class="p">()}</span>
<span class="k">return</span> <span class="n">s</span><span class="o">.</span><span class="n">ProcessSegment</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">request</span><span class="p">)</span>
</code></pre></div></div>
<p>In the child process, the ffmpeg calls are passed a filename that corresponds to an open file descriptor returned by <code class="language-plaintext highlighter-rouge">RecvFd</code>.</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">file</span> <span class="o">:=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"/proc/self/fd/%d"</span><span class="p">,</span> <span class="n">fd</span><span class="p">)</span> <span class="c">// This is a Linuxism</span>
<span class="n">pFormatContext</span> <span class="o">:=</span> <span class="n">avformat</span><span class="o">.</span><span class="n">AvformatAllocContext</span><span class="p">()</span>
<span class="n">avformat</span><span class="o">.</span><span class="n">AvformatOpenInput</span><span class="p">(</span><span class="o">&</span><span class="n">pFormatContext</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="no">nil</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="why-two-client-connections">Why two client connections?</h3>
<p>Socket control messages are considered out-of-band (OOB) data and are read into
a separate slice by <a href="https://pkg.go.dev/net#UnixConn.ReadMsgUnix"><code class="language-plaintext highlighter-rouge">ReadMsgUnix</code></a>.
Attempting to read available OOB data will always discard at least
1 byte of in-band data<label for="sn-oob" class="margin-toggle sidenote-number"></label>.
This dropped byte would cause problem for the the rpc server we attached to the parent to child connection, so we ended up
using two connections.
It may be possible to make an abstraction on top of <code class="language-plaintext highlighter-rouge">UnixConn</code> that allows multiplexing both messages on a single connection.
But for a project this frivolous, this is good enough for now.
<input id="sn-oob" class="margin-toggle" type="checkbox" />
<span class="sidenote">
See <a href="https://github.com/golang/go/issues/32465">proposal: net: add ability to read OOB data without discarding a byte</a> for more detail.
</span></p>
<p>Perhaps removing <code class="language-plaintext highlighter-rouge">net/rpc</code> entirely and returning individual frames immediately after decoding would be a cleaner solution?</p>
<h2 id="conclusion">Conclusion</h2>
<p>This code has some warts resulting from its origins as an ANSI HLS player.
Nevertheless, I’m pleased that this is now able to run fairly stable without constant
care & feeding. I plan on moving this into my local Kubernetes cluster & expect plenty of new problems
from the limited resources.</p>
<ul>
<li>Source: <a href="https://github.com/WIZARDISHUNGRY/hls-await"><code class="language-plaintext highlighter-rouge">WIZARDISHUNGRY/hls-await</code></a></li>
<li>Twitter: <a href="https://twitter.com/KCTV_bot">KCTV_bot</a></li>
</ul>
Introducing @KCTV_bot2022-03-08T00:00:00+00:00https://jonwillia.ms/2022/03/08/introducing-kctv_bot
<p>I recently finished adding Twitter support to my Go project for monitoring intermitant
<a href="https://en.wikipedia.org/wiki/HTTP_Live_Streaming">HLS</a> streams (<a href="https://github.com/WIZARDISHUNGRY/hls-await"><code class="language-plaintext highlighter-rouge">hls-await</code></a>). The result is
<a href="https://twitter.com/KCTV_bot">@KCTV_bot</a> - a Twitter bot that will live tweet screencaps
from DPRK television whenever it goes on the air.</p>
<p>There’s more documentation to come, but for now, enjoy a few screencaps:</p>
<blockquote class="twitter-tweet" data-dnt="true" data-conversation="none"><p lang="en" dir="ltr">It's currently 3:02PM in Pyongyang & KCTV is on the air! <a href="https://t.co/PKDERMD8RN">pic.twitter.com/PKDERMD8RN</a></p>— 🇰🇵 KCTV bot 📺 (@KCTV_bot) <a href="https://twitter.com/KCTV_bot/status/1500713519775375363?ref_src=twsrc%5Etfw">March 7, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" data-dnt="true" data-conversation="none"><p lang="und" dir="ltr"><a href="https://t.co/CvKIF0VfVq">pic.twitter.com/CvKIF0VfVq</a></p>— 🇰🇵 KCTV bot 📺 (@KCTV_bot) <a href="https://twitter.com/KCTV_bot/status/1500063704099999745?ref_src=twsrc%5Etfw">March 5, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
Speed Up GoMock with Conditional Generation2019-12-22T00:00:00+00:00https://jonwillia.ms/2019/12/22/conditional-gomock-mockgen
<p>Recently I’ve been frustrated by the slow reflection performance of Go’s <a href="https://github.com/golang/mock">mockgen</a> when running <code class="language-plaintext highlighter-rouge">go generate ./...</code> on a large project. I’ve found it useful to use the Bourne shell built-in <code class="language-plaintext highlighter-rouge">test</code> command to conditionally invoke <code class="language-plaintext highlighter-rouge">mockgen</code> iif:</p>
<ul>
<li>the destination is older than the source file</li>
<li>or the destination does not exist</li>
</ul>
<p><code class="language-plaintext highlighter-rouge">go generate</code> <a href="https://github.com/golang/go/issues/20520">does not implement any kind of parallelism</a>, so the slow performance of <code class="language-plaintext highlighter-rouge">mockgen</code>, while in source mode, has become a bit of a drag; thus –</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">ordering</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"context"</span>
<span class="p">)</span>
<span class="c">//go:generate sh -c "test client_mock_test.go -nt $GOFILE && exit 0; mockgen -package $GOPACKAGE -destination client_mock_test.go github.com/whatever/project/ordering OrderClient"</span>
<span class="k">type</span> <span class="n">OrderClient</span> <span class="k">interface</span> <span class="p">{</span>
<span class="n">Create</span><span class="p">(</span><span class="n">ctx</span> <span class="n">context</span><span class="o">.</span><span class="n">Context</span><span class="p">,</span> <span class="n">o</span> <span class="o">*</span><span class="n">OrderRequest</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="n">OrderResponse</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
<span class="n">Status</span><span class="p">(</span><span class="n">ctx</span> <span class="n">context</span><span class="o">.</span><span class="n">Context</span><span class="p">,</span> <span class="n">orderRefID</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="n">OrderResponse</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
<span class="n">Cancel</span><span class="p">(</span><span class="n">ctx</span> <span class="n">context</span><span class="o">.</span><span class="n">Context</span><span class="p">,</span> <span class="n">orderRefID</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="n">OrderResponse</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>On my fairly large project, this reduces many generate runs from the order of 45 seconds to 2 or 3 seconds.</p>
<p>(The above code sample probably works in source mode, but has been contrived for simplicity.)</p>
Deploying Anycast DNS Using OpenBSD and BGP2018-09-23T00:00:00+00:00https://jonwillia.ms/2018/09/23/anycast-dns-openbsd
<p>My home network is connected to <a href="https://nycmesh.net/">NYCMesh</a>, a community-owned open network.
Recently, the failure of an SD card inside a Raspberry Pi at an adjacent large hub has left my area of the network without a caching recursive resolver to serve DNS for both the <code class="language-plaintext highlighter-rouge">.mesh</code> TLD and the wider internet. I stood up my own instance of the <a href="https://github.com/nycmeshnet/nycmesh-dns"><code class="language-plaintext highlighter-rouge">10.10.10.10</code> anycast DNS resolver</a> to service DNS in my neighborhood of the network.</p>
<h2 id="overview">Overview</h2>
<p>Inside the mesh, DNS is serviced by the anycast IP address <code class="language-plaintext highlighter-rouge">10.10.10.10</code> by announcing a BGP route for this IP address.
Nodes near to me will use my instance for DNS resolution because the routing topology will prefer my instance over a distant instance.</p>
<p>The major components of this build will be:</p>
<ul>
<li><a href="https://www.openbsd.org/">OpenBSD</a> - Operating system for my network gateway and my anycast resolver instance.</li>
<li><a href="https://man.openbsd.org/vmd"><code class="language-plaintext highlighter-rouge">vmd</code></a> - Hypervisor for hosting a virtualized copy of OpenBSD, for running the resolver instance.</li>
<li><a href="https://man.openbsd.org/unbound"><code class="language-plaintext highlighter-rouge">unbound</code></a> - Caching recursive DNS resolver, for serving DNS requests to clients.</li>
<li><a href="https://man.openbsd.org/nsd"><code class="language-plaintext highlighter-rouge">nsd</code></a> - Authoritative DNS server, for supplying <code class="language-plaintext highlighter-rouge">.mesh</code> to <code class="language-plaintext highlighter-rouge">unbound</code>.</li>
<li><a href="https://man.openbsd.org/relayd"><code class="language-plaintext highlighter-rouge">relayd</code></a> - Application layer gateway that keeps a route from my BGP instance to the DNS instance up, if health checks are passing.</li>
<li><a href="https://www.openbgp.org/">OpenBGP</a> - <a href="https://en.wikipedia.org/wiki/Border_Gateway_Protocol">Border Gateway Protocol</a> daemon, included in OpenBSD.</li>
</ul>
<p>The gateway machine has already been configured as a router to allow forwarding of packets, and functions as a router, LAN DNS forwarder, and web server.</p>
<h2 id="setup-virtual-machine">Setup Virtual Machine</h2>
<p>The virtual machine base system is installed mostly using the <a href="https://man.openbsd.org/autoinstall">autoinstall</a> facility, you may prefer a manual installation.</p>
<p><strong><a href="https://man.openbsd.org/vm.conf"><code class="language-plaintext highlighter-rouge">/etc/vm.conf</code></a>:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vm "nycmesh-dns" {
enable
owner jon:wheel
memory 512M
# First disk from 'vmctl create "/home/vm/nycmesh-dns.img" -s 1G'
disk "/home/vm/nycmesh-dns.img"
#boot "/bsd.rd" # For install
interface {
switch "vmnet"
locked lladdr 00:00:0A:46:91:C2
}
}
</code></pre></div></div>
<p><strong><a href="https://man.openbsd.org/dhcpd.conf"><code class="language-plaintext highlighter-rouge">/etc/dhcpd.conf</code></a>:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>authoritative;
option domain-name "bongo.zone";
use-host-decl-names on;
filename "auto_install";
# vmd service zone
subnet 10.70.145.192 netmask 255.255.255.224 {
range 10.70.145.196 10.70.145.222;
option routers 10.70.145.193;
option domain-name-servers 10.70.145.1, 10.10.10.10, 10.70.131.129;
host nycmesh-dns {
fixed-address nycmesh-dns.bongo.zone, 10.10.10.10;
hardware ethernet 00:00:0A:46:91:C2;
}
}
</code></pre></div></div>
<p><strong><a href="https://man.openbsd.org/autoinstall"><code class="language-plaintext highlighter-rouge">/var/www/htdocs/default/nycmesh-dns-install.conf</code></a>:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># autoinstall response file for unattended installation
# https://man.openbsd.org/autoinstall
#Password for root account = plaintext / encrypt(1) / "*************" to disable
Password for root account = *************
Change the default console to com0 = yes
Which speed should com0 use = 19200
Public ssh key for root account = ssh-rsa AAAA…XYZZY [email protected]
Start sshd(8) by default = yes
Do you expect to run the X Window System = no
Setup a user = no
Allow root ssh login = prohibit-password
What timezone are you in = America/New_York
Which disk is the root disk = sd0
URL to autopartitioning template for disklabel = https://kibble.bongo.zone/disklabel.min
Location of sets = http
HTTP proxy URL = none
HTTP Server = cdn.openbsd.org
Server directory = /pub/OpenBSD/6.3/amd64
Set name(s) = -comp* -game* -x* -man*
</code></pre></div></div>
<p>We may now access this virtual machine <strong>only</strong> via ssh.</p>
<h2 id="zonefile-pull-on-the-vm">Zonefile pull on the VM</h2>
<p>NYCMesh generally uses <a href="https://www.knot-resolver.cz/"><code class="language-plaintext highlighter-rouge">kresd/knot</code></a> as their DNS server and
keeps the zone files and configuration in a <a href="https://github.com/nycmeshnet/nycmesh-dns">git repo</a>.
Because OpenBSD has a fairly old version of knot, I decided to use the base system DNS servers to
serve the zone files. (I should probably move this to a Linux VM running kresd/knot to be in line with the rest of the mesh.)</p>
<p>First I checked out a copy of the git repo using anonymous HTTP so I wouldn’t need github credentials on the VM.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pkg_add git python-2.7.14p1 bash
git clone https://github.com/nycmeshnet/nycmesh-dns.git
</code></pre></div></div>
<p>I setup a script to auto-pull the zonefile updates, based on the same script for Linux/Unbound.</p>
<p><strong><code class="language-plaintext highlighter-rouge">/root/nycmesh-dns/deploy-nsd.sh</code>:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/local/bin/bash</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:/usr/local/bin
<span class="c"># OpenBSD + Unbound + NSD</span>
<span class="nb">cd</span> /root/nycmesh-dns
git pull
<span class="nv">NEWCOMMIT</span><span class="o">=</span><span class="sb">`</span>git rev-parse HEAD<span class="sb">`</span>
<span class="nv">OLDCOMMIT</span><span class="o">=</span><span class="sb">`</span><span class="nb">cat </span>commit<span class="sb">`</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$NEWCOMMIT</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"</span><span class="nv">$OLDCOMMIT</span><span class="s2">"</span> <span class="o">]</span>
<span class="k">then
</span><span class="nb">exit </span>0
<span class="k">fi
</span>python makereverse.py
<span class="nb">cp</span> <span class="nt">-f</span> <span class="k">*</span>.zone /var/nsd/zones/master
rcctl restart nsd unbound
git rev-parse HEAD <span class="o">></span> commit
</code></pre></div></div>
<p>I later added a cron entry.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*/10 * * * * cd /root/nycmesh-dns && /root/nycmesh-dns/deploy-nsd.sh 2>&1 > /dev/null
</code></pre></div></div>
<h2 id="setup-nsd-and-unbound-on-the-vm">Setup NSD and Unbound on the VM</h2>
<p>First tweak the networking confiruration (<code class="language-plaintext highlighter-rouge">/etc/hostname.vio0</code>):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dhcp
inet alias 10.10.10.10/32
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">nsd</code> will serve zone files from git.</p>
<p><strong><a href="https://man.openbsd.org/nsd.conf"><code class="language-plaintext highlighter-rouge">/var/nsd/etc/nsd.conf</code></a>:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server:
hide-version: yes
verbosity: 1
database: "" # disable database
## bind to a specific address/port
ip-address: 127.0.0.1@53
remote-control:
control-enable: yes
zone:
name: "mesh"
zonefile: "master/mesh.zone"
zone:
name: "10.in-addr.arpa"
zonefile: "master/10.in-addr.arpa.zone"
zone:
name: "59.167.199.in-addr.arpa"
zonefile: "master/59.167.199.in-addr.arpa.zone"
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">unbound</code> will serve as a recursive resolver.</p>
<p><strong><a href="https://man.openbsd.org/unbound.conf"><code class="language-plaintext highlighter-rouge">/var/unbound/etc/unbound.conf</code></a>:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server:
private-domain: "mesh"
domain-insecure: "mesh"
do-not-query-localhost: no
#interface: 127.0.0.1
interface: 10.10.10.10
interface: 10.70.145.194
interface: 127.0.0.1@5353 # listen on alternative port
interface: ::1
do-ip6: no
prefetch: yes
# override the default "any" address to send queries; if multiple
# addresses are available, they are used randomly to counter spoofing
outgoing-interface: 10.70.145.194
access-control: 0.0.0.0/0 refuse
access-control: 127.0.0.0/8 allow
access-control: 10.0.0.0/8 allow
access-control: 199.167.59.0/24 allow
access-control: ::0/0 refuse
access-control: ::1 allow
hide-identity: yes
hide-version: yes
remote-control:
control-enable: yes
control-use-cert: no
control-interface: /var/run/unbound.sock
forward-zone:
name: "mesh."
forward-addr: 127.0.0.1
forward-zone:
name: "10.in-addr.arpa."
forward-addr: 127.0.0.1
forward-zone:
name: "59.167.199.in-addr.arpa."
forward-addr: 127.0.0.1
</code></pre></div></div>
<p>Start both servers and try to see if you can resolve <code class="language-plaintext highlighter-rouge">ns.mesh</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nycmesh-dns# rcctl restart nsd unbound
nsd(ok)
nsd(ok)
unbound(ok)
unbound(ok)
nycmesh-dns# host ns.mesh 10.10.10.10
Using domain server:
Name: 10.10.10.10
Address: 10.10.10.10#53
Aliases:
ns.mesh has address 10.10.10.11
</code></pre></div></div>
<h2 id="relayd-health-check">relayd health check</h2>
<p><code class="language-plaintext highlighter-rouge">relayd</code> adds a route to <code class="language-plaintext highlighter-rouge">10.0.10.10/32</code> if the healthcheck passes. If the DNS server stops responding, the route is removed from the kernel and BGP retracts it from peers.</p>
<p><strong><code class="language-plaintext highlighter-rouge">/usr/local/bin/mesh-dns-health-check.sh</code>:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="o">!</span> host <span class="nt">-W</span> 1 ns.mesh. <span class="nv">$1</span>
</code></pre></div></div>
<p><strong><a href="https://man.openbsd.org/relayd.conf"><code class="language-plaintext highlighter-rouge">/etc/relayd.conf</code></a>:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>log updates
timeout 2000
interval 3
table <dns-servers> { nycmesh-dns.bongo.zone ip ttl 1 retry 0 }
router "anycast-dns" {
route 10.10.10.10/32
#forward to <dns-servers> check icmp
forward to <dns-servers> check script "/usr/local/bin/mesh-dns-health-check.sh"
rtlabel export
}
</code></pre></div></div>
<p>Start relayd and verify the route gets added.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kibble# rcctl restart relayd
kibble# traceroute 10.10.10.10
traceroute to 10.10.10.10 (10.10.10.10), 64 hops max, 40 byte packets
1 10.10.10.10 (10.10.10.10) 1.162 ms 0.327 ms 0.485 ms
</code></pre></div></div>
<h2 id="bgp-announcement-of-10101010">BGP announcement of <code class="language-plaintext highlighter-rouge">10.10.10.10</code></h2>
<p>Setting up BGP is a whole task in and of itself, but I have included a partial BGP configuration for reference.</p>
<p><strong><a href="https://man.openbsd.org/bgpd.conf"><code class="language-plaintext highlighter-rouge">/etc/bgpd.conf</code></a>:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># global configuration
AS 65009
router-id 10.70.130.139
network 10.70.145.0/24
network 199.167.59.73/32
network inet static # This is the line that causes our dynamically inserted routes to get picked up
#network inet connected
# restricted socket for bgplg(8)
socket "/var/www/run/bgpd.rsock" restricted
# neighbors and peers
group "nycmesh" {
neighbor 10.70.130.138 {
remote-as 64996
descr "Node 1340"
announce self
}
}
# do not send or use routes from EBGP neighbors without
# further explicit configuration
#deny from ebgp
#deny to ebgp
allow from group nycmesh
</code></pre></div></div>
<h2 id="further-reading">Further reading</h2>
<p>Full configuration <a href="https://github.com/bongozone/kibble">available on github</a></p>
dockertest timeouts2018-08-10T00:00:00+00:00https://jonwillia.ms/2018/08/10/dockertest
<p>Added Docker <a href="https://github.com/ory/dockertest/pull/133">container timeouts</a> to <a href="https://github.com/ory/dockertest">dockertest</a>.</p>
Rhombic Shift Register 0.6.0 Released2018-04-18T00:00:00+00:00https://jonwillia.ms/2018/04/18/rhombic-shift-register-released
<figure>
<label for="mn-exports-imports" class="margin-toggle">Photo</label><input type="checkbox" id="mn-exports-imports" class="margin-toggle" /><span class="marginnote">
<img src="/assets/images/rsr-beta-1.png" alt="Rhombic Shift Register β 1" />
</span>
</figure>
<blockquote>
<p>
The <a href="https://bongo.zone/modules/rhombic-shift-register" class="newthought">Pulsum Quadratum Rhombic Shift Register</a> (RSR) is a four-voice, demuxing shift
register for the <a href="http://www.vcvrack.com/">VCVRack</a> modular synthesis environment.
The RSR consists of four looping “analog” shift registers whose inputs and outputs are
switched via control voltage.
By skillfully applying modulation, automatic variations upon
source patterns may be generated. Using the Rhombic Shift Register with a sequenced melody allows you to
design complex arabesque patterns and arpeggiations.
On the other hand, you need not need not use a sequencer or keyboard
to play the RSR. Good results have been obtained with quantized stepped and random voltages,
feedback patches and other unconventional sources.<label for="sn-westcoast" class="margin-toggle sidenote-number"></label><input id="sn-westcoast" class="margin-toggle" type="checkbox" />
<span class="sidenote">
Commonly termed “West coast synthesis”
</span></p>
</blockquote>
<p>The RSR is now <a href="https://vcvrack.com/plugins.html#PulsumQuadratum-shift">available for $15 from VCVRack</a>.</p>
Announcing the Rhombic Shift Register2018-03-23T00:00:00+00:00https://jonwillia.ms/2018/03/23/rhombic-shift-register<p>I’ve <a href="/2017/12/15/vcvrack">been working</a> on software modular synth design and finally have a fully featured module ready to share with early adopters.</p>
<figure>
<label for="mn-exports-imports" class="margin-toggle">Photo</label><input type="checkbox" id="mn-exports-imports" class="margin-toggle" /><span class="marginnote">
<img src="/assets/images/rsr-beta-1.png" alt="Rhombic Shift Register β 1" />
</span>
</figure>
<blockquote>
<p>
The <a href="https://bongo.zone/">Pulsum Quadratum</a> Rhombic Shift Register (RSR) is a four-voice, demuxing shift
register for the <a href="http://www.vcvrack.com/">VCVRack</a> modular synthesis environment.
The RSR consists of four looping “analog” shift registers whose inputs and outputs are
switched via control voltage.
By skillfully applying modulation, automatic variations upon
source patterns may be generated. Using the Rhombic Shift Register with a sequenced melody allows you to
design complex arabesque patterns and arpeggiations.
On the other hand, you need not need not use a sequencer or keyboard
to play the RSR. Good results have been obtained with quantized stepped and random voltages,
feedback patches and other unconventional sources.
</p>
</blockquote>
<p><del>A time-limited beta version is available at the <a href="https://bongo.zone/">Pulsum Quadratum</a> website.</del></p>
<p><mark>NB:</mark>
This has been <a href="/2018/03/23/rhombic-shift-register">superceded by a commercial version</a>.</p>
Lenovo Ideapad 120S2018-03-18T00:00:00+00:00https://jonwillia.ms/2018/03/18/lenovo-ideapad-120s
<p>I picked up a <a href="https://www3.lenovo.com/us/en/laptops/ideapad/ideapad-100-series/Ideapad-120S-11-Intel/p/88IP10S0891">Lenovo Ideapad 120S</a>
for ~$150USD, a relative steal even for such a puny machine. I’m compiling my notes on hardware support under Linux and OpenBSD as I go.</p>
<h2 id="general-notes">General notes</h2>
<p>The trackpad is pretty great for a cheap laptop, but not as nice as a MacBook’s. Windows 10 performance is terrible; taking multiple hours to install Windows updates. Battery life is good; right now I’m seeing ~7 hours reported remaining at 90% capacity
under Debian.</p>
<h3 id="keyboard">Keyboard</h3>
<p>The keyboard is of a chiclet design, similar to current generation MacBooks. Key caps are slightly rounded on the bottom, giving the keys a shield shape. The position of the Fn and Ctrl keys is my major complaint with the keyboard; they should be swapped.</p>
<table border="1" style="width: auto">
<tbody><tr>
<td colspan="9">Esc</td>
<td colspan="8">F1</td>
<td colspan="8">F2</td>
<td colspan="8">F3</td>
<td colspan="8">F4</td>
</tr>
<tr>
<td colspan="8">~</td>
<td colspan="10">1</td>
<td colspan="10">2</td>
<td colspan="10">3</td>
<td colspan="10">4</td>
</tr>
<tr>
<td colspan="10">Tab</td>
<td colspan="10">Q</td>
<td colspan="10">W</td>
<td colspan="10">E</td>
<td colspan="10">R</td>
</tr>
<tr>
<td colspan="12">CapsLk</td>
<td colspan="10">A</td>
<td colspan="10">S</td>
<td colspan="10">D</td>
</tr>
<tr>
<td colspan="18">Shift</td>
<td colspan="10">Z</td>
<td colspan="10">X</td>
<td colspan="10">C</td>
</tr>
<tr>
<td colspan="10">Ctrl</td>
<td colspan="10">Fn</td>
<td colspan="10">❖</td>
<td colspan="10">Alt</td>
<td colspan="48"> </td>
</tr>
</tbody></table>
<h3 id="storage">Storage</h3>
<p>The device comes with Windows 10 Home 64-bit installed on it. The hard drive appears to be a 64 GB MMC reader. The drive is partitioned with:</p>
<ul>
<li>an <a href="https://en.wikipedia.org/wiki/EFI_system_partition">EFI partition</a>, ~256 MB FAT</li>
<li>a <a href="https://en.wikipedia.org/wiki/Microsoft_Reserved_Partition">Microsoft Reserved Partition</a>, 16 MB</li>
<li>an OEM recovery partition, 10 GB NTFS</li>
<li>the Windows 10 Home partition, ~50 GB NTFS</li>
</ul>
<p>Although it would be possible to resize the NTFS partition to 35 GB or so and install another operating system alongside Windows,
I chose to nuke the entire partition map so that I wouldn’t be space constrained.</p>
<h3 id="biosefi">BIOS/EFI</h3>
<p>The system has a small button on the right hand side you may depress with a paper clip to enter the BIOS/EFI configuration screen.
Disable “Secure Boot” before attempting to install another operating system. You may also adjust the boot order here.
Insert your USB drive before entering the configuration menu and allow it to boot first.</p>
<p>There are also options related to enabling virtualization features (I turned these on).</p>
<h2 id="debian-testing-buster">Debian Testing (buster)</h2>
<p>The install image I picked was <a href="https://cdimage.debian.org/cdimage/weekly-builds/amd64/iso-cd/"> debian-testing-amd64-netinst.iso </a>;
I burned it to a flash drive by running (on a Mac)</p>
<pre class="code">
sudo dd if=debian-testing-amd64-netinst.iso of=/dev/diskXXX bs=1m
</pre>
<p>The trackpad did not work during install, so I used the text-based installer. It was totally functional upon reboot.</p>
<p>You will be prompted for <a href="https://wiki.debian.org/Firmware">missing non-free firmware</a> for the Atheros wireless card.
I followed the instructions and loaded the firmware via a second USB drive.
The installer prompted me again for missing firmware, this time with a much shorter list (including ath10k). You may safely proceed.
If your installer prompts you to manually select your network card (instead of proceeding to WiFi configuration),
you messed up; restart the installation.</p>
<h2 id="openbsd">OpenBSD</h2>
<p><em>So far I’ve tried 6.2 and a snapshot of pre-release 6.3. I will note where 6.3 differs. This section is subject to revision</em></p>
<p>Attempting to boot with the system in EFI mode results in a blank screen after the <code class="language-plaintext highlighter-rouge">BOOTX64</code> prompt. Booting using BIOS gets you into the installer in 6.2.
6.3 successfully boots the installer using EFI.</p>
<p>The internal storage is an MMC device, which although supposedly supported & and visible in the dmesg,
does not show up as a <code class="language-plaintext highlighter-rouge">sd</code> device. We are unable to proceed further unless we install to a USB drive.
The Atheros wireless isn’t supported and shows up as<label for="sn-62-dmesg" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-62-dmesg" class="margin-toggle" />
<span class="sidenote">See <a href="http://dmesgd.nycbug.org/index.cgi?do=view&id=3526"><code class="language-plaintext highlighter-rouge">dmesg</code></a></span>:</p>
<pre class="code">
vendor "Atheros", unknown product 0x0042 (class network subclass miscellaneous, rev 0x31) at pci2 dev 0 function 0 not configured
</pre>
<p>I’ve noticed problems with mass storage devices not showing up, which seems to relate to the USB hub</p>
<pre class="code">
uhub0: device problem, disabling port 5
</pre>
<p>After installing 6.3 snapshot to a second USB drive, selecting the USB drive as the boot target and booting,
we see the regular 6.3 multiprocessor kernel spin up as expected. After a bit, we get a blank screen like we did
booting the 6.2 install with BIOS and are unable to proceed further.</p>
Forcing Clang to statically link against an installed library2018-02-02T00:00:00+00:00https://jonwillia.ms/2018/02/02/static-linking
<p>When building <a href="/2018/01/22/rtl_sdr-vcvrack">redistributable binary plugins</a>,
we cannot rely on the end user having installed library dependencies.
In my case, I need my code to be statically linked against libusb and librtlsdr.</p>
<p>In the past, the venerable <a href="https://gcc.gnu.org">GCC</a> allowed us to specify static linking by specifying a switch
such as <code class="language-plaintext highlighter-rouge">-l:rtlsdr</code> and dynamic linking with <code class="language-plaintext highlighter-rouge">-lrtlsdr</code>. The modern <a href="http://llvm.org">LLVM</a> Clang compiler
is now the C/C++ compiler of choice on many platforms including in Apple’s Xcode.
Its linker lacks an option to force static linking when resolving a library passed in.
When both a static and dynamic version of a library exist, we must explicitly pass the path to the static library if we wish to link statically with Clang.</p>
<h3 id="static-linking-with-full-paths">Static linking with full paths</h3>
<pre class="code">
c++ -o plugin.dylib object.cpp.o … /usr/local/Cellar/libusb/1.0.21/lib/libusb-1.0.a /usr/local/Cellar/librtlsdr/0.5.3/lib/librtlsdr.a
</pre>
<h3 id="dynamic-linking-no-full-paths-needed">Dynamic linking (no full paths needed)</h3>
<pre class="code">
c++ -o plugin.dylib object.cpp.o … -lusb-1.0 -lrtlsdr -lusb-1.0
</pre>
<p>Getting your build system (e.g. <code class="language-plaintext highlighter-rouge">make</code>) to determine the exact path to a static version of a library can be trying.
Fortunately many modern packages include <a href="https://www.freedesktop.org/wiki/Software/pkg-config/">pkg-config</a>
which will let you do things such as the following in your Makefile</p>
<pre class="code">
PKGCONFIG= pkg-config
PACKAGES= libusb-1.0 librtlsdr
# FLAGS will be passed to both the C and C++ compiler
FLAGS += $(shell $(PKGCONFIG) --cflags $(PACKAGES))
</pre>
<p>The compiler will get the flags
<code class="language-plaintext highlighter-rouge">-I/usr/local/Cellar/librtlsdr/0.5.3/include/ -I/usr/local/Cellar/libusb/1.0.21/include/libusb-1.0</code>
to set the include path. pkg-config also can be used to set linker flags with <code class="language-plaintext highlighter-rouge">--libs</code>; however,
in my experience the <code class="language-plaintext highlighter-rouge">--static</code> option does not correctly emit static linking options.
My trick for getting make to emit the correct static linking options is:</p>
<pre class="code">
LDFLAGS +=$(shell $(PKGCONFIG) --variable=libdir libusb-1.0)/libusb-1.0.a
LDFLAGS +=$(shell $(PKGCONFIG) --variable=libdir librtlsdr)/librtlsdr.a
</pre>
<p>I’ve had varying success with passing static libraries on the command line,
<a href="https://github.com/WIZARDISHUNGRY/vcvrack-rtlsdr/issues/26">particularly running into problems on Linux</a>,
but it works on Windows (with some conditional code for library naming) and Mac.</p>
<p>If a library lacks pkg-config, you could parse the output of <code class="language-plaintext highlighter-rouge">ld -v</code> to enumerate search paths for <code class="language-plaintext highlighter-rouge">find</code>.</p>
<h3 id="validating-static-linking">Validating Static Linking</h3>
<table>
<thead>
<tr>
<th>Windows</th>
<th>Linux</th>
<th>Mac</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="language-plaintext highlighter-rouge">objdump file.dll</code></td>
<td><code class="language-plaintext highlighter-rouge">ldd file.so</code></td>
<td><code class="language-plaintext highlighter-rouge">otool -L file.dylib</code></td>
</tr>
</tbody>
</table>
RTL SDR FM module for VCVRack2018-01-22T00:00:00+00:00https://jonwillia.ms/2018/01/22/rtl_sdr-vcvrack
<p><label for="mn-pages" class="margin-toggle">⊕</label>
<input type="checkbox" id="mn-pages" class="margin-toggle" />
<span class="marginnote">
Download:<br />
<a href="https://vcvrack.com/plugins.html#SDR">Binaries</a>
<br />
<a href="https://github.com/WIZARDISHUNGRY/vcvrack-rtlsdr">Source</a>
</span></p>
<p>As <a href="/2017/12/15/vcvrack">previously mentioned</a>, I’ve been developing modules for the <a href="https://vcvrack.com">VCVRack</a> software modular environment.
Today I’m releasing the compiled version of RTL_SDR, a voltage-controlled FM radio
tuner for <a href="https://rtlsdr.org">RTL-SDR dongles</a>. You should be able to find one on Amazon for cheap.</p>
<p>
<figure class="iframe-wrapper">
<iframe width="560" height="315" src="https://www.youtube.com/embed/TGQdgvlga-Q" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>
</figure>
</p>
Messing around with VCVRack2017-12-15T00:00:00+00:00https://jonwillia.ms/2017/12/15/vcvrack
<p><strong>Update</strong>: <a href="/2018/03/23/rhombic-shift-register">Beta version available.</a>
<del><mark>The source code for this is missing right now because I'm working on commercial VCVRack development.</mark></del></p>
<p>I’ve been excited by all the activity around <a href="https://vcvrack.com">VCVRack</a>, an open source software <a href="">modular synthesizer</a> for desktop computers.
I decided to try my hand at writing a simple <a href="https://sites.google.com/site/westcoastsynthesis/asr">shift register</a> module for it.
The guts of a module is a C++ function that gets called once per output sample.
Implementation was dead-simple; perhaps I’ll expand on this module.</p>
<figure class="iframe-wrapper">
<iframe width="560" height="315" src="https://www.youtube.com/embed/I5DKvgAWdto" frameborder="0" gesture="media" allow="encrypted-media" allowfullscreen=""></iframe>
</figure>
<p><strong>The panel SVG labels do not match function!</strong> From top to bottom, the components are:</p>
<ul>
<li>Trigger in + trigger led</li>
<li>CV in</li>
<li>4x Shift out + CV led (would be nice to have this indicate scale degree with an RGB value)</li>
</ul>
<p><del><a href="https://github.com/WIZARDISHUNGRY/vcvrack-plugins">Source Code</a></del></p>
Ableton Drum Rack for MFB-5222017-11-29T00:00:00+00:00https://jonwillia.ms/2017/11/29/mfb-522
<p>
<span class="marginnote"><a href="/assets/uploads/Core 808 to MFB-522.adg">Core 808 to MFB-522.adg</a> (6.0K)</span>
I've been spending time working on music stuff, and decided to share my Ableton Drum Rack for the MFB-522 drum machine.
</p>
<p><img src="/assets/images/MFB-522.jpg" alt="MFB-522 Drumcomputer" /></p>
<p>The MFB-522 Drumcomputer is a discontinued drum machine from <a href="http://mfberlin.de/">MFB</a> that sounds very similar to the Roland TR-808.
While you can sequence it internally, I prefer to use Ableton Live or an Arturia Beatstep Pro to control it.
To program drum sequences when I only have my laptop, I like to use a sample-based Ableton drum rack such as <em>Kit-Core 808</em>.
You should be able to drop this Ableton rack onto your Core Drum sequence to sequence your MFB-522.</p>
<p>The MFB-522’s MIDI implementation uses a layout similar to the <a href="https://en.wikipedia.org/wiki/General_MIDI#Percussion">General MIDI percussion mapping</a> but deviating on number of notes.
Furthermore, the MIDI implementation chart inside the <a href="http://mfberlin.de/wp-content/uploads/mfb-522_english.pdf">MFB-522 manual (PDF)</a>
has several errors in which notes/note numbers correspond to a particular drum sound; keep this in mind when referring to it.
Ableton’s core drum racks seem to follow General MIDI Percussion, with their main kick drum located at <strong>C1</strong>.
I’ve omitted the 808 <em>maracas</em> (doesn’t have an equivalent in the MFB-522) and <em>congas</em> (triggered by the <em>toms</em> when a two-position switch is flipped).
The <em>short clap</em> and <em>short bass drum</em> have been mapped an octave below the regular <em>clap</em> and <em>bass drum</em>.</p>
<p><img src="/assets/images/Core 808 to MFB-522.png" alt="Core 808 to MFB-522" /></p>
Two-Factor Authentication for OpenBSD2016-04-22T00:00:00+00:00https://jonwillia.ms/2016/04/22/2fa-openbsd-ssh-google-authenticator
<p><em>This post is adapted from my <a href="https://github.com/WIZARDISHUNGRY/totp-util/wiki/OpenBSD-Guide">OpenBSD guide</a> in the <a href="https://github.com/WIZARDISHUNGRY/totp-util">totp-util</a> wiki.</em></p>
<p>I recently set up a semi-public OpenBSD box, and thought I could stand to lock down password logins, especially for the root user.
A popular system for two-factor authentication is <a href="https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm">TOTP</a>:</p>
<blockquote>
<p>In a typical two-factor authentication application, user authentication proceeds as follows: a user enters username and password into a website or other server, generates a one-time password for the server using TOTP running locally on a smartphone or other device, and types that password into the server as well. The server then also runs TOTP to verify the entered one-time password. For this to work, the clocks of the user’s device and the server need to be roughly synchronized (the server will typically accept one-time passwords generated from timestamps that differ by ±1 time interval from the client’s timestamp). A single secret key, to be used for all subsequent authentication sessions, must have been shared between the server and the user’s device over a secure channel ahead of time. If some more steps are carried out, the user can also authenticate the server using TOTP.</p>
</blockquote>
<p>I wrote <a href="https://github.com/WIZARDISHUNGRY/totp-util">totp-util</a> to simplify the process of setting up Google Authenticator on UNIX systems.</p>
<h2 id="install-utilities">Install utilities</h2>
<pre class="code">
npm install -g https://github.com/WIZARDISHUNGRY/totp-util
pkg_add login_oath
</pre>
<h2 id="user-setup">User setup</h2>
<ul>
<li>run <code class="language-plaintext highlighter-rouge">totp-util</code> to setup <code class="language-plaintext highlighter-rouge">~/.totp-key</code></li>
<li>Scan the code in Google Authenticator</li>
</ul>
<h2 id="setup-authentication-and-ssh">Setup authentication and SSH</h2>
<p>We’re assuming everyone on the server is using SSH key authentication.
Change <code class="language-plaintext highlighter-rouge">/etc/login.conf</code> to force TOTP+password when not using public key authentication.</p>
<pre class="code">
# Default allowed authentication styles
auth-defaults:auth=-totp-and-pwd,skey:
</pre>
<p>Edit <code class="language-plaintext highlighter-rouge">/etc/ssh/sshd_config</code> to force SSH logins by root to use <strong>both</strong> an ssh key and a totp/password.</p>
<pre class="code">
Match User root
AuthenticationMethods publickey,password
</pre>
<p>Restart sshd and update the login capabilities database.</p>
<pre class="code">
/etc/rc.d/sshd restart
cap_mkdb /etc/login.conf
</pre>
<p>Now regular users should be able to authenticate with just SSH (or a password plus TOTP token) but root will need password, ssh-key and a TOTP token.</p>
<h2 id="logging-in">Logging in</h2>
<pre class="code">
$ ssh root@machine
Authenticated with partial success.
user@machine's password: 123456/password
</pre>
dump1090 on OpenBSD2016-03-31T00:00:00+00:00https://jonwillia.ms/2016/03/31/dump1090
<p>Fixed <a href="https://github.com/mutability/dump1090">dump1090</a> <a href="https://github.com/mutability/dump1090/pull/111">compilation on OpenBSD</a>.</p>
dump1090 on OpenBSD2016-03-31T00:00:00+00:00https://jonwillia.ms/2016/03/31/dump1090-openbsd
<p>Fixed <a href="https://github.com/mutability/dump1090">dump1090</a> <a href="https://github.com/mutability/dump1090/pull/111">compilation on OpenBSD</a>.</p>
Block Tweet Sponsors2014-09-29T00:00:00+00:00https://jonwillia.ms/2014/09/29/block-tweet-sponsors
<p><mark><em>April, 2016: </em> this is broken, likely by a Twitter API change.</mark></p>
<blockquote class="twitter-tweet" lang="en"><p><a href="https://twitter.com/twitter">@twitter</a> Stop putting things in my feed from people I don't follow. Hurts the signal-to-noise ratio and decreases utility of the platform.</p>
<footer>— Nullsleep (@Nullsleep) <a href="https://twitter.com/Nullsleep/status/512267449312751616">September 17, 2014</a></footer></blockquote>
<p>I find the promoted tweets on Twitter super annoying, so I threw together a PHP (sorry!) script to block <a href="https://github.com/WIZARDISHUNGRY/block-tweet-sponsors">tweet sponsors</a>. If anyone has an idea on how to get the mobile timeline so I never see ads for mobile games with in-purchasing, hit me up.</p>
Fix Joli ORM for Titanium SDK2014-09-12T00:00:00+00:00https://jonwillia.ms/2014/09/12/jolijs
<p><a href="https://github.com/xavierlacot/joli.js/pull/44">Fixed</a> <a href="https://github.com/xavierlacot/joli.js">Joli.js</a>
for newer versions of Titanium SDK.</p>