/*
 * 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.sync.{AtomicBool, AtomicInt64, Mutex, AtomicOptionReference}
import std.net.*
import std.fs.File
import stdx.net.tls.common.*
import stdx.log.{Logger, LogLevel}
import stdx.crypto.common.{Certificate, PrivateKey, getGlobalCryptoKit}

/**
 * Server Co-routine pool configurations.
 */
public struct ServicePoolConfig {
    /**
     * The max number of activate Co-routine.
     * The value must be greater than 0.
     */
    public let capacity: Int64

    /**
     * The max number of task queue, the pool reject the tasks when the queue is full.
     * The value must be greater than 0.
     */
    public let queueCapacity: Int64

    /**
     * The preheat size of Co-routine in the pool.
     * The value must between 1 and capacity.
     */
    public let preheat: Int64

    public init(
        capacity!: Int64 = SERVER_COROUTINE_POOL_CAPACITY,
        queueCapacity!: Int64 = SERVER_COROUTINE_POOL_QUEUE_CAPACITY,
        preheat!: Int64 = SERVER_COROUTINE_POOL_PREHEAT
    ) {
        this.capacity = capacity
        this.queueCapacity = queueCapacity
        this.preheat = preheat
    }
}

public class ServerBuilder {
    var _addr: ?String = None
    var _port: ?UInt16 = None
    var _listener: ?ServerSocket = None
    var _logger: Logger = mutexLogger()
    var _distributor: HttpRequestDistributor = DirectDistributor()
    var _protocolServiceFactory: ProtocolServiceFactory = ProtocolServiceFactoryImpl()
    var _transportConfig: TransportConfig = TransportConfig()
    var _tlsConfig: ?TlsConfig = None

    var _readTimeout: Duration = Duration.Max
    var _writeTimeout: Duration = Duration.Max
    var _readHeaderTimeout: Duration = Duration.Max
    var _httpKeepAliveTimeout: Duration = Duration.Max
    var _maxRequestHeaderSize: Int64 = Int64(DEFAULT_MAX_HEADER_LIST_SIZE)
    var _maxRequestBodySize: Int64 = DEFAULT_MAX_BODY_SIZE

    var _headerTableSize: UInt32 = 4096
    var _maxConcurrentStreams: UInt32 = INITIAL_MAX_CONCURRENT_STREAMS
    var _initialWindowSize: UInt32 = DEFAULT_WINDOW_SIZE
    var _maxFrameSize: UInt32 = MIN_FRAME_SIZE
    var _maxHeaderListSize: UInt32 = DEFAULT_MAX_HEADER_LIST_SIZE
    var _enableConnectProtocol: Bool = false

    var _afterBind: () -> Unit = {=>}
    var _onShutdown: () -> Unit = {=>}

    var _servicePoolConfig: ServicePoolConfig = ServicePoolConfig()

    public init() {}

    /**
     * Indicates the IP address bound to the generated server, this property must be set.
     *
     * @param addr Server IP or domain.
     * @return ServerBuilder whose addr has been set.
     */
    public func addr(addr: String): ServerBuilder {
        _addr = addr
        return this
    }

    /**
     * Indicates the port number bound to the generated server, this property must be set.
     *
     * @param port Server port.
     * @return ServerBuilder whose port has been set.
     */
    public func port(port: UInt16): ServerBuilder {
        _port = port
        return this
    }

    /**
     * ServerSocket of the generated server, a default listener will be provided.
     *
     * @param listener for listening to the bound address and port.
     * @return ServerBuilder whose listener has been set.
     */
    public func listener(listener: ServerSocket): ServerBuilder {
        _listener = listener
        return this
    }

    /**
     * Logger for the generated server, the default logger will write to Console.stdout, the default LogLevel is INFO, if set to DEBUG,  all handshake information, request, response will be logged.
     *
     * @param logger used to record and print logs.
     * @return ServerBuilder whose logger has been set.
     */
    public func logger(logger: Logger): ServerBuilder {
        _logger = logger
        return this
    }

    /**
     * Distribute the request to HttpRequestHandler by url, a default distributor will be provided.
     *
     * @param distributor used to distributes a request to the corresponding HttpRequestHandler for processing based on the path in the URL.
     * @return ServerBuilder whose distributor has been set.
     */
    public func distributor(distributor: HttpRequestDistributor): ServerBuilder {
        _distributor = distributor
        return this
    }

    /**
     * Generated Server use Protocol,StreamingSocket and HttpRequestDistributor to create ProtocolService instance, a default factory will be provided.
     *
     * @param factory used to set up the application layer configuration of the generated server.
     * @return ServerBuilder whose protocol service factory has been set.
     */
    public func protocolServiceFactory(factory: ProtocolServiceFactory): ServerBuilder {
        _protocolServiceFactory = factory
        return this
    }

    /**
     * Transport layer configuration of the generated server, the default config will be provided.
     *
     * @param config used to set up the application layer configuration of the generated server.
     * @return ServerBuilder whose transport configuration has been set.
     */
    public func transportConfig(config: TransportConfig): ServerBuilder {
        _transportConfig = config
        return this
    }

    /**
     * TLS configuration of the generated server, the default config will be provided.
     *
     * @param config configurations required to support TLS.
     * @return ServerBuilder whose TLS configuration has been set.
     */
    public func tlsConfig(config: TlsConfig): ServerBuilder {
        _tlsConfig = config
        return this
    }

    /**
     * The generated server read the entire request timeout.
     *
     * @param timeout specifies the timeout period, value Duration(0) means unlimited, the default value is 0.
     * @return ServerBuilder whose read timeout configuration has been set.
     */
    public func readTimeout(timeout: Duration): ServerBuilder {
        _readTimeout = checkDuration(timeout)
        return this
    }

    /**
     * The generated server write the entire response timeout.
     *
     * @param timeout specifies the timeout period, value Duration(0) means unlimited, the default value is 0.
     * @return ServerBuilder whose write timeout configuration has been set.
     */
    public func writeTimeout(timeout: Duration): ServerBuilder {
        _writeTimeout = checkDuration(timeout)
        return this
    }

    /**
     * The generated server read header peer request timeout.
     *
     * @param timeout specifies the timeout period, value Duration(0) means unlimited, the default value is 0.
     * @return ServerBuilder whose read header timeout configuration has been set.
     */
    public func readHeaderTimeout(timeout: Duration): ServerBuilder {
        _readHeaderTimeout = checkDuration(timeout)
        return this
    }

    /**
     * Http keep-alive timeout.
     *
     * @param timeout specifies the timeout period, value Duration(0) means unlimited, the default value is 0.
     * @return ServerBuilder whose keep-alive timeout configuration has been set.
     */
    public func httpKeepAliveTimeout(timeout: Duration): ServerBuilder {
        _httpKeepAliveTimeout = checkDuration(timeout)
        return this
    }

    /**
     * The generated server rejects requests that exceed this headers limit.
     *
     * @param size max size of headers per request, value 0 means unlimited, the default value is 0.
     * @return ServerBuilder whose max size of request header has been set.
     *
     * @throws IllegalArgumentException, if size is negative.
     */
    public func maxRequestHeaderSize(size: Int64): ServerBuilder {
        if (size < 0) {
            throw IllegalArgumentException("Headers size shouldn't be negative, got ${size}.")
        }
        _maxRequestHeaderSize = size
        return this
    }

    /**
     * The generated server rejects requests that exceed this body limit.
     *
     * @param size max size of body per request, value 0 means unlimited, the default value is 0.
     * @return ServerBuilder whose max size of request body has been set.
     *
     * @throws IllegalArgumentException, if size is negative.
     */
    public func maxRequestBodySize(size: Int64): ServerBuilder {
        if (size < 0) {
            throw IllegalArgumentException("Body size shouldn't be negative, got ${size}.")
        }
        _maxRequestBodySize = size
        return this
    }

    /**
     * HTTP2.0 Configuration
     * Max header table size for hpack encoder/decoder
     *
     * @param size max header table size for hpack encoder/decoder, the default value is 4096.
     * @return ServerBuilder whose header table size has been set.
     */
    public func headerTableSize(size: UInt32): ServerBuilder {
        _headerTableSize = size
        return this
    }

    /**
     * HTTP2.0 Configuration
     * Max number of concurrent streams per connection
     *
     * @param size max number of concurrent streams per connection, the default value is UInt32(2**31 - 1).
     * @return ServerBuilder whose max number of concurrent streams has been set.
     */
    public func maxConcurrentStreams(size: UInt32): ServerBuilder {
        _maxConcurrentStreams = size
        return this
    }

    /**
     * HTTP2.0 Configuration
     * Init window size
     *
     * @param size init window size, the default value is 65535.
     * @return ServerBuilder whose initial window size has been set.
     */
    public func initialWindowSize(size: UInt32): ServerBuilder {
        _initialWindowSize = size
        return this
    }

    /**
     * HTTP2.0 Configuration
     * Max frame size
     *
     * @param max frame size, the default value is 16384.
     * @return ServerBuilder whose max size of frame has been set.
     */
    public func maxFrameSize(size: UInt32): ServerBuilder {
        _maxFrameSize = size
        return this
    }

    /**
     * HTTP2.0 Configuration
     * Max size of header decoded by hpack decoder
     *
     * @param max size of header decoded by hpack decoder, the default value is UInt32.Max.
     * @return ServerBuilder whose max size of header list has been set.
     */
    public func maxHeaderListSize(size: UInt32): ServerBuilder {
        _maxHeaderListSize = size
        return this
    }

    /**
     * HTTP2.0 Configuration
     * Enable using CONNECT method to upgrade to websocket
     *
     * @param if the value is true, the connection can be upgraded to WebSocket, the default value is false.
     * @return ServerBuilder whose enableConnectProtocol has been set.
     */
    public func enableConnectProtocol(flag: Bool): ServerBuilder {
        _enableConnectProtocol = flag
        return this
    }

    /**
     * Register the bind callback, by default afterBind will be set to an empty function.
     *
     * @param f This callback function is called after ServerSocket bind, before ServerSocket accept.
     * @return ServerBuilder whose bind callback has been set.
     */
    public func afterBind(f: () -> Unit): ServerBuilder {
        _afterBind = f
        return this
    }

    /**
     * Register the shut down callback, by default onShutdown will be set to none.
     *
     * @param f This callback function is called when the generated server is shut down.
     * @return ServerBuilder whose shut down callback has been set.
     */
    public func onShutdown(f: () -> Unit): ServerBuilder {
        _onShutdown = f
        return this
    }

    /**
     * Service pool config, use to control the protocol-service pool size before server started.
     *
     * @param cfg ServicePoolConfig.
     * @return ServerBuilder whose service pool configuration has been set.
     */
    public func servicePoolConfig(cfg: ServicePoolConfig): ServerBuilder {
        _servicePoolConfig = cfg
        return this
    }

    /**
     * Build a server instance that configured by the builder.
     *
     * @return Server that configured by the builder.
     */
    public func build(): Server {
        if (_maxConcurrentStreams < 100) {
            _logger.warn("[ServerBuilder#build] max concurrent streams num is recommended to be over 100")
        }
        if (_initialWindowSize > MAX_WINDOW) {
            throw IllegalArgumentException("Initial window size should not exceed 2^31-1.")
        }
        if (_maxFrameSize < MIN_FRAME_SIZE || _maxFrameSize > MAX_FRAME_SIZE) {
            throw IllegalArgumentException("Max frame size should not be under 2^14 or over 2^24-1.")
        }
        var addr = ""
        var port: UInt16 = 0
        if (_listener.isNone()) {
            addr = _addr ?? throw IllegalArgumentException("The IP address number is not set.") // cjlint-ignore !G.EXP.03
            port = _port ?? throw IllegalArgumentException("The port number is not set.") // cjlint-ignore !G.EXP.03
        }
        return Server(
            _listener: _listener ?? TcpServerSocket(bindAt: IPSocketAddress(addr, port)),
            _logger: _logger,
            _distributor: _distributor,
            _protocolServiceFactory: _protocolServiceFactory,
            _transportConfig: _transportConfig,
            _tlsConfig: _tlsConfig,
            _readTimeout: _readTimeout,
            _writeTimeout: _writeTimeout,
            _readHeaderTimeout: _readHeaderTimeout,
            _httpKeepAliveTimeout: _httpKeepAliveTimeout,
            _maxRequestHeaderSize: _maxRequestHeaderSize,
            _maxRequestBodySize: _maxRequestBodySize,
            _headerTableSize: _headerTableSize,
            _maxConcurrentStreams: _maxConcurrentStreams,
            _initialWindowSize: _initialWindowSize,
            _maxFrameSize: _maxFrameSize,
            _maxHeaderListSize: _maxHeaderListSize,
            _enableConnectProtocol: _enableConnectProtocol,
            _afterBind: _afterBind,
            _onShutdown: _onShutdown,
            _servicePoolConfig: _servicePoolConfig
        )
    }
}

public class Server { // cjlint-ignore !G.ENU.01
    let pool: CoroutinePool
    let activeConns = AtomicInt64(0)
    var tlsServerSession: ?TlsSession = None

    private var _connId: UInt64 = 0
    private var callBackMutex = Mutex()

    var streamPools: ?ConcurrentRingPool<PutSafeRingPool<Any>> = None
    var arrayPool: ?ArrayPool = None

    Server(
        let _listener!: ServerSocket,
        let _logger!: Logger,
        let _distributor!: HttpRequestDistributor,
        let _protocolServiceFactory!: ProtocolServiceFactory,
        let _transportConfig!: TransportConfig,
        var _tlsConfig!: ?TlsConfig,
        let _readTimeout!: Duration,
        let _writeTimeout!: Duration,
        let _readHeaderTimeout!: Duration,
        let _httpKeepAliveTimeout!: Duration,
        let _maxRequestHeaderSize!: Int64,
        let _maxRequestBodySize!: Int64,
        let _headerTableSize!: UInt32,
        let _maxConcurrentStreams!: UInt32,
        let _initialWindowSize!: UInt32,
        let _maxFrameSize!: UInt32,
        let _maxHeaderListSize!: UInt32,
        let _enableConnectProtocol!: Bool,
        var _afterBind!: () -> Unit,
        var _onShutdown!: () -> Unit,
        let _servicePoolConfig!: ServicePoolConfig,
        let quit!: AtomicBool = AtomicBool(false)
    ) {
        pool = CoroutinePool(_servicePoolConfig.preheat, _servicePoolConfig.capacity, _servicePoolConfig.queueCapacity)
        pool.logger = _logger
        if (_tlsConfig.isSome()) {
            tlsServerSession = getGlobalTlsKit().getTlsServerSession(TLS_CTX_SESSION_NAME)
        }
    }

    /* Gets the address bound to this server. */
    public prop addr: String {
        get() {
            (listener.localAddress as IPSocketAddress)?.address.toString() ?? ""
        }
    }

    /* Gets the port bound to this server. */
    public prop port: UInt16 {
        get() {
            (listener.localAddress as IPSocketAddress)?.port ?? 0
        }
    }

    /* Gets the listener of this server. */
    public prop listener: ServerSocket {
        get() {
            _listener
        }
    }

    /* Gets the logger of this server. */
    public prop logger: Logger {
        get() {
            _logger
        }
    }

    /* Gets the distributor of this server. */
    public prop distributor: HttpRequestDistributor {
        get() {
            _distributor
        }
    }

    /* Gets the protocolServiceFactory of this server. */
    public prop protocolServiceFactory: ProtocolServiceFactory {
        get() {
            _protocolServiceFactory
        }
    }

    /* Gets the transportConfig of this server. */
    public prop transportConfig: TransportConfig {
        get() {
            _transportConfig
        }
    }

    /* Gets the tls server config, if TLS is not supported, none is returned.*/
    public func getTlsConfig(): ?TlsConfig {
        return _tlsConfig
    }

    /* Gets the readTimeout of this server. */
    public prop readTimeout: Duration {
        get() {
            _readTimeout
        }
    }

    /* Gets the writeTimeout of this server. */
    public prop writeTimeout: Duration {
        get() {
            _writeTimeout
        }
    }

    /* Gets the readHeaderTimeout of this server. */
    public prop readHeaderTimeout: Duration {
        get() {
            _readHeaderTimeout
        }
    }

    /* Gets the httpKeepAliveTimeout of this server. */
    public prop httpKeepAliveTimeout: Duration {
        get() {
            _httpKeepAliveTimeout
        }
    }

    /* Gets the maxRequestHeaderSize of this server. */
    public prop maxRequestHeaderSize: Int64 {
        get() {
            _maxRequestHeaderSize
        }
    }

    /* Gets the maxRequestBodySize of this server. */
    public prop maxRequestBodySize: Int64 {
        get() {
            _maxRequestBodySize
        }
    }

    /* Gets the headerTableSize of this server. */
    public prop headerTableSize: UInt32 {
        get() {
            _headerTableSize
        }
    }

    /* Gets the maxConcurrentStreams of this server. */
    public prop maxConcurrentStreams: UInt32 {
        get() {
            _maxConcurrentStreams
        }
    }

    /* Gets the initialWindowSize of this server. */
    public prop initialWindowSize: UInt32 {
        get() {
            _initialWindowSize
        }
    }

    /* Gets the maxFrameSize of this server. */
    public prop maxFrameSize: UInt32 {
        get() {
            _maxFrameSize
        }
    }

    /* Gets the maxHeaderListSize of this server. */
    public prop maxHeaderListSize: UInt32 {
        get() {
            _maxHeaderListSize
        }
    }

    /* Gets the enableConnectProtocol of this server. */
    public prop enableConnectProtocol: Bool {
        get() {
            _enableConnectProtocol
        }
    }

    /* Gets the servicePoolConfig of this server. */
    public prop servicePoolConfig: ServicePoolConfig {
        get() {
            _servicePoolConfig
        }
    }

    /*
     * Enable the service on the server.
     *
     * @throws SocketException, if the port to fail in listening.
     */
    public func serve(): Unit {
        listener.bind()
        httpLogDebug(logger, "[Server#serve] bindAndListen(${addr}, ${port})")
        synchronized(callBackMutex) {
            _afterBind()
        }
        let maxConns = _servicePoolConfig.capacity + _servicePoolConfig.queueCapacity
        while (!quit.load()) {
            // Pause accepting when at capacity to avoid accepting connections only to immediately close them
            if (activeConns.load() >= maxConns) {
                sleep(10 * Duration.millisecond)
                continue
            }
            // try accept
            let conn: StreamingSocket
            try {
                conn = listener.accept(timeout: 100 * Duration.millisecond)
            } catch (e: SocketTimeoutException) {
                continue
            } catch (e: SocketException) {
                if (!quit.load()) {
                    throw e
                } else {
                    return
                }
            }

            try {
                increaseConnId() // count connection id for logger
                if (logger.enabled(LogLevel.DEBUG)) {
                    httpLogDebug(logger,
                        "[conn#${_connId}] [Server#serve] accept a client connection, local addr: ${conn.localAddress}, remote addr: ${conn.remoteAddress}"
                    )
                }

                /*
                 * Since the Server#close may conflict with Worker#run, the AtomicOptionReference is used to store the protocol-service instance.
                 */
                let psRef = AtomicOptionReference<ProtocolService>()
                activeConns.fetchAdd(1)
                pool.submit<Unit>(
                    {
                        =>
                        httpLogDebug(logger, "[Server#serve] serve client connection begin")
                        try {
                            setTransportConfig(conn)
                            let ps = protocolService(conn)
                            psRef.store(ps)
                            ps.serve()
                        } catch (e: Exception) {
                            httpLogWarn(logger, "[Server#serve] failed to serve a client connection, ${e}")
                            conn.close()
                        }
                        activeConns.fetchSub(1)
                        httpLogDebug(logger, "[Server#serve] serve client connection end")
                    },
                    connId: _connId,
                    onClose: {
                        => if (let Some(serv) <- psRef.load()) {
                            serv.close()
                        } else {
                            conn.close()
                        }
                    },
                    onCloseGracefully: {
                        => if (let Some(serv) <- psRef.load()) {
                            serv.closeGracefully()
                        } else {
                            conn.close()
                        }
                    }
                )
            } catch (e: Exception) {
                httpLogWarn(logger,
                    "[conn#${_connId}] [Server#serve] failed to submit task servicing a client connection, ${e}")
                activeConns.fetchSub(1)
                conn.close()
            }
        }
    }

    public func close(): Unit {
        httpLogDebug(logger, "[Server#close] Server closing...")
        if (quit.load()) {
            httpLogDebug(logger, "[Server#close] Server already closed")
            return
        }
        quit.store(true)
        synchronized(callBackMutex) {
            _onShutdown()
        }
        pool.close()
        _listener.close()
        streamPools?.close()
        arrayPool?.close()
        httpLogDebug(logger, "[Server#close] Server closed")
    }

    public func closeGracefully(): Unit {
        httpLogDebug(logger, "[Server#closeGracefully] Server closing...")
        if (quit.load()) {
            httpLogDebug(logger, "[Server#closeGracefully] Server already closed")
            return
        }
        quit.store(true)
        synchronized(callBackMutex) {
            _onShutdown()
        }
        pool.closeGracefully()
        _listener.close()
        streamPools?.close()
        arrayPool?.close()
        httpLogDebug(logger, "[Server#closeGracefully] Server closed")
    }

    public func afterBind(f: () -> Unit): Unit {
        synchronized(callBackMutex) {
            _afterBind = f
        }
    }

    public func onShutdown(f: () -> Unit): Unit {
        synchronized(callBackMutex) {
            _onShutdown = f
        }
    }

    /**
     * Update cert dynamically.
     *
     * @param certificateChainFile certificate chain file
     * @param privateKeyFile private key file
     *
     * @throws IllegalArgumentException, while certificateChainFile contains null character,
     * or privateKeyFile contains null character.
     * @throws HttpException, while the TLS certificate is not configured.
     */
    public func updateCert(certificateChainFile: String, privateKeyFile: String): Unit {
        var cfg = _tlsConfig ?? throw HttpException("The TLS certificate is not configured.")
        cfg.certificate = (certificateFromFile(certificateChainFile), privateKeyFromFile(privateKeyFile))
        _tlsConfig = cfg
    }

    /**
     * Update cert dynamically.
     *
     * @param certChain server certificate
     * @param certKey server certificate corresponding private key
     *
     * @throws HttpException, while the TLS certificate is not configured.
     */
    public func updateCert(certChain: Array<Certificate>, certKey: PrivateKey): Unit {
        var cfg = _tlsConfig ?? throw HttpException("The TLS certificate is not configured.")
        cfg.certificate = (certChain, certKey)
        _tlsConfig = cfg
    }

    /**
     * Update ca dynamically.
     *
     * @param newCaFile CA certificate file
     *
     * @throws IllegalArgumentException, while caFile contains null character.
     * @throws HttpException, while the TLS certificate is not configured.
     */
    public func updateCA(newCaFile: String): Unit {
        var cfg = _tlsConfig ?? throw HttpException("The TLS certificate is not configured.")
        if (!newCaFile.isEmpty()) {
            let certificates = certificateFromFile(newCaFile)
            cfg.verifyMode = CertificateVerifyMode.CustomCA(certificates)
        }
        _tlsConfig = cfg
        if (logger.enabled(LogLevel.DEBUG)) {
            httpLogDebug(logger, "[Server#updateCA] CA updated successfully")
        }
    }

    /**
     * Update ca dynamically.
     *
     * @param newCa CA certificate
     *
     * @throws HttpException, while the TLS certificate is not configured.
     */
    public func updateCA(newCa: Array<Certificate>): Unit {
        var cfg = _tlsConfig ?? throw HttpException("The TLS certificate is not configured.")
        cfg.verifyMode = CustomCA(newCa)
        _tlsConfig = cfg
        if (logger.enabled(LogLevel.DEBUG)) {
            httpLogDebug(logger, "[Server#updateCA] CA updated successfully")
        }
    }

    func protocolService(socket: StreamingSocket): ProtocolService {
        let (protocol, conn) = match ((socket as TlsConnection, _tlsConfig)) {
            case (Some(conn), _) =>
                if (logger.enabled(LogLevel.TRACE)) {
                    httpLogTrace(logger, "[Server#protocolService] Got a TLS socket.")
                }
                (alpn(conn.handshakeResult.getOrThrow().alpnProtocol), socket)
            case (_, Some(cfg)) =>
                if (logger.enabled(LogLevel.TRACE)) {
                    httpLogTrace(logger, "[Server#protocolService] Got a TLS configuration, ready for handshake.")
                }
                let conn = getGlobalTlsKit().getTlsServer(socket, cfg, session: tlsServerSession)
                let result = conn.handshake(timeout: Duration.second * 3)

                // TLS handshake result
                if (logger.enabled(LogLevel.TRACE)) {
                    httpLogTrace(logger, "[Server#protocolService] TLS handshake result: TLS version = ${result.version}, cipher suite = ${result.cipherSuite}.")
                }

                (alpn(result.alpnProtocol), conn)
            case (_, _) => (HTTP1_1, socket)
        }

        if (logger.enabled(LogLevel.TRACE)) {
            httpLogTrace(logger, "[Server#protocolService] Protocol: ${protocol}.")
        }

        let service = protocolServiceFactory.create(protocol, conn)
        service.server = this
        return service
    }

    private func alpn(alpnProtocolName: ?String): Protocol {
        // cjlint-ignore -start !G.OTH.03
        // alpn protocol ids ref: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
        // cjlint-ignore -end
        return match (alpnProtocolName) {
            case Some("h2") => HTTP2_0
            case Some("http/1.1") | None => HTTP1_1
            case Some("http/1.0") => HTTP1_0
            case Some(v) => UnknownProtocol(v)
        }
    }

    /**
     * set readTimeout\writeTimeout\keepAlive\readBufferSize\writeBufferSize
     */
    func setTransportConfig(conn: StreamingSocket): Unit {
        match (conn) {
            case conn: TcpSocket =>
                conn.readTimeout = transportConfig.readTimeout
                conn.writeTimeout = transportConfig.writeTimeout
                conn.keepAlive = transportConfig.keepAliveConfig
                if (let Some(wb) <- transportConfig.writeBufferSize) {
                    conn.sendBufferSize = wb
                }
                if (let Some(rb) <- transportConfig.readBufferSize) {
                    conn.receiveBufferSize = rb
                }
            case _ => ()
        }
    }

    @OverflowWrapping
    private func increaseConnId(): Unit {
        _connId++ // count connection id for logger
    }

    func certificateFromFile(path: String): Array<Certificate> {
        getGlobalCryptoKit().certificateFromPem(String.fromUtf8(File.readFrom(path))).map({c => c})
    }

    func privateKeyFromFile(path: String): PrivateKey {
        getGlobalCryptoKit().privateKeyFromPem(String.fromUtf8(File.readFrom(path)))
    }
}