Android SIP API : REGISTER リクエストの有効期間と再送信

# 概要

## SIP REGISTER リクエストの有効期間

REGISTER リクエストには有効期間 (Expires) を指定することができます。

* http://www.softfront.co.jp/tech/ietfdoc/trans/rfc3261j.txt (Page 56 - 60 を参照)

REGISTER の有効期間に関する動作を簡単にまとめると以下のような感じです。

* クライアントは、REGISTER リクエストの Expires に希望する期間を指定して、送信。
* サーバーは、レスポンスにサーバー自身が希望する期間を指定して、送信。サーバーが指定した期間は、クライアントが希望した値かもしれないし、サーバーが希望する別の値に変更しているかもしれない。
* クライアントは、レスポンスに書かれた期間を過ぎた後(または期間に近づいた際に)、 REGISTER リクエストを再送信することが必要。

## Android SIP API の auto registration 機能

Android SIP API には auto registration 機能があり、この機能を有効にした場合は REGISTER リクエストの送信・再送信は android 側が行ってくれます。

auto registration を使用しない場合、[SipManager.register(SipProfile localProfile, int expiryTime, SipRegistrationListener listener)](https://developer.android.com/reference/android/net/sip/SipManager#register(android.net.sip.SipProfile,%20int,%20android.net.sip.SipRegistrationListener))、[SipSession.register(int expiryTime)](https://developer.android.com/reference/android/net/sip/SipSession#register(int)) を使用して REGISTER リクエストの送信を指示します。引数`expiryTime` に期間を指定できます。


## 問題
Android SIP API の auto registration を使用する場合、どのくらいの期間を指定しているのか、サーバーからの応答に指示された期間をどう処理しているのか、わからなかったためコードを調べました。




# コードの調査

以下のサイトを使用しました。ブランチは `android-7.0.0_r35` としました。

* https://cs.android.com/


## 結論

結論を先に書きます。

auto registration 機能を使用する場合には

* REGISTER リクエストには有効期間に 3600 秒を設定して送信している。
* REGISTER リクエストのレスポンスに書かれた有効期間の60秒前に、REGISTER リクエストを再送する。
    * (レスポンスに書かれた有効期間が 3600 秒であれば、3600 - 60 秒後に再送信)
    * レスポンスに有効期間が書かれていない場合は、有効期間を3600秒として動作する。

## SipManager.open からの呼び出し階層

auto registration を使用する場合は、
[SipManager.open(SipProfile localProfile, PendingIntent incomingCallPendingIntent, SipRegistrationListener listener)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/android/net/sip/SipManager.java;l=236) を使用すると思います。呼び出しをたどってみます。

[SipManager.open(SipProfile localProfile, PendingIntent incomingCallPendingIntent, SipRegistrationListener listener)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/android/net/sip/SipManager.java;l=236) 
→ [SipService.open3(SipProfile localProfile, PendingIntent incomingCallPendingIntent, SipRegistrationListener listener, String opPackageName)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=160;drc=android-7.0.0_r35)
→ [SipSessionGroupExt.openToReceiveCalls()](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=532;drc=android-7.0.0_r35)
→ [SipAutoReg.start(SipSessionGroup group)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=817;drc=android-7.0.0_r35)
→ [SipSessionImpl.unregister](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;l=733)
→ [SipSessionImpl.doCommandAsync(final EventObject command)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;l=681)
→ [SipSessionImpl.processCommand(EventObject command)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;l=737)
→ [SipSessionImpl.process(EventObject command)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=762;drc=android-7.0.0_r35)
→ [SipSessionImpl.readyForCall(EventObject evt)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;l=1063)
→ [SipSessionImpl.readyForCall, 1091](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=1091;drc=android-7.0.0_r35)


SipSessionImpl.unregister ...
まずは、既にサーバーに登録されている状態を解除しようとするようです。
(解除は、REGISTER リクエストを使用しますが、Expires に 0 を指定)

SipSessionImpl.readyForCall ... 
SipHelper.sendRegister が REGISTER リクエストを送信しているようです。
mState は SipSession.State.DEREGISTRING になります。


### REGISTER リクエストのレスポンス処理

レスポンスを正常に受信できたと仮定すると、以下の呼び出しになるはずです。


[SipSessionGroup.processResponse(ResponseEvent event)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=341;drc=android-7.0.0_r35)
→ [SipSessionGroup.process(EventObject event)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;l=365)
→ [SipSessionImpl.process(EventObject evt)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;l=762)
→ [SipSessionImpl.registeringToReady(EventObject evt)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;l=947)
→ [SipSessionImpl.onRegistrationDone(int duration)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;l=1475)
→ [SipAutoReg.onRegistrationDone(ISipSession session, int duration)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=1040;drc=android-7.0.0_r35;bpv=0;bpt=0)
→ [SipAutoReg.run()](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=989;drc=android-7.0.0_r35;bpv=0;bpt=0)
→ [SipSessionImpl.register(int duration)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;bpv=0;bpt=0;l=728)

SipSessionImpl.registeringToReady ...
ステータスコードが 200 だった場合は、mState は SipSession.State.REGISTER ではないので、引数 duration に -1 を渡して onRegistrationDone を実行します。


SipSessionImpl.onRegistrationDone ...
mProxy のメソッド呼び出しにより、mProxy にセットされているリスナが呼び出されます。


SipAutoReg.onRegistrationDone ...
引数 duration は -1 のため、run() が実行されます。


SipAutoReg.run ...
引数 duration に 定数 EXPIRE_TIME を渡して register を実行します。


## REGISTER リクエストの送信に関するコード

SipHelper.sendRegister について調べてみます。

### 1
REGISTER リクエストを作成しているのは、[SipHelper.sendRegister(SipProfile userProfile, String tag, int expiry)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipHelper.java;l=211;drc=android-7.0.0_r35)。
222 行 : 引数 `expiry` がリクエストにセットされる。


### 2
`SipHelper.sendRegister` の呼び出し元は、[SipSessionImpl.readyForCall(EventObject evt)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=1084;drc=android-7.0.0_r35) 。
引数`expriy` には、変数`duration` を指定している。
変数`duration` は、`RegisterCommand.getDuration()` の値がセットされている。

### 3
[RegisterCommand.getDuration()](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=1776;drc=android-7.0.0_r35) は、インスタンス時の引数`duration` を返す。
`RegisterCommand` を作成しているのは、[SipSessionGroup.register(int duration)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=728) 。
`SipSessionGroup.register` の引数`duration` を変更せず使用して、`RegisterCommand` をインスタンス化する。


### 4
`SipSessionGroup.register` の呼び出し元はいくつかあるが、auto registration 時は以下の2つ。
 
* [SipAutoReg.onResponse(boolean portChanged)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=902)
* [SipAutoReg.run()](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=989)

どちらも、定数`EXPIRY_TIME`を指定している。[EXPIRY_TIME](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=66;drc=android-7.0.0_r35;bpv=1;bpt=1) は 3600。






## REGISTER リクエストの応答処理に関するコード

REGISTER リクエストに対するレスポンスの処理を調べてみます。

### 1

レスポンスから `Expires` を取得しているのは、[SipSessionImpl.getExpiryTime(Response response)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=924)。
取得できなかった場合は、[EXPRIY_TIME](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=95;drc=android-7.0.0_r35) (3600) を返している。


### 2

`SipSessionImpl.getExpiryTime(Response response)` の呼び出し元は、[SipSessionImpl.registeringToReady(EventObject evt)](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=947;bpv=1;bpt=1)。

`getExpiryTime` で取得した値は、[SipSessionImpl.onRegistrationDone](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;bpv=1;bpt=1;l=1475) に渡す。


### 3

`SipSessionImpl.onRegistrationDone` は `mProxy.onRegistrationDone` を呼ぶ。

`mProxy` は、`setListener` でセットされる。呼び出し元をたどると以下のように `SipAutoReg` クラスがある。
[SipSessionImpl.setListener](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;bpv=1;bpt=1;l=674)
← [new SipSessionImpl](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;drc=android-7.0.0_r35;bpv=1;bpt=1;l=592)
← [SipSessionImpl.createSession](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java;l=269;drc=android-7.0.0_r35;bpv=1;bpt=1)
← [SipAutoReg.start](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=817)


`SipAutoReg.onRegistrationDone` は、`SipAutoReg.restart` に `duration - MIN_EXPIRY_TIME` を渡す。
[MIN_EXPIRY_TIME](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=68;drc=android-7.0.0_r35) は 60 。
(おそらく、期限が切れる60秒前に再送するため)

### 4
 
[SipAutoReg.restart](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=1003;drc=android-7.0.0_r35) はタイマーを実行する。タイマーの期間は `duration * 1000` ミリ秒後。
タイマーにより、[SipAutoReg.run](https://cs.android.com/android/platform/superproject/+/android-7.0.0_r35:frameworks/opt/net/voip/src/java/com/android/server/sip/SipService.java;l=989;drc=android-7.0.0_r35) が実行され、`mSession.register` を実行。
(mSession.register ... REGISTER リクエストの送信指示)


# 参考

* http://www.softfront.co.jp/tech/ietfdoc/trans/rfc3261j.txt (page 56 - 60 : REGISTERリクエストについて)