
Secure (with AEAD) version of USTP, called USTP-Secure (ustps://)
USTP means UDP Speedy Transmission Protocol.
USTP keeps the same authenticated transport model, but the project name is now simply USTP again.
By default, USTP uses AEAD for DATA.
It also supports an optional negotiated cleartext + HMAC mode for DATA, where payload bytes are visible on the wire but tampering is detected and rejected.
USTP now supports an optional congestion controller called USTP Congestion. It is still UDP-first, but it can optionally slow down and ramp back up when the path starts showing congestion signals.
Status: Beta
USTP is no longer just a proof of concept. It is currently in the Beta phase.
USTP can be used for many kinds of applications and transports.
This repository, however, is focused specifically on streaming over USTP.
02/08/2026: USTP-Secure is now back to USTP.
USTP-Secure was an old project name and no longer a good fit for the project.USTP name is now preferred again.UDP Speedy Transmission Protocol, without Secure in the title.2026-07-18: USTP/2 Beta was removed from the current tree.
Codex using GPT-5.4 (Low).--loss 33.Brazil -> Canada with about 140ms RTT.chacha20 = CHACHA20_POLY1305aes-256-gcm = AES_256_GCMaes-128-gcm = AES_128_GCMchacha20DATA protection mode is AEAD.DATA protection mode is cleartext + per-packet HMAC.HELLO, ACK, RETRANSMIT_REQUEST, CLOSE) stay plaintext on purpose.ACK:, NACK:, HELLO:, and CLOSE: are the plaintext control record prefixes.USS1 means UDP Speedy Secure, version 1.USC1 means UDP Speedy Clear, version 1.UPAK is the binary UPACK DATA frame marker.ACK: 10, NACK: 42, HELLO: ..., and CLOSE:.UPAK identifies binary DATA packets after decryption.USTP Congestion, negotiated during the handshake.seq and an application-facing stream_pos.seq is used for ACK, loss detection, retransmission, and RTT sampling.stream_pos tells the application where the payload belongs in the logical byte stream.seq is a 32-bit counter that starts at 1 for each fresh session.stream_pos is a 64-bit byte counter that starts at 0 for each fresh logical stream.HELLO carrying its X25519 public key, requested cipher, requested congestion-control mode (on or off), and requested DATA protection mode (cleartext on|off).session_idDATA protection modeDATA.IP:port that completed the challenge.session_id is still used as a session label, but it is not accepted from a different IP:port.HELLOUSTPS-CHALLENGE1 carrying token, session_id, selected cipher, negotiated congestion-control mode, negotiated DATA protection mode, and server public keyUSTPS-CHALLENGE-REPLY1USTPS-SESSION1IP:port can actually receive packets thereUSTP, but several on-wire ASCII identifiers still use the historical USTPS-* prefix.USTPS-KEX1USTPS-CHALLENGE1USTPS-CHALLENGE-REPLY1USTPS-SESSION1USTPS-RESUME1USTPS-RTT1USTP, but those historical wire identifiers were not renamed in the protocol bytes.UPACK DATA payload limit: 900 bytes.UPACK fixed header: 20 bytes.USS1 secure envelope overhead in AEAD mode:
4 bytes magic1 byte cipher id12 bytes AEAD nonce16 bytes AEAD tag953 bytes before UDP/IP headers.USC1 cleartext envelope overhead in cleartext mode:
4 bytes magic16 bytes HMAC tag940 bytes before UDP/IP headers.seq was already accepted.DATA encryption uses a fresh random 12-byte AEAD nonce per encrypted packet.seq.seq is for transport reliability.stream_pos is for logical application ordering.nonce is only for AEAD packet protection.DATA.USTP Congestion is optional.--congestion-control auto|on|off--congestion-control on|offautooffauto, the server follows what the client asked foron, congestion control is forced on even if the client asked for offoff, congestion control is forced off even if the client asked for onRTT, retransmission timeout events (), and explicit retransmit requests ()IP:port changes, the current session is expected to end and the client should reconnect cleanly.session_idIP:port, bind the session to that endpoint, and reconnect if the endpoint changes.ACK is serialized like ACK: 10 MAC:<tag> or batched like ACK: 10 11 12 ... MAC:<tag>.RETRANSMIT_REQUEST is serialized like NACK: 42 MAC:<tag> or batched like NACK: 42 43 44 ... MAC:<tag>.HELLO is serialized like HELLO: <base64-payload>.CLOSE is serialized like CLOSE:.DATA uses the binary UPACK frame format instead of ASCII to avoid bloating media payload packets.USS1... datagrams that decrypt to DATA frames.DATA packet is ACKed individually.RETRANSMIT_REQUEST only for the missing seq.RTO is not fixed-only: it is adapted from measured RTT samples of non-retransmitted packets.ACK: the receiver acknowledged one or more seq values, so the sender can retire them from the retransmission buffer.NACK: the receiver detected a missing seq and explicitly requested retransmission of that missing packet only.GAP: the client received a packet whose stream_pos is ahead of the next ordered output position, so there is currently a hole in the logical byte stream.RECOVERY: a late packet arrived with stream_pos below the current frontier, meaning an earlier gap is being repaired or was repaired after newer data had already been seen.RESYNC: the client anchored ordered output to a new stream_pos after a clean stream-state reset.RTO: retransmission timeout. The sender did not see ACK progress in time, so it queued a packet for retry even without an explicit NACK.no data for 10s: the client did not receive stream data for long enough and exits so a new clean session can be started.stream_pos when it needs a byte stream or player-friendly output.--udp-unordered-live is documented separately as dangerous.stream_pos to rebuild ordered output when your application needs a byte stream.stream_pos.stream_pos and release data only when the required positions are available.seq; use seq only for transport reliability logic.stream_pos metadata when the application wants to reconstruct ordered output. If USTP Congestion is enabled, the sender may slow or speed up, but that does not change the transport model.python3 server.py \
--peer-port 0 \
--bind-ip 0.0.0.0 \
--bind-port 40001 \
--video "<HLS_URL_OR_LOCAL_FILE>" \
--stream-container mpegts \
--cipher chacha20 \
--congestion-control auto \
--cleartext auto
The default stream container is mpegts because it is the most reliable option with VLC in the current TCP-local playback path.
You can change the FFmpeg muxer/container with --stream-container.
Examples:
--stream-container mpegts (default, classic MPEG-TS compatibility)--stream-container flv (experimental here; VLC may misdetect it as audio-only in this pipeline)--stream-container nut (low overhead FFmpeg-native streaming container, but VLC may not open it)--stream-container matroska (MKV/Matroska)If you want custom ffmpeg encoding/transcoding parameters instead of the default copy mode, use --video-parameters.
Example:
python3 server.py \
--peer-port 0 \
--bind-ip 0.0.0.0 \
--bind-port 40001 \
--video "<HLS_URL_OR_LOCAL_FILE>" \
--stream-container mpegts \
--video-parameters "-c:v libx264 -preset veryfast -b:v 2500k -c:a aac -b:a 128k" \
--cipher chacha20 \
--cleartext off
Behavior:
--video-parameters: uses -c copy--stream-container mpegts and no --video-parameters: also adds -mpegts_flags +resend_headers--video-parameters: uses exactly what you passed instead of the default copy settings-f <stream-container>--loss--loss simulates outbound packet loss on the server side for testing recovery behavior.0 to 100python3 server.py \
--peer-port 0 \
--bind-ip 0.0.0.0 \
--bind-port 40001 \
--video "<HLS_URL_OR_LOCAL_FILE>" \
--cipher chacha20 \
--loss 40
--loss 0 means no simulated loss.--loss 40 means the server randomly drops about 40% of its outbound packets before they leave the process.--loss at 0.python3 client.py \
--peer-ip <SERVER_IP_OR_DOMAIN> \
--peer-port 40001 \
--bind-ip 0.0.0.0 \
--bind-port 0 \
--output-mode tcp \
--tcp-host 127.0.0.1 \
--tcp-port 1238 \
--cipher chacha20 \
--congestion-control off \
--cleartext off
Examples:
--cleartext off--cleartext onNotes:
1500ms.~/.ustps_known_hosts.json.--regen-key to allow replacing the stored TOFU key after interactive confirmation.<peer-ip-or-domain>:<peer-port>, so a different server at a different address/port is treated as a different host identity.--udp-unordered-live--udp-unordered-live is dangerous and generally not recommended for normal media players.TCP output or ordered UDP output with a reorder buffer.VLC:
tcp://127.0.0.1:1238
DATA protectionUSTP Internet-Draft: https://datatracker.ietf.org/doc/draft-x1co-ustps/USSH: a shell/remote terminal protocol implemented fully from scratch on top of USTP:
https://github.com/x1colegal/USSHACK and NACK/RETRANSMIT_REQUEST remain plaintext, but are authenticated with a per-session HMAC tag.DATA packets use a binary frame format named UPACK (UPAK on the wire).IP:port.--cipher is set on the server, the server uses that exact cipher.--cipher is omitted or set to auto, the server uses the cipher requested by the client.DATA protection mode is negotiated separately from cipher choice:
--cleartext auto|on|off--cleartext on|offautooffauto, the server follows the client request.on, the server forces cleartext + HMAC.off, the server forces AEAD.~/.ustps_host_key by default so TOFU remains stable across reconnects and restarts.--regen-key on the server only when you intentionally want to rotate that host key.USS1 is the outer secure AEAD envelope format.USC1 is the outer cleartext+HMAC DATA envelope format.USS1... for AEAD-protected DATAUSC1... for cleartext+HMAC DATA981 bytes on the wire in AEAD mode and about 968 bytes in cleartext mode.1001 bytes on the wire in AEAD mode and about 988 bytes in cleartext mode.RTONACKRTT inflates, RTO starts happening, or loss/retransmit pressure rises, it backs offUSTP Congestion controls rate pressure, not reliability semantics.UPAK...USC1... datagrams carrying visible UPACK bytes and an HMAC tag.MAC:<tag> value is computed from the session key and is stripped after verification before the packet reaches the transport state machine.stream state reset: the client cleared local reorder/gap state after a clean new stream/session boundary.