The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#define NEED_sv_2pvbyte
#include "ppport.h"

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include <uv.h>

#define UV_ERRNO_CONST_GEN(val, name, s) \
    newCONSTSUB(stash, #name, newSViv(val));

#define UV_CONST_GEN(uc, lc) \
    newCONSTSUB(stash, #uc, newSViv(UV_##uc));    \
    newCONSTSUB(stash, "UV_" #uc, newSViv(UV_##uc));

/* Handle wrappers */
typedef struct p5uv_handle_s p5uv_handle_t;
typedef struct p5uv_stream_s p5uv_stream_t;
typedef struct p5uv_tcp_s p5uv_tcp_t;
typedef struct p5uv_udp_s p5uv_udp_t;
typedef struct p5uv_pipe_s p5uv_pipe_t;
typedef struct p5uv_tty_s p5uv_tty_t;
typedef struct p5uv_poll_s p5uv_poll_t;
typedef struct p5uv_timer_s p5uv_timer_t;
typedef struct p5uv_prepare_s p5uv_prepare_t;
typedef struct p5uv_check_s p5uv_check_t;
typedef struct p5uv_idle_s p5uv_idle_t;
typedef struct p5uv_async_s p5uv_async_t;

#define P5UV_HANDLE_FIELDS \
    uv_handle_t* handle;   \
    SV* close_cb;

struct p5uv_handle_s {
    P5UV_HANDLE_FIELDS
};

#define P5UV_STREAM_FIELDS \
    SV* read_cb;           \
    SV* write_cb;          \
    SV* connect_cb;        \
    SV* connection_cb;     \
    SV* shutdown_cb;

struct p5uv_stream_s {
    P5UV_HANDLE_FIELDS
    P5UV_STREAM_FIELDS
};

struct p5uv_tcp_s {
    P5UV_HANDLE_FIELDS
    P5UV_STREAM_FIELDS
};

struct p5uv_udp_s {
    P5UV_HANDLE_FIELDS
    SV* send_cb;
    SV* recv_cb;
};

struct p5uv_tty_s {
    P5UV_HANDLE_FIELDS
    P5UV_STREAM_FIELDS
};

struct p5uv_pipe_s {
    P5UV_HANDLE_FIELDS
    P5UV_STREAM_FIELDS
};

struct p5uv_poll_s {
    P5UV_HANDLE_FIELDS
    SV* cb;
};

struct p5uv_prepare_s {
    P5UV_HANDLE_FIELDS
    SV* cb;
};

struct p5uv_check_s {
    P5UV_HANDLE_FIELDS
    SV* cb;
};

struct p5uv_idle_s {
    P5UV_HANDLE_FIELDS
    SV* cb;
};

struct p5uv_async_s {
    P5UV_HANDLE_FIELDS
    SV* cb;
};

struct p5uv_timer_s {
    P5UV_HANDLE_FIELDS
    SV* cb;
};

#undef P5UV_HANDLE_FIELDS
#undef P5UV_STREAM_FIELDS

static p5uv_handle_t* p5uv_handle_init(uv_handle_t* uv_handle) {
    p5uv_handle_t* p5uv_handle;

    switch (uv_handle->type) {
        case UV_TCP:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_tcp_t));
            break;
        case UV_UDP:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_udp_t));
            break;
        case UV_TTY:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_tty_t));
            break;
        case UV_NAMED_PIPE:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_pipe_t));
            break;
        case UV_POLL:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_poll_t));
            break;
        case UV_PREPARE:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_prepare_t));
            break;
        case UV_CHECK:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_check_t));
            break;
        case UV_IDLE:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_idle_t));
            break;
        case UV_ASYNC:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_async_t));
            break;
        case UV_TIMER:
            p5uv_handle = (p5uv_handle_t*)calloc(1, sizeof(p5uv_timer_t));
            break;
        default:
            croak("Unknown handle type: %d", uv_handle->type);
    }

    if (NULL == p5uv_handle) {
        croak("cannot allocate handle wrapper");
    }

    return p5uv_handle;
}

static SV* sv_handle_wrap(uv_handle_t* uv_handle) {
    SV* sv;
    HV* hv;

    hv = (HV*)sv_2mortal((SV*)newHV());
    sv = sv_2mortal(newRV_inc((SV*)hv));

    sv_bless(sv, gv_stashpv("UV::handle", 1));

    sv_magic((SV*)hv, NULL, PERL_MAGIC_ext, NULL, 0);
    mg_find((SV*)hv, PERL_MAGIC_ext)->mg_obj = (SV*)uv_handle;

    return sv;
}

static SV* sv_handle_wrap_init(uv_handle_t* uv_handle) {
    uv_handle->data = (void*)p5uv_handle_init(uv_handle);
    return sv_handle_wrap(uv_handle);
}

static void shutdown_cb(uv_shutdown_t* req, int status) {
    uv_stream_t* stream = req->handle;
    p5uv_stream_t* p5stream = (p5uv_stream_t*)stream->data;
    SV* sv_status;
    dSP;

    sv_status = sv_2mortal(newSViv(status));

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    XPUSHs(sv_status);
    PUTBACK;

    call_sv(p5stream->shutdown_cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;

    free(req);
}

static void close_cb(uv_handle_t* handle) {
    p5uv_handle_t* p5handle;
    p5uv_stream_t* p5stream;
    p5uv_udp_t* p5udp;
    p5uv_poll_t* p5poll;
    dSP;

    p5handle = (p5uv_handle_t*)handle->data;

    if (p5handle->close_cb) {
        ENTER;
        SAVETMPS;

        PUSHMARK(SP);
        PUTBACK;

        call_sv(p5handle->close_cb, G_SCALAR);

        SPAGAIN;

        PUTBACK;
        FREETMPS;
        LEAVE;

        SvREFCNT_dec(p5handle->close_cb);
    }

    switch (handle->type) {
        case UV_TCP:
        case UV_TTY:
        case UV_NAMED_PIPE:
            /* stream */
            p5stream = (p5uv_stream_t*)p5handle;
            if (NULL != p5stream->read_cb)
                SvREFCNT_dec(p5stream->read_cb);
            if (NULL != p5stream->write_cb)
                SvREFCNT_dec(p5stream->write_cb);
            if (NULL != p5stream->connect_cb)
                SvREFCNT_dec(p5stream->connect_cb);
            if (NULL != p5stream->connection_cb)
                SvREFCNT_dec(p5stream->connection_cb);
            if (NULL != p5stream->shutdown_cb)
                SvREFCNT_dec(p5stream->shutdown_cb);
            break;

        case UV_UDP:
            p5udp = (p5uv_udp_t*)p5handle;
            if (NULL != p5udp->send_cb)
                SvREFCNT_dec(p5udp->send_cb);
            if (NULL != p5udp->recv_cb)
                SvREFCNT_dec(p5udp->recv_cb);
            break;

        case UV_POLL:
        case UV_PREPARE:
        case UV_CHECK:
        case UV_IDLE:
        case UV_ASYNC:
        case UV_TIMER:
            /* simple cb handles */
            p5poll = (p5uv_poll_t*)p5handle;
            if (NULL != p5poll->cb)
                SvREFCNT_dec(p5poll->cb);
            break;

        default:
            croak("unknown handle type: %d", handle->type);
    }

    free(handle);
    Safefree(p5handle);
}

static void poll_cb(uv_poll_t* handle, int status, int events) {
    SV* sv_status;
    SV* sv_events;
    p5uv_poll_t* p5poll = (p5uv_poll_t*)handle->data;

    dSP;

    ENTER;
    SAVETMPS;

    sv_status = sv_2mortal(newSViv(status));
    sv_events = sv_2mortal(newSViv(events));

    PUSHMARK(SP);
    XPUSHs(sv_status);
    XPUSHs(sv_events);
    PUTBACK;

    call_sv(p5poll->cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;
}

static void connection_cb(uv_stream_t* server, int status) {
    p5uv_stream_t* p5stream = (p5uv_stream_t*)server->data;

    dSP;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    PUTBACK;

    assert(0 == status);

    call_sv(p5stream->connection_cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;
}

static void connect_cb(uv_connect_t* req, int status) {
    uv_stream_t* stream = req->handle;
    SV* sv_status;
    SV* cb;

    dSP;

    ENTER;
    SAVETMPS;

    sv_status = sv_2mortal(newSViv(status));

    PUSHMARK(SP);
    XPUSHs(sv_status);
    PUTBACK;

    cb = ((p5uv_stream_t*)stream->data)->connect_cb;

    call_sv(cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;

    Safefree(req);
}

static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) {
    char* buf;

    PERL_UNUSED_ARG(handle);

    buf = (char*)malloc(suggested_size);
    if (NULL == buf) {
        croak("cannot allocate buffer");
    }

    return uv_buf_init(buf, suggested_size);
}

static void read_cb(uv_stream_t* stream, ssize_t nread, uv_buf_t buf) {
    SV* sv_nread;
    SV* sv_buf;
    p5uv_stream_t* p5stream = (p5uv_stream_t*)stream->data;

    dSP;

    ENTER;
    SAVETMPS;

    sv_nread = sv_2mortal(newSViv(nread));
    if (nread > 0) {
        sv_buf = sv_2mortal(newSVpv(buf.base, nread));
    }
    else {
        sv_buf = sv_2mortal(newSV(0));
    }

    PUSHMARK(SP);
    XPUSHs(sv_nread);
    XPUSHs(sv_buf);
    PUTBACK;

    call_sv(p5stream->read_cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;

    free(buf.base);
}

static void read2_cb(uv_pipe_t* pipe, ssize_t nread, uv_buf_t buf, uv_handle_type pending) {
    SV* sv_nread;
    SV* sv_buf;
    SV* sv_pending;
    p5uv_stream_t* p5stream = (p5uv_stream_t*)pipe->data;

    dSP;

    ENTER;
    SAVETMPS;

    sv_nread = sv_2mortal(newSViv(nread));
    if (nread > 0) {
        sv_buf = sv_2mortal(newSVpv(buf.base, nread));
    }
    else {
        sv_buf = sv_2mortal(newSV(0));
    }
    sv_pending = sv_2mortal(newSViv(pending));

    PUSHMARK(SP);
    XPUSHs(sv_nread);
    XPUSHs(sv_buf);
    XPUSHs(sv_pending);
    PUTBACK;

    call_sv(p5stream->read_cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;

    free(buf.base);
}

static void write_cb(uv_write_t* req, int status){
    uv_stream_t* stream = req->handle;
    p5uv_stream_t* p5stream = (p5uv_stream_t*)stream->data;
    SV* sv_status;

    dSP;

    if (p5stream->write_cb) {
        ENTER;
        SAVETMPS;

        sv_status = sv_2mortal(newSViv(status));

        PUSHMARK(SP);
        XPUSHs(sv_status);
        PUTBACK;

        call_sv(p5stream->write_cb, G_SCALAR);

        SPAGAIN;

        PUTBACK;
        FREETMPS;
        LEAVE;
    }

    Safefree(req);
}

static void send_cb(uv_udp_send_t* req, int status) {
    uv_udp_t* udp     = req->handle;
    p5uv_udp_t* p5udp = (p5uv_udp_t*)udp->data;
    SV* sv_status;
    dSP;

    if (p5udp->send_cb) {
        ENTER;
        SAVETMPS;

        sv_status = sv_2mortal(newSViv(status));

        PUSHMARK(SP);
        XPUSHs(sv_status);
        PUTBACK;

        call_sv(p5udp->send_cb, G_SCALAR);

        SPAGAIN;

        PUTBACK;
        FREETMPS;
        LEAVE;
    }

    Safefree(req);
}

static void recv_cb(uv_udp_t* handle, ssize_t nread, uv_buf_t buf,
    struct sockaddr* addr, unsigned flags) {

    SV* sv_nread;
    SV* sv_buf;
    SV* sv_host;
    SV* sv_port;
    SV* sv_flags;
    struct sockaddr_in* addrin;
    struct sockaddr_in6* addrin6;
    char ip[INET6_ADDRSTRLEN];
    p5uv_udp_t* p5udp = (p5uv_udp_t*)handle->data;
    dSP;

    ENTER;
    SAVETMPS;

    sv_nread = sv_2mortal(newSViv(nread));
    sv_flags = sv_2mortal(newSViv(flags));
    if (nread > 0) {
        sv_buf = sv_2mortal(newSVpv(buf.base, nread));
    }
    else {
        sv_buf = sv_2mortal(newSV(0));
    }

    if (NULL != addr) {
        switch (addr->sa_family) {
            case AF_INET:
                addrin = (struct sockaddr_in*)addr;
                uv_inet_ntop(AF_INET, &addrin->sin_addr, ip, INET6_ADDRSTRLEN);
                sv_host = sv_2mortal(newSV(0));
                sv_setpv(sv_host, ip);
                sv_port = sv_2mortal(newSViv(ntohs(addrin->sin_port)));
                break;

            case AF_INET6:
                addrin6 = (struct sockaddr_in6*)addr;
                uv_inet_ntop(AF_INET6, &addrin6->sin6_addr, ip, INET6_ADDRSTRLEN);
                sv_host = sv_2mortal(newSV(0));
                sv_setpv(sv_host, ip);
                sv_port = sv_2mortal(newSViv(ntohs(addrin6->sin6_port)));
                break;

            default:
                assert(0 && "bad address family");
                abort();
        }
    }
    else {
        sv_host = sv_2mortal(newSV(0));
        sv_port = sv_2mortal(newSV(0));
    }

    PUSHMARK(SP);
    XPUSHs(sv_nread);
    XPUSHs(sv_buf);
    XPUSHs(sv_host);
    XPUSHs(sv_port);
    XPUSHs(sv_flags);
    PUTBACK;

    call_sv(p5udp->recv_cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;

    free(buf.base);
}

static void prepare_cb(uv_prepare_t* handle, int status) {
    SV* sv_status;
    p5uv_prepare_t* p5prepare = (p5uv_prepare_t*)handle->data;
    dSP;

    sv_status = sv_2mortal(newSViv(status));

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    XPUSHs(sv_status);
    PUTBACK;

    call_sv(p5prepare->cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;
}

static void check_cb(uv_check_t* handle, int status) {
    SV* sv_status;
    p5uv_check_t* p5check = (p5uv_check_t*)handle->data;
    dSP;

    sv_status = sv_2mortal(newSViv(status));

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    XPUSHs(sv_status);
    PUTBACK;

    call_sv(p5check->cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;
}

static void idle_cb(uv_idle_t* handle, int status) {
    SV* sv_status;
    p5uv_idle_t* p5idle = (p5uv_idle_t*)handle->data;

    dSP;

    ENTER;
    SAVETMPS;

    sv_status = sv_2mortal(newSViv(status));

    PUSHMARK(SP);
    XPUSHs(sv_status);
    PUTBACK;

    call_sv(p5idle->cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;
}

static void async_cb(uv_async_t* handle, int status) {
    SV* sv_status;
    p5uv_async_t* p5async = (p5uv_async_t*)handle->data;

    dSP;

    ENTER;
    SAVETMPS;

    sv_status = sv_2mortal(newSViv(status));

    PUSHMARK(SP);
    XPUSHs(sv_status);
    PUTBACK;

    call_sv(p5async->cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;
}

static void timer_cb(uv_timer_t* handle, int status) {
    SV* sv_status;
    p5uv_timer_t* p5timer = (p5uv_timer_t*)handle->data;

    dSP;

    ENTER;
    SAVETMPS;

    sv_status = sv_2mortal(newSViv(status));

    PUSHMARK(SP);
    XPUSHs(sv_status);
    PUTBACK;

    call_sv(p5timer->cb, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;
}

static void walk_cb(uv_handle_t* handle, void* arg) {
    SV* sv_handle = sv_handle_wrap(handle);

    dSP;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    XPUSHs(sv_handle);
    PUTBACK;

    call_sv((SV*)arg, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;
}

static void getaddrinfo_cb(uv_getaddrinfo_t* handle, int status, struct addrinfo* res) {
    SV* sv_status;
    AV* av_res;
    SV* sv_res;
    struct addrinfo* address;
    struct sockaddr_in* in;
    struct sockaddr_in6* in6;
    char ip[INET6_ADDRSTRLEN];
    SV* sv_ip;
    dSP;

    sv_status = sv_2mortal(newSViv(status));

    av_res = (AV*)sv_2mortal((SV*)newAV());
    sv_res = sv_2mortal(newRV_inc((SV*)av_res));

    if (0 == status) {
        for (address = res; address; address = address->ai_next) {
            assert(address->ai_socktype == SOCK_STREAM);

            switch (address->ai_family) {
                case AF_INET:
                    in = (struct sockaddr_in*)address->ai_addr;
                    uv_inet_ntop(AF_INET, &in->sin_addr, ip, INET6_ADDRSTRLEN);
                    sv_ip = newSV(0);
                    sv_setpv(sv_ip, ip);
                    av_push(av_res, sv_ip);
                    break;
                case AF_INET6:
                    in6 = (struct sockaddr_in6*)address->ai_addr;
                    uv_inet_ntop(AF_INET6, &in6->sin6_addr, ip, INET6_ADDRSTRLEN);
                    sv_ip = newSV(0);
                    sv_setpv(sv_ip, ip);
                    av_push(av_res, sv_ip);
                    break;
            }
        }
    }

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    XPUSHs(sv_status);
    XPUSHs(sv_res);
    PUTBACK;

    call_sv((SV*)handle->data, G_SCALAR);

    SPAGAIN;

    PUTBACK;
    FREETMPS;
    LEAVE;

    uv_freeaddrinfo(res);
    SvREFCNT_dec(handle->data);
    Safefree(handle);
}

MODULE=UV PACKAGE=UV PREFIX=uv_

PROTOTYPES: DISABLE

BOOT:
{
    HV* stash = gv_stashpv("UV", 1);

    /* errno */
    UV_ERRNO_MAP(UV_ERRNO_CONST_GEN);

    /* handle type */
    UV_HANDLE_TYPE_MAP(UV_CONST_GEN);

    /* req type */
    UV_REQ_TYPE_MAP(UV_CONST_GEN);

    /* udp */
    newCONSTSUB(stash, "UDP_IPV6ONLY", newSViv(UV_UDP_IPV6ONLY));
    newCONSTSUB(stash, "UDP_PARTIAL", newSViv(UV_UDP_PARTIAL));

    /* udp membership */
    newCONSTSUB(stash, "LEAVE_GROUP", newSViv(UV_LEAVE_GROUP));
    newCONSTSUB(stash, "JOIN_GROUP", newSViv(UV_JOIN_GROUP));

    /* poll */
    newCONSTSUB(stash, "READABLE", newSViv(UV_READABLE));
    newCONSTSUB(stash, "WRITABLE", newSViv(UV_WRITABLE));
}

void
uv_default_loop()
CODE:
{
    SV* sv;
    HV* hv;

    hv = (HV*)sv_2mortal((SV*)newHV());
    sv = sv_2mortal(newRV_inc((SV*)hv));

    sv_bless(sv, gv_stashpv("UV::loop", 1));

    sv_magic((SV*)hv, NULL, PERL_MAGIC_ext, NULL, 0);
    mg_find((SV*)hv, PERL_MAGIC_ext)->mg_obj = (SV*)uv_default_loop();

    ST(0) = sv;
    XSRETURN(1);
}

int
uv_run()
CODE:
{
    RETVAL = uv_run(uv_default_loop());
}
OUTPUT:
    RETVAL

int
uv_run_once()
CODE:
{
    RETVAL = uv_run_once(uv_default_loop());
}
OUTPUT:
    RETVAL

void
uv_version()
CODE:
{
    SV* sv;

    sv = sv_2mortal(newSV(0));
    sv_setpvf(sv, "%d.%d", UV_VERSION_MAJOR, UV_VERSION_MINOR);

    ST(0) = sv;
}

int
uv_last_error()
CODE:
{
    uv_err_t err;

    err = uv_last_error(uv_default_loop());
    RETVAL = err.code;
}
OUTPUT:
    RETVAL

const char*
uv_strerror(int code)
CODE:
{
    uv_err_t err;
    err.code = code;
    RETVAL = uv_strerror(err);
}
OUTPUT:
    RETVAL

const char*
uv_err_name(int code)
CODE:
{
    uv_err_t err;
    err.code = code;
    RETVAL = uv_err_name(err);
}
OUTPUT:
    RETVAL

int
uv_shutdown(uv_stream_t* handle, SV* cb)
CODE:
{
    uv_shutdown_t* req;
    p5uv_stream_t* p5stream;

    Newx(req, 1, uv_shutdown_t);

    p5stream = (p5uv_stream_t*)handle->data;

    if (p5stream->shutdown_cb)
        SvREFCNT_dec(p5stream->shutdown_cb);
    p5stream->shutdown_cb = SvREFCNT_inc(cb);

    RETVAL = uv_shutdown(req, handle, shutdown_cb);
}
OUTPUT:
    RETVAL

int
uv_is_active(uv_handle_t* handle)

void
uv_walk(SV* cb)
CODE:
{
    uv_walk(uv_default_loop(), walk_cb, SvREFCNT_inc(cb));
    SvREFCNT_dec(cb);
}

void
uv_close(uv_handle_t* handle, SV* cb = NULL)
CODE:
{
    p5uv_handle_t* p5handle = (p5uv_handle_t*)handle->data;

    if (p5handle->close_cb) {
        SvREFCNT_dec(p5handle->close_cb);
        p5handle->close_cb = NULL;
    }

    if (cb) {
        p5handle->close_cb = SvREFCNT_inc(cb);
    }

    uv_close(handle, close_cb);
}

NV
uv_now()
CODE:
{
    /* what's the proper way to return a int64_t? */
    RETVAL = (NV) uv_now(uv_default_loop());
}
OUTPUT:
    RETVAL

int
uv_listen(uv_stream_t* stream, int backlog, SV* cb)
CODE:
{
    p5uv_stream_t* p5stream = (p5uv_stream_t*)stream->data;

    if (p5stream->connection_cb)
        SvREFCNT_dec(p5stream->connection_cb);
    p5stream->connection_cb = SvREFCNT_inc(cb);

    RETVAL = uv_listen(stream, backlog, connection_cb);
}
OUTPUT:
    RETVAL

int
uv_accept(uv_stream_t* server, uv_stream_t* client)

int
uv_read_start(uv_stream_t* stream, SV* cb)
CODE:
{
    p5uv_stream_t* p5stream = (p5uv_stream_t*)stream->data;

    if (p5stream->read_cb)
        SvREFCNT_dec(p5stream->read_cb);
    p5stream->read_cb = SvREFCNT_inc(cb);

    RETVAL = uv_read_start(stream, alloc_cb, read_cb);
}
OUTPUT:
    RETVAL

int
uv_read_stop(uv_stream_t* stream)

int
uv_read2_start(uv_stream_t* stream, SV* cb)
CODE:
{
    p5uv_stream_t* p5stream = (p5uv_stream_t*)stream;

    if (p5stream->read_cb)
        SvREFCNT_dec(p5stream->read_cb);
    p5stream->read_cb = SvREFCNT_inc(cb);

    RETVAL = uv_read2_start(stream, alloc_cb, read2_cb);
}
OUTPUT:
    RETVAL

int
uv_write(uv_stream_t* stream, SV* sv_buf, SV* cb = NULL)
CODE:
{
    p5uv_stream_t* p5stream = (p5uv_stream_t*)stream->data;
    char* buf;
    STRLEN len;
    uv_write_t* req;
    uv_buf_t b;

    if (p5stream->write_cb) {
        SvREFCNT_dec(p5stream->write_cb);
        p5stream->write_cb = NULL;
    }

    if (cb)
        p5stream->write_cb = SvREFCNT_inc(cb);

    Newx(req, 1, uv_write_t);

    buf = SvPV(sv_buf, len);
    b = uv_buf_init(buf, len);

    RETVAL = uv_write(req, stream, &b, 1, write_cb);
}
OUTPUT:
    RETVAL

int
uv_write2(uv_stream_t* stream, SV* sv_buf, uv_stream_t* send_stream, SV* cb = NULL)
CODE:
{
    p5uv_stream_t* p5stream = (p5uv_stream_t*)stream->data;
    char* buf;
    STRLEN len;
    uv_write_t* req;
    uv_buf_t b;

    if (p5stream->write_cb) {
        SvREFCNT_dec(p5stream->write_cb);
        p5stream->write_cb = NULL;
    }

    if (cb)
        p5stream->write_cb = SvREFCNT_inc(cb);

    Newx(req, 1, uv_write_t);

    buf = SvPV(sv_buf, len);
    b = uv_buf_init(buf, len);

    RETVAL = uv_write2(req, stream, &b, 1, send_stream, write_cb);
}
OUTPUT:
    RETVAL

int
uv_is_readable(uv_stream_t* stream);

int
uv_is_writable(uv_stream_t* stream);

int
uv_is_closing(uv_handle_t* handle);

void
uv_tcp_init()
CODE:
{
    uv_tcp_t* tcp;
    int r;

    Newx(tcp, 1, uv_tcp_t);

    r = uv_tcp_init(uv_default_loop(), tcp);
    if (r) {
        croak("cannot initialize tcp handle");
    }

    ST(0) = sv_handle_wrap_init((uv_handle_t*)tcp);
    XSRETURN(1);
}

int
uv_tcp_nodelay(uv_tcp_t* tcp, int enable = 1)

int
uv_tcp_keepalive(uv_tcp_t* tcp, int enable, unsigned int delay)

int
uv_tcp_simultaneous_accepts(uv_tcp_t* tcp, int enable)

int
uv_tcp_bind(uv_tcp_t* tcp, const char* ip, int port)
CODE:
{
    RETVAL = uv_tcp_bind(tcp, uv_ip4_addr(ip, port));
}
OUTPUT:
    RETVAL

int
uv_tcp_bind6(uv_tcp_t* tcp, const char* ip, int port)
CODE:
{
    RETVAL = uv_tcp_bind6(tcp, uv_ip6_addr(ip, port));
}
OUTPUT:
    RETVAL

void
uv_tcp_getsockname(uv_tcp_t* handle)
CODE:
{
    int r;
    struct sockaddr_storage address;
    struct sockaddr_in* in;
    struct sockaddr_in6* in6;
    int addrlen;
    char ip[INET6_ADDRSTRLEN];

    SV* sv_ip;
    SV* sv_port;

    addrlen = sizeof(address);
    r = uv_tcp_getsockname(handle, (struct sockaddr*)&address, &addrlen);
    assert(0 == r);

    switch (address.ss_family) {
        case AF_INET:
            in = (struct sockaddr_in*)&address;
            uv_inet_ntop(AF_INET, &in->sin_addr, ip, INET6_ADDRSTRLEN);
            sv_ip = sv_2mortal(newSV(0));
            sv_setpv(sv_ip, ip);
            sv_port = sv_2mortal(newSViv(ntohs(in->sin_port)));
            break;
        case AF_INET6:
            in6 = (struct sockaddr_in6*)&address;
            uv_inet_ntop(AF_INET6, &in6->sin6_addr, ip, INET6_ADDRSTRLEN);
            sv_ip = sv_2mortal(newSV(0));
            sv_setpv(sv_ip, ip);
            sv_port = sv_2mortal(newSViv(htons(in6->sin6_port)));
            break;
        default:
            croak("bad family");
    }

    ST(0) = sv_ip;
    ST(1) = sv_port;
    XSRETURN(2);
}

void
uv_tcp_getpeername(uv_tcp_t* handle)
CODE:
{
    int r;
    struct sockaddr_storage address;
    struct sockaddr_in* in;
    struct sockaddr_in6* in6;
    int addrlen;
    char ip[INET6_ADDRSTRLEN];

    SV* sv_ip;
    SV* sv_port;

    addrlen = sizeof(address);
    r = uv_tcp_getpeername(handle, (struct sockaddr*)&address, &addrlen);
    assert(0 == r);

    switch (address.ss_family) {
        case AF_INET:
            in = (struct sockaddr_in*)&address;
            uv_inet_ntop(AF_INET, &in->sin_addr, ip, INET6_ADDRSTRLEN);
            sv_ip = sv_2mortal(newSV(0));
            sv_setpv(sv_ip, ip);
            sv_port = sv_2mortal(newSViv(ntohs(in->sin_port)));
            break;
        case AF_INET6:
            in6 = (struct sockaddr_in6*)&address;
            uv_inet_ntop(AF_INET6, &in6->sin6_addr, ip, INET6_ADDRSTRLEN);
            sv_ip = sv_2mortal(newSV(0));
            sv_setpv(sv_ip, ip);
            sv_port = sv_2mortal(newSViv(htons(in6->sin6_port)));
            break;
        default:
            croak("bad family");
    }

    ST(0) = sv_ip;
    ST(1) = sv_port;
    XSRETURN(2);
}

int
uv_tcp_connect(uv_tcp_t* tcp, const char* ip, int port, SV* cb)
CODE:
{
    uv_connect_t* req;
    p5uv_tcp_t* p5tcp = (p5uv_tcp_t*)tcp->data;

    if (p5tcp->connect_cb)
        SvREFCNT_dec(p5tcp->connect_cb);
    p5tcp->connect_cb = SvREFCNT_inc(cb);

    Newx(req, 1, uv_connect_t);

    RETVAL = uv_tcp_connect(req, tcp, uv_ip4_addr(ip, port), connect_cb);
}
OUTPUT:
    RETVAL

int
uv_tcp_connect6(uv_tcp_t* tcp, const char* ip, int port, SV* cb)
CODE:
{
    uv_connect_t* req;
    p5uv_tcp_t* p5tcp = (p5uv_tcp_t*)tcp->data;

    if (p5tcp->connect_cb)
        SvREFCNT_dec(p5tcp->connect_cb);
    p5tcp->connect_cb = SvREFCNT_inc(cb);

    Newx(req, 1, uv_connect_t);

    RETVAL = uv_tcp_connect6(req, tcp, uv_ip6_addr(ip, port), connect_cb);
}
OUTPUT:
    RETVAL

void
uv_udp_init()
CODE:
{
    uv_udp_t* udp;
    int r;

    Newx(udp, 1, uv_udp_t);

    r = uv_udp_init(uv_default_loop(), udp);
    if (r) {
        croak("cannot initialize udp handle");
    }

    ST(0) = sv_handle_wrap_init((uv_handle_t*)udp);
    XSRETURN(1);
}

int
uv_udp_bind(uv_udp_t* udp, const char* ip, int port, int flags = 0)
CODE:
{
    RETVAL = uv_udp_bind(udp, uv_ip4_addr(ip, port), flags);
}
OUTPUT:
    RETVAL

int
uv_udp_bind6(uv_udp_t* udp, const char* ip, int port, int flags = 0)
CODE:
{
    RETVAL = uv_udp_bind6(udp, uv_ip6_addr(ip, port), flags);
}
OUTPUT:
    RETVAL

void
uv_udp_getsockname(uv_udp_t* udp)
CODE:
{
    struct sockaddr_storage address;
    struct sockaddr_in* in;
    struct sockaddr_in6* in6;
    char ip[INET6_ADDRSTRLEN];
    int addrlen;
    int r;
    SV* sv_ip;
    SV* sv_port;

    addrlen = sizeof(address);
    r = uv_udp_getsockname(udp, (struct sockaddr*)&address, &addrlen);
    assert(0 == r);

    switch (address.ss_family) {
        case AF_INET:
            in = (struct sockaddr_in*)&address;
            uv_inet_ntop(AF_INET, &in->sin_addr, ip, INET6_ADDRSTRLEN);
            sv_ip = sv_2mortal(newSV(0));
            sv_setpv(sv_ip, ip);
            sv_port = sv_2mortal(newSViv(ntohs(in->sin_port)));
            break;
        case AF_INET6:
            in6 = (struct sockaddr_in6*)&address;
            uv_inet_ntop(AF_INET6, &in6->sin6_addr, ip, INET6_ADDRSTRLEN);
            sv_ip = sv_2mortal(newSV(0));
            sv_setpv(sv_ip, ip);
            sv_port = sv_2mortal(newSViv(ntohs(in6->sin6_port)));
            break;
        default:
            croak("bad address family");
    }

    ST(0) = sv_ip;
    ST(1) = sv_port;
    XSRETURN(2);
}

int
uv_udp_set_membership(uv_udp_t* udp, const char* multicast_addr, const char* interface_addr, int membership)

int
uv_udp_set_multicast_loop(uv_udp_t* udp, int on);

int
uv_udp_set_multicast_ttl(uv_udp_t* udp, int ttl);

int
uv_udp_set_broadcast(uv_udp_t* udp, int on);

int
uv_udp_set_ttl(uv_udp_t* udp, int ttl);

int
uv_udp_send(uv_udp_t* udp, SV* sv_buf, const char* ip, int port, SV* cb = NULL)
CODE:
{
    p5uv_udp_t* p5udp = (p5uv_udp_t*)udp->data;
    char* buf;
    STRLEN len;
    uv_udp_send_t* req;
    uv_buf_t b;

    if (p5udp->send_cb) {
        SvREFCNT_dec(p5udp->send_cb);
        p5udp->send_cb = NULL;
    }

    if (cb) {
        p5udp->send_cb = SvREFCNT_inc(cb);
    }

    Newx(req, 1, uv_udp_send_t);

    buf = SvPV(sv_buf, len);
    b   = uv_buf_init(buf, len);

    RETVAL = uv_udp_send(req, udp, &b, 1, uv_ip4_addr(ip, port), send_cb);
}
OUTPUT:
    RETVAL

int
uv_udp_send6(uv_udp_t* udp, SV* sv_buf, const char* ip, int port, SV* cb = NULL)
CODE:
{
    p5uv_udp_t* p5udp = (p5uv_udp_t*)udp->data;
    char* buf;
    STRLEN len;
    uv_udp_send_t* req;
    uv_buf_t b;

    if (p5udp->send_cb) {
        SvREFCNT_dec(p5udp->send_cb);
        p5udp->send_cb = NULL;
    }

    if (cb) {
        p5udp->send_cb = SvREFCNT_inc(cb);
    }

    Newx(req, 1, uv_udp_send_t);

    buf = SvPV(sv_buf, len);
    b   = uv_buf_init(buf, len);

    RETVAL = uv_udp_send6(req, udp, &b, 1, uv_ip6_addr(ip, port), send_cb);
}
OUTPUT:
    RETVAL

int
uv_udp_recv_start(uv_udp_t* udp, SV* cb)
CODE:
{
    p5uv_udp_t* p5udp = (p5uv_udp_t*)udp->data;

    if (p5udp->recv_cb)
        SvREFCNT_dec(p5udp->recv_cb);
    p5udp->recv_cb = SvREFCNT_inc(cb);

    RETVAL = uv_udp_recv_start(udp, alloc_cb, recv_cb);
}
OUTPUT:
    RETVAL

int
uv_udp_recv_stop(uv_udp_t* udp)

void
uv_tty_init(int fd, int readable)
CODE:
{
    uv_tty_t* tty;
    int r;

    Newx(tty, 1, uv_tty_t);

    r = uv_tty_init(uv_default_loop(), tty, fd, readable);
    if (r) {
        croak("cannot initialize tty handle");
    }

    ST(0) = sv_handle_wrap_init((uv_handle_t*)tty);
    XSRETURN(1);
}

int
uv_tty_set_mode(uv_tty_t* tty, int mode)

void
uv_tty_reset_mode()

void
uv_tty_get_winsize(uv_tty_t* tty)
CODE:
{
    int width, height;
    int r;
    SV* sv_width;
    SV* sv_height;

    r = uv_tty_get_winsize(tty, &width, &height);
    assert(0 == r);

    sv_width = sv_2mortal(newSViv(width));
    sv_height = sv_2mortal(newSViv(height));

    ST(0) = sv_width;
    ST(1) = sv_height;
    XSRETURN(2);
}

void
uv_poll_init(int fd)
CODE:
{
    uv_poll_t* poll;
    int r;

    Newx(poll, 1, uv_poll_t);

    r = uv_poll_init(uv_default_loop(), poll, fd);
    if (r) {
        croak("cannot initialize poll handle");
    }

    ST(0) = sv_handle_wrap_init((uv_handle_t*)poll);
    XSRETURN(1);
}

int
uv_poll_start(uv_poll_t* handle, int events, SV* cb)
CODE:
{
    p5uv_poll_t* p5poll = (p5uv_poll_t*)handle->data;

    if (p5poll->cb)
        SvREFCNT_dec(p5poll->cb);
    p5poll->cb = SvREFCNT_inc(cb);

    RETVAL = uv_poll_start(handle, events, poll_cb);
}
OUTPUT:
    RETVAL

int
uv_poll_stop(uv_poll_t* handle)

int
uv_guess_handle(int fd)

void
uv_pipe_init(int ipc)
CODE:
{
    uv_pipe_t* pipe;
    int r;

    Newx(pipe, 1, uv_pipe_t);

    r = uv_pipe_init(uv_default_loop(), pipe, ipc);
    if (r) {
        croak("cannot initialize pipe handle");
    }

    ST(0) = sv_handle_wrap_init((uv_handle_t*)pipe);
    XSRETURN(1);
}

void
uv_pipe_open(uv_pipe_t* pipe, int fd)

int
uv_pipe_bind(uv_pipe_t* pipe, const char* name)

void
uv_pipe_connect(uv_pipe_t* pipe, const char* name, SV* cb)
CODE:
{
    uv_connect_t* req;
    p5uv_pipe_t* p5pipe = (p5uv_pipe_t*)pipe->data;

    if (p5pipe->connect_cb)
        SvREFCNT_dec(p5pipe->connect_cb);
    p5pipe->connect_cb = SvREFCNT_inc(cb);

    Newx(req, 1, uv_connect_t);

    uv_pipe_connect(req, pipe, name, connect_cb);
}

void
uv_prepare_init()
CODE:
{
    uv_prepare_t* prepare;
    int r;

    Newx(prepare, 1, uv_prepare_t);

    r = uv_prepare_init(uv_default_loop(), prepare);
    if (r) {
        croak("cannot initialize prepare handle");
    }

    ST(0) = sv_handle_wrap_init((uv_handle_t*)prepare);
    XSRETURN(1);
}

int
uv_prepare_start(uv_prepare_t* prepare, SV* cb)
CODE:
{
    p5uv_prepare_t* p5prepare = (p5uv_prepare_t*)prepare->data;

    if (p5prepare->cb)
        SvREFCNT_dec(p5prepare->cb);
    p5prepare->cb = SvREFCNT_inc(cb);

    RETVAL = uv_prepare_start(prepare, prepare_cb);
}
OUTPUT:
    RETVAL

int
uv_prepare_stop(uv_prepare_t* prepare)

void
uv_check_init()
CODE:
{
    uv_check_t* check;
    int r;

    Newx(check, 1, uv_check_t);

    r = uv_check_init(uv_default_loop(), check);
    if (r) {
        croak("cannot initialize check handle");
    }

    ST(0) = sv_handle_wrap_init((uv_handle_t*)check);
    XSRETURN(1);
}

int
uv_check_start(uv_check_t* check, SV* cb)
CODE:
{
    p5uv_check_t* p5check = (p5uv_check_t*)check->data;

    if (p5check->cb)
        SvREFCNT_dec(p5check->cb);
    p5check->cb = SvREFCNT_inc(cb);

    RETVAL = uv_check_start(check, check_cb);
}
OUTPUT:
    RETVAL

int
uv_check_stop(uv_check_t* check)

void
uv_idle_init()
CODE:
{
    uv_idle_t* idle;
    int r;

    Newx(idle, 1, uv_idle_t);

    r = uv_idle_init(uv_default_loop(), idle);
    if (r) {
        croak("cannot initialize idle handle");
    }

    ST(0) = sv_handle_wrap_init((uv_handle_t*)idle);
    XSRETURN(1);
}

int
uv_idle_start(uv_idle_t* idle, SV* cb)
CODE:
{
    p5uv_idle_t* p5idle = (p5uv_idle_t*)idle->data;

    if (p5idle->cb)
        SvREFCNT_dec(p5idle->cb);
    p5idle->cb = SvREFCNT_inc(cb);

    RETVAL = uv_idle_start(idle, idle_cb);
}
OUTPUT:
    RETVAL

int
uv_idle_stop(uv_idle_t* idle)

void
uv_async_init(SV* cb)
CODE:
{
    SV* sv_async;
    uv_async_t* async;
    p5uv_async_t* p5async;
    int r;

    Newx(async, 1, uv_async_t);

    r = uv_async_init(uv_default_loop(), async, async_cb);
    if (r) {
        croak("cannot initialize async handle");
    }

    sv_async = sv_handle_wrap_init((uv_handle_t*)async);

    p5async = (p5uv_async_t*)async->data;
    p5async->cb = SvREFCNT_inc(cb);

    ST(0) = sv_async;
    XSRETURN(1);
}

int
uv_async_send(uv_async_t* async)

void
uv_timer_init()
CODE:
{
    uv_timer_t* timer;
    int r;

    Newx(timer, 1, uv_timer_t);

    r = uv_timer_init(uv_default_loop(), timer);
    if (r) {
        croak("cannot initialize timer handle");
    }

    ST(0) = sv_handle_wrap_init((uv_handle_t*)timer);
    XSRETURN(1);
}

int
uv_timer_start(uv_timer_t* timer, double timeout, double repeat, SV* cb)
CODE:
{
    p5uv_timer_t* p5timer = (p5uv_timer_t*)timer->data;

    if (p5timer->cb)
        SvREFCNT_dec(p5timer->cb);
    p5timer->cb = SvREFCNT_inc(cb);

    RETVAL = uv_timer_start(timer, timer_cb, (int64_t)timeout, (int64_t)repeat);
}
OUTPUT:
    RETVAL

int
uv_timer_stop(uv_timer_t* timer)

int
uv_timer_again(uv_timer_t* timer)

void
uv_timer_set_repeat(uv_timer_t* timer, double repeat)
CODE:
{
    uv_timer_set_repeat(timer, (int64_t)repeat);
}

double
uv_timer_get_repeat(uv_timer_t* timer)
CODE:
{
    RETVAL = (double)uv_timer_get_repeat(timer);
}
OUTPUT:
    RETVAL

int
uv_getaddrinfo(const char* node, SV* sv_service, SV* cb, int hint = 0)
CODE:
{
    uv_getaddrinfo_t* handle;
    struct addrinfo hints;
    int fam;
    char* service = NULL;

    if (SvPOK(sv_service)) {
        service = SvPV_nolen(sv_service);
    }

    fam = AF_UNSPEC;
    switch (hint) {
        case 4:
            fam = AF_INET;
            break;
        case 6:
            fam = AF_INET6;
            break;
    }

    Newx(handle, 1, uv_getaddrinfo_t);

    handle->data = (void*)SvREFCNT_inc(cb);

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = fam;
    hints.ai_socktype = SOCK_STREAM;

    RETVAL = uv_getaddrinfo(uv_default_loop(), handle, getaddrinfo_cb, node, service, &hints);
}
OUTPUT:
    RETVAL

MODULE=UV PACKAGE=UV::loop

unsigned int
active_handles(SV* sv_loop)
CODE:
{
    MAGIC* m;

    if (!SvROK(sv_loop)) {
        croak("Usage: UV::default_loop->active_handles");
    }

    m = mg_find(SvRV(sv_loop), PERL_MAGIC_ext);
    if (!m) {
        croak("invalid UV::loop object");
    }

    uv_loop_t* loop = (uv_loop_t*)m->mg_obj;
    RETVAL = loop->active_handles;
}
OUTPUT:
    RETVAL

MODULE=UV PACKAGE=UV::handle

int
type(uv_handle_t* handle)
CODE:
{
    RETVAL = handle->type;
}
OUTPUT:
    RETVAL