/****************************************************************************
 Copyright (c) 2010-2012 cocos2d-x.org
 Copyright (c) 2013-2016 Chukong Technologies Inc.
 Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.

 http://www.cocos.com

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights to
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 of the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
****************************************************************************/

/***************************************************************************
* "[WebSocket module] is based in part on the work of the libwebsockets  project
* (http://libwebsockets.org)"
*****************************************************************************/

// clang-format off
#include "base/Macros.h"
#include "base/memory/Memory.h"
#include "uv.h"
// clang-format on

#if __OHOS__ || __LINUX__ || __QNX__
    #include "libwebsockets.h"
#else
    #include "websockets/libwebsockets.h"
#endif

#include <algorithm>
#include <atomic>
#include <cerrno>
#include <condition_variable>
#include <csignal>
#include <memory> // for std::shared_ptr
#include <mutex>
#include <thread>
#include "application/ApplicationManager.h"
#include "base/Scheduler.h"
#include "base/std/container/list.h"
#include "base/std/container/queue.h"
#include "base/std/container/string.h"
#include "network/Uri.h"
#include "network/WebSocket.h"

#include "platform/FileUtils.h"
#include "platform/StdC.h"

#define NS_NETWORK_BEGIN \
    namespace cc {       \
    namespace network {
#define NS_NETWORK_END \
    }                  \
    }

#define WS_RX_BUFFER_SIZE              (65536)
#define WS_RESERVE_RECEIVE_BUFFER_SIZE (4096)

#if CC_PLATFORM == CC_PLATFORM_ANDROID
    #define WS_ENABLE_LIBUV 1
#else
    #define WS_ENABLE_LIBUV 0
#endif

#ifdef LOG_TAG
    #undef LOG_TAG
#endif
#define LOG_TAG "WebSocket.cpp"

struct lws;
struct LwsProtocols;
struct lws_vhost;

#if (CC_PLATFORM == CC_PLATFORM_WINDOWS)
// log, CC_LOG_DEBUG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output
// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output.

//IDEA: Move _winLog, winLog to a separated file
static void _winLog(const char *format, va_list args) {
    static const int MAX_LOG_LENGTH = 16 * 1024;
    int bufferSize = MAX_LOG_LENGTH;
    char *buf = nullptr;

    do {
        buf = ccnew char[bufferSize];
        if (buf == nullptr)
            return; // not enough memory

        int ret = vsnprintf(buf, bufferSize - 3, format, args);
        if (ret < 0) {
            bufferSize *= 2;

            delete[] buf;
        } else
            break;

    } while (true);

    strcat(buf, "\n");

    int pos = 0;
    int len = strlen(buf);
    char tempBuf[MAX_LOG_LENGTH + 1] = {0};
    WCHAR wszBuf[MAX_LOG_LENGTH + 1] = {0};

    do {
        std::copy(buf + pos, buf + pos + MAX_LOG_LENGTH, tempBuf);

        tempBuf[MAX_LOG_LENGTH] = 0;

        MultiByteToWideChar(CP_UTF8, 0, tempBuf, -1, wszBuf, sizeof(wszBuf));
        OutputDebugStringW(wszBuf);

        pos += MAX_LOG_LENGTH;

    } while (pos < len);

    delete[] buf;
}

static void wsLog(const char *format, ...) {
    va_list args;
    va_start(args, format);
    _winLog(format, args);
    va_end(args);
}

#else
    #define wsLog printf //NOLINT
#endif

#define DO_QUOTEME(x) #x
#define QUOTEME(x)    DO_QUOTEME(x)

// Since CC_LOG_DEBUG isn't thread safe, we uses LOGD for multi-thread logging.
#if CC_PLATFORM == CC_PLATFORM_ANDROID
    #if CC_DEBUG > 0
        #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    #else
        #define LOGD(...)
    #endif

    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#elif defined(__OHOS__)
    #include "cocos/base/Log.h"
    #define LOGD(...) CC_LOG_DEBUG(__VA_ARGS__)
    #define LOGE(...) CC_LOG_ERROR(__VA_ARGS__)
#else
    #if CC_DEBUG > 0
        #define LOGD(fmt, ...) wsLog("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
    #else
        #define LOGD(fmt, ...)
    #endif

    #define LOGE(fmt, ...) wsLog("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__)
#endif

static void printWebSocketLog(int level, const char *line) {
#if CC_DEBUG > 0
    static const char *const LOG_LEVEL_NAMES[] = {
        "ERR",
        "WARN",
        "NOTICE",
        "INFO",
        "DEBUG",
        "PARSER",
        "HEADER",
        "EXTENSION",
        "CLIENT",
        "LATENCY",
    };

    char buf[30] = {0};
    int n;

    for (n = 0; n < LLL_COUNT; n++) {
        if (level != (1 << n)) {
            continue;
        }
        sprintf(buf, "%s: ", LOG_LEVEL_NAMES[n]);
        break;
    }

    LOGD("%s%s\n", buf, line);

#endif // #if CC_DEBUG > 0
}

class WebSocketImpl {
public:
    static void closeAllConnections();
    explicit WebSocketImpl(cc::network::WebSocket *ws);
    ~WebSocketImpl();

    bool init(const cc::network::WebSocket::Delegate &delegate,
              const ccstd::string &url,
              const ccstd::vector<ccstd::string> *protocols = nullptr,
              const ccstd::string &caFilePath = "");

    void send(const ccstd::string &message);
    void send(const unsigned char *binaryMsg, unsigned int len);
    void close();
    void closeAsync();
    void closeAsync(int code, const ccstd::string &reason);
    cc::network::WebSocket::State getReadyState() const;
    const ccstd::string &getUrl() const;
    const ccstd::string &getProtocol() const;
    cc::network::WebSocket::Delegate *getDelegate() const;

    size_t getBufferedAmount() const;
    ccstd::string getExtensions() const;

private:
    // The following callback functions are invoked in websocket thread
    void onClientOpenConnectionRequest();
    int onSocketCallback(struct lws *wsi, enum lws_callback_reasons reason, void *in, ssize_t len);

    int onClientWritable();
    int onClientReceivedData(void *in, ssize_t len);
    int onConnectionOpened();
    int onConnectionError();
    int onConnectionClosed(uint16_t code, const ccstd::string &reason);

    struct lws_vhost *createVhost(struct lws_protocols *protocols, int *sslConnection);

    cc::network::WebSocket *_ws;
    cc::network::WebSocket::State _readyState;
    std::mutex _readyStateMutex;
    ccstd::string _url;
    ccstd::vector<char> _receivedData;

    struct lws *_wsInstance;
    struct lws_protocols *_lwsProtocols;
    ccstd::string _clientSupportedProtocols;
    ccstd::string _selectedProtocol;

    std::shared_ptr<std::atomic<bool>> _isDestroyed;
    cc::network::WebSocket::Delegate *_delegate;

    std::mutex _closeMutex;
    std::condition_variable _closeCondition;

    ccstd::vector<ccstd::string> _enabledExtensions;

    enum class CloseState {
        NONE,
        SYNC_CLOSING,
        SYNC_CLOSED,
        ASYNC_CLOSING
    };
    CloseState _closeState;

    ccstd::string _caFilePath;

    friend class WsThreadHelper;
    friend class WebSocketCallbackWrapper;
};

enum WsMsg {
    WS_MSG_TO_SUBTRHEAD_SENDING_STRING = 0,
    WS_MSG_TO_SUBTRHEAD_SENDING_BINARY,
    WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION
};

class WsThreadHelper;

static ccstd::vector<WebSocketImpl *> *websocketInstances{nullptr};
static std::recursive_mutex instanceMutex;
static struct lws_context *wsContext{nullptr};
static WsThreadHelper *wsHelper{nullptr};
static std::atomic_bool wsPolling{false};

#if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_OPENHARMONY)
static ccstd::string getFileNameForPath(const ccstd::string &filePath) {
    ccstd::string fileName = filePath;
    const size_t lastSlashIdx = fileName.find_last_of("\\/");
    if (ccstd::string::npos != lastSlashIdx) {
        fileName.erase(0, lastSlashIdx + 1);
    }
    return fileName;
}
#endif

static struct lws_protocols defaultProtocols[2];

static lws_context_creation_info convertToContextCreationInfo(const struct lws_protocols *protocols, bool peerServerCert) {
    lws_context_creation_info info;
    memset(&info, 0, sizeof(info));
    /*
     * create the websocket context.  This tracks open connections and
     * knows how to route any traffic and which protocol version to use,
     * and if each connection is client or server side.
     *
     * For this client-only demo, we tell it to not listen on any port.
     */

    info.port = CONTEXT_PORT_NO_LISTEN;
    info.protocols = protocols;

    // IDEA: Disable 'permessage-deflate' extension temporarily because of issues:
    // https://github.com/cocos2d/cocos2d-x/issues/16045, https://github.com/cocos2d/cocos2d-x/issues/15767
    // libwebsockets issue: https://github.com/warmcat/libwebsockets/issues/593
    // Currently, we couldn't find out the exact reason.
    // libwebsockets official said it's probably an issue of user code
    // since 'libwebsockets' passed AutoBahn stressed Test.

    //    info.extensions = exts;

    info.gid = -1;
    info.uid = -1;
    if (peerServerCert) {
        info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
    } else {
        info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED;
    }
#if WS_ENABLE_LIBUV
    info.options |= LWS_SERVER_OPTION_LIBUV;
#endif
    info.user = nullptr;

    return info;
}

class WsMessage {
public:
    WsMessage() : id(++idCount) {}
    unsigned int id;
    unsigned int what{0}; // message type
    cc::network::WebSocket::Data *data{nullptr};
    void *user{nullptr};

private:
    static unsigned int idCount;
};

unsigned int WsMessage::idCount = 0;

/**
 *  @brief Websocket thread helper, it's used for sending message between UI thread and websocket thread.
 */
class WsThreadHelper {
public:
    WsThreadHelper();
    ~WsThreadHelper();

    // Creates a new thread
    bool createWebSocketThread();
    // Quits websocket thread.
    void quitWebSocketThread();

    // Sends message to Cocos thread. It's needed to be invoked in Websocket thread.
    static void sendMessageToCocosThread(const std::function<void()> &cb);

    // Sends message to Websocket thread. It's needs to be invoked in Cocos thread.
    void sendMessageToWebSocketThread(WsMessage *msg);

    size_t countBufferedBytes(const WebSocketImpl *ws);

    // Waits the sub-thread (websocket thread) to exit,
    void joinWebSocketThread() const;

    static void onSubThreadStarted();
    static void onSubThreadLoop();
    static void onSubThreadEnded();

protected:
    void wsThreadEntryFunc() const;

public:
    ccstd::list<WsMessage *> *_subThreadWsMessageQueue;
    std::mutex _subThreadWsMessageQueueMutex;
    std::thread *_subThreadInstance{nullptr};

private:
    bool _needQuit{false};
};

// Wrapper for converting websocket callback from static function to member function of WebSocket class.
class WebSocketCallbackWrapper {
public:
    static int onSocketCallback(struct lws *wsi, enum lws_callback_reasons reason, void * /*user*/, void *in, size_t len) {
        // Gets the user data from context. We know that it's a 'WebSocket' instance.
        if (wsi == nullptr) {
            return 0;
        }
        int ret = 0;
        {
            std::lock_guard<std::recursive_mutex> lk(instanceMutex);
            auto *ws = static_cast<WebSocketImpl *>(lws_wsi_user(wsi));
            if (ws != nullptr && websocketInstances != nullptr) {
                if (std::find(websocketInstances->begin(), websocketInstances->end(), ws) != websocketInstances->end()) {
                    ret = ws->onSocketCallback(wsi, reason, in, len);
                }
            } else {
                //            LOGD("ws instance is nullptr.\n");
            }
        }

        return ret;
    }
};

// Implementation of WsThreadHelper
WsThreadHelper::WsThreadHelper()

{
    _subThreadWsMessageQueue = ccnew ccstd::list<WsMessage *>();
}

WsThreadHelper::~WsThreadHelper() {
    joinWebSocketThread();
    CC_SAFE_DELETE(_subThreadInstance);
    delete _subThreadWsMessageQueue;
}

bool WsThreadHelper::createWebSocketThread() {
    // Creates websocket thread
    _subThreadInstance = ccnew std::thread(&WsThreadHelper::wsThreadEntryFunc, this);
    return true;
}

void WsThreadHelper::quitWebSocketThread() {
    _needQuit = true;
}

void WsThreadHelper::onSubThreadLoop() {
    if (wsContext) {
        //        _readyStateMutex.unlock();
        wsHelper->_subThreadWsMessageQueueMutex.lock();
        bool isEmpty = wsHelper->_subThreadWsMessageQueue->empty();

        if (!isEmpty) {
            auto iter = wsHelper->_subThreadWsMessageQueue->begin();
            for (; iter != wsHelper->_subThreadWsMessageQueue->end();) {
                auto *msg = (*iter);
                auto *ws = static_cast<WebSocketImpl *>(msg->user);
                // REFINE: ws may be a invalid pointer
                if (msg->what == WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION) {
                    ws->onClientOpenConnectionRequest();
                    delete *iter;
                    iter = wsHelper->_subThreadWsMessageQueue->erase(iter);
                } else {
                    ++iter;
                }
            }
        }
        wsHelper->_subThreadWsMessageQueueMutex.unlock();
        // Windows: Cause delay 40ms for event WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION
        // Android: Let libuv lws to decide when to stop
        wsPolling = true;
        lws_service(wsContext, WS_ENABLE_LIBUV ? 40 : 4);
        #if CC_PLATFORM == CC_PLATFORM_OPENHARMONY
            std::this_thread::sleep_for(std::chrono::milliseconds(3));
        #endif
        wsPolling = false;
    }
}

void WsThreadHelper::onSubThreadStarted() {
    int logLevel = LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO /* | LLL_DEBUG | LLL_PARSER | LLL_HEADER*/ | LLL_EXT | LLL_CLIENT | LLL_LATENCY;
    lws_set_log_level(logLevel, printWebSocketLog);

    memset(defaultProtocols, 0, 2 * sizeof(struct lws_protocols));

    defaultProtocols[0].name = "";
    defaultProtocols[0].callback = WebSocketCallbackWrapper::onSocketCallback;
    defaultProtocols[0].rx_buffer_size = WS_RX_BUFFER_SIZE;
    defaultProtocols[0].id = std::numeric_limits<uint32_t>::max();

    lws_context_creation_info creationInfo = convertToContextCreationInfo(defaultProtocols, true);
    wsContext = lws_create_context(&creationInfo);
#if WS_ENABLE_LIBUV
    if (lws_uv_initloop(wsContext, nullptr, 0)) {
        LOGE("WsThreadHelper: failed to init libuv");
    }
#endif
}

void WsThreadHelper::onSubThreadEnded() {
    if (wsContext != nullptr) {
        lws_context_destroy(wsContext);
#if WS_ENABLE_LIBUV
        lws_context_destroy2(wsContext);
#endif
    }
}

void WsThreadHelper::wsThreadEntryFunc() const {
    LOGD("WebSocket thread start, helper instance: %p\n", this);
    onSubThreadStarted();

    while (!_needQuit) {
        onSubThreadLoop();
    }

    onSubThreadEnded();

    LOGD("WebSocket thread exit, helper instance: %p\n", this);
}

void WsThreadHelper::sendMessageToCocosThread(const std::function<void()> &cb) {
    if (CC_CURRENT_APPLICATION() != nullptr) {
        CC_CURRENT_APPLICATION()->getEngine()->getScheduler()->performFunctionInCocosThread(cb);
    }
}

void WsThreadHelper::sendMessageToWebSocketThread(WsMessage *msg) {
    std::lock_guard<std::mutex> lk(_subThreadWsMessageQueueMutex);
    _subThreadWsMessageQueue->push_back(msg);
}

size_t WsThreadHelper::countBufferedBytes(const WebSocketImpl *ws) {
    std::lock_guard<std::mutex> lk(_subThreadWsMessageQueueMutex);
    size_t total = 0;
    for (auto *msg : *_subThreadWsMessageQueue) {
        if (msg->user == ws && msg->data && (msg->what == WS_MSG_TO_SUBTRHEAD_SENDING_STRING || msg->what == WS_MSG_TO_SUBTRHEAD_SENDING_BINARY)) {
            total += msg->data->getRemain();
        }
    }
    return total;
}

void WsThreadHelper::joinWebSocketThread() const {
    if (_subThreadInstance->joinable()) {
        _subThreadInstance->join();
    }
}

// Define a WebSocket frame
class WebSocketFrame {
public:
    bool init(unsigned char *buf, ssize_t len) {
        if (buf == nullptr && len > 0) {
            return false;
        }

        if (!_data.empty()) {
            LOGD("WebSocketFrame was initialized, should not init it again!\n");
            return false;
        }

        _data.resize(LWS_PRE + len);
        if (len > 0) {
            std::copy(buf, buf + len, _data.begin() + LWS_PRE);
        }

        _payload = _data.data() + LWS_PRE;
        _payloadLength = len;
        _frameLength = len;
        return true;
    }

    void update(ssize_t issued) {
        _payloadLength -= issued;
        _payload += issued;
    }

    unsigned char *getPayload() const { return _payload; }
    ssize_t getPayloadLength() const { return _payloadLength; }
    ssize_t getFrameLength() const { return _frameLength; }

private:
    unsigned char *_payload{nullptr};
    ssize_t _payloadLength{0};

    ssize_t _frameLength{0};
    ccstd::vector<unsigned char> _data;
};

//

void WebSocketImpl::closeAllConnections() {
    if (websocketInstances != nullptr) {
        ssize_t count = websocketInstances->size();
        for (ssize_t i = count - 1; i >= 0; i--) {
            WebSocketImpl *instance = websocketInstances->at(i);
            instance->close();
        }

        std::lock_guard<std::recursive_mutex> lk(instanceMutex);
        websocketInstances->clear();
        delete websocketInstances;
        websocketInstances = nullptr;
    }
}

WebSocketImpl::WebSocketImpl(cc::network::WebSocket *ws)
: _ws(ws),
  _readyState(cc::network::WebSocket::State::CONNECTING),
  _wsInstance(nullptr),
  _lwsProtocols(nullptr),
  _isDestroyed(std::make_shared<std::atomic<bool>>(false)),
  _delegate(nullptr),
  _closeState(CloseState::NONE) {
    // reserve data buffer to avoid allocate memory frequently
    _receivedData.reserve(WS_RESERVE_RECEIVE_BUFFER_SIZE);

    {
        std::lock_guard<std::recursive_mutex> lk(instanceMutex);
        if (websocketInstances == nullptr) {
            websocketInstances = ccnew ccstd::vector<WebSocketImpl *>();
        }
        websocketInstances->push_back(this);
    }

    // NOTE: !!! Be careful while merging cocos2d-x-lite back to cocos2d-x. !!!
    // 'close' is a synchronous operation which may wait some seconds to make sure connection is closed.
    // But JSB doesn't need to listen on EVENT_RESET event to close connection,
    // since finalize callback (refer to 'WebSocket_finalize' function in jsb_websocket.cpp) will invoke 'closeAsync'.
    //
    //    std::shared_ptr<std::atomic<bool>> isDestroyed = _isDestroyed;
    //    _resetDirectorListener = cc::Director::getInstance()->getEventDispatcher()->addCustomEventListener(cc::Director::EVENT_RESET, [this, isDestroyed](cc::EventCustom*){
    //        if (*isDestroyed)
    //            return;
    //        close();
    //    });
}

WebSocketImpl::~WebSocketImpl() {
    LOGD("In the destructor of WebSocket (%p)\n", this);

    std::unique_lock<std::recursive_mutex> lk(instanceMutex);

    if (websocketInstances != nullptr) {
        auto iter = std::find(websocketInstances->begin(), websocketInstances->end(), this);
        if (iter != websocketInstances->end()) {
            websocketInstances->erase(iter);
        } else {
            LOGD("ERROR: WebSocket instance (%p) wasn't added to the container which saves websocket instances!\n", this);
        }
    }

    if (websocketInstances == nullptr || websocketInstances->empty()) {
        lk.unlock();
        wsHelper->quitWebSocketThread();
        LOGD("before join ws thread\n");
        wsHelper->joinWebSocketThread();
        LOGD("after join ws thread\n");

        CC_SAFE_DELETE(wsHelper);
    }

    // NOTE: Refer to the comment in constructor!!!
    //    cc::Director::getInstance()->getEventDispatcher()->removeEventListener(_resetDirectorListener);

    *_isDestroyed = true;
}

bool WebSocketImpl::init(const cc::network::WebSocket::Delegate &delegate,
                         const ccstd::string &url,
                         const ccstd::vector<ccstd::string> *protocols /* = nullptr*/,
                         const ccstd::string &caFilePath /* = ""*/) {
    _delegate = const_cast<cc::network::WebSocket::Delegate *>(&delegate);
    _url = url;
    _caFilePath = caFilePath;

    if (_url.empty()) {
        return false;
    }

    if (protocols != nullptr && !protocols->empty()) {
        size_t size = protocols->size();
        _lwsProtocols = static_cast<struct lws_protocols *>(malloc((size + 1) * sizeof(struct lws_protocols)));
        memset(_lwsProtocols, 0, (size + 1) * sizeof(struct lws_protocols));

        static uint32_t wsId = 0;

        for (size_t i = 0; i < size; ++i) {
            _lwsProtocols[i].callback = WebSocketCallbackWrapper::onSocketCallback;
            size_t nameLen = protocols->at(i).length();
            char *name = static_cast<char *>(malloc(nameLen + 1));
            name[nameLen] = '\0';
            strcpy(name, protocols->at(i).c_str());
            _lwsProtocols[i].name = name;
            _lwsProtocols[i].id = ++wsId;
            _lwsProtocols[i].rx_buffer_size = WS_RX_BUFFER_SIZE;
            _lwsProtocols[i].per_session_data_size = 0;
            _lwsProtocols[i].user = nullptr;

            _clientSupportedProtocols += name;
            if (i < (size - 1)) {
                _clientSupportedProtocols += ",";
            }
        }
    }

    bool isWebSocketThreadCreated = true;
    if (wsHelper == nullptr) {
        wsHelper = ccnew WsThreadHelper();
        isWebSocketThreadCreated = false;
    }

    auto *msg = ccnew WsMessage();
    msg->what = WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION;
    msg->user = this;
    wsHelper->sendMessageToWebSocketThread(msg);

    // fixed https://github.com/cocos2d/cocos2d-x/issues/17433
    // createWebSocketThread has to be after message WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION was sent.
    // And websocket thread should only be created once.
    if (!isWebSocketThreadCreated) {
        wsHelper->createWebSocketThread();
    }

#if WS_ENABLE_LIBUV
    if (wsContext && wsPolling) {
        auto *loop = lws_uv_getloop(wsContext, 0);
        if (loop) {
            uv_stop(loop);
        }
    }
#endif

    return true;
}

size_t WebSocketImpl::getBufferedAmount() const {
    return wsHelper->countBufferedBytes(this);
}

ccstd::string WebSocketImpl::getExtensions() const {
    //join vector with ";"
    if (_enabledExtensions.empty()) return "";
    ccstd::string ret;
    for (const auto &enabledExtension : _enabledExtensions) ret += (enabledExtension + "; ");
    ret += _enabledExtensions[_enabledExtensions.size() - 1];
    return ret;
}

void WebSocketImpl::send(const ccstd::string &message) {
    if (_readyState == cc::network::WebSocket::State::OPEN) {
        // In main thread
        auto *data = ccnew cc::network::WebSocket::Data();
        data->bytes = static_cast<char *>(malloc(message.length() + 1));
        // Make sure the last byte is '\0'
        data->bytes[message.length()] = '\0';
        strcpy(data->bytes, message.c_str());
        data->len = static_cast<ssize_t>(message.length());

        auto *msg = ccnew WsMessage();
        msg->what = WS_MSG_TO_SUBTRHEAD_SENDING_STRING;
        msg->data = data;
        msg->user = this;
        wsHelper->sendMessageToWebSocketThread(msg);
    } else {
        LOGD("Couldn't send message since websocket wasn't opened!\n");
    }
}

void WebSocketImpl::send(const unsigned char *binaryMsg, unsigned int len) {
    if (_readyState == cc::network::WebSocket::State::OPEN) {
        // In main thread
        auto *data = ccnew cc::network::WebSocket::Data();
        if (len == 0) {
            // If data length is zero, allocate 1 byte for safe.
            data->bytes = static_cast<char *>(malloc(1));
            data->bytes[0] = '\0';
        } else {
            data->bytes = static_cast<char *>(malloc(len));
            memcpy(data->bytes, binaryMsg, len);
        }
        data->len = len;

        auto *msg = ccnew WsMessage();
        msg->what = WS_MSG_TO_SUBTRHEAD_SENDING_BINARY;
        msg->data = data;
        msg->user = this;
        wsHelper->sendMessageToWebSocketThread(msg);
    } else {
        LOGD("Couldn't send message since websocket wasn't opened!\n");
    }
}

void WebSocketImpl::close() {
    if (_closeState != CloseState::NONE) {
        LOGD("close was invoked, don't invoke it again!\n");
        return;
    }

    _closeState = CloseState::SYNC_CLOSING;
    LOGD("close: WebSocket (%p) is closing...\n", this);
    {
        _readyStateMutex.lock();
        if (_readyState == cc::network::WebSocket::State::CLOSED) {
            // If readState is closed, it means that onConnectionClosed was invoked in websocket thread,
            // but the callback of performInCocosThread has not been triggered. We need to invoke
            // onClose to release the websocket instance.
            _readyStateMutex.unlock();
            _delegate->onClose(_ws, 1000, "close_normal", true);
            return;
        }

        _readyState = cc::network::WebSocket::State::CLOSING;
        _readyStateMutex.unlock();
    }

    {
        std::unique_lock<std::mutex> lkClose(_closeMutex);
        _closeCondition.wait(lkClose);
        _closeState = CloseState::SYNC_CLOSED;
    }

    // Wait 5 milliseconds for onConnectionClosed to exit!
    std::this_thread::sleep_for(std::chrono::milliseconds(5));
    _delegate->onClose(_ws, 1000, "close_normal", true);
}

void WebSocketImpl::closeAsync(int code, const ccstd::string &reason) {
    if (_wsInstance) {
        lws_close_reason(_wsInstance, static_cast<lws_close_status>(code), reinterpret_cast<unsigned char *>(const_cast<char *>(reason.c_str())), reason.length());
    }
    closeAsync();
}

void WebSocketImpl::closeAsync() {
    if (_closeState != CloseState::NONE) {
        LOGD("close was invoked, don't invoke it again!\n");
        return;
    }

    _closeState = CloseState::ASYNC_CLOSING;

    LOGD("closeAsync: WebSocket (%p) is closing...\n", this);
    std::lock_guard<std::mutex> lk(_readyStateMutex);
    if (_readyState == cc::network::WebSocket::State::CLOSED || _readyState == cc::network::WebSocket::State::CLOSING) {
        LOGD("closeAsync: WebSocket (%p) was closed, no need to close it again!\n", this);
        return;
    }

    _readyState = cc::network::WebSocket::State::CLOSING;
}

cc::network::WebSocket::State WebSocketImpl::getReadyState() const {
    std::lock_guard<std::mutex> lk(const_cast<WebSocketImpl *>(this)->_readyStateMutex);
    return _readyState;
}

const ccstd::string &WebSocketImpl::getUrl() const {
    return _url;
}

const ccstd::string &WebSocketImpl::getProtocol() const {
    return _selectedProtocol;
}

cc::network::WebSocket::Delegate *WebSocketImpl::getDelegate() const {
    return _delegate;
}

struct lws_vhost *WebSocketImpl::createVhost(struct lws_protocols *protocols, int *sslConnectionOut) {
    auto *fileUtils = cc::FileUtils::getInstance();
    bool isCAFileExist = fileUtils->isFileExist(_caFilePath);
    if (isCAFileExist) {
        _caFilePath = fileUtils->fullPathForFilename(_caFilePath);
    }

    lws_context_creation_info info = convertToContextCreationInfo(protocols, isCAFileExist);

    int sslConnection = *sslConnectionOut;
    if (sslConnection != 0) {
        if (isCAFileExist) {
#if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_OPENHARMONY)
            // if ca file is in the apk, try to extract it to writable path
            ccstd::string writablePath = fileUtils->getWritablePath();
            ccstd::string caFileName = getFileNameForPath(_caFilePath);
            ccstd::string newCaFilePath = writablePath + caFileName;

            if (fileUtils->isFileExist(newCaFilePath)) {
                LOGD("CA file (%s) in writable path exists!", newCaFilePath.c_str());
                _caFilePath = newCaFilePath;
                info.ssl_ca_filepath = _caFilePath.c_str();
            } else {
                if (fileUtils->isFileExist(_caFilePath)) {
                    ccstd::string fullPath = fileUtils->fullPathForFilename(_caFilePath);
                    LOGD("Found CA file: %s", fullPath.c_str());
                    if (fullPath[0] != '/') {
                        LOGD("CA file is in APK");
                        auto caData = fileUtils->getDataFromFile(fullPath);
                        if (!caData.isNull()) {
                            FILE *fp = fopen(newCaFilePath.c_str(), "wb");
                            if (fp != nullptr) {
                                LOGD("New CA file path: %s", newCaFilePath.c_str());
                                fwrite(caData.getBytes(), caData.getSize(), 1, fp);
                                fclose(fp);
                                _caFilePath = newCaFilePath;
                                info.ssl_ca_filepath = _caFilePath.c_str();
                            } else {
                                CC_ABORT(); // Open new CA file failed
                            }
                        } else {
                            CC_ABORT(); // CA file is empty!
                        }
                    } else {
                        LOGD("CA file isn't in APK!");
                        _caFilePath = fullPath;
                        info.ssl_ca_filepath = _caFilePath.c_str();
                    }
                } else {
                    CC_ABORT(); // CA file doesn't exist!
                }
            }
#else
            info.ssl_ca_filepath = _caFilePath.c_str();
#endif
        } else {
            LOGD("WARNING: CA Root file isn't set. SSL connection will not peer server certificate\n");
            *sslConnectionOut = sslConnection | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
        }
    }

    lws_vhost *vhost = lws_create_vhost(wsContext, &info);

    return vhost;
}

void WebSocketImpl::onClientOpenConnectionRequest() {
    if (nullptr != wsContext) {
        static const struct lws_extension EXTS[] = {
            {"permessage-deflate",
             lws_extension_callback_pm_deflate,
             // client_no_context_takeover extension is not supported in the current version, it will cause connection fail
             // It may be a bug of lib websocket build
             //            "permessage-deflate; client_no_context_takeover; client_max_window_bits"
             "permessage-deflate; client_max_window_bits"},
            {"deflate-frame",
             lws_extension_callback_pm_deflate,
             "deflate_frame"},
            {nullptr, nullptr, nullptr /* terminator */}};

        _readyStateMutex.lock();
        _readyState = cc::network::WebSocket::State::CONNECTING;
        _readyStateMutex.unlock();

        cc::network::Uri uri = cc::network::Uri::parse(_url);
        LOGD("scheme: %s, host: %s, port: %d, path: %s\n", uri.getScheme().c_str(), uri.getHostName().c_str(), static_cast<int>(uri.getPort()), uri.getPathEtc().c_str());

        int sslConnection = 0;
        if (uri.isSecure()) {
            sslConnection = LCCSCF_USE_SSL;
        }

        struct lws_vhost *vhost = nullptr;
        if (_lwsProtocols != nullptr) {
            vhost = createVhost(_lwsProtocols, &sslConnection);
        } else {
            vhost = createVhost(defaultProtocols, &sslConnection);
        }

        int port = static_cast<int>(uri.getPort());
        if (port == 0) {
            port = uri.isSecure() ? 443 : 80;
        }

        const ccstd::string &hostName = uri.getHostName();
        ccstd::string path = uri.getPathEtc();
        const ccstd::string &authority = uri.getAuthority();
        if (path.empty()) {
            path = "/";
        }

        struct lws_client_connect_info connectInfo;
        memset(&connectInfo, 0, sizeof(connectInfo));
        connectInfo.context = wsContext;
        connectInfo.address = hostName.c_str();
        connectInfo.port = port;
        connectInfo.ssl_connection = sslConnection;
        connectInfo.path = path.c_str();
        connectInfo.host = hostName.c_str();
        connectInfo.origin = authority.c_str();
        connectInfo.protocol = _clientSupportedProtocols.empty() ? nullptr : _clientSupportedProtocols.c_str();
        connectInfo.ietf_version_or_minus_one = -1;
        connectInfo.userdata = this;
        connectInfo.client_exts = EXTS;
        connectInfo.vhost = vhost;

        _wsInstance = lws_client_connect_via_info(&connectInfo);

        if (nullptr == _wsInstance) {
            onConnectionError();
            return;
        }
    } else {
        LOGE("Create websocket context failed!");
    }
}

int WebSocketImpl::onClientWritable() {
    //    LOGD("onClientWritable ... \n");
    {
        std::lock_guard<std::mutex> readMutex(_readyStateMutex);
        if (_readyState == cc::network::WebSocket::State::CLOSING) {
            LOGD("Closing websocket (%p) connection.\n", this);
            return -1;
        }
    }

    do {
        std::lock_guard<std::mutex> lk(wsHelper->_subThreadWsMessageQueueMutex);

        if (wsHelper->_subThreadWsMessageQueue->empty()) {
            break;
        }

        auto iter = wsHelper->_subThreadWsMessageQueue->begin();

        while (iter != wsHelper->_subThreadWsMessageQueue->end()) {
            WsMessage *msg = *iter;
            if (msg->user == this) {
                break;
            }
            ++iter;
        }

        ssize_t bytesWrite = 0;
        if (iter != wsHelper->_subThreadWsMessageQueue->end()) {
            WsMessage *subThreadMsg = *iter;

            auto *data = subThreadMsg->data;

            const ssize_t cBufferSize = WS_RX_BUFFER_SIZE;

            const ssize_t remaining = data->len - data->issued;
            const ssize_t n = std::min(remaining, cBufferSize);

            WebSocketFrame *frame = nullptr;

            if (data->ext) {
                frame = static_cast<WebSocketFrame *>(data->ext);
            } else {
                frame = ccnew WebSocketFrame();
                bool success = frame && frame->init(reinterpret_cast<unsigned char *>(data->bytes + data->issued), n);
                if (success) {
                    data->ext = frame;
                } else { // If frame initialization failed, delete the frame and drop the sending data
                         // These codes should never be called.
                    LOGD("WebSocketFrame initialization failed, drop the sending data, msg(%d)\n", (int)subThreadMsg->id);
                    delete frame;
                    CC_SAFE_FREE(data->bytes);
                    CC_SAFE_DELETE(data);
                    wsHelper->_subThreadWsMessageQueue->erase(iter);
                    CC_SAFE_DELETE(subThreadMsg);
                    break;
                }
            }

            int writeProtocol;

            if (data->issued == 0) {
                if (WS_MSG_TO_SUBTRHEAD_SENDING_STRING == subThreadMsg->what) {
                    writeProtocol = LWS_WRITE_TEXT;
                } else {
                    writeProtocol = LWS_WRITE_BINARY;
                }

                // If we have more than 1 fragment
                if (data->len > cBufferSize) {
                    writeProtocol |= LWS_WRITE_NO_FIN;
                }
            } else {
                // we are in the middle of fragments
                writeProtocol = LWS_WRITE_CONTINUATION;
                // and if not in the last fragment
                if (remaining != n) {
                    writeProtocol |= LWS_WRITE_NO_FIN;
                }
            }

            bytesWrite = lws_write(_wsInstance, frame->getPayload(), frame->getPayloadLength(), static_cast<lws_write_protocol>(writeProtocol));

            // Handle the result of lws_write
            // Buffer overrun?
            if (bytesWrite < 0) {
                LOGD("ERROR: msg(%u), lws_write return: %d, but it should be %d, drop this message.\n", subThreadMsg->id, (int)bytesWrite, (int)n);
                // socket error, we need to close the socket connection
                CC_SAFE_FREE(data->bytes);
                delete (static_cast<WebSocketFrame *>(data->ext));
                data->ext = nullptr;
                CC_SAFE_DELETE(data);
                wsHelper->_subThreadWsMessageQueue->erase(iter);
                CC_SAFE_DELETE(subThreadMsg);

                closeAsync();
            } else if (bytesWrite < frame->getPayloadLength()) {
                frame->update(bytesWrite);
                LOGD("frame wasn't sent completely, bytesWrite: %d, remain: %d\n", (int)bytesWrite, (int)frame->getPayloadLength());
            }
            // Do we have another fragments to send?
            else if (remaining > frame->getFrameLength() && bytesWrite == frame->getPayloadLength()) {
                // A frame was totally sent, plus data->issued to send next frame
                LOGD("msg(%u) append: %d + %d = %d\n", subThreadMsg->id, (int)data->issued, (int)frame->getFrameLength(), (int)(data->issued + frame->getFrameLength()));
                data->issued += frame->getFrameLength();
                delete (static_cast<WebSocketFrame *>(data->ext));
                data->ext = nullptr;
            }
            // Safely done!
            else {
                LOGD("Safely done, msg(%d)!\n", subThreadMsg->id);
                if (remaining == frame->getFrameLength()) {
                    LOGD("msg(%u) append: %d + %d = %d\n", subThreadMsg->id, (int)data->issued, (int)frame->getFrameLength(), (int)(data->issued + frame->getFrameLength()));
                    LOGD("msg(%u) was totally sent!\n", subThreadMsg->id);
                } else {
                    LOGD("ERROR: msg(%u), remaining(%d) < bytesWrite(%d)\n", subThreadMsg->id, (int)remaining, (int)frame->getFrameLength());
                    LOGD("Drop the msg(%u)\n", subThreadMsg->id);
                    closeAsync();
                }

                CC_SAFE_FREE(data->bytes);
                delete (static_cast<WebSocketFrame *>(data->ext));
                data->ext = nullptr;
                CC_SAFE_DELETE(data);
                wsHelper->_subThreadWsMessageQueue->erase(iter);
                CC_SAFE_DELETE(subThreadMsg);

                LOGD("-----------------------------------------------------------\n");
            }
        }

    } while (false);

    if (_wsInstance != nullptr) {
        lws_callback_on_writable(_wsInstance);
    }

    return 0;
}

int WebSocketImpl::onClientReceivedData(void *in, ssize_t len) {
    // In websocket thread
    static int packageIndex = 0;
    packageIndex++;
    if (in != nullptr && len > 0) {
        LOGD("Receiving data:index:%d, len=%d\n", packageIndex, (int)len);

        auto *inData = static_cast<unsigned char *>(in);
        _receivedData.insert(_receivedData.end(), inData, inData + len);
    } else {
        LOGD("Empty message received, index=%d!\n", packageIndex);
    }

    // If no more data pending, send it to the client thread
    size_t remainingSize = lws_remaining_packet_payload(_wsInstance);
    int isFinalFragment = lws_is_final_fragment(_wsInstance);
    //    LOGD("remainingSize: %d, isFinalFragment: %d\n", (int)remainingSize, isFinalFragment);

    if (remainingSize == 0 && isFinalFragment) {
        auto *frameData = ccnew ccstd::vector<char>(std::move(_receivedData));

        // reset capacity of received data buffer
        _receivedData.reserve(WS_RESERVE_RECEIVE_BUFFER_SIZE);

        ssize_t frameSize = frameData->size();

        bool isBinary = (lws_frame_is_binary(_wsInstance) != 0);

        if (!isBinary) {
            frameData->push_back('\0');
        }

        std::shared_ptr<std::atomic<bool>> isDestroyed = _isDestroyed;
        wsHelper->sendMessageToCocosThread([this, frameData, frameSize, isBinary, isDestroyed]() {
            // In UI thread
            LOGD("Notify data len %d to Cocos thread.\n", (int)frameSize);

            cc::network::WebSocket::Data data;
            data.isBinary = isBinary;
            data.bytes = static_cast<char *>(frameData->data());
            data.len = frameSize;

            if (*isDestroyed) {
                LOGD("WebSocket instance was destroyed!\n");
            } else {
                _delegate->onMessage(_ws, data);
            }

            delete frameData;
        });
    }

    return 0;
}

int WebSocketImpl::onConnectionOpened() {
    const lws_protocols *lwsSelectedProtocol = lws_get_protocol(_wsInstance);
    _selectedProtocol = lwsSelectedProtocol->name;
    LOGD("onConnectionOpened...: %p, client protocols: %s, server selected protocol: %s\n", this, _clientSupportedProtocols.c_str(), _selectedProtocol.c_str());
    /*
     * start the ball rolling,
     * LWS_CALLBACK_CLIENT_WRITEABLE will come next service
     */
    lws_callback_on_writable(_wsInstance);

    {
        std::lock_guard<std::mutex> lk(_readyStateMutex);
        if (_readyState == cc::network::WebSocket::State::CLOSING || _readyState == cc::network::WebSocket::State::CLOSED) {
            return 0;
        }
        _readyState = cc::network::WebSocket::State::OPEN;
    }

    std::shared_ptr<std::atomic<bool>> isDestroyed = _isDestroyed;
    wsHelper->sendMessageToCocosThread([this, isDestroyed]() {
        if (*isDestroyed) {
            LOGD("WebSocket instance was destroyed!\n");
        } else {
            _delegate->onOpen(_ws);
        }
    });
    return 0;
}

int WebSocketImpl::onConnectionError() {
    {
        std::lock_guard<std::mutex> lk(_readyStateMutex);
        LOGD("WebSocket (%p) onConnectionError, state: %d ...\n", this, (int)_readyState);
        if (_readyState == cc::network::WebSocket::State::CLOSED) {
            return 0;
        }
        _readyState = cc::network::WebSocket::State::CLOSING;
    }

    std::shared_ptr<std::atomic<bool>> isDestroyed = _isDestroyed;
    wsHelper->sendMessageToCocosThread([this, isDestroyed]() {
        if (*isDestroyed) {
            LOGD("WebSocket instance was destroyed!\n");
        } else {
            _delegate->onError(_ws, cc::network::WebSocket::ErrorCode::CONNECTION_FAILURE);
        }
    });

    onConnectionClosed(static_cast<uint16_t>(cc::network::WebSocket::ErrorCode::CONNECTION_FAILURE), "connection error");

    return 0;
}

int WebSocketImpl::onConnectionClosed(uint16_t code, const ccstd::string &reason) {
    {
        std::lock_guard<std::mutex> lk(_readyStateMutex);
        LOGD("WebSocket (%p) onConnectionClosed, state: %d ...\n", this, (int)_readyState);
        if (_readyState == cc::network::WebSocket::State::CLOSED) {
            return 0;
        }

        if (_readyState == cc::network::WebSocket::State::CLOSING) {
            if (_closeState == CloseState::SYNC_CLOSING) {
                LOGD("onConnectionClosed, WebSocket (%p) is closing by client synchronously.\n", this);
                for (;;) {
                    std::lock_guard<std::mutex> lkClose(_closeMutex);
                    _closeCondition.notify_one();
                    if (_closeState == CloseState::SYNC_CLOSED) {
                        break;
                    }
                    std::this_thread::sleep_for(std::chrono::milliseconds(1));
                }

                return 0;
            }

            if (_closeState == CloseState::ASYNC_CLOSING) {
                LOGD("onConnectionClosed, WebSocket (%p) is closing by client asynchronously.\n", this);
            } else {
                LOGD("onConnectionClosed, WebSocket (%p) is closing by server.\n", this);
            }
        } else {
            LOGD("onConnectionClosed, WebSocket (%p) is closing by server.\n", this);
        }

        _readyState = cc::network::WebSocket::State::CLOSED;
    }

    std::shared_ptr<std::atomic<bool>> isDestroyed = _isDestroyed;
    wsHelper->sendMessageToCocosThread([this, isDestroyed, code, reason]() {
        if (*isDestroyed) {
            LOGD("WebSocket instance (%p) was destroyed!\n", this);
        } else {
            _delegate->onClose(_ws, code, reason, true);
        }
    });

    LOGD("WebSocket (%p) onConnectionClosed DONE!\n", this);
    return 0;
}

int WebSocketImpl::onSocketCallback(struct lws * /*wsi*/, enum lws_callback_reasons reason, void *in, ssize_t len) {
    //LOGD("socket callback for %d reason\n", reason);

    int ret = 0;
    switch (reason) {
        case LWS_CALLBACK_CLIENT_ESTABLISHED:
            ret = onConnectionOpened();
            break;

        case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
            ret = onConnectionError();
            break;

        case LWS_CALLBACK_WSI_DESTROY:
            ret = onConnectionClosed(static_cast<uint16_t>(LWS_CALLBACK_WSI_DESTROY), "lws_callback_wsi_destroy");
            break;

        case LWS_CALLBACK_CLIENT_RECEIVE:
            ret = onClientReceivedData(in, len);
            break;

        case LWS_CALLBACK_CLIENT_WRITEABLE:
            ret = onClientWritable();
            break;
        case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
        case LWS_CALLBACK_LOCK_POLL:
        case LWS_CALLBACK_UNLOCK_POLL:
            break;
        case LWS_CALLBACK_PROTOCOL_INIT:
            LOGD("protocol init...");
            break;
        case LWS_CALLBACK_PROTOCOL_DESTROY:
            LOGD("protocol destroy...");
            break;
        case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
            if (in && len > 0) {
                _enabledExtensions.emplace_back(static_cast<char *>(in), 0, len);
            }
            break;
        default:
            LOGD("WebSocket (%p) Unhandled websocket event: %d\n", this, reason);
            break;
    }

    return ret;
}

NS_NETWORK_BEGIN

/*static*/
void WebSocket::closeAllConnections() {
    WebSocketImpl::closeAllConnections();
}

WebSocket::WebSocket() {
    _impl = ccnew WebSocketImpl(this);
}

WebSocket::~WebSocket() {
    delete _impl;
}

bool WebSocket::init(const Delegate &delegate,
                     const ccstd::string &url,
                     const ccstd::vector<ccstd::string> *protocols /* = nullptr*/,
                     const ccstd::string &caFilePath /* = ""*/) {
    return _impl->init(delegate, url, protocols, caFilePath);
}

void WebSocket::send(const ccstd::string &message) {
    _impl->send(message);
}

void WebSocket::send(const unsigned char *binaryMsg, unsigned int len) {
    _impl->send(binaryMsg, len);
}

void WebSocket::close() {
    _impl->close();
}

void WebSocket::closeAsync() {
    _impl->closeAsync();
}
void WebSocket::closeAsync(int code, const ccstd::string &reason) {
    _impl->closeAsync(code, reason);
}

WebSocket::State WebSocket::getReadyState() const {
    return _impl->getReadyState();
}

ccstd::string WebSocket::getExtensions() const {
    return _impl->getExtensions();
}

size_t WebSocket::getBufferedAmount() const {
    return _impl->getBufferedAmount();
}

const ccstd::string &WebSocket::getUrl() const {
    return _impl->getUrl();
}

const ccstd::string &WebSocket::getProtocol() const {
    return _impl->getProtocol();
}

WebSocket::Delegate *WebSocket::getDelegate() const {
    return _impl->getDelegate();
}

NS_NETWORK_END
