In WebRTC's JSEP (RFC 9429), in what order do offerer and answerer call createOffer, setLocalDescription, setRemoteDescription and createAnswer, and why does it matter?
answer
- creating is not applying
- offerer sets local first
- answerer sets remote first
- signalingState marks each step
- candidates need a remote description
basics
~10 sOfferer: createOffer, setLocalDescription, send. Answerer: setRemoteDescription, createAnswer, setLocalDescription, send back. Offerer: setRemoteDescription. Creating only drafts SDP; setting applies it and moves the signalling state, so steps out of order are rejected.
solid answer
~40 sThe offerer calls `createOffer`, applies it with `setLocalDescription` (state `have-local-offer`) and sends it. The answerer applies it with `setRemoteDescription` (`have-remote-offer`), calls `createAnswer`, applies that with `setLocalDescription` (`stable`) and sends it back; the offerer finishes with `setRemoteDescription` (`stable`). Order matters because `createOffer` and `createAnswer` only generate SDP: they change no state and start no gathering. `setLocalDescription` commits a description and starts candidate gathering for it. `createAnswer` is defined against the most recently applied remote offer, so calling it first is rejected with `InvalidStateError`, and `addIceCandidate` is rejected the same way until a remote description exists. The offerer applies its own offer before sending it because ICE checks, and on a re-offer even media, can arrive before the answer does. RFC 9429 obsoletes RFC 8829 without changing this order.
code
pseudocode · 19 lines// offerer
offer = pc.createOffer() // drafts SDP; no state change
pc.setLocalDescription(offer) // have-local-offer; gathering starts
channel.send(offer)
// answerer, on receiving the offer
pc.setRemoteDescription(offer) // have-remote-offer
for c in queuedCandidates: pc.addIceCandidate(c)
answer = pc.createAnswer()
pc.setLocalDescription(answer) // stable
channel.send(answer)
// offerer, on receiving the answer
pc.setRemoteDescription(answer) // stable
for c in queuedCandidates: pc.addIceCandidate(c)
// either side, on receiving a trickled candidate
if pc.remoteDescription is null: queuedCandidates.append(candidate)
else: pc.addIceCandidate(candidate)go deeper
Recall the seven steps in order: create, set local and send on one side; set remote, create, set local and send on the other; set remote to finish.
Explain why creating changes nothing while setting moves the signalling state and starts gathering, and name the errors an out-of-order call produces.
Show you can diagnose sequencing bugs from signalingState and rejected promises, and that you queue early candidates instead of dropping them.
Judge where the sequencing logic should live, such as one negotiation module that owns every create and set call, so feature code never races the signalling state.
## The two halves of every exchange **JSEP**, the JavaScript Session Establishment Protocol (RFC 9429, which obsoletes RFC 8829), defines how a WebRTC endpoint produces and applies SDP **session descriptions**. The W3C WebRTC 1.0 API exposes it as methods on `RTCPeerConnection`. A complete offer/answer exchange is seven steps: 1. Offerer: `createOffer()` generates offer SDP from the current transceivers and data channels. 2. Offerer: `setLocalDescription(offer)` applies it; the signalling state becomes `have-local-offer`. 3. Offerer: sends the offer over the application's signalling channel. 4. Answerer: `setRemoteDescription(offer)`; the state becomes `have-remote-offer`. 5. Answerer: `createAnswer()` generates an answer compatible with that offer. 6. Answerer: `setLocalDescription(answer)`; the state returns to `stable`, and the answer is sent back. 7. Offerer: `setRemoteDescription(answer)`; its state also returns to `stable`. The same sequence repeats for every later change, such as adding a track or restarting ICE. ## Create versus set The split between *creating* and *setting* is the core of the design. | Method | Changes signalling state? | Starts candidate gathering? | |---|---|---| | `createOffer` | no | no | | `createAnswer` | no | no | | `setLocalDescription` | yes | yes, for transports the description needs | | `setRemoteDescription` | yes | no | RFC 9429 says of `createOffer` that it "does not change the PeerConnection state, trigger candidate gathering, or cause media to start or stop flowing"; the offer only becomes the pending local description when it is set. The W3C API also accepts `setLocalDescription()` with no argument, which creates the right offer or answer and applies it in one step. Its own examples use that form because it avoids races between creating and applying. ## Why each side sets its descriptions in this order - **The offerer applies its offer before sending it.** JSEP notes that the offerer may receive ICE connectivity checks, and on a re-offer even media, before the answer arrives. Its media stack must already know what it offered in order to handle them, and it must support both the current and the pending description until the answer settles which one wins. - **The answerer applies the remote offer before creating an answer.** `createAnswer` is defined against the parameters of the most recent `setRemoteDescription`; RFC 9429 says that call "MUST have been called prior to calling createAnswer". The W3C API rejects it with `InvalidStateError` unless the state is `have-remote-offer` or `have-local-pranswer`. - **Remote candidates need a remote description.** The W3C `addIceCandidate` steps reject with `InvalidStateError` while `remoteDescription` is null, because a candidate is matched to an `m=` section by its `sdpMid` or `sdpMLineIndex`. A peer that receives trickled candidates early must queue them. - **A local description must be the one that was created.** The W3C API rejects with `InvalidModificationError` an offer or answer whose SDP differs from the last one created, so edits made between create and set do not survive. ## The signalling states along the way | State | Meaning | |---|---| | `stable` | no exchange in progress; a new offer may be created | | `have-local-offer` | this side applied an offer and awaits the answer | | `have-remote-offer` | this side applied a remote offer and owes an answer | | `have-local-pranswer` / `have-remote-pranswer` | a provisional answer has been applied | | `closed` | the connection was closed | Two offers in flight at once (both peers in `have-local-offer`) is **glare**, and JSEP leaves resolving it to the application. ## What goes wrong when the order slips - Sending the offer before setting it locally: the peer's answer and early ICE checks can arrive at an offerer that has no pending description to match them against. - Calling `createAnswer` on a fresh connection: the promise rejects, because there is no remote offer to answer. - Applying trickled candidates before `setRemoteDescription`: each `addIceCandidate` rejects, and a call that drops those candidates may fail to connect or fall back to a worse path. - Treating `createOffer` as the moment negotiation starts: nothing is committed, gathering has not begun, and calling it twice simply produces two drafts. The states make these errors observable: checking `signalingState` before each step is how an application tells a bug in its own sequencing from a network problem.
- Can you edit the SDP returned by createOffer before passing it to setLocalDescription?Not through the W3C API. `setLocalDescription` rejects with `InvalidModificationError` when the supplied offer or answer differs from the one last created. Change behaviour through the API instead: transceiver direction, codec preferences or the configuration. RFC 9429 also warns implementations to expect bogus SDP from scripts, which is part of why the API refuses edits.
- What does calling setLocalDescription with no argument do in WebRTC?It creates the appropriate description and applies it in one step: an offer in `stable`, `have-local-offer` or `have-remote-pranswer`, and an answer otherwise (`have-remote-offer` or `have-local-pranswer`). The W3C specification's examples use it because nothing can slip between creating and applying, which matters when signalling messages arrive concurrently.
- Why would an answerer's addIceCandidate calls fail even though every candidate is valid?Because they reached it before it applied the offer. While `remoteDescription` is null, `addIceCandidate` rejects with `InvalidStateError`. A callee that waits for the user to accept before calling `setRemoteDescription` must buffer incoming candidates and add them right after the offer is applied.
saying these in an interview costs you the question
- createOffer starts ICE candidate gathering by itself.
- The answerer can call createAnswer before applying the offer.
- Send the offer first, and call setLocalDescription after the answer arrives.
- addIceCandidate works before any remote description is set.
- RFC 9429 changed the offer/answer call order from RFC 8829.