improve message error handling (#286)

* message envelope sizes and protocol doc corrections

* change error handling in message delivery loop
This commit is contained in:
Evgeny Poberezkin
2022-01-12 10:36:22 +00:00
committed by GitHub
parent d1eba1ef2f
commit bfa4911217
6 changed files with 65 additions and 58 deletions
+30 -27
View File
@@ -566,15 +566,15 @@ This command is sent to the server by the sender both to confirm the queue after
```abnf
send = %s"SEND " smpEncMessage
smpEncMessage = smpPubHeader sentMsgBody ; message up to 16078 bytes
smpEncMessage = smpPubHeader sentMsgBody ; message up to 16088 bytes
smpPubHeader = smpClientVersion ("1" senderPublicDhKey / "0")
smpClientVersion = word16
senderPublicDhKey = length x509encoded
; sender's Curve25519 public key to agree DH secret for E2E encryption in this queue
; it is only sent in confirmation message
x509encoded = <binary X509 key encoding>
sentMsgBody = 16030*16030 OCTET
; E2E-encrypted smpClientMessage padded to 16030 bytes before encryption
sentMsgBody = 16032*16032 OCTET
; E2E-encrypted smpClientMessage padded to 16032 bytes before encryption
word16 = 2*2 OCTET
```
@@ -593,14 +593,14 @@ Until the queue is secured, the server should accept any number of unsigned mess
The body should be encrypted with the recipient's "public" key (`EK`); once decrypted it must have this format:
```abnf
sentMsgBody = <encrypted padded(smpClientMessage, 16030)>
sentMsgBody = <encrypted padded(smpClientMessage, 16032)>
smpClientMessage = smpPrivHeader clientMsgBody
smpPrivHeader = emptyHeader / smpConfirmationHeader
emptyHeader = " "
smpConfirmationHeader = %s"K" senderKey
senderKey = length x509encoded
; the sender's Ed25519 or Ed448 public key to sign SEND commands for this queue
clientMsgBody = *OCTET ; up to 16012 in case of emptyHeader
clientMsgBody = *OCTET ; up to 16016 in case of emptyHeader
```
`clientHeader` in the initial unsigned message is used to transmit sender's server key and can be used in the future revisions of SMP protocol for other purposes.
@@ -610,16 +610,16 @@ SMP transmission structure for sent messages:
```
------- transmission (= 16384 bytes)
2 | originalLength
286- | signature sessionId corrId queueId %s"SEND" SP (1+114 + 1+32? + 1+32 + 1+24 + 4+1 = 210)
....... smpEncMessage (= 16078 bytes = 16384 - 306 bytes)
276- | signature sessionId corrId queueId %s"SEND" SP (1+114 + 1+32? + 1+32 + 1+24 + 4+1 = 210)
....... smpEncMessage (= 16088 bytes = 16384 - 296 bytes)
8- | smpPubHeader (for messages it is only version and '0' to mean "no DH key" = 3 bytes)
24 | nonce for smpClientMessage
16 | auth tag for smpClientMessage
------- smpClientMessage (E2E encrypted, = 16030 bytes = 16078 - 48)
------- smpClientMessage (E2E encrypted, = 16032 bytes = 16088 - 48)
2 | originalLength
8- | smpPrivHeader
12- | smpPrivHeader
.......
| clientMsgBody (<= 16020 bytes = 16030 - 10)
| clientMsgBody (<= 16016 bytes = 16032 - 14)
.......
0+ | smpClientMessage pad
------- smpClientMessage end
@@ -634,31 +634,34 @@ SMP transmission structure for received messages:
```
------- transmission (= 16384 bytes)
2 | originalLength
286- | signature sessionId corrId queueId %s"MSG" SP msgId timestamp (1+114 + 1+32? + 1+32 + 1+24 + 3+1 + 24+1 + 8 = 243)
276- | signature sessionId corrId queueId %s"MSG" SP msgId timestamp (1+114 + 1+32? + 1+32 + 1+24 + 3+1 + 24+1 + 8 = 243)
16 | auth tag (msgId is used as nonce)
------- serverEncryptedMsg (= 16080 bytes = 16384 - 304 bytes)
------- serverEncryptedMsg (= 16090 bytes = 16384 - 294 bytes)
2 | originalLength
....... smpEncMessage (= 16078 bytes = 16080 - 2 bytes)
8- | smpPubHeader (empty header for the message)
....... smpEncMessage (= 16088 bytes = 16090 - 2 bytes)
16- | smpPubHeader (empty header for the message)
24 | nonce for smpClientMessage
16 | auth tag for smpClientMessage
------- smpClientMessage (E2E encrypted, = 16030 bytes = 16078 - 48 bytes)
------- smpClientMessage (E2E encrypted, = 16032 bytes = 16088 - 56 bytes)
2 | originalLength
8- | smpPrivHeader (empty header for the message)
....... clientMsgBody (<= 16020 bytes = 16030 - 10)
16- | smpPrivHeader (empty header for the message)
....... clientMsgBody (<= 16016 bytes = 16032 - 16)
-- TODO move internal structure (below) to agent protocol
8- | agentPublicHeader (the size is for user messages post handshake, without E2E X3DH keys - it is version and 'M' for the messages - 3 bytes in total)
....... E2E double-ratchet encrypted (= 16012 bytes = 16020 - 8)
88 | double-ratchet header (actual size is 69 bytes, the rest is reserved)
16 | double-ratchet header auth tag
16 | double-ratchet header iv
20- | agentPublicHeader (the size is for user messages post handshake, without E2E X3DH keys - it is version and 'M' for the messages - 3 bytes in total)
....... E2E double-ratchet encrypted (<= 15996 bytes = 16016 - 20)
1 | encoded double ratchet header length (it is 123 now)
123 | encoded double ratchet header, including:
2 | version
16 | double-ratchet header iv
16 | double-ratchet header auth tag
1+88 | double-ratchet header (actual size is 69 bytes, the rest is reserved)
16 | message auth tag (IV generated from chain ratchet)
------- encrypted agent message (= 15876 bytes = 16012 - 136)
------- encrypted agent message (= 15856 bytes = 15996 - 140)
2 | originalLength
84 (41) | agentHeader (8 + 1+32)
64- | agentHeader (the actual size is 41 = 8 + 1+32)
2 | %s"MM"
.......
| application message (<= 15788 bytes = 15876 - 88)
| application message (<= 15788 bytes = 15856 - 68)
.......
0+ | encrypted agent message pad
------- encrypted agent message end
@@ -706,7 +709,7 @@ The server must deliver messages to all subscribed simplex queues on the current
```abnf
message = %s"MSG " msgId SP timestamp SP encryptedMsgBody
encryptedMsgBody = <encrypt paddedSentMsgBody> ; server-encrypted padded sent msgBody
paddedSentMsgBody = <padded(sentMsgBody, maxMessageLength + 2)> ; maxMessageLength = 16078
paddedSentMsgBody = <padded(sentMsgBody, maxMessageLength + 2)> ; maxMessageLength = 16088
msgId = length 24*24OCTET
timestamp = 8*8OCTET
```
@@ -759,7 +762,7 @@ No further messages should be delivered to unsubscribed transport connection.
- transmission has no required queue ID (`NO_QUEUE`)
- authentication error (`AUTH`) - incorrect signature, unknown (or suspended) queue, sender's ID is used in place of recipient's and vice versa, and some other cases (see [Send message](#send-message) command).
- message queue quota exceeded error (`QUOTA`) - too many messages were sent to the message queue. Further messages can only be sent after the recipient retrieves the messages.
- sent message is too large (> 16078) to be delivered (`LARGE_MSG`).
- sent message is too large (> 16088) to be delivered (`LARGE_MSG`).
- internal server error (`INTERNAL`).
The syntax for error responses:
+16 -12
View File
@@ -458,18 +458,20 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} connId sq = do
withStore (\st -> E.try $ getPendingMsgData st connId msgId) >>= \case
Left (e :: E.SomeException) ->
notify $ MERR mId (INTERNAL $ show e)
Right (rq_, (msgType, msgBody)) -> do
withRetryInterval ri $ \loop -> do
Right (rq_, (msgType, msgBody)) ->
withRetryInterval ri $ \loop ->
tryError (sendAgentMessage c sq msgBody) >>= \case
Left e -> case e of
SMP SMP.QUOTA -> loop
SMP SMP.AUTH -> case msgType of
HELLO_ -> loop
REPLY_ -> notify $ ERR e
A_MSG_ -> notify $ MERR mId e
SMP {} -> notify $ MERR mId e
CMD {} -> notify $ MERR mId e
_ -> loop
Left e -> do
case e of
SMP SMP.QUOTA -> loop
SMP SMP.AUTH -> case msgType of
HELLO_ -> loop
REPLY_ -> notify (ERR e) >> delMsg msgId
A_MSG_ -> notify (MERR mId e) >> delMsg msgId
SMP (SMP.CMD _) -> notify (MERR mId e) >> delMsg msgId
SMP SMP.LARGE_MSG -> notify (MERR mId e) >> delMsg msgId
SMP {} -> notify (MERR mId e) >> loop
_ -> loop
Right () -> do
case msgType of
HELLO_ -> do
@@ -483,8 +485,10 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} connId sq = do
_ -> createReplyQueue c connId sq
A_MSG_ -> notify $ SENT mId
_ -> pure ()
withStore $ \st -> deleteMsg st connId msgId
delMsg msgId
where
delMsg :: InternalId -> m ()
delMsg msgId = withStore $ \st -> deleteMsg st connId msgId
notify :: ACommand 'Agent -> m ()
notify cmd = atomically $ writeTBQueue subQ ("", connId, cmd)
+4 -4
View File
@@ -161,7 +161,7 @@ e2eEncConnInfoLength :: Int
e2eEncConnInfoLength = 14848
e2eEncUserMsgLength :: Int
e2eEncUserMsgLength = 15876
e2eEncUserMsgLength = 15856
-- | Raw (unparsed) SMP agent protocol transmission.
type ARawTransmission = (ByteString, ByteString, ByteString)
@@ -304,9 +304,9 @@ data AgentMsgEnvelope
encAgentMessage :: ByteString
}
| AgentInvitation -- the connInfo in contactInvite is only encrypted with per-queue E2E, not with double ratchet,
{ agentVersion :: !Version,
connReq :: !(ConnectionRequestUri 'CMInvitation),
connInfo :: !ByteString -- this message is only encrypted with per-queue E2E, not with double ratchet,
{ agentVersion :: Version,
connReq :: (ConnectionRequestUri 'CMInvitation),
connInfo :: ByteString -- this message is only encrypted with per-queue E2E, not with double ratchet,
}
deriving (Show)
+8 -8
View File
@@ -285,22 +285,22 @@ instance AlgorithmI a => Encoding (MsgHeader a) where
data EncMessageHeader = EncMessageHeader
{ ehVersion :: Version,
ehBody :: ByteString,
ehIV :: IV,
ehAuthTag :: AuthTag,
ehIV :: IV
ehBody :: ByteString
}
instance Encoding EncMessageHeader where
smpEncode EncMessageHeader {ehVersion, ehBody, ehAuthTag, ehIV} =
smpEncode (ehVersion, ehBody, ehAuthTag, ehIV)
smpEncode EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody} =
smpEncode (ehVersion, ehIV, ehAuthTag, ehBody)
smpP = do
(ehVersion, ehBody, ehAuthTag, ehIV) <- smpP
pure EncMessageHeader {ehVersion, ehBody, ehAuthTag, ehIV}
(ehVersion, ehIV, ehAuthTag, ehBody) <- smpP
pure EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody}
data EncRatchetMessage = EncRatchetMessage
{ emHeader :: ByteString,
emBody :: ByteString,
emAuthTag :: AuthTag
emAuthTag :: AuthTag,
emBody :: ByteString
}
instance Encoding EncRatchetMessage where
+4 -4
View File
@@ -108,10 +108,6 @@ instance Encoding SystemTime where
smpEncode = smpEncode . systemSeconds
smpP = MkSystemTime <$> smpP <*> pure 0
instance (Encoding a, Encoding b) => Encoding (a, b) where
smpEncode (a, b) = smpEncode a <> smpEncode b
smpP = (,) <$> smpP <*> smpP
-- lists encode/parse as a sequence of items prefixed with list length (as 1 byte)
smpEncodeList :: Encoding a => [a] -> ByteString
smpEncodeList xs = B.cons (lenEncode $ length xs) . B.concat $ map smpEncode xs
@@ -130,6 +126,10 @@ instance Encoding a => Encoding (L.NonEmpty a) where
0 -> fail "empty list"
n -> L.fromList <$> A.count n smpP
instance (Encoding a, Encoding b) => Encoding (a, b) where
smpEncode (a, b) = smpEncode a <> smpEncode b
smpP = (,) <$> smpP <*> smpP
instance (Encoding a, Encoding b, Encoding c) => Encoding (a, b, c) where
smpEncode (a, b, c) = smpEncode a <> smpEncode b <> smpEncode c
smpP = (,,) <$> smpP <*> smpP <*> smpP
+3 -3
View File
@@ -118,14 +118,14 @@ smpClientVRange :: VersionRange
smpClientVRange = mkVersionRange 1 smpClientVersion
maxMessageLength :: Int
maxMessageLength = 16078
maxMessageLength = 16088
-- it is shorter to allow per-queue e2e encryption DH key in the "public" header
e2eEncConfirmationLength :: Int
e2eEncConfirmationLength = 15942
e2eEncConfirmationLength = 15936
e2eEncMessageLength :: Int
e2eEncMessageLength = 16030
e2eEncMessageLength = 16032
-- | SMP protocol clients
data Party = Recipient | Sender | Notifier