Skip to content

Commit 6ebbbe2

Browse files
committed
Recover from no session/ no prekey exceptions
Fixes #939 Echo Client crash new number Fixes #961 Exception: No such signedprekeyrecord! Fixes #869 No such signedprekeyrecord! 1040 Fixes #593 axolotl: NoSessionException Fixes #683 Message decrypt error
1 parent 5c745cf commit 6ebbbe2

File tree

3 files changed

+105
-8
lines changed

3 files changed

+105
-8
lines changed

yowsup/layers/axolotl/layer.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
from yowsup.structs import ProtocolTreeNode
1616
from .protocolentities import GetKeysIqProtocolEntity, ResultGetKeysIqProtocolEntity
1717
from axolotl.util.hexutil import HexUtil
18-
from yowsup.env import CURRENT_ENV
1918
from axolotl.invalidmessageexception import InvalidMessageException
2019
from .protocolentities import EncryptNotification
2120
from yowsup.layers.protocol_acks.protocolentities import OutgoingAckProtocolEntity
21+
from axolotl.invalidkeyidexception import InvalidKeyIdException
22+
from axolotl.nosessionexception import NoSessionException
23+
from .protocolentities.receipt_outgoing_retry import RetryOutgoingReceiptProtocolEntity
2224
import binascii
2325
import sys
2426

@@ -40,6 +42,7 @@ def __init__(self):
4042

4143
self.sessionCiphers = {}
4244
self.pendingMessages = {}
45+
self.pendingIncomingMessages = {}
4346
self.skipEncJids = []
4447

4548
def __str__(self):
@@ -101,6 +104,10 @@ def receive(self, protocolTreeNode):
101104
elif protocolTreeNode.tag == "notification" and protocolTreeNode["type"] == "encrypt":
102105
self.onEncryptNotification(protocolTreeNode)
103106
return
107+
elif protocolTreeNode.tag == "receipt" and protocolTreeNode["type"] == "retry":
108+
# should bring up that message, resend it, but in upper layer?
109+
# as it might have to be fetched from a persistent storage
110+
pass
104111
self.toUpper(protocolTreeNode)
105112
######
106113

@@ -130,6 +137,13 @@ def processPendingMessages(self, jid):
130137

131138
del self.pendingMessages[jid]
132139

140+
def processPendingIncomingMessages(self, jid):
141+
if jid in self.pendingIncomingMessages:
142+
for messageNode in self.pendingIncomingMessages[jid]:
143+
self.onMessage(messageNode)
144+
145+
del self.pendingIncomingMessages[jid]
146+
133147
#### handling message types
134148

135149
def handlePlaintextNode(self, node):
@@ -143,7 +157,7 @@ def handlePlaintextNode(self, node):
143157
self.pendingMessages[node["to"]] = []
144158
self.pendingMessages[node["to"]].append(node)
145159

146-
self._sendIq(entity, self.onGetKeysResult, self.onGetKeysError)
160+
self._sendIq(entity, lambda a, b: self.onGetKeysResult(a, b, self.processPendingMessages), self.onGetKeysError)
147161
else:
148162

149163
sessionCipher = self.getSessionCipher(recipient_id)
@@ -172,9 +186,29 @@ def handleEncMessage(self, node):
172186
self.handlePreKeyWhisperMessage(node)
173187
else:
174188
self.handleWhisperMessage(node)
175-
except InvalidMessageException:
176-
logger.error("Invalid message from %s!! Your axololtl database data might be inconsistent with WhatsApp, or with what that contact has" % node["from"])
177-
sys.exit(1)
189+
except InvalidMessageException as e:
190+
# logger.error("Invalid message from %s!! Your axololtl database data might be inconsistent with WhatsApp, or with what that contact has" % node["from"])
191+
# sys.exit(1)
192+
logger.error(e)
193+
retry = RetryOutgoingReceiptProtocolEntity.fromMesageNode(node)
194+
retry.setRegData(self.store.getLocalRegistrationId())
195+
self.toLower(retry.toProtocolTreeNode())
196+
except InvalidKeyIdException as e:
197+
logger.error(e)
198+
retry = RetryOutgoingReceiptProtocolEntity.fromMesageNode(node)
199+
retry.setRegData(self.store.getLocalRegistrationId())
200+
self.toLower(retry.toProtocolTreeNode())
201+
except NoSessionException as e:
202+
logger.error(e)
203+
entity = GetKeysIqProtocolEntity([node["from"]])
204+
if node["from"] not in self.pendingIncomingMessages:
205+
self.pendingIncomingMessages[node["from"]] = []
206+
self.pendingIncomingMessages[node["from"]].append(node)
207+
208+
self._sendIq(entity, lambda a, b: self.onGetKeysResult(a, b, self.processPendingIncomingMessages), self.onGetKeysError)
209+
210+
211+
178212
def handlePreKeyWhisperMessage(self, node):
179213
pkMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node)
180214

@@ -243,7 +277,7 @@ def persistKeys(self, registrationId, identityKeyPair, preKeys, signedPreKey, fr
243277
def onSentKeysError(self, errorNode, keysEntity):
244278
raise Exception("Sent keys were not accepted")
245279

246-
def onGetKeysResult(self, resultNode, getKeysEntity):
280+
def onGetKeysResult(self, resultNode, getKeysEntity, processPendingFn):
247281
entity = ResultGetKeysIqProtocolEntity.fromProtocolTreeNode(resultNode)
248282

249283
resultJids = entity.getJids()
@@ -261,7 +295,7 @@ def onGetKeysResult(self, resultNode, getKeysEntity):
261295
self.store, recipient_id, 1)
262296
sessionBuilder.processPreKeyBundle(preKeyBundle)
263297

264-
self.processPendingMessages(jid)
298+
processPendingFn(jid)
265299

266300
def onGetKeysError(self, errorNode, getKeysEntity):
267301
pass
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from yowsup.structs import ProtocolEntity, ProtocolTreeNode
2+
from yowsup.layers.protocol_receipts.protocolentities import OutgoingReceiptProtocolEntity
3+
from yowsup.layers.axolotl.protocolentities.iq_keys_get_result import ResultGetKeysIqProtocolEntity
4+
class RetryOutgoingReceiptProtocolEntity(OutgoingReceiptProtocolEntity):
5+
6+
'''
7+
<receipt type="retry" to="xxxxxxxxxxx@s.whatsapp.net" id="1415389947-12" t="1432833777">
8+
<retry count="1" t="1432833266" id="1415389947-12" v="1">
9+
</retry>
10+
<registration>
11+
HEX:xxxxxxxxx
12+
</registration>
13+
</receipt>
14+
15+
'''
16+
17+
def __init__(self, _id, to, t, v = "1", count = "1",regData = ""):
18+
super(RetryOutgoingReceiptProtocolEntity, self).__init__(_id,to)
19+
self.setRetryData(t,v,count,regData)
20+
21+
def setRetryData(self, t,v,count,regData):
22+
self.t = int(t)
23+
self.v = int(v)
24+
self.count = int(count)
25+
self.regData = regData
26+
27+
def setRegData(self,regData):
28+
'''
29+
In axolotl layer:
30+
regData = self.store.getLocalRegistrationId()
31+
'''
32+
self.regData = ResultGetKeysIqProtocolEntity._intToBytes(regData)
33+
34+
def toProtocolTreeNode(self):
35+
node = super(RetryOutgoingReceiptProtocolEntity, self).toProtocolTreeNode()
36+
node.setAttribute("type", "retry")
37+
retry = ProtocolTreeNode("retry", {"count": str(self.count),"t":str(self.t),"id":self.getId(),"v":str(self.v)})
38+
node.addChild(retry)
39+
registration = ProtocolTreeNode("registration",data=self.regData)
40+
node.addChild(registration)
41+
return node
42+
43+
def __str__(self):
44+
out = super(RetryOutgoingReceiptProtocolEntity, self).__str__()
45+
return out
46+
47+
@staticmethod
48+
def fromProtocolTreeNode(node):
49+
entity = OutgoingReceiptProtocolEntity.fromProtocolTreeNode(node)
50+
entity.__class__ = RetryOutgoingReceiptProtocolEntity
51+
retryNode = node.getChild("retry")
52+
entity.setRetryData(retryNode["t"], retryNode["v"], retryNode["count"], node.getChild("registration").data)
53+
54+
55+
@staticmethod
56+
def fromMesageNode(MessageNodeToBeRetried):
57+
return RetryOutgoingReceiptProtocolEntity(
58+
MessageNodeToBeRetried.getAttributeValue("id"),
59+
MessageNodeToBeRetried.getAttributeValue("from"),
60+
MessageNodeToBeRetried.getAttributeValue("t"),
61+
MessageNodeToBeRetried.getChild("enc").getAttributeValue("v")
62+
)

yowsup/layers/axolotl/store/sqlite/litesignedprekeystore.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from axolotl.state.signedprekeystore import SignedPreKeyStore
22
from axolotl.state.signedprekeyrecord import SignedPreKeyRecord
3+
from axolotl.invalidkeyidexception import InvalidKeyIdException
34
class LiteSignedPreKeyStore(SignedPreKeyStore):
45
def __init__(self, dbConn):
56
"""
@@ -18,7 +19,7 @@ def loadSignedPreKey(self, signedPreKeyId):
1819

1920
result = cursor.fetchone()
2021
if not result:
21-
raise Exception("No such signedprekeyrecord! %s " % signedPreKeyId)
22+
raise InvalidKeyIdException("No such signedprekeyrecord! %s " % signedPreKeyId)
2223

2324
return SignedPreKeyRecord(serialized=result[0])
2425

0 commit comments

Comments
 (0)