<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jephter</title>
    <description>The latest articles on DEV Community by Jephter (@iamjephter).</description>
    <link>https://dev.to/iamjephter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3684933%2Fe38c6d29-1971-410b-af93-cc1dbbff8b76.png</url>
      <title>DEV Community: Jephter</title>
      <link>https://dev.to/iamjephter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamjephter"/>
    <language>en</language>
    <item>
      <title>Bridging QUIC and SIP: Building a Low-Latency Telephony Gateway in Rust</title>
      <dc:creator>Jephter</dc:creator>
      <pubDate>Tue, 21 Jul 2026 23:13:18 +0000</pubDate>
      <link>https://dev.to/iamjephter/bridging-quic-and-sip-building-a-low-latency-telephony-gateway-in-rust-4dd3</link>
      <guid>https://dev.to/iamjephter/bridging-quic-and-sip-building-a-low-latency-telephony-gateway-in-rust-4dd3</guid>
      <description>&lt;p&gt;&lt;em&gt;A low-latency media gateway in Rust: the client stream runs on QUIC, the carrier stream runs on UDP, and Tokio keeps the heavy CPU-bound transcoding work away from the async I/O threads.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem &amp;amp; Why VoIP Needs a Unified Gateway
&lt;/h2&gt;

&lt;p&gt;Many web-to-telephony platforms have a quiet design bottleneck: they rely on large WebRTC infrastructure that can be expensive to scale, and most teams accept the resource cost without question.&lt;/p&gt;

&lt;p&gt;Traditional telephony calls commonly use the &lt;strong&gt;G.711 companding standard&lt;/strong&gt;, which samples mono audio at 8kHz and represents each sample with 8 bits, producing a constant &lt;strong&gt;64 kbps codec payload&lt;/strong&gt;. The real network usage is higher after RTP, UDP, IP, and link-layer headers are included. In low-connectivity environments (such as satellite links, remote base stations, or cellular edges), maintaining that rate per call can be expensive and sensitive to network jitter.&lt;/p&gt;

&lt;p&gt;The packet math makes the difference clear. With 20ms packetization, G.711 produces 160 bytes of audio fifty times per second. Add the minimum 12-byte RTP header, 8-byte UDP header, and 20-byte IPv4 header, and the rate is already about &lt;strong&gt;80 kbps at the IP layer&lt;/strong&gt;, before Ethernet, tunneling, encryption, or provider overhead. That is why I separate codec bitrate from actual network bandwidth when planning capacity.&lt;/p&gt;

&lt;p&gt;If you are newer to digital signal processing: &lt;strong&gt;companding&lt;/strong&gt; (compressing and expanding) is a legacy technique that maps linear PCM samples into 8-bit logarithmic representations. This saved wire bandwidth on early networks but limits traditional telephony audio to a narrow frequency range.&lt;/p&gt;

&lt;p&gt;Deep learning speech models offer an alternative: &lt;strong&gt;Google's Lyra&lt;/strong&gt;, a neural audio codec that uses generative speech models to compress speech to &lt;strong&gt;3.2 to 9.2 kbps&lt;/strong&gt;. That is the encoded speech bitrate reported by the Google project, not the full on-the-wire rate after transport overhead.&lt;/p&gt;

&lt;p&gt;Lyra is the codec target for this architecture, but I need to be precise about the current repository. When prebuilt Lyra libraries are unavailable, &lt;code&gt;build.rs&lt;/code&gt; compiles &lt;code&gt;external/lyra/lyra_production_real.cpp&lt;/code&gt;. That file implements a custom experimental transform and quantizer; it does not use Google's Lyra model and should not be described as Google Lyra-compatible. The transport and FFI design can host the real codec, but the bitstream used by the default local test build is its own format.&lt;/p&gt;

&lt;p&gt;The hard part is not only the compression. The hard part is moving it without breaking the latency requirements of real-time voice.&lt;/p&gt;

&lt;p&gt;Browsers cannot speak SIP directly without a gateway, and legacy carriers do not understand modern QUIC-based protocols or application-specific speech codecs. To bridge this gap, I built a unified gateway in Rust. The REST control plane sits in &lt;code&gt;backend&lt;/code&gt;, while the real-time transcoding and media forwarding engines sit in &lt;code&gt;sip_gateway&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you are newer to telephony: &lt;strong&gt;SIP (Session Initiation Protocol)&lt;/strong&gt; is the signaling standard that handles call setup, ringing, and teardown - think of it as the control plane. &lt;strong&gt;RTP (Real-time Transport Protocol)&lt;/strong&gt; carries the actual audio payload. Legacy carriers commonly rely on SIP and RTP. On the web side, &lt;strong&gt;QUIC&lt;/strong&gt; is a multiplexed, encrypted transport built over UDP, and &lt;strong&gt;WebTransport&lt;/strong&gt; is the browser API that gives web applications access to low-latency streams and datagrams over HTTP/3.&lt;/p&gt;

&lt;p&gt;In plain terms, a gateway is a translator. It accepts modern, lightweight QUIC packets from a browser, decodes the negotiated speech payload, transcodes it into traditional logarithmic telephony bytes, and serializes it into RTP/UDP packets for the carrier. Symmetrically, it performs the reverse process for the incoming stream.&lt;/p&gt;

&lt;p&gt;I built the gateway this way because I wanted to avoid the usual architectural trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not a heavy WebRTC signaling node that duplicates media state across the database path.&lt;/li&gt;
&lt;li&gt;Not a generic proxy that stalls under heavy CPU loads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, the gateway is an &lt;strong&gt;ephemeral, stateful media engine&lt;/strong&gt;. It authenticates users at the cloud edge, maps their active sessions in memory, transcodes their audio streams, and terminates connections cleanly when the carrier hangs up. It avoids database access in the per-packet media path, but it is not stateless while calls are active.&lt;/p&gt;

&lt;p&gt;That sounds abstract until you look at the scheduling boundaries of real-time voice. A 20ms audio frame gives the system a tight processing budget. If CPU-heavy codec work runs inside the same runtime threads that poll network sockets, it can starve the executor and turn compute delay into jitter or dropped audio.&lt;/p&gt;

&lt;p&gt;The 20ms frame duration is not a claim that users hear every single 20ms delay. It is one part of the engineering budget. Capture, packetization, encoding, queueing, network transit, jitter buffering, decoding, resampling, and playout all contribute to one-way latency. The goal is to keep every stage bounded because small delays accumulate across the complete path.&lt;/p&gt;

&lt;p&gt;The result is a dedicated, decoupled media plane. That separation is the core of the gateway's design.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rust, WebTransport, and the Telephony Gateway Architecture
&lt;/h2&gt;

&lt;p&gt;The gateway is split into two halves with a strict boundary between them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;backend&lt;/code&gt; (Control Plane)&lt;/strong&gt;: An Axum service on Tokio with a SurrealDB-backed store. It manages user registrations, session mapping, and token authorization. It generates short-lived WebTransport JWT authentication tokens but never enters the per-frame media path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sip_gateway&lt;/code&gt; (Media Bridge)&lt;/strong&gt;: The real-time media server. It hosts the QUIC server accepting WebTransport connections from clients, manages Media-over-QUIC (MoQ) tracks, binds to UDP sockets for legacy RTP streams, and handles native codec/PCM &amp;lt;=&amp;gt; G.711 conversion through C++ FFI wrappers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the container level, the system is a bidirectional routing bridge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                      +-----------------------+
                      |     backend (Axum)    |
                      |  [Control &amp;amp; Sessions] |
                      +-----------+-----------+
                                  | JWT / API Keys
                                  v
+--------------+    QUIC      +------------------------+   SIP/UDP   +-----------------+
|  Web Client  |&amp;lt;------------&amp;gt;|      sip_gateway       |&amp;lt;-----------&amp;gt;|   SIP Carrier   |
| (WT / MoQ)   | (WebTransport|                        | (RTP/G.711) |  (PSTN Network) |
|              |    &amp;amp; MoQ)    |    [Transcoding:       |             |                 |
|              |              |   Codec/PCM &amp;lt;-&amp;gt; G.711] |             |                 |
+--------------+              +------------------------+             +-----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway manages media session states via three core components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MediaEngine&lt;/code&gt;: Holds codec configurations and sample rates (48kHz for the current web-side codec configuration, 8kHz for G.711).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MediaBridge&lt;/code&gt;: Coordinates active call media streams and owns the maps of stateful encoders and decoders.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RtpHandler&lt;/code&gt;: Binds to the local UDP port, deserializes incoming RTP packets, and serializes outgoing companded payloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The native codec selection deserves special attention. &lt;code&gt;build.rs&lt;/code&gt; can link prebuilt libraries, but when those libraries are absent it automatically compiles the custom C++ wrapper and still enables the internal &lt;code&gt;cfg(lyra)&lt;/code&gt; path. That means a successful build, or even a successful &lt;code&gt;LyraEncoder::new&lt;/code&gt;, does not prove that Google's Lyra implementation was linked. A deployment that promises Google Lyra must identify the exact library and model artifacts at build time, verify bitstream interoperability against Google's encoder and decoder, and expose the selected codec implementation in readiness and metrics.&lt;/p&gt;

&lt;p&gt;We keep the media forwarding plane in memory on purpose. Voice routing is hot state. Querying a database to check where to send the next 20ms audio packet would damage real-time latency. The control plane creates the session and authentication context, and the media plane routes active frames in memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Design Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Offloading C++ FFI Transcoding to Tokio's Blocking Pool
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Problem&lt;/strong&gt;: Executing native codec processing through C++ FFI is blocking work. A real neural codec can make that work especially expensive. Running it directly on Tokio's cooperative async workers can prevent those workers from polling network sockets on time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Solution&lt;/strong&gt;: We delegate FFI transcoding tasks to Tokio's blocking thread pool using &lt;code&gt;tokio::task::spawn_blocking&lt;/code&gt;. The async I/O loop reads the network data, extracts the buffer, and transfers ownership of the codec work to the blocking pool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Trade-off&lt;/strong&gt;: Blocking tasks introduce scheduling and ownership overhead. More importantly, Tokio's blocking pool is not an unlimited CPU scheduler. At high call counts, the gateway still needs bounded admission or a fixed-size codec worker pool to prevent too many CPU-bound jobs from running at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are newer to this language: &lt;code&gt;tokio::task::spawn_blocking&lt;/code&gt; takes a closure, runs it on a Tokio-managed blocking thread pool, and returns a &lt;code&gt;JoinHandle&lt;/code&gt; future. It protects the cooperative async workers, but it does not create a permanently dedicated thread for every call. Once a blocking task has started, aborting its handle does not stop the native operation. The codec call must return by itself, which is another reason to bound the work and understand its worst-case execution time.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. "Take, Execute, Put Back" Concurrency
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Problem&lt;/strong&gt;: Neural codecs are highly stateful. They maintain internal history and synthesis state. Sharing an encoder instance across streams is incorrect, and locking a global codec registry map while native code runs creates severe contention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Solution&lt;/strong&gt;: We implement a "Take, Execute, Put Back" pattern. When a frame arrives, the stream task briefly acquires a write lock, removes the encoder instance from the map, and immediately drops the lock. The task moves ownership of the encoder to the blocking thread. Once encoding is done, we re-acquire the lock briefly and insert the encoder back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Trade-off&lt;/strong&gt;: This pattern requires two lock acquisitions per frame, but the critical sections contain only hash map operations. It also depends on frames for one stream being processed sequentially. If two frames for the same stream arrive concurrently, the second can observe that the codec has temporarily been removed and take a fallback path. A per-stream worker or mutex is the safer production evolution of this design.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Remove the encoder to take exclusive ownership, dropping the lock immediately&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;encoder_opt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.encoders&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder_opt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encode_res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoder_opt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;encode_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;task&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn_blocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="nf"&gt;.encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;audio_frame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;encode_task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Lyra encode task failed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Re-acquire lock briefly to put the encoder back&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoder_opt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.encoders&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Direct Logarithmic Companding in the RTP Layer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Problem&lt;/strong&gt;: Converting linear PCM samples to G.711 (mu-law or A-law) bytes during packet serialization can bottleneck throughput if it relies on unnecessary intermediate work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Solution&lt;/strong&gt;: The &lt;code&gt;RtpHandler&lt;/code&gt; uses direct bitwise mapping functions to convert samples during serialization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Trade-off&lt;/strong&gt;: This couples companding logic directly to the RTP layer and still performs heap allocations. The current implementation creates both an RTP packet vector and a payload vector, so it is allocation-conscious, not zero-allocation. The next optimization is to reserve the final packet and write the payload directly after the header.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In sip_gateway/src/transport/sip/rtp_handler.rs&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;CodecType&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;G711MuLaw&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;audio_frame&lt;/span&gt;
        &lt;span class="py"&gt;.samples&lt;/span&gt;
        &lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;linear_to_mulaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nn"&gt;CodecType&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;G711ALaw&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;audio_frame&lt;/span&gt;
        &lt;span class="py"&gt;.samples&lt;/span&gt;
        &lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;linear_to_alaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nd"&gt;unreachable!&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Companding the payload is only one part of correct RTP behavior. The current handler is intentionally narrow: it derives sequence information from the frame timestamp, writes a millisecond value into the RTP timestamp field, and uses a fixed SSRC. Carrier-ready RTP requires independent per-stream state, suitable random starting sequence numbers, timestamps, and SSRC values, and a G.711 timestamp that advances by 160 clock ticks for each 20ms packet at 8kHz. It also requires parsing or explicitly rejecting CSRC entries, header extensions, and padding, then using RTCP reports for loss, jitter, and synchronization.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Codec Fallback Must Be Negotiated
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Problem&lt;/strong&gt;: Some current error paths send raw or simply mapped audio bytes when native codec creation or encoding fails. Those bytes are not automatically valid for the codec the track advertised.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Solution&lt;/strong&gt;: Put the codec implementation, codec identity, and payload version in the media envelope. If the selected codec becomes unavailable, either renegotiate a codec the peer supports or fail the stream clearly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Trade-off&lt;/strong&gt;: Failing a call is visible, but silently changing the byte format under the same track is worse because it produces corruption that looks like a network or decoder problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Decoupling WebTransport Signaling from the Core Database
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Problem&lt;/strong&gt;: Putting database operations inside the media path adds latency and gives every audio frame a dependency on persistent storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Solution&lt;/strong&gt;: Establish and authenticate the session first, keep active routing state in memory, and move persistent analytics or call records outside the per-frame path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Trade-off&lt;/strong&gt;: In-memory state is fast, but it disappears when a node crashes. Important call events need a reliable asynchronous persistence strategy rather than untracked background tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My production-hardening work covers protocol versioning, bounded codec workers, and proper load benchmarks. The current C++ FFI bindings run on CPU cores, so I measure and control the call capacity of a gateway node before considering hardware acceleration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Dives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1: Tuning UDP Sockets at the OS Edge
&lt;/h3&gt;

&lt;p&gt;In high-concurrency telephony gateways, standard socket defaults may be insufficient. When processing many concurrent media streams, OS-level UDP receive queues can overflow during bursts, resulting in packet loss and audio stutter.&lt;/p&gt;

&lt;p&gt;The current &lt;code&gt;RtpHandler&lt;/code&gt; binds its socket directly with &lt;code&gt;tokio::net::UdpSocket::bind&lt;/code&gt;. It does &lt;strong&gt;not yet&lt;/strong&gt; configure larger &lt;code&gt;SO_RCVBUF&lt;/code&gt; or &lt;code&gt;SO_SNDBUF&lt;/code&gt; values with &lt;code&gt;socket2&lt;/code&gt;. I treat that as deployment work to measure and tune, not as a performance feature that already exists.&lt;/p&gt;

&lt;p&gt;The production socket setup configures the socket before handing it to Tokio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;socket2&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SocketAddr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Domain&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;IPV4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DGRAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="nf"&gt;.set_reuse_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="nf"&gt;.set_recv_buffer_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2_097_152&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="nf"&gt;.set_send_buffer_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2_097_152&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SocketAddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.0.0.0:40000"&lt;/span&gt;&lt;span class="nf"&gt;.parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="nf"&gt;.bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;std_socket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UdpSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;std_socket&lt;/span&gt;&lt;span class="nf"&gt;.set_nonblocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tokio_socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;UdpSocket&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std_socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buffer sizes must be verified on the deployed operating system because kernels can clamp or transform the requested values. Larger buffers absorb short bursts; they do not remove the need for fast consumers, packet-loss metrics, and an explicit overload policy.&lt;/p&gt;

&lt;h3&gt;
  
  
  2: QUIC and WebTransport Session Negotiation
&lt;/h3&gt;

&lt;p&gt;In this gateway, WebTransport runs over HTTP/3 and QUIC. During connection setup, the browser and server establish QUIC with TLS 1.3 and negotiate HTTP/3 through ALPN. The client then creates a WebTransport session with an HTTP extended &lt;code&gt;CONNECT&lt;/code&gt; request. The exact negotiation fields have changed between WebTransport draft versions, so the client, server, and &lt;code&gt;h3-webtransport&lt;/code&gt; library versions must be tested together.&lt;/p&gt;

&lt;p&gt;To manage authentication at the gateway edge, we parse the request URL query parameters to extract the JWT token generated by our Axum control plane. The gateway verifies the HS256 signature, expiration, and expected audience before accepting the session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Extract JWT from WebTransport CONNECT query string&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;validate_gateway_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;None&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;GatewayError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;AuthenticationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Missing token"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because query strings can appear in proxy or access logs, these short-lived tokens must be redacted from logs. URI-based bearer tokens are generally discouraged for exactly this reason. The current W3C WebTransport specification includes request headers, so my preferred path is an appropriate header when the target browsers support it. When compatibility requires a URL token, I make it short-lived and single-use, bind it to the intended call or session, and reject replay through its JWT ID. The split-based query parsing in the current gateway also needs a proper URL query parser so encoded values and duplicate keys are handled safely.&lt;/p&gt;

&lt;p&gt;The gateway currently pins validation to HS256 and checks the expected audience. A stronger service boundary also requires issuer validation, required identity claims, key rotation, and asymmetric signing when the gateway should hold only a verification key instead of the backend's signing secret.&lt;/p&gt;

&lt;p&gt;If the JWT is valid, the gateway accepts the WebTransport session and binds the validated identity to it. QUIC supports connection migration and NAT rebinding, but that protocol capability does not guarantee a seamless Wi-Fi-to-cellular handoff by itself. Browser behavior, path validation, timeouts, and application session state all matter. In the current code, &lt;code&gt;transport.allow_spin(true)&lt;/code&gt; enables QUIC's spin bit; it does not enable migration, so migration behavior must be verified with integration tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  3: Media-over-QUIC (MoQ) Pub/Sub Architecture
&lt;/h3&gt;

&lt;p&gt;Once the WebTransport session is established, media is routed through &lt;code&gt;moq-lite&lt;/code&gt; tracks. MoQ uses a publisher/subscriber model in which media is organized into tracks, groups, and objects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Track&lt;/strong&gt;: A continuous media source (for example, &lt;code&gt;"audio-user_123"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group&lt;/strong&gt;: A collection of media objects and a useful subscription boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object&lt;/strong&gt;: An addressable media unit containing bytes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the gateway, we publish each audio frame by creating and closing one producer group. The payload begins with an 8-byte little-endian timestamp, followed by the encoded audio bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In sip_gateway/src/transport/web/moq.rs&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="nf"&gt;.append_group&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;frame_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;encoded_data&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;frame_data&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="nf"&gt;.to_le_bytes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;frame_data&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;encoded_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="nf"&gt;.write_frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame_data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="nf"&gt;.close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the current implementation uses &lt;strong&gt;one group per audio frame&lt;/strong&gt;, not one group per second of audio. Closing the group also does not automatically drop stale media. QUIC streams are reliable and ordered within a stream, so a real-time implementation still needs an explicit freshness policy using delivery timeouts, stream cancellation, datagrams, bounded queues, or timestamp-based discarding at the receiver. Current MOQT drafts define object and subgroup delivery timeouts for this purpose, but the application still has to configure and test them through the library version it deploys.&lt;/p&gt;

&lt;p&gt;MoQ is still evolving as an IETF draft. The timestamp prefix is an application-specific format, so a stable wire contract needs an explicit version, codec identifier, sample rate, channel count, timestamp unit, and sequence number before clients depend on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4: Session Lifecycles on SIP Carrier Hangup
&lt;/h3&gt;

&lt;p&gt;When a PSTN carrier hangs up an established call, it sends a SIP &lt;code&gt;BYE&lt;/code&gt;. A &lt;code&gt;CANCEL&lt;/code&gt; is used to stop an unanswered INVITE transaction before the call is established. Both messages contain the SIP &lt;code&gt;Call-ID&lt;/code&gt; used for the dialog or transaction.&lt;/p&gt;

&lt;p&gt;However, the internal registry (&lt;code&gt;active_calls&lt;/code&gt;) maps calls by an internal call ID. If the server tries to process a hangup using the SIP &lt;code&gt;Call-ID&lt;/code&gt; directly as the lookup key, the removal fails. The active call entry remains in memory, and the background media tasks can continue running.&lt;/p&gt;

&lt;p&gt;To fix this crossover leak, the gateway scans active calls, resolves the SIP header to the correct internal registry key, aborts the tasks explicitly, and removes the call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In sip_gateway/src/transport/sip/server.rs&lt;/span&gt;

&lt;span class="c1"&gt;// Map incoming sip_call_id to internal call_id&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;internal_call_id_opt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;
    &lt;span class="nf"&gt;.values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.find&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.sip_call_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;sip_call_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.call_id&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_call_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;internal_call_id_opt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="nf"&gt;.get_mut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;internal_call_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="py"&gt;.web_to_pstn_task&lt;/span&gt;&lt;span class="nf"&gt;.take&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="nf"&gt;.abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="py"&gt;.pstn_to_web_task&lt;/span&gt;&lt;span class="nf"&gt;.take&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="nf"&gt;.abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="nf"&gt;.remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;internal_call_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that the gateway requests cancellation of both media tasks when a call is terminated. A secondary &lt;code&gt;sip_call_id -&amp;gt; internal_call_id&lt;/code&gt; index removes the scan across active calls, and structured cancellation makes cleanup awaitable and verifiable.&lt;/p&gt;

&lt;p&gt;The server also contains a SIP shutdown method that hangs up active calls, but the process currently handles &lt;code&gt;Ctrl-C&lt;/code&gt; without calling and awaiting that method, and it does not yet wire the production &lt;code&gt;SIGTERM&lt;/code&gt; drain path. My rolling-deployment sequence is to stop accepting new calls, mark the instance as draining, allow active calls to finish within a grace period, and then force cleanup. Receiving a signal is not graceful shutdown by itself; the resource lifecycle has to be completed before the process leaves.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Type System Enforces
&lt;/h2&gt;

&lt;p&gt;Rust's type system helps us enforce architectural invariants at compile time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Algebraic Data Types (ADTs)&lt;/strong&gt;: We use enums instead of raw strings to represent call topologies (&lt;code&gt;WebToPstn&lt;/code&gt;, &lt;code&gt;PstnToWeb&lt;/code&gt;, &lt;code&gt;WebToWeb&lt;/code&gt;). This lets the compiler check exhaustive routing matches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership&lt;/strong&gt;: Moving an encoder or decoder into &lt;code&gt;spawn_blocking&lt;/code&gt; prevents simultaneous Rust access to that codec instance while the blocking operation runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Error Paths&lt;/strong&gt;: &lt;code&gt;Result&lt;/code&gt; makes setup, codec, and transport failures visible in the control flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FFI Safety Bounds&lt;/strong&gt;: The wrappers manually declare &lt;code&gt;Send&lt;/code&gt; and &lt;code&gt;Sync&lt;/code&gt; with &lt;code&gt;unsafe impl&lt;/code&gt;. This is a promise made to the compiler, not something the compiler proves about the C++ library. Each declaration must match the native library's real thread-safety guarantees.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Performance, Backpressure, and Bounded State
&lt;/h2&gt;

&lt;p&gt;I build the gateway around explicit limits because unbounded behavior is where systems quietly become unreliable under load.&lt;/p&gt;

&lt;p&gt;The current repository configures QUIC flow-control windows and datagram buffers, but it does &lt;strong&gt;not yet&lt;/strong&gt; contain bounded per-stream audio channels. It also has a &lt;code&gt;jitter_buffer_ms&lt;/code&gt; configuration value set to &lt;code&gt;100&lt;/code&gt;, but that value alone is not an implemented jitter buffer. The active stream maps are created with &lt;code&gt;HashMap::new()&lt;/code&gt;, not pre-allocated to a measured call capacity.&lt;/p&gt;

&lt;p&gt;These are the production controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bounded Per-Stream Queues&lt;/strong&gt;: Preserve frame order and apply a defined overload policy when codec workers fall behind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Late-Frame Handling&lt;/strong&gt;: Drop audio that has missed its playback deadline instead of allowing reliable delivery to build delay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Real Jitter Buffer&lt;/strong&gt;: Reorder packets within a measured delay budget and expose late, lost, and discarded frame metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capacity Limits&lt;/strong&gt;: Bound active calls, codec jobs, sessions, tracks, and memory per connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository currently has no Criterion benchmark suite proving a specific frame-processing time. I do not publish a latency number until it is measured on specified hardware with release builds, the exact native codec implementation named, realistic bidirectional calls, and latency percentiles.&lt;/p&gt;

&lt;p&gt;I ran &lt;code&gt;cargo test --lib&lt;/code&gt; against the current default build: all &lt;strong&gt;40 tests passed&lt;/strong&gt;. The build log also confirmed that prebuilt Lyra libraries were not found and the custom C++ wrapper was compiled instead. In its round-trip test, that wrapper encoded one 20ms, 960-sample frame to 248 bytes. At fifty frames per second, that is &lt;strong&gt;99.2 kbps of codec payload&lt;/strong&gt;, so this fallback does not demonstrate Google's 3.2 to 9.2 kbps bandwidth result. The test proves that the local encoder and decoder agree with each other; it does not prove Google Lyra bitstream compatibility, speech quality, packet-loss behavior, or production capacity.&lt;/p&gt;

&lt;p&gt;The current resampler also uses simple linear interpolation between 8kHz and 48kHz. It is easy to follow, but production requires measured audio quality or a proper band-limited resampling library.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Measure Before Production
&lt;/h3&gt;

&lt;p&gt;The gateway already exposes health and JSON metrics endpoints and keeps call-level metric structures. That is a useful start, but some values are placeholders or are not yet wired directly into the media path. Before production, I measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Codec queue time separately from native encode and decode execution time.&lt;/li&gt;
&lt;li&gt;End-to-end frame age at p50, p95, p99, and maximum.&lt;/li&gt;
&lt;li&gt;RTP packets sent, received, lost, late, duplicated, and reordered.&lt;/li&gt;
&lt;li&gt;Jitter-buffer depth, late-frame discards, and concealment events.&lt;/li&gt;
&lt;li&gt;MoQ objects expired, cancelled, dropped, or delivered too late to play.&lt;/li&gt;
&lt;li&gt;Active calls, active codec jobs, Tokio task count, socket count, and memory per call.&lt;/li&gt;
&lt;li&gt;Call setup time, teardown time, and leaked resources after repeated call churn.&lt;/li&gt;
&lt;li&gt;Calls per core for each codec implementation and configured bitrate, with the CPU model and compiler flags recorded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An average alone can hide the exact stalls that damage speech. For this kind of system, queue depth and tail latency tell me more than a single throughput number.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scaling and Failure Boundaries
&lt;/h2&gt;

&lt;p&gt;The media gateway keeps live call state in memory, so horizontal scaling is not just adding replicas behind a generic load balancer. New calls can be distributed across nodes, but all packets and control actions for an active call must reach the node that owns its SIP dialog, RTP socket, WebTransport session, MoQ tracks, and codec state.&lt;/p&gt;

&lt;p&gt;In production, I make that ownership explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route a call to one gateway node and keep it there for the call's lifetime.&lt;/li&gt;
&lt;li&gt;Advertise RTP addresses and ports that remain reachable for that node.&lt;/li&gt;
&lt;li&gt;Stop assigning new calls before a node is drained or replaced.&lt;/li&gt;
&lt;li&gt;Keep control-plane call records separate from media state so the backend can reconcile a node failure.&lt;/li&gt;
&lt;li&gt;Accept that losing a media node normally drops its active calls unless the system implements a much more expensive state-transfer design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why I call the gateway ephemeral and stateful. The node can be replaced, but an active call is still tied to the resources owned by that node.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By isolating native FFI codec work, keeping collection locks brief, and enforcing explicit lifecycle cleanup, the Rust telephony gateway creates a practical bridge between modern QUIC-based web clients and legacy SIP/RTP carriers.&lt;/p&gt;

&lt;p&gt;The architecture is the strongest part of the system, but the implementation still needs bounded codec scheduling, RTP hardening, a real jitter buffer, a versioned media envelope, and reproducible load benchmarks before making production-scale performance claims.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The I/O loops own network scheduling. The blocking pool owns compute. The gateway owns resource boundaries.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Standards and Documentation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/google/lyra" rel="noopener noreferrer"&gt;Google Lyra: generative low-bitrate speech codec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.itu.int/rec/T-REC-G.711" rel="noopener noreferrer"&gt;ITU-T G.711: Pulse code modulation of voice frequencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.itu.int/rec/T-REC-G.114" rel="noopener noreferrer"&gt;ITU-T G.114: One-way transmission time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc3261.html" rel="noopener noreferrer"&gt;RFC 3261: SIP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc3550.html" rel="noopener noreferrer"&gt;RFC 3550: RTP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9000.html" rel="noopener noreferrer"&gt;RFC 9000: QUIC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/webtransport/" rel="noopener noreferrer"&gt;W3C WebTransport specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/" rel="noopener noreferrer"&gt;WebTransport over HTTP/3: current IETF draft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/draft-ietf-moq-transport/" rel="noopener noreferrer"&gt;Media over QUIC Transport: current IETF draft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html" rel="noopener noreferrer"&gt;Tokio &lt;code&gt;spawn_blocking&lt;/code&gt; documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.rust-lang.org/nomicon/send-and-sync.html" rel="noopener noreferrer"&gt;Rustonomicon: &lt;code&gt;Send&lt;/code&gt; and &lt;code&gt;Sync&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6750.html" rel="noopener noreferrer"&gt;RFC 6750: Bearer token usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8725.html" rel="noopener noreferrer"&gt;RFC 8725: JWT best current practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>quic</category>
      <category>webtransport</category>
      <category>voip</category>
    </item>
    <item>
      <title>Architecting a Blind Relay: E2EE Clipboard Sync with Rust and Tauri</title>
      <dc:creator>Jephter</dc:creator>
      <pubDate>Wed, 29 Apr 2026 12:19:09 +0000</pubDate>
      <link>https://dev.to/iamjephter/building-a-blind-relay-in-rust-with-tauri-at-the-edge-57gp</link>
      <guid>https://dev.to/iamjephter/building-a-blind-relay-in-rust-with-tauri-at-the-edge-57gp</guid>
      <description>&lt;p&gt;&lt;em&gt;A blind relay in Rust for encrypted clipboard sync: the client owns the key, and the server only moves ciphertext.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem &amp;amp; Why Clipboard Sync Needs a Blind Relay
&lt;/h2&gt;

&lt;p&gt;Most clipboard sync tools have a quiet design flaw: the server can read everything—and most people never notice.&lt;/p&gt;

&lt;p&gt;Echo is my cross-device clipboard sync project. Copy text on one device, paste it on another. The hard part is not moving the text. The hard part is moving it without teaching the server how to read it. Echo is end-to-end encrypted clipboard sync built on a blind relay: Tauri sits at the native edge, the Rust/Axum backend handles auth and delivery, and the backend can move data without becoming part of the trust model.&lt;/p&gt;

&lt;p&gt;In plain terms, a blind relay is a courier. It can check who you are, carry the package, and deliver it to the right place. It cannot open the package. In Echo, the package is ciphertext, and only the devices hold the key.&lt;/p&gt;

&lt;p&gt;I built Echo this way because I did not want the usual trade. Not “encrypted in transit” with plaintext sitting in the middle. Not “encrypted at rest” with a backend that can still inspect what it relays. In Echo, “blind relay” means the server can authenticate the user, enforce rate limits, store ciphertext, send it to the right devices, and wake sleeping clients when needed. It does not get a decryption path.&lt;/p&gt;

&lt;p&gt;That sounds abstract until you look at what actually ends up on a clipboard. SSH commands with temporary credentials. Customer data copied out of an internal tool. Admin URLs with tokens embedded in query params. One-time codes. Half-finished production queries. The clipboard is full of data that is sensitive precisely because it is short-lived and easy to treat casually.&lt;/p&gt;

&lt;p&gt;The first time I felt this sharply was not during some security review. It was while moving a short-lived command between machines and realizing the fastest path was still a chat window. That is the normal failure mode. Not a dramatic breach. Just a convenient tool inheriting data it should never have seen.&lt;/p&gt;

&lt;p&gt;What I built is simple to describe and strict in practice. Echo watches the clipboard on one device, encrypts the payload on the client with XChaCha20-Poly1305, sends ciphertext and a nonce through an Axum WebSocket relay, then decrypts only on the receiving device. The important choices are the typed WebSocket protocol, a one-owner WebSocket sink, bounded queues, and client-side encryption.&lt;/p&gt;

&lt;p&gt;The result is a ciphertext relay, not a server-side encryption story. That distinction is the article.&lt;/p&gt;

&lt;p&gt;Once I decided the server would be blind, the architecture got much simpler. The client owns the key. The client encrypts before the network. The backend handles delivery, ordering, and backpressure. Rust ended up being the right language because the hard parts were ownership, protocol design, concurrency, and removing bad states before they spread.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust, Tauri, and the Blind Relay Architecture
&lt;/h2&gt;

&lt;p&gt;Echo has two distinct halves and one hard boundary between them.&lt;/p&gt;

&lt;p&gt;The backend is an Axum service on Tokio with SQLx behind it. It owns authentication, the typed WebSocket protocol, live fan-out, bounded history, and push-token persistence. The client is a Tauri shell with a React app inside it. Tauri handles the native clipboard boundary and local persistence. The React layer handles keys, pairing, reconnect behavior, encryption, and UI state.&lt;/p&gt;

&lt;p&gt;I do not think of Echo as a generic Tauri clipboard sync app. The Rust Tauri architecture matters because the trust boundary is split cleanly: native clipboard work at the edge, ciphertext coordination in the backend.&lt;/p&gt;

&lt;p&gt;At the container level, the system is just a left-to-right relay with two side channels: durable ciphertext history and push wake-ups for sleeping devices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayz9ptawxfnxm9nwtuq9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayz9ptawxfnxm9nwtuq9.png" alt="Mermaid-style architecture diagram for Echo, an encrypted clipboard sync system built with Rust, Tauri, Axum WebSockets, client-side encryption, and a blind relay server that only forwards ciphertext" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AppState&lt;/code&gt; is intentionally thin. It coordinates three smaller subsystems instead of accumulating behavior until nobody trusts it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SyncState&lt;/code&gt; owns live sessions, broadcast channels, and the in-memory history cache.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RateLimiter&lt;/code&gt; owns admission control.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PushManager&lt;/code&gt; owns push-token state and delivery limits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I kept the hot path in memory on purpose. Sync services always have hot state. Hiding that behind the database does not make the system simpler. It just makes latency worse and makes it harder to know which copy of the state is in charge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Design Decisions
&lt;/h2&gt;

&lt;p&gt;I started with the trust boundary, not the stack.&lt;/p&gt;

&lt;p&gt;Every real clipboard payload is encrypted on the client before it crosses the network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NONCE_BYTES&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;xchacha20poly1305&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;toBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
    &lt;span class="na"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;toBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used XChaCha20-Poly1305 because I wanted a large nonce space and a boring API. That is the right kind of boring. The key stays on the device. Pairing moves it directly between devices through a QR or deep-link flow. The backend never gets a decryption path because I did not want one to exist.&lt;/p&gt;

&lt;p&gt;If you are newer to this language: &lt;code&gt;plaintext&lt;/code&gt; is the readable clipboard text, &lt;code&gt;ciphertext&lt;/code&gt; is the encrypted output, and the &lt;code&gt;nonce&lt;/code&gt; is a unique value used with the key for one encryption operation. The server stores and relays the ciphertext and nonce. It never gets the key.&lt;/p&gt;

&lt;p&gt;The second design decision was to stop pretending the protocol was “basically one message type.” The early version leaned too hard on strings and I knew it was wrong while writing it. A sync system already has enough moving parts. It does not need control flow and data flow squeezed into the same shape because it feels convenient in the first commit.&lt;/p&gt;

&lt;p&gt;A typed WebSocket protocol means different kinds of messages have different shapes. A handshake is not a clipboard payload. A presence event is not an error. That sounds obvious, but making it explicit removes a lot of guessing from the code.&lt;/p&gt;

&lt;p&gt;The third decision was about ownership. I rejected &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;_&amp;gt;&amp;gt;&lt;/code&gt; around the WebSocket sink almost immediately. In async Rust that pattern usually turns into hidden coordination cost. The sink has one owner. Every other outbound path goes through a bounded channel. The lifecycle becomes obvious. Backpressure becomes explicit. Shutdown stops depending on unwritten rules.&lt;/p&gt;

&lt;p&gt;The one-owner sink rule is simple: only one task writes to the socket. Everyone else sends messages to that task. That keeps the async write path boring, which is exactly what I want.&lt;/p&gt;

&lt;p&gt;The fourth decision was to keep Tauri narrow. I wanted it exactly where it belongs: at the native edge, where clipboard behavior, local secret storage, and mobile bridge behavior are platform concerns. Everything above that line stays in app code.&lt;/p&gt;

&lt;p&gt;One thing I would add next is protocol versioning. The current wire model is finally strict enough that version negotiation would be straightforward. Today the client and server still move in lockstep. I can live with that at this stage. I would not call it finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The protocol only got sane once I modeled it as an ADT
&lt;/h3&gt;

&lt;p&gt;Rust gives you tagged enums for a reason. If a protocol has multiple states with different meanings, model them that way.&lt;/p&gt;

&lt;p&gt;An ADT here is just a Rust enum used to model the possible message types. Instead of carrying one loose object around and asking “what kind of thing is this?”, the compiler can force the code to handle each variant directly.&lt;/p&gt;

&lt;p&gt;Echo now uses tagged enums on the wire:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq,&lt;/span&gt; &lt;span class="nd"&gt;Eq)]&lt;/span&gt;
&lt;span class="nd"&gt;#[serde(tag&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;rename_all&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"snake_case"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ClientMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Handshake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandshakeMessage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;Clipboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClipboardMessage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq,&lt;/span&gt; &lt;span class="nd"&gt;Eq)]&lt;/span&gt;
&lt;span class="nd"&gt;#[serde(tag&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;rename_all&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"snake_case"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ServerMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Clipboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClipboardFrame&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;Presence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PresenceMessage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProtocolError&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one decision removed a surprising amount of defensive code. The sync loop no longer has to guess meaning from special strings or overloaded fields. If a frame parses as &lt;code&gt;ClientMessage::Clipboard&lt;/code&gt;, the code already knows it is the encrypted payload path. If it parses as &lt;code&gt;Handshake&lt;/code&gt;, it is control flow. That is exactly the kind of separation I want the compiler enforcing.&lt;/p&gt;

&lt;p&gt;The frontend mirrors the same model in &lt;code&gt;desktop/src/protocol.ts&lt;/code&gt;, which matters more than people think. Client and server bugs get much rarer when both sides are forced to agree on the shape of the conversation instead of keeping two sets of assumptions in sync by luck.&lt;/p&gt;

&lt;h3&gt;
  
  
  Owning the WebSocket Sink in Async Rust
&lt;/h3&gt;

&lt;p&gt;This is the part of the backend I would defend hardest in review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;write_tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WRITER_CHANNEL_CAPACITY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;writer_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run_writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_rx&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I gave the sink to one task and never looked back.&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;tokio::spawn&lt;/code&gt; call also hides a Rust rule that is doing real architectural work for me: spawned futures must be &lt;code&gt;Send + 'static&lt;/code&gt;. In practice, that means the task owns what it needs, can move across worker threads safely, and does not borrow some stack frame that will disappear under it. That is exactly what I want for connection handling.&lt;/p&gt;

&lt;p&gt;That matters even if you are not deep into async Rust yet. A connection task should not depend on borrowed local state from a handler that may already be gone. Owning the data makes shutdown and cancellation much easier to reason about.&lt;/p&gt;

&lt;p&gt;Once the sink belongs to &lt;code&gt;run_writer&lt;/code&gt;, nobody else gets to send directly. History replay goes through the channel. Presence fan-out goes through the channel. Error frames go through the channel. Ping frames go through the channel. The write path is now one thing.&lt;/p&gt;

&lt;p&gt;The teardown path gets simpler too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;select!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;send_task&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;recv_task&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;ping_task&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;writer_task&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;send_task&lt;/span&gt;&lt;span class="nf"&gt;.abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;recv_task&lt;/span&gt;&lt;span class="nf"&gt;.abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;ping_task&lt;/span&gt;&lt;span class="nf"&gt;.abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;writer_task&lt;/span&gt;&lt;span class="nf"&gt;.abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That code is blunt on purpose. Sync systems do not usually fail in interesting ways. They fail in reconnect logic, stale tasks, and half-closed connections. I wanted the lifecycle to be clear enough that you could trace it in your head without inventing missing rules.&lt;/p&gt;

&lt;p&gt;I also make the server authoritative for device identity after the handshake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;clipboard_msg&lt;/span&gt;&lt;span class="py"&gt;.device_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;clipboard_msg&lt;/span&gt;&lt;span class="py"&gt;.device_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device_name&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line closes a subtle but ugly class of bugs. A client does not get to authenticate once and then improvise a different identity mid-session. The server binds identity to the connection and stamps every clipboard frame with the validated values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tauri is where I stop clipboard echo loops at the edge
&lt;/h3&gt;

&lt;p&gt;Cross-device clipboard sync has a boring failure mode: a remote write lands in the local clipboard, the local watcher sees it as a fresh local update, and the system starts rebroadcasting its own output.&lt;/p&gt;

&lt;p&gt;I kill that loop in native code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clipboard-remote-write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="nf"&gt;.payload&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ignored_hash_clone&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the polling loop ignores that exact value once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ignored_hash&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ignored&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ignored&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current_hash&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;last_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the few places where &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;_&amp;gt;&amp;gt;&lt;/code&gt; is not just acceptable, it is the right tool. The listener is synchronous. The critical section is tiny. There is no &lt;code&gt;.await&lt;/code&gt; near the lock. I do not need an async primitive. I need one shared piece of state and a fixed rule.&lt;/p&gt;

&lt;p&gt;I prefer this to debounce-style heuristics because timing lies. Clipboard APIs are noisy. Focus changes are noisy. “Probably the same event” is not an invariant. “Ignore the next clipboard value with this exact hash” is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Type System Enforces
&lt;/h2&gt;

&lt;p&gt;The best thing Rust bought me here was not speed. It was removing states I never wanted the system to represent.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DeviceId&lt;/code&gt;, &lt;code&gt;DeviceName&lt;/code&gt;, and &lt;code&gt;PushToken&lt;/code&gt; are validated newtypes. That pushes string validation to the edge. Once a handler or state method takes &lt;code&gt;&amp;amp;DeviceId&lt;/code&gt;, I already know the value is non-empty and within bounds. That check is not scattered through the core logic.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AuthUser&lt;/code&gt; pushes authentication into the handler signature. If a route accepts &lt;code&gt;AuthUser&lt;/code&gt;, Axum has already validated the bearer token and parsed &lt;code&gt;sub&lt;/code&gt; into a &lt;code&gt;Uuid&lt;/code&gt;. The unauthenticated state is not something the handler can forget to deal with because it never reaches that point.&lt;/p&gt;

&lt;p&gt;The rate limiter uses an enum instead of a boolean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;IntervalPolicy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Enforce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Ignore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a small example, but I like it because it is honest. &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; say nothing. &lt;code&gt;Enforce&lt;/code&gt; and &lt;code&gt;Ignore&lt;/code&gt; make the call say what it means.&lt;/p&gt;

&lt;p&gt;The protocol is another example. Presence, handshake, clipboard, and error frames are separate types because they are separate concepts. The compiler keeps those paths apart at zero runtime cost. That is exactly the kind of leverage I want in a system that spends its life moving state between machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance, Backpressure, and Bounded State
&lt;/h2&gt;

&lt;p&gt;Echo is built around limits because unbounded behavior is where small systems quietly become unreliable.&lt;/p&gt;

&lt;p&gt;History in memory is capped at 50 messages per user. Durable history is trimmed to the newest 100 rows. The writer channel is capped at 64 messages. The client-side offline queue is capped at 200. Broadcast capacity scales by device count and clamps between 50 and 500.&lt;/p&gt;

&lt;p&gt;Those are not random constants I sprinkled in at the end. They are the system deciding where backpressure should show up instead of pretending it can absorb infinite demand.&lt;/p&gt;

&lt;p&gt;The rate limiter is also laid out to avoid extra contention:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Clone,&lt;/span&gt; &lt;span class="nd"&gt;Default)]&lt;/span&gt;
&lt;span class="nd"&gt;#[repr(align(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt; &lt;span class="c1"&gt;// prevent false sharing between DashMap shard entries&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RateLimitState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;last_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;window_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;#[repr(align(64))]&lt;/code&gt; is there because this state is hot and concurrent. I am not interested in cargo-cult micro-optimizations, but this one is cheap and justified.&lt;/p&gt;

&lt;p&gt;I also accepted owned &lt;code&gt;String&lt;/code&gt; payloads and real clones on fan-out because clipboard messages are usually small and the simpler ownership model matters more than shaving that path too early. The bigger performance win is structural: the system appends to memory and fans out first, then writes to Postgres behind the hot path. Delivery does not wait for the database.&lt;/p&gt;

&lt;p&gt;The repo includes Criterion benches for broadcast and rate limiting. I care about them, but the larger performance story is simpler than any benchmark graph: bounded queues, no lock-across-await paths, one-owner sinks, and CPU-heavy work kept off the async scheduler when it belongs somewhere else.&lt;/p&gt;

&lt;p&gt;This is not a sketch. The repo has benches for the hot paths, the protocol is typed end to end, and the backend, frontend, and native builds all pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The most important decision in Echo was refusing to let the server join the trust model.&lt;/p&gt;

&lt;p&gt;Once that was fixed, the rest of the design got much clearer. The client owns secrets. The server owns coordination. Rust enforces the protocol shape and the ownership model. Tauri handles the native edge where web code has no business bluffing.&lt;/p&gt;

&lt;p&gt;That is the takeaway I would carry into any system like this. If your relay can read the data, it is not a relay anymore. It is the problem.&lt;/p&gt;

&lt;p&gt;The code is public, including the typed protocol, WebSocket handler, Tauri clipboard edge, and architecture diagram: &lt;a href="https://github.com/jephter-olamiposi/echo" rel="noopener noreferrer"&gt;github.com/jephter-olamiposi/echo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>tauri</category>
      <category>security</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
