/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

package stdx.net.http

import std.collection.ArrayList
import std.sync.{AtomicBool, Mutex}
import stdx.log.Logger
import stdx.encoding.url.URL
import stdx.crypto.common.*
import stdx.encoding.base64.{toBase64String, fromBase64String}

// GUID is used to generate Sec-WebSocket-Accept.
const GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
// the size of each fragment's payload when send large message.
const FRAMESIZE = 4 * 1024
// the limit of each frame payload length is 20M, to prevent the DOS attack
const MAX_FRAME_PAYLOAD_LENGTH = 20 * 1024 * 1024

@FastNative
foreign func DYN_SHA1(d: CPointer<UInt8>, n: Int32, md: CPointer<UInt8>, msg: CPointer<DynMsg>): CPointer<UInt8>

foreign func MallocDynMsg(): CPointer<DynMsg>

foreign func FreeDynMsg(dynMsgPtr: CPointer<DynMsg>): Unit

@C
struct DynMsg {
    var found = true
    var funcName = CPointer<UInt8>()
}

/**
 * websocket extensions are not supported yet
 */
public class WebSocket {
    let conn: WebSocketConn
    let _logger: Logger = mutexLogger()
    var _subProtocol: String = ""
    let isClient: Bool
    let writeMutex = Mutex()
    let readMutex = Mutex()
    let isClosed = AtomicBool(false)
    let isSentCloseFrame = AtomicBool(false)

    init(conn: WebSocketConn, subProtocol: String, isClient: Bool) {
        this.conn = conn
        this._subProtocol = subProtocol
        this.isClient = isClient
    }

    /*
     * the subProtocol of the webSocket
     */
    public prop subProtocol: String {
        get() {
            _subProtocol
        }
    }

    /**
     * logger
     * setting logger.level will take effect immediately.
     * NOTE: logger should be thread-safe
     */
    public prop logger: Logger {
        get() {
            _logger
        }
    }

    /**
     * read a frame, block to get frame.
     * if websocket receives a malformed fame, it will send
     * an error code to the peer end and close the connection,
     * and throws a WebSocketException.
     *
     * @return received websocketFrame
     *
     * @throws WebSocketException if the frame is malformed,
     *          or received unexpected frame on http/2.0 layer
     *          or if the conn is closed.
     * @throws SocketException if failed to read data.
     * @throws ConnectionException if conn is closed by peer.
     */
    public func read(): WebSocketFrame {
        if (isClosed.load()) {
            throw WebSocketException("The connection is closed.")
        }
        let frame: WebSocketFrame
        synchronized(readMutex) {
            // read 16 bits
            let bytes = Array<UInt8>(2, repeat: 0)
            readExact(bytes, "frame header")
            frame = toWebSocketFrameFromFirstTwoBytes(bytes)

            // client --> server  must      mask
            // server --> client  must not  mask
            // if the data is being sent by the client, the frames must be masked.
            // RFC 6455 6.1.5.
            // a server must not mask any frames that it sends to the client.
            // RFC 6455 5.1.
            if ((isClient && frame.mask) || (!isClient && !frame.mask)) {
                failTheWebSocketConnection(WebSocketStatusCode.PROTOCOL_ERROR, "receiving an invalid mask message")
            }
            // if a nonzero value is received and none of the negotiated extensions
            // defines the meaning of such a nonzero value, the receiving endpoint
            // must _fail the websocket connection_.
            // RFC 6455 5.2.
            if (frame.rsv1 || frame.rsv2 || frame.rsv3) {
                failTheWebSocketConnection(WebSocketStatusCode.PROTOCOL_ERROR,
                    "extensions not supported yet, rsv must be 0")
            }
            // if an unknown opcode is received, the receiving endpoint
            // must _fail the websocket connection_.
            // RFC 6455 5.2.
            if (frame.frameType == UnknownWebFrame) {
                failTheWebSocketConnection(WebSocketStatusCode.UNSUPPORTED_DATA,
                    "receiving a message with invalid frame type")
            }

            updatePayloadLen(frame)

            // read masking-key
            if (frame.mask) {
                readExact(frame.maskingKey, "masking key")
            }

            // control frames cannot be fragmented and must have
            // a payload length of 125 bytes or less.
            match (frame.frameType) {
                case PingWebFrame | PongWebFrame | CloseWebFrame =>
                    if (frame.payloadLength > 125) {
                        failTheWebSocketConnection(WebSocketStatusCode.PROTOCOL_ERROR,
                            "receiving a control frame has a payload length more than 125 bytes")
                    }
                    if (!frame.fin) {
                        failTheWebSocketConnection(WebSocketStatusCode.PROTOCOL_ERROR,
                            "receiving a control frame that is fragmented")
                    }
                case _ => ()
            }
            readFramePayload(frame)
        }
        return frame
    }

    private func readExact(byteArray: Array<UInt8>, fieldName: String): Unit {
        let len = conn.readRaw(byteArray)
        if (len != byteArray.size) {
            throw ConnectionException("Connection closed while reading websocket ${fieldName}.")
        }
    }

    private func updatePayloadLen(frame: WebSocketFrame): Unit {
        // read extended payload length.
        // match 7 bit payload len
        match (frame.payloadLen) {
            // if 126, the following 2 bytes interpreted as a
            // 16-bit unsigned integer are the payload length.
            // RFC 6455 5.2.
            case 126 =>
                let extendedPayloadLength = Array<UInt8>(2, repeat: 0)
                readExact(extendedPayloadLength, "extended payload length")
                frame.payloadLength = toInt64(extendedPayloadLength)
            // if 127, the following 8 bytes interpreted as a
            // 64-bit unsigned integer are the payload length.
            // RFC 6455 5.2.
            case 127 =>
                let extendedPayloadLength = Array<UInt8>(8, repeat: 0)
                readExact(extendedPayloadLength, "extended payload length")
                frame.payloadLength = toInt64(extendedPayloadLength)
            case _ => frame.payloadLength = Int64(frame.payloadLen)
        }
    }

    private func readFramePayload(frame: WebSocketFrame): Unit {
        if (frame.payloadLength != 0) {
            checkFramePayloadLimit(frame.payloadLength)
            let payloadData = Array<UInt8>(frame.payloadLength, repeat: 0)
            readExact(payloadData, "payload")
            // server must remove masking for data frames received from a client.
            frame._payload = if (!isClient) {
                maskOrUnmask(frame.maskingKey, payloadData)
            } else {
                payloadData
            }
        }
    }

    // cjlint-ignore -start !G.OTH.03
    /**
     * Implementations that have implementation- and/or platform-specific
     * limitations regarding the frame size or total message size after
     * reassembly from multiple frames MUST protect themselves against
     * exceeding those limits
     * https://www.rfc-editor.org/rfc/rfc6455.html#section-10.4
     *
     * 1009 indicates that an endpoint is terminating the connection
     * because it has received a message that is too big for it to
     * process.
     */
    // cjlint-ignore -end
    private func checkFramePayloadLimit(payloadLength: Int64) {
        if (payloadLength >= MAX_FRAME_PAYLOAD_LENGTH) {
            failTheWebSocketConnection(WebSocketStatusCode.MESSAGE_TOO_BIG,
                "payload length too large, unexpected value: ${payloadLength}")
        }
    }

    /**
     * write a message, block to write message
     *
     * @param frameType the type of the message
     * @param byteArray message to be sent
     * @param frameSize the frame size of each fragmented data frame
     *
     * @throws WebSocketException if the frame is malformed
     *          or if send data frames after the close frame is sent
     *          or WebSocketException if the conn is closed.
     * @throws SocketException if failed to write data.
     */
    public func write(frameType: WebSocketFrameType, byteArray: Array<UInt8>, frameSize!: Int64 = FRAMESIZE): Unit {
        if (isClosed.load()) {
            throw WebSocketException("The connection is closed.")
        }

        match (frameType) {
            // control frames cannot be fragmented and must have a payload length of 125 bytes or less.
            // an unfragmented message consists of a single frame with th FIN bit set and an opcode other than 0.
            // unfragment
            //                          FIN = 1, Opcode != 0
            case PingWebFrame | PongWebFrame =>
                if (byteArray.size > 125) {
                    throw WebSocketException("All control frames must have a payload length of 125 bytes or less.")
                }
                writeFrame(true, frameType, byteArray, isClient)
            case CloseWebFrame =>
                match {
                    // the first two bytes of the body must be 2-byte unsigned integer.
                    case byteArray.size == 0 => writeFrame(true, CloseWebFrame, byteArray, isClient)
                    case byteArray.size == 1 => throw WebSocketException("Invalid close payload.")
                    case byteArray.size <= 125 =>
                        let status = toUInt16(byteArray[..2])
                        // valid status code ranges defined in
                        // RFC 6455 7.4.2.
                        // status must between 1000 and 4999,
                        // 1005, 1006, 1015 are reserved values and must not be
                        // set as status code in a Close control frame by an endpoint.
                        if (status >= 5000 || status < 1000 || status == 1005 || status == 1006 || status == 1015) {
                            throw WebSocketException("Invalid close status code.")
                        }
                        writeFrame(true, CloseWebFrame, byteArray, isClient)
                    case _ => throw WebSocketException(
                        "All control frames must have a payload length of 125 bytes or less.")
                }
                isSentCloseFrame.store(true)
            // a fragmented message consists of a single frame with the FIN bit
            // clear and an opcode other than 0, followed by zero or more frames
            // with the FIN bit clear and the opcode set to 0, and terminated by
            // a single frame with the FIN bit set and an opcode of 0.
            case TextWebFrame | BinaryWebFrame =>
                // must not send any more data frames after sending a Close frame
                // RFC 6455 5.1.1.
                if (isSentCloseFrame.load()) {
                    throw WebSocketException("No more data frames can be sent after sending a Close frame.")
                }
                if (frameSize <= 0) {
                    throw WebSocketException("FrameSize must > 0.")
                }
                // unfragment
                //                      FIN = 1, Opcode != 0
                if (byteArray.size <= frameSize) {
                    writeFrame(true, frameType, byteArray, isClient)
                    return
                }
                // fragment
                // first frame:        FIN = 0, Opcode != 0
                writeFrame(false, frameType, byteArray.slice(0, frameSize), isClient)
                var sendLen = frameSize
                // intermediate frame:  FIN = 0, Opcode = 0
                while (sendLen + frameSize < byteArray.size) {
                    writeFrame(false, ContinuationWebFrame, byteArray.slice(sendLen, frameSize), isClient)
                    sendLen += frameSize
                }
                // last frame:          FIN = 1, Opcode = 0
                writeFrame(true, ContinuationWebFrame, byteArray.slice(sendLen, (byteArray.size - sendLen)), isClient)
            case _ => throw WebSocketException("Invalid frame type, the type must be Text, Binary, Close, Ping, Pong.")
        }
    }

    private func writeFrame(fin: Bool, frameType: WebSocketFrameType, byteArray: Array<UInt8>, isClient: Bool) {
        synchronized(writeMutex) {
            let frameBytesExceptPayload = toWebSocketFrameBytesExceptPayload(fin, frameType, byteArray.size, isClient)
            conn.writeRaw(frameBytesExceptPayload)
            if (byteArray.isEmpty()) {
                return
            }
            // client must mask data frames sent to server.
            let payload = if (isClient) {
                // last 4 bytes is maskingKey.
                maskOrUnmask(frameBytesExceptPayload[frameBytesExceptPayload.size - 4..], byteArray)
            } else {
                byteArray
            }
            conn.writeRaw(payload)
        }
    }

    /**
     * write a close frame
     *
     * @param status close status code
     * @param reason the websocket connection close reason
     *                defined as the UTF-8-encoded data
     *
     * @throws WebSocketException if the frame is malformed
     *          or if the conn is closed.
     * @throws SocketException if failed to write data.
     */
    public func writeCloseFrame(status!: ?UInt16 = None, reason!: String = ""): Unit {
        if (isClosed.load()) {
            throw WebSocketException("The connection is closed.")
        }

        // status none means the Close frame contains no body.
        let statusCode = match (status) {
            case None =>
                writeFrame(true, CloseWebFrame, Array<UInt8>(), isClient)
                isSentCloseFrame.store(true)
                return
            case Some(value) => value
        }

        // status must between 1000 and 4999,
        // 1005, 1006, 1015 are reserved values and must not be set as status code in a Close control frame by an endpoint.
        if (statusCode >= 5000 || statusCode < 1000 || statusCode == 1005 || statusCode == 1006 || statusCode == 1015) {
            throw WebSocketException("Invalid status code.")
        }
        // the first two bytes of the body must be 2-byte unsigned integer,
        // and the Close frame must have a payload length of 125 bytes or less.
        if (reason.size > 123) {
            throw WebSocketException("All control frames must have a payload length of 125 bytes or less.")
        }

        // the first two bytes of the body must be 2-byte unsigned integer (in network byte order).
        let statusArr = fromUInt16(statusCode)
        // the body may contain UTF-8-encoded data with value reason.
        // RFC 6455 5.5.1.
        let reasonArr = unsafe { reason.rawData() }
        let payload = Array<UInt8>(2 + reasonArr.size, repeat: 0)
        statusArr.copyTo(payload, 0, 0, 2)
        reasonArr.copyTo(payload, 0, 2, reasonArr.size)
        writeFrame(true, CloseWebFrame, payload, isClient)
        isSentCloseFrame.store(true)
    }

    /**
     * write a ping frame
     *
     * @param byteArray payload to be sent
     *
     * @throws WebSocketException if the frame is malformed
     *          or if the conn is closed.
     * @throws SocketException if failed to write data.
     */
    public func writePingFrame(byteArray: Array<UInt8>): Unit {
        write(PingWebFrame, byteArray)
    }

    /**
     * write a pong frame
     *
     * @param byteArray payload to be sent
     *
     * @throws WebSocketException if the frame is malformed
     *          or if the conn is closed.
     * @throws SocketException if failed to write data.
     */
    public func writePongFrame(byteArray: Array<UInt8>): Unit {
        write(PongWebFrame, byteArray)
    }

    /**
     * close the websocket connection
     * RFC 6455 7.1.2.
     */
    public func closeConn(): Unit {
        if (isClosed.load()) {
            return
        }
        conn.close()
        isClosed.store(true)
    }

    /**
     * if the websocket connection is established prior to the point where the
     * endpoint is required to _fail the websocket connection_.
     * the endpoint should send a Close frame with an appropriate status code
     * before proceeding to close the websocket connection.
     * RFC 6455 7.1.7.
     */
    private func failTheWebSocketConnection(status: UInt16, message: String) {
        // send a Close frame and close connection
        writeCloseFrame(status: status)
        closeConn()
        // throw WebSocketException
        throw WebSocketException("The websocket connection is failed, since ${message}." )
    }

    /**
     * upgrade from server
     *
     * @param ctx conveys upgrade request and responseBuilder
     * @param subProtocols subProtocols supported by the server, the default value is empty,
     *                      indicating that no subProtocol is supported.
     * @param origins origins supported by the server, the default value is empty,
     *                 indicating that the server accept handshakes from all origins.
     * @param userFunc user-defined callback, where param is the upgrade request received from client,
     *                  returned value is response header, which will be sent to client as part of handshake
     *                  response after some checking, e.g. server side can send some cookie, authentication header to client.
     *                  the default value is a func which returns a empty HttpHeader
     *
     * @return websocket instance
     *
     * @throws SocketException, if failed to read request or send response
     * @throws WebSocketException, if handshake failed, including check request header failed,
     *          run userFunc failed, check response headers returned by userFunc failed, run checkOrigin failed.
     */
    public static func upgradeFromServer(ctx: HttpContext, subProtocols!: ArrayList<String> = ArrayList<String>(),
        origins!: ArrayList<String> = ArrayList<String>(),
        userFunc!: (HttpRequest) -> HttpHeaders = {_: HttpRequest => HttpHeaders()}): WebSocket {
        synchronized(ctx.writerMtx) {
            if (ctx.upgraded) {
                throw WebSocketException("Upgrade to websocket failed, the connection has been upgraded.")
            }
            if (ctx.responseFlushedByUser) {
                throw WebSocketException("Upgrade to websocket failed, a response has been sent to the client.")
            }
            if (ctx.responded) {
                throw WebSocketException("Already responded.")
            }

            let responseHeader = userFunc(ctx.request)

            return match (ctx.httpConn) {
                // server1_1
                case httpConn: HttpEngineConn1 =>
                    // reading the Client's Opening Handshake
                    let webSocketKey = parseUpgradeRequest1(ctx)
                    let subProtocol = parseUpgradeRequestCommon(ctx, origins, subProtocols)
                    // sending the Server's Opening Handshake
                    let acceptValue = generateAcceptValue(webSocketKey)
                    replyUpgradeResponse1(httpConn, subProtocol, acceptValue, responseHeader)
                    // extract the conn and construct websocket
                    let websocketConn = WebSocketConn1(httpConn.conn)
                    ctx.upgraded = true
                    WebSocket(websocketConn, subProtocol, false)
                // server2_0
                case httpConn: HttpEngineConn2 =>
                    // reading the Client's Opening Handshake
                    if (httpConn.upgrade != "websocket") {
                        responseAndThrows(ctx, HttpStatusCode.STATUS_BAD_REQUEST,
                            "the upgrade request to websocket on http/2.0 must be a CONNECT request")
                    }
                    let subProtocol = parseUpgradeRequestCommon(ctx, origins, subProtocols)
                    // sending the Server's Opening Handshake
                    replyUpgradeResponse2(httpConn, subProtocol, responseHeader)
                    let websocketConn = WebSocketConn2(httpConn)
                    ctx.upgraded = true
                    WebSocket(websocketConn, subProtocol, false)
                case _ => throw WebSocketException("Only HTTP/1.1 or HTTP/2.0 to WebSocket upgrade is supported.")
            }
        }
    }

    /**
     * upgrade from client
     *
     * @param client the client from which to send a websocket upgrade request
     * @param url the target url
     * @param subProtocols the subProtocols the client wishes to speak, ordered by preference. the default value is empty.
     * @param headers the upgrade request headers, such as cookie, origin.
     *
     * @return websocket instance
     * @return httpHeader in the response
     *
     * @throws SocketException, if failed to send
     * @throws HttpException, if some thing wrong is http request
     * @throws WebSocketException, if handshake failed, including get conn from pool failed, check response from server failed.
     */
    public static func upgradeFromClient(client: Client, url: URL, version!: Protocol = HTTP1_1,
        subProtocols!: ArrayList<String> = ArrayList<String>(), headers!: HttpHeaders = HttpHeaders()): (WebSocket,
        HttpHeaders) {

        // a client opens a connection and sends a handshake
        // only HTTP/1.1 and HTTP/2.0 are supported to upgrade to WebSocket.
        match (version) {
            case HTTP1_1 | HTTP2_0 => ()
            case _ => throw WebSocketException("Only the upgrade from HTTP1_1 and HTTP2_0 to WebSocket is supported.")
        }
        // generate sec-websocket-key
        let webSocketKey = generateKey()
        // generate Sec-WebSocket-Accept
        let acceptValue = generateAcceptValue(webSocketKey)

        let upgradeRequest = constructUpgradeRequest(url, webSocketKey, subProtocols, headers, version)

        // once the client's opening handshake has been sent,
        // the client must wait for a response from the server
        // before sending any further data.
        if (client.isClosed.load()) {
            throw WebSocketException("This client has already closed.")
        }
        let resp = client.doRequest(upgradeRequest)

        let subProtocol = validateUpgradeResponse(resp, acceptValue, subProtocols)

        // extract the conn and construct websocket
        // client1_1
        let conn = match (version) {
            case HTTP1_1 =>
                let connNode = match (resp.connNode.getOrThrow() as ConnNode) {
                    case Some(v) => v
                    case None => throw WebSocketException("Get connection failed.")
                }
                WebSocketConn1(connNode.h1Engine.extractFromConnInUse(connNode))
            case HTTP2_0 => (resp.body as WebSocketConn) ?? throw HttpException("H2 body broken.")
            case _ => throw WebSocketException("Not supported protocol.")
        }

        let websocket = WebSocket(conn, subProtocol, true)
        return (websocket, resp.headers)
    }
}

/**
 * the handshake consists of an HTTP Upgrade request,
 * along with a list of required and optional header fields
 * RFC 6455 4.1.
 */
func constructUpgradeRequest(url: URL, webSocketKey: String, subProtocols: ArrayList<String>, headers: HttpHeaders,
    version: Protocol): HttpRequest {
    // the websocket protocol defines two URI schemes,
    // ws: the default port for ws is 80,
    // wss: the secure scheme, the default port for wss is 443
    // RFC 6455 3.
    let upgradeUrl = match (url.scheme) {
        case "ws" => url.replace(scheme: "http")
        case "wss" => url.replace(scheme: "https")
        case _ => throw WebSocketException(
            "Upgrade to websocket failed, invalid URL scheme, the scheme must be ws or wss.")
    }
    if (url.hostName.isEmpty()) {
        throw WebSocketException("Upgrade to websocket failed, no host in request URL.")
    }

    // send an opening handshake, and read the server’s handshake in response.
    // the handshake consists of an HTTP Upgrade request,
    // along with a list of required and optional header fields.
    // RFC 6455 4.1.
    let upgradeRequestBuilder = HttpRequestBuilder().url(upgradeUrl)
    if (version == HTTP2_0) {
        upgradeRequestBuilder.version(version).connect()
        upgradeRequestBuilder.headers.map.add(Str(":protocol"), HeaderValue("websocket"))
    } else {
        upgradeRequestBuilder
            .version(version)
            // the method of the request must be GET
            // RFC 6455 4.1.2.
            .method("GET")
            // must contain an Upgrade header field
            // whose value must include "websocket"
            // RFC 6455 4.1.5.
            .header("upgrade", "websocket")
            // must contain a Connection header field
            // whose value must include "Upgrade"
            // RFC 6455 4.1.6.
            .header("connection", "Upgrade")
    }
    upgradeRequestBuilder
        // must include a header field with the name sec-websocket-key
        // RFC 6455 4.1.7.
        .header("sec-websocket-key", webSocketKey)
        // must include a header field with the name sec-websocket-version
        // the value of this header must be 13
        // RFC 6455 4.1.9.
        .header("sec-websocket-version", "13")
    // may include a header field with a name sec-websocket-protocol
    // If present, this value indicates one or more comma-separated subprotocol
    // the client wishes to speak, ordered by preference.
    // RFC 6455 4.1.10.
    if (!subProtocols.isEmpty()) {
        upgradeRequestBuilder.headers.set("sec-websocket-protocol", subProtocols[0])
        for (i in 1..subProtocols.size) {
            upgradeRequestBuilder.headers.add("sec-websocket-protocol", subProtocols[i])
        }
    }

    // may include any other header fields.
    // websocket extensions not supported yet
    // RFC 6455 4.1.11.
    if (!headers.isEmpty()) {
        if (!headers.get("sec-websocket-extensions").isEmpty()) {
            throw WebSocketException("Upgrade to websocket failed, websocket extensions not supported yet.")
        }
        upgradeRequestBuilder.headers.addAll(headers)
    }
    return upgradeRequestBuilder.build()
}

/**
 * validate the response
 * the client must validate the server's response as follows:
 * RFC 6455 4.1.
 */
func validateUpgradeResponse(resp: HttpResponse, acceptValue: String, subProtocols: ArrayList<String>): String {
    // version-related check
    match (resp.version) {
        // the status code received from the server should be 101
        // connection still maintained by Client.
        case HTTP1_1 =>
            if (resp.status != HttpStatusCode.STATUS_SWITCHING_PROTOCOLS) {
                // conn is not returned to HttpEngine1, should be disconnected
                failTheWebSocketConnection(resp, "the status code should be 101, but received ${resp.status}")
            }

            // if the response lacks an Upgrade header field or the Upgrade header field
            // contains a value that is not an ASCII case-insensitive match for the value "websocket",
            // the client must _fail the websocket connection_.
            let upgradeValues = resp.headers.getInternal("upgrade") ?? failTheWebSocketConnection(resp,
                "the handshake response lacks Upgrade: websocket header field")
            if (!(upgradeValues |> splitValuesByComma |> "websocket".caseInsensitiveMatchAll)) {
                failTheWebSocketConnection(resp, "the handshake response has a wrong Upgrade header field")
            }

            // if the response lacks an Connection header field or the Upgrade header field
            // doesn't contain a token that is not an ASCII case-insensitive match for the value "upgrade",
            // the client must _fail the websocket connection_.
            if (!(resp.headers.get("connection") |> splitValuesByComma |> "upgrade".caseInsensitiveMatchOne)) {
                failTheWebSocketConnection(resp, "the handshake response lacks Connection: Upgrade header field")
            }

            // if the response lacks a Sec-WebSocket-Accept header field or the Sec-WebSocket-Accept header field
            // contains a value other than the base64-encoded SHA-1 of the concatenation of the sec-websocket-key
            // (as a string, not base64-decoded) with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
            // but ignoring any leading and trailing whitespace,
            // the client must _fail the websocket connection_.
            let acceptValues = resp.headers.getInternal("sec-websocket-accept") ?? failTheWebSocketConnection(resp,
                "the handshake response lacks Sec-Websocket-Accept header field")
            if (!(acceptValues |> splitValuesByComma |> acceptValue.matchAll)) {
                failTheWebSocketConnection(resp, "the handshake response has a wrong accept string")
            }
        case HTTP2_0 =>
            if (resp.status != HttpStatusCode.STATUS_OK) {
                failTheWebSocketConnection(resp, "the status code should be 200, but received ${resp}")
            }
        // an HTTP/1.1 upgrade request but receive an HTTP/1.0 response
        case _ => failTheWebSocketConnection(resp,
            "wrong http version ${resp.version} which cannot be upgraded to websocket")
    }

    // websocket extensions not supported yet
    if (!resp.headers.get("sec-websocket-extensions").isEmpty()) {
        failTheWebSocketConnection(resp, "websocket extensions not supported yet")
    }

    // if the response includes a sec-websocket-protocol header field and this header field indicates
    // the use of a subprotocol that was not present in the client’s handshake
    // (the server has indicated a subprotocol not requested by the client),
    // the client must _fail the websocket connection_
    let subProtocol = match (resp.headers.getInternal("sec-websocket-protocol")) {
        case None => ""
        case Some(values) =>
            let subs = splitValuesByComma(values)
            if (subs.size != 1) {
                failTheWebSocketConnection(resp, "the handshake response must have one or null subprotocol")
            }
            if (!subProtocols.contains(subs[0].trimAscii())) {
                failTheWebSocketConnection(resp, "the handshake response has wrong subprotocol")
            } else {
                subs[0]
            }
    }
    return subProtocol
}

/**
 * parse the upgrade request.
 * the server must parse at least part of this handshake in order to
 * obtain the necessary information to generate the server part of the handshake.
 * RFC 6455 4.2.1.
 */
func parseUpgradeRequest1(ctx: HttpContext): String {
    // an HTTP/1.1 or higher GET request.
    if (ctx.request.version == HTTP1_0) {
        responseAndThrows(ctx, HttpStatusCode.STATUS_BAD_REQUEST,
            "the upgrade request to websocket must be an HTTP/1.1 or higher request")
    }
    if (ctx.request.method != "GET") {
        responseAndThrows(ctx, HttpStatusCode.STATUS_BAD_REQUEST,
            "the upgrade request to websocket must be a GET request")
    }

    // Host header is checked when read request.

    // an Upgrade header field containing the value "websocket", case-insensitive.
    if (!(ctx.request.headers.get("upgrade") |> splitValuesByComma |> "websocket".caseInsensitiveMatchOne)) {
        responseAndThrows(ctx, HttpStatusCode.STATUS_BAD_REQUEST,
            "the upgrade request lacks Upgrade: websocket header field")
    }

    // a Connection header field that include the token "Upgrade", case-insensitive.
    if (!(ctx.request.headers.get("connection") |> splitValuesByComma |> "upgrade".caseInsensitiveMatchOne)) {
        responseAndThrows(ctx, HttpStatusCode.STATUS_BAD_REQUEST,
            "the upgrade request lacks Connection: Upgrade header field")
    }

    // a sec-websocket-key header field with a base64-encoded value that,
    // when decoded, is 16 bytes in length.
    let keyValues = ctx.request.headers.getInternal("sec-websocket-key") ?? responseAndThrows(ctx, // cjlint-ignore !G.EXP.03
        HttpStatusCode.STATUS_BAD_REQUEST, "the upgrade request lacks sec-websocket-key header field")
    if (!keyValues.isSingle() || ((fromBase64String(keyValues.single)?.size ?? 0) != 16)) {
        responseAndThrows(ctx, HttpStatusCode.STATUS_BAD_REQUEST,
            "the upgrade request's sec-websocket-key header value is invalid")
    }

    return keyValues.single
}

func parseUpgradeRequestCommon(ctx: HttpContext, origins: ArrayList<String>, subProtocols: ArrayList<String>): String {
    // optionally
    // an Origin header field in the client's handshake indicates the origin of the script establishing the connection.
    // the server may use this information as part of a determination of whether to accept the incoming connection.
    // if the server does not validate the origin (origins.isEmpty() == true), it will accept connections from anywhere.
    if (origins.isEmpty()) {
        httpLogWarn(mutexLogger(), "[WebSocket#upgradeFromServer] No origin restrictions configured. " +
            "This enables Cross-Site WebSocket Hijacking (CSWSH) vulnerability. " +
            "Consider setting allowed origins explicitly.")
    } else {
        let originValues = ctx.request.headers.getInternal("origin") ?? responseAndThrows(ctx, // cjlint-ignore !G.EXP.03
            HttpStatusCode.STATUS_FORBIDDEN, "the upgrade request lacks Origin field")
        if (!originValues.isSingle() || !origins.contains(originValues.single)) {
            responseAndThrows(ctx, HttpStatusCode.STATUS_FORBIDDEN,
                "the upgrade request's origin is not allowed by server")
        }
    }

    // optionally
    // a sec-websocket-protocol header field, with a list of values indicating which protocols the client would like to
    // speak, ordered by preference
    var subProtocol: String = ""
    if (!subProtocols.isEmpty()) {
        let protocolValues = ctx.request.headers.get("sec-websocket-protocol") |> splitValuesByComma
        for (protocol in protocolValues) {
            if (subProtocols.contains(protocol)) {
                subProtocol = protocol
                break
            }
        }
    }

    // a sec-websocket-version header field, with a value of 13
    let versionValues = ctx.request.headers.getInternal("sec-websocket-version") ?? responseAndThrows(ctx, // cjlint-ignore !G.EXP.03
        HttpStatusCode.STATUS_BAD_REQUEST, "the upgrade request lacks sec-websocket-version header field")
    if (!versionValues.isSingle() || versionValues.single != "13") {
        responseAndThrows(ctx, HttpStatusCode.STATUS_UPGRADE_REQUIRED,
            "the upgrade request's sec-websocket-version must be 13")
    }

    // websocket extensions are not supported yet
    return subProtocol
}

/**
 * if the server chooses to accept the incoming connection,
 * it must reply with a valid HTTP response indicating the following
 * RFC 6455 4.2.2.5.
 */
func replyUpgradeResponse1(conn: HttpEngineConn1, subProtocol: String, acceptValue: String, responseHeader: HttpHeaders) {
    let responseBuilder = HttpResponseBuilder()
        // a status-line with a 101 response code
        // RFC 6455 4.2.2.5.1.
        .status(HttpStatusCode.STATUS_SWITCHING_PROTOCOLS)
        // an Upgrade header field with value "websocket"
        // RFC 6455 4.2.2.5.2.
        .header("upgrade", "websocket")
        // a Connection header field with value "Upgrade"
        // RFC 6455 4.2.2.5.3.
        .header("connection", "upgrade")
        // a Sec-WebSocket-Accept header field
        // RFC 6455 4.2.2.5.4.
        .header("sec-websocket-accept", acceptValue)

    // optionally
    // a sec-websocket-protocol header field
    // RFC 6455 4.2.2.5.4.
    if (!subProtocol.isEmpty()) {
        responseBuilder.header("sec-websocket-protocol", subProtocol)
    }
    // other headers such as set-cookie
    // RFC 6455 1.3.
    responseBuilder.addHeaders(responseHeader)
    conn.writeResponse(responseBuilder.build())
}

func replyUpgradeResponse2(conn: HttpEngineConn2, subProtocol: String, responseHeader: HttpHeaders) {
    let headers = HttpHeaders()
    if (!subProtocol.isEmpty()) {
        headers.add("sec-websocket-protocol", subProtocol)
    }
    headers.addAll(responseHeader)
    conn.writeHeader(HttpStatusCode.STATUS_OK, headers, streamEnd: false)
}

/**
 * the value of this header field must be a nonce consisting of
 * a randomly selected 16-byte value that has been base64-encoded.
 * RFC 6455 4.1.7.
 */
func generateKey(): String {
    let r = Array<Byte>(16, repeat: 0)
    getGlobalCryptoKit().getRandomGen().nextBytes(r)
    return toBase64String(r)
}

/**
 * to do so, the client must _close the websocket connection_,
 * and may report the problem to the user.
 * to _close the websocket connection_,
 * an endpoint closes the underlying TCP connection
 * RFC 6455 7.1.7.
 */
func failTheWebSocketConnection(resp: HttpResponse, message: String) {
    // extract the conn and close
    // client1_1
    match (resp.connNode) {
        case Some(v) =>
            if (let Some(node) <- (v as ConnNode)) {
                node.closeConn()
            }
        case None => ()
    }
    // throw WebSocketException
    throw WebSocketException("Upgrade to websocket failed, ${message}." )
}

func responseAndThrows(ctx: HttpContext, status: UInt16, message: String) {
    let respBuilder = HttpResponseBuilder().status(status).body(message)
    if (status == HttpStatusCode.STATUS_UPGRADE_REQUIRED) {
        respBuilder.header("sec-websocket-version", "13")
    }
    match (ctx.httpConn) {
        case httpConn: HttpEngineConn1 =>
            respBuilder.header("connection", "close")
            httpConn.writeResponse(respBuilder.build())
        case httpConn: HttpEngineConn2 =>
            httpConn.writeResponse(respBuilder)
            httpConn.responded = true
        case _ => ()
    }
    ctx.upgraded = true
    throw WebSocketException(message)
}

/**
 * the value of Sec-WebSocket-Accept header field is constructed by concatenating key with the string
 * "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this concatenated value to obtain
 * a 20-byte value and base64-encoding this 20-byte hash
 * RFC 6455 4.2.5.5.4.
 */
func generateAcceptValue(key: String): String {
    let mdArray: Array<UInt8> = Array<UInt8>(20, repeat: 0)
    let data = unsafe { (key + GUID).rawData() }
    unsafe {
        let d: CPointerHandle<UInt8> = acquireArrayRawData(data)
        let md: CPointerHandle<UInt8>
        try {
            md = acquireArrayRawData(mdArray)
        } catch (e: Exception) {
            releaseArrayRawData(d)
            throw e
        }

        let dynMsgPtr = MallocDynMsg()
        if (dynMsgPtr.isNull()) {
            releaseArrayRawData(d)
            releaseArrayRawData(md)
            throw WebSocketException("Malloc failed.")
        }

        try {
            DYN_SHA1(d.pointer, Int32(data.size), md.pointer, dynMsgPtr)
            if (!dynMsgPtr.read().found) {
                let funcName = CString(dynMsgPtr.read().funcName).toString()
                throw WebSocketException("Can not load openssl library or function ${funcName}.")
            }
        } finally {
            FreeDynMsg(dynMsgPtr)
            releaseArrayRawData(d)
            releaseArrayRawData(md)
        }
    }
    return toBase64String(mdArray)
}

/**
 * in network byte order
 */
@OverflowWrapping
func fromInt64(number: Int64, size: Int64): Array<UInt8> {
    let bytes = Array<UInt8>(size, repeat: 0)
    for (i in 0..size) {
        bytes[i] = UInt8(number >> ((size - 1 - i) * 8))
    }
    return bytes
}

@OverflowWrapping
func fromUInt16(number: UInt16): Array<UInt8> {
    let bytes = Array<UInt8>(2, repeat: 0)
    bytes[0] = UInt8(number >> 8)
    bytes[1] = UInt8(number)
    return bytes
}

/**
 * bytes.size <= 8
 */
func toInt64(bytes: Array<UInt8>): Int64 {
    var int = 0
    for (byte in bytes) {
        int = int << 8
        int = int | Int64(byte)
    }
    return int & 0X7FFF_FFFF_FFFF_FFFF
}

func toUInt16(bytes: Array<UInt8>): UInt16 {
    var int: UInt16 = 0
    for (byte in bytes) {
        int = int << 8
        int = int | UInt16(byte)
    }
    return int
}

/**
 * the algorithm used to convert masked data into unmasked data, or vice versa
 * the same algorithm applies regardless of the direction of the translation,
 * e.g., the same steps are applied to mask the data as to unmask the data.
 * j                   = i MOD 4
 * transformed-octet-i = original-octet-i XOR masking-key-octet-j
 * RFC 6455 5.3.
 */
func maskOrUnmask(maskingKey: Array<UInt8>, bytes: Array<UInt8>): Array<UInt8> {
    let masked = Array<UInt8>(bytes.size, repeat: 0)
    for (i in 0..bytes.size) {
        masked[i] = bytes[i] ^ maskingKey[i % 4]
    }
    return masked
}