<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="https://chris.harrington.mn/feed.xml" rel="self" type="application/atom+xml" /><link href="https://chris.harrington.mn/" rel="alternate" type="text/html" /><updated>2023-04-19T00:07:32+00:00</updated><id>https://chris.harrington.mn/feed.xml</id><title type="html">Chris Harrington</title><subtitle>Chris Harrington is an AWS Certified DevOps Engineer in Minneapolis, MN with experience in Go (aka Golang), PostgreSQL, JavaScript, C, and other languages.</subtitle><entry><title type="html">AWS Certified</title><link href="https://chris.harrington.mn/meta/2023/04/05/aws-devops-certification.html" rel="alternate" type="text/html" title="AWS Certified" /><published>2023-04-05T00:00:00+00:00</published><updated>2023-04-05T00:00:00+00:00</updated><id>https://chris.harrington.mn/meta/2023/04/05/aws-devops-certification</id><content type="html" xml:base="https://chris.harrington.mn/meta/2023/04/05/aws-devops-certification.html">&lt;p&gt;Well it has been a bit. Like most professional blogs, this fell disused while I was focused on my “real job”.&lt;/p&gt;

&lt;p&gt;In the mean time, though, I am happy to share that I’ve been certified as both an &lt;a href=&quot;https://aw.certmetrics.com/amazon/public/verification.aspx?code=026THM1K4MV11GS8&quot;&gt;AWS Certified Cloud Practitioner&lt;/a&gt; and an &lt;a href=&quot;https://aw.certmetrics.com/amazon/public/verification.aspx?code=0H7WWYWC9BFEQZ3N&quot;&gt;AWS Certified DevOps Engineer - Professional&lt;/a&gt;. I’m continuing to work on building skills, and I’m taking the exam to become an &lt;em&gt;AWS Certified Solutions Architect - Associate&lt;/em&gt; today. We’ll see how that goes. (&lt;strong&gt;Update&lt;/strong&gt;: It went well! I’m now also an &lt;a href=&quot;https://aw.certmetrics.com/amazon/public/verification.aspx?code=0GL7T5G1WNR11S96&quot;&gt;AWS Certified Solutions Architect - Associate&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;One thing I took away from the DevOps Engineer certification exam is that I do not know nearly enough about the AWS CodeCommit, CodeBuild, CodePipeline, and CodeDeploy products. So as a personal project, I’m thinking I’ll propose a CodePipeline process at work, to wean us away from our current CI/CD vendor. Could be fun, and I hope to learn from the process.&lt;/p&gt;</content><author><name></name></author><category term="meta" /><category term="journal," /><category term="aws" /><summary type="html">Well it has been a bit. Like most professional blogs, this fell disused while I was focused on my “real job”.</summary></entry><entry><title type="html">Directly accessing USB storage in WSL2 Debian</title><link href="https://chris.harrington.mn/project/2022/07/30/wsl2-usb-storage.html" rel="alternate" type="text/html" title="Directly accessing USB storage in WSL2 Debian" /><published>2022-07-30T00:00:00+00:00</published><updated>2022-07-30T00:00:00+00:00</updated><id>https://chris.harrington.mn/project/2022/07/30/wsl2-usb-storage</id><content type="html" xml:base="https://chris.harrington.mn/project/2022/07/30/wsl2-usb-storage.html">&lt;p&gt;I needed to access a USB storage device in WSL2 so I could install LVM and LUKS on it. The only full Linux boxes I have at my disposal don’t have USB-C, and I didn’t have an adapter, so naturally I turned to the most absurd and indirect way to solve this problem. I followed some guides partially, including &lt;a href=&quot;https://devblogs.microsoft.com/commandline/connecting-usb-devices-to-wsl/&quot;&gt;this Microsoft blog&lt;/a&gt; and &lt;a href=&quot;https://unix.stackexchange.com/a/702288/128767&quot;&gt;this Stack Exchange answer&lt;/a&gt;, but none matched my situation exactly.&lt;/p&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;
&lt;p&gt;The core USB-passthrough function in WSL2 is powered by USB-IP. This is a protocol that translates USB traffic into IP traffic. WSL2 uses this to host a USB device on the Windows side as a server, and then connects the device on the Linux side as a client. Once the USB device is present on the Linux side, though, the current kernel implementation &lt;abbr title=&quot;Of course, it usually doesn't need to; WSL2 supports mounting folders inside through the normal file-sharing mechanism, assuming you'd use the Windows USB Storage support.&quot;&gt;does not provide USB storage&lt;/abbr&gt; out of the box.&lt;/p&gt;

&lt;p&gt;That means we’ll need to complete a handful of tasks:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Get the WSL2 Kernel and build the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usb-storage&lt;/code&gt; module&lt;/li&gt;
  &lt;li&gt;Install the Linux-side USB-IP tool&lt;/li&gt;
  &lt;li&gt;Install the Windows-side USB-IP tool/driver&lt;/li&gt;
  &lt;li&gt;Connect the device to Linux&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;my-situation&quot;&gt;My situation&lt;/h2&gt;
&lt;p&gt;You should always use caution following any guide on the internet. In particular, this guide may be out of date by the time you read it. (It may also be malicious, so be careful to verify anything a guide asks you to download or run.) So you can follow along, I have WSL2 running Debian. Inside of Linux, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat /etc/issue&lt;/code&gt; reports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Debian GNU/Linux 11&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uname -r&lt;/code&gt; reports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5.10.102.1-microsoft-standard-WSL2&lt;/code&gt;. I’m currently running Windows 10 21H2 (as reported by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;winver&lt;/code&gt;). If yours are similar, you’ll probably have success following this guide.&lt;/p&gt;

&lt;h2 id=&quot;wsl-prerequisites&quot;&gt;WSL prerequisites&lt;/h2&gt;
&lt;p&gt;Because we’re going to be doing stuff that is sensitive to the kernel version you’re using, it’s best if we get the most up-to-date WSL2 kernel available before we go too far. This update actually happens in Windows: you will need to run this in an elevated (Administrator) Command Prompt or PowerShell:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wsl --update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;debian-prerequisites&quot;&gt;Debian prerequisites&lt;/h2&gt;
&lt;p&gt;Next, before we try to build a kernel, you will want to get some things installed by running inside your Debian shell:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install build-essential flex bison libssl-dev bc libelf-dev usb-ip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This sets up Debian to be able to compile the kernel as well as grabbing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usb-ip&lt;/code&gt; Linux software while we’re at it.&lt;/p&gt;

&lt;h2 id=&quot;linux-kernel-usb-storage-module&quot;&gt;Linux Kernel usb-storage module&lt;/h2&gt;
&lt;p&gt;If you’re relatively seasoned with Linux, you might be &lt;abbr title=&quot;For your benefit and mine -- mostly mine -- I don't host comments here. If you do have feedback, please don't hesitate to send them to my email: chris@harrington.mn&quot;&gt;searching for the comment section&lt;/abbr&gt; already. “Wait, why not use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uas&lt;/code&gt;? It’s way faster.” This is correct, and that was my initial goal. However, it turns out that the &lt;a href=&quot;https://gist.github.com/ironiridis/d515faecc1a2c063b297600d33dbfa24&quot;&gt;Linux USB-IP host controller is incompatible&lt;/a&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uas&lt;/code&gt;, and so we will need to fall back to the more basic variation, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usb-storage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To start, we need to check out the WSL2 kernel source code inside the Debian shell. This will take a long time. The filesystem on WSL2 is not very fast, and the git operation does millions of filesystem operations and downloads gigs of data.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/microsoft/WSL2-Linux-Kernel.git ~/wsl2k &amp;amp;&amp;amp; cd ~/wsl2k
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Have you done this step before? Jump down to the &lt;a href=&quot;#reuse-git-repo&quot;&gt;cleaning up section&lt;/a&gt; for how to reuse your git repo to save yourself some time.)&lt;/p&gt;

&lt;p&gt;Now, we need to check out the specific kernel revision that matches your running kernel. This one-liner tries to construct the right tag and branch to match your kernel. Don’t worry if this doesn’t work, since it will just keep using the default branch’s code, which should still match the kernel you updated to earlier (by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wsl --update&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export wslBranch=$(git ls-remote --symref https://github.com/microsoft/WSL2-Linux-Kernel.git HEAD | grep -oE 'refs/heads/[^[:space:]]+' | cut -f 3 -d /)
export wslTag=tags/linux-msft-wsl-$(grep -oE '([0-9]+\.?)+' /proc/version | head -n1)
git checkout -f -B $wslBranch $wslTag
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we’ll unpack the existing kernel configuration, and make a couple of changes. The first is to disable the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BTF&lt;/code&gt; debugging data, as it requires software that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get&lt;/code&gt; refused to install for me. The second is to explicitly enable the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usb-storage&lt;/code&gt; module. The last line runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yes&lt;/code&gt; (but &lt;abbr title=&quot;The &amp;quot;yes&amp;quot; utility just outputs its argument over and over again. We use &amp;quot;yes&amp;quot; to respond &amp;quot;no&amp;quot; to the kernel configuration tool, because we don't want any of the optional choices compiled in.&quot;&gt;paradoxically&lt;/abbr&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt;) to update the kernel configuration to be satisfied with the previous change.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gunzip &amp;lt; /proc/config.gz &amp;gt; .config
sed -ix 's/CONFIG_DEBUG_INFO_BTF=y/CONFIG_DEBUG_INFO_BTF=n/' .config
sed -ix 's/^.*CONFIG_USB_STORAGE.*$/CONFIG_USB_STORAGE=m/' .config
yes n | make oldconfig
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, we compile. If you have lots of CPU power, you can replace the first &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make -jX&lt;/code&gt; where X is the number of cores you want to use, and the compile may run faster.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;make modules &amp;amp;&amp;amp; sudo make modules_install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another surprising problem I encountered was to find that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modules_install&lt;/code&gt; does not use the same location that the kernel expects. I ended up with modules in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/lib/modules/5.10.102.1-microsoft-standard-WSL2+&lt;/code&gt; but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modprobe&lt;/code&gt; wanted no plus symbol at the end. So you may also need to copy your module directory into the right place.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo cp -av /lib/modules/$(uname -r)+ /lib/modules/$(uname -r)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, to make sure the module compiled correctly and is matched with your kernel, probe it. If this command outputs nothing, you should be in business.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo modprobe usb-storage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Keep this terminal open in the background so your Debian VM doesn’t suspend.&lt;/p&gt;

&lt;h2 id=&quot;windows-usb-ip&quot;&gt;Windows USB-IP&lt;/h2&gt;
&lt;p&gt;You’ll want to download and install &lt;a href=&quot;https://github.com/dorssel/usbipd-win/releases&quot;&gt;the latest release of usbipd-win&lt;/a&gt;. I used &lt;a href=&quot;https://github.com/dorssel/usbipd-win/releases/tag/v2.3.0&quot;&gt;usbipd-win 2.3.0&lt;/a&gt; because it was the most current at the time. This will require administrative access on Windows. I didn’t need to reboot, so you probably don’t need to either.&lt;/p&gt;

&lt;p&gt;Before you continue, note that Windows has probably mounted your USB storage or is otherwise hanging on to it. You will want to &lt;a href=&quot;https://support.microsoft.com/en-us/windows/safely-remove-hardware-in-windows-1ee6677d-4e6c-4359-efca-fd44b9cec369&quot;&gt;safely eject the device&lt;/a&gt; first, to minimize the risk that something will be upset (either Windows &lt;em&gt;or&lt;/em&gt; Linux).&lt;/p&gt;

&lt;p&gt;You’ll need to identify which device ID you want to connect to Linux. The easiest way is to open an elevated (Administrator) Command Prompt or PowerShell, list the devices, identify the “bus ID” of the device, and then attach it. In my case, the ID of my storage device was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1-13&lt;/code&gt;. You will need to substitute your own.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;usbipd wsl list
usbipd wsl attach --busid 1-13
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If all goes according to plan, you should be able to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usbipd wsl list&lt;/code&gt; again and see the state of your device change to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Attached - Debian&lt;/code&gt;. If so, you should be able to swing back over to your Debian terminal and do the normal things, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dmesg&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lsblk&lt;/code&gt; to see your device.&lt;/p&gt;

&lt;h2 id=&quot;cleaning-things-up&quot;&gt;Cleaning things up&lt;/h2&gt;
&lt;p&gt;The kernel sources we checked out in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/wsl2k&lt;/code&gt;, alongside the built object files from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt;, clock in at a staggering 6.2 gigabytes in my case. If everything is working for you, you can clear this out in your Debian shell. Note that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-f&lt;/code&gt; flag here is because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt; will create read-only files which &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm&lt;/code&gt; will otherwise prompt you about.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ; rm -rf ~/wsl2k
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p id=&quot;reuse-git-repo&quot;&gt;Note that WSL2 automatically updates kernels behind your back. As a result, you may find that you are unable to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo modprobe usb-storage&lt;/code&gt; anymore in the future. If that’s the case, you will need the kernel sources again. If you anticipate needing the direct USB Storage ability in the future, you should hang on to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/wsl2k&lt;/code&gt; directory. In the &lt;a href=&quot;#linux-kernel-usb-storage-module&quot;&gt;directions above&lt;/a&gt;, instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone ... ; cd ~/wsl2k&lt;/code&gt; do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cd ~/wsl2k ; git fetch ; git reset --hard ; make distclean&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you don’t need to build kernels or anything anymore, you can also remove the packages we installed. Just replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get install&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get remove&lt;/code&gt;. However, be warned: you may have actually been using some of these packages already for other purposes. If in any doubt, just leave them in place. You also likely want to leave &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usb-ip&lt;/code&gt; installed unless you’re certain you won’t be using the USB passthrough function again.&lt;/p&gt;

&lt;p&gt;To disconnect your USB device from the passthrough mechanism, you can run this in an elevated (Administrator) Command Prompt or PowerShell. Make sure you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unmount&lt;/code&gt; any USB storage filesystems you have mounted in Debian first. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--all&lt;/code&gt; does just what it says, although if you only want to disconnect one passed-through device, specify the ID as before with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--busid 1-13&lt;/code&gt; instead. (Naturally, substitute your ID from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usbipd wsl list&lt;/code&gt;.)&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;usbipd wsl detach --all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name></name></author><category term="project" /><category term="wsl2" /><category term="usb" /><category term="debian" /><category term="linux" /><summary type="html">I needed to access a USB storage device in WSL2 so I could install LVM and LUKS on it. The only full Linux boxes I have at my disposal don’t have USB-C, and I didn’t have an adapter, so naturally I turned to the most absurd and indirect way to solve this problem. I followed some guides partially, including this Microsoft blog and this Stack Exchange answer, but none matched my situation exactly.</summary></entry><entry><title type="html">Creating a DNS-based URL Shortener [Part 1]</title><link href="https://chris.harrington.mn/project/urlshorten/2022/05/09/url-shortener.html" rel="alternate" type="text/html" title="Creating a DNS-based URL Shortener [Part 1]" /><published>2022-05-09T00:00:00+00:00</published><updated>2022-05-09T00:00:00+00:00</updated><id>https://chris.harrington.mn/project/urlshorten/2022/05/09/url-shortener</id><content type="html" xml:base="https://chris.harrington.mn/project/urlshorten/2022/05/09/url-shortener.html">&lt;p&gt;It’s 2022, and nobody wants to use URL shorteners anymore. They are &lt;a href=&quot;https://en.wikipedia.org/wiki/URL_shortening#Privacy_and_security&quot;&gt;user-hostile&lt;/a&gt;, harmful to &lt;a href=&quot;https://en.wikipedia.org/wiki/Link_rot&quot;&gt;the health of the open internet&lt;/a&gt;, and &lt;a href=&quot;https://en.wikipedia.org/wiki/t.co&quot;&gt;virtually unnecessary&lt;/a&gt;. So let’s make one.&lt;/p&gt;

&lt;p&gt;To avoid an aphorism, there are a lot of ways to accomplish this task. I wanted to play around with AWS Route 53 a bit, so that’s where I started when I wrote &lt;a href=&quot;https://github.com/ironiridis/drin&quot;&gt;the code&lt;/a&gt; we’ll discuss.&lt;/p&gt;

&lt;h2 id=&quot;what-well-use&quot;&gt;What we’ll use&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Go 1.18+&lt;/li&gt;
  &lt;li&gt;AWS
    &lt;ul&gt;
      &lt;li&gt;ACM&lt;/li&gt;
      &lt;li&gt;API Gateway&lt;/li&gt;
      &lt;li&gt;Graviton2&lt;/li&gt;
      &lt;li&gt;Lambda&lt;/li&gt;
      &lt;li&gt;Route 53&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;DNS
    &lt;ul&gt;
      &lt;li&gt;You should have tools like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dig&lt;/code&gt; available&lt;/li&gt;
      &lt;li&gt;You can alternately use &lt;a href=&quot;https://mxtoolbox.com/SuperTool.aspx&quot;&gt;mxtoolbox&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Your domain, if you have one
    &lt;ul&gt;
      &lt;li&gt;In this post I’ll refer to it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example.com&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;If you don’t have a domain connected to Route 53, you can buy one&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;set-up-the-aws-services&quot;&gt;Set up the AWS Services&lt;/h2&gt;
&lt;p&gt;Part 1 of this series will be configuring your AWS account. If you already know your way around AWS, you can probably skip it, but this part will cover every AWS step needed to get working.&lt;/p&gt;

&lt;h3 id=&quot;making-sure-route-53-and-your-domain-agree&quot;&gt;Making sure Route 53 and your domain agree&lt;/h3&gt;
&lt;p&gt;First, you’ll want to have your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example.com&lt;/code&gt; domain pointed at your Route 53 hosted zone name servers. When you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dig -t ns example.com&lt;/code&gt; you should get a set of records that look the same (though not necessarily in the same order) as the NS record in your hosted zone. If it doesn’t look right, then you likely need to configure the authoritative nameservers with your domain registrar.&lt;/p&gt;

&lt;p&gt;Now if you don’t have a domain, it turns out you can just buy one through Amazon’s &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/route53/home#DomainListing:&quot;&gt;Route 53 Domains&lt;/a&gt;. As of this writing, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.link&lt;/code&gt; domains are $5 USD, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.click&lt;/code&gt; domains are $3 USD. Be the owner of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;risky.click&lt;/code&gt; for three bucks! Or don’t, it’s apparently already taken. Anyway, when you buy a domain through AWS, the console will automatically create a Route 53 hosted zone for it, skipping some faffing around.&lt;/p&gt;

&lt;h3 id=&quot;requesting-an-acm-certificate&quot;&gt;Requesting an ACM certificate&lt;/h3&gt;
&lt;p&gt;AWS offers free SSL certificates for services running on their platform. We’ll need one, since it isn’t 1997 anymore. Request one from &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/acm/home?region=us-east-1#/welcome&quot;&gt;ACM in us-east-1&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Click “Request”.&lt;/li&gt;
  &lt;li&gt;Select (or leave selected) “Request a public certificate” and click “Next”.&lt;/li&gt;
  &lt;li&gt;Under “Fully qualified domain name”, you’ll put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.example.com&lt;/code&gt; – again, substituting your real domain here.&lt;/li&gt;
  &lt;li&gt;Choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DNS validation&lt;/code&gt; – Route 53 will make this seamless for you – then click “Request”.&lt;/li&gt;
  &lt;li&gt;You may not be able to choose your pending certificate yet, if not, refresh or visit the &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/acm/home?region=us-east-1#/certificates/list&quot;&gt;certificate list&lt;/a&gt; again.&lt;/li&gt;
  &lt;li&gt;Assuming the certificate is indeed “Pending validation”, you should be able to click “Create records in Route 53”.&lt;/li&gt;
  &lt;li&gt;You should see a summary of the records that will be created on your behalf, click “Create records”.&lt;/li&gt;
  &lt;li&gt;In a minute or two, you should be able to refresh your pending certificate and see it is now Issued.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;creating-your-api-gateway-api&quot;&gt;Creating your API Gateway API&lt;/h3&gt;
&lt;p&gt;Once your certificate is issued, go visit &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/apigateway/main/apis?region=us-east-1&amp;amp;apiId=unselected&quot;&gt;API Gateway in us-east-1&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Click “Create API”, then click “Build” under HTTP API.&lt;/li&gt;
  &lt;li&gt;Under Integrations, skip the “Add integration” button. We’ll do that in a minute.&lt;/li&gt;
  &lt;li&gt;Invent a name that’s meaningful to you. We’ll pretend you wrote &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shorturl&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Click “Review and Create” and then “Create”.&lt;/li&gt;
  &lt;li&gt;You should be looking at your API. (If not, go back to &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/apigateway/main/apis?region=us-east-1&amp;amp;apiId=unselected&quot;&gt;the API list&lt;/a&gt; and click on it.) On the left edge, click “Custom domain names”, then click “Create”.&lt;/li&gt;
  &lt;li&gt;You need a domain name for your shortener. I suggest creating a subdomain for this app, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;short.example.com&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Under ACM certificate, you’re going to select the certificate we requested earlier. It should look like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.example.com&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Click “Create domain name”. You should be looking at your custom domain name now. Click “API mappings” then “Configure API mappings”.&lt;/li&gt;
  &lt;li&gt;Click “Add new mapping”. Under API, select your API, the one we’re pretending you called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shorturl&lt;/code&gt;. Under stage, select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$default&lt;/code&gt;. Click “Save”.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;creating-the-route-53-alias&quot;&gt;Creating the Route 53 Alias&lt;/h3&gt;
&lt;p&gt;Now that you have created an API Gateway API with a custom domain, we can create a record in Route 53 to connect that together.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;In your hosted zone, click “Create record”. If you see a link that says “Switch to quick create”, click that.&lt;/li&gt;
  &lt;li&gt;Under Record name, type just the subdomain part of your domain. So if your domain is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;short.example.com&lt;/code&gt;, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;short&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Under Record type, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; should already be selected. If it isn’t, select it.&lt;/li&gt;
  &lt;li&gt;Next to Value, turn on the toggle switch for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alias&lt;/code&gt;. This section will then read “Route traffic to”.&lt;/li&gt;
  &lt;li&gt;Under Route traffic to, choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alias to API Gateway API&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;In the next dropdown that appears, you’ll choose the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;us-east-1&lt;/code&gt; region, labelled “US East (N. Virginia)”.&lt;/li&gt;
  &lt;li&gt;In the next dropdown that appears, clicking inside should reveal the API you created above. (It may just be a hostname, not the name you gave it.) If nothing appears, you either have the wrong name under “Record name” or you have the wrong name in your API Gateway Custom domain. These must match. Once you do see your API, select it.&lt;/li&gt;
  &lt;li&gt;You can leave everything else as is and click “Create records”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, you should be able to open your web browser and go to your domain &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;short.example.com&lt;/code&gt;. We’re expecting to see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;message&quot;:&quot;Not Found&quot;}&lt;/code&gt;, which is perfect, because API Gateway is responding to our request, but there’s no route yet defined that matches. If that’s the result you get, you’ve done everything correctly up to this step.&lt;/p&gt;

&lt;h3 id=&quot;creating-the-lambda-function&quot;&gt;Creating the Lambda function&lt;/h3&gt;
&lt;p&gt;At the heart of this very silly app is a Lambda function. It is invoked by API Gateway and updates Route 53 in order to service requests. You’ll want to head to &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/functions&quot;&gt;the us-east-1 Lambda page&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Click “Create function”. Leave “Author from scratch” selected.&lt;/li&gt;
  &lt;li&gt;Under Function name, provide a name for your function. It isn’t important, we’ll pretend you called it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shorturlfunc&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Under Runtime, scroll down to and select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Provide your own bootstrap on Amazon Linux 2&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Under Architecture, select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arm64&lt;/code&gt;. This enables the AWS Graviton2 architecture, which is purely optional, but fun.&lt;/li&gt;
  &lt;li&gt;The remaining options are fine if left alone, go ahead and click “Create function”.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;connecting-it-to-api-gateway&quot;&gt;Connecting it to API Gateway&lt;/h3&gt;
&lt;p&gt;Head back to &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/apigateway/main/apis?region=us-east-1&amp;amp;apiId=unselected&quot;&gt;API Gateway in us-east-1&lt;/a&gt;, and select the API you created. It’s time to connect your Lambda function to the internet.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Expand the “Develop” section on the left side if necessary, and click “Routes”. Then click “Create”.&lt;/li&gt;
  &lt;li&gt;Under “Route and Method”, leave the drop-down set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ANY&lt;/code&gt;. In the next field, change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/{x}&lt;/code&gt;. Click “Create”.&lt;/li&gt;
  &lt;li&gt;You should be back to the Routes view, so click “Create” again.&lt;/li&gt;
  &lt;li&gt;This time, change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ANY&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt;. Leave &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt; as is. Click “Create”.&lt;/li&gt;
  &lt;li&gt;On the left side, click “Integrations”. You should be looking at the Integrations view, and one of your routes should be selected.&lt;/li&gt;
  &lt;li&gt;Click “Create and attach an integration”.&lt;/li&gt;
  &lt;li&gt;Under “Attach this integration to a route”, leave the value that’s there.&lt;/li&gt;
  &lt;li&gt;Under “Integration target”, “Integration type”, choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lambda function&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;The “AWS Region” should already be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;us-east-1&lt;/code&gt;. Under “Lambda function”, click inside the field to load functions in that region. You should see the function we created in the last section, and maybe you called it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shorturlfunc&lt;/code&gt;. Select it.&lt;/li&gt;
  &lt;li&gt;Skip everything else and click “Create” at the bottom of the page.&lt;/li&gt;
  &lt;li&gt;You should be back to the Integrations view, and one of your routes should show “AWS Lambda” on it. Select the other route.&lt;/li&gt;
  &lt;li&gt;You should now see a view that offers a choice between creating a new integration or using an existing one. We’ll do the latter.&lt;/li&gt;
  &lt;li&gt;In the dropdown, you should see an option named like your Lambda function (eg &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shorturlfunc&lt;/code&gt;). Select it. Click “Attach integration”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re pretty close now. Now visiting your domain in a browser should produce &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;message&quot;:&quot;Internal Server Error&quot;}&lt;/code&gt;. This doesn’t seem like an improvement, but it is; API Gateway is trying to invoke your function, but something goes wrong. In fact, your Lambda function is missing its bootstrap program. That will come in Part 2; for now, we have a couple of small things to do.&lt;/p&gt;

&lt;h3 id=&quot;short-detour-on-route-53-for-the-zone-id&quot;&gt;Short detour on Route 53 for the Zone ID&lt;/h3&gt;
&lt;p&gt;Route 53 creates a unique identifier for your hosted zone, and we’re going to need it. It looks like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Z01010101S99Z9ZXZXZX2&lt;/code&gt;, and is available in the “Hosted zone details” section above your zone records. You need this value, so &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/route53/v2/hostedzones#&quot;&gt;go get it&lt;/a&gt; before we continue.&lt;/p&gt;

&lt;h3 id=&quot;back-to-lambda-to-finish-setup&quot;&gt;Back to Lambda to finish setup&lt;/h3&gt;
&lt;p&gt;In your Lambda function, we’ll need to tell your function which zone we modify to store URLs.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;While looking at the Lambda function, find the tab strip and click “Configuration”.&lt;/li&gt;
  &lt;li&gt;Under the configuration view, on the left side, find and click “Environment variables”.&lt;/li&gt;
  &lt;li&gt;In the Environment variables view, click “Edit”. Then click “Add environment variable”.&lt;/li&gt;
  &lt;li&gt;Under “Key”, put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ROUTE53_ZONE_ID&lt;/code&gt;. Under “Value”, paste your Zone ID we got above. (Don’t lose it, we need it for one more thing.)&lt;/li&gt;
  &lt;li&gt;Click “Add environment variable” again. Under “Key”, put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ROUTE53_RECORD_TTL&lt;/code&gt;. Under “Value”, put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1h&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Click “Save”.&lt;/li&gt;
  &lt;li&gt;Back on the page for your function, under the configuration view, on the left side, find and click “Permissions”.&lt;/li&gt;
  &lt;li&gt;You should see a section titled “Execution role” with a link under “Role name”. Click that link, we’re headed to IAM.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;modifying-the-lambda-role&quot;&gt;Modifying the Lambda role&lt;/h3&gt;
&lt;p&gt;IAM Roles are short-term assumed identities that are scoped to work with certain services. Lambda uses them in order to grant permissions to functions as they’re executing. We’ll do that here, by granting your function permission to modify Route 53 records.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;You should be looking at the “Permissions” view. Find and click the button labeled “Add permissions”. This will offer two options; click “Create inline policy”.&lt;/li&gt;
  &lt;li&gt;Welcome to the IAM Policy Visual Editor! Click on “Choose a service” and in the search field that appears, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;53&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;There are several “Route 53” services, but we specifically want the original. Click the option that reads “Route 53”.&lt;/li&gt;
  &lt;li&gt;In the search field, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;change&lt;/code&gt;, and check the box next to “ChangeResourceRecordSets”.&lt;/li&gt;
  &lt;li&gt;Expand the “Resources” section, and click “Add ARN”. In the field that appears, paste your Hosted Zone ID. Click “Add”.&lt;/li&gt;
  &lt;li&gt;Click on “Review policy”. For “Name”, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;short-url-lambda-modify-route53&lt;/code&gt;. It doesn’t have to be exact, but policy names are restricted to letters, numbers, and a handful of symbols – no spaces.&lt;/li&gt;
  &lt;li&gt;Click “Create policy”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that’s it!&lt;/p&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;You’ve configured all the AWS pieces more-or-less how they need to be configured. The only thing that is missing is the code to run in the Lambda function. In Part 2, we’ll check out the code, compile it, and deploy it. Then we’ll discuss what it’s doing, identify some flaws, and add a feature.&lt;/p&gt;</content><author><name></name></author><category term="project" /><category term="urlshorten" /><category term="aws" /><category term="route53" /><category term="lambda" /><category term="go" /><summary type="html">It’s 2022, and nobody wants to use URL shorteners anymore. They are user-hostile, harmful to the health of the open internet, and virtually unnecessary. So let’s make one.</summary></entry><entry><title type="html">The Rigol DS1202Z-E does not support GPT on USB storage</title><link href="https://chris.harrington.mn/techsupport/2022/04/16/rigol-ds1202z-e-usb-error.html" rel="alternate" type="text/html" title="The Rigol DS1202Z-E does not support GPT on USB storage" /><published>2022-04-16T00:00:00+00:00</published><updated>2022-04-16T00:00:00+00:00</updated><id>https://chris.harrington.mn/techsupport/2022/04/16/rigol-ds1202z-e-usb-error</id><content type="html" xml:base="https://chris.harrington.mn/techsupport/2022/04/16/rigol-ds1202z-e-usb-error.html">&lt;p&gt;If you happen to own or use a Rigol osilloscope, you may have been a victim of the error message:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flash Drive Not a DOS Disk,Please Format Flash Drive!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I looked up a few threads, including &lt;a href=&quot;https://www.eevblog.com/forum/testgear/rigol-ds1074z-not-recognising-usb-flash-drives/&quot;&gt;this top Google result&lt;/a&gt;, that were all over the place: You didn’t eject the drive before you took it out of your computer. You can’t use USB3 drives with your scope. Some drives just are too big, or too old, or too new, or they simply aren’t compatible for some other mystery reason. The answer these (and other) threads seem to arrive at is always: just keep trying new USB sticks until you find one that works.&lt;/p&gt;

&lt;p&gt;As an IT person, I don’t like this explanation. It doesn’t actually solve any problem, and it definitely doesn’t prevent it from happening in the future. After encountering this issue myself and actually finding the &lt;em&gt;real reason&lt;/em&gt;, I’d like to share it with you.&lt;/p&gt;

&lt;p&gt;My Rigol DS1202Z-E (and possibly other Rigol scopes) does not support USB disks that use a &lt;a href=&quot;https://en.wikipedia.org/wiki/GUID_Partition_Table&quot;&gt;GUID Partition Table&lt;/a&gt;, aka GPT. It’s not worth your time for me to explain what this is, why it’s on your drive, or why you would (or wouldn’t) want it, and so on. The most important part to understand is that you don’t need to buy a new USB stick, and “formatting” the drive, as the on-screen diagnostic requests, will not help. At least, not exactly.&lt;/p&gt;

&lt;h2 id=&quot;fixing-your-usb-drive&quot;&gt;Fixing your USB drive&lt;/h2&gt;
&lt;p&gt;The single most important part of this article is to be adamantly clear: you cannot fix your drive without erasing it. All of the steps below carry the risk of data loss, so you need to proceed carefully.&lt;/p&gt;

&lt;h3 id=&quot;fixing-it-on-windows-10&quot;&gt;Fixing it on Windows 10&lt;/h3&gt;
&lt;p&gt;You’re going to need to open an elevated command prompt or PowerShell window. You can do this by holding the Windows Key and tapping &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt;, you should get a menu offering “Windows PowerShell (Admin)” or “Command Prompt (Admin)”. Both of these are fine, so whichever one you have, select that. If your computer is configured to ask permission before elevating permission (as it should) you will be asked to confirm, either by clicking “Yes” or entering your admin-level password.&lt;/p&gt;

&lt;p&gt;At this point, you want to start with the USB disk disconnected. This is critical; we are dealing with tools that could potentially erase your hard drive, so we need to be certain which drive is which. Once you’ve made sure the USB disk is definitely not inserted into your computer, you’re going to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diskpart&lt;/code&gt; (and press enter) in your admin command window. This will take a moment, and when it’s ready, you will see a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DISKPART&amp;gt;&lt;/code&gt; prompt. Here, you will type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list disk&lt;/code&gt; and press enter; this will display all of the disks in your computer that you absolutely do not want to ruin. Pay attention to the numbers.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/rigol-gpt-usb/usb-disk-diskpart-nousb.PNG&quot; alt=&quot;diskpart list disk, before inserting the usb stick&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and insert your USB stick. Give your computer a moment to collect itself, and go back to that window and type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list disk&lt;/code&gt; again. (Don’t forget to hit enter.) You should see another entry, and the new entry size should be about the size of your USB device. (As a caveat: the companies that make storage devices have changed how they define “gigabyte” over the years, and some of them also slightly fudge their numbers. In this screenshot, my device is labeled “16GB” but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diskpart&lt;/code&gt; says it has “14 GB”.)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/rigol-gpt-usb/usb-disk-diskpart-withusb.PNG&quot; alt=&quot;diskpart list disk, after inserting the usb stick&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note in the screenshot the asterisk under the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gpt&lt;/code&gt; column; this corresponds to a GPT-layout device. This is what is causing my problem with the Rigol. If your USB device does &lt;em&gt;not&lt;/em&gt; have this asterisk, stop here. My instructions may do more harm than good for you. You can just close the command window to exit the disk erasing danger zone.&lt;/p&gt;

&lt;p&gt;The next step is to select the disk. In my screenshots, I am using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disk 2&lt;/code&gt; because that’s where it is showing up for me, but it will likely show up as a different number for you. If your USB device isn’t &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disk 2&lt;/code&gt; then you need to use your disk number in the following command.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select disk 2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;then&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list disk&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/rigol-gpt-usb/usb-disk-diskpart-disk2selected.PNG&quot; alt=&quot;diskpart list disk, after selecting the disk&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This verifies we really, really have the right disk selected. You’ll see an asterisk on the left edge of the disk you selected. Now, be aware: the following step will destroy data. If you have files you want to keep on this USB device, pause here and copy them off the device.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clean&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/rigol-gpt-usb/usb-disk-diskpart-afterclean.PNG&quot; alt=&quot;diskpart clean&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This step erases all the partitions on the device, and prepares the device to convert it to the more basic MBR-layout.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;convert mbr&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/rigol-gpt-usb/usb-disk-diskpart-afterconvert.PNG&quot; alt=&quot;diskpart convert mbr&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This changes the device to use MBR.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list disk&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/rigol-gpt-usb/usb-disk-diskpart-listafterconvert.PNG&quot; alt=&quot;diskpart list disk, after converting to mbr&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now you should see your disk is missing the asterisk in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gpt&lt;/code&gt; column. This means it worked! A couple more steps while we’re here to make the Rigol happy.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create partition primary&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/rigol-gpt-usb/usb-disk-diskpart-aftercreate.PNG&quot; alt=&quot;diskpart create partition primary&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This creates a new partition, which is the thing you actually are working with when you see a Drive in Windows. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:&lt;/code&gt; is a partition on a disk that may have several partitions. This will create an empty partition, we need to format it though. (Note that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;partition 1&lt;/code&gt; here is always going to be the same; we had zero partitions before, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; is the first one which we just created.)&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select paritition 1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format fs=fat32 quick&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/rigol-gpt-usb/usb-disk-diskpart-afterformat.PNG&quot; alt=&quot;diskpart format&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This selects the new partition we made, and then puts a FAT32 filesystem on it. Windows may pop up your new empty USB Drive now, and you should be all set. You can close the Command window, eject the drive, and jam it into your Rigol meter.&lt;/p&gt;</content><author><name></name></author><category term="techsupport" /><category term="electronics" /><summary type="html">If you happen to own or use a Rigol osilloscope, you may have been a victim of the error message:</summary></entry><entry><title type="html">What is a side-channel attack?</title><link href="https://chris.harrington.mn/explainer/2020/07/05/what-is-a-sidechannel-attack.html" rel="alternate" type="text/html" title="What is a side-channel attack?" /><published>2020-07-05T00:00:00+00:00</published><updated>2020-07-05T00:00:00+00:00</updated><id>https://chris.harrington.mn/explainer/2020/07/05/what-is-a-sidechannel-attack</id><content type="html" xml:base="https://chris.harrington.mn/explainer/2020/07/05/what-is-a-sidechannel-attack.html">&lt;p&gt;A side-channel attack is a name for a broad class of different security challenges. The &lt;a href=&quot;https://en.wikipedia.org/wiki/Side-channel_attack&quot; title=&quot;Wikipedia - Side-channel attack&quot;&gt;obligatory Wikipedia article&lt;/a&gt; does a good job of discussing it, especially in a technical sense. Here I’ll discuss a contrived example that lays out the concept, but doesn’t actually get into computing at all.&lt;/p&gt;

&lt;!--- I really wanted to work in &quot;Banking in the 90s&quot; somehow, but 🤷‍♂️ https://www.youtube.com/watch?v=XCiDuy4mrWU --&gt;

&lt;h2 id=&quot;theres-got-to-be-a-way&quot;&gt;There’s Got to Be a Way&lt;/h2&gt;
&lt;!--- Mariah Carey - https://en.wikipedia.org/wiki/There%27s_Got_to_Be_a_Way --&gt;
&lt;p&gt;Let’s invent a scenario: It’s the 1990’s, and you are a private investigator working for a big bank company. This bank has noticed small sums of cash vanishing from several locations on a predictable schedule, and they suspect some employees are cooperating to smuggle it out of the location. Now, all of the cash disappearing from the vault is treated with a chemical… but there’s a problem. All of the employees know that this chemical can be washed off in a typical home clothes washing machine. &lt;!--- I was not aware of the Sonic Youth album at this point in writing this article, but in retrospect during editing, I am very pleased with the coincidence. https://en.wikipedia.org/wiki/Washing_Machine_(album) --&gt; It just takes a particularly heavy duty cycle.&lt;/p&gt;

&lt;p&gt;The bank is emphatic that all the employees involved in the heist need to be caught at the same time. That means: no breaking down doors or tense &lt;a href=&quot;https://en.wikipedia.org/wiki/Law_%26_Order_%28franchise%29&quot; title=&quot;dun dun&quot;&gt;Law &amp;amp; Order&lt;/a&gt;-style interviews with suspects. We need to detect as many of them as possible without notice. “No problem,” you say, “I can detect if they’re removing that chemical without ever entering their home, or even looking in a window.”&lt;/p&gt;

&lt;p&gt;The trick is to measure the consumption of power. There’s two means to accomplish this. Assuming the residential power isn’t &lt;a href=&quot;https://en.wikipedia.org/wiki/Undergrounding&quot;&gt;buried&lt;/a&gt;, the first method would involve an individual with a cheap ladder, a plausible high-visibility vest, and a &lt;a href=&quot;https://en.wikipedia.org/wiki/Current_clamp&quot;&gt;clamp-on ammeter&lt;/a&gt;. If that’s not possible, building-external &lt;a href=&quot;https://en.wikipedia.org/wiki/Electricity_meter&quot;&gt;electricity meters&lt;/a&gt; can be observed from a distance with a reasonably capable pair of binoculars. Ever noticed that little wheel inside that turns kind of slowly and has the large dark mark on one part? That wheel turning once represents some fixed amount of power consumption. (Well, in the 90’s, anyway. Today the purely digital ones are more common, but they have similar indicators.)&lt;/p&gt;

&lt;h2 id=&quot;all-the-small-things&quot;&gt;All the Small Things&lt;/h2&gt;
&lt;!--- Blink-182 - https://en.wikipedia.org/wiki/All_the_Small_Things --&gt;
&lt;p&gt;Homes are full of devices that consume power. Let’s take a look at a few classes of power consumers.&lt;/p&gt;

&lt;h3 id=&quot;continuous-unvarying&quot;&gt;Continuous, unvarying&lt;/h3&gt;
&lt;p&gt;A lot of power consumption in a typical house is pretty boring. Lights, fans, televisions, these all draw a reasonably consistent amount of power during operation, and many of these are left on for long periods of time. A 60 watt lightbulb (remember those?) will consume that amount of power, ramping up nearly instantly to its peak consumption, and immediately dropping off to zero consumption when it’s switched off again.&lt;/p&gt;

&lt;h3 id=&quot;periodic&quot;&gt;Periodic&lt;/h3&gt;
&lt;p&gt;Now consider a space heater: it will draw a lot of power, with a relatively predictable on/off cycle. If it’s rated for 1800 watts, you’d expect an increase of 1800 watts on a power meter for (say) 10 minutes, then a drop of 1800 watts for 2 minutes, then an increase again for 10 minutes, and so on. This ends up being pretty predictable, too.&lt;/p&gt;

&lt;h3 id=&quot;non-periodic&quot;&gt;Non-periodic&lt;/h3&gt;
&lt;p&gt;Here’s where things get good. A device like a washing machine has varied cycle programs designed to be good for washing clothes. The machine will run several motors on and off with varying speeds, in bursts of different lengths, all with widely different loads. The consumption of the pump motor is different than the consumption of the basket spinning motor at low speed, and that consumption is different still from the basket spinning motor at high speed.&lt;/p&gt;

&lt;h2 id=&quot;i-learned-from-the-best&quot;&gt;I Learned from the Best&lt;/h2&gt;
&lt;!--- Whitney Houston - https://en.wikipedia.org/wiki/I_Learned_from_the_Best --&gt;
&lt;p&gt;To get a good idea of what to look for, you need to take some measurements of your own washing machine. Naturally you will want to load the unit with actual cash to make sure the behavior of your machine is similar to the behavior of the employees you’re trying to monitor. Taking detailed recordings of the power consumption of your machine, the important values to record are not the &lt;em&gt;absolute&lt;/em&gt; amounts, but their relative &lt;em&gt;scale&lt;/em&gt;. Making sure to measure &lt;em&gt;only&lt;/em&gt; the consumption of your machine, you can create a sort of power fingerprint for this activity. Ideally, you will want to measure this several times, gathering several samples and seeing if there’s variation in your machine. In a perfect world, you might even buy several machines and measure them all, but you will likely find they are all very similar on their most “heavy duty” settings.&lt;/p&gt;

&lt;h2 id=&quot;when-i-come-around&quot;&gt;When I Come Around&lt;/h2&gt;
&lt;!--- Green Day - https://en.wikipedia.org/wiki/When_I_Come_Around --&gt;
&lt;p&gt;Now your task is simple. We know what the consumption pattern of a machine running its heaviest duty cycle looks like. You now task your fleet of interns (oh, uh, let’s say you have interns.) to remotely record the power consumption of each suspect house every evening after the bank closes. They gather enough data to plot, minute-by-minute, the consumption pattern of their assigned home.&lt;/p&gt;

&lt;p&gt;It’s important to capture data even on nights where no cash goes missing. We will want to know if these employees are just always doing lots of laundry. Or if they have some other strange influence on their power usage, which may be due to &lt;a href=&quot;https://www.computerworld.com/article/2469854/bitcoin-miners-busted--police-confuse-bitcoin-power-usage-for-pot-farm.html&quot;&gt;other, unrelated activities&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because people use their homes for things other than laundry, the data will be noisy, but that’s okay. Remember that the bulk of the things inside a home will either be continuous or periodic; both modify our target’s consumption by a relatively fixed, predictable amount. We know if we see consumption jump up by 1000ish watts, we can probably filter that out by looking at the following data just with that as the new “bottom” of our scale. What we’re looking for here is jumps up and down in consumption that match our own pattern of measurements, not absolute values.&lt;/p&gt;

&lt;h2 id=&quot;weve-got-it-goin-on&quot;&gt;We’ve Got It Goin’ On&lt;/h2&gt;
&lt;!--- Backstreet Boys - https://en.wikipedia.org/wiki/We%27ve_Got_It_Goin%27_On --&gt;
&lt;p&gt;The name of the game at this point is “correlation”. We have a pattern of power usage we can look for, we have a set of measurements from homes that may or may not be participating in this heist, we have the schedule where the cash vanishes, and that’s all we need for our side-channel attack. Using this information, we can detect with reasonably high probability when a home is doing this particular task of washing the cash, and from there, we can deduce which employees are in on the heist.&lt;/p&gt;

&lt;p&gt;Mostly.&lt;/p&gt;

&lt;p&gt;Of course part of the problem with a contrived hypothetical scenario is that it deliberately elides inconvenient details.&lt;/p&gt;

&lt;h2 id=&quot;i-dont-want-to-miss-a-thing&quot;&gt;I Don’t Want to Miss a Thing&lt;/h2&gt;
&lt;!--- Aerosmith - https://en.wikipedia.org/wiki/I_Don%27t_Want_to_Miss_a_Thing --&gt;
&lt;p&gt;So how inaccurate is our goofy example? Well, if you’re talking about attacking a computer CPU, it’s not too bad! You can think of the whole home as the CPU itself, and the home appliances &amp;amp; washing machine as representing internal processor structures. Our initial measurement that we did on our own washing machine would be analogous to measuring power usage with our CPU as otherwise idle as possible. Note that while in our example we measure the power consumption of the CPU, there are other things we can measure, such as execution times. There’s a surprising amount of information available to gather about the state of a CPU: even the temperature of a certain core can be a kind of data point, as it relates to power consumption and workload.&lt;/p&gt;

&lt;p&gt;Of course, washing machines can have a lot of variations, but so do many of the targets of a side-channel attack. Some attacks are highly targeted and limited to a specific environment, but some can be generalized. Some attacks are general enough that they can run inside of a script on a web page, detecting with a reasonable confidence what kind of processor they’re running on, and then tuning their attack from there.&lt;/p&gt;

&lt;p&gt;However, one thing our scenario ignores is repetition. Most side-channel attacks are, at their core, statistical. This means taking hundreds or thousands of samples, repeating the same task over and over again. Indeed, attacks leveraging &lt;a href=&quot;https://en.wikipedia.org/wiki/Spectre_%28security_vulnerability%29&quot;&gt;Spectre&lt;/a&gt; generally need to trigger the execution of some code millions of times just to exfiltrate a few hundreds of bytes. And while some side-channel attacks can be totally passive, the majority have some component that can trigger the behavior we want to spy on. For example, if some malware wants to find the encryption key a browser session is using, it may repeatedly trigger a reload of something in that session.&lt;/p&gt;

&lt;p&gt;The great thing about statistical analysis is that, with enough of it, you can make other sources of noise in your data invisible. And the great thing about computers is that they can do things over and over really quickly.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;TechRepublic has a pretty &lt;a href=&quot;https://www.techrepublic.com/article/spectre-and-meltdown-explained-a-comprehensive-guide-for-professionals/&quot;&gt;detailed article on Spectre and Meltdown&lt;/a&gt; that makes for pretty good reading.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name></name></author><category term="explainer" /><category term="security" /><summary type="html">A side-channel attack is a name for a broad class of different security challenges. The obligatory Wikipedia article does a good job of discussing it, especially in a technical sense. Here I’ll discuss a contrived example that lays out the concept, but doesn’t actually get into computing at all.</summary></entry><entry><title type="html">Detecting duplicate images - Part 2: The naive way</title><link href="https://chris.harrington.mn/project/dupeimages/2020/07/04/duplicate-images-part2.html" rel="alternate" type="text/html" title="Detecting duplicate images - Part 2: The naive way" /><published>2020-07-04T00:00:00+00:00</published><updated>2020-07-04T00:00:00+00:00</updated><id>https://chris.harrington.mn/project/dupeimages/2020/07/04/duplicate-images-part2</id><content type="html" xml:base="https://chris.harrington.mn/project/dupeimages/2020/07/04/duplicate-images-part2.html">&lt;p&gt;Let’s start by defining some data types we can use for output. Go has some great &lt;a href=&quot;https://golang.org/pkg/encoding/json/&quot; title=&quot;encoding/json&quot;&gt;standard library support for JSON encoding&lt;/a&gt; which makes this pretty painless, and if we optimize for this encoding, most other encoding packages will be easy to implement.&lt;/p&gt;

&lt;p&gt;In order to achieve our goal, the minimum we need to know is:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;What the two images we’re comparing are, and&lt;/li&gt;
  &lt;li&gt;How different they are.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s define a Subject type and a Comparison type.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Subject&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Filename&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Comparison&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Distance&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;SubjectA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Subject&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;SubjectB&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Subject&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To start out, we’ll assume all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Subject&lt;/code&gt;s are local files. It’s useful to abstract this away now at this step, though, since we can keep the notion of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Subject&lt;/code&gt; later as we add other sources or interfaces.&lt;/p&gt;

&lt;p&gt;What does the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Distance&lt;/code&gt; member of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparison&lt;/code&gt; mean? How different is a value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; versus a value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;65536&lt;/code&gt;? For now, we won’t make any concrete assertions, except to say that a larger value indicates a larger difference, and relatively small values indicates “similar”. The only value we can define concretely is to say that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; means “identical”.&lt;/p&gt;

&lt;p&gt;Of course we will need to track all of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Subject&lt;/code&gt;s, so let’s make a type for that.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SubjectList&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Subject&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NewSubjectList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SubjectList&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// Assume for now we'll have the same number of subjects as arguments&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SubjectList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NArgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SubjectList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AddFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Filename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;How do we specify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Subject&lt;/code&gt;s? Let’s start by passing filenames to an invocation of our program. Once again the standard library makes our lives easier by providing &lt;a href=&quot;https://golang.org/pkg/flag/&quot; title=&quot;flag&quot;&gt;the flag package&lt;/a&gt;. Using flag now lets us add command-line options later. Also, for the sake of convenience, let’s allow the user to pass in a list of files, or directories, or a combination of both. I like to handle this with &lt;a href=&quot;https://golang.org/pkg/path/filepath/&quot; title=&quot;path/filepath&quot;&gt;the path/filepath package&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;flag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;sl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NewSubjectList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;filepath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Walk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filewalkfunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SubjectList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filewalkfunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FileInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FileInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filewalkfunc&lt;/code&gt; looks weird to you, don’t worry, it is a little weird. Because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filepath.Walk&lt;/code&gt; expects a function, we need some way to pass our list object along with that function. We could create a global variable and reference it from our function, but some smart Go folks &lt;a href=&quot;https://peter.bourgon.org/blog/2017/06/09/theory-of-modern-go.html&quot; title=&quot;Peter Bourgon's best practices for modern Go&quot;&gt;believe this&lt;/a&gt; &lt;a href=&quot;https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables&quot; title=&quot;Dave Cheney's article on avoiding package scoped variables&quot;&gt;is harmful&lt;/a&gt;. Besides, writing tests with globals (Go calls them Package Scoped Variables, because that’s precisely what they are) is pretty painful. Anyway, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filewalkfunc&lt;/code&gt; returns &lt;a href=&quot;https://www.calhoun.io/what-is-a-closure/&quot; title=&quot;Jon Calhoun's article on Go closures&quot;&gt;a function that closes around&lt;/a&gt; a pointer to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SubjectList&lt;/code&gt;. That’s the call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s.AddFile&lt;/code&gt;. The outer function definition does look pretty weird, but it makes sense when you remember Go &lt;a href=&quot;https://golang.org/doc/codewalk/functions/&quot; title=&quot;The Go code walk on functions&quot;&gt;funcs are a first-class type&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that we have gathered up all of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Subject&lt;/code&gt;s, it’s time to actually do something with them. Our strategy for this first (naive!) version will be to iterate through our list and compare each image with every other image. We’ll first define the nice abstract way we want this to happen:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SubjectList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CompareAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Comparison&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// we know that a list of size N will have ((N-1)*N)/2 comparisons. this&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// relationship is the &quot;Triangular numbers&quot;! https://oeis.org/A000217&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Comparison&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;c&quot;&gt;// take the last subject&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;subjecta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
		&lt;span class=&quot;c&quot;&gt;// modify the slice to exclude it&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
		&lt;span class=&quot;c&quot;&gt;// now, compare all the remaining subjects&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subjectb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subjecta&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CompareTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subjectb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In the next article, we will implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Subject.CompareTo()&lt;/code&gt;. We will need to address two core problems: how we compare two pictures that might not be the same size, and how we quantify the difference between pixels in two images.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;See the complete code for this article &lt;a href=&quot;https://github.com/ironiridis/portfolio-examples/tree/386d7b319ba5cf06aac67de0080e7bcaadad0f13&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name></name></author><category term="project" /><category term="dupeimages" /><category term="graphics" /><category term="go" /><summary type="html">Let’s start by defining some data types we can use for output. Go has some great standard library support for JSON encoding which makes this pretty painless, and if we optimize for this encoding, most other encoding packages will be easy to implement.</summary></entry><entry><title type="html">Detecting duplicate images - Part 1: Concept</title><link href="https://chris.harrington.mn/project/dupeimages/2020/06/25/duplicate-images-part1.html" rel="alternate" type="text/html" title="Detecting duplicate images - Part 1: Concept" /><published>2020-06-25T00:00:00+00:00</published><updated>2020-06-25T00:00:00+00:00</updated><id>https://chris.harrington.mn/project/dupeimages/2020/06/25/duplicate-images-part1</id><content type="html" xml:base="https://chris.harrington.mn/project/dupeimages/2020/06/25/duplicate-images-part1.html">&lt;p&gt;Say you have thousands of image files, and you know at least some of them are highly simliar. It would be handy to be able to find the images that are most similar, and consolidate. How could we approach this problem?&lt;/p&gt;

&lt;h3 id=&quot;what-we-have&quot;&gt;What we have&lt;/h3&gt;
&lt;p&gt;For the sake of our discussion, let’s say we have 10,000+ JPEG files, all around 16:9 aspect ratio, but of a variety of different sizes. Let’s say that these are large enough that we don’t want to copy them to our local computer running some desktop photo organizing software, or that we have so many files that such software might be impractical to use. Maybe our files are out in &lt;a href=&quot;https://aws.amazon.com/s3/&quot;&gt;Amazon S3&lt;/a&gt; or need to be read in over HTTP.&lt;/p&gt;

&lt;h3 id=&quot;what-we-need&quot;&gt;What we need&lt;/h3&gt;
&lt;p&gt;Let’s say we want to deal with these duplicates or similar images with some business logic, rather than simply deleting them. Ideally, our program should:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Provide machine-readable output,&lt;/li&gt;
  &lt;li&gt;Be able to detect images that are similar but not identical, such as images that were scaled, slightly cropped, watermarked, or edited,&lt;/li&gt;
  &lt;li&gt;Provide some kind of “score” through which we can rank similarity, and&lt;/li&gt;
  &lt;li&gt;Be as fast, efficient, and accurate as possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;approach&quot;&gt;Approach&lt;/h3&gt;
&lt;p&gt;In this series, we will use Go. It has &lt;a href=&quot;https://godoc.org/github.com/aws/aws-sdk-go/service/s3&quot;&gt;exhaustive support for S3&lt;/a&gt;, natural support for multi-core processing, and is a joy to use. &lt;a href=&quot;/project/dupeimages/2020/07/04/duplicate-images-part2.html&quot;&gt;In part 2&lt;/a&gt;, we start with a naive comparison approach that fails many of our requirements above. In the following parts, we will refine until we have a reusable tool.&lt;/p&gt;</content><author><name></name></author><category term="project" /><category term="dupeimages" /><category term="graphics" /><category term="go" /><summary type="html">Say you have thousands of image files, and you know at least some of them are highly simliar. It would be handy to be able to find the images that are most similar, and consolidate. How could we approach this problem?</summary></entry><entry><title type="html">Welcome to my portfolio</title><link href="https://chris.harrington.mn/meta/2020/06/24/welcome.html" rel="alternate" type="text/html" title="Welcome to my portfolio" /><published>2020-06-24T00:00:00+00:00</published><updated>2020-06-24T00:00:00+00:00</updated><id>https://chris.harrington.mn/meta/2020/06/24/welcome</id><content type="html" xml:base="https://chris.harrington.mn/meta/2020/06/24/welcome.html">&lt;p&gt;As we turn the corner into the latter half of 2020, everything seems to be in upheaval. It’s hard to imagine anyone not aware of the ongoing &lt;a href=&quot;https://en.wikipedia.org/wiki/COVID-19_pandemic&quot;&gt;COVID-19 pandemic&lt;/a&gt;, or of the &lt;a href=&quot;https://en.wikipedia.org/wiki/George_Floyd_protests&quot;&gt;reignited racial conflict&lt;/a&gt;. Businesses are closing; many permanently. Things are so uncertain, that the phrase “uncertain times” has now been &lt;a href=&quot;https://www.wsj.com/articles/in-these-uncertain-times-coronavirus-ads-strike-some-repetitive-notes-11587355261&quot;&gt;criticised as insincere&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, what better time, uncertain as it is, to begin anew? After 15 years in an industry that &lt;a href=&quot;https://www.qsc.com/systems/resources/case-studies/linkedin-case-study/&quot;&gt;centered entirely around&lt;/a&gt; how &lt;a href=&quot;https://www.extron.com/article/malaysiapetronas&quot;&gt;technology can optimize&lt;/a&gt; the &lt;a href=&quot;https://www.crestron.com/News/Press-Releases/2020/Crestron-and-Logitech-Partner-to-Deliver-a-Premier&quot;&gt;number of people&lt;/a&gt; packed &lt;a href=&quot;https://www.amx.com/en-US/news/amx-3m-philippines-creates-world-class-office-space-with-harman-professional-solutions-networked-audio-system&quot;&gt;into a conference room&lt;/a&gt;, it seems as though I wouldn’t be alone in taking an introspective pause. To paraphrase &lt;a href=&quot;https://www.youtube.com/watch?v=mRNX6XJOeGU&quot;&gt;Ian Malcom&lt;/a&gt;, perhaps we should have stopped to consider whether this was the right strategy.&lt;/p&gt;

&lt;p&gt;In my industry, we didn’t weigh the consequences of commonly shared touch interfaces and cables. Of tightly packed row seating or intimate “collaboration pods”. In a future where conference room technology may become obsolete, or indeed maybe even dangerous, perhaps now is the moment to do something new. And whether or not you believe “this will all blow over”, it is clear that corporations have now been made immediately aware that offices, conference rooms, and the technology and furniture that went with them, may not be the value proposition they once appeared to be.&lt;/p&gt;

&lt;p&gt;Where I find myself at this moment is looking back at my coworkers, friends, and teammates, all of whom were critical to my long successful time in corporate AV. Every one of them will be missed dearly. It was a privilege to work alongside these dedicated, intelligent, thoughtful folks. I’m hopeful this new chapter gives me the opportunity to join a new team where I can learn and contribute.&lt;/p&gt;

&lt;p&gt;That leaves me here. I’m beginning to collect some examples of my work and I will be archiving them in this portfolio. And I’m looking forward to showcasing some of my projects and talents.&lt;/p&gt;

&lt;p&gt;If you’re looking for a Minneapolis-based developer – or one who is comfortable working remote – I am available now for full time and contract work.&lt;/p&gt;</content><author><name></name></author><category term="meta" /><category term="journal" /><summary type="html">As we turn the corner into the latter half of 2020, everything seems to be in upheaval. It’s hard to imagine anyone not aware of the ongoing COVID-19 pandemic, or of the reignited racial conflict. Businesses are closing; many permanently. Things are so uncertain, that the phrase “uncertain times” has now been criticised as insincere.</summary></entry></feed>