| /* |
| * This file is part of the libsigrokdecode project. |
| * |
| * Copyright (C) 2012 Bert Vermeulen <bert@biot.com> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "libsigrokdecode-internal.h" /* First, so we avoid a _POSIX_C_SOURCE warning. */ |
| #include "libsigrokdecode.h" |
| #include "config.h" |
| #include <inttypes.h> |
| |
| typedef struct { |
| PyObject_HEAD |
| } srd_Decoder; |
| |
| /* This is only used for nicer srd_dbg() output. */ |
| static const char *OUTPUT_TYPES[] = { |
| "OUTPUT_ANN", |
| "OUTPUT_PYTHON", |
| "OUTPUT_BINARY", |
| "OUTPUT_META", |
| }; |
| |
| static int convert_annotation(struct srd_decoder_inst *di, PyObject *obj, |
| struct srd_proto_data *pdata) |
| { |
| PyObject *py_tmp; |
| struct srd_pd_output *pdo; |
| struct srd_proto_data_annotation *pda; |
| int ann_class; |
| char **ann_text; |
| |
| /* Should be a list of [annotation class, [string, ...]]. */ |
| if (!PyList_Check(obj) && !PyTuple_Check(obj)) { |
| srd_err("Protocol decoder %s submitted %s instead of list.", |
| di->decoder->name, obj->ob_type->tp_name); |
| return SRD_ERR_PYTHON; |
| } |
| |
| /* Should have 2 elements. */ |
| if (PyList_Size(obj) != 2) { |
| srd_err("Protocol decoder %s submitted annotation list with " |
| "%d elements instead of 2", di->decoder->name, |
| PyList_Size(obj)); |
| return SRD_ERR_PYTHON; |
| } |
| |
| /* |
| * The first element should be an integer matching a previously |
| * registered annotation class. |
| */ |
| py_tmp = PyList_GetItem(obj, 0); |
| if (!PyLong_Check(py_tmp)) { |
| srd_err("Protocol decoder %s submitted annotation list, but " |
| "first element was not an integer.", di->decoder->name); |
| return SRD_ERR_PYTHON; |
| } |
| ann_class = PyLong_AsLong(py_tmp); |
| if (!(pdo = g_slist_nth_data(di->decoder->annotations, ann_class))) { |
| srd_err("Protocol decoder %s submitted data to unregistered " |
| "annotation class %d.", di->decoder->name, ann_class); |
| return SRD_ERR_PYTHON; |
| } |
| |
| /* Second element must be a list. */ |
| py_tmp = PyList_GetItem(obj, 1); |
| if (!PyList_Check(py_tmp)) { |
| srd_err("Protocol decoder %s submitted annotation list, but " |
| "second element was not a list.", di->decoder->name); |
| return SRD_ERR_PYTHON; |
| } |
| if (py_strseq_to_char(py_tmp, &ann_text) != SRD_OK) { |
| srd_err("Protocol decoder %s submitted annotation list, but " |
| "second element was malformed.", di->decoder->name); |
| return SRD_ERR_PYTHON; |
| } |
| |
| if (!(pda = g_try_malloc(sizeof(struct srd_proto_data_annotation)))) |
| return SRD_ERR_MALLOC; |
| pda->ann_class = ann_class; |
| pda->ann_text = ann_text; |
| pdata->data = pda; |
| |
| return SRD_OK; |
| } |
| |
| static int convert_binary(struct srd_decoder_inst *di, PyObject *obj, |
| struct srd_proto_data *pdata) |
| { |
| struct srd_proto_data_binary *pdb; |
| PyObject *py_tmp; |
| Py_ssize_t size; |
| int bin_class; |
| char *class_name, *buf; |
| |
| /* Should be a tuple of (binary class, bytes). */ |
| if (!PyTuple_Check(obj)) { |
| srd_err("Protocol decoder %s submitted SRD_OUTPUT_BINARY with " |
| "%s instead of tuple.", di->decoder->name, |
| obj->ob_type->tp_name); |
| return SRD_ERR_PYTHON; |
| } |
| |
| /* Should have 2 elements. */ |
| if (PyTuple_Size(obj) != 2) { |
| srd_err("Protocol decoder %s submitted SRD_OUTPUT_BINARY tuple " |
| "with %d elements instead of 2", di->decoder->name, |
| PyList_Size(obj)); |
| return SRD_ERR_PYTHON; |
| } |
| |
| /* The first element should be an integer. */ |
| py_tmp = PyTuple_GetItem(obj, 0); |
| if (!PyLong_Check(py_tmp)) { |
| srd_err("Protocol decoder %s submitted SRD_OUTPUT_BINARY tuple, " |
| "but first element was not an integer.", di->decoder->name); |
| return SRD_ERR_PYTHON; |
| } |
| bin_class = PyLong_AsLong(py_tmp); |
| if (!(class_name = g_slist_nth_data(di->decoder->binary, bin_class))) { |
| srd_err("Protocol decoder %s submitted SRD_OUTPUT_BINARY with " |
| "unregistered binary class %d.", di->decoder->name, bin_class); |
| return SRD_ERR_PYTHON; |
| } |
| |
| /* Second element should be bytes. */ |
| py_tmp = PyTuple_GetItem(obj, 1); |
| if (!PyBytes_Check(py_tmp)) { |
| srd_err("Protocol decoder %s submitted SRD_OUTPUT_BINARY tuple, " |
| "but second element was not bytes.", di->decoder->name); |
| return SRD_ERR_PYTHON; |
| } |
| |
| /* Consider an empty set of bytes a bug. */ |
| if (PyBytes_Size(py_tmp) == 0) { |
| srd_err("Protocol decoder %s submitted SRD_OUTPUT_BINARY " |
| "with empty data set.", di->decoder->name); |
| return SRD_ERR_PYTHON; |
| } |
| |
| if (!(pdb = g_try_malloc(sizeof(struct srd_proto_data_binary)))) |
| return SRD_ERR_MALLOC; |
| if (PyBytes_AsStringAndSize(py_tmp, &buf, &size) == -1) |
| return SRD_ERR_PYTHON; |
| pdb->bin_class = bin_class; |
| pdb->size = size; |
| if (!(pdb->data = g_try_malloc(pdb->size))) |
| return SRD_ERR_MALLOC; |
| memcpy((void *)pdb->data, (const void *)buf, pdb->size); |
| pdata->data = pdb; |
| |
| return SRD_OK; |
| } |
| |
| static int convert_meta(struct srd_proto_data *pdata, PyObject *obj) |
| { |
| long long intvalue; |
| double dvalue; |
| |
| if (pdata->pdo->meta_type == G_VARIANT_TYPE_INT64) { |
| if (!PyLong_Check(obj)) { |
| PyErr_Format(PyExc_TypeError, "This output was registered " |
| "as 'int', but '%s' was passed.", obj->ob_type->tp_name); |
| return SRD_ERR_PYTHON; |
| } |
| intvalue = PyLong_AsLongLong(obj); |
| if (PyErr_Occurred()) |
| return SRD_ERR_PYTHON; |
| pdata->data = g_variant_new_int64(intvalue); |
| } else if (pdata->pdo->meta_type == G_VARIANT_TYPE_DOUBLE) { |
| if (!PyFloat_Check(obj)) { |
| PyErr_Format(PyExc_TypeError, "This output was registered " |
| "as 'float', but '%s' was passed.", obj->ob_type->tp_name); |
| return SRD_ERR_PYTHON; |
| } |
| dvalue = PyFloat_AsDouble(obj); |
| if (PyErr_Occurred()) |
| return SRD_ERR_PYTHON; |
| pdata->data = g_variant_new_double(dvalue); |
| } |
| |
| return SRD_OK; |
| } |
| |
| static PyObject *Decoder_put(PyObject *self, PyObject *args) |
| { |
| GSList *l; |
| PyObject *py_data, *py_res; |
| struct srd_decoder_inst *di, *next_di; |
| struct srd_pd_output *pdo; |
| struct srd_proto_data *pdata; |
| uint64_t start_sample, end_sample; |
| int output_id; |
| struct srd_pd_callback *cb; |
| |
| if (!(di = srd_inst_find_by_obj(NULL, self))) { |
| /* Shouldn't happen. */ |
| srd_dbg("put(): self instance not found."); |
| return NULL; |
| } |
| |
| if (!PyArg_ParseTuple(args, "KKiO", &start_sample, &end_sample, |
| &output_id, &py_data)) { |
| /* |
| * This throws an exception, but by returning NULL here we let |
| * Python raise it. This results in a much better trace in |
| * controller.c on the decode() method call. |
| */ |
| return NULL; |
| } |
| |
| if (!(l = g_slist_nth(di->pd_output, output_id))) { |
| srd_err("Protocol decoder %s submitted invalid output ID %d.", |
| di->decoder->name, output_id); |
| return NULL; |
| } |
| pdo = l->data; |
| |
| srd_spew("Instance %s put %" PRIu64 "-%" PRIu64 " %s on oid %d.", |
| di->inst_id, start_sample, end_sample, |
| OUTPUT_TYPES[pdo->output_type], output_id); |
| |
| if (!(pdata = g_try_malloc0(sizeof(struct srd_proto_data)))) { |
| srd_err("Failed to g_malloc() struct srd_proto_data."); |
| return NULL; |
| } |
| pdata->start_sample = start_sample; |
| pdata->end_sample = end_sample; |
| pdata->pdo = pdo; |
| |
| switch (pdo->output_type) { |
| case SRD_OUTPUT_ANN: |
| /* Annotations are only fed to callbacks. */ |
| if ((cb = srd_pd_output_callback_find(di->sess, pdo->output_type))) { |
| /* Convert from PyDict to srd_proto_data_annotation. */ |
| if (convert_annotation(di, py_data, pdata) != SRD_OK) { |
| /* An error was already logged. */ |
| break; |
| } |
| cb->cb(pdata, cb->cb_data); |
| } |
| break; |
| case SRD_OUTPUT_PYTHON: |
| for (l = di->next_di; l; l = l->next) { |
| next_di = l->data; |
| srd_spew("Sending %d-%d to instance %s", |
| start_sample, end_sample, next_di->inst_id); |
| if (!(py_res = PyObject_CallMethod( |
| next_di->py_inst, "decode", "KKO", start_sample, |
| end_sample, py_data))) { |
| srd_exception_catch("Calling %s decode(): ", |
| next_di->inst_id); |
| } |
| Py_XDECREF(py_res); |
| } |
| if ((cb = srd_pd_output_callback_find(di->sess, pdo->output_type))) { |
| /* Frontends aren't really supposed to get Python |
| * callbacks, but it's useful for testing. */ |
| pdata->data = py_data; |
| cb->cb(pdata, cb->cb_data); |
| } |
| break; |
| case SRD_OUTPUT_BINARY: |
| if ((cb = srd_pd_output_callback_find(di->sess, pdo->output_type))) { |
| /* Convert from PyDict to srd_proto_data_binary. */ |
| if (convert_binary(di, py_data, pdata) != SRD_OK) { |
| /* An error was already logged. */ |
| break; |
| } |
| cb->cb(pdata, cb->cb_data); |
| } |
| break; |
| case SRD_OUTPUT_META: |
| if ((cb = srd_pd_output_callback_find(di->sess, pdo->output_type))) { |
| /* Annotations need converting from PyObject. */ |
| if (convert_meta(pdata, py_data) != SRD_OK) { |
| /* An exception was already set up. */ |
| break; |
| } |
| cb->cb(pdata, cb->cb_data); |
| } |
| break; |
| default: |
| srd_err("Protocol decoder %s submitted invalid output type %d.", |
| di->decoder->name, pdo->output_type); |
| break; |
| } |
| |
| g_free(pdata); |
| |
| Py_RETURN_NONE; |
| } |
| |
| static PyObject *Decoder_register(PyObject *self, PyObject *args, |
| PyObject *kwargs) |
| { |
| struct srd_decoder_inst *di; |
| struct srd_pd_output *pdo; |
| PyObject *py_new_output_id; |
| PyTypeObject *meta_type_py; |
| const GVariantType *meta_type_gv; |
| int output_type; |
| char *proto_id, *meta_name, *meta_descr; |
| char *keywords[] = {"output_type", "proto_id", "meta", NULL}; |
| |
| meta_type_py = NULL; |
| meta_type_gv = NULL; |
| meta_name = meta_descr = NULL; |
| |
| if (!(di = srd_inst_find_by_obj(NULL, self))) { |
| PyErr_SetString(PyExc_Exception, "decoder instance not found"); |
| return NULL; |
| } |
| |
| /* Default to instance id, which defaults to class id. */ |
| proto_id = di->inst_id; |
| if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|s(Oss)", keywords, |
| &output_type, &proto_id, |
| &meta_type_py, &meta_name, &meta_descr)) { |
| /* Let Python raise this exception. */ |
| return NULL; |
| } |
| |
| /* Check if the meta value's type is supported. */ |
| if (output_type == SRD_OUTPUT_META) { |
| if (meta_type_py == &PyLong_Type) |
| meta_type_gv = G_VARIANT_TYPE_INT64; |
| else if (meta_type_py == &PyFloat_Type) |
| meta_type_gv = G_VARIANT_TYPE_DOUBLE; |
| else { |
| PyErr_Format(PyExc_TypeError, "Unsupported type '%s'.", |
| meta_type_py->tp_name); |
| return NULL; |
| } |
| } |
| |
| srd_dbg("Instance %s creating new output type %d for %s.", |
| di->inst_id, output_type, proto_id); |
| |
| if (!(pdo = g_try_malloc(sizeof(struct srd_pd_output)))) { |
| PyErr_SetString(PyExc_MemoryError, "struct srd_pd_output"); |
| return NULL; |
| } |
| |
| /* pdo_id is just a simple index, nothing is deleted from this list anyway. */ |
| pdo->pdo_id = g_slist_length(di->pd_output); |
| pdo->output_type = output_type; |
| pdo->di = di; |
| pdo->proto_id = g_strdup(proto_id); |
| |
| if (output_type == SRD_OUTPUT_META) { |
| pdo->meta_type = meta_type_gv; |
| pdo->meta_name = g_strdup(meta_name); |
| pdo->meta_descr = g_strdup(meta_descr); |
| } |
| |
| di->pd_output = g_slist_append(di->pd_output, pdo); |
| py_new_output_id = Py_BuildValue("i", pdo->pdo_id); |
| |
| return py_new_output_id; |
| } |
| |
| static PyMethodDef Decoder_methods[] = { |
| {"put", Decoder_put, METH_VARARGS, |
| "Accepts a dictionary with the following keys: startsample, endsample, data"}, |
| {"register", (PyCFunction)Decoder_register, METH_VARARGS|METH_KEYWORDS, |
| "Register a new output stream"}, |
| {NULL, NULL, 0, NULL} |
| }; |
| |
| /** @cond PRIVATE */ |
| SRD_PRIV PyTypeObject srd_Decoder_type = { |
| PyVarObject_HEAD_INIT(NULL, 0) |
| .tp_name = "sigrokdecode.Decoder", |
| .tp_basicsize = sizeof(srd_Decoder), |
| .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, |
| .tp_doc = "sigrok Decoder base class", |
| .tp_methods = Decoder_methods, |
| }; |
| /** @endcond */ |