2010-12-14 4 views
3

J'essaye d'écrire une extension Cython à CPython pour envelopper la bibliothèque mcrypt, de sorte que je puisse l'utiliser avec Python 3. Cependant, je rencontre un problème où je me sépare en essayant d'utiliser une des API mcrypt.Cython octets à C char *

Le code qui échoue est:

def _real_encrypt(self, source): 
    src_len = len(source) 
    cdef char* ciphertext = source 
    cmc.mcrypt_generic(self._mcStream, <void *>ciphertext, src_len) 
    retval = source[:src_len] 
    return retval 

Maintenant, la façon dont je comprends la documentation Cython, l'affectation de la ligne 3 devrait copier le contenu de la mémoire tampon (un objet en Python 3) à la C pointeur de chaîne. Je comprendre que cela signifie également qu'il attribuerait la mémoire, mais quand je fait cette modification:

def _real_encrypt(self, source): 
    src_len = len(source) 
    cdef char* ciphertext = <char *>malloc(src_len) 
    ciphertext = source 
    cmc.mcrypt_generic(self._mcStream, <void *>ciphertext, src_len) 
    retval = source[:src_len] 
    return retval 

il est écrasé encore avec un segfault. Il se bloque à l'intérieur de mcrypt_generic, mais quand j'utilise du code C simple, je suis capable de le faire fonctionner correctement, donc il doit y avoir quelque chose que je ne comprends pas bien comment Cython fonctionne avec les données C ici.

Merci pour toute aide!

ETA: Le problème était un bug de ma part. Je travaillais là-dessus après avoir été réveillé pendant trop d'heures (n'est-ce pas quelque chose que nous avons tous fait à un moment donné?) Et j'ai raté quelque chose de stupide. Le code que j'ai maintenant, ce qui fonctionne, est:

def _real_encrypt(self, source): 
    src_len = len(source) 
    cdef char *ciphertext = <char *>malloc(src_len) 
    cmc.strncpy(ciphertext, source, src_len) 
    cmc.mcrypt_generic_init(self._mcStream, <void *>self._key, 
          len(self._key), NULL) 
    cmc.mcrypt_generic(self._mcStream, <void *>ciphertext, 
         src_len) 

    retval = ciphertext[:src_len] 
    cmc.mcrypt_generic_deinit(self._mcStream) 
    return retval 

Il est probablement pas le code le plus efficace dans le monde, car il fait une copie pour faire le chiffrement, puis une deuxième copie à la valeur de retour. Je ne suis pas sûr si c'est possible d'éviter cela, cependant, puisque je ne suis pas sûr s'il est possible de prendre un tampon nouvellement alloué et le renvoyer à Python in-place comme un bytestring. Mais maintenant que j'ai une fonction de travail, je vais également implémenter une méthode block-by-block, de sorte que l'on puisse fournir un itérable de blocs pour le chiffrement ou le décryptage, et pouvoir le faire sans avoir la source entière et la destination tout dans la mémoire tout à la fois --- de cette façon, il serait possible de crypter/décrypter des fichiers énormes sans avoir à en garder jusqu'à trois copies en mémoire à un moment donné ...

Merci à tous pour l'aide!

Répondre

3

Le premier pointe le char* à la chaîne Python. Le deuxième alloue de la mémoire, mais re-pointe le pointeur sur la chaîne Python et ignore la mémoire nouvellement allouée. Vous devriez appeler la fonction de bibliothèque C strcpy de Cython, vraisemblablement; mais je ne connais pas les détails.

+0

... ce serait pourquoi je devrais probablement être allé au lit la nuit dernière, parce que j'essayais de programmer tout fatigué. En effet, ce qui l'a fait fonctionner était un appel à strncpy (que j'ai utilisé en raison de la possibilité de NULL octets dans l'entrée), et puis je pouvais faire l'appel à mcrypt_generic, copier la sortie dans un bytestring Python, libérer le tampon temporaire et retour. Merci pour cette réponse, elle m'a indiqué dans la bonne direction. –

+0

!!! 'strncpy' ne ** vous ** aidera pas s'il peut valablement y avoir des octets NULL dans l'entrée. Cela signifie que votre entrée n'est pas vraiment une chaîne du tout, mais une séquence d'octets. Utilisez 'memcpy' ou quelque chose comme ça. –

+0

Oh, vous avez absolument raison; le n est "jusqu'à". Oh, merde. Oh, oh, merde. Je pense que vous pourriez avoir juste signalé mon bogue que j'ai posté sur une autre question: http://stackoverflow.com/questions/4451977/data-corruption-wheres-the-bug –

1

L'approche que je l'ai utilisé (avec Python 2.x) est de déclarer les paramètres de type chaîne dans la signature de la fonction afin que le code Cython fait toutes les conversions et vérification du type automatiquement:

def _real_encrypt(self,char* src): 
    ... 
3

Quelques commentaires sur votre code pour aider à l'améliorer, à mon humble avis. Il y a des fonctions fournies par l'API C de python qui font exactement ce que vous devez faire, et assurez-vous que tout se conforme à la façon de faire de Python. Il va gérer les NULL embarqués sans problème.

Plutôt que d'appeler directement malloc, changer ceci:

cdef char *ciphertext = <char *>malloc(src_len) 

à

cdef str retval = PyString_FromStringAndSize(PyString_AsString(source), <Py_ssize_t>src_len) 
cdef char *ciphertext = PyString_AsString(retval) 

Les lignes ci-dessus vont créer une nouvelle marque objet str Python initialisé au contenu de source. La deuxième ligne pointe ciphertext vers retval tampon interne char * sans copie. Quoi que modifie ciphertext va modifier retval. Puisque retval est une toute nouvelle chaîne Python, elle peut être modifiée par le code C avant d'être renvoyée par _real_encrypt. Pour plus d'informations, consultez les documents C/API Python sur les fonctions ci-dessus, here et here.

L'effet net vous permet d'économiser une copie. L'ensemble du code serait quelque chose comme:

cdef extern from "Python.h": 
    object PyString_FromStringAndSize(char *, Py_ssize_t) 
    char *PyString_AsString(object) 

def _real_encrypt(self, source): 
    src_len = len(source) 
    cdef str retval = PyString_FromStringAndSize(PyString_AsString(source), <Py_ssize_t>src_len) 
    cdef char *ciphertext = PyString_AsString(retval) 
    cmc.mcrypt_generic_init(self._mcStream, <void *>self._key, 
          len(self._key), NULL) 
    cmc.mcrypt_generic(self._mcStream, <void *>ciphertext, 
         src_len) 
    # since the above initialized ciphertext, the retval str is also correctly initialized, too. 
    cmc.mcrypt_generic_deinit(self._mcStream) 
    return retval