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
Previously, once rtpengine detected that the remote endpoint of a session is a local endpoint, the
loop check would be permanently enabled for that session. A renegotiation that changes the remote
endpoint to be non-local, did not cause the check to be turned off again. This patch addresses that.
Closes#2152
Change-Id: I880428b09f5d1e5b4641c9de4563010e31d7a790
Per RFC 4867 section 8.1, absent fmtp parameters imply defaults (bandwidth-efficient mode, no CRC,
no robust-sorting, no interleaving). Previously, codec matching was rejected when either side had no
``a=fmtp`` attribute for the AMR payload type, causing AMR to be dropped as a "stray answer codec".
This patch changes this behavior to instead "parse" fmtp even when absent so that the format struct
is populated with zero-initialized defaults and fmtp_parsed is set.
Closes#2141
Change-Id: I1371cb1fdb9354746a4019477845d777e619a2f6
With the more modern codec handling, there shouldn't be any more need to
unconditionally remove unsupported codecs if some codecs are requested
for transcoding.
Change-Id: Ica931de24ad6042f82e06d2efa69b93b4a7d8dd0
Previously, the ports used for a monologue subscription would leak upon re-subscribe.
``__assign_stream_fds`` cleared the stream fd queue without decrementing the ref count. This
prevented ``monologue_destroy`` from releasing the ports on unsubscribe.
Closes#2125
Change-Id: Ib86290d6a35e2388e34098f2847f1e34b3db2c5d
Allow invalid payload types for rejected RTP streams.
Allow empty codec store for zero-port streams.
Has some side effects on tests.
Closes#2122
Change-Id: I28c63a3e5b7309084e7899a19ff4550e8f56aac5
With the newly introduced `ng_command_find()` lookup func.
Add the local `ng_command_def` and maintain via it.
Check whether:
- the handler has been defined
- the handler gets properly selected
- the command is defined (known)
Accordingly update the opmode of the command context.
By default define as `OP_OTHER` always.
For the case when no handler has been defined, introduce
the error reply "No handler found".
Accordingly trigger selected handler after
the command was found.
The main payoff here is that the command processing
flow becomes quite transparent and easy to maintain:
- parse command string
- lookup command definition
- set opmode from table
- call uniform handler
- shared error/success reply handling for all commands
Deprecate strhash based `__csh_lookup()`/`CSH_LOOKUP()`.
Accordingly adap the daemons's and tests Makefile,
remove the strhash objects with the normal control_ng one.
Additionally:
introduce the special `resultstr = "pong"` for the
ping command case, because all other commands have "ok".
Change-Id: Ifff7d7f61ae4d25fd220460f4485d794c9a7cbd5
Resolve file name conflicts between different components
Ensure shared log levels are identical across components
Encapsulates lib/*
Change-Id: If848d6fa0e4cd8eac88ce9e63ee77fc5a7a481fd