/*
 +----------------------------------------------------------------------+
 | Swoole                                                               |
 +----------------------------------------------------------------------+
 | Copyright (c) 2012-2018 The Swoole Group                             |
 +----------------------------------------------------------------------+
 | This source file is subject to version 2.0 of the Apache license,    |
 | that is bundled with this package in the file LICENSE, and is        |
 | available through the world-wide-web at the following url:           |
 | http://www.apache.org/licenses/LICENSE-2.0.html                      |
 | If you did not receive a copy of the Apache2.0 license and are unable|
 | to obtain it through the world-wide-web, please send a note to       |
 | license@swoole.com so we can mail you a copy immediately.            |
 +----------------------------------------------------------------------+
 | Author: Tianfeng Han  <mikan.tenny@gmail.com>                        |
 +----------------------------------------------------------------------+
 */

#include "php_swoole.h"

#ifdef SW_COROUTINE
#include "swoole_coroutine.h"

static zend_class_entry swoole_socket_coro_ce;
static zend_class_entry *swoole_socket_coro_class_entry_ptr;
static zend_object_handlers swoole_socket_coro_handlers;

static zend_class_entry swoole_socket_coro_exception_ce;
static zend_class_entry *swoole_socket_coro_exception_class_entry_ptr;

enum socket_opcode
{
    SW_SOCKET_OPCODE_ACCEPT,
    SW_SOCKET_OPCODE_CONNECT,
    SW_SOCKET_OPCODE_RECV,
    SW_SOCKET_OPCODE_RECVFROM,
    SW_SOCKET_OPCODE_SEND,
};

typedef struct
{
    zval object;
    int fd;
    int domain;
    int type;
    int cid;
    enum socket_opcode opcode;
    php_context context;
    swTimer_node *timer;
#ifdef SWOOLE_SOCKETS_SUPPORT
    zval *resource;
#endif
    zend_object std;
} socket_coro;

static PHP_METHOD(swoole_socket_coro, __construct);
static PHP_METHOD(swoole_socket_coro, bind);
static PHP_METHOD(swoole_socket_coro, listen);
static PHP_METHOD(swoole_socket_coro, accept);
static PHP_METHOD(swoole_socket_coro, connect);
static PHP_METHOD(swoole_socket_coro, recv);
static PHP_METHOD(swoole_socket_coro, send);
static PHP_METHOD(swoole_socket_coro, recvfrom);
static PHP_METHOD(swoole_socket_coro, sendto);
static PHP_METHOD(swoole_socket_coro, getpeername);
static PHP_METHOD(swoole_socket_coro, getsockname);
static PHP_METHOD(swoole_socket_coro, close);
#ifdef SWOOLE_SOCKETS_SUPPORT
static PHP_METHOD(swoole_socket_coro, getSocket);
#endif

static int swoole_socket_connect(socket_coro *sock, char *host, size_t l_host, int port);
static void socket_onTimeout(swTimer *timer, swTimer_node *tnode);

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_socket_coro_construct, 0, 0, 3)
    ZEND_ARG_INFO(0, domain)
    ZEND_ARG_INFO(0, type)
    ZEND_ARG_INFO(0, protocol)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_socket_coro_bind, 0, 0, 1)
    ZEND_ARG_INFO(0, address)
    ZEND_ARG_INFO(0, port)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_socket_coro_listen, 0, 0, 0)
    ZEND_ARG_INFO(0, backlog)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_socket_coro_accept, 0, 0, 0)
    ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_socket_coro_recv, 0, 0, 0)
    ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_socket_coro_send, 0, 0, 1)
    ZEND_ARG_INFO(0, data)
    ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_socket_coro_recvfrom, 0, 0, 1)
    ZEND_ARG_INFO(1, peername)
    ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_socket_coro_sendto, 0, 0, 3)
    ZEND_ARG_INFO(0, addr)
    ZEND_ARG_INFO(0, port)
    ZEND_ARG_INFO(0, data)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_socket_coro_connect, 0, 0, 1)
    ZEND_ARG_INFO(0, host)
    ZEND_ARG_INFO(0, port)
    ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_void, 0, 0, 0)
ZEND_END_ARG_INFO()

static const zend_function_entry swoole_socket_coro_methods[] =
{
    PHP_ME(swoole_socket_coro, __construct, arginfo_swoole_socket_coro_construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    PHP_ME(swoole_socket_coro, bind, arginfo_swoole_socket_coro_bind, ZEND_ACC_PUBLIC)
    PHP_ME(swoole_socket_coro, listen, arginfo_swoole_socket_coro_listen, ZEND_ACC_PUBLIC)
    PHP_ME(swoole_socket_coro, accept, arginfo_swoole_socket_coro_accept, ZEND_ACC_PUBLIC)
    PHP_ME(swoole_socket_coro, connect, arginfo_swoole_socket_coro_connect, ZEND_ACC_PUBLIC)
    PHP_ME(swoole_socket_coro, recv, arginfo_swoole_socket_coro_recv, ZEND_ACC_PUBLIC)
    PHP_ME(swoole_socket_coro, send, arginfo_swoole_socket_coro_send, ZEND_ACC_PUBLIC)
    PHP_ME(swoole_socket_coro, recvfrom, arginfo_swoole_socket_coro_recvfrom, ZEND_ACC_PUBLIC)
    PHP_ME(swoole_socket_coro, sendto, arginfo_swoole_socket_coro_sendto, ZEND_ACC_PUBLIC)
    PHP_ME(swoole_socket_coro, getpeername, arginfo_swoole_void, ZEND_ACC_PUBLIC)
    PHP_ME(swoole_socket_coro, getsockname, arginfo_swoole_void, ZEND_ACC_PUBLIC)
#ifdef SWOOLE_SOCKETS_SUPPORT
    PHP_ME(swoole_socket_coro, getSocket, arginfo_swoole_void, ZEND_ACC_PUBLIC)
#endif
    PHP_ME(swoole_socket_coro, close, arginfo_swoole_void, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

static inline socket_coro * sw_socket_coro_fetch_object(zend_object *obj)
{
    return (socket_coro *) ((char *) obj - XtOffsetOf(socket_coro, std));
}

#define Z_SOCKET_CORO_OBJ_P(zv) sw_socket_coro_fetch_object(Z_OBJ_P(zv));


static void swoole_socket_coro_free_storage(zend_object *object)
{
    socket_coro *sock = (socket_coro *) sw_socket_coro_fetch_object(object);
    if (sock->fd >= 0)
    {
        SwooleG.main_reactor->close(SwooleG.main_reactor, sock->fd);
    }
    zend_object_std_dtor(&sock->std);
}

static zend_object *swoole_socket_coro_create(zend_class_entry *ce TSRMLS_DC)
{
    socket_coro *sock = ecalloc(1, sizeof(socket_coro) + zend_object_properties_size(ce));
    zend_object_std_init(&sock->std, ce TSRMLS_CC);
    object_properties_init(&sock->std, ce);
    sock->std.handlers = &swoole_socket_coro_handlers;

    return &sock->std;
}

void swoole_socket_coro_init(int module_number TSRMLS_DC)
{
    INIT_CLASS_ENTRY(swoole_socket_coro_ce, "Swoole\\Coroutine\\Socket", swoole_socket_coro_methods);

    swoole_socket_coro_class_entry_ptr = zend_register_internal_class(&swoole_socket_coro_ce TSRMLS_CC);
    swoole_socket_coro_class_entry_ptr->ce_flags |= ZEND_ACC_FINAL;
    swoole_socket_coro_class_entry_ptr->create_object = swoole_socket_coro_create;
    swoole_socket_coro_class_entry_ptr->serialize = zend_class_serialize_deny;
    swoole_socket_coro_class_entry_ptr->unserialize = zend_class_unserialize_deny;
    zend_declare_property_long(swoole_socket_coro_class_entry_ptr, SW_STRL("errCode") - 1, 0, ZEND_ACC_PUBLIC TSRMLS_CC);

    memcpy(&swoole_socket_coro_handlers, zend_get_std_object_handlers(), sizeof(swoole_socket_coro_handlers));
    swoole_socket_coro_handlers.free_obj = swoole_socket_coro_free_storage;
    swoole_socket_coro_handlers.clone_obj = NULL;
    swoole_socket_coro_handlers.offset = XtOffsetOf(socket_coro, std);

    INIT_CLASS_ENTRY(swoole_socket_coro_exception_ce, "Swoole\\Coroutine\\Socket\\Exception", NULL);
    swoole_socket_coro_exception_class_entry_ptr = sw_zend_register_internal_class_ex(&swoole_socket_coro_exception_ce,
            zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC);

    if (SWOOLE_G(use_shortname))
    {
        sw_zend_register_class_alias("Co\\Socket", swoole_socket_coro_class_entry_ptr);
        sw_zend_register_class_alias("Co\\Socket\\Exception", swoole_socket_coro_exception_class_entry_ptr);
    }
}

static int socket_onReadable(swReactor *reactor, swEvent *event)
{
    socket_coro *sock = (socket_coro *) event->socket->object;
    php_context *context = &sock->context;

    zval *retval = NULL;
    zval result;

    swSocketAddress client_addr;
    socklen_t client_addrlen = sizeof(client_addr);

    reactor->del(reactor, sock->fd);

    if (sock->timer)
    {
        swTimer_del(&SwooleG.timer, sock->timer);
        sock->timer = NULL;
    }

    switch (sock->opcode)
    {
    case SW_SOCKET_OPCODE_ACCEPT:
    {
        int conn;
#ifdef HAVE_ACCEPT4
        conn = accept4(sock->fd, (struct sockaddr *) &client_addr, &client_addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#else
        conn = accept(event->fd, (struct sockaddr *) &client_addr, &client_addrlen);
        if (conn >= 0)
        {
            swoole_fcntl_set_option(conn, 1, 1);
        }
#endif
        if (conn >= 0)
        {
            zend_object *client;
            client = swoole_socket_coro_create(swoole_socket_coro_class_entry_ptr);
            socket_coro *client_sock = (socket_coro *) sw_socket_coro_fetch_object(client);
            ZVAL_OBJ(&result, &client_sock->std);
            client_sock->fd = conn;
            client_sock->domain = sock->domain;
            client_sock->object = result;
            // zend_object_std_dtor(&client_sock->std);
        }
        else
        {
            zend_update_property_long(swoole_socket_coro_class_entry_ptr, &sock->object, ZEND_STRL("errCode"), errno TSRMLS_CC);
            ZVAL_FALSE(&result);
        }
        break;
    }
    case SW_SOCKET_OPCODE_RECV:
    {
        zend_string *buf = zend_string_alloc(SW_BUFFER_SIZE_BIG, 0);
        int bytes = 0;

        while (1)
        {
            int n = recv(sock->fd, ZSTR_VAL(buf) + bytes, ZSTR_LEN(buf) - bytes - 1, MSG_DONTWAIT);
            if (n < 0)
            {
                if (errno == EINTR)
                {
                    continue;
                }
                else
                {
                    if (bytes == 0)
                    {
                        bytes = -1;
                    }
                    break;
                }
            }
            else if (n == 0)
            {
                break;
            }
            else
            {
                bytes += n;
                if (sock->type != SOCK_STREAM)
                {
                    break;
                }
                if (ZSTR_LEN(buf) - 1 == bytes)
                {
                    zend_string_realloc(buf, ZSTR_LEN(buf) + SW_BUFFER_SIZE_BIG, 0);
                }
                continue;
            }
        }
        if (bytes < 0)
        {
            zend_update_property_long(swoole_socket_coro_class_entry_ptr, &sock->object, ZEND_STRL("errCode"), errno TSRMLS_CC);
            zend_string_free(buf);
            ZVAL_FALSE(&result);
        }
        else if (bytes == 0)
        {
            zend_string_free(buf);
            ZVAL_EMPTY_STRING(&result);
        }
        else
        {
            ZVAL_NEW_STR(&result, buf);
            ZSTR_LEN(buf) = bytes;
            ZSTR_VAL(buf)[bytes] = 0;
        }
        break;
    }
    case SW_SOCKET_OPCODE_RECVFROM:
    {
        zend_string *buf = zend_string_alloc(SW_BUFFER_SIZE_BIG, 0);
        swSocketAddress info;
        zval *peername = Z_REFVAL(context->coro_params);
        info.len = sizeof(info.addr);
        int bytes = recvfrom(sock->fd, ZSTR_VAL(buf), ZSTR_LEN(buf) - 1, 0, (struct sockaddr *) &info.addr, &info.len);
        if (bytes < 0)
        {
            zend_update_property_long(swoole_socket_coro_class_entry_ptr, &sock->object, ZEND_STRL("errCode"), errno TSRMLS_CC);
            zend_string_free(buf);
            ZVAL_FALSE(&result);
        }
        else if (bytes == 0)
        {
            zend_string_free(buf);
            ZVAL_EMPTY_STRING(&result);
        }
        else
        {
            array_init(peername);
            if (sock->domain == AF_INET)
            {
                add_assoc_long(peername, "port", ntohs(info.addr.inet_v4.sin_port));
                add_assoc_string(peername, "address", inet_ntoa(info.addr.inet_v4.sin_addr));
            }
            else if (sock->domain == AF_INET6)
            {
                add_assoc_long(peername, "port", ntohs(info.addr.inet_v6.sin6_port));
                char tmp[INET6_ADDRSTRLEN];
                if (inet_ntop(AF_INET6, &info.addr.inet_v6.sin6_addr, tmp, sizeof(tmp)))
                {
                    sw_add_assoc_string(peername, "address", tmp, 1);
                }
                else
                {
                    swoole_php_fatal_error(E_WARNING, "inet_ntop() failed.");
                }
            }
            else if (sock->domain == AF_UNIX)
            {
                add_assoc_string(peername, "address", info.addr.un.sun_path);
            }
            ZVAL_NEW_STR(&result, buf);
            ZSTR_LEN(buf) = bytes;
            ZSTR_VAL(buf)[bytes] = 0;
        }
        break;
    }
    default:
        break;
    }

    //unbind coroutine
    sock->cid = 0;
    int ret = coro_resume(context, &result, &retval);
    zval_ptr_dtor(&result);
    if (ret == CORO_END && retval)
    {
        zval_ptr_dtor(retval);
    }
    return SW_OK;
}

static int socket_onWritable(swReactor *reactor, swEvent *event)
{
    socket_coro *sock = (socket_coro *) event->socket->object;
    php_context *context = &sock->context;

    zval *retval = NULL;
    zval result;

    reactor->del(reactor, sock->fd);

    if (sock->timer)
    {
        swTimer_del(&SwooleG.timer, sock->timer);
        sock->timer = NULL;
    }

    switch (sock->opcode)
    {
    case SW_SOCKET_OPCODE_SEND:
    {
        int n = send(sock->fd, Z_STRVAL(context->coro_params), Z_STRLEN(context->coro_params), MSG_DONTWAIT);
        if (n < 0)
        {
            zend_update_property_long(swoole_socket_coro_class_entry_ptr, &sock->object, ZEND_STRL("errCode"), ETIMEDOUT TSRMLS_CC);
            ZVAL_FALSE(&result);
            break;
        }
        else
        {
            ZVAL_LONG(&result, n);
        }
        break;
    }
    case SW_SOCKET_OPCODE_CONNECT:
    {
        socklen_t len = sizeof(SwooleG.error);
        if (getsockopt(event->fd, SOL_SOCKET, SO_ERROR, &SwooleG.error, &len) < 0)
        {
            zend_update_property_long(swoole_socket_coro_class_entry_ptr, &sock->object, ZEND_STRL("errCode"), errno TSRMLS_CC);
            ZVAL_FALSE(&result);
            break;
        }
        if (SwooleG.error == 0)
        {
            ZVAL_TRUE(&result);
        }
        else
        {
            zend_update_property_long(swoole_socket_coro_class_entry_ptr, &sock->object, ZEND_STRL("errCode"), SwooleG.error TSRMLS_CC);
            ZVAL_FALSE(&result);
        }
        break;
    }
    default:
        break;
    }

    //unbind coroutine
    sock->cid = 0;
    int ret = coro_resume(context, &result, &retval);
    zval_ptr_dtor(&result);
    if (ret == CORO_END && retval)
    {
        zval_ptr_dtor(retval);
    }
    return SW_OK;
}

static void socket_onResolveCompleted(swAio_event *event)
{
    socket_coro *sock = (socket_coro *) event->object;
    php_context *context = &sock->context;

    zval *retval = NULL;
    zval result;

    if (event->error == 0)
    {
        int ret = swoole_socket_connect(sock, event->buf, strlen(event->buf), Z_LVAL(context->coro_params));
        if (ret == -1 && errno == EINPROGRESS)
        {
            efree(event->buf);
            if (context->private_data)
            {
                int ms = (int) (Z_DVAL_P((zval *) context->private_data) * 1000);
                php_swoole_check_timer(ms);
                sock->timer = SwooleG.timer.add(&SwooleG.timer, ms, 0, sock, socket_onTimeout);
                efree(context->private_data);
                context->private_data = NULL;
            }
            if (SwooleG.main_reactor->add(SwooleG.main_reactor, sock->fd, PHP_SWOOLE_FD_SOCKET | SW_EVENT_WRITE) < 0)
            {
                goto _error;
            }
            else
            {
                swConnection *_socket = swReactor_get(SwooleG.main_reactor, sock->fd);
                _socket->object = sock;
                return;
            }
        }
        else if (ret == 0)
        {
            ZVAL_TRUE(&result);
            sock->cid = 0;
            int ret = coro_resume(context, &result, &retval);
            if (ret == CORO_END && retval)
            {
                sw_zval_ptr_dtor(&retval);
            }
        }
        goto _error;
    }
    else
    {
        _error:
        ZVAL_FALSE(&result);
        //unbind coroutine
        sock->cid = 0;
        int ret = coro_resume(context, &result, &retval);
        if (ret == CORO_END && retval)
        {
            sw_zval_ptr_dtor(&retval);
        }
    }
}

static void socket_onTimeout(swTimer *timer, swTimer_node *tnode)
{
    socket_coro *sock = (socket_coro *) tnode->data;
    php_context *context = &sock->context;
    sock->timer = NULL;
    SwooleG.main_reactor->del(SwooleG.main_reactor, sock->fd);

    zval *retval = NULL;
    zval result;
    if (sock->opcode == SW_SOCKET_OPCODE_RECV)
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, &sock->object, ZEND_STRL("errCode"), EAGAIN TSRMLS_CC);
    }
    else
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, &sock->object, ZEND_STRL("errCode"), ETIMEDOUT TSRMLS_CC);
    }
    ZVAL_FALSE(&result);

    //unbind coroutine
    sock->cid = 0;
    int ret = coro_resume(context, &result, &retval);
    zval_ptr_dtor(&result);
    if (ret == CORO_END && retval)
    {
        zval_ptr_dtor(retval);
    }
}

static int swoole_socket_connect(socket_coro *sock, char *host, size_t l_host, int port)
{
    switch (sock->domain)
    {
    case AF_INET:
    {
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        socklen_t len = sizeof(addr);

        if (!inet_pton(AF_INET, host, &addr.sin_addr))
        {
            return -2;
        }
        else
        {
            return connect(sock->fd, (struct sockaddr *) &addr, len);
        }
    }
    case AF_INET6:
    {
        struct sockaddr_in6 addr;
        addr.sin6_family = AF_INET6;
        addr.sin6_port = htons(port);
        socklen_t len = sizeof(addr);

        if (!inet_pton(AF_INET6, host, &addr.sin6_addr))
        {
            return -1;
        }
        else
        {
            return connect(sock->fd, (struct sockaddr *) &addr, len);
        }
    }
    case AF_UNIX:
    {
        struct sockaddr_un s_un = { 0 };
        if (l_host >= sizeof(s_un.sun_path))
        {
            return -1;
        }

        s_un.sun_family = AF_UNIX;
        memcpy(&s_un.sun_path, host, l_host);
        return connect(sock->fd, (struct sockaddr *) &s_un, (socklen_t) (XtOffsetOf(struct sockaddr_un, sun_path) + l_host));
    }

    default:
        break;
    }
    return -3;
}

static PHP_METHOD(swoole_socket_coro, __construct)
{
    zend_long domain, type, protocol;

    ZEND_PARSE_PARAMETERS_START(3, 3)
        Z_PARAM_LONG(domain);
        Z_PARAM_LONG(type);
        Z_PARAM_LONG(protocol);
    ZEND_PARSE_PARAMETERS_END();

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    sock->fd = socket(domain, type, protocol);
    sock->domain = domain;
    sock->type = type;
    sock->object = *getThis();

    if (sock->fd < 0)
    {
        zend_throw_exception_ex(swoole_socket_coro_exception_class_entry_ptr, errno, "Unable to create socket [%d]: %s",
                strerror(errno), errno TSRMLS_CC);
        RETURN_FALSE;
    }

    php_swoole_check_reactor();
    if (!swReactor_handle_isset(SwooleG.main_reactor, PHP_SWOOLE_FD_SOCKET))
    {
        SwooleG.main_reactor->setHandle(SwooleG.main_reactor, PHP_SWOOLE_FD_SOCKET | SW_EVENT_READ, socket_onReadable);
        SwooleG.main_reactor->setHandle(SwooleG.main_reactor, PHP_SWOOLE_FD_SOCKET | SW_EVENT_WRITE, socket_onWritable);
    }

    swSetNonBlock(sock->fd);
}

static PHP_METHOD(swoole_socket_coro, bind)
{
    char *address;
    size_t l_address;
    zend_long port = 0;

    struct sockaddr_storage sa_storage = {0};
    struct sockaddr *sock_type = (struct sockaddr*) &sa_storage;

    ZEND_PARSE_PARAMETERS_START(1, 2)
        Z_PARAM_STRING(address, l_address);
        Z_PARAM_OPTIONAL
        Z_PARAM_LONG(port);
    ZEND_PARSE_PARAMETERS_END();

    int retval;

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    switch (sock->domain)
    {
    case AF_UNIX:
    {    struct sockaddr_un *sa = (struct sockaddr_un *) sock_type;
        sa->sun_family = AF_UNIX;

        if (l_address >= sizeof(sa->sun_path))
        {
            swoole_php_error(E_WARNING, "invalid path: too long (maximum size is %d)", (int )sizeof(sa->sun_path) - 1);
            RETURN_FALSE;
        }
        memcpy(&sa->sun_path, address, l_address);

        retval = bind(sock->fd, (struct sockaddr *) sa,
        offsetof(struct sockaddr_un, sun_path) + l_address);
        break;
    }

    case AF_INET:
    {
        struct sockaddr_in *sa = (struct sockaddr_in *) sock_type;
        sa->sin_family = AF_INET;
        sa->sin_port = htons((unsigned short) port);
        if (!inet_aton(address, &sa->sin_addr))
        {
            RETURN_FALSE;
        }
        retval = bind(sock->fd, (struct sockaddr *) sa, sizeof(struct sockaddr_in));
        break;
    }

    case AF_INET6:
    {
        struct sockaddr_in6 *sa = (struct sockaddr_in6 *) sock_type;
        sa->sin6_family = AF_INET6;
        sa->sin6_port = htons((unsigned short) port);

        if (!inet_pton(AF_INET6, address, &sa->sin6_addr))
        {
            RETURN_FALSE;
        }
        retval = bind(sock->fd, (struct sockaddr *)sa, sizeof(struct sockaddr_in6));
        break;
    }
    default:
        RETURN_FALSE;
    }

    if (retval != 0)
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"), errno TSRMLS_CC);
        RETURN_FALSE;
    }

    RETURN_TRUE;
}

static PHP_METHOD(swoole_socket_coro, listen)
{
    zend_long backlog = 0;

    ZEND_PARSE_PARAMETERS_START(0, 1)
        Z_PARAM_OPTIONAL
        Z_PARAM_LONG(backlog);
    ZEND_PARSE_PARAMETERS_END();

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    if (listen(sock->fd, backlog) != 0)
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"), errno TSRMLS_CC);
        RETURN_FALSE;
    }
    RETURN_TRUE;
}

static PHP_METHOD(swoole_socket_coro, accept)
{
    coro_check(TSRMLS_C);

    double timeout = -1;

    ZEND_PARSE_PARAMETERS_START(0, 1)
        Z_PARAM_OPTIONAL
        Z_PARAM_DOUBLE(timeout);
    ZEND_PARSE_PARAMETERS_END();

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    if (unlikely(sock->cid && sock->cid != sw_get_current_cid()))
    {
        swoole_php_fatal_error(E_WARNING, "socket has already been bound to another coroutine.");
        RETURN_FALSE;
    }

    if (SwooleG.main_reactor->add(SwooleG.main_reactor, sock->fd, PHP_SWOOLE_FD_SOCKET | SW_EVENT_READ) < 0)
    {
        RETURN_FALSE;
    }

    swConnection *_socket = swReactor_get(SwooleG.main_reactor, sock->fd);
    _socket->object = sock;

    php_context *context = &sock->context;
    context->state = SW_CORO_CONTEXT_RUNNING;
    context->onTimeout = NULL;
    sock->opcode = SW_SOCKET_OPCODE_ACCEPT;

    if (timeout > 0)
    {
        int ms = (int) (timeout * 1000);
        php_swoole_check_timer(ms);
        sock->timer = SwooleG.timer.add(&SwooleG.timer, ms, 0, sock, socket_onTimeout);
    }

    coro_save(context);
    coro_yield();
}

static PHP_METHOD(swoole_socket_coro, recv)
{
    coro_check(TSRMLS_C);

    double timeout = -1;

    ZEND_PARSE_PARAMETERS_START(0, 1)
        Z_PARAM_OPTIONAL
        Z_PARAM_DOUBLE(timeout);
    ZEND_PARSE_PARAMETERS_END();

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    if (unlikely(sock->cid && sock->cid != sw_get_current_cid()))
    {
        swoole_php_fatal_error(E_WARNING, "socket has already been bound to another coroutine.");
        RETURN_FALSE;
    }
    if (SwooleG.main_reactor->add(SwooleG.main_reactor, sock->fd, PHP_SWOOLE_FD_SOCKET | SW_EVENT_READ) < 0)
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"), errno TSRMLS_CC);
        RETURN_FALSE;
    }

    swConnection *_socket = swReactor_get(SwooleG.main_reactor, sock->fd);
    _socket->object = sock;

    php_context *context = &sock->context;
    context->state = SW_CORO_CONTEXT_RUNNING;
    context->onTimeout = NULL;
    sock->opcode = SW_SOCKET_OPCODE_RECV;

    if (timeout > 0)
    {
        int ms = (int) (timeout * 1000);
        php_swoole_check_timer(ms);
        sock->timer = SwooleG.timer.add(&SwooleG.timer, ms, 0, sock, socket_onTimeout);
    }

    coro_save(context);
    coro_yield();
}

static PHP_METHOD(swoole_socket_coro, recvfrom)
{
    coro_check(TSRMLS_C);

    zval *peername;
    double timeout = -1;

    ZEND_PARSE_PARAMETERS_START(1, 2)
        Z_PARAM_ZVAL(peername);
        Z_PARAM_OPTIONAL
        Z_PARAM_DOUBLE(timeout);
    ZEND_PARSE_PARAMETERS_END();

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    if (unlikely(sock->cid && sock->cid != sw_get_current_cid()))
    {
        swoole_php_fatal_error(E_WARNING, "socket has already been bound to another coroutine.");
        RETURN_FALSE;
    }
    if (SwooleG.main_reactor->add(SwooleG.main_reactor, sock->fd, PHP_SWOOLE_FD_SOCKET | SW_EVENT_READ) < 0)
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"), errno TSRMLS_CC);
        RETURN_FALSE;
    }

    swConnection *_socket = swReactor_get(SwooleG.main_reactor, sock->fd);
    _socket->object = sock;

    php_context *context = &sock->context;
    context->state = SW_CORO_CONTEXT_RUNNING;
    context->onTimeout = NULL;
    context->coro_params = *peername;
    sock->opcode = SW_SOCKET_OPCODE_RECVFROM;

    if (timeout > 0)
    {
        int ms = (int) (timeout * 1000);
        php_swoole_check_timer(ms);
        sock->timer = SwooleG.timer.add(&SwooleG.timer, ms, 0, sock, socket_onTimeout);
    }

    coro_save(context);
    coro_yield();
}

static PHP_METHOD(swoole_socket_coro, send)
{
    coro_check(TSRMLS_C);

    double timeout = -1;
    zval *data;

    ZEND_PARSE_PARAMETERS_START(1, 2)
        Z_PARAM_ZVAL(data);
        Z_PARAM_OPTIONAL
        Z_PARAM_DOUBLE(timeout);
    ZEND_PARSE_PARAMETERS_END();

    if (Z_TYPE_P(data) != IS_STRING)
    {
        RETURN_FALSE;
    }

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    if (unlikely(sock->cid && sock->cid != sw_get_current_cid()))
    {
        swoole_php_fatal_error(E_WARNING, "socket has already been bound to another coroutine.");
        RETURN_FALSE;
    }
    int ret = send(sock->fd, Z_STRVAL_P(data), Z_STRLEN_P(data), MSG_DONTWAIT);
    if (ret < 0)
    {
        if (errno == EAGAIN)
        {
            goto _yield;
        }
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"), errno TSRMLS_CC);
        RETURN_FALSE;
    }
    else
    {
        RETURN_LONG(ret);
    }

    swConnection *_socket = swReactor_get(SwooleG.main_reactor, sock->fd);
    _socket->object = sock;

    _yield:
    if (SwooleG.main_reactor->add(SwooleG.main_reactor, sock->fd, PHP_SWOOLE_FD_SOCKET | SW_EVENT_WRITE) < 0)
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"), errno TSRMLS_CC);
        RETURN_FALSE;
    }

    php_context *context = &sock->context;
    context->state = SW_CORO_CONTEXT_RUNNING;
    context->onTimeout = NULL;
    context->coro_params = *data;
    sock->opcode = SW_SOCKET_OPCODE_SEND;

    if (timeout > 0)
    {
        int ms = (int) (timeout * 1000);
        php_swoole_check_timer(ms);
        sock->timer = SwooleG.timer.add(&SwooleG.timer, ms, 0, sock, socket_onTimeout);
    }

    coro_save(context);
    coro_yield();
}

static PHP_METHOD(swoole_socket_coro, sendto)
{
    char *data;
    size_t l_data;
    char *addr;
    size_t l_addr;
    zend_long port = 0;

    ZEND_PARSE_PARAMETERS_START(3, 3)
        Z_PARAM_STRING(addr, l_addr);
        Z_PARAM_LONG(port);
        Z_PARAM_STRING(data, l_data);
    ZEND_PARSE_PARAMETERS_END();

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());

    int ret;
    if (sock->domain == AF_INET)
    {
        ret = swSocket_udp_sendto(sock->fd, addr, port, data, l_data);
    }
    else if (sock->domain == AF_INET6)
    {
        ret = swSocket_udp_sendto6(sock->fd, addr, port, data, l_data);
    }
    else if (sock->domain == AF_UNIX)
    {
        ret = swSocket_unix_sendto(sock->fd, addr, data, l_data);
    }
    else
    {
        RETURN_FALSE;
    }

    if (ret < 0)
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"), errno TSRMLS_CC);
        RETURN_FALSE;
    }
    else
    {
        RETURN_LONG(ret);
    }
}

static PHP_METHOD(swoole_socket_coro, close)
{
    coro_check(TSRMLS_C);

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    if (sock->fd < 0)
    {
        RETURN_FALSE;
    }
    if (unlikely(sock->cid && sock->cid != sw_get_current_cid()))
    {
        swoole_php_fatal_error(E_WARNING, "socket has already been bound to another coroutine.");
        RETURN_FALSE;
    }
    int ret = SwooleG.main_reactor->close(SwooleG.main_reactor, sock->fd);
    sock->fd = -1;
    SW_CHECK_RETURN(ret);
}

static PHP_METHOD(swoole_socket_coro, getsockname)
{
    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    array_init(return_value);

    swSocketAddress info;
    char addr_str[INET6_ADDRSTRLEN + 1];

    if (getsockname(sock->fd, (struct sockaddr *) &info.addr.inet_v4, &info.len) != 0)
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"), errno TSRMLS_CC);
        RETURN_FALSE;
    }

    switch (sock->domain)
    {
    case AF_INET6:
        inet_ntop(AF_INET6, &info.addr.inet_v6.sin6_addr, addr_str, INET6_ADDRSTRLEN);
        add_assoc_string(return_value, "address", addr_str);
        add_assoc_long(return_value, "port", htons(info.addr.inet_v6.sin6_port));
        break;
    case AF_INET:
        inet_ntop(AF_INET, &info.addr.inet_v4.sin_addr, addr_str, INET_ADDRSTRLEN);
        add_assoc_string(return_value, "address", addr_str);
        add_assoc_long(return_value, "port", htons(info.addr.inet_v4.sin_port));
        break;
    case AF_UNIX:
        add_assoc_string(return_value, "address", info.addr.un.sun_path);
        break;
    default:
        swoole_php_error(E_WARNING, "Unsupported address family %d", sock->domain);
        RETURN_FALSE;
    }
}

static PHP_METHOD(swoole_socket_coro, getpeername)
{
    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    array_init(return_value);

    swSocketAddress info;
    char addr_str[INET6_ADDRSTRLEN + 1];

    if (getpeername(sock->fd, (struct sockaddr *) &info.addr.inet_v4, &info.len) != 0)
    {
        zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"), errno TSRMLS_CC);
        RETURN_FALSE;
    }

    switch (sock->domain)
    {
    case AF_INET6:
        inet_ntop(AF_INET6, &info.addr.inet_v6.sin6_addr, addr_str, INET6_ADDRSTRLEN);
        add_assoc_string(return_value, "address", addr_str);
        add_assoc_long(return_value, "port", htons(info.addr.inet_v6.sin6_port));
        break;
    case AF_INET:
        inet_ntop(AF_INET, &info.addr.inet_v4.sin_addr, addr_str, INET_ADDRSTRLEN);
        add_assoc_string(return_value, "address", addr_str);
        add_assoc_long(return_value, "port", htons(info.addr.inet_v4.sin_port));
        break;
    case AF_UNIX:
        add_assoc_string(return_value, "address", info.addr.un.sun_path);
        break;
    default:
        swoole_php_error(E_WARNING, "Unsupported address family %d", sock->domain);
        RETURN_FALSE;
    }
}

static PHP_METHOD(swoole_socket_coro, connect)
{
    coro_check(TSRMLS_C);

    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    char *host;
    size_t l_host;
    zend_long port = 0;
    double timeout = SW_CLIENT_DEFAULT_TIMEOUT;

    ZEND_PARSE_PARAMETERS_START(1, 3)
        Z_PARAM_STRING(host, l_host);
        Z_PARAM_OPTIONAL
        Z_PARAM_LONG(port);
        Z_PARAM_DOUBLE(timeout);
    ZEND_PARSE_PARAMETERS_END();

    if (sock->domain == AF_INET6 || sock->domain == AF_INET)
    {
        if (ZEND_NUM_ARGS() == 1)
        {
            swoole_php_error(E_WARNING, "Socket of type AF_INET/AF_INET6 requires port argument");
            RETURN_FALSE;
        }
        else if (port == 0 || port >= 65536)
        {
            swoole_php_error(E_WARNING, "Invalid port argument[%d]", port);
            RETURN_FALSE;
        }
    }
    if (unlikely(sock->cid && sock->cid != sw_get_current_cid()))
    {
        swoole_php_fatal_error(E_WARNING, "socket has already been bound to another coroutine.");
        RETURN_FALSE;
    }

    int retval = swoole_socket_connect(sock, host, l_host, port);
    if (retval == -2)
    {
        swAio_event ev;
        bzero(&ev, sizeof(swAio_event));

        ev.nbytes = l_host < SW_IP_MAX_LENGTH ? SW_IP_MAX_LENGTH : l_host + 1;
        ev.buf = emalloc(ev.nbytes);
        if (!ev.buf)
        {
            swWarn("malloc failed.");
            RETURN_FALSE;
        }

        memcpy(ev.buf, host, l_host);
        ((char *) ev.buf)[l_host] = 0;
        ev.flags = sock->domain;
        ev.type = SW_AIO_GETHOSTBYNAME;
        ev.object = sock;
        ev.callback = socket_onResolveCompleted;

        php_swoole_check_aio();

        if (swAio_dispatch(&ev) < 0)
        {
            efree(ev.buf);
            RETURN_FALSE
        }
        else
        {
            ZVAL_LONG(&sock->context.coro_params, port);
            zval *ztimeout;
            if (timeout > 0)
            {
                ztimeout = emalloc(sizeof(zval));
                ZVAL_DOUBLE(ztimeout, timeout);
                sock->context.private_data = ztimeout;
            }
            else
            {
                sock->context.private_data = NULL;
            }
            goto _yield;
        }
    }
    else if (retval == -1)
    {
        if (errno == EINPROGRESS)
        {
            if (SwooleG.main_reactor->add(SwooleG.main_reactor, sock->fd, PHP_SWOOLE_FD_SOCKET | SW_EVENT_WRITE) < 0)
            {
                goto _error;
            }

            swConnection *_socket = swReactor_get(SwooleG.main_reactor, sock->fd);
            _socket->object = sock;

            if (timeout > 0)
            {
                int ms = (int) (timeout * 1000);
                php_swoole_check_timer(ms);
                sock->timer = SwooleG.timer.add(&SwooleG.timer, ms, 0, sock, socket_onTimeout);
            }

            php_context *context;
            _yield: context = &sock->context;
            context->state = SW_CORO_CONTEXT_RUNNING;
            context->onTimeout = NULL;
            sock->opcode = SW_SOCKET_OPCODE_CONNECT;

            coro_save(context);
            coro_yield();
        }
        else
        {
            _error: zend_update_property_long(swoole_socket_coro_class_entry_ptr, getThis(), ZEND_STRL("errCode"),
                    errno TSRMLS_CC);
        }
    }
    else if (retval == 0)
    {
        RETURN_TRUE;
    }
    else
    {
        RETURN_FALSE;
    }
}

#ifdef SWOOLE_SOCKETS_SUPPORT
static PHP_METHOD(swoole_socket_coro, getSocket)
{
    socket_coro *sock = (socket_coro *) Z_SOCKET_CORO_OBJ_P(getThis());
    if (sock->fd < 0)
    {
        RETURN_FALSE;
    }
    php_socket *socket_object = swoole_convert_to_socket(sock->fd);
    if (!socket_object)
    {
        RETURN_FALSE;
    }
    SW_ZEND_REGISTER_RESOURCE(return_value, (void * ) socket_object, php_sockets_le_socket());
    zval *zsocket = sw_zval_dup(return_value);
    sw_zval_add_ref(&zsocket);
    sock->resource = zsocket;
}
#endif
#endif