Skip to content

Commit 429eb4b

Browse files
committed
Fixes
- Qt six import was broken - variable in Qt support interface rename incomplete - objectProcessor wasn't handling memoryview correctly - inventory data corruption fix
1 parent 8a3f1e5 commit 429eb4b

5 files changed

Lines changed: 53 additions & 24 deletions

File tree

src/bitmessageqt/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@
5656
import bitmessage_icons_rc # noqa:F401 pylint: disable=unused-import
5757
import helper_sent
5858

59-
from six.moves import iteritems, itervalues, range as xrange
60-
from six import text_type
59+
from six import iteritems, itervalues, text_type
6160

6261
try:
6362
from plugins.plugin import get_plugin, get_plugins

src/bitmessageqt/support.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def createSupportMessage(myapp):
141141
if paths.frozen:
142142
frozen = paths.frozen
143143
portablemode = "True" if state.appdata == paths.lookupExeFolder() else "False"
144-
cpow = "True" if proofofwork.bmpow else "False"
144+
cpow = "True" if proofofwork.BMPOW else "False"
145145
openclpow = str(
146146
config.safeGet('bitmessagesettings', 'opencl')
147147
) if openclEnabled() else "None"

src/class_objectProcessor.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,19 @@ def checkackdata(data):
141141
# bypass nonce and time, retain object type/version/stream + body
142142
readPosition = 16
143143

144-
if data[readPosition:] in state.ackdataForWhichImWatching:
144+
# data may be a memoryview, which is not hashable and thus
145+
# cannot be used as a dictionary key; convert the slice to bytes.
146+
ackcheckdata = bytes(data[readPosition:])
147+
148+
if ackcheckdata in state.ackdataForWhichImWatching:
145149
logger.info('This object is an acknowledgement bound for me.')
146-
del state.ackdataForWhichImWatching[data[readPosition:]]
150+
del state.ackdataForWhichImWatching[ackcheckdata]
147151
sqlExecute(
148152
"UPDATE sent SET status='ackreceived', lastactiontime=?"
149-
" WHERE ackdata=?", int(time.time()), data[readPosition:])
153+
" WHERE ackdata=?", int(time.time()), ackcheckdata)
150154
queues.UISignalQueue.put((
151155
'updateSentItemStatusByAckdata', (
152-
data[readPosition:],
156+
ackcheckdata,
153157
_translate(
154158
"MainWindow",
155159
"Acknowledgement of the message received %1"
@@ -208,7 +212,9 @@ def processgetpubkey(data):
208212

209213
myAddress = ''
210214
if requestedAddressVersionNumber <= 3:
211-
requestedHash = data[readPosition:readPosition + 20]
215+
# data may be a memoryview; convert slices to bytes so they
216+
# are hashable and can be used as dictionary keys.
217+
requestedHash = bytes(data[readPosition:readPosition + 20])
212218
if len(requestedHash) != 20:
213219
return logger.debug(
214220
'The length of the requested hash is not 20 bytes.'
@@ -220,7 +226,8 @@ def processgetpubkey(data):
220226
if requestedHash in shared.myAddressesByHash:
221227
myAddress = shared.myAddressesByHash[requestedHash]
222228
elif requestedAddressVersionNumber >= 4:
223-
requestedTag = data[readPosition:readPosition + 32]
229+
# data may be a memoryview; convert to bytes for hashability.
230+
requestedTag = bytes(data[readPosition:readPosition + 32])
224231
if len(requestedTag) != 32:
225232
return logger.debug(
226233
'The length of the requested tag is not 32 bytes.'
@@ -413,7 +420,8 @@ def processpubkey(self, data):
413420
'(within processpubkey) payloadLength less than 350.'
414421
' Sanity check failed.')
415422

416-
tag = data[readPosition:readPosition + 32]
423+
# data may be a memoryview; convert to bytes for hashability.
424+
tag = bytes(data[readPosition:readPosition + 32])
417425
if tag not in state.neededPubkeys:
418426
return logger.info(
419427
'We don\'t need this v4 pubkey. We didn\'t ask for it.')
@@ -807,7 +815,8 @@ def processbroadcast(self, data):
807815
' v4 broadcast: %s seconds.',
808816
time.time() - messageProcessingStartTime)
809817
elif broadcastVersion == 5:
810-
embeddedTag = data[readPosition:readPosition + 32]
818+
# data may be a memoryview; convert to bytes for hashability.
819+
embeddedTag = bytes(data[readPosition:readPosition + 32])
811820
readPosition += 32
812821
if embeddedTag not in shared.MyECSubscriptionCryptorObjects:
813822
logger.debug('We\'re not interested in this broadcast.')

src/storage/sqlite.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@
99
from .storage import InventoryItem, InventoryStorage
1010

1111

12+
def _to_blob(data):
13+
"""Coerce buffer-like *data* for sqlite3 BLOB binding.
14+
15+
In Python 2, sqlite3.Binary is buffer() which rejects memoryview;
16+
bytes(memoryview) silently returns the repr string. Use tobytes()
17+
to get the actual content.
18+
"""
19+
if isinstance(data, memoryview):
20+
data = data.tobytes()
21+
return sqlite3.Binary(data)
22+
23+
1224
class SqliteInventory(InventoryStorage):
1325
"""Inventory using SQLite"""
1426
def __init__(self):
@@ -109,12 +121,12 @@ def flush(self):
109121
for objectHash, value in self._inventory.items():
110122
sql.execute(
111123
'INSERT INTO inventory VALUES (?, ?, ?, ?, ?, ?)',
112-
sqlite3.Binary(objectHash),
124+
_to_blob(objectHash),
113125
value.type,
114126
value.stream,
115-
sqlite3.Binary(bytes(value.payload)),
127+
_to_blob(value.payload),
116128
value.expires,
117-
sqlite3.Binary(bytes(value.tag)))
129+
_to_blob(value.tag))
118130
self._inventory.clear()
119131

120132
def clean(self):

src/tests/test_inventory_flush.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,44 +84,53 @@ def _make_hash(seed):
8484
"""Return a 32-byte hash derived from *seed*."""
8585
return (b'\x00' * 31 + bytes([seed & 0xFF]))[-32:]
8686

87-
def _flush_and_check(self, obj_hash):
87+
def _flush_and_check(self, obj_hash, expected_payload=None):
8888
"""
89-
Flush the inventory to the database, clear the _objects lookup
90-
cache so that __contains__ is forced to hit sqlite, then verify
91-
the hash is found via the normal inventory API.
89+
Flush the inventory to the database, clear both in-memory
90+
caches so that __contains__ and __getitem__ are forced to
91+
hit sqlite, then verify the hash is found and (optionally)
92+
that the payload content survived the round-trip.
9293
"""
9394
self.inventory.flush()
9495
self.inventory._objects.clear()
9596
self.assertIn(obj_hash, self.inventory)
97+
if expected_payload is not None:
98+
value = self.inventory[obj_hash]
99+
self.assertEqual(
100+
bytes(value.payload), expected_payload,
101+
"Payload content corrupted after flush")
96102

97103
# -- test cases -------------------------------------------------------
98104

99105
def test_flush_with_bytes_payload(self):
100106
"""Baseline: payload and tag are plain bytes."""
101107
h = self._make_hash(1)
108+
payload = b'\x80\x01' + os.urandom(64)
102109
self.inventory[h] = (
103-
2, 1, b'\x80\x01' + os.urandom(64),
110+
2, 1, payload,
104111
int(time.time()) + 3600, b'\xff' * 32)
105-
self._flush_and_check(h)
112+
self._flush_and_check(h, payload)
106113

107114
def test_flush_with_memoryview_payload(self):
108115
"""
109116
Reproduce the production crash: payload and tag as memoryview
110117
cause 'Error binding parameter 3 - probably unsupported type.'
111118
"""
112119
h = self._make_hash(2)
120+
payload = b'\x80\x02' + os.urandom(64)
113121
self.inventory[h] = (
114-
2, 1, memoryview(b'\x80\x02' + os.urandom(64)),
122+
2, 1, memoryview(payload),
115123
int(time.time()) + 3600, memoryview(b'\xee' * 32))
116-
self._flush_and_check(h)
124+
self._flush_and_check(h, payload)
117125

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

126135
def test_flush_with_empty_tag(self):
127136
"""Empty tag (b'') must not break the INSERT."""

0 commit comments

Comments
 (0)