Skip to content

docs/JAMULUS_PROTOCOL.md: message reference, directory flows, small fixes - #3794

Open
mcfnord wants to merge 2 commits into
jamulussoftware:mainfrom
mcfnord:enhance-protocol-doc
Open

docs/JAMULUS_PROTOCOL.md: message reference, directory flows, small fixes#3794
mcfnord wants to merge 2 commits into
jamulussoftware:mainfrom
mcfnord:enhance-protocol-doc

Conversation

@mcfnord

@mcfnord mcfnord commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

One file. Fills the main gaps in the protocol doc, verified against src/protocol.cpp / src/socket.cpp / src/serverlist.cpp on current main:

  • How a receiver tells protocol packets from audio packets (they share one port; parse-or-audio in CSocket::ProcessPacket()), the CRC parameters, and how acknowledge/retransmit actually works — previously the doc said only "must be acknowledged".
  • A complete message-ID reference table for both message classes, with the legacy IDs called out. This is the piece a Wireshark user or protocol reader currently has to reconstruct from protocol.h.
  • A new section on directories: registration and refresh/timeout intervals, server-list responses (why both reduced and full lists are sent), and the CLM_SEND_EMPTY_MESSAGE/CLM_EMPTY_MESSAGE NAT hole-punch flow — previously undocumented here despite being a third of the connectionless messages.
  • Fixes message names that drifted from protocol.h (REQ_CHANNEL_INFOS, CHANNEL_INFOS, REQ_CONN_CLIENTS_LIST).

CHANGELOG: SKIP

🤖 Generated with Claude Code

@ann0see
ann0see self-requested a review July 17, 2026 22:08
Comment thread docs/JAMULUS_PROTOCOL.md Outdated
Comment thread docs/JAMULUS_PROTOCOL.md Outdated
Comment thread docs/JAMULUS_PROTOCOL.md Outdated
@ann0see ann0see added the AI AI generated or potentially AI generated label Jul 21, 2026
Comment thread docs/JAMULUS_PROTOCOL.md
Comment thread docs/JAMULUS_PROTOCOL.md
@ann0see
ann0see requested a review from softins July 23, 2026 09:17

@softins softins left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. Just a few comments and suggested changes.

Comment thread docs/JAMULUS_PROTOCOL.md
Comment thread docs/JAMULUS_PROTOCOL.md
Comment thread docs/JAMULUS_PROTOCOL.md Outdated
Comment thread docs/JAMULUS_PROTOCOL.md Outdated
Comment thread docs/JAMULUS_PROTOCOL.md Outdated
Comment thread docs/JAMULUS_PROTOCOL.md Outdated
@ann0see

ann0see commented Jul 26, 2026

Copy link
Copy Markdown
Member

Please squash the commits here.

…ixes

- Explain how receivers classify datagrams (protocol frame vs audio)
  and specify the CRC and the acknowledge/retransmit mechanism
- Add a complete message-ID reference for connection-based and
  connectionless messages
- Add a section on directory registration, server lists and NAT hole
  punching (registration refresh/timeout intervals, CLM message flows)
- Fix message names to match protocol.h (REQ_CHANNEL_INFOS,
  CHANNEL_INFOS, REQ_CONN_CLIENTS_LIST)
- Apply style guide capitalisation (Client, Server, Directory, Channel,
  Jitter Buffer) per https://jamulus.io/contribute/Style-and-Tone
- Move SPECIAL_SPLIT_MESSAGE (2001) out of the connection-based table
  into its own paragraph explaining it's a transport container, not a
  message type
- Document the raw (uncompressed PCM) audio option alongside
  OPUS/OPUS64: how it's advertised (FS_RAW_AUDIO in
  CLM_SERVER_FEATURES), disabled (--noraw), and how the Server tells
  it apart from OPUS (packet-size comparison, no distinct message type)

Co-Authored-By: Peter L Jones <pljones@users.noreply.github.com>
Co-Authored-By: Tony Mountifield <tony@mountifield.org>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mcfnord
mcfnord force-pushed the enhance-protocol-doc branch from 5d057a5 to 2386d21 Compare July 27, 2026 06:41
Comment thread docs/JAMULUS_PROTOCOL.md Outdated

Jamulus uses connectionless UDP packets to communicate between the client and server, and additionally for directory server registration. The `src/protocol.cpp` file contains much of the details of the packets themselves, whereas this document is intended to form a higher-level view of the protocol interactions.
Some of the messages need to be acknowledged, some do not. If a message ID is less than 1000, the message must be acknowledged in under `SEND_MESS_TIMEOUT_MS` ms.
Jamulus uses connectionless UDP packets to communicate between the Client and Server, and additionally for registration with a Directory. The `src/protocol.cpp` file contains much of the details of the packets themselves, whereas this document is intended to form a higher-level view of the protocol interactions.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Nit: "connectionless UDP packets" is a horrible phrase... UDP is a connectionless IP protocol, so "connectionless" is redundant. It's also a packet-based protocol (inherently), so "packets" is redundant. But there you are...)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MY LLM WROTE:

Agreed on both counts — dropped to just "Jamulus uses UDP to communicate...". Fixed in be4b4e7.

Comment thread docs/JAMULUS_PROTOCOL.md Outdated
Jamulus uses connectionless UDP packets to communicate between the client and server, and additionally for directory server registration. The `src/protocol.cpp` file contains much of the details of the packets themselves, whereas this document is intended to form a higher-level view of the protocol interactions.
Some of the messages need to be acknowledged, some do not. If a message ID is less than 1000, the message must be acknowledged in under `SEND_MESS_TIMEOUT_MS` ms.
Jamulus uses connectionless UDP packets to communicate between the Client and Server, and additionally for registration with a Directory. The `src/protocol.cpp` file contains much of the details of the packets themselves, whereas this document is intended to form a higher-level view of the protocol interactions.
Messages with an ID below 1000 are connection-based: each one is acknowledged by an `ACKN (1)` message carrying the same sequence counter, and the sender retransmits it every `SEND_MESS_TIMEOUT_MS` ms until the acknowledgement arrives. Messages with an ID from 1000 to 1999 (`CLM_*`) are connectionless: they work without an established audio connection and are never acknowledged.

@pljones pljones Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then it has the nerve to go from saying "Jamulus uses connectionless..." to "connection-based"... and "... an established audio connection..." without any explanation. I think a little scene-setting is needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MY LLM WROTE:

Fair — it was using "connection-based" as a classification before establishing what a connection is here. Added a short paragraph between the two: UDP gives no delivery guarantee and no connection, so Jamulus layers its own session semantics on top; a Client and Server count as connected once the Client is sending valid audio packets and the Server has assigned it a channel. The two message-class names now have something to refer to, and I say explicitly they are named relative to that session rather than to anything at the transport level.

Comment thread docs/JAMULUS_PROTOCOL.md Outdated
Jamulus uses connectionless UDP packets to communicate between the client and server, and additionally for directory server registration. The `src/protocol.cpp` file contains much of the details of the packets themselves, whereas this document is intended to form a higher-level view of the protocol interactions.
Some of the messages need to be acknowledged, some do not. If a message ID is less than 1000, the message must be acknowledged in under `SEND_MESS_TIMEOUT_MS` ms.
Jamulus uses connectionless UDP packets to communicate between the Client and Server, and additionally for registration with a Directory. The `src/protocol.cpp` file contains much of the details of the packets themselves, whereas this document is intended to form a higher-level view of the protocol interactions.
Messages with an ID below 1000 are connection-based: each one is acknowledged by an `ACKN (1)` message carrying the same sequence counter, and the sender retransmits it every `SEND_MESS_TIMEOUT_MS` ms until the acknowledgement arrives. Messages with an ID from 1000 to 1999 (`CLM_*`) are connectionless: they work without an established audio connection and are never acknowledged.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the sender retransmits it every SEND_MESS_TIMEOUT_MS ms until the acknowledgement arrives"

Forever? I know it's not a full spec but given it's got this partly stated, it might as well put the upper bound in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MY LLM WROTE:

I went to look up the bound and there is not one — so the honest answer to "Forever?" is very nearly yes, and the sentence was hiding that rather than just being incomplete.

OnTimerSendMess() (protocol.h:332) is just SendMessage(), and SendMessage() restarts TimerSendMess unconditionally whenever the queue is non-empty (protocol.cpp:552-566). No retry counter anywhere. Retransmission stops on exactly two events: a matching ACKN pops the queue (protocol.cpp:711-716), or CProtocol::Reset() clears it (protocol.cpp:516) — called from channel.cpp at 167, 394 and 678, i.e. channel disabled, protocol disabled while sending, and channel time-out/disconnect.

So the real bound is the channel lifetime, not a retry cap. Documented it that way rather than inventing a limit, and added the concrete 400 ms. If you would rather this were a bounded retry, that is a code change and I am happy to raise it separately.

…, set the scene for connection-based vs connectionless, state the retransmit bound

- "connectionless UDP packets" was doubly redundant: UDP is inherently
  both connectionless and packet-based.
- The text used "connection-based" and "established audio connection"
  right after calling the transport connectionless, with no explanation.
  Added a sentence defining what counts as connected in Jamulus terms,
  so the two message-class names have something to refer to.
- The retransmit sentence stopped at "until the acknowledgement arrives".
  There is in fact no retry counter: OnTimerSendMess() re-sends and
  SendMessage() restarts the timer while the queue is non-empty, so the
  loop is bounded only by an ACKN popping the queue or CProtocol::Reset()
  clearing it (disconnect, time-out, protocol disabled). Stated that,
  plus the concrete 400 ms value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread docs/JAMULUS_PROTOCOL.md

UDP offers no delivery guarantee and no notion of a connection, so Jamulus layers its own session and reliability semantics on top of it. A Client and Server count as *connected* once the Client is sending valid audio packets and the Server has assigned it a channel. The two message classes below are named relative to that session, not to anything at the transport level.

Messages with an ID below 1000 are connection-based: they apply to an established session, and each is acknowledged by an `ACKN (1)` message carrying the same sequence counter. Until that acknowledgement arrives, the sender retransmits the message every `SEND_MESS_TIMEOUT_MS` (400) ms. The protocol layer sets no retry limit: retransmission ends when the message is acknowledged, or when the channel clears the send queue via `CProtocol::Reset()` — on disconnect, on channel time-out, or when the protocol is disabled.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mm, leaves me asking how big an exploit surface that is -- I don't think it's that large as there aren't very many protocol messages that a malicious client could send to a server after initiating a connection that require a response that could have the ACKN unsent. But a spoofable sender address would open this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI AI generated or potentially AI generated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants