2 * zfec -- fast forward error correction library with Python interface
6 #include <structmember.h>
10 #if (PY_VERSION_HEX < 0x02050000)
11 typedef int Py_ssize_t;
18 static PyObject *py_fec_error;
20 static char fec__doc__[] = "\
21 FEC - Forward Error Correction \n\
24 static char Encoder__doc__[] = "\
25 Hold static encoder state (an in-memory table for matrix multiplication), and k and m parameters, and provide {encode()} method.\n\n\
26 @param k: the number of packets required for reconstruction \n\
27 @param m: the number of packets generated \n\
42 Encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwdict) {
45 self = (Encoder*)type->tp_alloc(type, 0);
49 self->fec_matrix = NULL;
52 return (PyObject *)self;
56 Encoder_init(Encoder *self, PyObject *args, PyObject *kwdict) {
57 static char *kwlist[] = {
63 if (!PyArg_ParseTupleAndKeywords(args, kwdict, "ii:Encoder.__init__", kwlist, &ink, &inm))
67 PyErr_Format(py_fec_error, "Precondition violation: first argument is required to be greater than or equal to 1, but it was %d", ink);
71 PyErr_Format(py_fec_error, "Precondition violation: second argument is required to be greater than or equal to 1, but it was %d", inm);
75 PyErr_Format(py_fec_error, "Precondition violation: second argument is required to be less than or equal to 256, but it was %d", inm);
79 PyErr_Format(py_fec_error, "Precondition violation: first argument is required to be less than or equal to the second argument, but they were %d and %d respectively", ink, inm);
82 self->kk = (unsigned short)ink;
83 self->mm = (unsigned short)inm;
84 self->fec_matrix = fec_new(self->kk, self->mm);
89 static char Encoder_encode__doc__[] = "\
90 Encode data into m packets.\n\
92 @param inblocks: a sequence of k buffers of data to encode -- these are the k primary blocks, i.e. the input data split into k pieces (for best performance, make it a tuple instead of a list); All blocks are required to be the same length.\n\
93 @param desired_blocks_nums optional sequence of blocknums indicating which blocks to produce and return; If None, all m blocks will be returned (in order). (For best performance, make it a tuple instead of a list.)\n\
94 @returns: a list of buffers containing the requested blocks; Note that if any of the input blocks were 'primary blocks', i.e. their blocknum was < k, then the result sequence will contain a Python reference to the same Python object as was passed in. As long as the Python object in question is immutable (i.e. a string) then you don't have to think about this detail, but if it is mutable (i.e. an array), then you have to be aware that if you subsequently mutate the contents of that object then that will also change the contents of the sequence that was returned from this call to encode().\n\
98 Encoder_encode(Encoder *self, PyObject *args) {
100 PyObject* desired_blocks_nums = NULL; /* The blocknums of the blocks that should be returned. */
101 PyObject* result = NULL;
103 gf** check_blocks_produced = (gf**)alloca((self->mm - self->kk) * sizeof(PyObject*)); /* This is an upper bound -- we will actually use only num_check_blocks_produced of these elements (see below). */
104 PyObject** pystrs_produced = (PyObject**)alloca((self->mm - self->kk) * sizeof(PyObject*)); /* This is an upper bound -- we will actually use only num_check_blocks_produced of these elements (see below). */
105 unsigned num_check_blocks_produced = 0; /* The first num_check_blocks_produced elements of the check_blocks_produced array and of the pystrs_produced array will be used. */
106 const gf** incblocks = (const gf**)alloca(self->kk * sizeof(const gf*));
107 unsigned num_desired_blocks;
108 PyObject* fast_desired_blocks_nums = NULL;
109 PyObject** fast_desired_blocks_nums_items;
110 unsigned* c_desired_blocks_nums = (unsigned*)alloca(self->mm * sizeof(unsigned));
111 unsigned* c_desired_checkblocks_ids = (unsigned*)alloca((self->mm - self->kk) * sizeof(unsigned));
113 PyObject* fastinblocks = NULL;
114 PyObject** fastinblocksitems;
115 Py_ssize_t sz, oldsz = 0;
116 unsigned char check_block_index = 0; /* index into the check_blocks_produced and (parallel) pystrs_produced arrays */
118 if (!PyArg_ParseTuple(args, "O|O:Encoder.encode", &inblocks, &desired_blocks_nums))
121 for (i=0; i<self->mm - self->kk; i++)
122 pystrs_produced[i] = NULL;
123 if (desired_blocks_nums) {
124 fast_desired_blocks_nums = PySequence_Fast(desired_blocks_nums, "Second argument (optional) was not a sequence.");
125 if (!fast_desired_blocks_nums)
127 num_desired_blocks = PySequence_Fast_GET_SIZE(fast_desired_blocks_nums);
128 fast_desired_blocks_nums_items = PySequence_Fast_ITEMS(fast_desired_blocks_nums);
129 for (i=0; i<num_desired_blocks; i++) {
130 if (!PyInt_Check(fast_desired_blocks_nums_items[i])) {
131 PyErr_Format(py_fec_error, "Precondition violation: second argument is required to contain int.");
134 c_desired_blocks_nums[i] = PyInt_AsLong(fast_desired_blocks_nums_items[i]);
135 if (c_desired_blocks_nums[i] >= self->kk)
136 num_check_blocks_produced++;
139 num_desired_blocks = self->mm;
140 for (i=0; i<num_desired_blocks; i++)
141 c_desired_blocks_nums[i] = i;
142 num_check_blocks_produced = self->mm - self->kk;
145 fastinblocks = PySequence_Fast(inblocks, "First argument was not a sequence.");
149 if (PySequence_Fast_GET_SIZE(fastinblocks) != self->kk) {
150 PyErr_Format(py_fec_error, "Precondition violation: Wrong length -- first argument (the sequence of input blocks) is required to contain exactly k blocks. len(first): %zu, k: %d", PySequence_Fast_GET_SIZE(fastinblocks), self->kk);
154 /* Construct a C array of gf*'s of the input data. */
155 fastinblocksitems = PySequence_Fast_ITEMS(fastinblocks);
156 if (!fastinblocksitems)
159 for (i=0; i<self->kk; i++) {
160 if (!PyObject_CheckReadBuffer(fastinblocksitems[i])) {
161 PyErr_Format(py_fec_error, "Precondition violation: %u'th item is required to offer the single-segment read character buffer protocol, but it does not.", i);
164 if (PyObject_AsReadBuffer(fastinblocksitems[i], (const void**)&(incblocks[i]), &sz))
166 if (oldsz != 0 && oldsz != sz) {
167 PyErr_Format(py_fec_error, "Precondition violation: Input blocks are required to be all the same length. length of one block was: %zu, length of another block was: %zu", oldsz, sz);
173 /* Allocate space for all of the check blocks. */
175 for (i=0; i<num_desired_blocks; i++) {
176 if (c_desired_blocks_nums[i] >= self->kk) {
177 c_desired_checkblocks_ids[check_block_index] = c_desired_blocks_nums[i];
178 pystrs_produced[check_block_index] = PyString_FromStringAndSize(NULL, sz);
179 if (pystrs_produced[check_block_index] == NULL)
181 check_blocks_produced[check_block_index] = (gf*)PyString_AsString(pystrs_produced[check_block_index]);
182 if (check_blocks_produced[check_block_index] == NULL)
187 assert (check_block_index == num_check_blocks_produced);
189 /* Encode any check blocks that are needed. */
190 fec_encode(self->fec_matrix, incblocks, check_blocks_produced, c_desired_checkblocks_ids, num_check_blocks_produced, sz);
192 /* Wrap all requested blocks up into a Python list of Python strings. */
193 result = PyList_New(num_desired_blocks);
196 check_block_index = 0;
197 for (i=0; i<num_desired_blocks; i++) {
198 if (c_desired_blocks_nums[i] < self->kk) {
199 Py_INCREF(fastinblocksitems[c_desired_blocks_nums[i]]);
200 if (PyList_SetItem(result, i, fastinblocksitems[c_desired_blocks_nums[i]]) == -1) {
201 Py_DECREF(fastinblocksitems[c_desired_blocks_nums[i]]);
205 if (PyList_SetItem(result, i, pystrs_produced[check_block_index]) == -1)
207 pystrs_produced[check_block_index] = NULL;
214 for (i=0; i<num_check_blocks_produced; i++)
215 Py_XDECREF(pystrs_produced[i]);
216 Py_XDECREF(result); result = NULL;
218 Py_XDECREF(fastinblocks); fastinblocks=NULL;
219 Py_XDECREF(fast_desired_blocks_nums); fast_desired_blocks_nums=NULL;
224 Encoder_dealloc(Encoder * self) {
225 if (self->fec_matrix)
226 fec_free(self->fec_matrix);
227 self->ob_type->tp_free((PyObject*)self);
230 static PyMethodDef Encoder_methods[] = {
231 {"encode", (PyCFunction)Encoder_encode, METH_VARARGS, Encoder_encode__doc__},
235 static PyMemberDef Encoder_members[] = {
236 {"k", T_SHORT, offsetof(Encoder, kk), READONLY, "k"},
237 {"m", T_SHORT, offsetof(Encoder, mm), READONLY, "m"},
238 {NULL} /* Sentinel */
241 static PyTypeObject Encoder_type = {
242 PyObject_HEAD_INIT(NULL)
244 "_fec.Encoder", /*tp_name*/
245 sizeof(Encoder), /*tp_basicsize*/
247 (destructor)Encoder_dealloc, /*tp_dealloc*/
254 0, /*tp_as_sequence*/
262 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
263 Encoder__doc__, /* tp_doc */
266 0, /* tp_richcompare */
267 0, /* tp_weaklistoffset */
270 Encoder_methods, /* tp_methods */
271 Encoder_members, /* tp_members */
275 0, /* tp_descr_get */
276 0, /* tp_descr_set */
277 0, /* tp_dictoffset */
278 (initproc)Encoder_init, /* tp_init */
280 Encoder_new, /* tp_new */
283 static char Decoder__doc__[] = "\
284 Hold static decoder state (an in-memory table for matrix multiplication), and k and m parameters, and provide {decode()} method.\n\n\
285 @param k: the number of packets required for reconstruction \n\
286 @param m: the number of packets generated \n\
301 Decoder_new(PyTypeObject *type, PyObject *args, PyObject *kwdict) {
304 self = (Decoder*)type->tp_alloc(type, 0);
308 self->fec_matrix = NULL;
311 return (PyObject *)self;
315 Decoder_init(Encoder *self, PyObject *args, PyObject *kwdict) {
316 static char *kwlist[] = {
323 if (!PyArg_ParseTupleAndKeywords(args, kwdict, "ii:Decoder.__init__", kwlist, &ink, &inm))
327 PyErr_Format(py_fec_error, "Precondition violation: first argument is required to be greater than or equal to 1, but it was %d", ink);
331 PyErr_Format(py_fec_error, "Precondition violation: second argument is required to be greater than or equal to 1, but it was %d", inm);
335 PyErr_Format(py_fec_error, "Precondition violation: second argument is required to be less than or equal to 256, but it was %d", inm);
339 PyErr_Format(py_fec_error, "Precondition violation: first argument is required to be less than or equal to the second argument, but they were %d and %d respectively", ink, inm);
342 self->kk = (unsigned short)ink;
343 self->mm = (unsigned short)inm;
344 self->fec_matrix = fec_new(self->kk, self->mm);
349 #define SWAP(a,b,t) {t tmp; tmp=a; a=b; b=tmp;}
351 static char Decoder_decode__doc__[] = "\
352 Decode a list blocks into a list of segments.\n\
353 @param blocks a sequence of buffers containing block data (for best performance, make it a tuple instead of a list)\n\
354 @param blocknums a sequence of integers of the blocknum for each block in blocks (for best performance, make it a tuple instead of a list)\n\
356 @return a list of strings containing the segment data (i.e. ''.join(retval) yields a string containing the decoded data)\n\
360 Decoder_decode(Decoder *self, PyObject *args) {
361 PyObject*restrict blocks;
362 PyObject*restrict blocknums;
363 PyObject* result = NULL;
365 const gf**restrict cblocks = (const gf**restrict)alloca(self->kk * sizeof(const gf*));
366 unsigned* cblocknums = (unsigned*)alloca(self->kk * sizeof(unsigned));
367 gf**restrict recoveredcstrs = (gf**)alloca(self->kk * sizeof(gf*)); /* self->kk is actually an upper bound -- we probably won't need all of this space. */
368 PyObject**restrict recoveredpystrs = (PyObject**restrict)alloca(self->kk * sizeof(PyObject*)); /* self->kk is actually an upper bound -- we probably won't need all of this space. */
370 PyObject*restrict fastblocknums = NULL;
371 PyObject*restrict fastblocks;
372 unsigned needtorecover=0;
373 PyObject** fastblocksitems;
374 PyObject** fastblocknumsitems;
375 Py_ssize_t sz, oldsz = 0;
377 unsigned nextrecoveredix=0;
379 if (!PyArg_ParseTuple(args, "OO:Decoder.decode", &blocks, &blocknums))
382 for (i=0; i<self->kk; i++)
383 recoveredpystrs[i] = NULL;
384 fastblocks = PySequence_Fast(blocks, "First argument was not a sequence.");
387 fastblocknums = PySequence_Fast(blocknums, "Second argument was not a sequence.");
391 if (PySequence_Fast_GET_SIZE(fastblocks) != self->kk) {
392 PyErr_Format(py_fec_error, "Precondition violation: Wrong length -- first argument is required to contain exactly k blocks. len(first): %zu, k: %d", PySequence_Fast_GET_SIZE(fastblocks), self->kk);
395 if (PySequence_Fast_GET_SIZE(fastblocknums) != self->kk) {
396 PyErr_Format(py_fec_error, "Precondition violation: Wrong length -- blocknums is required to contain exactly k blocks. len(blocknums): %zu, k: %d", PySequence_Fast_GET_SIZE(fastblocknums), self->kk);
400 /* Construct a C array of gf*'s of the data and another of C ints of the blocknums. */
401 fastblocknumsitems = PySequence_Fast_ITEMS(fastblocknums);
402 if (!fastblocknumsitems)
404 fastblocksitems = PySequence_Fast_ITEMS(fastblocks);
405 if (!fastblocksitems)
408 for (i=0; i<self->kk; i++) {
409 if (!PyInt_Check(fastblocknumsitems[i])) {
410 PyErr_Format(py_fec_error, "Precondition violation: second argument is required to contain int.");
413 tmpl = PyInt_AsLong(fastblocknumsitems[i]);
414 if (tmpl < 0 || tmpl > 255) {
415 PyErr_Format(py_fec_error, "Precondition violation: block nums can't be less than zero or greater than 255. %ld\n", tmpl);
418 cblocknums[i] = (unsigned)tmpl;
419 if (cblocknums[i] >= self->kk)
422 if (!PyObject_CheckReadBuffer(fastblocksitems[i])) {
423 PyErr_Format(py_fec_error, "Precondition violation: %u'th item is required to offer the single-segment read character buffer protocol, but it does not.\n", i);
426 if (PyObject_AsReadBuffer(fastblocksitems[i], (const void**)&(cblocks[i]), &sz))
428 if (oldsz != 0 && oldsz != sz) {
429 PyErr_Format(py_fec_error, "Precondition violation: Input blocks are required to be all the same length. length of one block was: %zu, length of another block was: %zu\n", oldsz, sz);
435 /* Move src packets into position. At the end of this loop we want the i'th
436 element of the arrays to be the block with block number i, if that block
437 is among our inputs. */
438 for (i=0; i<self->kk;) {
439 if (cblocknums[i] >= self->kk || cblocknums[i] == i)
442 /* put pkt in the right position. */
443 unsigned c = cblocknums[i];
445 SWAP (cblocknums[i], cblocknums[c], int);
446 SWAP (cblocks[i], cblocks[c], const gf*);
447 SWAP (fastblocksitems[i], fastblocksitems[c], PyObject*);
451 /* Allocate space for all of the recovered blocks. */
452 for (i=0; i<needtorecover; i++) {
453 recoveredpystrs[i] = PyString_FromStringAndSize(NULL, sz);
454 if (recoveredpystrs[i] == NULL)
456 recoveredcstrs[i] = (gf*)PyString_AsString(recoveredpystrs[i]);
457 if (recoveredcstrs[i] == NULL)
461 /* Decode any recovered blocks that are needed. */
462 fec_decode(self->fec_matrix, cblocks, recoveredcstrs, cblocknums, sz);
464 /* Wrap up both original primary blocks and decoded blocks into a Python list of Python strings. */
465 result = PyList_New(self->kk);
468 for (i=0; i<self->kk; i++) {
469 if (cblocknums[i] == i) {
470 /* Original primary block. */
471 Py_INCREF(fastblocksitems[i]);
472 if (PyList_SetItem(result, i, fastblocksitems[i]) == -1) {
473 Py_DECREF(fastblocksitems[i]);
477 /* Recovered block. */
478 if (PyList_SetItem(result, i, recoveredpystrs[nextrecoveredix]) == -1)
480 recoveredpystrs[nextrecoveredix] = NULL;
487 for (i=0; i<self->kk; i++)
488 Py_XDECREF(recoveredpystrs[i]);
489 Py_XDECREF(result); result = NULL;
491 Py_XDECREF(fastblocks); fastblocks=NULL;
492 Py_XDECREF(fastblocknums); fastblocknums=NULL;
497 Decoder_dealloc(Decoder * self) {
498 if (self->fec_matrix)
499 fec_free(self->fec_matrix);
500 self->ob_type->tp_free((PyObject*)self);
503 static PyMethodDef Decoder_methods[] = {
504 {"decode", (PyCFunction)Decoder_decode, METH_VARARGS, Decoder_decode__doc__},
508 static PyMemberDef Decoder_members[] = {
509 {"k", T_SHORT, offsetof(Encoder, kk), READONLY, "k"},
510 {"m", T_SHORT, offsetof(Encoder, mm), READONLY, "m"},
511 {NULL} /* Sentinel */
514 static PyTypeObject Decoder_type = {
515 PyObject_HEAD_INIT(NULL)
517 "_fec.Decoder", /*tp_name*/
518 sizeof(Decoder), /*tp_basicsize*/
520 (destructor)Decoder_dealloc, /*tp_dealloc*/
527 0, /*tp_as_sequence*/
535 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
536 Decoder__doc__, /* tp_doc */
539 0, /* tp_richcompare */
540 0, /* tp_weaklistoffset */
543 Decoder_methods, /* tp_methods */
544 Decoder_members, /* tp_members */
548 0, /* tp_descr_get */
549 0, /* tp_descr_set */
550 0, /* tp_dictoffset */
551 (initproc)Decoder_init, /* tp_init */
553 Decoder_new, /* tp_new */
558 _hexwrite(unsigned char*s, size_t l) {
560 for (i = 0; i < l; i++)
561 printf("%.2x", s[i]);
566 test_from_agl(PyObject* self, PyObject* args) {
567 unsigned char b0c[8], b1c[8];
568 unsigned char b0[8], b1[8], b2[8], b3[8], b4[8];
570 const unsigned char *blocks[3] = {b0, b1, b2};
571 unsigned char *outblocks[2] = {b3, b4};
572 unsigned block_nums[] = {3, 4};
574 fec_t *const fec = fec_new(3, 5);
576 const unsigned char *inpkts[] = {b3, b4, b2};
577 unsigned char *outpkts[] = {b0, b1};
578 unsigned indexes[] = {3, 4, 2};
584 /*printf("_from_c before encoding:\n");
585 printf("b0: "); _hexwrite(b0, 8); printf(", ");
586 printf("b1: "); _hexwrite(b1, 8); printf(", ");
587 printf("b2: "); _hexwrite(b2, 8); printf(", ");
590 fec_encode(fec, blocks, outblocks, block_nums, 2, 8);
592 /*printf("after encoding:\n");
593 printf("b3: "); _hexwrite(b3, 8); printf(", ");
594 printf("b4: "); _hexwrite(b4, 8); printf(", ");
597 memcpy(b0c, b0, 8); memcpy(b1c, b1, 8);
599 fec_decode(fec, inpkts, outpkts, indexes, 8);
601 /*printf("after decoding:\n");
602 printf("b0: "); _hexwrite(b0, 8); printf(", ");
603 printf("b1: "); _hexwrite(b1, 8);
606 if ((memcmp(b0, b0c,8) == 0) && (memcmp(b1, b1c,8) == 0))
612 static PyMethodDef fec_functions[] = {
613 {"test_from_agl", test_from_agl, METH_NOARGS, NULL},
617 #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
618 #define PyMODINIT_FUNC void
623 PyObject *module_dict;
625 if (PyType_Ready(&Encoder_type) < 0)
627 if (PyType_Ready(&Decoder_type) < 0)
630 module = Py_InitModule3("_fec", fec_functions, fec__doc__);
634 Py_INCREF(&Encoder_type);
635 Py_INCREF(&Decoder_type);
637 PyModule_AddObject(module, "Encoder", (PyObject *)&Encoder_type);
638 PyModule_AddObject(module, "Decoder", (PyObject *)&Decoder_type);
640 module_dict = PyModule_GetDict(module);
641 py_fec_error = PyErr_NewException("_fec.Error", NULL, NULL);
642 PyDict_SetItemString(module_dict, "Error", py_fec_error);
646 * originally inspired by fecmodule.c by the Mnet Project, especially Myers
647 * Carpenter and Hauke Johannknecht