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
Keep enum `ng_opmode` generated from the command list,
and also keep `ng_command_strings[]`, `ng_command_strings_esc[]`
and `ng_command_strings_short[]`.
And add the wrapper func name into the array, to be able
to appeal to it later, when processing any coming NG command.
Change-Id: I6aa725679d2405277f4497db46c3a8010d9ccd56
The `OP_...` enum is meant to only represent commands list.
Then make the list from the NG commands macro
the actual source of truth for the enum count.
This eliminates human mistakes, when introducing a new command,
because no more need to update enum manually.
Additionally:
- move the command list macro to the header
- existing extern declarations remain untouched
- `OP_OTHER` remains sentinel
- eventually replace the manual enum with macro based one
Change-Id: I33d928367fe0a254571cb0b7d4aafe4445b30b77
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
There is enough overlap between the two enums that it doesn't make sense
to carry along both. Unify.
Change-Id: I9de8fbdb8d78a2002d8c1b62cea57188c937d61d
Use ngbuf's free function to destroy the JSON parser instead of the
callback for the bencode_buffer object.
Change-Id: I7eccf7284f55b34ef1a4800017ea1a4519f42bbc