Skip to content

Commit 9a1ebdf

Browse files
committed
Late SDP implementation
Accept receiving SDP-less INVITE requests
1 parent 390f4b5 commit 9a1ebdf

File tree

2 files changed

+172
-107
lines changed

2 files changed

+172
-107
lines changed

src/Constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ JsSIP.C= {
2121
NOT_FOUND: 'Not Found',
2222
ADDRESS_INCOMPLETE: 'Address Incomplete',
2323
INCOMPATIBLE_SDP: 'Incompatible SDP',
24+
MISSING_SDP: 'Missing SDP',
2425
AUTHENTICATION_ERROR: 'Authentication Error',
2526
DIALOG_ERROR: 'Dialog Error',
2627

src/RTCSession.js

Lines changed: 171 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ RTCSession = function(ua) {
3838
this.earlyDialogs = {};
3939
this.rtcMediaHandler = null;
4040

41+
// RTCSession confirmation flag
42+
this.is_confirmed = false;
43+
44+
// is late SDP being negotiated
45+
this.late_sdp = false;
46+
4147
// Session Timers
4248
this.timers = {
4349
ackTimer: null,
@@ -286,11 +292,19 @@ RTCSession.prototype.answer = function(options) {
286292
return;
287293
}
288294

289-
self.rtcMediaHandler.createAnswer(
290-
answerCreationSucceeded,
291-
answerCreationFailed,
292-
RTCAnswerConstraints
293-
);
295+
if (self.late_sdp) {
296+
self.rtcMediaHandler.createOffer(
297+
sdpCreationSucceeded,
298+
sdpCreationFailed,
299+
RTCAnswerConstraints
300+
);
301+
} else {
302+
self.rtcMediaHandler.createAnswer(
303+
sdpCreationSucceeded,
304+
sdpCreationFailed,
305+
RTCAnswerConstraints
306+
);
307+
}
294308
},
295309

296310
// rtcMediaHandler.addStream failed
@@ -302,8 +316,8 @@ RTCSession.prototype.answer = function(options) {
302316
self.failed('system', null, JsSIP.C.causes.WEBRTC_ERROR);
303317
},
304318

305-
// rtcMediaHandler.createAnswer succeeded
306-
answerCreationSucceeded = function(body) {
319+
// rtcMediaHandler.createAnswer or rtcMediaHandler.createOffer succeeded
320+
sdpCreationSucceeded = function(body) {
307321
var
308322
// run for reply success callback
309323
replySucceeded = function() {
@@ -326,8 +340,8 @@ RTCSession.prototype.answer = function(options) {
326340
);
327341
},
328342

329-
// rtcMediaHandler.createAnsewr failed
330-
answerCreationFailed = function() {
343+
// rtcMediaHandler.createAnswer or rtcMediaHandler.createOffer failed
344+
sdpCreationFailed = function() {
331345
if (self.status === C.STATUS_TERMINATED) {
332346
return;
333347
}
@@ -804,10 +818,45 @@ RTCSession.prototype.getRemoteStreams = function() {
804818
RTCSession.prototype.init_incoming = function(request) {
805819
var expires,
806820
self = this,
807-
contentType = request.getHeader('Content-Type');
821+
contentType = request.getHeader('Content-Type'),
822+
823+
waitForAnswer = function() {
824+
self.status = C.STATUS_WAITING_FOR_ANSWER;
825+
826+
// Set userNoAnswerTimer
827+
self.timers.userNoAnswerTimer = window.setTimeout(function() {
828+
request.reply(408);
829+
self.failed('local',null, JsSIP.C.causes.NO_ANSWER);
830+
}, self.ua.configuration.no_answer_timeout
831+
);
832+
833+
/* Set expiresTimer
834+
* RFC3261 13.3.1
835+
*/
836+
if (expires) {
837+
self.timers.expiresTimer = window.setTimeout(function() {
838+
if(self.status === C.STATUS_WAITING_FOR_ANSWER) {
839+
request.reply(487);
840+
self.failed('system', null, JsSIP.C.causes.EXPIRES);
841+
}
842+
}, expires
843+
);
844+
}
845+
846+
// Fire 'newRTCSession' event.
847+
self.newRTCSession('remote', request);
848+
849+
// Reply 180.
850+
request.reply(180, null, ['Contact: ' + self.contact]);
851+
852+
// Fire 'progress' event.
853+
// TODO: Document that 'response' field in 'progress' event is null for
854+
// incoming calls.
855+
self.progress('local', null);
856+
};
808857

809858
// Check body and content type
810-
if(!request.body || (contentType !== 'application/sdp')) {
859+
if(request.body && (contentType !== 'application/sdp')) {
811860
request.reply(415);
812861
return;
813862
}
@@ -845,57 +894,29 @@ RTCSession.prototype.init_incoming = function(request) {
845894
constraints: {"optional": [{'DtlsSrtpKeyAgreement': 'true'}]}
846895
});
847896

848-
this.rtcMediaHandler.onMessage(
849-
'offer',
850-
request.body,
851-
/*
852-
* onSuccess
853-
* SDP Offer is valid. Fire UA newRTCSession
854-
*/
855-
function() {
856-
self.status = C.STATUS_WAITING_FOR_ANSWER;
857-
858-
// Set userNoAnswerTimer
859-
self.timers.userNoAnswerTimer = window.setTimeout(function() {
860-
request.reply(408);
861-
self.failed('local',null, JsSIP.C.causes.NO_ANSWER);
862-
}, self.ua.configuration.no_answer_timeout
863-
);
864-
865-
/* Set expiresTimer
866-
* RFC3261 13.3.1
897+
if (request.body) {
898+
this.rtcMediaHandler.onMessage(
899+
'offer',
900+
request.body,
901+
/*
902+
* onSuccess
903+
* SDP Offer is valid. Fire UA newRTCSession
867904
*/
868-
if (expires) {
869-
self.timers.expiresTimer = window.setTimeout(function() {
870-
if(self.status === C.STATUS_WAITING_FOR_ANSWER) {
871-
request.reply(487);
872-
self.failed('system', null, JsSIP.C.causes.EXPIRES);
873-
}
874-
}, expires
875-
);
905+
waitForAnswer,
906+
/*
907+
* onFailure
908+
* Bad media description
909+
*/
910+
function(e) {
911+
self.logger.warn('invalid SDP');
912+
self.logger.warn(e);
913+
request.reply(488);
876914
}
877-
878-
// Fire 'newRTCSession' event.
879-
self.newRTCSession('remote', request);
880-
881-
// Reply 180.
882-
request.reply(180, null, ['Contact: ' + self.contact]);
883-
884-
// Fire 'progress' event.
885-
// TODO: Document that 'response' field in 'progress' event is null for
886-
// incoming calls.
887-
self.progress('local', null);
888-
},
889-
/*
890-
* onFailure
891-
* Bad media description
892-
*/
893-
function(e) {
894-
self.logger.warn('invalid SDP');
895-
self.logger.warn(e);
896-
request.reply(488);
897-
}
898-
);
915+
);
916+
} else {
917+
this.late_sdp = true;
918+
waitForAnswer();
919+
}
899920
};
900921

901922
RTCSession.prototype.connect = function(target, options) {
@@ -1106,39 +1127,18 @@ RTCSession.prototype.receiveReinvite = function(request) {
11061127
sdp, idx, direction,
11071128
self = this,
11081129
contentType = request.getHeader('Content-Type'),
1109-
hold = true;
1110-
1111-
if (! request.body) {
1112-
this.logger.warn('re-INVITE without body not implemented');
1113-
request.reply(415, 're-INVITE Without Body Not Implemented');
1114-
return;
1115-
}
1130+
hold = false,
11161131

1117-
if (contentType !== 'application/sdp') {
1118-
this.logger.warn('invalid Content-Type');
1119-
request.reply(415);
1120-
return;
1121-
}
1122-
1123-
sdp = JsSIP.Parser.parseSDP(request.body);
1124-
1125-
for (idx=0; idx < sdp.media.length; idx++) {
1126-
direction = sdp.direction || sdp.media[idx].direction || 'sendrecv';
1127-
1128-
if (direction !== 'sendonly' && direction !== 'inactive') {
1129-
hold = false;
1130-
}
1131-
}
1132+
createSdp = function(onSuccess, onFailure) {
1133+
if (self.late_sdp) {
1134+
self.rtcMediaHandler.createOffer(onSuccess, onFailure);
1135+
} else {
1136+
self.rtcMediaHandler.createAnswer(onSuccess, onFailure);
1137+
}
1138+
},
11321139

1133-
this.rtcMediaHandler.onMessage(
1134-
'offer',
1135-
request.body,
1136-
/*
1137-
* onSuccess
1138-
* SDP Offer is valid
1139-
*/
1140-
function() {
1141-
self.rtcMediaHandler.createAnswer(
1140+
answer = function() {
1141+
createSdp(
11421142
// onSuccess
11431143
function(body) {
11441144
request.reply(200, null, ['Contact: ' + self.contact], body,
@@ -1160,16 +1160,47 @@ RTCSession.prototype.receiveReinvite = function(request) {
11601160
request.reply(500);
11611161
}
11621162
);
1163-
},
1164-
/*
1165-
* onFailure
1166-
* Bad media description
1167-
*/
1168-
function(e) {
1169-
self.logger.error(e);
1170-
request.reply(488);
1163+
};
1164+
1165+
1166+
if (request.body) {
1167+
if (contentType !== 'application/sdp') {
1168+
this.logger.warn('invalid Content-Type');
1169+
request.reply(415);
1170+
return;
11711171
}
1172-
);
1172+
1173+
sdp = JsSIP.Parser.parseSDP(request.body);
1174+
1175+
for (idx=0; idx < sdp.media.length; idx++) {
1176+
direction = sdp.direction || sdp.media[idx].direction || 'sendrecv';
1177+
1178+
if (direction === 'sendonly' || direction === 'inactive') {
1179+
hold = true;
1180+
}
1181+
}
1182+
1183+
this.rtcMediaHandler.onMessage(
1184+
'offer',
1185+
request.body,
1186+
/*
1187+
* onSuccess
1188+
* SDP Offer is valid
1189+
*/
1190+
answer,
1191+
/*
1192+
* onFailure
1193+
* Bad media description
1194+
*/
1195+
function(e) {
1196+
self.logger.error(e);
1197+
request.reply(488);
1198+
}
1199+
);
1200+
} else {
1201+
this.late_sdp = true;
1202+
answer();
1203+
}
11731204
};
11741205

11751206
/**
@@ -1244,7 +1275,8 @@ RTCSession.prototype.receiveUpdate = function(request) {
12441275
* In dialog Request Reception
12451276
*/
12461277
RTCSession.prototype.receiveRequest = function(request) {
1247-
var contentType;
1278+
var contentType,
1279+
self = this;
12481280

12491281
if(request.method === JsSIP.C.CANCEL) {
12501282
/* RFC3261 15 States that a UAS may have accepted an invitation while a CANCEL
@@ -1270,8 +1302,40 @@ RTCSession.prototype.receiveRequest = function(request) {
12701302
if(this.status === C.STATUS_WAITING_FOR_ACK) {
12711303
window.clearTimeout(this.timers.ackTimer);
12721304
window.clearTimeout(this.timers.invite2xxTimer);
1273-
this.status = C.STATUS_CONFIRMED;
1274-
this.confirmed('remote', request);
1305+
1306+
if (this.late_sdp) {
1307+
if (!request.body) {
1308+
self.ended('remote', request, JsSIP.C.causes.MISSING_SDP);
1309+
break;
1310+
}
1311+
1312+
this.rtcMediaHandler.onMessage(
1313+
'answer',
1314+
request.body,
1315+
/*
1316+
* onSuccess
1317+
* SDP Answer fits with Offer. Media will start
1318+
*/
1319+
function() {
1320+
self.late_sdp = false;
1321+
self.status = C.STATUS_CONFIRMED;
1322+
},
1323+
/*
1324+
* onFailure
1325+
* SDP Answer does not fit the Offer. Accept the call and Terminate.
1326+
*/
1327+
function(e) {
1328+
self.logger.warn(e);
1329+
self.ended('remote', request, JsSIP.C.causes.BAD_MEDIA_DESCRIPTION);
1330+
}
1331+
);
1332+
} else {
1333+
this.status = C.STATUS_CONFIRMED;
1334+
}
1335+
1336+
if (this.status === C.STATUS_CONFIRMED && !this.is_confirmed) {
1337+
this.confirmed('remote', request);
1338+
}
12751339
}
12761340
break;
12771341
case JsSIP.C.BYE:
@@ -1579,7 +1643,7 @@ RTCSession.prototype.receiveInviteResponse = function(response) {
15791643
this.status = C.STATUS_CONFIRMED;
15801644

15811645
if(!response.body) {
1582-
this.acceptAndTerminate(response, 400, 'Missing session description');
1646+
this.acceptAndTerminate(response, 400, JsSIP.C.causes.MISSING_SDP);
15831647
this.failed('remote', response, JsSIP.C.causes.BAD_MEDIA_DESCRIPTION);
15841648
break;
15851649
}
@@ -1597,10 +1661,8 @@ RTCSession.prototype.receiveInviteResponse = function(response) {
15971661
* SDP Answer fits with Offer. Media will start
15981662
*/
15991663
function() {
1600-
var ack;
1601-
16021664
session.accepted('remote', response);
1603-
ack = session.sendRequest(JsSIP.C.ACK);
1665+
session.sendRequest(JsSIP.C.ACK);
16041666
session.confirmed('local', null);
16051667
},
16061668
/*
@@ -1812,6 +1874,8 @@ RTCSession.prototype.confirmed = function(originator, ack) {
18121874
var session = this,
18131875
event_name = 'confirmed';
18141876

1877+
this.is_confirmed = true;
1878+
18151879
session.emit(event_name, session, {
18161880
originator: originator,
18171881
ack: ack || null

0 commit comments

Comments
 (0)