/*
parsers.c - efficient content parsing

Copyright 2008 Matt Mackall <[email protected]> and others

This software may be used and distributed according to the terms of
the GNU General Public License, incorporated herein by reference.
*/

#include <Python.h>
#include <ctype.h>
#include <string.h>

static int hexdigit(char c)
{
       if (c >= '0' && c <= '9')
               return c - '0';
       if (c >= 'a' && c <= 'f')
               return c - 'a' + 10;
       if (c >= 'A' && c <= 'F')
               return c - 'A' + 10;

       PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
       return 0;
}

/*
* Turn a hex-encoded string into binary.
*/
static PyObject *unhexlify(const char *str, int len)
{
       PyObject *ret;
       const char *c;
       char *d;

       ret = PyString_FromStringAndSize(NULL, len / 2);
       if (!ret)
               return NULL;

       d = PyString_AS_STRING(ret);
       for (c = str; c < str + len;) {
               int hi = hexdigit(*c++);
               int lo = hexdigit(*c++);
               *d++ = (hi << 4) | lo;
       }

       return ret;
}

/*
* This code assumes that a manifest is stitched together with newline
* ('\n') characters.
*/
static PyObject *parse_manifest(PyObject *self, PyObject *args)
{
       PyObject *mfdict, *fdict;
       char *str, *cur, *start, *zero;
       int len;

       if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
                             &PyDict_Type, &mfdict,
                             &PyDict_Type, &fdict,
                             &str, &len))
               goto quit;

       for (start = cur = str, zero = NULL; cur < str + len; cur++) {
               PyObject *file = NULL, *node = NULL;
               PyObject *flags = NULL;
               int nlen;

               if (!*cur) {
                       zero = cur;
                       continue;
               }
               else if (*cur != '\n')
                       continue;

               if (!zero) {
                       PyErr_SetString(PyExc_ValueError,
                                       "manifest entry has no separator");
                       goto quit;
               }

               file = PyString_FromStringAndSize(start, zero - start);
               if (!file)
                       goto bail;

               nlen = cur - zero - 1;

               node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
               if (!node)
                       goto bail;

               if (nlen > 40) {
                       PyObject *flags;

                       flags = PyString_FromStringAndSize(zero + 41,
                                                          nlen - 40);
                       if (!flags)
                               goto bail;

                       if (PyDict_SetItem(fdict, file, flags) == -1)
                               goto bail;
               }

               if (PyDict_SetItem(mfdict, file, node) == -1)
                       goto bail;

               start = cur + 1;
               zero = NULL;

               Py_XDECREF(flags);
               Py_XDECREF(node);
               Py_XDECREF(file);
               continue;
       bail:
               Py_XDECREF(flags);
               Py_XDECREF(node);
               Py_XDECREF(file);
               goto quit;
       }

       if (len > 0 && *(cur - 1) != '\n') {
               PyErr_SetString(PyExc_ValueError,
                               "manifest contains trailing garbage");
               goto quit;
       }

       Py_INCREF(Py_None);
       return Py_None;
quit:
       return NULL;
}

#ifdef _WIN32
# ifdef _MSC_VER
/* msvc 6.0 has problems */
#  define inline __inline
typedef unsigned long uint32_t;
typedef unsigned __int64 uint64_t;
# else
#  include <stdint.h>
# endif
static uint32_t ntohl(uint32_t x)
{
       return ((x & 0x000000ffUL) << 24) |
              ((x & 0x0000ff00UL) <<  8) |
              ((x & 0x00ff0000UL) >>  8) |
              ((x & 0xff000000UL) >> 24);
}
#else
/* not windows */
# include <sys/types.h>
# if defined __BEOS__ && !defined __HAIKU__
#  include <ByteOrder.h>
# else
#  include <arpa/inet.h>
# endif
# include <inttypes.h>
#endif

static PyObject *parse_dirstate(PyObject *self, PyObject *args)
{
       PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
       PyObject *fname = NULL, *cname = NULL, *entry = NULL;
       char *str, *cur, *end, *cpos;
       int state, mode, size, mtime;
       unsigned int flen;
       int len;
       char decode[16]; /* for alignment */

       if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
                             &PyDict_Type, &dmap,
                             &PyDict_Type, &cmap,
                             &str, &len))
               goto quit;

       /* read parents */
       if (len < 40)
               goto quit;

       parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
       if (!parents)
               goto quit;

       /* read filenames */
       cur = str + 40;
       end = str + len;

       while (cur < end - 17) {
               /* unpack header */
               state = *cur;
               memcpy(decode, cur + 1, 16);
               mode = ntohl(*(uint32_t *)(decode));
               size = ntohl(*(uint32_t *)(decode + 4));
               mtime = ntohl(*(uint32_t *)(decode + 8));
               flen = ntohl(*(uint32_t *)(decode + 12));
               cur += 17;
               if (flen > end - cur) {
                       PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
                       goto quit;
               }

               entry = Py_BuildValue("ciii", state, mode, size, mtime);
               if (!entry)
                       goto quit;
               PyObject_GC_UnTrack(entry); /* don't waste time with this */

               cpos = memchr(cur, 0, flen);
               if (cpos) {
                       fname = PyString_FromStringAndSize(cur, cpos - cur);
                       cname = PyString_FromStringAndSize(cpos + 1,
                                                          flen - (cpos - cur) - 1);
                       if (!fname || !cname ||
                           PyDict_SetItem(cmap, fname, cname) == -1 ||
                           PyDict_SetItem(dmap, fname, entry) == -1)
                               goto quit;
                       Py_DECREF(cname);
               } else {
                       fname = PyString_FromStringAndSize(cur, flen);
                       if (!fname ||
                           PyDict_SetItem(dmap, fname, entry) == -1)
                               goto quit;
               }
               cur += flen;
               Py_DECREF(fname);
               Py_DECREF(entry);
               fname = cname = entry = NULL;
       }

       ret = parents;
       Py_INCREF(ret);
quit:
       Py_XDECREF(fname);
       Py_XDECREF(cname);
       Py_XDECREF(entry);
       Py_XDECREF(parents);
       return ret;
}

const char nullid[20];
const int nullrev = -1;

/* create an index tuple, insert into the nodemap */
static PyObject * _build_idx_entry(PyObject *nodemap, int n, uint64_t offset_flags,
                                  int comp_len, int uncomp_len, int base_rev,
                                  int link_rev, int parent_1, int parent_2,
                                  const char *c_node_id)
{
       int err;
       PyObject *entry, *node_id, *n_obj;

       node_id = PyString_FromStringAndSize(c_node_id, 20);
       n_obj = PyInt_FromLong(n);
       if (!node_id || !n_obj)
               err = -1;
       else
               err = PyDict_SetItem(nodemap, node_id, n_obj);

       Py_XDECREF(n_obj);
       if (err)
               goto error_dealloc;

       entry = Py_BuildValue("LiiiiiiN", offset_flags, comp_len,
                             uncomp_len, base_rev, link_rev,
                             parent_1, parent_2, node_id);
       if (!entry)
               goto error_dealloc;
       PyObject_GC_UnTrack(entry); /* don't waste time with this */

       return entry;

error_dealloc:
       Py_XDECREF(node_id);
       return NULL;
}

/* RevlogNG format (all in big endian, data may be inlined):
*    6 bytes: offset
*    2 bytes: flags
*    4 bytes: compressed length
*    4 bytes: uncompressed length
*    4 bytes: base revision
*    4 bytes: link revision
*    4 bytes: parent 1 revision
*    4 bytes: parent 2 revision
*   32 bytes: nodeid (only 20 bytes used)
*/
static int _parse_index_ng (const char *data, int size, int inlined,
                           PyObject *index, PyObject *nodemap)
{
       PyObject *entry;
       int n = 0, err;
       uint64_t offset_flags;
       int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2;
       const char *c_node_id;
       const char *end = data + size;
       char decode[64]; /* to enforce alignment with inline data */

       while (data < end) {
               unsigned int step;

               memcpy(decode, data, 64);
               offset_flags = ntohl(*((uint32_t *) (decode + 4)));
               if (n == 0) /* mask out version number for the first entry */
                       offset_flags &= 0xFFFF;
               else {
                       uint32_t offset_high =  ntohl(*((uint32_t *) decode));
                       offset_flags |= ((uint64_t) offset_high) << 32;
               }

               comp_len = ntohl(*((uint32_t *) (decode + 8)));
               uncomp_len = ntohl(*((uint32_t *) (decode + 12)));
               base_rev = ntohl(*((uint32_t *) (decode + 16)));
               link_rev = ntohl(*((uint32_t *) (decode + 20)));
               parent_1 = ntohl(*((uint32_t *) (decode + 24)));
               parent_2 = ntohl(*((uint32_t *) (decode + 28)));
               c_node_id = decode + 32;

               entry = _build_idx_entry(nodemap, n, offset_flags,
                                       comp_len, uncomp_len, base_rev,
                                       link_rev, parent_1, parent_2,
                                       c_node_id);
               if (!entry)
                       return 0;

               if (inlined) {
                       err = PyList_Append(index, entry);
                       Py_DECREF(entry);
                       if (err)
                               return 0;
               } else
                       PyList_SET_ITEM(index, n, entry); /* steals reference */

               n++;
               step = 64 + (inlined ? comp_len : 0);
               if (end - data < step)
                       break;
               data += step;
       }
       if (data != end) {
               if (!PyErr_Occurred())
                       PyErr_SetString(PyExc_ValueError, "corrupt index file");
               return 0;
       }

       /* create the nullid/nullrev entry in the nodemap and the
        * magic nullid entry in the index at [-1] */
       entry = _build_idx_entry(nodemap,
                       nullrev, 0, 0, 0, -1, -1, -1, -1, nullid);
       if (!entry)
               return 0;
       if (inlined) {
               err = PyList_Append(index, entry);
               Py_DECREF(entry);
               if (err)
                       return 0;
       } else
               PyList_SET_ITEM(index, n, entry); /* steals reference */

       return 1;
}

/* This function parses a index file and returns a Python tuple of the
* following format: (index, nodemap, cache)
*
* index: a list of tuples containing the RevlogNG records
* nodemap: a dict mapping node ids to indices in the index list
* cache: if data is inlined, a tuple (index_file_content, 0) else None
*/
static PyObject *parse_index(PyObject *self, PyObject *args)
{
       const char *data;
       int size, inlined;
       PyObject *rval = NULL, *index = NULL, *nodemap = NULL, *cache = NULL;
       PyObject *data_obj = NULL, *inlined_obj;

       if (!PyArg_ParseTuple(args, "s#O", &data, &size, &inlined_obj))
               return NULL;
       inlined = inlined_obj && PyObject_IsTrue(inlined_obj);

       /* If no data is inlined, we know the size of the index list in
        * advance: size divided by size of one one revlog record (64 bytes)
        * plus one for the nullid */
       index = inlined ? PyList_New(0) : PyList_New(size / 64 + 1);
       if (!index)
               goto quit;

       nodemap = PyDict_New();
       if (!nodemap)
               goto quit;

       /* set up the cache return value */
       if (inlined) {
               /* Note that the reference to data_obj is only borrowed */
               data_obj = PyTuple_GET_ITEM(args, 0);
               cache = Py_BuildValue("iO", 0, data_obj);
               if (!cache)
                       goto quit;
       } else {
               cache = Py_None;
               Py_INCREF(Py_None);
       }

       /* actually populate the index and the nodemap with data */
       if (!_parse_index_ng (data, size, inlined, index, nodemap))
               goto quit;

       rval = Py_BuildValue("NNN", index, nodemap, cache);
       if (!rval)
               goto quit;
       return rval;

quit:
       Py_XDECREF(index);
       Py_XDECREF(nodemap);
       Py_XDECREF(cache);
       Py_XDECREF(rval);
       return NULL;
}


static char parsers_doc[] = "Efficient content parsing.";

static PyMethodDef methods[] = {
       {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
       {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
       {"parse_index", parse_index, METH_VARARGS, "parse a revlog index\n"},
       {NULL, NULL}
};

PyMODINIT_FUNC initparsers(void)
{
       Py_InitModule3("parsers", methods, parsers_doc);
}