Python: ctypes хеширует замену массива c_char без отключения над '\ 0' байтами

В целях иллюстрации этот скрипт создает файл mapfile содержащий содержимое файлов, заданное в качестве аргументов, добавляемое двоичным заголовком с контрольной суммой sha1, которое позволяет обнаруживать дублирование при последующих запусках.

Здесь нужна ctypes.c_char замена ctypes.c_char , которая может содержать контрольные суммы sha1 с минимальным fuzz, но не задыхаться от байтов '\ 0'.

 # -*- coding: utf8 -* import io import mmap import ctypes import hashlib import logging from collections import OrderedDict log = logging.getLogger(__file__) def align(size, alignment): """return size aligned to alignment""" excess = size % alignment if excess: size = size - excess + alignment return size class Header(ctypes.Structure): Identifier = b'HEAD' _fields_ = [ ('id', ctypes.c_char * 4), ('hlen', ctypes.c_uint16), ('plen', ctypes.c_uint32), ('name', ctypes.c_char * 128), ('sha1', ctypes.c_char * 20), ] HeaderSize = ctypes.sizeof(Header) class CtsMap: def __init__(self, ctcls, mm, offset = 0): self.ctcls = ctcls self.mm = mm self.offset = offset def __enter__(self): mm = self.mm offset = self.offset ctsize = ctypes.sizeof(self.ctcls) if offset + ctsize > mm.size(): newsize = align(offset + ctsize, mmap.PAGESIZE) mm.resize(newsize) self.ctinst = self.ctcls.from_buffer(mm, offset) return self.ctinst def __exit__(self, exc_type, exc_value, exc_traceback): del self.ctinst self.ctinst = None class MapFile: def __init__(self, filename): try: # try to create initial file mapsize = mmap.PAGESIZE self._fd = open(filename, 'x+b') self._fd.write(b'\0' * mapsize) except FileExistsError: # file exists and is writable self._fd = open(filename, 'r+b') self._fd.seek(0, io.SEEK_END) mapsize = self._fd.tell() # mmap this file completely self._fd.seek(0) self._mm = mmap.mmap(self._fd.fileno(), mapsize) self._offset = 0 self._toc = OrderedDict() self.gen_toc() def gen_toc(self): while self._offset < self._mm.size(): with CtsMap(Header, self._mm, self._offset) as hd: if hd.id == Header.Identifier and hd.hlen == HeaderSize: self._toc[hd.sha1] = self._offset log.debug('toc: [%s]%s: %s', len(hd.sha1), hd.sha1, self._offset) self._offset += HeaderSize + hd.plen else: break del hd def add_data(self, datafile, data): datasize = len(data) sha1 = hashlib.sha1() sha1.update(data) digest = sha1.digest() if digest in self._toc: log.debug('add_data: %s added already', digest) return None log.debug('add_data: %s, %s bytes, %s', datafile, datasize, digest) with CtsMap(Header, self._mm, self._offset) as hd: hd.id = Header.Identifier hd.hlen = HeaderSize hd.plen = datasize hd.name = datafile hd.sha1 = digest del hd self._offset += HeaderSize log.debug('add_data: %s', datasize) blktype = ctypes.c_char * datasize with CtsMap(blktype, self._mm, self._offset) as blk: blk.raw = data del blk self._offset += datasize return HeaderSize + datasize def close(self): self._mm.close() self._fd.close() if __name__ == '__main__': import os import sys logconfig = dict( level = logging.DEBUG, format = '%(levelname)5s: %(message)s', ) logging.basicConfig(**logconfig) mf = MapFile('mapfile') for datafile in sys.argv[1:]: if os.path.isfile(datafile): try: data = open(datafile, 'rb').read() except OSError: continue else: mf.add_data(datafile.encode('utf-8'), data) mf.close() 

Выполнить: python3 hashable_ctypes_bytes.py somefiles*

Вызвав его второй раз, он считывает файл, собирая все элементы в упорядоченном dict, используя ключ sha1 как ключ. К сожалению, семантика массива c_char немного связана с проводкой, потому что она также ведет себя как «\ 0» с завершенной строкой c, что приводит к усеченным контрольным суммам здесь.

См. Строки 3 и 4:

 DEBUG: toc: [20]b'\xcd0\xd7\xd3\xbf\x9f\xe1\xfe\xffr\xa6g#\xee\xf8\x84\xb5S,u': 0 DEBUG: toc: [20]b'\xe9\xfe\x1a;i\xcdG0\x84\x1b\r\x7f\xf9\x14\x868\xbdVl\x8d': 1273 DEBUG: toc: [19]b'\xa2\xdb\xff$&\xfe\x0f\xb4\xcaB<F\x92\xc0\xf1`(\x96N': 3642 DEBUG: toc: [15]b'O\x1b~c\x82\xeb)\x8f\xb5\x9c\x15\xd5e:\xa9': 4650 DEBUG: toc: [20]b'\x80\xe9\xbcF\x97\xdc\x93DG\x90\x19\x8c\xca\xfep\x05\xbdM\xfby': 13841 DEBUG: add_data: b'\xcd0\xd7\xd3\xbf\x9f\xe1\xfe\xffr\xa6g#\xee\xf8\x84\xb5S,u' added already DEBUG: add_data: b'\xe9\xfe\x1a;i\xcdG0\x84\x1b\r\x7f\xf9\x14\x868\xbdVl\x8d' added already DEBUG: add_data: b'../python/tmp/colorselect.py', 848 bytes, b'\xa2\xdb\xff$&\xfe\x0f\xb4\xcaB<F\x92\xc0\xf1`(\x96N\x00' DEBUG: add_data: 848 DEBUG: add_data: b'../python/tmp/DemoCode.py', 9031 bytes, b'O\x1b~c\x82\xeb)\x8f\xb5\x9c\x15\xd5e:\xa9\x00p\x0f\xc04' DEBUG: add_data: 9031 DEBUG: add_data: b'\x80\xe9\xbcF\x97\xdc\x93DG\x90\x19\x8c\xca\xfep\x05\xbdM\xfby' added already 

Обычное предложение заменяет c_char * 20 на c_byte * 20 и теряет прозрачную обработку байтов на этом пути. Помимо проблем, связанных с преобразованием данных, массивы c_byte не являются хешируемыми из-за того, что они являются bytearrays. Я не нашел практического решения, не переживая тяжелые проблемы с конверсией взад и вперед или прибегая к шестигранникам, что удваивает требования к размеру таблицы sha1.

Я думаю, что решение дизайна c_char смешать его с семантикой терминации нуля C было ошибкой в ​​первую очередь. Чтобы справиться с этим, я мог бы представить, чтобы добавить тип c_char_nz в ctypes, что устраняет эту проблему.

Для тех из вас, которые внимательно читают код, вы можете задаться вопросом о del-выводах структур ctypes. Обсуждение этого можно найти здесь:.

One Solution collect form web for “Python: ctypes хеширует замену массива c_char без отключения над '\ 0' байтами”

Хотя приведенный ниже код делает обратное преобразование, о котором вы упомянули, он действительно скрывает проблему. Я нашел хэш, содержащий нуль, и поле теперь можно использовать в качестве словарного ключа. Надеюсь, поможет.

 from ctypes import * import hashlib class Test(Structure): _fields_ = [('_sha1',c_ubyte * 20)] @property def sha1(self): return bytes(self._sha1) @sha1.setter def sha1(self, value): self._sha1 = (c_ubyte * 20)(*value) test = Test() test.sha1 = hashlib.sha1(b'aaaaaaaaaaa').digest() D = {test.sha1:0} print(D) 

Вывод:

 {b'u\\\x00\x1fJ\xe3\xc8\x84>ZP\xddj\xa2\xfa#\x89=\xd3\xad': 0} 
  • Python 3 Создание массива байтов
  • Производительность функции Python
  • Ошибка при использовании Pandas pivot_table с полями = True
  • не может импортировать имя GoogleMaps в python
  • Как написать utf8 для стандартного вывода таким образом, который работает с python2 и python3
  • Создание скрипта `nosetests` для выбора папки по версии Python
  • python3: привязать метод к экземпляру класса с .__ get __ (), он работает, но почему?
  • Обратная совместимость Python 3.5 для внешних модулей
  • Python - лучший язык программирования в мире.