|  | /* | 
|  | * Licensed to the Apache Software Foundation (ASF) under one | 
|  | * or more contributor license agreements. See the NOTICE file | 
|  | * distributed with this work for additional information | 
|  | * regarding copyright ownership. The ASF licenses this file | 
|  | * to you under the Apache License, Version 2.0 (the | 
|  | * "License"); you may not use this file except in compliance | 
|  | * with the License. You may obtain a copy of the License at | 
|  | * | 
|  | *   http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, | 
|  | * software distributed under the License is distributed on an | 
|  | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
|  | * KIND, either express or implied. See the License for the | 
|  | * specific language governing permissions and limitations | 
|  | * under the License. | 
|  | */ | 
|  |  | 
|  | #include <Python.h> | 
|  | #include "cStringIO.h" | 
|  | #include <stdbool.h> | 
|  | #include <stdint.h> | 
|  | #include <netinet/in.h> | 
|  |  | 
|  | /* Fix endianness issues on Solaris */ | 
|  | #if defined (__SVR4) && defined (__sun) | 
|  | #if defined(__i386) && !defined(__i386__) | 
|  | #define __i386__ | 
|  | #endif | 
|  |  | 
|  | #ifndef BIG_ENDIAN | 
|  | #define BIG_ENDIAN (4321) | 
|  | #endif | 
|  | #ifndef LITTLE_ENDIAN | 
|  | #define LITTLE_ENDIAN (1234) | 
|  | #endif | 
|  |  | 
|  | /* I386 is LE, even on Solaris */ | 
|  | #if !defined(BYTE_ORDER) && defined(__i386__) | 
|  | #define BYTE_ORDER LITTLE_ENDIAN | 
|  | #endif | 
|  | #endif | 
|  |  | 
|  | // TODO(dreiss): defval appears to be unused.  Look into removing it. | 
|  | // TODO(dreiss): Make parse_spec_args recursive, and cache the output | 
|  | //               permanently in the object.  (Malloc and orphan.) | 
|  | // TODO(dreiss): Why do we need cStringIO for reading, why not just char*? | 
|  | //               Can cStringIO let us work with a BufferedTransport? | 
|  | // TODO(dreiss): Don't ignore the rv from cwrite (maybe). | 
|  |  | 
|  | /* ====== BEGIN UTILITIES ====== */ | 
|  |  | 
|  | #define INIT_OUTBUF_SIZE 128 | 
|  |  | 
|  | // Stolen out of TProtocol.h. | 
|  | // It would be a huge pain to have both get this from one place. | 
|  | typedef enum TType { | 
|  | T_STOP       = 0, | 
|  | T_VOID       = 1, | 
|  | T_BOOL       = 2, | 
|  | T_BYTE       = 3, | 
|  | T_I08        = 3, | 
|  | T_I16        = 6, | 
|  | T_I32        = 8, | 
|  | T_U64        = 9, | 
|  | T_I64        = 10, | 
|  | T_DOUBLE     = 4, | 
|  | T_STRING     = 11, | 
|  | T_UTF7       = 11, | 
|  | T_STRUCT     = 12, | 
|  | T_MAP        = 13, | 
|  | T_SET        = 14, | 
|  | T_LIST       = 15, | 
|  | T_UTF8       = 16, | 
|  | T_UTF16      = 17 | 
|  | } TType; | 
|  |  | 
|  | #ifndef __BYTE_ORDER | 
|  | # if defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN) | 
|  | #  define __BYTE_ORDER BYTE_ORDER | 
|  | #  define __LITTLE_ENDIAN LITTLE_ENDIAN | 
|  | #  define __BIG_ENDIAN BIG_ENDIAN | 
|  | # else | 
|  | #  error "Cannot determine endianness" | 
|  | # endif | 
|  | #endif | 
|  |  | 
|  | // Same comment as the enum.  Sorry. | 
|  | #if __BYTE_ORDER == __BIG_ENDIAN | 
|  | # define ntohll(n) (n) | 
|  | # define htonll(n) (n) | 
|  | #elif __BYTE_ORDER == __LITTLE_ENDIAN | 
|  | # if defined(__GNUC__) && defined(__GLIBC__) | 
|  | #  include <byteswap.h> | 
|  | #  define ntohll(n) bswap_64(n) | 
|  | #  define htonll(n) bswap_64(n) | 
|  | # else /* GNUC & GLIBC */ | 
|  | #  define ntohll(n) ( (((unsigned long long)ntohl(n)) << 32) + ntohl(n >> 32) ) | 
|  | #  define htonll(n) ( (((unsigned long long)htonl(n)) << 32) + htonl(n >> 32) ) | 
|  | # endif /* GNUC & GLIBC */ | 
|  | #else /* __BYTE_ORDER */ | 
|  | # error "Can't define htonll or ntohll!" | 
|  | #endif | 
|  |  | 
|  | // Doing a benchmark shows that interning actually makes a difference, amazingly. | 
|  | #define INTERN_STRING(value) _intern_ ## value | 
|  |  | 
|  | #define INT_CONV_ERROR_OCCURRED(v) ( ((v) == -1) && PyErr_Occurred() ) | 
|  | #define CHECK_RANGE(v, min, max) ( ((v) <= (max)) && ((v) >= (min)) ) | 
|  |  | 
|  | // Py_ssize_t was not defined before Python 2.5 | 
|  | #if (PY_VERSION_HEX < 0x02050000) | 
|  | typedef int Py_ssize_t; | 
|  | #endif | 
|  |  | 
|  | /** | 
|  | * A cache of the spec_args for a set or list, | 
|  | * so we don't have to keep calling PyTuple_GET_ITEM. | 
|  | */ | 
|  | typedef struct { | 
|  | TType element_type; | 
|  | PyObject* typeargs; | 
|  | } SetListTypeArgs; | 
|  |  | 
|  | /** | 
|  | * A cache of the spec_args for a map, | 
|  | * so we don't have to keep calling PyTuple_GET_ITEM. | 
|  | */ | 
|  | typedef struct { | 
|  | TType ktag; | 
|  | TType vtag; | 
|  | PyObject* ktypeargs; | 
|  | PyObject* vtypeargs; | 
|  | } MapTypeArgs; | 
|  |  | 
|  | /** | 
|  | * A cache of the spec_args for a struct, | 
|  | * so we don't have to keep calling PyTuple_GET_ITEM. | 
|  | */ | 
|  | typedef struct { | 
|  | PyObject* klass; | 
|  | PyObject* spec; | 
|  | } StructTypeArgs; | 
|  |  | 
|  | /** | 
|  | * A cache of the item spec from a struct specification, | 
|  | * so we don't have to keep calling PyTuple_GET_ITEM. | 
|  | */ | 
|  | typedef struct { | 
|  | int tag; | 
|  | TType type; | 
|  | PyObject* attrname; | 
|  | PyObject* typeargs; | 
|  | PyObject* defval; | 
|  | } StructItemSpec; | 
|  |  | 
|  | /** | 
|  | * A cache of the two key attributes of a CReadableTransport, | 
|  | * so we don't have to keep calling PyObject_GetAttr. | 
|  | */ | 
|  | typedef struct { | 
|  | PyObject* stringiobuf; | 
|  | PyObject* refill_callable; | 
|  | } DecodeBuffer; | 
|  |  | 
|  | /** Pointer to interned string to speed up attribute lookup. */ | 
|  | static PyObject* INTERN_STRING(cstringio_buf); | 
|  | /** Pointer to interned string to speed up attribute lookup. */ | 
|  | static PyObject* INTERN_STRING(cstringio_refill); | 
|  |  | 
|  | static inline bool | 
|  | check_ssize_t_32(Py_ssize_t len) { | 
|  | // error from getting the int | 
|  | if (INT_CONV_ERROR_OCCURRED(len)) { | 
|  | return false; | 
|  | } | 
|  | if (!CHECK_RANGE(len, 0, INT32_MAX)) { | 
|  | PyErr_SetString(PyExc_OverflowError, "string size out of range"); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static inline bool | 
|  | parse_pyint(PyObject* o, int32_t* ret, int32_t min, int32_t max) { | 
|  | long val = PyInt_AsLong(o); | 
|  |  | 
|  | if (INT_CONV_ERROR_OCCURRED(val)) { | 
|  | return false; | 
|  | } | 
|  | if (!CHECK_RANGE(val, min, max)) { | 
|  | PyErr_SetString(PyExc_OverflowError, "int out of range"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | *ret = (int32_t) val; | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* --- FUNCTIONS TO PARSE STRUCT SPECIFICATOINS --- */ | 
|  |  | 
|  | static bool | 
|  | parse_set_list_args(SetListTypeArgs* dest, PyObject* typeargs) { | 
|  | if (PyTuple_Size(typeargs) != 2) { | 
|  | PyErr_SetString(PyExc_TypeError, "expecting tuple of size 2 for list/set type args"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->element_type = PyInt_AsLong(PyTuple_GET_ITEM(typeargs, 0)); | 
|  | if (INT_CONV_ERROR_OCCURRED(dest->element_type)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->typeargs = PyTuple_GET_ITEM(typeargs, 1); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | parse_map_args(MapTypeArgs* dest, PyObject* typeargs) { | 
|  | if (PyTuple_Size(typeargs) != 4) { | 
|  | PyErr_SetString(PyExc_TypeError, "expecting 4 arguments for typeargs to map"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->ktag = PyInt_AsLong(PyTuple_GET_ITEM(typeargs, 0)); | 
|  | if (INT_CONV_ERROR_OCCURRED(dest->ktag)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->vtag = PyInt_AsLong(PyTuple_GET_ITEM(typeargs, 2)); | 
|  | if (INT_CONV_ERROR_OCCURRED(dest->vtag)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->ktypeargs = PyTuple_GET_ITEM(typeargs, 1); | 
|  | dest->vtypeargs = PyTuple_GET_ITEM(typeargs, 3); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | parse_struct_args(StructTypeArgs* dest, PyObject* typeargs) { | 
|  | if (PyTuple_Size(typeargs) != 2) { | 
|  | PyErr_SetString(PyExc_TypeError, "expecting tuple of size 2 for struct args"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->klass = PyTuple_GET_ITEM(typeargs, 0); | 
|  | dest->spec = PyTuple_GET_ITEM(typeargs, 1); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static int | 
|  | parse_struct_item_spec(StructItemSpec* dest, PyObject* spec_tuple) { | 
|  |  | 
|  | // i'd like to use ParseArgs here, but it seems to be a bottleneck. | 
|  | if (PyTuple_Size(spec_tuple) != 5) { | 
|  | PyErr_SetString(PyExc_TypeError, "expecting 5 arguments for spec tuple"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->tag = PyInt_AsLong(PyTuple_GET_ITEM(spec_tuple, 0)); | 
|  | if (INT_CONV_ERROR_OCCURRED(dest->tag)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->type = PyInt_AsLong(PyTuple_GET_ITEM(spec_tuple, 1)); | 
|  | if (INT_CONV_ERROR_OCCURRED(dest->type)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->attrname = PyTuple_GET_ITEM(spec_tuple, 2); | 
|  | dest->typeargs = PyTuple_GET_ITEM(spec_tuple, 3); | 
|  | dest->defval = PyTuple_GET_ITEM(spec_tuple, 4); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* ====== END UTILITIES ====== */ | 
|  |  | 
|  |  | 
|  | /* ====== BEGIN WRITING FUNCTIONS ====== */ | 
|  |  | 
|  | /* --- LOW-LEVEL WRITING FUNCTIONS --- */ | 
|  |  | 
|  | static void writeByte(PyObject* outbuf, int8_t val) { | 
|  | int8_t net = val; | 
|  | PycStringIO->cwrite(outbuf, (char*)&net, sizeof(int8_t)); | 
|  | } | 
|  |  | 
|  | static void writeI16(PyObject* outbuf, int16_t val) { | 
|  | int16_t net = (int16_t)htons(val); | 
|  | PycStringIO->cwrite(outbuf, (char*)&net, sizeof(int16_t)); | 
|  | } | 
|  |  | 
|  | static void writeI32(PyObject* outbuf, int32_t val) { | 
|  | int32_t net = (int32_t)htonl(val); | 
|  | PycStringIO->cwrite(outbuf, (char*)&net, sizeof(int32_t)); | 
|  | } | 
|  |  | 
|  | static void writeI64(PyObject* outbuf, int64_t val) { | 
|  | int64_t net = (int64_t)htonll(val); | 
|  | PycStringIO->cwrite(outbuf, (char*)&net, sizeof(int64_t)); | 
|  | } | 
|  |  | 
|  | static void writeDouble(PyObject* outbuf, double dub) { | 
|  | // Unfortunately, bitwise_cast doesn't work in C.  Bad C! | 
|  | union { | 
|  | double f; | 
|  | int64_t t; | 
|  | } transfer; | 
|  | transfer.f = dub; | 
|  | writeI64(outbuf, transfer.t); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* --- MAIN RECURSIVE OUTPUT FUCNTION -- */ | 
|  |  | 
|  | static int | 
|  | output_val(PyObject* output, PyObject* value, TType type, PyObject* typeargs) { | 
|  | /* | 
|  | * Refcounting Strategy: | 
|  | * | 
|  | * We assume that elements of the thrift_spec tuple are not going to be | 
|  | * mutated, so we don't ref count those at all. Other than that, we try to | 
|  | * keep a reference to all the user-created objects while we work with them. | 
|  | * output_val assumes that a reference is already held. The *caller* is | 
|  | * responsible for handling references | 
|  | */ | 
|  |  | 
|  | switch (type) { | 
|  |  | 
|  | case T_BOOL: { | 
|  | int v = PyObject_IsTrue(value); | 
|  | if (v == -1) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writeByte(output, (int8_t) v); | 
|  | break; | 
|  | } | 
|  | case T_I08: { | 
|  | int32_t val; | 
|  |  | 
|  | if (!parse_pyint(value, &val, INT8_MIN, INT8_MAX)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writeByte(output, (int8_t) val); | 
|  | break; | 
|  | } | 
|  | case T_I16: { | 
|  | int32_t val; | 
|  |  | 
|  | if (!parse_pyint(value, &val, INT16_MIN, INT16_MAX)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writeI16(output, (int16_t) val); | 
|  | break; | 
|  | } | 
|  | case T_I32: { | 
|  | int32_t val; | 
|  |  | 
|  | if (!parse_pyint(value, &val, INT32_MIN, INT32_MAX)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writeI32(output, val); | 
|  | break; | 
|  | } | 
|  | case T_I64: { | 
|  | int64_t nval = PyLong_AsLongLong(value); | 
|  |  | 
|  | if (INT_CONV_ERROR_OCCURRED(nval)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!CHECK_RANGE(nval, INT64_MIN, INT64_MAX)) { | 
|  | PyErr_SetString(PyExc_OverflowError, "int out of range"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writeI64(output, nval); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case T_DOUBLE: { | 
|  | double nval = PyFloat_AsDouble(value); | 
|  | if (nval == -1.0 && PyErr_Occurred()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writeDouble(output, nval); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case T_STRING: { | 
|  | Py_ssize_t len = PyString_Size(value); | 
|  |  | 
|  | if (!check_ssize_t_32(len)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writeI32(output, (int32_t) len); | 
|  | PycStringIO->cwrite(output, PyString_AsString(value), (int32_t) len); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case T_LIST: | 
|  | case T_SET: { | 
|  | Py_ssize_t len; | 
|  | SetListTypeArgs parsedargs; | 
|  | PyObject *item; | 
|  | PyObject *iterator; | 
|  |  | 
|  | if (!parse_set_list_args(&parsedargs, typeargs)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | len = PyObject_Length(value); | 
|  |  | 
|  | if (!check_ssize_t_32(len)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writeByte(output, parsedargs.element_type); | 
|  | writeI32(output, (int32_t) len); | 
|  |  | 
|  | iterator =  PyObject_GetIter(value); | 
|  | if (iterator == NULL) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | while ((item = PyIter_Next(iterator))) { | 
|  | if (!output_val(output, item, parsedargs.element_type, parsedargs.typeargs)) { | 
|  | Py_DECREF(item); | 
|  | Py_DECREF(iterator); | 
|  | return false; | 
|  | } | 
|  | Py_DECREF(item); | 
|  | } | 
|  |  | 
|  | Py_DECREF(iterator); | 
|  |  | 
|  | if (PyErr_Occurred()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | case T_MAP: { | 
|  | PyObject *k, *v; | 
|  | Py_ssize_t pos = 0; | 
|  | Py_ssize_t len; | 
|  |  | 
|  | MapTypeArgs parsedargs; | 
|  |  | 
|  | len = PyDict_Size(value); | 
|  | if (!check_ssize_t_32(len)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!parse_map_args(&parsedargs, typeargs)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writeByte(output, parsedargs.ktag); | 
|  | writeByte(output, parsedargs.vtag); | 
|  | writeI32(output, len); | 
|  |  | 
|  | // TODO(bmaurer): should support any mapping, not just dicts | 
|  | while (PyDict_Next(value, &pos, &k, &v)) { | 
|  | // TODO(dreiss): Think hard about whether these INCREFs actually | 
|  | //               turn any unsafe scenarios into safe scenarios. | 
|  | Py_INCREF(k); | 
|  | Py_INCREF(v); | 
|  |  | 
|  | if (!output_val(output, k, parsedargs.ktag, parsedargs.ktypeargs) | 
|  | || !output_val(output, v, parsedargs.vtag, parsedargs.vtypeargs)) { | 
|  | Py_DECREF(k); | 
|  | Py_DECREF(v); | 
|  | return false; | 
|  | } | 
|  | Py_DECREF(k); | 
|  | Py_DECREF(v); | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | // TODO(dreiss): Consider breaking this out as a function | 
|  | //               the way we did for decode_struct. | 
|  | case T_STRUCT: { | 
|  | StructTypeArgs parsedargs; | 
|  | Py_ssize_t nspec; | 
|  | Py_ssize_t i; | 
|  |  | 
|  | if (!parse_struct_args(&parsedargs, typeargs)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | nspec = PyTuple_Size(parsedargs.spec); | 
|  |  | 
|  | if (nspec == -1) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < nspec; i++) { | 
|  | StructItemSpec parsedspec; | 
|  | PyObject* spec_tuple; | 
|  | PyObject* instval = NULL; | 
|  |  | 
|  | spec_tuple = PyTuple_GET_ITEM(parsedargs.spec, i); | 
|  | if (spec_tuple == Py_None) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (!parse_struct_item_spec (&parsedspec, spec_tuple)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | instval = PyObject_GetAttr(value, parsedspec.attrname); | 
|  |  | 
|  | if (!instval) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (instval == Py_None) { | 
|  | Py_DECREF(instval); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | writeByte(output, (int8_t) parsedspec.type); | 
|  | writeI16(output, parsedspec.tag); | 
|  |  | 
|  | if (!output_val(output, instval, parsedspec.type, parsedspec.typeargs)) { | 
|  | Py_DECREF(instval); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | Py_DECREF(instval); | 
|  | } | 
|  |  | 
|  | writeByte(output, (int8_t)T_STOP); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case T_STOP: | 
|  | case T_VOID: | 
|  | case T_UTF16: | 
|  | case T_UTF8: | 
|  | case T_U64: | 
|  | default: | 
|  | PyErr_SetString(PyExc_TypeError, "Unexpected TType"); | 
|  | return false; | 
|  |  | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* --- TOP-LEVEL WRAPPER FOR OUTPUT -- */ | 
|  |  | 
|  | static PyObject * | 
|  | encode_binary(PyObject *self, PyObject *args) { | 
|  | PyObject* enc_obj; | 
|  | PyObject* type_args; | 
|  | PyObject* buf; | 
|  | PyObject* ret = NULL; | 
|  |  | 
|  | if (!PyArg_ParseTuple(args, "OO", &enc_obj, &type_args)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | buf = PycStringIO->NewOutput(INIT_OUTBUF_SIZE); | 
|  | if (output_val(buf, enc_obj, T_STRUCT, type_args)) { | 
|  | ret = PycStringIO->cgetvalue(buf); | 
|  | } | 
|  |  | 
|  | Py_DECREF(buf); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* ====== END WRITING FUNCTIONS ====== */ | 
|  |  | 
|  |  | 
|  | /* ====== BEGIN READING FUNCTIONS ====== */ | 
|  |  | 
|  | /* --- LOW-LEVEL READING FUNCTIONS --- */ | 
|  |  | 
|  | static void | 
|  | free_decodebuf(DecodeBuffer* d) { | 
|  | Py_XDECREF(d->stringiobuf); | 
|  | Py_XDECREF(d->refill_callable); | 
|  | } | 
|  |  | 
|  | static bool | 
|  | decode_buffer_from_obj(DecodeBuffer* dest, PyObject* obj) { | 
|  | dest->stringiobuf = PyObject_GetAttr(obj, INTERN_STRING(cstringio_buf)); | 
|  | if (!dest->stringiobuf) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!PycStringIO_InputCheck(dest->stringiobuf)) { | 
|  | free_decodebuf(dest); | 
|  | PyErr_SetString(PyExc_TypeError, "expecting stringio input"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dest->refill_callable = PyObject_GetAttr(obj, INTERN_STRING(cstringio_refill)); | 
|  |  | 
|  | if(!dest->refill_callable) { | 
|  | free_decodebuf(dest); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!PyCallable_Check(dest->refill_callable)) { | 
|  | free_decodebuf(dest); | 
|  | PyErr_SetString(PyExc_TypeError, "expecting callable"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool readBytes(DecodeBuffer* input, char** output, int len) { | 
|  | int read; | 
|  |  | 
|  | // TODO(dreiss): Don't fear the malloc.  Think about taking a copy of | 
|  | //               the partial read instead of forcing the transport | 
|  | //               to prepend it to its buffer. | 
|  |  | 
|  | read = PycStringIO->cread(input->stringiobuf, output, len); | 
|  |  | 
|  | if (read == len) { | 
|  | return true; | 
|  | } else if (read == -1) { | 
|  | return false; | 
|  | } else { | 
|  | PyObject* newiobuf; | 
|  |  | 
|  | // using building functions as this is a rare codepath | 
|  | newiobuf = PyObject_CallFunction( | 
|  | input->refill_callable, "s#i", *output, read, len, NULL); | 
|  | if (newiobuf == NULL) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // must do this *AFTER* the call so that we don't deref the io buffer | 
|  | Py_CLEAR(input->stringiobuf); | 
|  | input->stringiobuf = newiobuf; | 
|  |  | 
|  | read = PycStringIO->cread(input->stringiobuf, output, len); | 
|  |  | 
|  | if (read == len) { | 
|  | return true; | 
|  | } else if (read == -1) { | 
|  | return false; | 
|  | } else { | 
|  | // TODO(dreiss): This could be a valid code path for big binary blobs. | 
|  | PyErr_SetString(PyExc_TypeError, | 
|  | "refill claimed to have refilled the buffer, but didn't!!"); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static int8_t readByte(DecodeBuffer* input) { | 
|  | char* buf; | 
|  | if (!readBytes(input, &buf, sizeof(int8_t))) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return *(int8_t*) buf; | 
|  | } | 
|  |  | 
|  | static int16_t readI16(DecodeBuffer* input) { | 
|  | char* buf; | 
|  | if (!readBytes(input, &buf, sizeof(int16_t))) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return (int16_t) ntohs(*(int16_t*) buf); | 
|  | } | 
|  |  | 
|  | static int32_t readI32(DecodeBuffer* input) { | 
|  | char* buf; | 
|  | if (!readBytes(input, &buf, sizeof(int32_t))) { | 
|  | return -1; | 
|  | } | 
|  | return (int32_t) ntohl(*(int32_t*) buf); | 
|  | } | 
|  |  | 
|  |  | 
|  | static int64_t readI64(DecodeBuffer* input) { | 
|  | char* buf; | 
|  | if (!readBytes(input, &buf, sizeof(int64_t))) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return (int64_t) ntohll(*(int64_t*) buf); | 
|  | } | 
|  |  | 
|  | static double readDouble(DecodeBuffer* input) { | 
|  | union { | 
|  | int64_t f; | 
|  | double t; | 
|  | } transfer; | 
|  |  | 
|  | transfer.f = readI64(input); | 
|  | if (transfer.f == -1) { | 
|  | return -1; | 
|  | } | 
|  | return transfer.t; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | checkTypeByte(DecodeBuffer* input, TType expected) { | 
|  | TType got = readByte(input); | 
|  | if (INT_CONV_ERROR_OCCURRED(got)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (expected != got) { | 
|  | PyErr_SetString(PyExc_TypeError, "got wrong ttype while reading field"); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | skip(DecodeBuffer* input, TType type) { | 
|  | #define SKIPBYTES(n) \ | 
|  | do { \ | 
|  | if (!readBytes(input, &dummy_buf, (n))) { \ | 
|  | return false; \ | 
|  | } \ | 
|  | } while(0) | 
|  |  | 
|  | char* dummy_buf; | 
|  |  | 
|  | switch (type) { | 
|  |  | 
|  | case T_BOOL: | 
|  | case T_I08: SKIPBYTES(1); break; | 
|  | case T_I16: SKIPBYTES(2); break; | 
|  | case T_I32: SKIPBYTES(4); break; | 
|  | case T_I64: | 
|  | case T_DOUBLE: SKIPBYTES(8); break; | 
|  |  | 
|  | case T_STRING: { | 
|  | // TODO(dreiss): Find out if these check_ssize_t32s are really necessary. | 
|  | int len = readI32(input); | 
|  | if (!check_ssize_t_32(len)) { | 
|  | return false; | 
|  | } | 
|  | SKIPBYTES(len); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case T_LIST: | 
|  | case T_SET: { | 
|  | TType etype; | 
|  | int len, i; | 
|  |  | 
|  | etype = readByte(input); | 
|  | if (etype == -1) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | len = readI32(input); | 
|  | if (!check_ssize_t_32(len)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < len; i++) { | 
|  | if (!skip(input, etype)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case T_MAP: { | 
|  | TType ktype, vtype; | 
|  | int len, i; | 
|  |  | 
|  | ktype = readByte(input); | 
|  | if (ktype == -1) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | vtype = readByte(input); | 
|  | if (vtype == -1) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | len = readI32(input); | 
|  | if (!check_ssize_t_32(len)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < len; i++) { | 
|  | if (!(skip(input, ktype) && skip(input, vtype))) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case T_STRUCT: { | 
|  | while (true) { | 
|  | TType type; | 
|  |  | 
|  | type = readByte(input); | 
|  | if (type == -1) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (type == T_STOP) | 
|  | break; | 
|  |  | 
|  | SKIPBYTES(2); // tag | 
|  | if (!skip(input, type)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case T_STOP: | 
|  | case T_VOID: | 
|  | case T_UTF16: | 
|  | case T_UTF8: | 
|  | case T_U64: | 
|  | default: | 
|  | PyErr_SetString(PyExc_TypeError, "Unexpected TType"); | 
|  | return false; | 
|  |  | 
|  | } | 
|  |  | 
|  | return true; | 
|  |  | 
|  | #undef SKIPBYTES | 
|  | } | 
|  |  | 
|  |  | 
|  | /* --- HELPER FUNCTION FOR DECODE_VAL --- */ | 
|  |  | 
|  | static PyObject* | 
|  | decode_val(DecodeBuffer* input, TType type, PyObject* typeargs); | 
|  |  | 
|  | static bool | 
|  | decode_struct(DecodeBuffer* input, PyObject* output, PyObject* spec_seq) { | 
|  | int spec_seq_len = PyTuple_Size(spec_seq); | 
|  | if (spec_seq_len == -1) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | while (true) { | 
|  | TType type; | 
|  | int16_t tag; | 
|  | PyObject* item_spec; | 
|  | PyObject* fieldval = NULL; | 
|  | StructItemSpec parsedspec; | 
|  |  | 
|  | type = readByte(input); | 
|  | if (type == -1) { | 
|  | return false; | 
|  | } | 
|  | if (type == T_STOP) { | 
|  | break; | 
|  | } | 
|  | tag = readI16(input); | 
|  | if (INT_CONV_ERROR_OCCURRED(tag)) { | 
|  | return false; | 
|  | } | 
|  | if (tag >= 0 && tag < spec_seq_len) { | 
|  | item_spec = PyTuple_GET_ITEM(spec_seq, tag); | 
|  | } else { | 
|  | item_spec = Py_None; | 
|  | } | 
|  |  | 
|  | if (item_spec == Py_None) { | 
|  | if (!skip(input, type)) { | 
|  | return false; | 
|  | } else { | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!parse_struct_item_spec(&parsedspec, item_spec)) { | 
|  | return false; | 
|  | } | 
|  | if (parsedspec.type != type) { | 
|  | if (!skip(input, type)) { | 
|  | PyErr_SetString(PyExc_TypeError, "struct field had wrong type while reading and can't be skipped"); | 
|  | return false; | 
|  | } else { | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | fieldval = decode_val(input, parsedspec.type, parsedspec.typeargs); | 
|  | if (fieldval == NULL) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (PyObject_SetAttr(output, parsedspec.attrname, fieldval) == -1) { | 
|  | Py_DECREF(fieldval); | 
|  | return false; | 
|  | } | 
|  | Py_DECREF(fieldval); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* --- MAIN RECURSIVE INPUT FUCNTION --- */ | 
|  |  | 
|  | // Returns a new reference. | 
|  | static PyObject* | 
|  | decode_val(DecodeBuffer* input, TType type, PyObject* typeargs) { | 
|  | switch (type) { | 
|  |  | 
|  | case T_BOOL: { | 
|  | int8_t v = readByte(input); | 
|  | if (INT_CONV_ERROR_OCCURRED(v)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | switch (v) { | 
|  | case 0: Py_RETURN_FALSE; | 
|  | case 1: Py_RETURN_TRUE; | 
|  | // Don't laugh.  This is a potentially serious issue. | 
|  | default: PyErr_SetString(PyExc_TypeError, "boolean out of range"); return NULL; | 
|  | } | 
|  | break; | 
|  | } | 
|  | case T_I08: { | 
|  | int8_t v = readByte(input); | 
|  | if (INT_CONV_ERROR_OCCURRED(v)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return PyInt_FromLong(v); | 
|  | } | 
|  | case T_I16: { | 
|  | int16_t v = readI16(input); | 
|  | if (INT_CONV_ERROR_OCCURRED(v)) { | 
|  | return NULL; | 
|  | } | 
|  | return PyInt_FromLong(v); | 
|  | } | 
|  | case T_I32: { | 
|  | int32_t v = readI32(input); | 
|  | if (INT_CONV_ERROR_OCCURRED(v)) { | 
|  | return NULL; | 
|  | } | 
|  | return PyInt_FromLong(v); | 
|  | } | 
|  |  | 
|  | case T_I64: { | 
|  | int64_t v = readI64(input); | 
|  | if (INT_CONV_ERROR_OCCURRED(v)) { | 
|  | return NULL; | 
|  | } | 
|  | // TODO(dreiss): Find out if we can take this fastpath always when | 
|  | //               sizeof(long) == sizeof(long long). | 
|  | if (CHECK_RANGE(v, LONG_MIN, LONG_MAX)) { | 
|  | return PyInt_FromLong((long) v); | 
|  | } | 
|  |  | 
|  | return PyLong_FromLongLong(v); | 
|  | } | 
|  |  | 
|  | case T_DOUBLE: { | 
|  | double v = readDouble(input); | 
|  | if (v == -1.0 && PyErr_Occurred()) { | 
|  | return false; | 
|  | } | 
|  | return PyFloat_FromDouble(v); | 
|  | } | 
|  |  | 
|  | case T_STRING: { | 
|  | Py_ssize_t len = readI32(input); | 
|  | char* buf; | 
|  | if (!readBytes(input, &buf, len)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return PyString_FromStringAndSize(buf, len); | 
|  | } | 
|  |  | 
|  | case T_LIST: | 
|  | case T_SET: { | 
|  | SetListTypeArgs parsedargs; | 
|  | int32_t len; | 
|  | PyObject* ret = NULL; | 
|  | int i; | 
|  |  | 
|  | if (!parse_set_list_args(&parsedargs, typeargs)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (!checkTypeByte(input, parsedargs.element_type)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | len = readI32(input); | 
|  | if (!check_ssize_t_32(len)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | ret = PyList_New(len); | 
|  | if (!ret) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < len; i++) { | 
|  | PyObject* item = decode_val(input, parsedargs.element_type, parsedargs.typeargs); | 
|  | if (!item) { | 
|  | Py_DECREF(ret); | 
|  | return NULL; | 
|  | } | 
|  | PyList_SET_ITEM(ret, i, item); | 
|  | } | 
|  |  | 
|  | // TODO(dreiss): Consider biting the bullet and making two separate cases | 
|  | //               for list and set, avoiding this post facto conversion. | 
|  | if (type == T_SET) { | 
|  | PyObject* setret; | 
|  | #if (PY_VERSION_HEX < 0x02050000) | 
|  | // hack needed for older versions | 
|  | setret = PyObject_CallFunctionObjArgs((PyObject*)&PySet_Type, ret, NULL); | 
|  | #else | 
|  | // official version | 
|  | setret = PySet_New(ret); | 
|  | #endif | 
|  | Py_DECREF(ret); | 
|  | return setret; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | case T_MAP: { | 
|  | int32_t len; | 
|  | int i; | 
|  | MapTypeArgs parsedargs; | 
|  | PyObject* ret = NULL; | 
|  |  | 
|  | if (!parse_map_args(&parsedargs, typeargs)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (!checkTypeByte(input, parsedargs.ktag)) { | 
|  | return NULL; | 
|  | } | 
|  | if (!checkTypeByte(input, parsedargs.vtag)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | len = readI32(input); | 
|  | if (!check_ssize_t_32(len)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | ret = PyDict_New(); | 
|  | if (!ret) { | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < len; i++) { | 
|  | PyObject* k = NULL; | 
|  | PyObject* v = NULL; | 
|  | k = decode_val(input, parsedargs.ktag, parsedargs.ktypeargs); | 
|  | if (k == NULL) { | 
|  | goto loop_error; | 
|  | } | 
|  | v = decode_val(input, parsedargs.vtag, parsedargs.vtypeargs); | 
|  | if (v == NULL) { | 
|  | goto loop_error; | 
|  | } | 
|  | if (PyDict_SetItem(ret, k, v) == -1) { | 
|  | goto loop_error; | 
|  | } | 
|  |  | 
|  | Py_DECREF(k); | 
|  | Py_DECREF(v); | 
|  | continue; | 
|  |  | 
|  | // Yuck!  Destructors, anyone? | 
|  | loop_error: | 
|  | Py_XDECREF(k); | 
|  | Py_XDECREF(v); | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  |  | 
|  | error: | 
|  | Py_XDECREF(ret); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | case T_STRUCT: { | 
|  | StructTypeArgs parsedargs; | 
|  | if (!parse_struct_args(&parsedargs, typeargs)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | PyObject* ret = PyObject_CallObject(parsedargs.klass, NULL); | 
|  | if (!ret) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (!decode_struct(input, ret, parsedargs.spec)) { | 
|  | Py_DECREF(ret); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | case T_STOP: | 
|  | case T_VOID: | 
|  | case T_UTF16: | 
|  | case T_UTF8: | 
|  | case T_U64: | 
|  | default: | 
|  | PyErr_SetString(PyExc_TypeError, "Unexpected TType"); | 
|  | return NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /* --- TOP-LEVEL WRAPPER FOR INPUT -- */ | 
|  |  | 
|  | static PyObject* | 
|  | decode_binary(PyObject *self, PyObject *args) { | 
|  | PyObject* output_obj = NULL; | 
|  | PyObject* transport = NULL; | 
|  | PyObject* typeargs = NULL; | 
|  | StructTypeArgs parsedargs; | 
|  | DecodeBuffer input = {}; | 
|  |  | 
|  | if (!PyArg_ParseTuple(args, "OOO", &output_obj, &transport, &typeargs)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (!parse_struct_args(&parsedargs, typeargs)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (!decode_buffer_from_obj(&input, transport)) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (!decode_struct(&input, output_obj, parsedargs.spec)) { | 
|  | free_decodebuf(&input); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | free_decodebuf(&input); | 
|  |  | 
|  | Py_RETURN_NONE; | 
|  | } | 
|  |  | 
|  | /* ====== END READING FUNCTIONS ====== */ | 
|  |  | 
|  |  | 
|  | /* -- PYTHON MODULE SETUP STUFF --- */ | 
|  |  | 
|  | static PyMethodDef ThriftFastBinaryMethods[] = { | 
|  |  | 
|  | {"encode_binary",  encode_binary, METH_VARARGS, ""}, | 
|  | {"decode_binary",  decode_binary, METH_VARARGS, ""}, | 
|  |  | 
|  | {NULL, NULL, 0, NULL}        /* Sentinel */ | 
|  | }; | 
|  |  | 
|  | PyMODINIT_FUNC | 
|  | initfastbinary(void) { | 
|  | #define INIT_INTERN_STRING(value) \ | 
|  | do { \ | 
|  | INTERN_STRING(value) = PyString_InternFromString(#value); \ | 
|  | if(!INTERN_STRING(value)) return; \ | 
|  | } while(0) | 
|  |  | 
|  | INIT_INTERN_STRING(cstringio_buf); | 
|  | INIT_INTERN_STRING(cstringio_refill); | 
|  | #undef INIT_INTERN_STRING | 
|  |  | 
|  | PycString_IMPORT; | 
|  | if (PycStringIO == NULL) return; | 
|  |  | 
|  | (void) Py_InitModule("thrift.protocol.fastbinary", ThriftFastBinaryMethods); | 
|  | } |