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
Introduce a common NG commands table `ng_command_defs`
for all type of things: OP enum, name, escaped name,
short name and most important the handler.
This table operates on and returns a dedicated struct `ng_command_def`,
which has all the information required on the command (like
all type of things mentioned above) and may have either
single-parameter or double-parameter signature (for cases with
the `addr` requiring commands such as the offer command).
Also add unified signatures for X and XA handlers,
they will be used later accordingly. Most of those use single-parameter,
and those exceptional use the second one.
Also add dummy plug-functions for ping and block/unblock silence media.
The ping one doesn't have any particular handler, but because a new
structure of NG commands wants to see a real declaration of the func
handler, just give it. For the block/unblock silence media, there aren't
even handling of commands in the control NG, they are just dummy OPs,
so fulfill the actual NG commands structure, and give them dummy funcs.
Change-Id: I2064ddca1595079959a6c6843119556a8b6bf5d5
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
Simply take the xmlrpc-callback address as string and don't try to parse
it out. Store it in the call object as string as well.
Obsolete `created_from_addr`. The string form `created_from` is all we
need.
Change `created_from` to `str` as well.
Change-Id: Ib67b57b1d2d474d7b033f56ef8be59f71e44641b
The GLib slice allocator has been obsoleted. Having a mixture of two
allocators can lead to hidden issues on systems that use different
implementations. Take the leap and replace everything with g_new/g_free.
Change-Id: I025c8383ef47b2c2472573360407fd6a6ca339b3
This mode is only supposed to be used by cases when:
- in-dialog offerer doesn't have own MoH capabilities; and
- still want the recipient to hear the MoH music; hence
- an offerer checks whether a recipient is capable of MoH,
and launches a player based on given capabilities;
- the rest functions the same as with usual MoH hold;
Other information:
- `mode=reflect` is only to be used within dialog,
so, it's not meant for a session origination,
and hence stands alone from other MoH option flags
that actually give a metadata (e.g. blob data)
- `mode=reflect` hence contradicts with `mode=sendrecv`
which in its turn serves another purpose
Usage:
- `moh => { mode => 'reflect' }`
Change-Id: I8d0d55f2711c6b47bfca17691582c1ffe66eae3d
Protects against double MoH played,
e.g. when inadvertently two rtpengine instances try to trigger MoH.
This gives a clue to the current rtpengine instance,
that another one already started MoH for this call.
By default is set to false.
Change-Id: I7cc36a177b0ce1bdb64ff8b42bf31f13fb1e4c91
Switch from specialised handling of config sections (used to load
signalling templates) to a more general approach using a callback
mechanism. This allows us to add more information to the config file
while keeping the details of the underlying GKeyFile hidden. Use a typed
hash table for type safety.
Change-Id: I71ddfda0202b47df363bcc5acf1725078774f8f1
Change old code to directly return `str` objects instead of pointers to
allocated ones. Largely a no-op change but makes some code easier and
eliminates some old kludges.
Change-Id: I2be19dd24bb7ff046d86cc32a3af235283e65dd0
Make `moh-max-duration` available for both
music-on-hold functionality as well as
for the media player.
For that to work, do the following:
- keep `moh-max-duration` config option only for MoH,
if not set (so 0) by default is 1800000ms (half an hour)
- for the play media functionality introduce flag option
`repeat-duration`, by default is disabled
Policy changes:
- duration counter can be used in common with repeats
counter, but then takes a precedence over it.
Hence if first a duration is underflown, then EOF triggered.
Otherwise if the duration counter is still positive, but
repeats are negative, then do EOF based on repeats.
- the repeats counter will always count down during each
iteration, even when used together with the duration counter
For MoH to survive, the repeats counter is simple set to 999
to let the duration counter always win over repeats one
- MoH cannot take duration disabled, since otherwise
would make no sense for it. Hence always takes internally
defined value 1800000ms (half an hour) if not defined
by the configuration option
Backwards compatibility:
- is kept in regards of repeats counter
- is kept in regards of the play media functionality
Change-Id: I48ff3c17c9bed31f80c3106b275b703a9ccb4b26
Introduced Music on Hold functionality:
- available only for the offer/answer model,
no other scenarios (publish, subscriber etc.)
are covered with it
- it gets advertised always at the beginning of
the call (original offer/answer exchange)
- can be added for both sides: offerer/answerer
- the one who advertises its MoH capabilities
with its SDP offer or answer, can later trigger
MoH using sendonly SDP and unhold remote
party using sendrecv SDP
- MoH covers only audio type of media sessions
- there is no specific selection of media sections
to be held, thus, if one audio media puts the
call on hold, the whole call is held
- list of parameters to be given when advertising
MoH capabilities: a sound source (file, blob or db source);
sendonly/sendrecv hold; zero-connection hold;
At least an audio file source must be given
- MoH cannot be mixed with the play media functionality,
the last one triggered will override previous one
- MoH must be unheld to stop the media being sent
towards a recipient, otherwise only a termination
of monologues will stop this packets stream
Change-Id: Iefd83ced79c14dadad936348a1d529007d6e7b3b
There is enough overlap between the two enums that it doesn't make sense
to carry along both. Unify.
Change-Id: I9de8fbdb8d78a2002d8c1b62cea57188c937d61d
Allow to-tag updates in answers only until a non-provisional response is
received. Add a flag to optionally suppress this feature.
Change-Id: I67110ede662f618ba5a355500d3d84050e6318cc
If we receive an answer from a to-tag that hasn't previously seen and no
corresponding monologue exists (created from a previous offer),
previously we would treat this is a separate and new call branch, create
a brand new monologue and dissociate the previous one. This may lead to
unexpected results as this new monologue has been created without the
same initialisation as was done for the original one, and so may be left
with incorrect or incomplete data (e.g. SRTP keys, codec information,
interface bindings, etc).
Improve this by treating an unexpected and unseen to-tag as an alias to
the already existing to-tag. Going forward both tags can be used
interchangeably to refer to the same monologue.
Add a flag to suppress this new behaviour, in case some situation is
made worse by it.
Change-Id: Ie8f838eebd50d29d3549031998a2eb1f610b04bb
In order to be able to control, which media types
one wants to have in the call session,
add support of the "sdp-media-remove" flag.
Syntax:
"sdp-media-remove" : ["<media-type>", "<media-type>", ...]
Additionally, add according unit tests.
Change-Id: Ic52456f8124319992ea9ca8c161daefb1df46b59
Convert existing sdp manipulation helpers
to work with arrays directly, independently from
which array is actually given.
Moreover, don't use the `G_N_ELEMENTS()`,
since we already have the `__MT_MAX`
as the size of the array, so we can directly use
that instead of relying on the G_N_ELEMENTS macro
(for array size checks).
Change-Id: I38e71dab30a467b933c213c8157fa06651ebaf04
Add `call_ng_process_flags()` based parsing
as for other opmodes, like offer and answer.
This keeps the backwards compatibility with
the older "flags" parsing approach on the
module side, as well as adds the possibility
to parse rtpp-flags on the daemon side.
As an advantage, there is no need to use
specific local parsing for things like
to/from tags, call-id, delete-delay etc.
Additionally:
- this commit introduces flags-flags parsing
for the "fatal" flag.
However, as before is only taken into account
by the `call_delete_ng()` processing,
so no functional change.
- this commit introduces main-flags parsing
for the "delete-delay" flag, which is also
only taken into account by the `call_delete_ng()`
processing, so no functional change.
- this commit adds To-tag options flag prasing
into the `call_ng_flags_flags()` function,
and is used by `call_delete_ng()` specificially,
for cases when more specific identification of
monologues to be deleted is used.
Change-Id: Ia992e5375a2f86318d9ad193a7857dd589038eed
Support session level group attribute
for the sdp_create approach.
Additionally: fix the `testVideoroomWebRTCVideo` test,
because as it turned out it used to lose the `a=group:`
attribute before.
Change-Id: I799dfe0dcc5e0708864150cd0e0262a61dd4cc05