Specify Caller ID for Each Call with PJSIP Library

Cristea Bogdan Eugen
5 min readJun 4, 2020

Introduction

PJSIP library is an well known open-source library developed by Teluu under GPL or commercial license and implementing the Session Initiation Protocol (SIP) stack. It is written in C and it is available on all major operating systems (e.g. macOS, Linux, Windows, Android, iOS). Its primary use is in VoIP applications based on SIP and Real Time Protocol (RTP). PJSIP library is also one of the SIP stacks used by Asterisk communications framework.

Motivation

One possible application for this library is in implementing softphones. In such cases the caller ID is specified when the softphone registers with the SIP server (e.g. Asterisk, FreeSwitch). When a call is received, the callee will see the caller ID on its softphone interface and can use this information to identify the caller. However, there are cases when an user needs to use multiple caller IDs. PJSIP library supports multiple SIP accounts for registering with one or more SIP servers, but only one account can be used as the default account when calling. In order to change the caller ID, the user must select a different SIP account and register with the corresponding SIP server before making the call. Another option, when only the caller ID needs to be changed, is to force the caller ID when making the call while still using the same SIP account. PJSIP library does not have caller ID support for each call, but it can be modified to have such feature.

Detailed Instructions

In the following I will present detailed instructions for modifying PJSIP library sources in order to add caller ID for each call support. You need to use an Unix-based operating system (Linux is preferred) and have basic knowledge in C programming language and in compiling applications on Unix. I assume that you already have PJSIP library sources (see appendix) and the current folder is the root folder of the source files.

Paste the functions below at line 3197 in file pjsip/src/pjsip-ua/sip_inv.c (for PJSIP library release 2.10), just above pjsip_inv_send_msg() function:

static char *escape_quoted(const char *string, char *outbuf, int buflen)
{
const char *ptr = string;
char *out = outbuf;
char *allow = "\t\v !"; /* allow LWS (minus \r and \n) and "!" */

while (*ptr && out - outbuf < buflen - 1) {
if (!(strchr(allow, *ptr))
&& !(*ptr >= '#' && *ptr <= '[') /* %x23 - %x5b */
&& !(*ptr >= ']' && *ptr <= '~') /* %x5d - %x7e */
&& !((unsigned char) *ptr > 0x7f)) { /* UTF8-nonascii */

if (out - outbuf >= buflen - 2) {
break;
}
out += sprintf(out, "\\%c", (unsigned char) *ptr);
} else {
*out = *ptr;
out++;
}
ptr++;
}

if (buflen) {
*out = '\0';
}

return outbuf;
}
static void modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr, const char *cid)
{
pjsip_name_addr *id_name_addr;
pjsip_sip_uri *id_uri;

id_name_addr = (pjsip_name_addr *) id_hdr->uri;
id_uri = pjsip_uri_get_uri(id_name_addr->uri);

/* if (id->name.valid) { */
int name_buf_len = strlen(cid) * 2 + 1;
char *name_buf = __builtin_alloca(name_buf_len);
escape_quoted(cid, name_buf, name_buf_len);
pj_strdup2(pool, &id_name_addr->display, name_buf);

/* if (id->number.valid) { */
pj_strdup2(pool, &id_uri->user, cid);
}
static void caller_id_outgoing_request(pjsip_tx_data *tdata)
{
static const pj_str_t STR_ASSERTED_ID = { "P-Asserted-Identity", 19 };
static const pj_size_t caller_id_len = 1024;
char caller_id[caller_id_len];

/* get P-Asserted-Identity header */
pjsip_generic_string_hdr *hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &STR_ASSERTED_ID, tdata->msg->hdr.next);
if (NULL != hdr) {
pjsip_fromto_hdr *from = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_FROM, tdata->msg->hdr.next);
if (NULL != from) {
pj_size_t len = pj_strlen(&hdr->hvalue);
if (4 < len) {
/* set CID only if it is not empty. CID should have the format 'sip:<CID>@<domain>' */
int index = 0;
const char *str_buf = pj_strbuf(&hdr->hvalue);
while ((index < (caller_id_len-1)) && ('@' != str_buf[4+index]) && ((4+index) < len)) {
caller_id[index] = str_buf[4+index];
++index;
}
caller_id[index] = '\0';
modify_id_header(tdata->pool, from, caller_id);
}
}
}
}
void handle_outgoing(pjsip_tx_data *tdata)
{
if (tdata->msg->type == PJSIP_REQUEST_MSG) {
caller_id_outgoing_request(tdata);
}
}

Then in pjsip_inv_send_msg() at line 3292, just above "if (tdata->msg->type == PJSIP_REQUEST_MSG) {" add the line below:

 handle_outgoing(tdata);

Open pjsip/src/pjsua-lib/pjsua_call.c file at line 4127, just above pjsua_call_on_state_changed() function and add the following forward declaration:

void handle_outgoing(pjsip_tx_data *tdata);

Go in the same file at line 4295, just above the line containing /* call->inv may be NULL now */ and paste the snippet below:

/* Only change the From header on the initial outbound INVITE. Switching it
* mid-call might confuse some UAs.
*/
if ((inv->state < PJSIP_INV_STATE_CONFIRMED) && (NULL != e) && (PJSIP_EVENT_TX_MSG == e->type)) {
handle_outgoing(e->body.tx_msg.tdata);
}

then go at line 5665 in the same file, at the end of pjsua_call_on_tsx_state_change() function, just above the line containing on_return: and paste the snippet:

if (PJSIP_EVENT_TX_MSG == e->body.tsx_state.type) {
handle_outgoing(e->body.tsx_state.src.tdata);
}

After changing the above 2 files, compile PJSIP library following the instructions in the appendix and you should have caller ID support enabled for each call.

In order to use this new feature you must insert P-Asserted-Identity header with the caller ID when making a call like in the snippet below:

pj_str_t dst_uri;
pj_cstr(&dst_uri, "sip:<callee number>@<domain>");
pjsua_msg_data msg_data;
pjsua_msg_data_init(&msg_data);
pjsip_generic_string_hdr subject;
pj_str_t hname;
pj_cstr(&hname, "P-Asserted-Identity");
pj_str_t hvalue;
pj_cstr(&hvalue, "sip:<caller ID>@<domain>");
pjsip_generic_string_hdr_init2(&subject, &hname, &hvalue);
pj_list_push_back(&msg_data.hdr_list, &subject);
status = pjsua_call_make_call(account_id, dst_uri, NULL, NULL, &msg_data, &call_id);

Conclusion

This article describes how PJSIP library can be modified in order to add caller ID support for each call. By changing only two files, pjsip/src/pjsip-ua/sip_inv.c and pjsip/src/pjsua-lib/pjsua_call.c and by adding several functions definitions and calls one can change the caller ID when making a call. The caller ID is specified in an additional header, P-Asserted-Identity, added when making a new call with pjsua_call_make_call() function. This feature can be useful in softphones or mass dialers when the caller ID needs to be changed without changing the current SIP account.

Appendix: Compile PJSIP Library on Unix-like Platforms

  • download PJSIP library sources from: https://www.pjsip.org/download.htm
  • uncompress the downloaded archive
  • change the current folder to PJSIP library sources folder
  • configure the library for compilation

./configure — enable-shared — enable-epoll — disable-libyuv — disable-libwebrtc — prefix=/usr

In this article I am assuming the PJSIP library is used as shared library ( — enable-shared) with a high number of parallel calls ( — enable-epoll)

  • create config_site.h header files where further customisations can be made

cp pjlib/include/pj/config_site_sample.h pjlib/include/pj/config_site.h

edit the created file and put at the top of the file

#define PJ_CONFIG_MAXIMUM_SPEED

Other options can be selected, but in this case I am choosing to select options that ensure the maximum speed of the library.

  • compile and install the library

make dep

make

sudo make install

Optionally you can compile Python2 bindings (PJSIP library needs to be compiled as shared library)

cd pjsip-apps/src/python

make

sudo make install

Note: if after running 'make' the error below is seen

gcc: error: /usr/lib/rpm/redhat/redhat-hardened-cc1: No such file or directory

you must install 'redhat-rpm-config'

sudo dnf install redhat-rpm-config

--

--

Cristea Bogdan Eugen

I have an PhD degree in Networks and Telecommunications. I am working as Software Engineer and my interests lie in the area of network communication protocols.