Skip to content
Merged

Fixes #2333

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/bitmessageqt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@
import bitmessage_icons_rc # noqa:F401 pylint: disable=unused-import
import helper_sent

from six.moves import iteritems, itervalues, range as xrange
from six import text_type
from six import iteritems, itervalues, text_type

try:
from plugins.plugin import get_plugin, get_plugins
Expand Down
2 changes: 1 addition & 1 deletion src/bitmessageqt/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def createSupportMessage(myapp):
if paths.frozen:
frozen = paths.frozen
portablemode = "True" if state.appdata == paths.lookupExeFolder() else "False"
cpow = "True" if proofofwork.bmpow else "False"
cpow = "True" if proofofwork.BMPOW else "False"
openclpow = str(
config.safeGet('bitmessagesettings', 'opencl')
) if openclEnabled() else "None"
Expand Down
25 changes: 17 additions & 8 deletions src/class_objectProcessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,19 @@ def checkackdata(data):
# bypass nonce and time, retain object type/version/stream + body
readPosition = 16

if data[readPosition:] in state.ackdataForWhichImWatching:
# data may be a memoryview, which is not hashable and thus
# cannot be used as a dictionary key; convert the slice to bytes.
ackcheckdata = bytes(data[readPosition:])

if ackcheckdata in state.ackdataForWhichImWatching:
logger.info('This object is an acknowledgement bound for me.')
del state.ackdataForWhichImWatching[data[readPosition:]]
del state.ackdataForWhichImWatching[ackcheckdata]
sqlExecute(
"UPDATE sent SET status='ackreceived', lastactiontime=?"
" WHERE ackdata=?", int(time.time()), data[readPosition:])
" WHERE ackdata=?", int(time.time()), ackcheckdata)
queues.UISignalQueue.put((
'updateSentItemStatusByAckdata', (
data[readPosition:],
ackcheckdata,
_translate(
"MainWindow",
"Acknowledgement of the message received %1"
Expand Down Expand Up @@ -208,7 +212,9 @@ def processgetpubkey(data):

myAddress = ''
if requestedAddressVersionNumber <= 3:
requestedHash = data[readPosition:readPosition + 20]
# data may be a memoryview; convert slices to bytes so they
# are hashable and can be used as dictionary keys.
requestedHash = bytes(data[readPosition:readPosition + 20])
if len(requestedHash) != 20:
return logger.debug(
'The length of the requested hash is not 20 bytes.'
Expand All @@ -220,7 +226,8 @@ def processgetpubkey(data):
if requestedHash in shared.myAddressesByHash:
myAddress = shared.myAddressesByHash[requestedHash]
elif requestedAddressVersionNumber >= 4:
requestedTag = data[readPosition:readPosition + 32]
# data may be a memoryview; convert to bytes for hashability.
requestedTag = bytes(data[readPosition:readPosition + 32])
if len(requestedTag) != 32:
return logger.debug(
'The length of the requested tag is not 32 bytes.'
Expand Down Expand Up @@ -413,7 +420,8 @@ def processpubkey(self, data):
'(within processpubkey) payloadLength less than 350.'
' Sanity check failed.')

tag = data[readPosition:readPosition + 32]
# data may be a memoryview; convert to bytes for hashability.
tag = bytes(data[readPosition:readPosition + 32])
if tag not in state.neededPubkeys:
return logger.info(
'We don\'t need this v4 pubkey. We didn\'t ask for it.')
Expand Down Expand Up @@ -807,7 +815,8 @@ def processbroadcast(self, data):
' v4 broadcast: %s seconds.',
time.time() - messageProcessingStartTime)
elif broadcastVersion == 5:
embeddedTag = data[readPosition:readPosition + 32]
# data may be a memoryview; convert to bytes for hashability.
embeddedTag = bytes(data[readPosition:readPosition + 32])
readPosition += 32
if embeddedTag not in shared.MyECSubscriptionCryptorObjects:
logger.debug('We\'re not interested in this broadcast.')
Expand Down
6 changes: 3 additions & 3 deletions src/network/bmproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ def bm_command_object(self):
try:
self.object.checkObjectByType()
objectProcessorQueue.put((
self.object.objectType, memoryview(self.object.data)))
self.object.objectType, bytes(self.object.data)))
except BMObjectInvalidError:
self.stopDownloadingObject(self.object.inventoryHash, True)
else:
Expand All @@ -457,8 +457,8 @@ def bm_command_object(self):

state.Inventory[self.object.inventoryHash] = (
self.object.objectType, self.object.streamNumber,
memoryview(self.payload[objectOffset:]), self.object.expiresTime,
memoryview(self.object.tag)
bytes(self.payload[objectOffset:]), self.object.expiresTime,
bytes(self.object.tag)
)
self.handleReceivedObject(
self.object.streamNumber, self.object.inventoryHash)
Expand Down
4 changes: 2 additions & 2 deletions src/storage/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ def flush(self):
sqlite3.Binary(objectHash),
value.type,
value.stream,
sqlite3.Binary(bytes(value.payload)),
sqlite3.Binary(value.payload),
value.expires,
sqlite3.Binary(bytes(value.tag)))
sqlite3.Binary(value.tag))
self._inventory.clear()

def clean(self):
Expand Down
66 changes: 23 additions & 43 deletions src/tests/test_inventory_flush.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# pylint: disable=import-outside-toplevel

import os
import struct
import tempfile
import threading
import time
Expand Down Expand Up @@ -84,70 +83,51 @@ def _make_hash(seed):
"""Return a 32-byte hash derived from *seed*."""
return (b'\x00' * 31 + bytes([seed & 0xFF]))[-32:]

def _flush_and_check(self, obj_hash):
def _flush_and_check(self, obj_hash, expected_payload=None):
"""
Flush the inventory to the database, clear the _objects lookup
cache so that __contains__ is forced to hit sqlite, then verify
the hash is found via the normal inventory API.
Flush the inventory to the database, clear both in-memory
caches so that __contains__ and __getitem__ are forced to
hit sqlite, then verify the hash is found and (optionally)
that the payload content survived the round-trip.
"""
self.inventory.flush()
self.inventory._objects.clear()
self.assertIn(obj_hash, self.inventory)
if expected_payload is not None:
value = self.inventory[obj_hash]
self.assertEqual(
bytes(value.payload), expected_payload,
"Payload content corrupted after flush")

# -- test cases -------------------------------------------------------

def test_flush_with_bytes_payload(self):
"""Baseline: payload and tag are plain bytes."""
def test_flush_payload_roundtrip(self):
"""Payload content must survive the flush round-trip."""
h = self._make_hash(1)
payload = b'\x80\x01' + os.urandom(64)
self.inventory[h] = (
2, 1, b'\x80\x01' + os.urandom(64),
2, 1, payload,
int(time.time()) + 3600, b'\xff' * 32)
self._flush_and_check(h)

def test_flush_with_memoryview_payload(self):
"""
Reproduce the production crash: payload and tag as memoryview
cause 'Error binding parameter 3 - probably unsupported type.'
"""
h = self._make_hash(2)
self.inventory[h] = (
2, 1, memoryview(b'\x80\x02' + os.urandom(64)),
int(time.time()) + 3600, memoryview(b'\xee' * 32))
self._flush_and_check(h)

def test_flush_with_bytearray_payload(self):
"""bytearray is another bytes-like type that could trip sqlite3."""
h = self._make_hash(3)
self.inventory[h] = (
2, 1, bytearray(b'\x80\x03' + os.urandom(64)),
int(time.time()) + 3600, bytearray(b'\xdd' * 32))
self._flush_and_check(h)
self._flush_and_check(h, payload)

def test_flush_with_empty_tag(self):
"""Empty tag (b'') must not break the INSERT."""
h = self._make_hash(4)
h = self._make_hash(2)
payload = b'\x80\x02' + os.urandom(64)
self.inventory[h] = (
2, 1, b'\x80\x04' + os.urandom(64),
2, 1, payload,
int(time.time()) + 3600, b'')
self._flush_and_check(h)
self._flush_and_check(h, payload)

# pylint: disable=redefined-variable-type
def test_flush_multiple_mixed_types(self):
"""Flush a batch of items with mixed blob types."""
def test_flush_multiple_items(self):
"""Flush a batch and verify every row arrives."""
count = 20
hashes = [self._make_hash(0x10 + i) for i in range(count)]
expires = int(time.time()) + 3600

for i, h in enumerate(hashes):
payload = struct.pack('>I', i) + os.urandom(60)
tag = struct.pack('>I', i) + b'\x00' * 28
if i % 3 == 0:
payload = memoryview(payload)
tag = memoryview(tag)
elif i % 3 == 1:
payload = bytearray(payload)
tag = bytearray(tag)
self.inventory[h] = (2, 1, payload, expires, tag)
self.inventory[h] = (
2, 1, os.urandom(64), expires, b'\x00' * 32)

self.inventory.flush()
self.inventory._objects.clear()
Expand Down
Loading