UNPKG

jssip

Version:

the Javascript SIP library

806 lines (703 loc) 18.2 kB
const EventEmitter = require('events').EventEmitter; const Logger = require('./Logger'); const JsSIP_C = require('./Constants'); const SIPMessage = require('./SIPMessage'); const Timers = require('./Timers'); const loggernict = new Logger('NonInviteClientTransaction'); const loggerict = new Logger('InviteClientTransaction'); const loggeract = new Logger('AckClientTransaction'); const loggernist = new Logger('NonInviteServerTransaction'); const loggerist = new Logger('InviteServerTransaction'); const C = { // Transaction states. STATUS_TRYING : 1, STATUS_PROCEEDING : 2, STATUS_CALLING : 3, STATUS_ACCEPTED : 4, STATUS_COMPLETED : 5, STATUS_TERMINATED : 6, STATUS_CONFIRMED : 7, // Transaction types. NON_INVITE_CLIENT : 'nict', NON_INVITE_SERVER : 'nist', INVITE_CLIENT : 'ict', INVITE_SERVER : 'ist' }; class NonInviteClientTransaction extends EventEmitter { constructor(ua, transport, request, eventHandlers) { super(); this.type = C.NON_INVITE_CLIENT; this.id = `z9hG4bK${Math.floor(Math.random() * 10000000)}`; this.ua = ua; this.transport = transport; this.request = request; this.eventHandlers = eventHandlers; let via = `SIP/2.0/${transport.via_transport}`; via += ` ${ua.configuration.via_host};branch=${this.id}`; this.request.setHeader('via', via); this.ua.newTransaction(this); } get C() { return C; } stateChanged(state) { this.state = state; this.emit('stateChanged'); } send() { this.stateChanged(C.STATUS_TRYING); this.F = setTimeout(() => { this.timer_F(); }, Timers.TIMER_F); if (!this.transport.send(this.request)) { this.onTransportError(); } } onTransportError() { loggernict.debug(`transport error occurred, deleting transaction ${this.id}`); clearTimeout(this.F); clearTimeout(this.K); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); this.eventHandlers.onTransportError(); } timer_F() { loggernict.debug(`Timer F expired for transaction ${this.id}`); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); this.eventHandlers.onRequestTimeout(); } timer_K() { this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } receiveResponse(response) { const status_code = response.status_code; if (status_code < 200) { switch (this.state) { case C.STATUS_TRYING: case C.STATUS_PROCEEDING: this.stateChanged(C.STATUS_PROCEEDING); this.eventHandlers.onReceiveResponse(response); break; } } else { switch (this.state) { case C.STATUS_TRYING: case C.STATUS_PROCEEDING: this.stateChanged(C.STATUS_COMPLETED); clearTimeout(this.F); if (status_code === 408) { this.eventHandlers.onRequestTimeout(); } else { this.eventHandlers.onReceiveResponse(response); } this.K = setTimeout(() => { this.timer_K(); }, Timers.TIMER_K); break; case C.STATUS_COMPLETED: break; } } } } class InviteClientTransaction extends EventEmitter { constructor(ua, transport, request, eventHandlers) { super(); this.type = C.INVITE_CLIENT; this.id = `z9hG4bK${Math.floor(Math.random() * 10000000)}`; this.ua = ua; this.transport = transport; this.request = request; this.eventHandlers = eventHandlers; request.transaction = this; let via = `SIP/2.0/${transport.via_transport}`; via += ` ${ua.configuration.via_host};branch=${this.id}`; this.request.setHeader('via', via); this.ua.newTransaction(this); } get C() { return C; } stateChanged(state) { this.state = state; this.emit('stateChanged'); } send() { this.stateChanged(C.STATUS_CALLING); this.B = setTimeout(() => { this.timer_B(); }, Timers.TIMER_B); if (!this.transport.send(this.request)) { this.onTransportError(); } } onTransportError() { clearTimeout(this.B); clearTimeout(this.D); clearTimeout(this.M); if (this.state !== C.STATUS_ACCEPTED) { loggerict.debug(`transport error occurred, deleting transaction ${this.id}`); this.eventHandlers.onTransportError(); } this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } // RFC 6026 7.2. timer_M() { loggerict.debug(`Timer M expired for transaction ${this.id}`); if (this.state === C.STATUS_ACCEPTED) { clearTimeout(this.B); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } } // RFC 3261 17.1.1. timer_B() { loggerict.debug(`Timer B expired for transaction ${this.id}`); if (this.state === C.STATUS_CALLING) { this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); this.eventHandlers.onRequestTimeout(); } } timer_D() { loggerict.debug(`Timer D expired for transaction ${this.id}`); clearTimeout(this.B); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } sendACK(response) { const ack = new SIPMessage.OutgoingRequest(JsSIP_C.ACK, this.request.ruri, this.ua, { 'route_set' : this.request.getHeaders('route'), 'call_id' : this.request.getHeader('call-id'), 'cseq' : this.request.cseq }); ack.setHeader('from', this.request.getHeader('from')); ack.setHeader('via', this.request.getHeader('via')); ack.setHeader('to', response.getHeader('to')); this.D = setTimeout(() => { this.timer_D(); }, Timers.TIMER_D); this.transport.send(ack); } cancel(reason) { // Send only if a provisional response (>100) has been received. if (this.state !== C.STATUS_PROCEEDING) { return; } const cancel = new SIPMessage.OutgoingRequest(JsSIP_C.CANCEL, this.request.ruri, this.ua, { 'route_set' : this.request.getHeaders('route'), 'call_id' : this.request.getHeader('call-id'), 'cseq' : this.request.cseq }); cancel.setHeader('from', this.request.getHeader('from')); cancel.setHeader('via', this.request.getHeader('via')); cancel.setHeader('to', this.request.getHeader('to')); if (reason) { cancel.setHeader('reason', reason); } this.transport.send(cancel); } receiveResponse(response) { const status_code = response.status_code; if (status_code >= 100 && status_code <= 199) { switch (this.state) { case C.STATUS_CALLING: this.stateChanged(C.STATUS_PROCEEDING); this.eventHandlers.onReceiveResponse(response); break; case C.STATUS_PROCEEDING: this.eventHandlers.onReceiveResponse(response); break; } } else if (status_code >= 200 && status_code <= 299) { switch (this.state) { case C.STATUS_CALLING: case C.STATUS_PROCEEDING: this.stateChanged(C.STATUS_ACCEPTED); this.M = setTimeout(() => { this.timer_M(); }, Timers.TIMER_M); this.eventHandlers.onReceiveResponse(response); break; case C.STATUS_ACCEPTED: this.eventHandlers.onReceiveResponse(response); break; } } else if (status_code >= 300 && status_code <= 699) { switch (this.state) { case C.STATUS_CALLING: case C.STATUS_PROCEEDING: this.stateChanged(C.STATUS_COMPLETED); this.sendACK(response); this.eventHandlers.onReceiveResponse(response); break; case C.STATUS_COMPLETED: this.sendACK(response); break; } } } } class AckClientTransaction extends EventEmitter { constructor(ua, transport, request, eventHandlers) { super(); this.id = `z9hG4bK${Math.floor(Math.random() * 10000000)}`; this.transport = transport; this.request = request; this.eventHandlers = eventHandlers; let via = `SIP/2.0/${transport.via_transport}`; via += ` ${ua.configuration.via_host};branch=${this.id}`; this.request.setHeader('via', via); } get C() { return C; } send() { if (!this.transport.send(this.request)) { this.onTransportError(); } } onTransportError() { loggeract.debug(`transport error occurred for transaction ${this.id}`); this.eventHandlers.onTransportError(); } } class NonInviteServerTransaction extends EventEmitter { constructor(ua, transport, request) { super(); this.type = C.NON_INVITE_SERVER; this.id = request.via_branch; this.ua = ua; this.transport = transport; this.request = request; this.last_response = ''; request.server_transaction = this; this.state = C.STATUS_TRYING; ua.newTransaction(this); } get C() { return C; } stateChanged(state) { this.state = state; this.emit('stateChanged'); } timer_J() { loggernist.debug(`Timer J expired for transaction ${this.id}`); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } onTransportError() { if (!this.transportError) { this.transportError = true; loggernist.debug(`transport error occurred, deleting transaction ${this.id}`); clearTimeout(this.J); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } } receiveResponse(status_code, response, onSuccess, onFailure) { if (status_code === 100) { /* RFC 4320 4.1 * 'A SIP element MUST NOT * send any provisional response with a * Status-Code other than 100 to a non-INVITE request.' */ switch (this.state) { case C.STATUS_TRYING: this.stateChanged(C.STATUS_PROCEEDING); if (!this.transport.send(response)) { this.onTransportError(); } break; case C.STATUS_PROCEEDING: this.last_response = response; if (!this.transport.send(response)) { this.onTransportError(); if (onFailure) { onFailure(); } } else if (onSuccess) { onSuccess(); } break; } } else if (status_code >= 200 && status_code <= 699) { switch (this.state) { case C.STATUS_TRYING: case C.STATUS_PROCEEDING: this.stateChanged(C.STATUS_COMPLETED); this.last_response = response; this.J = setTimeout(() => { this.timer_J(); }, Timers.TIMER_J); if (!this.transport.send(response)) { this.onTransportError(); if (onFailure) { onFailure(); } } else if (onSuccess) { onSuccess(); } break; case C.STATUS_COMPLETED: break; } } } } class InviteServerTransaction extends EventEmitter { constructor(ua, transport, request) { super(); this.type = C.INVITE_SERVER; this.id = request.via_branch; this.ua = ua; this.transport = transport; this.request = request; this.last_response = ''; request.server_transaction = this; this.state = C.STATUS_PROCEEDING; ua.newTransaction(this); this.resendProvisionalTimer = null; request.reply(100); } get C() { return C; } stateChanged(state) { this.state = state; this.emit('stateChanged'); } timer_H() { loggerist.debug(`Timer H expired for transaction ${this.id}`); if (this.state === C.STATUS_COMPLETED) { loggerist.debug('ACK not received, dialog will be terminated'); } this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } timer_I() { this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } // RFC 6026 7.1. timer_L() { loggerist.debug(`Timer L expired for transaction ${this.id}`); if (this.state === C.STATUS_ACCEPTED) { this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } } onTransportError() { if (!this.transportError) { this.transportError = true; loggerist.debug(`transport error occurred, deleting transaction ${this.id}`); if (this.resendProvisionalTimer !== null) { clearInterval(this.resendProvisionalTimer); this.resendProvisionalTimer = null; } clearTimeout(this.L); clearTimeout(this.H); clearTimeout(this.I); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } } resend_provisional() { if (!this.transport.send(this.last_response)) { this.onTransportError(); } } // INVITE Server Transaction RFC 3261 17.2.1. receiveResponse(status_code, response, onSuccess, onFailure) { if (status_code >= 100 && status_code <= 199) { switch (this.state) { case C.STATUS_PROCEEDING: if (!this.transport.send(response)) { this.onTransportError(); } this.last_response = response; break; } } if (status_code > 100 && status_code <= 199 && this.state === C.STATUS_PROCEEDING) { // Trigger the resendProvisionalTimer only for the first non 100 provisional response. if (this.resendProvisionalTimer === null) { this.resendProvisionalTimer = setInterval(() => { this.resend_provisional(); }, Timers.PROVISIONAL_RESPONSE_INTERVAL); } } else if (status_code >= 200 && status_code <= 299) { switch (this.state) { case C.STATUS_PROCEEDING: this.stateChanged(C.STATUS_ACCEPTED); this.last_response = response; this.L = setTimeout(() => { this.timer_L(); }, Timers.TIMER_L); if (this.resendProvisionalTimer !== null) { clearInterval(this.resendProvisionalTimer); this.resendProvisionalTimer = null; } /* falls through */ case C.STATUS_ACCEPTED: // Note that this point will be reached for proceeding this.state also. if (!this.transport.send(response)) { this.onTransportError(); if (onFailure) { onFailure(); } } else if (onSuccess) { onSuccess(); } break; } } else if (status_code >= 300 && status_code <= 699) { switch (this.state) { case C.STATUS_PROCEEDING: if (this.resendProvisionalTimer !== null) { clearInterval(this.resendProvisionalTimer); this.resendProvisionalTimer = null; } if (!this.transport.send(response)) { this.onTransportError(); if (onFailure) { onFailure(); } } else { this.stateChanged(C.STATUS_COMPLETED); this.H = setTimeout(() => { this.timer_H(); }, Timers.TIMER_H); if (onSuccess) { onSuccess(); } } break; } } } } /** * INVITE: * _true_ if retransmission * _false_ new request * * ACK: * _true_ ACK to non2xx response * _false_ ACK must be passed to TU (accepted state) * ACK to 2xx response * * CANCEL: * _true_ no matching invite transaction * _false_ matching invite transaction and no final response sent * * OTHER: * _true_ retransmission * _false_ new request */ function checkTransaction({ _transactions }, request) { let tr; switch (request.method) { case JsSIP_C.INVITE: tr = _transactions.ist[request.via_branch]; if (tr) { switch (tr.state) { case C.STATUS_PROCEEDING: tr.transport.send(tr.last_response); break; // RFC 6026 7.1 Invite retransmission. // Received while in C.STATUS_ACCEPTED state. Absorb it. case C.STATUS_ACCEPTED: break; } return true; } break; case JsSIP_C.ACK: tr = _transactions.ist[request.via_branch]; // RFC 6026 7.1. if (tr) { if (tr.state === C.STATUS_ACCEPTED) { return false; } else if (tr.state === C.STATUS_COMPLETED) { tr.state = C.STATUS_CONFIRMED; tr.I = setTimeout(() => { tr.timer_I(); }, Timers.TIMER_I); return true; } } // ACK to 2XX Response. else { return false; } break; case JsSIP_C.CANCEL: tr = _transactions.ist[request.via_branch]; if (tr) { request.reply_sl(200); if (tr.state === C.STATUS_PROCEEDING) { return false; } else { return true; } } else { request.reply_sl(481); return true; } default: // Non-INVITE Server Transaction RFC 3261 17.2.2. tr = _transactions.nist[request.via_branch]; if (tr) { switch (tr.state) { case C.STATUS_TRYING: break; case C.STATUS_PROCEEDING: case C.STATUS_COMPLETED: tr.transport.send(tr.last_response); break; } return true; } break; } } module.exports = { C, NonInviteClientTransaction, InviteClientTransaction, AckClientTransaction, NonInviteServerTransaction, InviteServerTransaction, checkTransaction };