<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://mackinnon.info/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mackinnon.info/" rel="alternate" type="text/html" /><updated>2026-03-02T23:37:12+00:00</updated><id>https://mackinnon.info/feed.xml</id><title type="html">Bruce MacKinnon</title><subtitle>Bruce MacKinnon&apos;s Notes</subtitle><entry><title type="html">Notes on the Shari SA818 Module</title><link href="https://mackinnon.info/2026/02/23/shari-sa818.html" rel="alternate" type="text/html" title="Notes on the Shari SA818 Module" /><published>2026-02-23T00:00:00+00:00</published><updated>2026-02-23T00:00:00+00:00</updated><id>https://mackinnon.info/2026/02/23/shari-sa818</id><content type="html" xml:base="https://mackinnon.info/2026/02/23/shari-sa818.html"><![CDATA[<p>These are my nodes on the Shari SA818 module in the context of All Star Link. I hear
a lot of negative comments about the quality of this device.  Or, to be fair, the 
quality of the imitations being sold on the internet. But there are many ASL stations
based on some variation of the Shari board so it’s important to have good integration 
for <a href="https://mackinnon.info/ampersand">Ampersand</a>.</p>

<p>Please see <a href="https://mackinnon.info/2025/10/07/sa818-module.html">my SA818 page</a> for 
information about the radio module itself.</p>

<p>Heres <a href="https://www.lyonscomputer.com.au/Allstarlink/AllStarLink-Builds/VK4PK-ASL-SHARI-Build/VK4PK-ASL-SHARI-Build.html#schematic">a schematic</a> for one
variation of this device.</p>

<p>The board contains a <a href="https://www.mpja.com/download/36814cp%20cm108%20data.pdf">CM108</a>
for audio input/output.</p>

<p>CM108 input signals:</p>

<ul>
  <li>The SA818 SQ signal is connected to the CM108 VOLDN pin. This is consistent with the 
usual convention: COS -&gt; VOLDN.</li>
</ul>

<p>CM108 output signals:</p>

<ul>
  <li>The SA818 PTT signal is connected to the CM108 GPIO3. This
pin is mapped to HID_OR1 in the 0b00000100 bit position. This can only be written when 
HID_OR0 0b11000000 is set to 0. For reasons that aren’t clear to me (and not consistent
with the datasheet), turning on GPI03 requires a 5-byte write { 0, 0, 0x4, 0x4, 0 } and
turning it off requires a 5-bute write { 0, 0, 0x04, 0x00, 0 }. The first 0x04 is tbe 
mask that sets GPI03 as output and the second 0x4 is the “high” signal or the “low” 
signal.</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[These are my nodes on the Shari SA818 module in the context of All Star Link. I hear a lot of negative comments about the quality of this device. Or, to be fair, the quality of the imitations being sold on the internet. But there are many ASL stations based on some variation of the Shari board so it’s important to have good integration for Ampersand.]]></summary></entry><entry><title type="html">Notes on Simulcast, Synchronization of Radio Base Stations</title><link href="https://mackinnon.info/2026/02/21/simulcast.html" rel="alternate" type="text/html" title="Notes on Simulcast, Synchronization of Radio Base Stations" /><published>2026-02-21T00:00:00+00:00</published><updated>2026-02-21T00:00:00+00:00</updated><id>https://mackinnon.info/2026/02/21/simulcast</id><content type="html" xml:base="https://mackinnon.info/2026/02/21/simulcast.html"><![CDATA[<p>Some links:</p>

<ul>
  <li><a href="https://allstarsimulcast.com/index.php/intro-to-radio-simulcast/">An important reference from KC2IRV that talks about synchronization requirements</a>.</li>
  <li><a href="https://www.youtube.com/watch?v=_XmfubdUqOU">A good intro video on simulcast</a>.</li>
  <li><a href="https://www.youtube.com/watch?v=1krkUGRrV-w">A video about simulcast systems</a></li>
  <li><a href="http://www.simulcastsolutions.com/frequency.asp">An article that gives some parameters</a>.</li>
  <li><a href="https://www.blog.adtran.com/en/the-hidden-crisis-in-public-safety-communication-the-simulcast-problem">An interesting article</a> that talks about the risks to public safety communications.</li>
  <li><a href="http://www.simulcastsolutions.com/simulcast_forums_case_studies.asp">Several case-studies and background</a></li>
</ul>

<p>The conventional wisdom states that the baseband signal needs to be synchronized to within
30 degrees of phase across different transmitters. For a 1kHz tone that equates to about 
80uS of accuracy.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Some links:]]></summary></entry><entry><title type="html">On the Benefits of Wheel Reinvention</title><link href="https://mackinnon.info/2026/02/18/building.html" rel="alternate" type="text/html" title="On the Benefits of Wheel Reinvention" /><published>2026-02-18T00:00:00+00:00</published><updated>2026-02-18T00:00:00+00:00</updated><id>https://mackinnon.info/2026/02/18/building</id><content type="html" xml:base="https://mackinnon.info/2026/02/18/building.html"><![CDATA[<p>I’m working on a <a href="https://mackinnon.info/ampersand/">pretty elaborate VOIP project</a> right now and 
someone on the AllStarLink forum questioned why I was spending so 
much time “reinventing the wheel.” It’s a fair question, and certainly
relevant given how much of my life has been spent reinventing various
wheels, axles, transmissions, breaking systems, and other well-proven 
technologies.</p>

<p>In my experience, the best way to <strong>really</strong> understand a technology is to 
try to implement it yourself. You can read the books, take the classes,
and watch the YouTube videos, but there is no substitute 
for sitting in front of a blank screen and trying to copy something with your
own hands. There are
100’s of subtle details lurking below the surface of a system of modest
complexity, none of which can be appreciated (let alone understood) by simply watching the highlight reel. Not to mention the AI summary …</p>

<p>A time-honored training method used by artists is called 
the “Master Copy” in which the aspiring student attempts to 
reproduce a great masterwork. “<em>Art begets art</em>,” says <a href="https://danielgravesart.com/">Daniel Graves</a>, 
a well-known painter and founder of the Florence Academy of Art, “<em>and
nothing is more useful for an art student than to copy a great 
master painting</em>.” Supposedly Chopin began his piano practice every day with 
some preludes and fugues from Bach’s 48. I spent two weeks in 
Maine last summer at the <a href="https://www.thewoodenboatschool.com/">The Wooden Boat School</a> where a bunch of us spent 
hours a day in a shop trying to accurately reproduce a 
wooden dory designed by George L. Chaisson, the legendary 
builder from 
the North Shore of Massachusetts.</p>

<p>I once had the chance to work with Dragomir Krgin, a famous
<a href="https://www.amazon.com/Handbook-Global-Fixed-Income-Calculations/dp/0471218359">expert in the field of bond market conventions</a> from Merrill Lynch, or at
least as famous as you can get working in such an esoteric field. I would 
approach Dr. Krgin with a question about some obtuse nuance in the “true
yield” calculation used for Italian government bonds and he would 
refuse to answer my question directly. Instead, he would
pull out his yellow pad and derive <strong>every answer from first 
principles</strong> (present value is the sum of the discounted future cash-flows). This 
could take hours, but I came to appreciate how 
the ease with which he could reinvent the “wheels” in his space allowed him 
to navigate complicated problem with confidence and authority.</p>

<p>There is something about the 
process of replicating the work of the great masters that prepares
your hands to do great work of your own.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I’m working on a pretty elaborate VOIP project right now and someone on the AllStarLink forum questioned why I was spending so much time “reinventing the wheel.” It’s a fair question, and certainly relevant given how much of my life has been spent reinventing various wheels, axles, transmissions, breaking systems, and other well-proven technologies.]]></summary></entry><entry><title type="html">Notes on Linux Boot</title><link href="https://mackinnon.info/2025/12/30/linux-boot.html" rel="alternate" type="text/html" title="Notes on Linux Boot" /><published>2025-12-30T00:00:00+00:00</published><updated>2025-12-30T00:00:00+00:00</updated><id>https://mackinnon.info/2025/12/30/linux-boot</id><content type="html" xml:base="https://mackinnon.info/2025/12/30/linux-boot.html"><![CDATA[<h2 id="uefi">UEFI</h2>

<p>Unified Extensible Firmware Interface (UEFI) is a specification for the firmware architecture of a computing platform. The standard is open but the implementations are generally proprietary.</p>

<p>The actual boot order, which tells the UEFI where to look (e.g., which disk, which partition, which bootloader file), is stored in the motherboard’s Non-Volatile RAM (NVRAM).</p>

<h2 id="esp">ESP</h2>

<p>The UEFI ESP (EFI System Partition) is a small, mandatory FAT32 partition on storage drives that holds boot loaders, drivers, and utilities for computers using the Unified Extensible Firmware Interface (UEFI) to start the operating system.</p>

<p>The ESP is a dedicated OS-independent partition, allowing multiple operating systems (like Windows and Linux) to coexist on the same drive, each with its own boot files in the ESP or related locations.</p>

<p>It provides files for UEFI utilities and firmware updates, often mounted at /boot/efi in Linux.</p>

<p>The ESP contains the .efi files that the UEFI firmware executes to start an operating system.</p>

<p>For Debian 12 (Bookworm) UEFI installs, the EFI System Partition (ESP) needs to be FAT32 formatted with a mount point of /boot/efi. While a minimum of 100-300 MB works, 500-1000 MB (1GB) is strongly recommended to comfortably handle multiple kernels, operating systems (dual-boot), and firmware updates without running out of space later, as resizing it can be difficult.</p>

<h2 id="bootx64efi">BOOTX64.EFI</h2>

<p>Every device that can be booted, has an EFI System Partition (ESP) formatted with the FAT32 filesystem. The UEFI firmware is supposed to, at a minimum look for the file “\EFI\BOOT\BOOTX64.EFI” in every ESP and add that to the boot menu.  The UEFI firmware can also maintain other boot menu entries that refer to files in the EFI System Partitions, such as the familiar “\EFI\MICROSOFT\BOOT\BOOTMGFW.EFI”.</p>

<p>The UEFI firmware scans all EFI System Partitions for this specific path if no other boot options are set.</p>

<h2 id="parition-setup">Parition Setup</h2>

<p>For new users, personal Debian boxes, home systems, and other single-user setups, a single / partition (plus swap) is probably the easiest, simplest way to go. The recommended partition type is ext4.</p>

<p>With respect to the issue of swap partition size, there are many views. One rule of thumb which works well is to use as much swap as you have system memory. It also shouldn’t be smaller than 512MB, in most cases. Of course, there are exceptions to these rules. For hibernation to work you must have a swap space (partition or file) at least as large as your RAM to save your system’s state.</p>

<h2 id="wyse-3040-notes">WYSE 3040 Notes</h2>

<p><a href="https://nickcharlton.net/posts/installing-debian-12-dell-wyse-3040">An article related to the Wyse 3040</a>.</p>

<p>The Wyse 3040 only uses UEFI and doesn’t support a legacy BIOS mode.</p>

<p>This machine has a bug that involves its firmware’s strict requirement to find bootloaders at the standard EFI/BOOT/BOOTX64.EFI fallback path, often failing with non-standard installations like Debian’s default, causing boot failures or blank screens.</p>

<p>Used the LxQt version of the Debian Live distro.</p>

<p>Added “nomodeset” from Grub menu in live install to address a blank screen issue.</p>

<h3 id="debian-setup-of-3040">Debian Setup of 3040</h3>

<p>An 8Gb drive shows up as /dev/mmcblk0 df</p>

<ul>
  <li>Partition 1: 500MB, ESP</li>
  <li>Partition 2: 7GB, ext4, /</li>
  <li>Partition 3: ~500MB, swap</li>
</ul>

<p>Selected: “ Force extra installation to the EFI removable media path?”</p>]]></content><author><name></name></author><summary type="html"><![CDATA[UEFI]]></summary></entry><entry><title type="html">Thoughts on AllStarLink Firewall Traversal</title><link href="https://mackinnon.info/2025/12/05/asl-firewall.html" rel="alternate" type="text/html" title="Thoughts on AllStarLink Firewall Traversal" /><published>2025-12-05T00:00:00+00:00</published><updated>2025-12-05T00:00:00+00:00</updated><id>https://mackinnon.info/2025/12/05/asl-firewall</id><content type="html" xml:base="https://mackinnon.info/2025/12/05/asl-firewall.html"><![CDATA[<p>I’ve been deep into the details of AllStarLink and have nearly
completed my no-Asterisk-strings-attached implementation. This has been a 
great learning experience. This is actually the second time I’ve embarked
on a crazy project like this, the previous time was <a href="https://github.com/brucemack/microlink">my implementation of EchoLink</a>.</p>

<p>I’ve been working on putting the <a href="https://ema.arrl.org/wellesley-amateur-radio-society/">Wellesley Amateur Radio Society (W1TKZ)</a> repeater system on AllStarLink using a 4G/LTE cellular hotspot.
I’ve never worked much with the mobile carriers so a lot of this is new to me. In particular,
the hotspot that I am using (Verizon + Netgear LM1200) provides generally good service, but
it has the known limitations of CGNAT on the IPv4 side. Bottom line: it’s not easy to accept inbound 
connections since (a) you don’t have a static IP address and (b) the port numbers are NATed
and (c) there’s a rigid firewall with no concept of “port forwarding rules” like you might 
have from a commercial or home ISP.</p>

<p>That said, the story on the IPv6 side is much better. I think robust support for IPv6 in AllStarLink is the best way to get away from the limitations
of the CGNAT world. With IPv6 <strong>there is no translation of addresses or ports</strong> - you get “real” 
internet addresses/ports all the way down to the AllStarLink end-point. My implementation of
IAX2 now supports IPv6 and it works well, with some important limitations. I’ll provide a different write-up on that later.</p>

<p>However, the problem isn’t solved. The inflexible firewall still exists and the Verizon 4G/LTE 
service doesn’t allow ports to be opened from the outside world. For obvious reasons, the carriers designed the mobile network to support clients, not servers. So even now with everything 
running on IPv6, making a call into the node at our repeater site is still blocked.</p>

<p>All of this reminded me of something from my EchoLink experience. I know people might quarrel 
with this, but from my perspective the AllStarLink and EchoLink systems are <strong>very similar</strong> 
from a technology standpoint. Both essentially borrow concepts/protocols from the VOIP/PBX world
and adapt them for ham radio use. Each system has its pros and cons. One of the big pros
of EchoLink it its smooth(er) traversal of firewalls. I think the AllStarLink system can 
borrow a proven technique from EchoLink.</p>

<h1 id="adapting-the-echolink-openover-protocol">Adapting the EchoLink OPEN/OVER Protocol</h1>

<p>If you want to know how the EchoLink system works, <a href="https://github.com/brucemack/microlink/blob/main/docs/el_supplement.md">my reverse-engineering document</a> may be
the most detailed source of information. EchoLink doesn’t enjoy an nice <a href="https://datatracker.ietf.org/doc/html/rfc5456">RFC like IAX2</a>, so everything needed to be figured out using Wireshark.</p>

<p>EchoLink has a central server called the “Addressing Server” that 
plays a similar role to the AllStarLink registration server. One simple feature in the EchoLink Addressing Server makes firewall traversal simpler. I call this 
feature the <a href="https://github.com/brucemack/microlink/blob/main/docs/el_supplement.md#echolink-pingopenover-protocol">“OPEN/OVER protocol”</a>.</p>

<p>Understanding this mechanism requires an understanding of dynamic firewall capabilities known as <a href="https://en.wikipedia.org/wiki/Stateful_firewall">Stateful Packet Inspection</a> and/or <a href="https://en.wikipedia.org/wiki/UDP_hole_punching">UDP Hole Punching</a>. A full explanation is beyond the scope of this document, but the key thing to understand is: on most “normal” home/cellular internet routers sending a UDP packet to a remote address will <strong>automatically grant temporary permission for the return path</strong>. This temporary permission is sometimes called a “UDP hole” because it enables a traffic pattern that would not normally be possible. The hole being described here includes (a) a firewall opening to allow return traffic on the same port and (b) the routing entries needed to get the return traffic back to the right place on your LAN. Without this hole, two-way communication would not be possible.</p>

<p>The UDP hole is transient and will only persist as long as the network path is actively being used. Once a path becomes inactive the UDP hole is closed. The lifetime of the hole depends 
on the carriers and the traffic situation, but it’s safe to assume that the holes will last through 15 seconds of inactivity.</p>

<p>If node 44444 (from port 4569) wants to connect to node 55555 (to port 4569) it will be blocked by the firewall. However, if node 55555 <em>just happens</em> to send a UDP message to node 44444 on the same ports, a 
temporary opening will be created on node 55555’s firewall for a short time. If node 44444 wants to 
connect to node 55555 via IAX2 during that window, the packets go through just fine.</p>

<p>So, the “trick” is to ask node 55555 to send a content-free message to node 44444 in 
advance of receiving a real IAX2 call from node 44444. EchoLink performs this trick by leveraging their 
addressing server.  AllStarLink could do the same thing, something like this:</p>

<ol>
  <li>Node 55555 sends an PING to the Registration Server on a regular basis.
This has the effect of keeping a UDP hole open on 55555’s firewall to accept packets <strong>FROM</strong> the Registration Server. The specific port-numbers here don’t matter much, although it would make 
sense to use a single well-known port on the server side.</li>
  <li>When node 44444 wants to call node 55555, it first sends an OPEN_REQUEST message to the Registration Server
with the IP address/port that it wants to connect to (i.e. 55555’s address/port).  Again, the specific
ports used don’t matter much.</li>
  <li>After authentication (likely PKI), the Registration Server uses its open path to 55555 to 
send an OPEN_REQUEST frame telling it of node 44444’s interest. It should use the same address/port that 
was used by node 55555 in step 1 to ensure successful delivery.</li>
  <li>Node 55555 sends a POKE frame directly to node 44444, thus establishing the needed UDP hole. This
is an IAX message on the normal IAX port.</li>
  <li>Finally, node 44444 places the call the usual way, leveraging the open path that was just created by the 
previous step.</li>
  <li>The ongoing call keeps the firewalls open in both directions.</li>
</ol>

<p>To be clear: <strong>the Registration Server does not stay in the middle of the call</strong> - this is not a proxy mechanism.
The Registration Server is just acting as a notification service to help nodes stuck behind 
restrictive firewalls.</p>

<p>Also, I don’t think the Registration Server would need to perform any real-time database access 
to support this feature. A public-key encryption mechanism could be used to perform the necessary validations. The Registration Server would need to periodically refresh its cache of 
the private keys for all nodes on the network. It would also need to keep track of the 
address and port most recently used for the regular PING (step 1) for each active node 
on the network.</p>

<p>I’m specifically mentioning the IAX POKE frame type because that message is already in the IAX2
protocol and is designed for this kind of situation. From the RFC:</p>

<blockquote>
  <p>A POKE message is sent to test connectivity of a remote IAX peer.  It
is similar to a PING message, except that it MUST be sent when there
is no existing call to the remote endpoint.  It MAY also be used to
“qualify” a user to a remote peer, so that the remote peer can
maintain awareness of the state of the user.  A POKE MUST have 0 as
its destination call number.</p>

  <p>Upon receiving a POKE message, the peer MUST respond with a PONG
message.</p>
</blockquote>

<p>One nice thing about the POKE is that it is processed by Asterisk (a) without needing 
any authentication and (b) without needing an active call. I’ve tested this on 
several nodes on the existing network and they all respond with the PONG, as 
required by the RFC.</p>

<p>This scheme will actually be simpler than that used on EchoLink because AllStarLink
only needs one port. EchoLink needs two ports, both of which need to be opened using
this handshake.</p>

<p>With this mechanism in place, there should be no reason for special inbound firewall 
rules. This lowers the friction to adding new nodes on the network.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I’ve been deep into the details of AllStarLink and have nearly completed my no-Asterisk-strings-attached implementation. This has been a great learning experience. This is actually the second time I’ve embarked on a crazy project like this, the previous time was my implementation of EchoLink.]]></summary></entry><entry><title type="html">Notes on AllStarLink USB Audio Interfaces</title><link href="https://mackinnon.info/2025/10/28/asl-usb-audio.html" rel="alternate" type="text/html" title="Notes on AllStarLink USB Audio Interfaces" /><published>2025-10-28T00:00:00+00:00</published><updated>2025-10-28T00:00:00+00:00</updated><id>https://mackinnon.info/2025/10/28/asl-usb-audio</id><content type="html" xml:base="https://mackinnon.info/2025/10/28/asl-usb-audio.html"><![CDATA[<p>Random notes from study of the mechanisms involved in creating a driver for
a USB audio interface. Some of this is probably just general Linux USB knowledge.</p>

<h1 id="hid-experiments-with-an-allscan-uci90">HID Experiments With an AllScan UCI90</h1>

<p>Many thanks to David Gleason for sending me one of these excellent devices.
See <a href="https://allscan.info/">AllScan.info</a>. This device uses the CM108B chip.</p>

<ul>
  <li><a href="https://allscan.info/images/UCI90/UCI90-v0.97-sch.jpg">Schematic for the UCI90</a></li>
  <li><a href="https://www.micros.com.pl/mediaserver/info-uicm108b.pdf">Datasheet for the CM108B</a></li>
  <li><a href="https://docs.kernel.org/hid/hidraw.html">See this page</a> for information on Linux USB controls.</li>
</ul>

<p>My testing was done on a Raspberry Pi 5 using the stock OS. No special drivers
we required to use the UCI90.</p>

<p>The CM108B chip allows GPIO pins to be read via USB HID registers. 
Here’s the code that can read 4 bytes of data out of the box via the HID
interface. From the Linux 
docs <em>“typically, this is used to request the initial states of an input report of 
a device, before an application listens for normal reports via the regular device 
read() interface. The format of the buffer issued with this report is 
identical to that of …“</em></p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
  <span class="c1">// Set the report ID</span>
  <span class="n">buf</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">HIDIOCGINPUT</span><span class="p">(</span><span class="mi">5</span><span class="p">),</span> <span class="n">buf</span><span class="p">);</span>
  <span class="c1">// The first byte is the report ID, ignore it</span>
  <span class="k">for</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ret</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
      <span class="n">printf</span><span class="p">(</span><span class="s">"%d %02X</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">buf</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
</code></pre></div></div>

<p>The PTT button on the microphone (connector K1 3.5mm) acts like a COS signal. This
is connected to the VOLDN pin (pin 48) on the CM108B.  This maps to HID_IR0 bit 1. So 
if the microphone button is pressed we’ll get this from the ioctl() call:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>            0x02,0x00,0x00,0x00
</code></pre></div></div>

<p>Whenever the GPIO state changes a USB HID interrupt is generated. This can be accessed
using a normal read() system call - no ioctl() required. The format of the data 
is the same - 4 bytes.</p>

<p>The CM108B is a stereo CODEC so the audio data needs to be stereo. That said, we
usually only care about one channel. The audio output on the speaker connection 
of the UCI90 (J2) and the microphone connection (K2 2.5mm) comes from the <strong>left channel of the CODEC</strong> (pin 30).</p>

<p>The audio input from the microphone connection appears 
on both channels at the same time.</p>

<h1 id="hid-experiments-with-usb-audio-box-containing-a-cm6206">HID Experiments With USB Audio Box Containing a CM6206</h1>

<p>This device is reported as “CM106 like” by the Linux kernel.</p>

<ul>
  <li><a href="https://pdf.dzsc.com/88888/20071017105428769.pdf">See CM106-F/L datasheet (page 18)</a></li>
  <li><a href="https://static6.arrow.com/aropdfconversion/93bbc7353fab6d53e77a2e0c6d577e23c048962d/cm6206_datasheet__v2.3.pdf">Or CM6206 datasheet</a></li>
</ul>

<p>We are reading/writing /dev/hidraw0 in all cases, using normal Linux
open/read/write calls. There is no ioctl necessary with this chip.</p>

<p>Per USB specification, numbers are represented in little-endian format.</p>

<h3 id="test-1-pressrelease-mute-button-on-control-box-output">Test 1: Press/release mute button on control box. Output:</h3>

<p>0x14, 0x00, 0x30 -&gt; 0001 0100, 0000 0000, 0011 0000
0x10, 0x00, 0x30 -&gt; 0001 0000, 0000 0000, 0011 0000</p>

<p>This shows the “mute” bit turning on/off (WORKING!).</p>

<h3 id="test-2-pressrelease-volume-down-button-output">Test 2: Press/release volume down button. Output:</h3>

<p>0x12, 0x00, 0x30
0x10, 0x00, 0x30</p>

<p>This shows the “VDN” bit turning on/off (WORKING!).</p>

<h3 id="test-3-generate-a-set-output-report-asking-for-the-contents-of">Test 3: Generate a Set Output Report asking for the contents of</h3>
<p>register 1.</p>

<p>Send 48, 00, 00, 01.
Received 0x30, 0x00, 0x30 -&gt; 0011 0000, 0000 0000, 0011 0000</p>

<p>From datasheet (page 19), this means PLLBINen=1 and SOFTMUTEen=1.</p>

<h3 id="test-4-read-register-3">Test 4: Read register 3.</h3>

<p>Send 48, 0, 0, 3
Received: 0x30, 0x7F, 0x14</p>

<p>Full register 3 contents: 0001 0100 0111 1111</p>

<h1 id="notes-on-audio-flow-through-chan_simpleusbb">Notes on Audio Flow Through chan_simpleusb.b</h1>

<p>Channel function simpleusb_write() is called when 
a data frame is received from the network. This
function does nothing more than put the frame onto
o-&gt;txq for later handling.</p>

<p>Channel function simple_read() appears to be called
periodically (at the frame rate?) and does a few things:</p>
<ul>
  <li>(A few steps)</li>
  <li>Takes frames off the transmit queue (o-&gt;txq):
    <ul>
      <li>Applies preemphasis, converts to 48k audio,</li>
      <li>Sends them to the sound card.</li>
    </ul>
  </li>
  <li>Reads from the sound card to get as much as possible
    <ul>
      <li>If there is no audio available then return immediately with a null frame.</li>
    </ul>
  </li>
  <li>Based on COS/CTCSS status (and transitions) sends control messages 
AST_CONTROL_RADIO_KEY or AST_CONTROL_RADIO_UNKEY.</li>
  <li>Converts the 48k sound to 8k, makes a frame, deals with HPF, deemphasis, etc.</li>
  <li>DTMF detection: ast_dsp_process(c, o-&gt;dsp, f) that 
appears to return a frame handle if there is any 
DTMF content in the audio frame.</li>
</ul>

<h1 id="linux-audio-interface-usb">Linux Audio Interface (USB)</h1>

<p>res_usbradio.c looks into this directory:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    /proc/asound/cards
</code></pre></div></div>

<p>Setting/Getting HID Register:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">ast_radio_hid_set_outputs</span><span class="p">(</span><span class="k">struct</span> <span class="n">usb_dev_handle</span> <span class="o">*</span><span class="n">handle</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">outputs</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">usleep</span><span class="p">(</span><span class="mi">1500</span><span class="p">);</span>
	<span class="n">usb_control_msg</span><span class="p">(</span><span class="n">handle</span><span class="p">,</span> 
        <span class="c1">// Request Type </span>
        <span class="n">USB_ENDPOINT_OUT</span> <span class="o">+</span> <span class="n">USB_TYPE_CLASS</span> <span class="o">+</span> <span class="n">USB_RECIP_INTERFACE</span><span class="p">,</span>
        <span class="c1">// Request</span>
        <span class="n">HID_REPORT_SET</span><span class="p">,</span>
        <span class="c1">// Value</span>
        <span class="mi">0</span> <span class="o">+</span> <span class="p">(</span><span class="n">HID_RT_OUTPUT</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">),</span> 
        <span class="c1">// Index</span>
        <span class="n">C108_HID_INTERFACE</span><span class="p">,</span> 
        <span class="c1">// Data</span>
        <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span> <span class="n">outputs</span><span class="p">,</span> 
        <span class="c1">// Length</span>
        <span class="mi">4</span><span class="p">,</span> 
        <span class="c1">// Timeout</span>
        <span class="mi">5000</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">ast_radio_hid_get_inputs</span><span class="p">(</span><span class="k">struct</span> <span class="n">usb_dev_handle</span> <span class="o">*</span><span class="n">handle</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">inputs</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">usleep</span><span class="p">(</span><span class="mi">1500</span><span class="p">);</span>
	<span class="n">usb_control_msg</span><span class="p">(</span><span class="n">handle</span><span class="p">,</span> 
        <span class="c1">// Request Type </span>
        <span class="n">USB_ENDPOINT_IN</span> <span class="o">+</span> <span class="n">USB_TYPE_CLASS</span> <span class="o">+</span> <span class="n">USB_RECIP_INTERFACE</span><span class="p">,</span>
        <span class="c1">// Request</span>
        <span class="n">HID_REPORT_GET</span><span class="p">,</span>
        <span class="c1">// Value</span>
        <span class="mi">0</span> <span class="o">+</span> <span class="p">(</span><span class="n">HID_RT_INPUT</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">),</span> 
        <span class="c1">// Index</span>
        <span class="n">C108_HID_INTERFACE</span><span class="p">,</span> 
        <span class="c1">// Data</span>
        <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span> <span class="n">inputs</span><span class="p">,</span> 
        <span class="c1">// Size</span>
        <span class="mi">4</span><span class="p">,</span> 
        <span class="mi">5000</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int usb_control_msg(struct usb_device * dev, 
    __u8 request, 
    __u8 requesttype, 
    __u16 value, 
    __u16 index, 
    void * data, 
    __u16 size, 
    int timeout);
</code></pre></div></div>

<p>Get Request Type: 0b1010_0001 (0xA1), request: 0x01, value: 0x0100, index: 0x0003, length: 0x0004
Set Request Type: 0b0010_0001 (0x21), request: 0x09, value: 0x0200
#define C108_HID_INTERFACE	3</p>

<p>Understanding request parameter:</p>

<p>USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE</p>

<p>From libusb docs:</p>
<ul>
  <li>Bit 7: Data Transfer Direction (0 = Host to Device, 1 = Device to Host)</li>
  <li>Bit 6-5: Type (00 = Standard, 01 = Class, 10 = Vendor, 11 = Reserved)</li>
  <li>
    <p>Bit 4-0: Recipient (00000 = Device, 00001 = Interface, 00010 = Endpoint, 00011 = Other)</p>
  </li>
  <li>USB_ENDPOINT_OUT (0x00): A transfer from the host to the device.</li>
  <li>USB_ENDPOINT_IN (0x80): A transfer from the device to the host.</li>
  <li>USB_TYPE_CLASS: corresponds to the value 0x01 « 5 (which is 0x20), meaning the bits 6 and 5 are set to 01. This signifies that the request is a class-specific request.</li>
  <li>USB_RECIP_INTERFACE (0x01): The request is directed at one of the device’s interfaces.</li>
</ul>

<p>Understanding request parameter:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    HID_REPORT_SET - 0x09 0b0000_1001
    HID_REPORT_GET - 0x01 0b0000_0001
</code></pre></div></div>

<p>Understanding “value” parameter:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    0 + (HID_RT_OUTPUT &lt;&lt; 8), 
    0 + (HID_RT_INPUT &lt;&lt; 8), 
</code></pre></div></div>

<p>Possibly: HID_RT_INPUT = 0x01; HID_RT_OUTPUT = 0x02. So HID_RT_OUTPUT « 8 = 0x0100 and HID_RT_INPUT « 8 = 0x0200?</p>

<h1 id="usb-audio-experiments">USB Audio Experiments</h1>

<p>Looking in /dev/snd. The naming convention pcmCxDy or controlCxDy indicates the 
card number (x) and device number (y) for a specific sound card. ”p” for playback,
“c” for capture.</p>

<h1 id="references">References</h1>

<ul>
  <li><a href="https://www.micros.com.pl/mediaserver/info-uicm108b.pdf">CM108 datasheet</a></li>
  <li><a href="https://www.usb.org/sites/default/files/documents/hid1_11.pdf">HID specification</a></li>
  <li><a href="https://www.beyondlogic.org/usbnutshell/usb6.shtml">USB Protocol Stuff</a></li>
  <li><a href="https://tldp.org/HOWTO/Alsa-sound.html#toc6">An ALSA HOWTO from &gt;25 years ago</a></li>
  <li><a href="https://alsamodular.sourceforge.net/alsa_programming_howto.html">Another that is more PCM related</a></li>
  <li><a href="https://www.asterisk.org/building-a-channel-driver-part-1/">Part 1 of an article about how to build an Asterisk Channel Driver (highly relevant)</a></li>
  <li><a href="https://www.asterisk.org/building-a-channel-driver-part-2/">Part 2</a></li>
  <li><a href="https://www.asterisk.org/building-a-channel-driver-part-3/">Part 3</a></li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Random notes from study of the mechanisms involved in creating a driver for a USB audio interface. Some of this is probably just general Linux USB knowledge.]]></summary></entry><entry><title type="html">Notes on SIP</title><link href="https://mackinnon.info/2025/10/22/sip-notes.html" rel="alternate" type="text/html" title="Notes on SIP" /><published>2025-10-22T00:00:00+00:00</published><updated>2025-10-22T00:00:00+00:00</updated><id>https://mackinnon.info/2025/10/22/sip-notes</id><content type="html" xml:base="https://mackinnon.info/2025/10/22/sip-notes.html"><![CDATA[<p>This page has my notes taken during the reverse-engineering of the SIP 
protocol, as implemented in Asterisk.</p>

<h2 id="example-sip-message">Example SIP Message</h2>

<p>Here’s an extract of a real SIP message. Note that HTTP format is used.
There is a request line, followed by a set of header lines, followed by
a blank line, followed by the “body” of the request.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    INVITE sip:61057@192.168.8.102:5060;user=phone SIP/2.0
    Via: SIP/2.0/UDP 192.168.8.225;branch=z9hG4bKa236ad2d25EAA60
    From: "Bruce AllStarLink" &lt;sip:1001@192.168.8.102&gt;;tag=C33436A7-230A9A
    To: &lt;sip:61057@192.168.8.102;user=phone&gt;
    CSeq: 1 INVITE
    Call-ID: 5ad49fee49257106bfba2eceaea5130b
    Contact: &lt;sip:1001@192.168.8.225&gt;
    Allow: INVITE,ACK,BYE,CANCEL,OPTIONS,INFO,MESSAGE,SUBSCRIBE,NOTIFY,PRACK,UPDATE,REFER
    User-Agent: PolycomVVX-VVX_410-UA/5.9.5.0614
    Accept-Language: en
    Supported: replaces,100rel
    Allow-Events: conference,talk,hold
    Max-Forwards: 70
    Content-Type: application/sdp
    Content-Length: 352

    v=0
    o=- 1761157361 1761157361 IN IP4 192.168.8.225
    s=Polycom IP Phone
    c=IN IP4 192.168.8.225
    t=0 0
    a=sendrecv
    m=audio 2260 RTP/AVP 9 102 0 8 18 127
    a=rtpmap:9 G722/8000
    a=rtpmap:102 G7221/16000
    a=fmtp:102 bitrate=32000
    a=rtpmap:0 PCMU/8000
    a=rtpmap:8 PCMA/8000
    a=rtpmap:18 G729/8000
    a=fmtp:18 annexb=no
    a=rtpmap:127 telephone-event/8000
</code></pre></div></div>

<h2 id="sip-handshake-notes">SIP Handshake Notes</h2>

<p>In this testing I used a properly configured SIP phone (.225) and an Asterisk 
server (.102). Here’s the sequence, filtered for the SIP traffic.</p>

<p><img src="/assets/images/sip-capture-4.jpg" alt="Capture 4" /></p>

<h2 id="rtp-port-traversal-examination">RTP Port Traversal Examination</h2>

<p>In this testing I used a properly configured SIP phone (.225) and an Asterisk 
server (.102).</p>

<p>Commands used to check firewall:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    sudo firewall-cmd --get-active-zones
    sudo firewall-cmd --zone=public --list-all
    sudo firewall-cmd --zone=allstarlink --list-all
</code></pre></div></div>

<p>The last command shows that a custom–sip rule had been added, which likely opens
port 5060 for inbound connections from the phone.</p>

<p>The AllStarLink documentation <a href="https://allstarlink.github.io/pi/cockpit-firewall/">has some helpful firewall notes here</a>.</p>

<p>The SIP negotiation happens on UDP port 5060 without any problems.</p>

<p>The final message in the SIP negotiation (OK (INVITE)) assigns UDP port 16314 
to the RTP/media. That is a random server-assigned port inside of the expected 
range of 10,000 to 20,000.</p>

<p><img src="/assets/images/sip-capture-2.jpg" alt="Capture 2" /></p>

<p>However, that port is <strong>normally 
not open to inbound connections</strong>. Notice the ICMP messages being generated 
in response to the phones RTP messages that 
tell the phone that its messages are being rejected.</p>

<p><img src="/assets/images/sip-capture-1.jpg" alt="Capture 1" /></p>

<p>But after a few seconds the server finally sends an RTP packet of its own 
to the phone on port 16314. This triggers the firewall hole-punch and no more 
ICMP errors are seen from the firewall.</p>

<p><img src="/assets/images/sip-capture-3.jpg" alt="Capture 3" /></p>

<h2 id="authentication-notes">Authentication Notes</h2>

<p>An initial INVITE message from the phone was rejected by the server with a 
status 401 (Unauthorized). However, this rejection contains this header:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WWW-Authenticate: Digest realm="asterisk",nonce="1761157360/8582866c17ef9e4cfd024254e37cf1e8",opaque="50d273e02ab5c6c3",algorithm=MD5,qop="auth"
</code></pre></div></div>

<p>The format of this header is dictated by <a href="https://datatracker.ietf.org/doc/html/rfc2617#section-3.2.1">RFC 2617 in section 3.2.1</a>.</p>

<p>Digest authentication is requested using the MD5 algorithm.</p>

<p>The subsequent INVITE message from the phone contained this new header:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Authorization: Digest username="1001", realm="asterisk", nonce="1761157360/8582866c17ef9e4cfd024254e37cf1e8", qop=auth, cnonce="/68T/KlfvEd3p7T", nc=00000001, opaque="50d273e02ab5c6c3", uri="sip:61057@192.168.8.102:5060;user=phone", response="30ffb4415b99d73da5ef3badee2781d1", algorithm=MD5
</code></pre></div></div>

<p>This particular endpoint was configured (/etc/asterisk/pjsip.conf) with a 
type=auth, auth_type=userpass, username=1001, password=1001.</p>

<p>The critical part of this header is the response attribute which contains
a hash value that allows the client to prove that it knows the password. The 
definition of 
how this hash is computed is provided in <a href="https://datatracker.ietf.org/doc/html/rfc2617#section-3.2.2">RFC 2617 in section 3.2.2</a>.</p>

<p>Here’s a short Python script that demonstrates the steps to validate
the secret. The request/response match those provided above.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">hashlib</span>
<span class="kn">import</span> <span class="nn">base64</span>

<span class="k">def</span> <span class="nf">MD5</span><span class="p">(</span><span class="n">a</span><span class="p">):</span>
    <span class="n">md5_hash</span> <span class="o">=</span> <span class="n">hashlib</span><span class="p">.</span><span class="n">md5</span><span class="p">()</span>
    <span class="n">md5_hash</span><span class="p">.</span><span class="n">update</span><span class="p">(</span><span class="n">a</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="s">"utf-8"</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">md5_hash</span><span class="p">.</span><span class="n">hexdigest</span><span class="p">()</span>

<span class="c1"># Here is the secret that the client has:
</span><span class="n">password</span><span class="o">=</span><span class="s">"1001"</span>

<span class="c1"># All of this is contained in the client's request (no 
# secrets here):
</span><span class="n">realm</span><span class="o">=</span><span class="s">"asterisk"</span>
<span class="n">username</span><span class="o">=</span><span class="s">"1001"</span>
<span class="n">uri</span><span class="o">=</span><span class="s">"sip:61057@192.168.8.102:5060;user=phone"</span>
<span class="n">nonce</span><span class="o">=</span><span class="s">"1761157360/8582866c17ef9e4cfd024254e37cf1e8"</span>
<span class="n">nc</span><span class="o">=</span><span class="s">"00000001"</span>
<span class="n">cnonce</span><span class="o">=</span><span class="s">"/68T/KlfvEd3p7T"</span>
<span class="n">qop</span><span class="o">=</span><span class="s">"auth"</span>
<span class="n">response</span><span class="o">=</span><span class="s">"30ffb4415b99d73da5ef3badee2781d1"</span>

<span class="c1"># Here we are following the steps outline in RFC 2616 section 3.2.2.1.
# Please see https://datatracker.ietf.org/doc/html/rfc2617#section-3.2.2.1
</span>
<span class="n">a1</span> <span class="o">=</span> <span class="n">username</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">realm</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">password</span>
<span class="n">a2</span> <span class="o">=</span> <span class="s">"INVITE"</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">uri</span>
<span class="n">secret</span> <span class="o">=</span> <span class="n">MD5</span><span class="p">(</span><span class="n">a1</span><span class="p">)</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">nonce</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">nc</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">cnonce</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">qop</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">MD5</span><span class="p">(</span><span class="n">a2</span><span class="p">)</span>

<span class="c1"># Do we match?
</span><span class="k">assert</span><span class="p">(</span><span class="n">MD5</span><span class="p">(</span><span class="n">secret</span><span class="p">)</span> <span class="o">==</span> <span class="n">response</span><span class="p">)</span>
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[This page has my notes taken during the reverse-engineering of the SIP protocol, as implemented in Asterisk.]]></summary></entry><entry><title type="html">Notes on Multiprecision Arithmetic</title><link href="https://mackinnon.info/2025/10/10/multiprecision-arithmetic.html" rel="alternate" type="text/html" title="Notes on Multiprecision Arithmetic" /><published>2025-10-10T00:00:00+00:00</published><updated>2025-10-10T00:00:00+00:00</updated><id>https://mackinnon.info/2025/10/10/multiprecision-arithmetic</id><content type="html" xml:base="https://mackinnon.info/2025/10/10/multiprecision-arithmetic.html"><![CDATA[<p>Arithmetic operations on integers that are larger than the word-size 
of the processor. Sometimes known as arbitrary-precision arithmetic, 
but “arbitrary” is a more general case of “multiple.”</p>

<p>Python does this automatically. Java uses BigInteger. There are other 
libraries.</p>

<p>This comes up (among other places) in RSA cryptography where we need
to compute things like:</p>

<p>c = (m<sup>e</sup>) mod n</p>

<p>With very large (ex: 128-bit) numbers.</p>

<h1 id="representation-using-multiple-digits">Representation Using Multiple Digits</h1>

<p>A two-digit representation using base B:</p>

<p>x = x<sub>1</sub>B<sup>m</sup> + x<sub>0<sub></sub></sub></p>

<h1 id="addition">Addition</h1>

<p>The addition to two N-digit numbers will result in (at most) an N+1 digit 
number.</p>

<h1 id="multiplication">Multiplication</h1>

<p>The multiplication of two N-digit number will result in (at most) a
2N digit number.</p>

<p>Karatsuba is a common efficiency improvement: https://en.wikipedia.org/wiki/Karatsuba_algorithm.</p>

<h2 id="division">Division</h2>

<p>If the goal is to divide Q = N / D, a starting estimate
for Q can be made by shifting N and D to the right by 
S bits.  Because Q = (N / s) / (D / s).</p>

<p>But this only works if there is significance in the first
S bits of both N and D. So a different algorithm would
look at the S <em>most significant</em> bits of N and D, keeping
in mind that the amount of the shift might be different 
for the two numbers.</p>

<h2 id="modulo">Modulo</h2>

<p>Discussion of multi-precision modulo: https://stackoverflow.com/questions/27057920/large-integer-arithmetic-how-to-implement-modulo</p>

<p>Summary: when computing D mod M you can remove any integer multiple of M from D withing impacting the answer. So keep
doing that until D - Q<em>M &lt;= M, and then modulo = D - Q</em>M.
This requires (1) a fast multiply (b) an iterative algorithm and (c) and a way of improving Q.</p>

<p>Another option: Figure out what 1/M is and then multiply
it by D to find Q. This might use the Newton-Raphson algorithm.</p>

<h1 id="references">References</h1>

<ul>
  <li>A GNU Library: https://gmplib.org/</li>
  <li>A good paper: https://www.hvks.com/Numerical/Downloads/HVE%20The%20Math%20behind%20arbitrary%20precision.pdf</li>
  <li>https://people.ece.cornell.edu/land/courses/ece4760/RP2350/arithmetic/index_arithmetic.html</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Arithmetic operations on integers that are larger than the word-size of the processor. Sometimes known as arbitrary-precision arithmetic, but “arbitrary” is a more general case of “multiple.”]]></summary></entry><entry><title type="html">Notes on RSA Cryptography</title><link href="https://mackinnon.info/2025/10/09/rsa-notes.html" rel="alternate" type="text/html" title="Notes on RSA Cryptography" /><published>2025-10-09T00:00:00+00:00</published><updated>2025-10-09T00:00:00+00:00</updated><id>https://mackinnon.info/2025/10/09/rsa-notes</id><content type="html" xml:base="https://mackinnon.info/2025/10/09/rsa-notes.html"><![CDATA[<p>These notes are written with a focus on the usage of RSA public-key cryptography
in the Asterisk (IAX2) implementation.</p>

<p>Per the IAX2 <a href="https://datatracker.ietf.org/doc/html/rfc5456">RFC 5456</a> in section 8.6.16, 
the RSA material exchanged in the authentication process follows the official RSA standard used documented in <a href="https://datatracker.ietf.org/doc/html/rfc2437">PKCS#1 V2.0 for RSA (RFC 2437)</a>. Note that there are later versions of RSA described (now up to V2.2).</p>

<h1 id="iax2-specific-notes">IAX2-Specific Notes</h1>

<p>During the AUTHREQ process the server sends a random integer (9-digit string) to the
client. The client “RSA signs” the SHA1 hash of this integer using its private key and 
sends back the resulting signature in the AUTHREP message.</p>

<p>The authentication process in the server involves these steps:</p>
<ul>
  <li>Hash the random integer using SHA1.</li>
  <li>Decrypt the signature received in the AUTHREP message to get the SHA1 hash 
that the client received.</li>
  <li>Compare the two to ensure a match.</li>
</ul>

<p>This is an RSVP1 operation as defined in the PKCS#1 terminology.</p>

<h1 id="notes-on-pkcs1-v20-rfc-2437">Notes on PKCS#1 V2.0 (RFC 2437)</h1>

<p>An overview of the PKCS#1 standard is here: https://en.wikipedia.org/wiki/PKCS_1.
Basically, this standard defines the terminology and data formats that allow the 
RSA math to be used in a practical application.</p>

<h2 id="representation-of-the-public-key">Representation of the Public Key</h2>

<p>The public key found in a .PEM file is a base-64 encoding of a complicated 
structure defined in the standard (specified using ASN.1). Part of the structure
has two important fields:</p>
<ul>
  <li>The RSA modulus n.</li>
  <li>The RSA public exponent e.</li>
</ul>

<p>The size of the modulus determines the size of the message that will be encrypted.</p>

<h2 id="encoding-the-message-eme-pkcs1-v1_5">Encoding the Message: EME-PKCS1-v1_5</h2>

<p>“Encoding” here doesn’t mean “encrypting.” The encoding process 
converts the message bytes (M) into something that can represented 
as an integer. The most important part is the padding since the 
encryption wants to work on a fixed block of data.</p>

<ul>
  <li>M is the original message.</li>
  <li>k is the length of the modulus (n) in octets.</li>
  <li>Message length can be up to k-11 octets. Padding is used to make up for any shortfall.</li>
  <li>The encoded message EM ends up being k-1 octets long.</li>
  <li>The padding process must add at least 8 octets of padding consisting of pseudorandomly generated nonzero octets. The pad is called PS.</li>
  <li>
    <p>The encoded message will look like this:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  EM = 02 || PS || 00 || M
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="converting-an-encoded-message-to-an-integer-os2ip-rfc-2437-section-42">Converting an Encoded Message to an Integer: OS2IP (RFC 2437 section 4.2)</h2>

<p>The OS2IP operation interprets a byte string as a big-endian representation 
of an integer. Each byte in the message is treated as a digit in a base-256 
numbering system. The first byte in the message becomes the most significant 
part of the integer.</p>

<h2 id="encryption-scheme-rsaes-pkcs1-v1_5">Encryption Scheme (RSAES-PKCS1-v1_5)</h2>

<p>https://crypto.stackexchange.com/questions/3617/how-do-ciphers-change-plaintext-into-numeric-digits-for-computing</p>

<h2 id="verification-rsavp1">Verification (RSAVP1)</h2>

<p>A verification primitive recovers the message representative from the
signature representative under the control of the corresponding
public key. This is exactly the same as decryption.</p>

<p>This requires Modular Exponentiation: https://en.wikipedia.org/wiki/Modular_exponentiation</p>

<h1 id="references">References</h1>

<p>https://www.practicalnetworking.net/series/cryptography/rsa-example/</p>

<p>https://crypto.stackexchange.com/questions/9896/how-does-rsa-signature-verification-work</p>]]></content><author><name></name></author><summary type="html"><![CDATA[These notes are written with a focus on the usage of RSA public-key cryptography in the Asterisk (IAX2) implementation.]]></summary></entry><entry><title type="html">Notes on the SA818 VHF/UHF Transceiver Module</title><link href="https://mackinnon.info/2025/10/07/sa818-module.html" rel="alternate" type="text/html" title="Notes on the SA818 VHF/UHF Transceiver Module" /><published>2025-10-07T00:00:00+00:00</published><updated>2025-10-07T00:00:00+00:00</updated><id>https://mackinnon.info/2025/10/07/sa818-module</id><content type="html" xml:base="https://mackinnon.info/2025/10/07/sa818-module.html"><![CDATA[<p><em>Copyright (C) Bruce MacKinnon, 2025.  Contact info at bottom of the page for comments/corrections.</em></p>

<p>I’ve been experimenting with this module. The datasheets are lacking a bit. Here 
are my notes.</p>

<p>The module has a power range of 3.3 to 5.5V (Vdd). When powered from a +5V
supply the logic I/Os including TXD, RXD, PTT, and AFSQ all operate using
3.3V logic. So there is no level-shifting required to communicate with 
the module from a 3.3V controller.</p>

<h1 id="audio-input">Audio Input</h1>

<p>A 10mVpp signal generator (HiZ) at 1 kHz gives a good tone on a receiver.</p>

<h1 id="fm-deviation-measurement">FM Deviation Measurement</h1>

<p>Here we use the Bessel null method to evaluate the deviation of the module.</p>

<p>Module is keyed with no audio input and the carrier is centered.</p>

<p><img src="/assets/images/deviation-PNG2.png" alt="Deviation2" /></p>

<p>Tone magnitude is set to 30mVpp and frequency to 1.5kHz. We see the expected sidebands appear:</p>

<p><img src="/assets/images/deviation-PNG3.png" alt="Deviation3" /></p>

<p>Tone frequency is reduced to 1.0kHz. We see the carrier start to diminish relative to the sidebands.</p>

<p><img src="/assets/images/deviation-PNG4.png" alt="Deviation4" /></p>

<p>Frequency is adjusted until the carrier is at the noise floor, about 850 Hz:</p>

<p><img src="/assets/images/deviation-PNG5.png" alt="Deviation5" /></p>

<p>Since the carrier’s first null will happen with a modulation index of 2.4, we can compute the deviation:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    Δf = deviation_index • modulation_frequency
</code></pre></div></div>

<p>So the deviation is about 2,040 Hz.</p>

<h1 id="programmingcommands">Programming/Commands</h1>

<p><a href="https://www.qsl.net/ta2ei/devreler/sa818/SA818%20programming%20manual.pdf">Programming Guide</a></p>

<p>IMPORTANT: The characters that make up a command (including the trailing CR/LF)
need to be sent in one batch. There is obviously some kind of internal timeout
that generates an error state if there is any delay between characters.
So <strong>connecting a terminal emulator and typing commands
interactively into the module (at human speed) won’t work.</strong></p>

<p>The examples in the programming guide notwithstanding, the frequencies must be
specified with 4 digits to the right of the decimal point.</p>

<p>AT+VERSION
AT+DMOCONNECT</p>
<h1 id="noaa-receiver">NOAA receiver</h1>
<p>AT+DMOSETGROUP=0,162.4750,162.4750,0000,1,0000
AT+DMOSETGROUP=0,146.5800,146.5800,0000,1,0000</p>
<h1 id="ctcss-tone885-on-transmit-and-receive">CTCSS tone=88.5 on transmit and receive</h1>
<p>AT+DMOSETGROUP=0,146.5800,146.5800,0008,1,0008
AT+DMOSETVOLUME=4</p>
<h1 id="query-for-rssi">Query for RSSI</h1>
<p>RSSI?</p>

<h1 id="a-python-cli-for-configuring-the-module">A Python CLI For Configuring The Module</h1>

<p>https://0x9900.com/programming-the-radio-module-sa818/</p>

<h1 id="other-notes">Other Notes</h1>

<p>There is an annoying lag between the activation of the PTT signal 
and the beginning of the transmission.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Copyright (C) Bruce MacKinnon, 2025. Contact info at bottom of the page for comments/corrections.]]></summary></entry></feed>