# Packet Manipulator

## 概要

Packet Manipulator は Diarkis が扱う通信パケットに対して特定の操作を適用する仕組みです。\
Diarkis Runtime 内に設定された Apply Point と呼ばれる処理の適用タイミングに対して Packet Filter を設定することでパケットに対する操作を適用します。 パケットに対する操作は C++ のコードとして実装することができ、Apply Point 毎に異なる通信パケットに関する情報を参照し、パケットを無視したり、通信量を計算したり、パケットを保存したり、特定のパケットに対してカスタムな処理をさせたりすることが可能です。 また、一般的に利用されることが想定されるパケットに遅延やパケロスを発生させる Packet Filter はプリセットとして提供されており、Packet Filter 自体のコードを書かなくても利用できるようになっています。\
なお、Packet Manipulator はデバッグ用の機能となりますので Release ビルドでは使用することはできません。

**注意点**

* **Packet Manipulator はデバッグ用の機能となります。**\
  **リリースビルドなど、DIARKIS\_DEBUG\_FEATURES が定義されていない環境では事故防止のためにインターフェイスが見えなくなりますのでご注意ください。**
* **本機能は** **experimental feature となります。**\
  **将来仕様や動作が変更される可能性がある点をご了承ください。**&#x20;

## Namespace

Packet Manipulator の全機能は `Diarkis::System::PacketManipulator` namespace 内に存在します。 以降、本ドキュメントで使用するクラス名等では namespace は省略して記載しますのでご留意ください。

## Packet Manipulator

Packet Manipulator の機能にアクセスするには `IPacketManipulator` のインスタンスを使用します。 `IPacketManipulator` のインスタンスは `DiarkisGetPacketManipulator` を呼び出して取得することができます。

### Packet Manipulator の更新処理

`IPacketManipulator` には Packet Filter の状態を含む内部状態を更新する `IPacketManipulator::Update` があります。 このメソッドの呼び出し頻度が Packet Filter 毎の更新頻度につながるため Packet Filter の処理が必要としている頻度で実行する必要があります。\
例えばプリセットのパケット遅延フィルタでは実際にパケットを受け取ったタイミングから、遅延してパケットを処理するため時間を計測しています。 この計測処理が Packet Filter の更新処理内で実行されているため、`IPacketManipulator::Update` が 100 ms 間隔で実行されていると遅延させる時間の計測も 100 ms 間隔となり想定している遅延時間よりも大きくずれる可能性があります。 Packet Manipulator の機能に必要な更新頻度を考慮して `IPacketManipulator::Update` を実行するようにしてください。

## Apply Point

Packet Manipulator では Packet Filter を適用するタイミングを **Apply Point** と呼びます。Apply Point へ Packet Filter を設定することでパケットに対して特定の処理が反映されるようになります。\
Apply Point には以下の種類が存在します。

| 名称                         | 適用タイミング                      | 説明                                           |
| -------------------------- | ---------------------------- | -------------------------------------------- |
| RawUdpReceive              | UDP ソケットでパケット受信時             | P2P 含めて Diarkis が使用するすべての UDP パケットに対して適用されます |
| RawUdpSend                 | UDP ソケットでパケット送信時             | P2P 含めて Diarkis が使用するすべての UDP パケットに対して適用されます |
| RoomPushAndResponseReceive | Room の Push/Response パケット受信時 | Room の Push/Response として受信したパケットにのみ適用されます    |
| P2PMessageReceive          | P2P パケット受信時                  | P2P メッセージとして受信したパケットにのみ適用されます                |

## Packet Filter Set

Packet Manipulator ではひとつの Apply Point に対して複数の Packet Filter を適用する仕組みとして Packet Filter Set が用意されています。 Packet Filter Set は Apply Point に関連付けられていて、`IPacketManipulator::GetOrAllocFilterSet(FilterApplyPoint applyPoint)` を使用することで Apply Point に対する `IPacketFilterSet` を取得することができます。 パケットに対して何か処理を適用するにはこの `IPacketFilterSet` に対して Packet Filter を追加していくことになります。

## Packet Filter の追加

Packet Filter を追加するには前述の `IPacketFilterSet` を使用します。`IPacketFilterSet` にはプリセットの Packet Filter を追加する `IPacketFilterSet::AddPacketDelayFilter` のようなメソッドと任意の型の Packet Filter を追加する `IPacketFilterSet::AddFilter` メソッドが存在します。

### Packet Filter と Apply Point の互換性

Packet Filter には Apply Point との互換性が存在します。これはある Packet Filter の機能が特定の Apply Point で機能するかどうかという設定で Packet Filter 側の実装状況や Apply Point での対応状況によります。 互換性がない Apply Point に Packet Filter を設定した場合、`IPacketFilterSet::Add...Filter` 系のメソッドが nullptr を返しますので返り値は必ずチェックするようにしてください。 Apply Point と Packet Filter の互換性には `IPacketFilter::CheckIfAvailableApplyPoint`が使用されており、Custom Filter を実装する際はこのメソッドで判定を行うことで互換性の設定を行うことができます。

## Preset Packet Filters

Packet Manipulator にはプリセットでいくつかの Packet Filter が用意されています。\
[Packet Manipulator Simple サンプル ](https://help.diarkis.io/diarkis-client/samples/cpp/packet_manipulator_simple)に使用した実装例がありますので参照してください。

### Packet Delay Filter

`IPacketFilterSet::AddPacketDelayFilter` で追加することができます。

| 引数          | 説明          |
| ----------- | ----------- |
| probability | パケット遅延の発生確率 |
| minDelay    | 最低遅延時間(ms)  |
| maxDelay    | 最高遅延時間(ms)  |
| seed        | 疑似乱数のシード    |

作成時に指定された確率である一定範囲のパケット遅延が発生するようになります。\
確率判定には疑似乱数が使用されているためフィルタが適用されるパケットや遅延時間はある程度同じになるようになっていますが、最終的には実行時にパケットが到達した順番に依存して変わる可能性があります。

### Packet Loss Filter

`IPacketFilterSet::AddPacketLossFilter` で追加することができます。

| 引数          | 説明          |
| ----------- | ----------- |
| probability | パケットロスの発生確率 |
| seed        | 疑似乱数のシード    |

作成時に指定された確率である一定範囲のパケット遅延が発生するようになります。\
確率判定には疑似乱数が使用されているためフィルタが適用されるパケットはある程度同じになるようになっていますが、最終的には実行時にパケットが到達した順番に依存して変わる可能性があります。

## User Custom Filter

ユーザーが独自の処理をパケットに適用可能な Custom Filter を実装することができます。\
[Packet Manipulator Custom Filter サンプル](https://help.diarkis.io/diarkis-client/samples/cpp/packet_manipulator_custom_filter) に使用した実装例がありますので参照してください。

Custom Filter を実装するには `IPacketFilter` とそのサブクラスを継承します。`IPacketFilter` が一番低レベルな Packet Filter インターフェイスを持っているクラスとなり、Packet Manipulator から渡されるフィルタ適用メソッドの引数の型は `IPacketFilterArgument` となります。 しかし、`IPacketFilterArgument` では低レベルなパケットの情報にしかアクセスできないため、実際は各 Apply Point に応じた Filter Base を継承してカスタムフィルタを実装することをおすすめします。 例えば `P2PMessageReceive` Apply Point で利用可能な `IP2PMessageReceiveFilterBase` を継承してカスタムフィルタを実装すると、`IP2PMessageReceiveFilterArgument` 型でパケットに関する情報を取得することができます。`IP2PMessageReceiveFilterArgument` では送信元の UID や IP アドレス、ポートなど `IPacketFilterArgument` ではアクセスできない P2P 固有の情報にアクセスすることが出来るようになります。

### フィルタ適用メソッド

`IPacketFilter` とそのサブクラスには `Apply` メソッドが存在し、Packet Filter 適用のタイミングでパケット情報を渡して実行されます。 ユーザーはこのメソッド内でフィルタ適用時に任意の処理を実装することができます。\
また、`Apply` メソッドの返り値でその後のパケットの扱いをコントロールすることができます。`FilterPostProcess::Skip` を返すことでその後、Diarkis Runtime 内ではそのパケットが無かったものとして扱われます。`FilterPostProcess::ApplyNextFilter` を返すと通常通りの処理を継続します。

### 更新メソッド

`IPacketFilter` とそのサブクラスには `Update` メソッド存在し、Packet Filter に対して定期的に実行したい処理を実装することができます。 `Update` メソッドには引数として、設定されている Apply Point で Diarkis Runtime がパケットを処理するための関数が渡されます。この関数に必要な情報を渡すことで、パケット受信時以外のタイミングでパケットを処理することができます。 このメソッドは `IPacketManipulator::Update` から呼び出されます。

### Apply Point 互換性判定メソッド

`IPacketFilter` とそのサブクラスには `CheckIfAvailableApplyPoint` メソッドが存在し、Packet Manipulator はこのメソッドを使用して Apply Point と Packet Filter に互換性があるかどうか判断します。

### Custom Filter 実装時の注意点

Custom Filter の `Apply` メソッドは Apply Point によってさまざまなスレッドから実行される可能性があります。Custom Filter 内で扱うデータの排他制御には十分ご注意ください。
