'Is it possible to convert a WebRTC SDP offer to answer?

I have two peers that want to connect to each other via WebRTC. Typically the first peer would create an offer and send it to the second via a signalling channel/server, the second peer would respond with an answer. This scenario works fine.

However, is it possible to support the case where both peers happen to try to connect to each other simultaneously both sending SDP offers to one another concurrently via the signalling server.

// Both peers do this simultaneously:
const conn = new RTCPeerConnection(null);
const sdpOffer = await conn.createOffer();
await conn.setLocalDescription(sdpOffer);
signalingService.send(peerId, sdpOffer);

// At some point in the future both peers also receive an SDP offer 
// (rather than answer) from the other peer whom they sent an offer to 
// via the signaling service. If this was an answer we'd call 
// RTCPeerConnection.setRemoteDescription, however this doesn't work for an 
// offer: 

conn.setRemoteDescription(peerSDPOffer); 
// In Chrome results in "DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer"

I even tried to "convert" the received peer offers into answers by rewriting the SDP type from offer to answer and setup:actpass to setup:active but that doesn't seem to work, instead I just get a new exception.

So the question is, is this simultaneous connect/offer use case supported in some fashion - or should I close one side/peer RTCPeerConnection & instantiate a new one using RTCPeerConnection.createAnswer this time?



Solution 1:[1]

This situation is known as "signaling glare". The WebRTC API does not really define how to handle this (except for something called "rollback" but it is not implemented in any browser yet and nobody has missed it so far) so you have to avoid this situation yourself.

Simply replacing the a=setup won't work since the underlying DTLS mechanism still needs the concept of a client and a server.

Solution 2:[2]

The answer for how to avoid glare these days is to use the Perfect Negotiation Pattern: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation

However, what OP described does work with the slight modification of setting setup:active on one peer and setup:passive on the other: https://codepen.io/evan-brass/pen/mdpgWGG?editors=0010

It might not work for audio / video connections (because those may require negotiating codecs), but I've tested it on Chrome / Firefox / Safari for DataChannel only connections.

You could choose which peer is active and which is passive using whatever system you use to determine 'politeness' in perfect negotiation. One possibility would be to compare the DTLS fingerprints and make whichever one is larger the active peer.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Philipp Hancke
Solution 2 evan-brass