+static PyObject *bup_limited_vint_pack(PyObject *self, PyObject *args)
+{
+ const char *fmt;
+ PyObject *packargs, *result;
+ Py_ssize_t sz, i, bufsz;
+ char *buf, *pos, *end;
+
+ if (!PyArg_ParseTuple(args, "sO", &fmt, &packargs))
+ return NULL;
+
+ if (!PyTuple_Check(packargs))
+ return PyErr_Format(PyExc_Exception, "pack() arg must be tuple");
+
+ sz = PyTuple_GET_SIZE(packargs);
+ if (sz != (Py_ssize_t)strlen(fmt))
+ return PyErr_Format(PyExc_Exception,
+ "number of arguments (%ld) does not match format string (%ld)",
+ (unsigned long)sz, (unsigned long)strlen(fmt));
+
+ if (sz > INT_MAX / 20)
+ return PyErr_Format(PyExc_Exception, "format is far too long");
+
+ // estimate no more than 20 bytes for each on average, the maximum
+ // vint/vuint we can encode is anyway 10 bytes, so this gives us
+ // some headroom for a few strings before we need to realloc ...
+ bufsz = sz * 20;
+ buf = malloc(bufsz);
+ if (!buf)
+ return PyErr_NoMemory();
+
+ pos = buf;
+ end = buf + bufsz;
+ for (i = 0; i < sz; i++) {
+ PyObject *item = PyTuple_GET_ITEM(packargs, i);
+ const char *bytes;
+
+ switch (fmt[i]) {
+ case 'V': {
+ long long val = PyLong_AsLongLong(item);
+ if (val == -1 && PyErr_Occurred())
+ return PyErr_Format(PyExc_OverflowError,
+ "pack arg %d invalid", (int)i);
+ if (end - pos < 10)
+ goto overflow;
+ pos += vuint_encode(val, pos);
+ break;
+ }
+ case 'v': {
+ long long val = PyLong_AsLongLong(item);
+ if (val == -1 && PyErr_Occurred())
+ return PyErr_Format(PyExc_OverflowError,
+ "pack arg %d invalid", (int)i);
+ if (end - pos < 10)
+ goto overflow;
+ pos += vint_encode(val, pos);
+ break;
+ }
+ case 's': {
+ bytes = PyBytes_AsString(item);
+ if (!bytes)
+ goto error;
+ if (end - pos < 10)
+ goto overflow;
+ Py_ssize_t val = PyBytes_GET_SIZE(item);
+ pos += vuint_encode(val, pos);
+ if (end - pos < val)
+ goto overflow;
+ memcpy(pos, bytes, val);
+ pos += val;
+ break;
+ }
+ default:
+ PyErr_Format(PyExc_Exception, "unknown xpack format string item %c",
+ fmt[i]);
+ goto error;
+ }
+ }
+
+ result = PyBytes_FromStringAndSize(buf, pos - buf);
+ free(buf);
+ return result;
+
+ overflow:
+ PyErr_SetString(PyExc_OverflowError, "buffer (potentially) overflowed");
+ error:
+ free(buf);
+ return NULL;
+}
+