---
# System prepended metadata

title: 111-3 IP Telephony Service Creation
tags: [pjsua, course]

---

:::info
[**Course Info**](https://course.ipv6.club.tw/VoIP/1113/index.html)
:::
:::warning
The practice environment in this note is macOS, so there may be some differences in other environments, such as Windows.
:::
[TOC]
## Lesson 1 - Basic I

### Download & Compile the source code of PJSUA
- [How To - Build PJSUA with Visual Studio](https://course.ipv6.club.tw/VoIP/1113/pjsua.html)
:::warning
Visual Studio 2022 的 Debuger Mode 旁邊的選項預設是 Any CPU，需改成 Win 32。
:::
- [How To - Build PJSUA with GNU](https://hackmd.io/@Phoebe61G/PJSIPonMAC)

### Run the program and talk to partner
```shell=
./pjsua.exe --no-tcp
./pjsua.exe --no-tcp sip:10.22.23.142
```
![](https://hackmd.io/_uploads/HkIvotzK3.png)

## Lesson 2 - Basic II

### Use Wireshark to observe VoIP calls
- Install [Wireshark](https://www.wireshark.org/download.html)
:::info
Basic SIP Call Flows see [RFC 3665](https://www.ietf.org/rfc/rfc3665.html)
:::
- Features
    - Capture all packets sent/received by your NIC.
    - Specify a **capture filter**.
    - Data display can be refined using a **display filter**.
    - **VoIP calls in the captured traffic can be detected.**
:::warning
If you enable **promiscuous mode**, traffic that isn't sent or received by your host will also be captured.
:::

- Click the Setting Button ![](https://hackmd.io/_uploads/Bkk2MqGK3.png =50x)
![](https://hackmd.io/_uploads/HJOobcMKh.png)
    > Unclick "Enable promiscuous mode" option.
    > Select the interface you are going to sniff.
    > Type your capture filter then click start.

#### Filter `icmp`
![](https://hackmd.io/_uploads/H1jKz5zth.png)

#### Filter `udp port 5060`
![](https://hackmd.io/_uploads/r15vgifYn.png)


### Wireshark Telephony
- Telephony related network statistics
    ![](https://hackmd.io/_uploads/B13qBjGth.png)
- SIP Flows Info
    ![](https://hackmd.io/_uploads/B1F3rozFn.png)
- Flow Sequence
    ![](https://hackmd.io/_uploads/SyAnBoMY2.png)

### Register to our own SIP Server
- [How To Registrar](http://course.ipv6.club.tw/VoIP/1113/registrar.html#registrar)

```shell=
./pjsua.exe --id=sip:111321512@sip.ncnu.net --registrar=sip:sip.ncnu.net --no-tcp
```
- Check the traffic in Wireshark
    ![](https://hackmd.io/_uploads/HJsxKjzKn.png)
    > You should observe a pair of SIP messages: **REGISTER** & **200 OK**.

- **un-REGISTER** request
    ![](https://hackmd.io/_uploads/SyeIojGt3.png)
    > You will see "Expires: 0".

### Capture RTP Packets (Media Data)

#### SIP Call Establishment
![](https://hackmd.io/_uploads/HJRP3e7Fn.png =500x)

- Observe Message Body -- the **SDP** part in **INVITE** packet
    ![](https://hackmd.io/_uploads/BJLDPxXtn.png)
    > Find the port number used for transmitting media.
    > In this case, port 4008 is used.
    > Note that port 4009 is used for RTCP.

:::info
How to open multiple Wireshark Windows
```shell=
open -n /Applications/Wireshark.app
```
:::

- Open a new instance of Wireshark and apply a filter to capture the media traffic on that specific port (4008).
    ![](https://hackmd.io/_uploads/rkRy_l7t3.png)
    > Wireshark may not recognize the RTP packets, it only shows UDP in the Protocol column.
    > Click **Analyze** --> **Decode as...** --> click "**+**" to divert specific protocol dissections.
    > ![](https://hackmd.io/_uploads/HyKYOgXK2.png)

- Now you can see that the media traffic is displayed as RTP packets.
![](https://hackmd.io/_uploads/rJl5deXF2.png)

#### Keep Alive
-  PJSUA keeps sending a keep-alive message. This is for NAT traversal.
-  The keep-alive message has a payload of 2 bytes (`\r\n`), so the length (including UDP header) is 10.
-  Apply filter `udp[4:2] > 10` to ignore the keep-alive messages.

## Lesson 3 - Instant Messaging (IM)

### Instant Messaging (IM) Method
> 即時訊息
:::info
[RFC 3428 - SIP Extension for Instant Messaging](https://www.rfc-editor.org/rfc/rfc3428.html)
[RFC 3994 - Indication of Message Composition for Instant Messaging](https://www.rfc-editor.org/rfc/rfc3994.html)
:::

- A service coupled with presence and buddy lists.
- Define an extension method for SIP called MESSAGE.


### Send IM and Observe the SIP Traffic
- PJSUA UI: UA1 type **`i`** then press ENTER, and input the SIP URI of UA2.
- The Message Composing Indication (active **`isComposing`**) will be sent to UA2.
    ![](https://hackmd.io/_uploads/S1lZ6VHt2.png)

- Then UA2 will get the **`is typing..`** prompt in PJSUA.
    ![](https://hackmd.io/_uploads/Sy18CNrt2.png)

- UA2 receives the message from UA1.
    ![](https://hackmd.io/_uploads/S1PvAVSYn.png)

- The content of the message will be placed in the **Message Body**.
    ![](https://hackmd.io/_uploads/H1g3bwrYn.png)
    > On the receiver side, there are two **`Via`** header fields because this message is routed via a SIP proxy server. The value of "Max-Forwards" header fields is decremented from 70 to 69.

## Lesson 4 - IM Code Tracing
### `pjsua_app_cli.c`
> under `pjsip-apps/src/pjsua`
- **cli** means **command line interface**
- 定義了執行 PJSUA 後，使用者輸入的指令以及對應的操作。如：
    - `m`: make call
    - `a`: answer call
    - `h`: hangup call
:::spoiler Different operations and the corresponding ID numbers.
![](https://hackmd.io/_uploads/BkqP-yl5h.png =500x)
:::

- Function `cmd_send_im()`
![](https://hackmd.io/_uploads/BJnrfye9h.png =500x)

- Function `pjsua_im_typing()` & `pjsua_im_send()`
![](https://hackmd.io/_uploads/rk_g4Jgcn.png =500x)

### `pjsua_im.c`
> under `pjsip/src/pjsua-lib/`
- `pjsua_im_typing()`
    - ![](https://hackmd.io/_uploads/BktqByl9h.png)

- `pjsua_im_send()`
    - ![](https://hackmd.io/_uploads/r1EsSJl92.png)

### Debugging
#### Visaul Studio on Windows
- Click the project name `pjsua` --> "Project" at toolbar --> "Property" --> "Debugging" --> "Command Arguments"
    - `--no-tcp --id=sip:111321512@sip.ncnu.net`
- Push **F5** for debugging.
#### VS Code on macOS


## Lesson 5 - SUBSCRIBE/NOTIFY

:::info
[RFC 3265](https://www.rfc-editor.org/rfc/rfc3265.html)
:::
- SUBSCRIBE
    - Request
- NOTIFY
- Event header
    - ID
        - Optional.
        - An **Event** has a corresponding ID.
![](https://hackmd.io/_uploads/SynyldCFh.png)

:::info
[RFC 3856](https://www.rfc-editor.org/rfc/rfc3856.html)
:::

- Why is SIP suited as a presence protocol?
    - It already contain presence information.
- Presence Agent (PA)
    - 
- 4 steps for getting information:
    1. Creates SUBSCRIBE Request
    2. 


- Presence Information Data Format (PIDF)
:::info
[RFC 3863](https://www.rfc-editor.org/rfc/rfc3863.html)
:::

![](https://hackmd.io/_uploads/rkA3HZPYh.png)
> When you start an UA, it shows your online status.

- Type `+b` to add a new buddy first.
    ![](https://hackmd.io/_uploads/S10GUbvY2.png)
- Type `s` to subscribe presence of the buddy you just added.
    ![](https://hackmd.io/_uploads/HyekPZPKh.png)
- Type `T` to set your UA online status to **busy**.
![](https://hackmd.io/_uploads/HJ6HwbvKn.png)

## Lesson 6 - SUBSCRIBE/NOTIFY Code Tracing


### Lab 1: Add a new status called "SLEEP"
#### `ui_change_online_status()`
> in `pjsip-apps/src/pjsua/pjsua_app_legacy.c`
![](https://hackmd.io/_uploads/Hy4xjsPch.png)

- ![](https://hackmd.io/_uploads/SyOMioD53.png)
- ![](https://hackmd.io/_uploads/ByfzooD9h.png)
- ![](https://hackmd.io/_uploads/ByC1bTD5h.png =400x)
- ![](https://hackmd.io/_uploads/Hy2yHhw53.png)

### Lab 2: `<note>` - add some words to the note
> In `pjsip/src/pjsip-simple/rpid.c`
> ![](https://hackmd.io/_uploads/Sy-CNhwq3.png)

- ![](https://hackmd.io/_uploads/SynzrnDch.png)
- ![](https://hackmd.io/_uploads/BkzjxTw53.png)

:::info
Another version:
![](https://hackmd.io/_uploads/rkQAPawcn.png)
:::

### Lab 3: Timestamp - change the time zone to UTC+8
- For Windows system, it in `os_time_win32.c`
    - ![](https://hackmd.io/_uploads/Bkx5t2Dch.png)
- For Unix system (like macOS), it in `os_time_common.c`
    - ![](https://hackmd.io/_uploads/Byb1xaPqh.png)
- ![](https://hackmd.io/_uploads/Skfu-pv9h.png)

### Other Functions
#### `pjsua_evsub_on_state()`
> In `pjsip/src/pjsua-lib/pjsua_pres.c`
![](https://hackmd.io/_uploads/rkaUAsv5n.png)

#### `pjsua_pres_notify()`
> In `pjsip/src/pjsua-lib/pjsua_pres.c`
![](https://hackmd.io/_uploads/B1vGknwq2.png)

#### `ui_add_buddy()` (after typing `+b` command)
![](https://hackmd.io/_uploads/HJkaDnw53.png)

#### `pjsip_pres_create_pidf()` (create PIDF)
![](https://hackmd.io/_uploads/HJaMOnP52.png)

:::spoiler 會議室聊天記錄
1. Solomon: 我覺得祐丞的寫法太複雜了，所以花了點時間去追蹤 `pj_memcpy` 的用法，以及 `pj_str_t` 的結構。 `Pool` 的用法我還沒有時間去 trace. 下一組如果還會用到字串，我建議帶大家追蹤一下 `string.h` 中幾個常用的函式。
2. Solomon: `pj_time_decode()` 要從 `pj_time_val` 轉換為 `pj_parsed_time`, 但你用 `GetLocalTime()` 是直接抓系統時鐘，揚棄 `pj_time_val` 傳來的數值。由於你不確定 `pj_time_decode()` 被呼叫時會不會有時間差，建議還是要採用 `pj_time_val` 的數值來轉換。
:::


## Lesson 7 - PUBLISH
### PUBLISH
:::info
- [RFC 3903 - SIP Extension for Event State Publication](https://datatracker.ietf.org/doc/html/rfc3903)
:::

:::spoiler Outline
* Introduction (section 1)
* Architecture (section 2)
* Operation (section 3~7)
    * PUBLISH Requests
    * PUBLISH Responses
* Entity-tags  in PUBLISH (section 8)
* Event Packages using PUBLISH (section 10)
* Protocol Element Definitions (section 11, 13)
* Security Considerations (section 14)
* Examples (Demo) (section 15)
:::

### Notes
#### Intro
- The publication of presence state by a presence user agent to a presence compositor, which has a tightly coupled relationship with the presence agent.
- Event Publication Agent (EPA): The User Agent Client (UAC) that issues PUBLISH requests to publish event state.
- Event State Compositor (ESC): The User Agent Server (UAS) that processes PUBLISH requests, and is responsible for compositing event state into a complete, composite event state of a resource.
    - Presence Compositor: A type of Event State Compositor that is responsible for compositing presence state for a presentity.
- Event Hard State: The steady-state or default event state of a resource.
- Event Soft State: Event state published by an EPA using the PUBLISH mechanism. 
    - An entity-tag is used to identify a specific soft state entity at the ESC.
    - Has a defined lifetime and will expire after a negotiated amount of time.

#### Operation
- PUBLISH is similar to REGISTER in that it allows a user to create, modify, and remove state in another entity which manages this state on behalf of the user.
- Addressing a PUBLISH request is identical to addressing a SUBSCRIBE request.
- The Request-URI of a PUBLISH request is populated with the address of the resource for which the user wishes to publish event state.
- In addition to a particular resource, all published event state is associated with a specific event package.  Through a subscription to that event package, the user is able to discover the composite event state of all of the active publications.
- The ESC assigns an identifier to the publication in the form of an entity-tag.
    - This identifier is then used by the EPA in any subsequent PUBLISH request that modifies, refreshes or removes the event state of that publication.

#### Entity-tags
- The syntax for entity-tags is a token instead of quoted-string.
- A PUBLISH precondition can only apply to a single entity-tag, so request preconditions with multiple entity-tags are not allowed.
- In PUBLISH ESCs are required to always return an entity-tag for a successful publication.
:::info
[HTTP ETag](https://en.wikipedia.org/wiki/HTTP_ETag)

:::


### Presentation References
:::info
- [Tutorialspoint - SIP Messaging](https://www.tutorialspoint.com/session_initiation_protocol/session_initiation_protocol_messaging.htm)
- [Andrew Proko - A DEEP DIVE INTO THE SIP PUBLISH METHOD](https://andrewjprokop.wordpress.com/2015/03/17/a-deep-dive-into-the-sip-publish-method/)
- [PJSIP Documentations - SIP Event State Publication](https://www.pjsip.org/pjsip/docs/html/group__PJSIP__SIMPLE__PUBLISH.htm)
- []()
:::

## Lesson 8 - PUBLISH Code Tracing

## Lesson 9 - STUN
### Lab
#### Observe STUN in PJSUA
> Observe Message Cookie & Transaction ID

![](https://hackmd.io/_uploads/B1jgklZoh.png)

#### XOR-d IP/Port with Message Cookie
> 拿 XOR-d IP / Port 跟 Message Cookie XOR 後，得到 MAPPED-ADDR

![](https://hackmd.io/_uploads/B1PyglWj2.png)

#### Verify FINGERPRINT
- WireShark **display filter: stun**, observe Google Meet traffics.
- Copy the **"Session Traversal Utilities for NAT"** part in STUN response.
    - 右鍵 -> Copy -> As a **Hex Stream** 
    - 刪掉 FINGERPRINT attribute header 
- Copy to the [CRC-32 calculator](http://sunshine2k.de/coding/javascript/crc/crc_js.html)
    - Paste the hex stream and get the CRC-32 value.
    - XOR the value with `0x5354554e`
    
- Back to the WireShark, the result will same as the **CRC-32 field** in FINGERPRINT.
![](https://hackmd.io/_uploads/Syh-Kl-i2.png)

#### Observe initial REGISTER request with and without STUN
> Observe the **Sent-by Address** field.
:::info
PJSUA-LIB has the capability to detect the (SIP) IP address change based on the response of REGISTER request and automatically update the registration with the correct IP if it detects that the IP/port seen by the server is different than the address specified in the Contact URI.
:::
- Without STUN
![](https://hackmd.io/_uploads/S1k3seZin.png)
- After appling STUN
![](https://hackmd.io/_uploads/BJKJneZs3.png)


## Lesson - STUN Trace Code

### Magic Cookie
> After modifying the value of PJ_STUN_MAGIC, it can still get MAPPED-ADDRESS from the STUN server, but Wireshark will determine that this is a "CLASSIC STUN" packet.
> 

```shell=
--ipv6 --bound-addr [自己的IPv6 address:port] --no-tcp
```

:::spoiler 對話紀錄
ID ok.  我把 Transaction ID設為 0x0102, Wireshark 抓到的是 0201. 看來是 Little Endian 和 Big Endian 的問題。

SOFTWARE ok.   STUN Request 中不帶 SOFTWARE attribute 了, 但 Response 中還是有。
我猜不少人看到 Response 有，就以為沒改成功。
:::

## Lesson - ICE Trace Code

```shell=
pjsua.exe --id=sip:111321512@sip.ncnu.net --registrar=sip:sip.ncnu.net --no-tcp --publish --reg-timeout=3600 --stun-srv=stun.sip.us:3478
```

:::spoiler 對話紀錄
execute OK.
但我的做法不太一樣。通常我會避免大規模剪貼，所以我只有把 pjsua\main.c 的 L.21 改成 #define THIS_FILE       "icedemo.c" 
再去 L.157 把原本的 int main() 註解掉，
然後加一行 #include "../samples/icedemo.c"
這樣就不必剪貼了。

exchange OK
我剪貼完後，現在雙方 show 都有顯示 Remote candidate.
不過我這次沒啟用 STUN, 所以都只有顯示 host local address.

我 STUN 顯示
  XOR-MAPPED-ADDRESS: length=8, IPv6 addr=[2112:a442:201:403:605:807:a09:100]:21098
  MAPPED-ADDRESS: length=8, IPv4 addr=163.22.18.99:21098
看起來是之前 attr->sockaddr.addr.sa_family = pj_AF_INET6() 那個實驗沒清掉，造成干擾。直接去 config_site.h 裡面把上次加的那行 #define PJ_HAS_IPV6 1 刪掉，似乎就可以解決 IPv6 的問題了。

TURN OK
若遇到執行錯誤 Assertion failed: afingerprint == ((void *)0), file C:\Waste\pjproject-2.13\pjnath\src\pjnath\stun_msg.c, line 2661

It is because in stun_session.c,
We Comment out L.330 and L.334.  Force it to always add FINGERPRINT
Now restore these two lines so that FINGERPRINT is not added.
TURN address can be received successfully.
:::


## 
### QA
- 假設雙方通話時所使用的 codec priority 不相同，會如何決定使用的 codec?
    - 以 caller 為主。
- 假設雙方通話時所使用的 iLBC frame length 不相同，是否可以正常通話?
    - 不能，須調整至相同 frame length 才可以進行通話。

## Lesson 15 - SRTP (RFC 3711)


# Demo

```bash=
pjsua.exe --id=sip:111321512@sip2.ncnu.net --registrar=sip:sip2.ncnu.net --no-tcp --reg-timeout=3600 --use-srtp=2 --srtp-secure=0 --add-codec=ilbc/8000
```

sip:[2001:e10:6840:72:4016:1f90:e30e:6b2d]
sip:[2001:e10:6840:72:8888:63bf:76bf:92fb]
## Enable IPv6
- pjlib/config_site.h
```c=
#include <pj/config_site_sample.h>
#define PJ_HAS_IPV6 1
```

- pjsua/pjsua_app.c
```c=
//pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP;
pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP6;
```

- pjmedia/transport_udp.c
```c=
//return pjmedia_transport_udp_create3(endpt, pj_AF_INET(), name, addr, port, options, p_tp);
return pjmedia_transport_udp_create3(endpt, pj_AF_INET6(), name, addr, port, options, p_tp);
```

:::warning
- pjsua/pjsua.h
```c=
#define DISABLED_FOR_TICKET_1185        0
```
:::

```c=
// Phoebe modify.
    char* secretmsg = (char*)malloc(32);
    printf("Input the secret msg: ");
    scanf("%s", secretmsg);
    char* buf = (char*)malloc(32);
    char* tmp_buf = buf;
    for (int i = 0; i < strlen(secretmsg); i++) {
        char ch = secretmsg[i];
        int ascii = (int)ch;
        sprintf(tmp_buf,"%x", ascii);
        tmp_buf = tmp_buf + 2;
        //printf("%x ", ascii);
    }
    //printf("\n");
    pj_str_t stego;
    stego.ptr = buf;
    stego.slen = strlen(buf);
```

```c=
for (int i = 0; i < callID.slen; i = i + 2) {
        char* buf = (char*)malloc(3);
        memcpy(buf, callID.ptr[i], 2);
        buf[3] = '\0';
        char* ch;
        int hex = strtol(buf, &ch, 16);
        printf("%s", strtol(buf, &ch, 16));
    }
```