@@ -1195,6 +1195,25 @@ runCommandProcessing c@AgentClient {subQ} server_ Worker {doWork} = do
11951195                    notify .  SWITCH  QDRcv  SPSecured  $  connectionStats conn'
11961196                  _ ->  internalErr " ICQSecure: no switching queue found" 
11971197              _ ->  internalErr " ICQSecure: queue address not found in connection" 
1198+         ICQSndSecure  sId -> 
1199+           withServer $  \ srv ->  tryWithLock " ICQSndSecure"   .  withDuplexConn $  \ (DuplexConnection  cData rqs sqs) -> 
1200+             case  find (sameQueue (srv, sId)) sqs of 
1201+               Just  sq'@ SndQueue  {server, sndId, sndSecure, status, smpClientVersion, e2ePubKey =  Just  dhPublicKey, dbReplaceQueueId =  Just  replaceQId} -> 
1202+                 case  find ((replaceQId == ) .  dbQId) sqs of 
1203+                   Just  sq1 ->  when (status ==  New ) $  do 
1204+                     secureSndQueue c sq'
1205+                     withStore' c $  \ db ->  setSndQueueStatus db sq' Secured 
1206+                     let  sq'' =  (sq' ::  SndQueue ) {status =  Secured }
1207+                         queueAddress =  SMPQueueAddress  {smpServer =  server, senderId =  sndId, dhPublicKey, sndSecure}
1208+                         qInfo =  SMPQueueInfo  {clientVersion =  smpClientVersion, queueAddress}
1209+                     --  sending QSEC to the new queue only, the old one will be removed if sent successfully
1210+                     void .  enqueueMessages c cData [sq''] SMP. noMsgFlags $  QSEC  [qInfo]
1211+                     sq1' <-  withStore' c $  \ db ->  setSndSwitchStatus db sq1 $  Just  SSSendingQSEC 
1212+                     let  sqs' =  updatedQs sq1' sqs
1213+                         conn' =  DuplexConnection  cData rqs sqs'
1214+                     notify .  SWITCH  QDSnd  SPCompleted  $  connectionStats conn'
1215+                   _ ->  internalErr " ICQSndSecure: no switching queue found" 
1216+               _ ->  internalErr " ICQSndSecure: queue address not found in connection" 
11981217        ICQDelete  rId ->  do 
11991218          withServer $  \ srv ->  tryWithLock " ICQDelete"   .  withDuplexConn $  \ (DuplexConnection  cData rqs sqs) ->  do 
12001219            case  removeQ (srv, rId) rqs of 
@@ -1393,6 +1412,7 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} ConnData {connId} sq@SndQueue {userI
13931412                    AM_QCONT_  ->  notifyDel msgId err
13941413                    AM_QADD_  ->  qError msgId " QADD: AUTH" 
13951414                    AM_QKEY_  ->  qError msgId " QKEY: AUTH" 
1415+                     AM_QSEC_  ->  qError msgId " QKEY: AUTH" 
13961416                    AM_QUSE_  ->  qError msgId " QUSE: AUTH" 
13971417                    AM_QTEST_  ->  qError msgId " QTEST: AUTH" 
13981418                    AM_EREADY_  ->  notifyDel msgId err
@@ -1446,8 +1466,13 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} ConnData {connId} sq@SndQueue {userI
14461466                AM_QKEY_  ->  do 
14471467                  SomeConn  _ conn <-  withStore c (`getConn`  connId)
14481468                  notify .  SWITCH  QDSnd  SPConfirmed  $  connectionStats conn
1469+                 AM_QSEC_  ->  withConnLock c connId " runSmpQueueMsgDelivery AM_QSEC_"   $  completeConnSwitch " QSEC"   SSSendingQSEC 
14491470                AM_QUSE_  ->  pure  () 
1450-                 AM_QTEST_  ->  withConnLock c connId " runSmpQueueMsgDelivery AM_QTEST_"   $  do 
1471+                 AM_QTEST_  ->  withConnLock c connId " runSmpQueueMsgDelivery AM_QTEST_"   $  completeConnSwitch " QTEST"   SSSendingQTEST 
1472+                 AM_EREADY_  ->  pure  () 
1473+               delMsgKeep (msgType ==  AM_A_MSG_ ) msgId
1474+               where 
1475+                 completeConnSwitch msgTag expectedStatus =  do 
14511476                  withStore' c $  \ db ->  setSndQueueStatus db sq Active 
14521477                  SomeConn  _ conn <-  withStore c (`getConn`  connId)
14531478                  case  conn of 
@@ -1459,9 +1484,9 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} ConnData {connId} sq@SndQueue {userI
14591484                        Just  SndQueue  {dbReplaceQueueId =  Just  replacedId, primary} -> 
14601485                          --  second part of this condition is a sanity check because dbReplaceQueueId cannot point to the same queue, see switchConnection'
14611486                          case  removeQP (\ sq' ->  dbQId sq' ==  replacedId &&  not  (sameQueue addr sq')) sqs of 
1462-                             Nothing  ->  internalErr msgId " sent QTEST : queue not found in connection" 
1487+                             Nothing  ->  internalErr msgId $   " sent "   <>  msgTag  <>   " : queue not found in connection" 
14631488                            Just  (sq', sq'' :  sqs') ->  do 
1464-                               checkSQSwchStatus sq' SSSendingQTEST 
1489+                               checkSQSwchStatus sq' expectedStatus 
14651490                              --  remove the delivery from the map to stop the thread when the delivery loop is complete
14661491                              atomically $  TM. delete (qAddress sq') $  smpDeliveryWorkers c
14671492                              withStore' c $  \ db ->  do 
@@ -1471,12 +1496,9 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} ConnData {connId} sq@SndQueue {userI
14711496                              let  sqs'' =  sq'' :|  sqs'
14721497                                  conn' =  DuplexConnection  cData' rqs sqs''
14731498                              notify .  SWITCH  QDSnd  SPCompleted  $  connectionStats conn'
1474-                             _ ->  internalErr msgId " sent QTEST: there is only one queue in connection" 
1475-                         _ ->  internalErr msgId " sent QTEST: queue not in connection or not replacing another queue" 
1476-                     _ ->  internalErr msgId " QTEST sent not in duplex connection" 
1477-                 AM_EREADY_  ->  pure  () 
1478-               delMsgKeep (msgType ==  AM_A_MSG_ ) msgId
1479-               where 
1499+                             _ ->  internalErr msgId $  " sent "   <>  msgTag <>  " : there is only one queue in connection" 
1500+                         _ ->  internalErr msgId $  " sent "   <>  msgTag <>  " : queue not in connection or not replacing another queue" 
1501+                     _ ->  internalErr msgId $  msgTag <>  "  sent not in duplex connection" 
14801502                setStatus status =  do 
14811503                  withStore' c $  \ db ->  do 
14821504                    setSndQueueStatus db sq status
@@ -2249,8 +2271,9 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(_, srv, _), _v, sessId, ts)
22492271                          (DuplexConnection  _ rqs _, Just  replacedId) ->  do 
22502272                            when primary .  withStore' c $  \ db ->  setRcvQueuePrimary db connId rq
22512273                            case  find ((replacedId == ) .  dbQId) rqs of 
2252-                               Just  rq'@ RcvQueue  {server, rcvId} ->  do 
2253-                                 checkRQSwchStatus rq' RSSendingQUSE 
2274+                               Just  rq'@ RcvQueue  {server, rcvId, rcvSwchStatus} ->  do 
2275+                                 unless (rcvSwchStatus ==  Just  RSSendingQUSE  ||  rcvSwchStatus ==  Just  RSSendingQADD ) $ 
2276+                                   switchStatusError rq RSSendingQUSE  rcvSwchStatus
22542277                                void $  withStore' c $  \ db ->  setRcvSwitchStatus db rq' $  Just  RSReceivedMessage 
22552278                                enqueueCommand c " "   connId (Just  server) $  AInternalCommand  $  ICQDelete  rcvId
22562279                              _ ->  notify .  ERR  .  AGENT  $  A_QUEUE  " replaced RcvQueue not found in connection" 
@@ -2271,6 +2294,7 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(_, srv, _), _v, sessId, ts)
22712294                              A_QCONT  addr ->  qDuplexAckDel conn'' " QCONT"   $  continueSending srvMsgId addr
22722295                              QADD  qs ->  qDuplexAckDel conn'' " QADD"   $  qAddMsg srvMsgId qs
22732296                              QKEY  qs ->  qDuplexAckDel conn'' " QKEY"   $  qKeyMsg srvMsgId qs
2297+                               QSEC  qs ->  qDuplexAckDel conn'' " QSEC"   $  qSecMsg srvMsgId qs
22742298                              QUSE  qs ->  qDuplexAckDel conn'' " QUSE"   $  qUseMsg srvMsgId qs
22752299                              --  no action needed for QTEST
22762300                              --  any message in the new queue will mark it active and trigger deletion of the old queue
@@ -2543,14 +2567,20 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(_, srv, _), _v, sessId, ts)
25432567                    let  (delSqs, keepSqs) =  L. partition ((Just  dbQueueId == ) .  dbReplaceQId) sqs
25442568                    case  L. nonEmpty keepSqs of 
25452569                      Just  sqs' ->  do 
2546-                         (sq_@ SndQueue  {sndPublicKey}, dhPublicKey) <-  lift $  newSndQueue userId connId qInfo
2570+                         (sq_@ SndQueue  {sndId,  sndPublicKey, sndSecure  =  sndSecure' }, dhPublicKey) <-  lift $  newSndQueue userId connId qInfo
25472571                        sq2 <-  withStore c $  \ db ->  do 
25482572                          liftIO $  mapM_  (deleteConnSndQueue db connId) delSqs
25492573                          addConnSndQueue db connId (sq_ ::  NewSndQueue ) {primary =  True  , dbReplaceQueueId =  Just  dbQueueId}
25502574                        logServer " <--"   c srv rId $  " MSG <QADD>:"   <>  logSecret srvMsgId <>  "  "   <>  logSecret (senderId queueAddress)
2551-                         let  sqInfo' =  (sqInfo ::  SMPQueueInfo ) {queueAddress =  queueAddress {dhPublicKey}}
2552-                         void .  enqueueMessages c cData' sqs SMP. noMsgFlags $  QKEY  [(sqInfo', sndPublicKey)]
2553-                         sq1 <-  withStore' c $  \ db ->  setSndSwitchStatus db sq $  Just  SSSendingQKEY 
2575+                         sq1 <- 
2576+                           if  sndSecure'
2577+                             then  do 
2578+                               enqueueCommand c " "   connId (Just  $  qServer sq2) $  AInternalCommand  $  ICQSndSecure  sndId
2579+                               withStore' c $  \ db ->  setSndSwitchStatus db sq $  Just  SSSecuringQueue 
2580+                             else  do 
2581+                               let  sqInfo' =  (sqInfo ::  SMPQueueInfo ) {queueAddress =  queueAddress {dhPublicKey}}
2582+                               void .  enqueueMessages c cData' sqs SMP. noMsgFlags $  QKEY  [(sqInfo', sndPublicKey)]
2583+                               withStore' c $  \ db ->  setSndSwitchStatus db sq $  Just  SSSendingQKEY 
25542584                        let  sqs'' =  updatedQs sq1 sqs' <>  [sq2]
25552585                            conn' =  DuplexConnection  cData' rqs sqs''
25562586                        notify .  SWITCH  QDSnd  SPStarted  $  connectionStats conn'
@@ -2578,6 +2608,24 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(_, srv, _), _v, sessId, ts)
25782608            where 
25792609              SMPQueueInfo  cVer' SMPQueueAddress  {smpServer, senderId, dhPublicKey} =  qInfo
25802610
2611+           qSecMsg  ::  SMP. MsgId  ->  NonEmpty  SMPQueueInfo  ->  Connection  'CDuplex ->  AM  () 
2612+           qSecMsg srvMsgId (qInfo :|  _) conn'@ (DuplexConnection  cData' rqs _) =  do 
2613+             when (ratchetSyncSendProhibited cData') $  throwE $  AGENT  (A_QUEUE  " ratchet is not synchronized"  )
2614+             clientVRange <-  asks $  smpClientVRange .  config
2615+             unless (qInfo `isCompatible`  clientVRange) .  throwE $  AGENT  A_VERSION 
2616+             case  findRQ (smpServer, senderId) rqs of 
2617+               Just  rq'@ RcvQueue  {e2ePrivKey =  dhPrivKey, smpClientVersion =  cVer, status =  status'}
2618+                 |  status' ==  New  ||  status' ==  Confirmed  ->  do 
2619+                     checkRQSwchStatus rq RSSendingQADD 
2620+                     logServer " <--"   c srv rId $  " MSG <QSEC>:"   <>  logSecret srvMsgId <>  "  "   <>  logSecret senderId
2621+                     let  dhSecret =  C. dh' dhPublicKey dhPrivKey
2622+                     withStore' c $  \ db ->  setRcvQueueConfirmedE2E db rq' dhSecret $  min  cVer cVer'
2623+                     notify .  SWITCH  QDRcv  SPCompleted  $  connectionStats conn'
2624+                 |  otherwise  ->  qError " QSEC: queue already secured" 
2625+               _ ->  qError " QSEC: queue address not found in connection" 
2626+             where 
2627+               SMPQueueInfo  cVer' SMPQueueAddress  {smpServer, senderId, dhPublicKey} =  qInfo
2628+ 
25812629          --  processed by queue sender
25822630          --  mark queue as Secured and to start sending messages to it
25832631          qUseMsg  ::  SMP. MsgId  ->  NonEmpty  ((SMPServer , SMP. SenderId ), Bool  ) ->  Connection  'CDuplex ->  AM  () 
0 commit comments