Add an opt-in rollback message to the NG protocol. It lets a signalling
application undo an SDP offer that rtpengine has applied but the remote
endpoint subsequently rejects, without deleting the established call.
A client enables checkpointing with track-state on an offer. rtpengine
snapshots the affected dialogue before applying it; an answer commits the
exchange and discards the snapshot, and rollback restores and consumes it.
A dialogue holds at most one outstanding checkpoint, so offers arriving
before an exchange completes keep the existing snapshot and a rollback
returns to the last completed offer/answer.
A checkpoint holds a snapshot in the call record format the Redis encoder
already produces, so there is one definition of what a call's state looks
like and no second encoder to keep in step. Decoding a snapshot needs the
field readers the Redis restore path already had, so those are shared
rather than duplicated; json_restore_call() reads the same fields, in the
same order, with the same strict-return behaviour.
Applying a snapshot copies negotiated state back onto the live objects,
matched by unique id. The objects are never recreated, so local sockets
and ports survive. State a rejected offer introduced is removed as well as
overwritten: a field the encoder writes only when set is absent from a
snapshot taken before it existed, and that absence clears it.
ICE and DTLS reconverge rather than being rewound, since applying the
offer has already reset the agent and shut down the DTLS association.
Restoring the accepted credentials and candidates lets connectivity checks
rebuild ICE state, and restoring the fingerprint, TLS id and setup permits
a fresh handshake.
Outstanding checkpoints are stored in the Redis call record so they
survive takeover. Checkpoint data is auxiliary: one that cannot be read is
discarded in full while the call itself is restored without rollback
capability.
The preload shims no longer inherit the sanitizer options from CFLAGS.
They are loaded by the harness into the test scripts' own interpreter,
where a sanitizer runtime crashed the daemon tests on aarch64 during
library initialisation. Everything else in CFLAGS, the Debian hardening
flags included, still applies to them.
The Redis tests no longer assume the record length is four digits, and
read the record across as many reads as it takes.
Squashed:
commit ad49459152
Author: Daniel Donoghue <oneofsomany@hotmail.com>
Date: Fri Aug 28 13:33:17 2026 +0200
MT#55283 address review comments
Snapshots are now bencode, GLib macros are used throughout, and keys and
offsets are built with the existing helpers rather than by hand.
New tests cover the snapshot format and the state that exists only inside
a snapshot.
commit ff47944ad5
Author: Daniel Donoghue <oneofsomany@hotmail.com>
Date: Fri Aug 28 15:24:56 2026 +0200
MT#55283 scope snapshots to the dialogue
A snapshot now holds only the two monologues being checkpointed, and only
the entries the decoder reads: the socket and endpoint map pools, sinks,
subscriptions and aliases are left out.
The from-tag check in the rollback handler is dropped, since
call_get_monologue() is keyed on the tag.
commit 3bb19f2804
Author: Daniel Donoghue <oneofsomany@hotmail.com>
Date: Mon Aug 31 08:41:08 2026 +0200
MT#55283 give each monologue its own checkpoint
The checkpoint moves from a list on the call to a pointer on the monologue,
and its snapshot covers only that monologue. Checkpoints are stored in the
call record as checkpoint-<monologue id>; num_checkpoints and the offerer
and answerer ids are gone.
A monologue is shared between the branches of a forked call, so rolling one
branch back no longer reinstates what rolling another back had undone.
The call-level json dict is no longer written into snapshots, since nothing
reads it back.
commit 0c986a81e2
Author: Daniel Donoghue <oneofsomany@hotmail.com>
Date: Mon Aug 31 10:01:54 2026 +0200
MT#55283 do not report a rollback that did not happen
call_merge() renumbers every unique id, and a snapshot is keyed on them, so
a checkpoint taken before a merge no longer describes anything. It is
dropped with the ids it refers to, and rollback reports none outstanding
rather than a success that restored nothing.
Both sides of a dialogue are checkpointed together. A monologue is shared
between the branches of a forked call, so one side could already hold a
checkpoint while the other had never been tracked, leaving a rollback to
restore half a dialogue and still report success.
Each fix has a test that fails without it.
commit dfe7a72aff
Author: Daniel Donoghue <oneofsomany@hotmail.com>
Date: Mon Aug 31 10:41:50 2026 +0200
MT#55283 document that merging a call drops its checkpoints
Merging renumbers the state a snapshot refers to, so an outstanding
checkpoint is discarded and a later rollback reports none.
Closes#2159
Change-Id: Ie773e2cd4a39fc1c8f39fafe959784e269c0c80e
Expression like macroses should usually be
wrapped in parentheses.
Then they behave predictably, when for example
embedded into larger expressions,
and also so future edits do not accidentally introduce precedence bugs.
Change-Id: I4ac485c2e1ed58507f4efeec69de63a6080523c2
With timestamps handling, which want to operate on signed integers.
Return the signed max value given from the calls of `atomic64_get_na()`.
Raw-cast the output of `atomic64_get_na()` to the signed integer as well.
Change-Id: Icc6878c9b08545f456ad5eb267ed6a3333c11ab7
The `dialog_unconfirm()` func simply does similar
work as `__monologue_unconfirm()` but works instead
on subscriptions additionally.
So just deprecate it and then `__monologue_unconfirm()`
just takes the work on subscribers/subscriptions as well.
Change-Id: I047fd86b8eba700d0347b21137a2aa40fab01643
This is needed to correctly print media labels for SIPREC in case the
input SDPs already had media labels.
Change-Id: I021f44ba28a3648ceb62db12425ddf545de42a96
For a repeated subscribe-request on an existing monologue, make sure
that existing media sections are re-used for the same subscriptions as
they were previously, and only create new ones in an append-only fashion
for media subs which didn't previously exist.
Change-Id: I410c6af17eb31a5c5c43c78a7f82ba867f8480b7
New NG protocol commands that mix audio from a source participant
into a destination participant's stream without a full offer/answer
exchange.
- inject start: creates one-way subscription with audio_player mixing
- inject stop: removes injection and restores original codec routing
- Supports cross-call injection via source-call-id (merges calls internally)
- inject attribute persisted through Redis for HA failover
- Audio-only; matches media by media-id, index, then type fallback
Closes#2082
Change-Id: Ib88113b021a4b76bf98892eb1af60c0b36c0d6d3
Use a pointer to the arena plus a comparison test to determine whether
to ref or to dup the string, instead of a function pointer. The function
pointer alone wasn't enough as there can be multiple arenas.
Change-Id: Icd317e524fc2e5842f90d172ef7089d2cb0a5a28
Store the current (last) used endpoint map in the media object. This can
then be directly used when port latching is requested.
Remove and close sockets from endpoint map if fewer ports are needed.
Change-Id: I58aab82f82ecc3b0a7fc7a781394c8d77e715ecf