-
Notifications
You must be signed in to change notification settings - Fork 790
Description
Environment
- JsSIP Version: Unknown (Source files manually integrated)
- Installation Method: I am using the source files (
RTCSession.js,Dialog.js, etc.) directly in my project structure (not installed via npm/yarn and not a git fork).
Description
I have encountered a race condition in RTCSession.js / Dialog.js regarding how CSeq numbers are generated for ACK requests confirming a re-INVITE.
If a new request (such as a REFER) is sent immediately after receiving the 200 OK for a re-INVITE, but before the ACK is generated, the Dialog increments its local CSeq counter. When the ACK is subsequently generated by RTCSession, the Dialog uses the new (incremented) CSeq instead of the CSeq associated with the original INVITE.
This causes a protocol mismatch (RFC 3261 Section 17.1.1.3), resulting in the remote party rejecting the ACK or dropping the call.
Reproduction Steps
- Establish an active call.
- Trigger a re-INVITE.
- Immediately trigger a
REFER(transfer) upon receiving the200 OK(simulating a user pressing transfer quickly, or an automated flow). - Observe the SIP packet flow.
Observed Behavior
- re-INVITE sent (CSeq:
N). - 200 OK received.
- REFER sent immediately. Dialog increments local CSeq to
N+1. - ACK sent for the re-INVITE.
Dialog.jsuses current_local_seqnum(N+1). - Result: Remote server drops call because ACK CSeq
N+1does not match INVITE CSeqN.
Expected Behavior
The ACK must always carry the same CSeq number as the INVITE it acknowledges, regardless of other requests sent in the interim.
Suggested Fix
I have patched this locally by passing the specific CSeq from the RTCSession down to the Dialog.
1. RTCSession.js
In _sendReinvite (and _sendUpdate), pass the response CSeq explicitly when sending the ACK.
// RTCSession.js -> _sendReinvite -> onSucceeded
// Old Code:
// this.sendRequest(JsSIP_C.ACK);
// New Code:
this.sendRequest(JsSIP_C.ACK, { cseq: response.cseq });
// Dialog.js
sendRequest(method, options = {}) {
// specific cseq extraction
const cseq = options.cseq || null;
// Pass cseq to _createRequest
const request = this._createRequest(method, extraHeaders, body, cseq);
}
_createRequest(method, extraHeaders, body, cseqOverride = null) {
if (!this._local_seqnum) {
this._local_seqnum = Math.floor(Math.random() * 10000);
}
// Use override if provided, otherwise default logic
let cseq;
if (cseqOverride) {
cseq = cseqOverride;
} else {
cseq = (method === JsSIP_C.CANCEL || method === JsSIP_C.ACK)
? this._local_seqnum
: (this._local_seqnum += 1);
}
// ... create SIPMessage.OutgoingRequest using 'cseq' variable ...
}