sip.js
Version:
A SIP library for JavaScript
127 lines (126 loc) • 6.53 kB
JavaScript
import { getReasonPhrase, newTag, utf8Length } from "./utils.js";
/**
* When a UAS wishes to construct a response to a request, it follows
* the general procedures detailed in the following subsections.
* Additional behaviors specific to the response code in question, which
* are not detailed in this section, may also be required.
* https://tools.ietf.org/html/rfc3261#section-8.2.6
* @internal
*/
export function constructOutgoingResponse(message, options) {
const CRLF = "\r\n";
if (options.statusCode < 100 || options.statusCode > 699) {
throw new TypeError("Invalid statusCode: " + options.statusCode);
}
const reasonPhrase = options.reasonPhrase ? options.reasonPhrase : getReasonPhrase(options.statusCode);
// SIP responses are distinguished from requests by having a Status-Line
// as their start-line. A Status-Line consists of the protocol version
// followed by a numeric Status-Code and its associated textual phrase,
// with each element separated by a single SP character.
// https://tools.ietf.org/html/rfc3261#section-7.2
let response = "SIP/2.0 " + options.statusCode + " " + reasonPhrase + CRLF;
// One largely non-method-specific guideline for the generation of
// responses is that UASs SHOULD NOT issue a provisional response for a
// non-INVITE request. Rather, UASs SHOULD generate a final response to
// a non-INVITE request as soon as possible.
// https://tools.ietf.org/html/rfc3261#section-8.2.6.1
if (options.statusCode >= 100 && options.statusCode < 200) {
// TODO
}
// When a 100 (Trying) response is generated, any Timestamp header field
// present in the request MUST be copied into this 100 (Trying)
// response. If there is a delay in generating the response, the UAS
// SHOULD add a delay value into the Timestamp value in the response.
// This value MUST contain the difference between the time of sending of
// the response and receipt of the request, measured in seconds.
// https://tools.ietf.org/html/rfc3261#section-8.2.6.1
if (options.statusCode === 100) {
// TODO
}
// The From field of the response MUST equal the From header field of
// the request. The Call-ID header field of the response MUST equal the
// Call-ID header field of the request. The CSeq header field of the
// response MUST equal the CSeq field of the request. The Via header
// field values in the response MUST equal the Via header field values
// in the request and MUST maintain the same ordering.
// https://tools.ietf.org/html/rfc3261#section-8.2.6.2
const fromHeader = "From: " + message.getHeader("From") + CRLF;
const callIdHeader = "Call-ID: " + message.callId + CRLF;
const cSeqHeader = "CSeq: " + message.cseq + " " + message.method + CRLF;
const viaHeaders = message.getHeaders("via").reduce((previous, current) => {
return previous + "Via: " + current + CRLF;
}, "");
// If a request contained a To tag in the request, the To header field
// in the response MUST equal that of the request. However, if the To
// header field in the request did not contain a tag, the URI in the To
// header field in the response MUST equal the URI in the To header
// field; additionally, the UAS MUST add a tag to the To header field in
// the response (with the exception of the 100 (Trying) response, in
// which a tag MAY be present). This serves to identify the UAS that is
// responding, possibly resulting in a component of a dialog ID. The
// same tag MUST be used for all responses to that request, both final
// and provisional (again excepting the 100 (Trying)).
// https://tools.ietf.org/html/rfc3261#section-8.2.6.2
let toHeader = "To: " + message.getHeader("to");
if (options.statusCode > 100 && !message.parseHeader("to").hasParam("tag")) {
let toTag = options.toTag;
if (!toTag) {
// Stateless UAS Behavior...
// o To header tags MUST be generated for responses in a stateless
// manner - in a manner that will generate the same tag for the
// same request consistently. For information on tag construction
// see Section 19.3.
// https://tools.ietf.org/html/rfc3261#section-8.2.7
toTag = newTag(); // FIXME: newTag() currently generates random tags
}
toHeader += ";tag=" + toTag;
}
toHeader += CRLF;
// FIXME: TODO: needs review... moved to InviteUserAgentServer (as it is specific to that)
// let recordRouteHeaders = "";
// if (request.method === C.INVITE && statusCode > 100 && statusCode <= 200) {
// recordRouteHeaders = request.getHeaders("record-route").reduce((previous, current) => {
// return previous + "Record-Route: " + current + CRLF;
// }, "");
// }
// FIXME: TODO: needs review...
let supportedHeader = "";
if (options.supported) {
supportedHeader = "Supported: " + options.supported.join(", ") + CRLF;
}
// FIXME: TODO: needs review...
let userAgentHeader = "";
if (options.userAgent) {
userAgentHeader = "User-Agent: " + options.userAgent + CRLF;
}
let extensionHeaders = "";
if (options.extraHeaders) {
extensionHeaders = options.extraHeaders.reduce((previous, current) => {
return previous + current.trim() + CRLF;
}, "");
}
// The relative order of header fields with different field names is not
// significant. However, it is RECOMMENDED that header fields which are
// needed for proxy processing (Via, Route, Record-Route, Proxy-Require,
// Max-Forwards, and Proxy-Authorization, for example) appear towards
// the top of the message to facilitate rapid parsing.
// https://tools.ietf.org/html/rfc3261#section-7.3.1
// response += recordRouteHeaders;
response += viaHeaders;
response += fromHeader;
response += toHeader;
response += cSeqHeader;
response += callIdHeader;
response += supportedHeader;
response += userAgentHeader;
response += extensionHeaders;
if (options.body) {
response += "Content-Type: " + options.body.contentType + CRLF;
response += "Content-Length: " + utf8Length(options.body.content) + CRLF + CRLF;
response += options.body.content;
}
else {
response += "Content-Length: " + 0 + CRLF + CRLF;
}
return { message: response };
}