/*
 * Module:  pyhttp.c
 * Author:  Toni Uhlig <matzeton@googlemail.com>
 * Purpose: Python loadable module for http codes/flags
 */

#include "helper.h" /* must be the first include if compiling a python module */

#include <stdio.h>
#include <stdlib.h>

#include "compat.h"
#include "http.h"
#include "xor_strings.h" /* DLLSECTION */


static const char pname[] = "pyhttp";


static PyObject* info(PyObject* self, PyObject* args)
{
    printf("%s: http codes/flags\n", pname);
    Py_RETURN_NONE;
}

#define PYDICT_SET_CMACRO(name, obj) PyDict_SetItemString( dict, name, obj );
#define PYDICT_SETI_CMACRO(mname) { PyObject* pyval = Py_BuildValue("I", mname); if (pyval) { PYDICT_SET_CMACRO( #mname, pyval ); Py_DECREF(pyval); } }
#define PYDICT_SETS_CMACRO(mname) { PyObject* pyval = Py_BuildValue("s", mname); if (pyval) { PYDICT_SET_CMACRO( #mname, pyval ); Py_DECREF(pyval); } }
static PyObject* __http_getCodes(PyObject* self, PyObject* args)
{
    PyObject* dict = PyDict_New();
    PYDICT_SETI_CMACRO(RC_INFO);
    PYDICT_SETI_CMACRO(RC_REGISTER);
    PYDICT_SETI_CMACRO(RC_PING);
    return dict;
}

static PyObject* __http_getCodeSiz(PyObject* self, PyObject* args)
{
    return Py_BuildValue("I", sizeof(rrcode));
}

static PyObject* __http_getFlags(PyObject* self, PyObject* args)
{
    PyObject* dict = PyDict_New();
    PYDICT_SETI_CMACRO(RF_AGAIN);
    PYDICT_SETI_CMACRO(RF_ERROR);
    PYDICT_SETI_CMACRO(RF_OK);

    return dict;
}

static PyObject* __http_getFlagSiz(PyObject* self, PyObject* args)
{
    return Py_BuildValue("I", sizeof(rflags));
}

static PyObject* __http_getConsts(PyObject* self, PyObject* args)
{
    PyObject* dict = PyDict_New();
    PYDICT_SETS_CMACRO(DLLSECTION);
    PYDICT_SETI_CMACRO(SID_LEN);
    PYDICT_SETI_CMACRO(SID_ZEROES0);
    PYDICT_SETI_CMACRO(SID_ZEROES1);
    PYDICT_SETI_CMACRO(MARKER_SIZ);
    PYDICT_SETI_CMACRO(RND_LEN);
    PYDICT_SETI_CMACRO(AESKEY_SIZ);

    return dict;
}

static PyObject* __http_parseResponse(PyObject* self, PyObject* args)
{
    PyObject* ctxRecvBuffer = NULL;
    PyObject* ctxStartMarker = NULL;
    Py_buffer recvBuffer = {0}, startMarker = {0};
    PyObject* rList = Py_BuildValue("[]");

    if (! PyArg_ParseTuple(args, "O|O:parseResponse", &ctxRecvBuffer, &ctxStartMarker) ||
            ! ctxRecvBuffer || ! ctxStartMarker) {
        PyErr_SetString(PyExc_TypeError, "Invalid arguments");
        PyErr_Print();
        return NULL;
    }

    if (PyObject_GetBuffer(ctxRecvBuffer, &recvBuffer, PyBUF_SIMPLE) < 0 ||
            PyObject_GetBuffer(ctxStartMarker, &startMarker, PyBUF_SIMPLE) < 0) {
        PyErr_SetString(PyExc_TypeError, "Argument types are not buffer objects");
        PyErr_Print();
        goto finalize;
    }
    if (recvBuffer.len <= 0) {
        PyErr_Format(PyExc_RuntimeError, "Invalid buffer length: %u", (unsigned)recvBuffer.len);
        PyErr_Print();
        goto finalize;
    }
    if (startMarker.len != MARKER_SIZ) {
        PyErr_Format(PyExc_TypeError, "Marker size is not exactly %u bytes: %u bytes", MARKER_SIZ, (unsigned)startMarker.len);
        PyErr_Print();
        goto finalize;
    }

    off_t bufOff = 0;
    http_resp* hResp = NULL;
    while (parseResponse(recvBuffer.buf, recvBuffer.len, &hResp, &bufOff, startMarker.buf) == RSP_OK &&
            hResp) {
        PyObject* tuple = Py_BuildValue("(s#BHIs#)", hResp->startMarker, MARKER_SIZ,
            hResp->respFlags, hResp->respCode, hResp->pkgsiz, &hResp->pkgbuf[0], hResp->pkgsiz);
        PyList_Append(rList, tuple);
        Py_DECREF(tuple);
    }

finalize:
    if (recvBuffer.buf != NULL)
        PyBuffer_Release(&recvBuffer);
    if (startMarker.buf != NULL)
        PyBuffer_Release(&startMarker);
    return rList;
}

static PyObject* __http_addRequest(PyObject* self, PyObject* args)
{
    struct http_resp* hResp = NULL;
    PyObject* ctxBuf;
    PyObject* ctxResp;
    Py_buffer pkgBuf = {0}, httpResp = {0};
    PyObject* retBuf = NULL;

    if (! PyArg_ParseTuple(args, "O|O:addRequest", &ctxBuf, &ctxResp) ||
            ! ctxBuf || ! ctxResp) {
        PyErr_SetString(PyExc_TypeError, "Invalid arguments");
        return NULL;
    }

    if (PyObject_GetBuffer(ctxBuf, &pkgBuf, PyBUF_SIMPLE) < 0 ||
            PyObject_GetBuffer(ctxResp, &httpResp, PyBUF_SIMPLE) < 0) {
        PyErr_SetString(PyExc_TypeError, "Argument types are not buffer objects");
        PyErr_Print();
        goto finalize;
    }

    hResp = (struct http_resp*)httpResp.buf;
    if (httpResp.len != sizeof(struct http_resp) + hResp->pkgsiz) {
        PyErr_Format(PyExc_RuntimeError, "Invalid http_resp size: %lu (required: %lu + %u)", httpResp.len, sizeof(struct http_resp), hResp->pkgsiz);
        PyErr_Print();
        goto finalize;
    }

    rrsize send_siz = pkgBuf.len;
    rrbuff send_buf = COMPAT(calloc)(send_siz, sizeof(*send_buf));
    if (! send_buf)
        goto finalize;
    COMPAT(memcpy)(send_buf, pkgBuf.buf, send_siz);
    if (addRequest(&send_buf, &send_siz, hResp) == RSP_OK)
        retBuf = PyByteArray_FromStringAndSize((const char*)send_buf, send_siz);
    COMPAT(free)(send_buf);
finalize:
    if (pkgBuf.buf != NULL)
        PyBuffer_Release(&pkgBuf);
    if (httpResp.buf != NULL)
        PyBuffer_Release(&httpResp);
    if (retBuf)
        return retBuf;
    else
        Py_RETURN_NONE;
}


/* define module methods */
static PyMethodDef pycryptMethods[] = {
    {"info",           info,                    METH_NOARGS,  "print module info"},
    {"getCodes",       __http_getCodes,         METH_NOARGS,  "get http request/response codes"},
    {"getCodeSiz",     __http_getCodeSiz,       METH_NOARGS,  "get code size"},
    {"getFlags",       __http_getFlags,         METH_NOARGS,  "get http response flags"},
    {"getFlagSiz",     __http_getFlagSiz,       METH_NOARGS,  "get flag size"},
    {"getConsts",      __http_getConsts,        METH_NOARGS,  "get const data/macros"},
    {"parseResponse",  __http_parseResponse,    METH_VARARGS, "buf,startMarker -> parse http request/response"},
    {"addRequest",     __http_addRequest,       METH_VARARGS, "buf,struct http_resp -> add a http request to an pkgbuffer"},
    {NULL, NULL, 0, NULL}
};

/* module initialization */
PyMODINIT_FUNC
initpyhttp(void)
{
    printf("ENABLED %s\n", pname);
    (void) Py_InitModule(pname, pycryptMethods);
}