jssip
Version:
The Javascript SIP library
244 lines (243 loc) • 9.7 kB
JavaScript
"use strict";
const Logger = require('./Logger');
const SIPMessage = require('./SIPMessage');
const JsSIP_C = require('./Constants');
const Transactions = require('./Transactions');
const Dialog_RequestSender = require('./Dialog/RequestSender');
const Utils = require('./Utils');
const logger = new Logger('Dialog');
const C = {
// Dialog states.
STATUS_EARLY: 1,
STATUS_CONFIRMED: 2,
STATUS_TERMINATED: 3,
};
// RFC 3261 12.1.
module.exports = class Dialog {
// Expose C object.
static get C() {
return C;
}
constructor(owner, message, type, state = C.STATUS_CONFIRMED) {
this._owner = owner;
this._ua = owner._ua;
this._uac_pending_reply = false;
this._uas_pending_reply = false;
if (!message.hasHeader('contact')) {
return {
error: 'unable to create a Dialog without Contact header field',
};
}
if (message instanceof SIPMessage.IncomingResponse) {
state = message.status_code < 200 ? C.STATUS_EARLY : C.STATUS_CONFIRMED;
}
const contact = message.parseHeader('contact');
// RFC 3261 12.1.1.
if (type === 'UAS') {
this._id = {
call_id: message.call_id,
local_tag: message.to_tag,
remote_tag: message.from_tag,
toString() {
return this.call_id + this.local_tag + this.remote_tag;
},
};
this._state = state;
this._remote_seqnum = message.cseq;
this._local_uri = message.parseHeader('to').uri;
this._remote_uri = message.parseHeader('from').uri;
this._remote_target = contact.uri;
this._route_set = message.getHeaders('record-route');
this._incoming_ack_seqnum = message.cseq;
this._outgoing_ack_seqnum = null;
}
// RFC 3261 12.1.2.
else if (type === 'UAC') {
this._id = {
call_id: message.call_id,
local_tag: message.from_tag,
remote_tag: message.to_tag,
toString() {
return this.call_id + this.local_tag + this.remote_tag;
},
};
this._state = state;
this._local_seqnum = message.cseq;
this._local_uri = message.parseHeader('from').uri;
this._remote_uri = message.parseHeader('to').uri;
this._remote_target = contact.uri;
this._route_set = message.getHeaders('record-route').reverse();
this._incoming_ack_seqnum = null;
this._outgoing_ack_seqnum = this._local_seqnum;
}
this._ua.newDialog(this);
logger.debug(`new ${type} dialog created with status ${this._state === C.STATUS_EARLY ? 'EARLY' : 'CONFIRMED'}`);
}
get id() {
return this._id;
}
get local_seqnum() {
return this._local_seqnum;
}
set local_seqnum(num) {
this._local_seqnum = num;
}
get owner() {
return this._owner;
}
get uac_pending_reply() {
return this._uac_pending_reply;
}
set uac_pending_reply(pending) {
this._uac_pending_reply = pending;
}
get uas_pending_reply() {
return this._uas_pending_reply;
}
isTerminated() {
return this._status === C.STATUS_TERMINATED;
}
update(message, type) {
this._state = C.STATUS_CONFIRMED;
logger.debug(`dialog ${this._id.toString()} changed to CONFIRMED state`);
if (type === 'UAC') {
// RFC 3261 13.2.2.4.
this._route_set = message.getHeaders('record-route').reverse();
}
}
terminate() {
logger.debug(`dialog ${this._id.toString()} deleted`);
this._ua.destroyDialog(this);
this._state = C.STATUS_TERMINATED;
}
sendRequest(method, options = {}) {
const extraHeaders = Utils.cloneArray(options.extraHeaders);
const eventHandlers = Utils.cloneObject(options.eventHandlers);
const body = options.body || null;
const request = this._createRequest(method, extraHeaders, body);
// Some usages may need to know about authentication.
const onAuthenticated = eventHandlers.onAuthenticated || (() => { });
// Increase the local CSeq on authentication.
eventHandlers.onAuthenticated = _request => {
this._local_seqnum += 1;
// In case of re-INVITE store outgoing ack_seqnum for its CANCEL or ACK.
if (request.method === JsSIP_C.INVITE) {
this._outgoing_ack_seqnum = this._local_seqnum;
}
onAuthenticated(_request);
};
const request_sender = new Dialog_RequestSender(this, request, eventHandlers);
request_sender.send();
// Return the instance of OutgoingRequest.
return request;
}
receiveRequest(request) {
// Check in-dialog request.
if (!this._checkInDialogRequest(request)) {
return;
}
// ACK received. Cleanup this._ack_seqnum.
if (request.method === JsSIP_C.ACK && this._incoming_ack_seqnum !== null) {
this._incoming_ack_seqnum = null;
}
// INVITE received. Set this._ack_seqnum.
else if (request.method === JsSIP_C.INVITE) {
this._incoming_ack_seqnum = request.cseq;
}
this._owner.receiveRequest(request);
}
// RFC 3261 12.2.1.1.
_createRequest(method, extraHeaders, body) {
extraHeaders = Utils.cloneArray(extraHeaders);
if (!this._local_seqnum) {
this._local_seqnum = Math.floor(Math.random() * 10000);
}
// CANCEL and ACK must use the same sequence number as the INVITE.
const cseq = method === JsSIP_C.CANCEL || method === JsSIP_C.ACK
? this._outgoing_ack_seqnum
: (this._local_seqnum += 1);
// In case of re-INVITE store ack_seqnum for future CANCEL or ACK.
if (method === JsSIP_C.INVITE) {
this._outgoing_ack_seqnum = cseq;
}
const request = new SIPMessage.OutgoingRequest(method, this._remote_target, this._ua, {
cseq: cseq,
call_id: this._id.call_id,
from_uri: this._local_uri,
from_tag: this._id.local_tag,
to_uri: this._remote_uri,
to_tag: this._id.remote_tag,
route_set: this._route_set,
}, extraHeaders, body);
return request;
}
// RFC 3261 12.2.2.
_checkInDialogRequest(request) {
if (!this._remote_seqnum) {
this._remote_seqnum = request.cseq;
}
else if (request.cseq < this._remote_seqnum) {
if (request.method === JsSIP_C.ACK) {
// We are not expecting any ACK with lower seqnum than the current one.
// Or this is not the ACK we are waiting for.
if (this._incoming_ack_seqnum === null ||
request.cseq !== this._incoming_ack_seqnum) {
return false;
}
}
else {
request.reply(500);
return false;
}
}
else if (request.cseq > this._remote_seqnum) {
this._remote_seqnum = request.cseq;
}
// RFC3261 14.2 Modifying an Existing Session -UAS BEHAVIOR-.
if (request.method === JsSIP_C.INVITE ||
(request.method === JsSIP_C.UPDATE && request.body)) {
if (this._uac_pending_reply === true) {
request.reply(491);
}
else if (this._uas_pending_reply === true) {
const retryAfter = ((Math.random() * 10) | 0) + 1;
request.reply(500, null, [`Retry-After:${retryAfter}`]);
return false;
}
else {
this._uas_pending_reply = true;
const stateChanged = () => {
if (request.server_transaction.state ===
Transactions.C.STATUS_ACCEPTED ||
request.server_transaction.state ===
Transactions.C.STATUS_COMPLETED ||
request.server_transaction.state ===
Transactions.C.STATUS_TERMINATED) {
request.server_transaction.removeListener('stateChanged', stateChanged);
this._uas_pending_reply = false;
}
};
request.server_transaction.on('stateChanged', stateChanged);
}
// RFC3261 12.2.2 Replace the dialog`s remote target URI if the request is accepted.
if (request.hasHeader('contact')) {
request.server_transaction.on('stateChanged', () => {
if (request.server_transaction.state === Transactions.C.STATUS_ACCEPTED) {
this._remote_target = request.parseHeader('contact').uri;
}
});
}
}
else if (request.method === JsSIP_C.NOTIFY) {
// RFC6665 3.2 Replace the dialog`s remote target URI if the request is accepted.
if (request.hasHeader('contact')) {
request.server_transaction.on('stateChanged', () => {
if (request.server_transaction.state === Transactions.C.STATUS_COMPLETED) {
this._remote_target = request.parseHeader('contact').uri;
}
});
}
}
return true;
}
};