メッセージブローカーコントロールAPI

nine の暗号化機能

nine の暗号機能はそのまま動く実装ではなく、多少の開発を要する API 形式で提供している。 これには次のような理由がある。

ネットゲームの場合、通常の通信セキュリティ技術とは異なった脅威へ対処する必要がある。 一般的な通信セキュリティ技術では、第三者からの盗聴や改竄への耐性が求められる。 ネットゲームでは、これに加えて悪意あるユーザーによるクライアント実行環境での盗聴・改竄への耐性も求められる。

技術分野としては、クライアントクラッキング対策は通信暗号化ではなく耐タンパー性(anti-tampering)の技術領域になる。 耐タンパー技術と通信セキュリティは分離し、クライアントチート対策は耐タンパー技術に任せることができれば望ましい。しかし現実的には様々な問題がある。
  • 現状は安価で高性能な耐タンパー技術が無く、ユーザーに負担を強いる
  • 耐タンパー技術をほどこしていない、通信セキュリティ部分を攻撃対象にされる恐れがある
以上の理由から、通信セキュリティは外部実装可能とし、ブラックボックス的な使い方を提供することは避けた。

また、セキュリティ技術は進歩が著しい分野の一つである。 最先端の技術を取りこむことができるという付随的なメリットもある。

メッセージブローカーコントロールAPI

メッセージコントロールAPI は、メッセージ型通信の利用時に低レベルな操作方法を提供するためのものである。 暗号化や圧縮などの送受信データ操作や、ハンドシェイク処理を実現できる。

クラス構成図

Controller

コントロールAPI の中核は Controller インターフェースクラスである。 この具象クラスを実装し、MessageServer::setController() で指定することで、コントロールを実現する。

コントローラにはいくつかのフック関数が定義されている。 これらは nine がメッセージの送受信を行う際の特定の段階で呼び出される。 この引数を介して、間接的なコントロールを行うことができる。

間接的な方法とは別に、直接コントロールする関数も定義されている。 コントロール関数は protected になっており、具象クラスから呼び出すことができる。 フック関数の中でコントロール関数を呼び出すことを想定している。

Controller API による送受信データ操作

送受信データの全体がコントローラAPI に渡されるわけではない。 データは以下の部分に分けて扱われる:
コントローラの関与しない部分
nine が独自に扱う部分。メッセージIDなどが含まれる。
内容はバージョンアップにより変更されるので説明しない。
コントローラが扱うデータ
実データ部分で、コントローラに改変可能部分として渡される部分。
コントローラが扱うデータ
コントローラ用の情報で、実データに埋め込みたくない情報。
Control API では attachment という用語を当てている。

Context

送受信のフック関数には Context オブジェクトが与えられる:
      struct Context {
            enum { SEQ_LEN = 8, };
            uint8_t sequence[SEQ_LEN];
      };
sequence は encode,decode 対象のデータの連番である。 ビッグエンディアンの 8byte整数になっている(メモリアドレスの小さい seqence[0] が一番大きい位になる) 暗号化のナンスに使うことを想定している。

後で説明するように、単一のデータに対しての encode/decode 処理は複数のフック関数を呼び出す。 これら一連のフック呼び出しでは、同一の Context オブジェクトを与えられる。 Context を利用して、フック関数間のデータ受け渡しなどを行うことができる。

直接的なコントロール関数

postControlMessage

任意のメッセージを「コントロールメッセージ」として送信する。 受信したコントロールメッセージは、handleControlMessage() フック関数で渡される。

この関数の中で送信データがバッファに書かれるが、encode 処理も行われる。 encode() 中にpostControlMessage() を呼ぶ場合は、再入りが発生するので注意すること。

enableNormalMessage
コントロールメッセージ以外の通常のメッセージの送受信を許諾を設定する。
例えばこれを用いて、ハンドシェイクが終わるまで通常メッセージの送受信を禁止することを実現できる。
setNineEncodeSecret
nineデフォルトのデータ暗号化で使う秘密情報を設定する。
setNineDecodeSecret
nineデフォルトのデータ復号で使う秘密情報を設定する。

接続フック関数

init()
コントローラの担当する Communicator の接続が確立した時に呼ばれる。初期化処理を実装することを想定している。
具体的には、onCommunicatorConnect(), onCommunicatorAccept() の後になる。

送信フック関数

メッセージを post() した時に、マーシャル処理の中でコントローラの送信フック関数が呼ばれる。 以下の順に、beginEncode() から endEncode() まで順番に呼ばれる。 まとめて encode処理と呼ぶ。

beginEncode()
encode処理を開始する
encode()
encode処理を行う
writeEncodeAttachment
アタッチメントデータを書き出す
endEncode
encode処理を終了する

beginEncode() が呼ばれてから endEncode() から出るまでの間は、アプリケーションで post() されたメッセージが encode に周ってくることはない。

また、beginEncode() が呼ばれてから、endEncode() が呼ばれるまでの間は、直接的なコントロール関数は呼ぶべきでない。コントロール関数を呼ぶ必要があるならば、endEncode() の中で呼ぶこと。

コントロール関数の中でも、postControlMessage() はさらに注意を要する。 postControlMessage() もまた送信処理であり、encode 処理も呼ばれることになる。

何も対処せずに endEncode() の中で postControlMessage() を呼んだ場合、無限ループに陥ることになる。 次のように、無限ループしないように実装すること:
void DhCtrl::endEncode(Context* pCtx)
{
   if (pCtx->sequence[7] == 0) { //every 256 messages
     MyMessage msg;
     sendMessage(&msg);
   }
}

受信フック関数

メッセージ全体のデータグラムを受信して、メッセージオブジェクトへの解析処理の中でコントローラの受信フック関数が呼ばれる。

以下の順に、beginDecode() から endDecode() まで順番に呼ばれる。 まとめて decode処理と呼ぶ。
beginDecode
decode処理を開始する
readDecodeAttachment
decode用にアタッチメントデータを渡す
decode
decode処理を行う
handleControlMessage
解析したメッセージオブジェクトがコントロールメッセージの場合に呼ばれる
endDecode
decode処理を終了する

decode シーケンスは encode に似ている。

beginDecode() の始まりから endDecode() の終了までの間は、他の受信メッセージの decode 処理は走らないようになっている。 beginDecode() から decode() の中では、コントロール関数は呼ぶべきでない。

handleControlMessage() は decode に特有の処理で、解析したメッセージがコントロールメッセージの場合に呼ばれる。 この中では必要に応じてコントロール関数を呼んでよい。 例えばハンドシェイクを完了させるメッセージの場合、enableNormalMessage() を呼ぶことになるだろう。

コントローラでは新たな解析処理を発生させることは無いので、encode のように再入に気を付ける必要はない。