<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Caius&#x27; Lab - Meshtastic</title>
      <link>https://caius.dev/</link>
      <description></description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://caius.dev/tags/meshtastic/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sat, 25 Jan 2025 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Decoding Meshtastic Channel Links</title>
          <pubDate>Sat, 25 Jan 2025 00:00:00 +0000</pubDate>
          <author>Caius Brindescu</author>
          <link>https://caius.dev/blog/decoding-meshtastic-links/</link>
          <guid>https://caius.dev/blog/decoding-meshtastic-links/</guid>
          <description xml:base="https://caius.dev/blog/decoding-meshtastic-links/">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;a class=&quot;post-anchor&quot; href=&quot;#introduction&quot; aria-label=&quot;Anchor link for: introduction&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;If you don&#x27;t know what Meshtastic is, this &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=N3FXej9fqIk&quot;&gt;video has a great introduction&lt;&#x2F;a&gt;.
The TL;DR: is that it&#x27;s a communication protocol using 900 MHz (in the US) &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;LoRa&quot;&gt;LORA Radios&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Communication is done either via direct messages (DMs), or via channels.
The channels are encrypted, so for 2 parties to communicate, they need to know the encryption key (pre-shared key or PSK, for short) for that channel.
Sharing the chaneel name, key etc. is done via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;meshtastic.org&#x2F;e&#x2F;&quot;&gt;QR codes or URLs&lt;&#x2F;a&gt;.
In this blog post, we&#x27;ll look at what information is encoded when sharing channel settings, and we can decode it using a &quot;simple&quot; Python script.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-channel-link&quot;&gt;The channel link&lt;a class=&quot;post-anchor&quot; href=&quot;#the-channel-link&quot; aria-label=&quot;Anchor link for: the-channel-link&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;We&#x27;ll use the following link as an example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;https:&#x2F;&#x2F;meshtastic.org&#x2F;e&#x2F;#CjQSIOsfCkgIpGY_8iW02ad-4QPaCSBISJqzIVoZKHdqKXd8GgtCbG9nQ2hhbm5lbCUEAAAAEg4IATgBQANIAVAeWBRoAQ&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The part that we are interested in is the fragment:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CjQSIOsfCkgIpGY_8iW02ad-4QPaCSBISJqzIVoZKHdqKXd8GgtCbG9nQ2hhbm5lbCUEAAAAEg4IATgBQANIAVAeWBRoAQ&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Base64&quot;&gt;URL safe base64&lt;&#x2F;a&gt; encoded string that has the information we are looking for.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;structure&quot;&gt;Structure&lt;a class=&quot;post-anchor&quot; href=&quot;#structure&quot; aria-label=&quot;Anchor link for: structure&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The message is encoded using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;protobuf.dev&#x2F;&quot;&gt;protobuf&lt;&#x2F;a&gt;.
The structure we are interested in is defined &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;meshtastic&#x2F;protobufs&#x2F;blob&#x2F;2cffaf53e3faf1b6e41a8b8f05312f2f893be413&#x2F;meshtastic&#x2F;channel.proto&quot;&gt;in this file&lt;&#x2F;a&gt;.
It&#x27;s reasonably well documented, so I will not get into all the details here.
But the main properties we are interested in are the channel name, and the PSK.
They are encoded as the &lt;code&gt;psk&lt;&#x2F;code&gt; and &lt;code&gt;name&lt;&#x2F;code&gt; properties in the top level &lt;code&gt;ChannelSettings&lt;&#x2F;code&gt; message.&lt;&#x2F;p&gt;
&lt;p&gt;For the rest of this post, we&#x27;ll ignore the remainder of the properties, but they can be extracted using the same approach.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;protobuf-and-python&quot;&gt;Protobuf and Python&lt;a class=&quot;post-anchor&quot; href=&quot;#protobuf-and-python&quot; aria-label=&quot;Anchor link for: protobuf-and-python&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;generating-python-code-from-the-protobuf-structure&quot;&gt;Generating Python code from the protobuf structure&lt;a class=&quot;post-anchor&quot; href=&quot;#generating-python-code-from-the-protobuf-structure&quot; aria-label=&quot;Anchor link for: generating-python-code-from-the-protobuf-structure&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The first setup is installing all the prerequisites.
There are 2 parts: the protobuf compiler and the Python protobuf library.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; apt install protobuf-compiler&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;pip&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; install protobuf==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;3.20.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: I recommend using a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.python.org&#x2F;3&#x2F;library&#x2F;venv.html&quot;&gt;virtual enviroment&lt;&#x2F;a&gt; for your python setup.
Here&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;packaging.python.org&#x2F;en&#x2F;latest&#x2F;guides&#x2F;installing-using-pip-and-virtual-environments&#x2F;#create-and-use-virtual-environments&quot;&gt;a good resource for setting it up&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Next, we&#x27;ll need to get the protobuf definition &quot;compiled&quot; into python code, so we can the deserialize the configuration from the URL.
The protobuf definition &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;meshtastic&#x2F;protobufs&quot;&gt;lives in GitHub&lt;&#x2F;a&gt;, so we&#x27;ll need to clone it.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; clone git@github.com:meshtastic&#x2F;protobufs.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We&#x27;ll need to compile a total of four proto files to get this working.
First, let&#x27;s create a new folder for our generated files to live in:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;mkdir&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; meshtastic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then, &lt;code&gt;cd&lt;&#x2F;code&gt; into the &lt;code&gt;protobuf&lt;&#x2F;code&gt; folder, and run the following commands.
(You can compile all of them if you want to, but it&#x27;s not needed for this use case.)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;protoc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --python_out=..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; meshtastic&#x2F;apponly.proto&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;protoc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --python_out=..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; meshtastic&#x2F;channel.proto&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;protoc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --python_out=..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; meshtastic&#x2F;device_ui.proto&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;protoc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --python_out=..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; meshtastic&#x2F;config.proto&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, we need to make the &lt;code&gt;meshtastic&lt;&#x2F;code&gt; folder a python &quot;module&quot;, we we&#x27;ll create an empty &lt;code&gt;__init__.py&lt;&#x2F;code&gt; file inside:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;touch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; meshtastic&#x2F;__init__.py&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;decoding&quot;&gt;Decoding&lt;a class=&quot;post-anchor&quot; href=&quot;#decoding&quot; aria-label=&quot;Anchor link for: decoding&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;How that have all the prerequisites, let&#x27;s get decoding!&lt;&#x2F;p&gt;
&lt;p&gt;To start with we&#x27;ll need to import the required pacakges:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; meshtastic.apponly_pb2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; ChannelSet&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; base64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We&#x27;ll see the string we want to decode as a constant (for now):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;TO_DECODE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;CjESIMC70tNI5vkpQpHZGeg0WV7y6KqEoD0_t74fM1_jCaMkGghCbG9nVGVzdCUEAAAAEg4IATgBQANIAVAeWBRoAQ&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next, we&#x27;ll create a new, empty object to populate with the deserialized data:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;channelSet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; ChannelSet()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, let&#x27;s decode and display the channel name and PSK:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;channelSet.ParseFromString(base64.urlsafe_b64decode(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;TO_DECODE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;==&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; channelSet.settings:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;Channel name: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; s.name)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;Psk: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; base64.b64encode(s.psk).decode())&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A few notes on the implementation.
The settings are encoded in a &quot;URL Safe&quot; base64 encoding.
However, Python still expects the padding to be there, so we&#x27;re adding the maximum of 2 padding characters to get around this.
If that&#x27;s too many, any extras will be ignored by the decoder.
&quot;Proper&quot; URL Safe encoding explictly omits padding, but the Python library still requires it, so here we are.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;results&quot;&gt;Results&lt;a class=&quot;post-anchor&quot; href=&quot;#results&quot; aria-label=&quot;Anchor link for: results&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Running the above code, we&#x27;ll get the settings we expect:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ python decode.py &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Channel name: BlogTest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Psk: wLvS00jm+SlCkdkZ6DRZXvLoqoSgPT+3vh8zX+MJoyQ=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can extract other properties if we want to, using the same approach.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-end&quot;&gt;The End&lt;a class=&quot;post-anchor&quot; href=&quot;#the-end&quot; aria-label=&quot;Anchor link for: the-end&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
</description>
      </item>
    </channel>
</rss>
