Patched RustDesk server 1.1.16 (hbbs/hbbr, AGPL-3.0)

Upstream: https://github.com/rustdesk/rustdesk-server
Pinned server commit: 73523b31cfd25d77dee862e6fc9f5e1fb5e485ef (1.1.16)
Pinned hbb_common: 83419b6549636ee39dacef7776c473f5802e08d6
Client protocol reference: rustdesk/rustdesk 1.4.9 src/common.rs, src/client.rs.

Build on a Linux Docker host with git and Python 3 (install.py does this):
  sh ./build.sh /absolute/path/server-build
Images: rustdesk-hbbs:1.1.16-secure.2 and rustdesk-hbbr:1.1.16-secure.1.
build.sh also writes hbbs-source.tar.gz, the full corresponding source for the
download page. One compiler job and a memory limit keep a small VPS usable.

Signalling handshake (rendezvous-handshake.patch, secure_codec.rs)
------------------------------------------------------------------
RustDesk clients with a key and an account token call secure_tcp before sending
their rendezvous request. Upstream OSS hbbs 1.1.16 waits for the request instead,
so both sides wait and the client fails after its handshake deadline.

The patch adds the existing RustDesk transport: a fresh Curve25519 public key
signed with the existing server Ed25519 key, the client's boxed session key,
then secretbox encryption with the upstream framing and sequence counters.
It uses hbb_common::tcp::Encrypt rather than introducing different crypto.
Unsigned clients retain the plain signalling protocol and skip the offer, as
supported by next_rendezvous_msg in the official client. Peer encryption and
permanent-password authentication remain the stock RustDesk implementations.
The API token is not an authorization gate for connecting to peers, matching
OSS behavior; the remote peer still requires its own password/permission.

Unit tests cover stock plaintext framing, boxed session-key agreement,
encrypted messages in both directions, invalid exchanges, wrong keys, replay
rejection and oversized frames.

Persistent WSS support (websocket-peers.patch, websocket_peers.rs)
-----------------------------------------------------------------
Adds peer registration on the WebSocket listener. UDP and WSS share the
existing UUID/key checks and database. A registration mutex retains UDP's
serialization for simultaneous WS peers. Validated WS sessions are indexed by
the proxy connection's IP AND source port. Signals target this channel, not
UDP. Heartbeats use the official empty binary message and keep_alive=30
seconds; disconnects expire only their own current peer session. A superseded
session cannot reclaim or expire a newer connection. OnlineRequest works over
the same WS endpoint. Native and WSS peers interoperate.

Use a reverse proxy with a valid TLS certificate, /ws/id -> 21118 and
/ws/relay -> 21119 (see ../nginx.conf). Publish both backend ports on loopback
only. Overwrite X-Real-IP and X-Forwarded-For in the proxy. TLS terminates at
nginx; end-to-end peer encryption remains in the official client.

Clients >= 1.4 need allow-websocket=Y and the API server https://<host>; the
installers set both. The client derives the WSS paths from the ID/relay
address. There is no automatic native-to-WebSocket fallback; WSS uses relaying,
so a firewall must permit WebSocket upgrades on TCP 443. The stock client
1.5.0 ignores its configured proxy in WebSocket mode, so a mandatory corporate
forward proxy is unsupported (rustdesk/rustdesk discussion 12723, hbb_common
commit 229b904508364c8997aad0fb5af57effac859f60, src/websocket.rs).

Atomic relay pairing (relay-pair.patch)
---------------------------------------
Fixes a race in OSS hbbr: removing a waiting socket and inserting the first
socket used separate locks. Simultaneous requests both found an empty slot and
one overwrote the other. Both decisions now use one lock, released before
relaying or waiting.

Protocol probes (QA only)
-------------------------
Dockerfile.probe builds a separate QA image. probe.py validates the native wire
protocol (signed server key, encrypted request/response pairs, plaintext
compatibility, rejection of malformed key exchange) using only an absent test
ID. websocket_probe.py checks registration, conflicting UUIDs, signals, relay
data in both directions, heartbeats, reconnects, malformed requests and 32
simultaneous relay pairs. Run it only against a disposable server, never
against a production database:
  docker build -t rustdesk-hbbs-probe -f Dockerfile.probe .
  docker run --rm --network host --entrypoint python rustdesk-hbbs-probe \
    /websocket_probe.py wss://TEST_HOST/ws/id wss://TEST_HOST/ws/relay \
    TEST_TCP_HOST TEST_TCP_PORT TEST_PUBLIC_KEY

Building from the source archive
--------------------------------
The archive contains the patched upstream tree and this directory, without
.git, runtime databases, credentials or executables. A revision marker
(UPSTREAM_COMMIT) replaces the Git metadata:
  mkdir -p /tmp/source-build
  tar -xzf hbbs-source.tar.gz -C /tmp/source-build
  sh /tmp/source-build/secure-hbbs/build.sh /tmp/source-build
build.sh creates an empty SQLite compile-time schema from build-schema.sql and
passes DATABASE_URL explicitly to sqlx; no runtime peer data are needed.

Rollback to the stock server: set the hbbs image in compose.yml to
rustdesk/rustdesk-server@sha256:8ecdab65deb7c84652a626380e31d11a8f1fbafd97916d57f95c20628f943c00
and run docker compose up -d --no-deps hbbs. Existing data and keys are reused.
Clients with WebSocket enabled must first be switched to allow-websocket=N.
