Add rs-metadata update support for SIPREC mid-dialog requests - #5206
Add rs-metadata update support for SIPREC mid-dialog requests#5206sorooshm78 wants to merge 5 commits into
Conversation
nanangizz
left a comment
There was a problem hiding this comment.
Thanks for the follow-up work on this — mid-dialog metadata handling is genuinely missing today, and the RFC 9806 media-type reordering in pjsip_siprec_get_metadata() is a good catch and correct.
I built the branch with PJSUA_HAS_SIPREC=1 (clean, no new warnings) and traced the touched paths. The build passing hides a few real defects, so I'd like these addressed before merging. Details are in the inline comments; summarising the blocking ones:
pjsip_siprec_parse_data_mode()can never detect a partial update. It looks for the literal"dataMode"parsed as an XML attribute, but RFC 7865 definesdataModeonly as the XSD type name — the document construct is the lowercase element<datamode>partial</datamode>. Case-sensitive match plus attribute-style parsing means a conformant document always yieldsis_complete = PJ_TRUE.- Use-after-free in
pjsua_call.c— the metadata copy is allocated frominv->pool_prov, whichinv_negotiate_sdp()resets after every negotiation. - A failed metadata check can leave the request unanswered, hanging the UAS transaction until timer-B/H.
- A metadata-only UPDATE (no SDP) is answered 488 — that is arguably the main use case for this feature per RFC 7866 §7.1.
inv->poolgrows unboundedly — one permanent allocation per metadata update on the long-term pool.
There are also no tests. pjsip/src/test/pjsua_call_test.c already has a SIPREC section exercising siprec_require_metadata; a mid-dialog re-INVITE/UPDATE case belongs there and would have caught items 1 and 4 straight away.
Style: 8 added lines exceed 80 columns (1 in sip_siprec.h, 2 in sip_inv.c, 5 in sip_siprec.c), and the new prototypes' continuation lines are indented one space past the open paren — please see the coding style guide.
Minor: the description says "Fixes #5024", but that is a PR rather than an issue — I think you mean it supersedes it.
| /* Only process if SIPREC is supported */ | ||
| if (!(inv->options & PJSIP_INV_SUPPORT_SIPREC)) | ||
| return PJ_SUCCESS; |
There was a problem hiding this comment.
Wrong flag. The doxygen for pjsip_siprec_verify_update() says the session must be SIPREC-enabled with PJSIP_INV_REQUIRE_SIPREC, and REQUIRE implies SUPPORT (see line 1365) — but not the reverse.
pjsua_call.c sets PJSIP_INV_SUPPORT_SIPREC on every incoming call whenever the account has use_siprec != PJSUA_SIP_SIPREC_INACTIVE, so ordinary non-recording calls run this path too. Combined with siprec_require_metadata = PJ_TRUE, every ordinary re-INVITE on such an account would be answered 400. Please test PJSIP_INV_REQUIRE_SIPREC.
| /* Process SIPREC metadata update if present */ | ||
| if (inv_process_siprec_metadata_update(inv, rdata) != PJ_SUCCESS) | ||
| return; |
There was a problem hiding this comment.
A bare return here relies on the helper having sent the response, which it does not always do (see my comment on the helper's error path). inv->invite_tsx has already been assigned above, so on that path the re-INVITE is left unanswered until the transaction times out.
Please guarantee a final response before returning.
| call->siprec_metadata.ptr = (char*) | ||
| pj_pool_alloc(call->inv->pool_prov, new_metadata->slen); | ||
| pj_memcpy(call->siprec_metadata.ptr, new_metadata->ptr, | ||
| new_metadata->slen); | ||
| call->siprec_metadata.slen = new_metadata->slen; |
There was a problem hiding this comment.
Use-after-free. pool_prov is the provisional flip-flop pool: inv_negotiate_sdp() swaps it and calls pj_pool_reset() on it after every negotiation (see the ticket #877 comment in sip_inv.h). This callback fires before negotiation in the same handler, so the copy is released within one or two re-INVITE rounds and the memory is then handed out to unrelated allocations.
Separately — call->siprec_metadata is write-only across the entire tree. Nothing reads it, there is no pjsua or pjsua2 accessor for it, and no pjsua_callback entry was added, so this whole hunk currently has no user-visible effect. Please either complete it (a pjsua-level callback plus an accessor, allocated from a pool with a suitable lifetime), or drop the pjsua part from this PR and keep the change at the pjsip-ua layer.
| #if PJSUA_HAS_SIPREC | ||
| inv_cb.on_siprec_metadata_update = &pjsua_call_on_siprec_metadata_update; |
There was a problem hiding this comment.
pjsua claims on_siprec_metadata_update unconditionally here, so an application built on pjsua can never install its own handler and never learns about the update. If the pjsua part stays in this PR, it needs a corresponding pjsua_callback entry to forward the event.
| #if PJSUA_HAS_SIPREC | ||
| /** | ||
| * This callback is called when SIPREC rs-metadata is updated via | ||
| * mid-dialog re-INVITE or UPDATE request. This allows applications | ||
| * to track metadata changes during the lifetime of a recording | ||
| * session, as required by RFC 7866. | ||
| * | ||
| * The callback is invoked after the metadata has been successfully | ||
| * parsed and stored in the INVITE session, but before SDP | ||
| * negotiation completes. | ||
| * | ||
| * This callback is optional. | ||
| * | ||
| * @param inv The invite session. | ||
| * @param old_metadata Previous metadata (may be NULL or empty). | ||
| * @param new_metadata New metadata from the update. | ||
| * @param rdata The received request containing the update. | ||
| */ | ||
| void (*on_siprec_metadata_update)(pjsip_inv_session *inv, |
There was a problem hiding this comment.
These #if PJSUA_HAS_SIPREC guards make the layout of public pjsip-ua structs (pjsip_inv_callback here, pjsip_inv_session below) depend on a PJSUA_* macro — a layering inversion, since pjsip-ua sits below pjsua-lib.
It happens to work today only because config_site.h is pulled in ahead of everything. Note that pjsua.h defines its own default for this macro after it has already included sip_inv.h, so defining PJSUA_HAS_SIPREC anywhere other than config_site.h would silently desynchronise these struct layouts between translation units. I'd prefer both fields unconditional.
| PJ_DECL(pj_status_t) pjsip_siprec_verify_update(pjsip_rx_data *rdata, | ||
| pj_str_t *metadata, | ||
| pj_pool_t *pool, | ||
| pjsip_tx_data **p_tdata, | ||
| pjsip_dialog *dlg, | ||
| pjsip_endpoint *endpt, | ||
| const pjsip_siprec_verify_setting *setting); |
There was a problem hiding this comment.
Style: the continuation lines are indented one space past the open paren, and the last line is 94 columns. Please align the parameters with the opening paren and keep to ~80 columns (same for pjsip_siprec_parse_data_mode() below).
Summary
Add support for SIPREC
rs-metadatadocument updates during mid-dialogre-INVITEandUPDATErequests, enabling SRS to track metadata changes throughout the recording session lifecycle as required by RFC 7866.Changes
on_siprec_metadata_updatecallback topjsip_inv_callbackandsiprec_metadatafield topjsip_inv_sessionfor tracking metadata changespjsip_siprec_verify_update()andpjsip_siprec_parse_data_mode()functions to process and validate mid-dialog metadata updatesUPDATEandre-INVITEhandlers with refactored shared helper functionapplication/rs-metadata+xml, accept legacyapplication/rs-metadatawith deprecation warningMotivation
RFC 7866 Section 4.5 requires that SRS must be able to receive and process metadata updates during the lifetime of a Communication Session (CS). The original implementation only processed metadata in the initial
INVITE, ignoring mid-dialog updates. This enables tracking participant changes, permission updates, and other metadata changes during recording.Reference