#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include <sys/types.h>
#include <sys/socket.h>

typedef PerlIO* InOutStream;
typedef int SysRet;

#ifndef PerlIO
#define PerlIO_fileno(f) fileno(f)
#endif

static Size_t aligned_cmsghdr_sz = 0;

struct Socket__MsgHdr {
    struct msghdr m;
    struct iovec io;
};

static void
smhobj_2msghdr(SV *obj, struct Socket__MsgHdr *mh)
{
    HV*     hash;
    SV **   svp;
    STRLEN  dlen;

    if (!obj || !sv_isa(obj, "Socket::MsgHdr"))
        croak("parameter not of type Socket::MsgHdr");

    hash = (HV*) SvRV(obj);

    Zero(mh, 1, struct Socket__MsgHdr);

    mh->m.msg_iov    = &mh->io;
    mh->m.msg_iovlen = 1;

    /* Set any values supplied by the user, but translate
     * empty strings to explicit NULLs (for FreeBSD's sake).
     */
    if ((svp = hv_fetch(hash, "name", 4, FALSE)) && SvOK(*svp)) {
        mh->m.msg_name    = SvPV_force(*svp, dlen);
        mh->m.msg_namelen = dlen;
        if (0 == dlen) mh->m.msg_name = NULL;
    }

    if ((svp = hv_fetch(hash, "buf", 3, FALSE)) && SvOK(*svp)) {
        mh->io.iov_base = SvPV_force(*svp, dlen);
        mh->io.iov_len  = dlen;
        if (0 == dlen) mh->io.iov_base = NULL;
    }

    if ((svp = hv_fetch(hash, "control", 7, FALSE)) && SvOK(*svp)) {
        mh->m.msg_control    = SvPV_force(*svp, dlen);
        mh->m.msg_controllen = dlen;
        if (0 == dlen) mh->m.msg_control = NULL;
    }

    if ((svp = hv_fetch(hash, "flags", 5, FALSE)) && SvOK(*svp)) {
        mh->m.msg_flags    = SvIV(*svp);
    }
}

MODULE = Socket::MsgHdr    PACKAGE = Socket::MsgHdr   PREFIX = smh_

SV *
smh_pack_cmsghdr(...)
    PROTOTYPE: $$$;@
    PREINIT:
        STRLEN len;
        STRLEN space;
        I32 i;
        struct cmsghdr *cm;
    CODE:
        space = 0;
        for (i=0; i<items; i+=3) {
            len = sv_len(ST(i+2));
            space += CMSG_SPACE(len);
        }
        RETVAL = newSV( space );
        SvPOK_on(RETVAL);
        SvCUR_set(RETVAL, space);

        cm = (struct cmsghdr *)SvPVX(RETVAL);

        for (i=0; i<items; i+=3) {
            len = sv_len(ST(i+2));
            cm->cmsg_len = CMSG_LEN(len);
            cm->cmsg_level = SvIV( ST(i) );
            cm->cmsg_type = SvIV( ST(i+1) );
            Copy(SvPVX(ST(i+2)), CMSG_DATA(cm), len, U8);
            cm = (struct cmsghdr *)((U8 *)cm + CMSG_SPACE( len ));
        }
    OUTPUT:
    RETVAL

void
smh_unpack_cmsghdr(cmsv)
    SV*     cmsv;
    INIT:
    struct msghdr dummy;
    struct cmsghdr *cm;
    STRLEN  len;
    PPCODE:
    dummy.msg_control    = (struct cmsghdr *) SvPV(cmsv, len);
    dummy.msg_controllen = len;

    if (!len)
        XSRETURN_EMPTY;

    cm = CMSG_FIRSTHDR(&dummy);
    for (; cm; cm = CMSG_NXTHDR(&dummy, cm)) {
       XPUSHs(sv_2mortal(newSViv(cm->cmsg_level)));
       XPUSHs(sv_2mortal(newSViv(cm->cmsg_type)));
       XPUSHs(sv_2mortal(newSVpvn(CMSG_DATA(cm),
                                 (cm->cmsg_len - aligned_cmsghdr_sz))));
    }

SysRet
smh_sendmsg(s, msg_hdr, flags = 0)
    InOutStream s;
    SV * msg_hdr;
    int flags;

    PROTOTYPE: $$;$
    PREINIT:
    struct Socket__MsgHdr mh;
    CODE:
    smhobj_2msghdr(msg_hdr, &mh);
    RETVAL = sendmsg(PerlIO_fileno(s), &mh.m, flags);
    OUTPUT:
    RETVAL

SysRet
smh_recvmsg(s, msg_hdr, flags = 0)
    InOutStream s;
    SV * msg_hdr;
    int flags;

    PROTOTYPE: $$;$
    PREINIT:
    struct Socket__MsgHdr mh;

    CODE:
    smhobj_2msghdr(msg_hdr, &mh);
    if ((RETVAL = recvmsg(PerlIO_fileno(s), &mh.m, flags)) >= 0) {
        SV**    svp;
        HV*     hsh;

        hsh = (HV*) SvRV(msg_hdr);

        if ((svp = hv_fetch(hsh, "name", 4, FALSE)))
            SvCUR_set(*svp, mh.m.msg_namelen);
        if ((svp = hv_fetch(hsh, "buf", 3, FALSE)))
            SvCUR_set(*svp, RETVAL);
        if ((svp = hv_fetch(hsh, "control", 7, FALSE)))
            SvCUR_set(*svp, mh.m.msg_controllen);
    }
    OUTPUT:
    RETVAL

BOOT:
    aligned_cmsghdr_sz = CMSG_LEN(0);