/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 *
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.service.websocket.tomcat;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jp.ossc.nimbus.beans.ServiceNameEditor;
import jp.ossc.nimbus.core.ServiceManagerFactory;
import jp.ossc.nimbus.core.ServiceName;
import jp.ossc.nimbus.core.ServiceNotFoundException;
import jp.ossc.nimbus.util.crypt.CryptParameters;
import jp.ossc.nimbus.util.crypt.FalsifiedParameterException;
import jp.ossc.nimbus.util.crypt.OverLimitExpiresException;

import org.apache.catalina.websocket.StreamInbound;

/**
 * {@link WebSocketFactory}サービスを使って、StreamInboundを生成するWebSocketServlet。<p>
 * 許可のないWebSocket接続を行わせないために、接続認証を行う機能も持つ。<br>
 * 接続認証では、HTTPリクエストヘッダまたはリクエストクエリで、初期ベクタ、暗号化パラメータ、ハッシュ値を受け取り、{@link CryptParameters}を使って、暗号化パラメータを復号して、共通鍵を取り出し、自身の知る共通鍵と一致するかで、認証を行う。<br>
 * 初期化パラメータ一覧<br>
 * <table border="1">
 *   <tr bgcolor="#CCCCFF">
 *     <th>key</th>
 *     <th>説明</th>
 *     <th>必須</th>
 *     <th>デフォルト</th>
 *   </tr>
 *   <tr>
 *     <td>WebSocketFactoryServiceName</td>
 *     <td>{@link WebSocketFactory}サービスのサービス名</td>
 *     <td align="center">◯</td>
 *     <td>必須設定</td>
 *   </tr>
 *   <tr>
 *     <td>Origin</td>
 *     <td>HTTPヘッダOriginの値</td>
 *     <td align="center"></td>
 *     <td>未設定（HTTPヘッダOriginは検証しない）</td>
 *   </tr>
 *   <tr>
 *     <td>Authenticate</td>
 *     <td>接続認証フラグ（true,false）</td>
 *     <td align="center"></td>
 *     <td>未設定（接続認証しない）</td>
 *   </tr>
 *   <tr>
 *     <td>CryptKey</td>
 *     <td>接続認証で、暗号パラメータを復号する際の暗号鍵</td>
 *     <td align="center">△</td>
 *     <td>接続認証を行う場合は、必須設定</td>
 *   </tr>
 *   <tr>
 *     <td>CryptAlgorithm</td>
 *     <td>接続認証で、暗号パラメータを復号する際の暗号アルゴリズム</td>
 *     <td align="center">△</td>
 *     <td>{@link CryptParameters#DEFAULT_SECRET_KEY_ALGORITHM}</td>
 *   </tr>
 *   <tr>
 *     <td>CryptTransformation</td>
 *     <td>接続認証で、暗号パラメータを復号する際の暗号変換方式</td>
 *     <td align="center">△</td>
 *     <td>{@link CryptParameters#DEFAULT_TRANSFORMATION}</td>
 *   </tr>
 *   <tr>
 *     <td>IVLength</td>
 *     <td>接続認証で、暗号パラメータを復号する際の初期ベクタ長</td>
 *     <td align="center">△</td>
 *     <td>{@link CryptParameters#DEFAULT_INITIAL_VECTOR_LENGTH}</td>
 *   </tr>
 *   <tr>
 *     <td>CryptProvider</td>
 *     <td>接続認証で、暗号パラメータを復号する際の暗号方式プロバイダ</td>
 *     <td align="center"></td>
 *     <td>未設定</td>
 *   </tr>
 *   <tr>
 *     <td>HashKey</td>
 *     <td>接続認証で、改竄防止のハッシュ値を生成する際のハッシュ共通鍵</td>
 *     <td align="center">△</td>
 *     <td>接続認証を行う場合で改竄防止機能を使用する場合は、必須設定</td>
 *   </tr>
 *   <tr>
 *     <td>HashAlgorithm</td>
 *     <td>接続認証で、改竄防止のハッシュ値を生成する際のハッシュアルゴリズム</td>
 *     <td align="center">△</td>
 *     <td>{@link CryptParameters#DEFAULT_HASH_ALGORITHM}</td>
 *   </tr>
 *   <tr>
 *     <td>HashProvider</td>
 *     <td>接続認証で、改竄防止のハッシュ値を生成する際のハッシュ方式プロバイダ</td>
 *     <td align="center"></td>
 *     <td>未設定</td>
 *   </tr>
 *   <tr>
 *     <td>CryptExpires</td>
 *     <td>接続認証で、暗号パラメータを復号する際の暗号パラメータ自体の有効時間[ms]</td>
 *     <td align="center"></td>
 *     <td>-1（有効時間チェックを行わない）</td>
 *   </tr>
 *   <tr>
 *     <td>CryptParameterName</td>
 *     <td>接続認証で、暗号パラメータをリクエストクエリから取得する場合のパラメータ名</td>
 *     <td align="center"></td>
 *     <td>{@link #DEFAULT_PARAMETER_NAME_CRYPT}</td>
 *   </tr>
 *   <tr>
 *     <td>IVParameterName</td>
 *     <td>接続認証で、初期ベクタをリクエストクエリから取得する場合のパラメータ名</td>
 *     <td align="center"></td>
 *     <td>{@link #DEFAULT_PARAMETER_NAME_IV}</td>
 *   </tr>
 *   <tr>
 *     <td>HashParameterName</td>
 *     <td>接続認証で、ハッシュ値をリクエストクエリから取得する場合のパラメータ名</td>
 *     <td align="center"></td>
 *     <td>{@link #DEFAULT_PARAMETER_NAME_HASH}</td>
 *   </tr>
 *   <tr>
 *     <td>CryptHttpHeaderName</td>
 *     <td>接続認証で、暗号パラメータをHTTPリクエストヘッダから取得する場合のヘッダ名</td>
 *     <td align="center"></td>
 *     <td>{@link #DEFAULT_HTTP_HEADER_NAME_CRYPT}</td>
 *   </tr>
 *   <tr>
 *     <td>IVHttpHeaderName</td>
 *     <td>接続認証で、初期ベクタをHTTPリクエストヘッダから取得する場合のヘッダ名</td>
 *     <td align="center"></td>
 *     <td>{@link #DEFAULT_HTTP_HEADER_NAME_IV}</td>
 *   </tr>
 *   <tr>
 *     <td>HashHttpHeaderName</td>
 *     <td>接続認証で、ハッシュ値をHTTPリクエストヘッダから取得する場合のヘッダ名</td>
 *     <td align="center"></td>
 *     <td>{@link #DEFAULT_HTTP_HEADER_NAME_HASH}</td>
 *   </tr>
 *   <tr>
 *     <td>CommonKey</td>
 *     <td>接続認証で、暗号パラメータを復号して得られる共通鍵</td>
 *     <td align="center">△</td>
 *     <td>接続認証を行う場合は、必須設定</td>
 *   </tr>
 *   <tr>
 *     <td>CommonKeyMapKey</td>
 *     <td>接続認証で、暗号パラメータの共通鍵のキー</td>
 *     <td align="center"></td>
 *     <td>{@link #DEFAULT_MAP_KEY_COMMON_KEY}</td>
 *   </tr>
 * </table>
 *
 * @author M.Ishida
 */
public class WebSocketServlet extends org.apache.catalina.websocket.WebSocketServlet{

    private static final long serialVersionUID = -542924531442074910L;

    /**
     * 初期化パラメータ名：{@link WebSocketFactory}サービスのサービス名。<p>
     * 必須設定である。<br>
     */
    public static final String INIT_PARAM_NAME_WEB_SOCKET_FACTORY_SERVICE_NAME = "WebSocketFactoryServiceName";

    /**
     * 初期化パラメータ名：HTTPヘッダOriginの値。<p>
     * このパラメータを設定すると、正規表現でHTTPヘッダOriginの値を検証できる。<br>
     * デフォルトでは、HTTPヘッダOriginは検証しない。<br>
     */
    public static final String INIT_PARAM_NAME_ORIGIN = "Origin";

    /**
     * 初期化パラメータ名：接続認証フラグ。<p>
     * trueを設定すると、WebSocketの接続時に認証を行う。<br>
     * デフォルトでは、falseで、接続認証しない。<br>
     */
    public static final String INIT_PARAM_NAME_AUTHENTICATE = "Authenticate";

    /**
     * 初期化パラメータ名：暗号鍵。<p>
     * 接続認証で、暗号パラメータを復号する際の暗号鍵を設定する。<br>
     * 接続認証を行う場合は、必須設定である。<br>
     */
    public static final String INIT_PARAM_NAME_CRYPT_KEY = "CryptKey";

    /**
     * 初期化パラメータ名：暗号アルゴリズム。<p>
     * 接続認証で、暗号パラメータを復号する際の暗号アルゴリズムを設定する。<br>
     * デフォルトは、{@link CryptParameters#DEFAULT_SECRET_KEY_ALGORITHM}。<br>
     */
    public static final String INIT_PARAM_NAME_CRYPT_ALGORITHM = "CryptAlgorithm";

    /**
     * 初期化パラメータ名：暗号変換方式。<p>
     * 接続認証で、暗号パラメータを復号する際の暗号変換方式を設定する。<br>
     * デフォルトは、{@link CryptParameters#DEFAULT_TRANSFORMATION}。<br>
     */
    public static final String INIT_PARAM_NAME_CRYPT_TRANSFORMATION = "CryptTransformation";

    /**
     * 初期化パラメータ名：初期ベクタ長。<p>
     * 接続認証で、暗号パラメータを復号する際の初期ベクタ長を設定する。<br>
     * デフォルトは、{@link CryptParameters#DEFAULT_INITIAL_VECTOR_LENGTH}。<br>
     */
    public static final String INIT_PARAM_NAME_IV_LENGTH = "IVLength";

    /**
     * 初期化パラメータ名：暗号方式プロバイダ。<p>
     * 接続認証で、暗号パラメータを復号する際の暗号方式プロバイダを設定する。<br>
     * デフォルトは、暗号方式プロバイダを指定しない。<br>
     */
    public static final String INIT_PARAM_NAME_CRYPT_PROVIDER = "CryptProvider";

    /**
     * 初期化パラメータ名：ハッシュ共通鍵。<p>
     * 接続認証で、改竄防止のハッシュ値を生成する際のハッシュ共通鍵を設定する。<br>
     * 接続認証を行う場合で改竄防止機能を使用する場合は、必須設定である。<br>
     */
    public static final String INIT_PARAM_NAME_HASH_KEY = "HashKey";

    /**
     * 初期化パラメータ名：ハッシュアルゴリズム。<p>
     * 接続認証で、改竄防止のハッシュ値を生成する際のハッシュアルゴリズムを設定する。<br>
     * デフォルトは、{@link CryptParameters#DEFAULT_HASH_ALGORITHM}。<br>
     */
    public static final String INIT_PARAM_NAME_HASH_ALGORITHM = "HashAlgorithm";

    /**
     * 初期化パラメータ名：ハッシュ方式プロバイダ。<p>
     * 接続認証で、改竄防止のハッシュ値を生成する際のハッシュ方式プロバイダを設定する。<br>
     * デフォルトは、ハッシュ方式プロバイダを指定しない。<br>
     */
    public static final String INIT_PARAM_NAME_HASH_PROVIDER = "HashProvider";

    /**
     * 初期化パラメータ名：暗号パラメータ有効時間。<p>
     * 接続認証で、暗号パラメータを復号する際の暗号パラメータ自体の有効時間[ms]を設定する。<br>
     * デフォルトは、-1で有効時間チェックを行わない。<br>
     */
    public static final String INIT_PARAM_NAME_CRYPT_EXPIRES = "CryptExpires";

    /**
     * 初期化パラメータ名：暗号パラメータのリクエストパラメータ名。<p>
     * 接続認証で、暗号パラメータをリクエストクエリから取得する場合のパラメータ名を設定する。<br>
     * デフォルトは、{@link #DEFAULT_PARAMETER_NAME_CRYPT}。<br>
     */
    public static final String INIT_PARAM_NAME_CRYPT_PARAMETER_NAME = "CryptParameterName";

    /**
     * 初期化パラメータ名：初期ベクタのリクエストパラメータ名。<p>
     * 接続認証で、初期ベクタをリクエストクエリから取得する場合のパラメータ名を設定する。<br>
     * デフォルトは、{@link #DEFAULT_PARAMETER_NAME_IV}。<br>
     */
    public static final String INIT_PARAM_NAME_IV_PARAMETER_NAME = "IVParameterName";

    /**
     * 初期化パラメータ名：ハッシュ値のリクエストパラメータ名。<p>
     * 接続認証で、ハッシュ値をリクエストクエリから取得する場合のパラメータ名を設定する。<br>
     * デフォルトは、{@link #DEFAULT_PARAMETER_NAME_HASH}。<br>
     */
    public static final String INIT_PARAM_NAME_HASH_PARAMETER_NAME = "HashParameterName";

    /**
     * 初期化パラメータ名：暗号パラメータのHTTPリクエストヘッダ名。<p>
     * 接続認証で、暗号パラメータをHTTPリクエストヘッダから取得する場合のヘッダ名を設定する。<br>
     * デフォルトは、{@link #DEFAULT_HTTP_HEADER_NAME_CRYPT}。<br>
     */
    public static final String INIT_PARAM_NAME_CRYPT_HTTP_HEADER_NAME_NAME = "CryptHttpHeaderName";

    /**
     * 初期化パラメータ名：初期ベクタのHTTPリクエストヘッダ名。<p>
     * 接続認証で、初期ベクタをHTTPリクエストヘッダから取得する場合のヘッダ名を設定する。<br>
     * デフォルトは、{@link #DEFAULT_HTTP_HEADER_NAME_IV}。<br>
     */
    public static final String INIT_PARAM_NAME_IV_HTTP_HEADER_NAME_NAME = "IVHttpHeaderName";

    /**
     * 初期化パラメータ名：ハッシュ値のHTTPリクエストヘッダ名。<p>
     * 接続認証で、ハッシュ値をHTTPリクエストヘッダから取得する場合のヘッダ名を設定する。<br>
     * デフォルトは、{@link #DEFAULT_HTTP_HEADER_NAME_HASH}。<br>
     */
    public static final String INIT_PARAM_NAME_HASH_HTTP_HEADER_NAME_NAME = "HashHttpHeaderName";

    /**
     * 初期化パラメータ名：共通鍵。<p>
     * 接続認証で、暗号パラメータを復号して得られる共通鍵を設定する。<br>
     * 接続認証を行う場合は、必須設定である。<br>
     */
    public static final String INIT_PARAM_NAME_COMMON_KEY = "CommonKey";

    /**
     * 初期化パラメータ名：暗号パラメータの共通鍵のキー。<p>
     * 接続認証で、暗号パラメータの共通鍵のキーを設定する。<br>
     * デフォルトは、{@link #DEFAULT_MAP_KEY_COMMON_KEY}。<br>
     */
    public static final String INIT_PARAM_NAME_COMMON_KEY_MAP_KEY = "CommonKeyMapKey";

    /**
     * 接続認証で、暗号パラメータをリクエストクエリから取得する場合のパラメータ名のデフォルト値。<p>
     */
    public static final String DEFAULT_PARAMETER_NAME_CRYPT = "crypt";

    /**
     * 接続認証で、初期ベクタをリクエストクエリから取得する場合のパラメータ名のデフォルト値。<p>
     */
    public static final String DEFAULT_PARAMETER_NAME_IV = "iv";

    /**
     * 接続認証で、ハッシュ値をリクエストクエリから取得する場合のパラメータ名のデフォルト値。<p>
     */
    public static final String DEFAULT_PARAMETER_NAME_HASH = "hash";

    /**
     * 接続認証で、共通鍵を暗号パラメータから取得するキーのデフォルト値。<p>
     */
    public static final String DEFAULT_MAP_KEY_COMMON_KEY = "commonKey";

    /**
     * 接続認証で、暗号パラメータをHTTPリクエストヘッダから取得する場合のヘッダ名のデフォルト値。<p>
     */
    public static final String DEFAULT_HTTP_HEADER_NAME_CRYPT = "x-websocket-crypt";

    /**
     * 接続認証で、初期ベクタをHTTPリクエストヘッダから取得する場合のヘッダ名のデフォルト値。<p>
     */
    public static final String DEFAULT_HTTP_HEADER_NAME_IV = "x-websocket-iv";

    /**
     * 接続認証で、ハッシュ値をHTTPリクエストヘッダから取得する場合のヘッダ名のデフォルト値。<p>
     */
    public static final String DEFAULT_HTTP_HEADER_NAME_HASH = "x-websocket-hash";

    protected WebSocketFactory webSocketFactory;
    protected Pattern originPattern;
    protected boolean isAutheticate = false;
    protected long cryptExpires = -1;
    protected CryptParameters crypt;
    protected String cryptParameterName = DEFAULT_PARAMETER_NAME_CRYPT;
    protected String ivParameterName = DEFAULT_PARAMETER_NAME_IV;
    protected String hashParameterName = DEFAULT_PARAMETER_NAME_HASH;
    protected String cryptHttpHeaderName = DEFAULT_HTTP_HEADER_NAME_CRYPT;
    protected String ivHttpHeaderName = DEFAULT_HTTP_HEADER_NAME_IV;
    protected String hashHttpHeaderName = DEFAULT_HTTP_HEADER_NAME_HASH;
    protected boolean isCheckFalsified;
    protected String commonKeyMapKey = DEFAULT_MAP_KEY_COMMON_KEY;
    protected String commonKey;

    @Override
    public void init() throws ServletException{
        super.init();

        final ServletConfig config = getServletConfig();
        final ServiceNameEditor editor = new ServiceNameEditor();

        String name = config.getInitParameter(
            INIT_PARAM_NAME_WEB_SOCKET_FACTORY_SERVICE_NAME
        );
        if(name == null){
            throw new ServletException("init-param '" + INIT_PARAM_NAME_WEB_SOCKET_FACTORY_SERVICE_NAME + "' must be specified.");
        }
        editor.setAsText(name);
        ServiceName webSocketFactoryServiceName = (ServiceName)editor.getValue();
        try{
            webSocketFactory = ServiceManagerFactory.getServiceObject(webSocketFactoryServiceName);
        }catch(ServiceNotFoundException e){
            throw new ServletException(e);
        }

        String origin = config.getInitParameter(
            INIT_PARAM_NAME_ORIGIN
        );
        if(origin != null){
            try{
                originPattern = Pattern.compile(origin);
            }catch(PatternSyntaxException e){
                throw new ServletException(e);
            }
        }

        String tmpCryptParameterName = config.getInitParameter(
            INIT_PARAM_NAME_CRYPT_PARAMETER_NAME
        );
        if(tmpCryptParameterName != null){
            cryptParameterName = tmpCryptParameterName;
        }

        String tmpIVParameterName = config.getInitParameter(
            INIT_PARAM_NAME_IV_PARAMETER_NAME
        );
        if(tmpIVParameterName != null){
            ivParameterName = tmpIVParameterName;
        }

        String tmpHashParameterName = config.getInitParameter(
            INIT_PARAM_NAME_HASH_PARAMETER_NAME
        );
        if(tmpHashParameterName != null){
            hashParameterName = tmpHashParameterName;
        }

        String tmpCryptHttpHeaderName = config.getInitParameter(
            INIT_PARAM_NAME_CRYPT_HTTP_HEADER_NAME_NAME
        );
        if(tmpCryptHttpHeaderName != null){
            cryptHttpHeaderName = tmpCryptHttpHeaderName;
        }

        String tmpIVHttpHeaderName = config.getInitParameter(
            INIT_PARAM_NAME_IV_HTTP_HEADER_NAME_NAME
        );
        if(tmpIVHttpHeaderName != null){
            ivHttpHeaderName = tmpIVHttpHeaderName;
        }

        String tmpHashHttpHeaderName = config.getInitParameter(
            INIT_PARAM_NAME_HASH_HTTP_HEADER_NAME_NAME
        );
        if(tmpHashHttpHeaderName != null){
            hashHttpHeaderName = tmpHashHttpHeaderName;
        }

        String authenticate = config.getInitParameter(
            INIT_PARAM_NAME_AUTHENTICATE
        );
        if(authenticate != null){
            isAutheticate = Boolean.valueOf(authenticate);
        }
        if(isAutheticate){
            String tmpCommonKey = config.getInitParameter(
                INIT_PARAM_NAME_COMMON_KEY
            );
            if(tmpCommonKey == null){
                throw new ServletException("init-param '" + INIT_PARAM_NAME_COMMON_KEY + "' must be specified.");
            }
            commonKey = tmpCommonKey;

            String tmpCommonKeyMapKey = config.getInitParameter(
                INIT_PARAM_NAME_COMMON_KEY_MAP_KEY
            );
            if(tmpCommonKeyMapKey != null){
                commonKeyMapKey = tmpCommonKeyMapKey;
            }

            String tmpCryptKey = config.getInitParameter(
                INIT_PARAM_NAME_CRYPT_KEY
            );
            if(tmpCryptKey == null){
                throw new ServletException("init-param '" + INIT_PARAM_NAME_CRYPT_KEY + "' must be specified.");
            }
            byte[] cryptKey = tmpCryptKey.getBytes();

            String tmpCryptAlgorithm = config.getInitParameter(
                INIT_PARAM_NAME_CRYPT_ALGORITHM
            );
            String cryptAlgorithm = CryptParameters.DEFAULT_SECRET_KEY_ALGORITHM;
            if(tmpCryptAlgorithm != null){
                cryptAlgorithm = tmpCryptAlgorithm;
            }

            String tmpCryptTransformation = config.getInitParameter(
                INIT_PARAM_NAME_CRYPT_TRANSFORMATION
            );
            String cryptTransformation = CryptParameters.DEFAULT_TRANSFORMATION;
            if(tmpCryptTransformation != null){
                cryptTransformation = tmpCryptTransformation;
            }

            String tmpIVLength = config.getInitParameter(
                INIT_PARAM_NAME_IV_LENGTH
            );
            int ivLength = CryptParameters.DEFAULT_INITIAL_VECTOR_LENGTH;
            if(tmpIVLength != null){
                try{
                    ivLength = Integer.parseInt(tmpIVLength);
                }catch(NumberFormatException e){
                    throw new ServletException("init-param '" + INIT_PARAM_NAME_IV_LENGTH + "' is illegal. value=" + tmpIVLength);
                }
            }

            String cryptProvider = config.getInitParameter(
                INIT_PARAM_NAME_CRYPT_PROVIDER
            );

            String hashKey = config.getInitParameter(
                INIT_PARAM_NAME_HASH_KEY
            );
            String hashAlgorithm = CryptParameters.DEFAULT_HASH_ALGORITHM;
            String hashProvider = null;
            if(hashKey != null){
                String tmpHashAlgorithm = config.getInitParameter(
                    INIT_PARAM_NAME_HASH_ALGORITHM
                );
                if(tmpHashAlgorithm != null){
                    hashAlgorithm = tmpHashAlgorithm;
                }

                hashProvider = config.getInitParameter(
                    INIT_PARAM_NAME_HASH_PROVIDER
                );
                isCheckFalsified = true;
            }
            try{
                crypt = new CryptParameters(
                    cryptKey,
                    cryptAlgorithm,
                    cryptTransformation,
                    ivLength,
                    cryptProvider,
                    hashKey
                );
                if(isCheckFalsified){
                    if(hashProvider == null){
                        crypt.setHashAlgorithm(hashAlgorithm);
                    }else{
                        crypt.setHashAlgorithm(hashAlgorithm, hashProvider);
                    }
                }
            }catch(Exception e){
                throw new ServletException("CryptParameters instansiate error.", e);
            }

            String cryptExpires = config.getInitParameter(
                INIT_PARAM_NAME_CRYPT_EXPIRES
            );
            if(cryptExpires != null){
                try{
                    this.cryptExpires = Long.parseLong(cryptExpires);
                }catch(NumberFormatException e){
                    throw new ServletException("init-param '" + INIT_PARAM_NAME_CRYPT_EXPIRES + "' is illegal. value=" + cryptExpires);
                }
            }

        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{

        if(isAutheticate){
            String params = request.getParameter(cryptParameterName);
            boolean isHttpHeader = false;
            if(params == null){
                params = request.getHeader(cryptHttpHeaderName);
                if(params != null){
                    isHttpHeader = true;
                }
            }
            String iv = isHttpHeader ? request.getHeader(ivHttpHeaderName) : request.getParameter(ivParameterName);
            if(params == null || iv == null){
                response.sendError(HttpServletResponse.SC_FORBIDDEN);
                return;
            }
            Map<String, Object> paramMap = null;
            if(isCheckFalsified){
                String hash = isHttpHeader ? request.getHeader(hashHttpHeaderName) : request.getParameter(hashParameterName);
                try{
                    paramMap = crypt.decrypt(iv, params, hash, cryptExpires);
                }catch(FalsifiedParameterException e){
                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }catch(OverLimitExpiresException e){
                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }
            }else{
                try{
                    paramMap = crypt.decrypt(iv, params, cryptExpires);
                }catch(FalsifiedParameterException e){
                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }catch(OverLimitExpiresException e){
                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }
            }
            String commonKey = (String)paramMap.get(commonKeyMapKey);
            if(!this.commonKey.equals(commonKey)){
                response.sendError(HttpServletResponse.SC_FORBIDDEN);
                return;
            }
        }
        super.doGet(request, response);
    }

    @Override
    public String selectSubProtocol(List<String> subProtocols) {
        return webSocketFactory.selectSubProtocol(subProtocols);
    }

    @Override
    public boolean verifyOrigin(String origin){
        return originPattern == null ? true : (origin == null ? false : originPattern.matcher(origin).matches());
    }

    @Override
    public StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request){
        return webSocketFactory.createWebSocketInbound(request, subProtocol);
    }

}