2006 年 12 月 26 日 23 時 57 分

認証なしのハンドシェイク


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


[写真]


ハンドシェイク処理については整理できたので、
RFBSession に実装コードを追加し、
ハンドシェイクの枠組みを作ってみよう。

========== RFBSession.java ==========

package jp.loafer.rfbdemo;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import jp.loafer.rfb.RFBContext;
import jp.loafer.rfb.io.RFBInputStream;
import jp.loafer.rfb.io.RFBOutputStream;
import jp.loafer.rfb.message.ProtocolVersionMessage;
import jp.loafer.rfb.message.SecurityResultMessage;

/**
* RFB プロトコルの処理。
* @author kes
*/
public class RFBSession {

    /**
     * {@link RFBSession} のインスタンスを作成。
     */
    public RFBSession() {
        // 処理不要
    }
   
    /**
     * RFB プロトコルの開始点。
     * @param in 汎用入力ストリーム。
     * @param out 汎用出力ストリーム。
     * @throws IOException 入出力エラー。
     */
    public void execute(InputStream in, OutputStream out) throws IOException {

        // 基本オブジェクトの用意
        RFBContext context = new RFBContext();
        RFBInputStream rin = new RFBInputStream(in);
        RFBOutputStream rout = new RFBOutputStream(out);

        // ProtocolVersion ハンドシェイク
        processProtocolVersion(context, rin, rout);

        // Security ハンドシェイク
        processSecurity(context, rin, rout);

    }

    private int serverVersion = 3008; // 3.8

    private static final int SECURITY_INVALID = 0;
    private static final int SECURITY_NONE = 1;
    private static final int SECURITY_VNC_AUTHENTICATION = 2;
   
    // ProtocolVersion ハンドシェイク   
    private void processProtocolVersion(RFBContext context,
            RFBInputStream in, RFBOutputStream out) throws IOException {

        // サーバのバージョンを送信
        ProtocolVersionMessage serverVersionMessage
                = new ProtocolVersionMessage(serverVersion);
        out.writeMessage(context, serverVersionMessage);
        out.flush();

        // クライアントのバージョンを受信
        ProtocolVersionMessage clientVersionMessage
                = in.readMessage(context, ProtocolVersionMessage.class);
       
        int version = clientVersionMessage.getVersion();
   
        // バージョンを検証
        try {

            if (version > 3008)
                    throw new IOException("Unexpected version.");

            // 3.5 は 3.3 として扱えと RFB 仕様書に明記してある
            if (version == 3005) version = 3003;

            // 現時点では 3.3, 3.7, 3.8 の 3 種類しかない
            if (version != 3003 && version != 3007 && version != 3008)
                    throw new IOException("Unknown version.");

            // サーバ機能の一時停止テスト
            // if (true) throw new IOException("Service unavailable.");

        } catch (IOException ex) {

            // セキュリティの種類メッセージでエラーを通知

            // 一応クライアントの返したバージョンを尊重して返答する
            if (version < 3007) {
                out.writeU32(SECURITY_INVALID);
            } else {
                out.writeU8(0); // 0 個はエラーを表す
            }
            out.writeString(ex.getMessage());
            out.flush();

            throw ex;
        }

        // バージョン確定
        context.setVersion(version);

    }

    // Security ハンドシェイク   
    private void processSecurity(RFBContext context,
            RFBInputStream in, RFBOutputStream out) throws IOException {

        if (context.getVersion() >= 3007) {
            // 3.7 以降
            out.writeU8(1); // サーバが対応する認証は 1 つ
            out.writeU8(SECURITY_NONE);
            out.flush();

            int type = in.readU8(); // クライアントが選択
            if (type != SECURITY_NONE) // 不正な選択
                    throw new IOException("Unexpected security type.");

        } else {
            // 3.3
            out.writeU32(SECURITY_NONE);
            out.flush();
            // クライアントの返事はなし
        }

        // セキュリティ処理
        processNullSecurity(context, in, out);

    }

    // None セキュリティ処理
    private void processNullSecurity(RFBContext context,
            RFBInputStream in, RFBOutputStream out) throws IOException {

        // 3.8 の場合は SecurityResult で成功を返す
        if (context.getVersion() >= 3008) {

            SecurityResultMessage result
                    = new SecurityResultMessage(
                            SecurityResultMessage.STATUS_OK);
            out.writeMessage(context, result);
            out.flush();
        }

    }

}

========== end of RFBSession.java ==========

一気に書いた。

ハンドシェイクの処理に応じてメソッドを分割し、
メインの execute メソッドではそれらを呼び出すだけだ。

processProtocolVersion では
サーバとクライアントでバージョンの取り決めを行う。
上記サーバのバージョンは 3.8 にハードコードしてあるが、
クライアントが返す値がそれ以下でも対応可能となっている。

エラーが起きた際は、切断してもいいのだが、
一昨々日述べたセキュリティのハンドシェイクを使えば、
エラーを文字列で返すことができるため利用している。

processSecurity では、
セキュリティのハンドシェイクを行う。
ここでは、認証なしのみをサポートすることにした。

一昨々日述べた通り、バージョンによる違いがあるため、
通信バージョンによって分岐して慎重に処理をしている。
RFB 3.7 未満ではクライアントの返答は存在しないが、
それ以降では返答を受信して確認する必要がある。

セキュリティの種類が(認証なしに)確定すると、
processNullSecurity を呼び出して、
種類によって異なるセキュリティ処理を行う。

認証なし (NONE) の場合は、バージョンによって違い、
RFB 3.8 の場合は SecurityResult を使って結果を返すが、
それ未満では一切メッセージが流れることはない。

さて、上記のメソッドは共通のシグネチャにしてある。
戻り値が void で引数がコンテキストと入出力ストリームだ。
これは、今後処理をクラスに分割し、
共通のインタフェースを持たせるための布石である。

それでは、テストしてみよう。

RealVNC で接続してみると、写真のメッセージが出た。
これは、接続が途中で終了したことを示している。
現在ハンドシェイクの部分しか実装していないため、
必然的に出たエラーである。

次に、エラーメッセージがクライアントに届くかどうか、
わざと接続エラーを通知してみよう。
「サーバ機能の一時停止テスト」と書いている部分の
コメントを外し、サービス停止を通知してみる。

RealVNC で接続してみると、
「Service unavailable.」と表示された。
実装には特に問題ないようだ。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.