mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-10-22 21:52:51 +00:00
xftp: FDEL and FACK commands (#648)
* xftp: FDEL and FACK commands * correction * more tests --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
1 parent
bbf9e28696
commit
97b8b37325
6 files changed
+118
-16
No files matched your search
@@ -131,16 +131,12 @@ createXFTPChunk ::
|
||||
ExceptT XFTPClientError IO (SenderId, NonEmpty RecipientId)
|
||||
createXFTPChunk c spKey file rsps =
|
||||
sendXFTPCommand c spKey "" (FNEW file rsps) Nothing >>= \case
|
||||
-- TODO check that body is empty
|
||||
(FRSndIds sId rIds, _body) -> pure (sId, rIds)
|
||||
(FRSndIds sId rIds, body) -> noFile body (sId, rIds)
|
||||
(r, _) -> throwError . PCEUnexpectedResponse $ bshow r
|
||||
|
||||
uploadXFTPChunk :: XFTPClient -> C.APrivateSignKey -> XFTPFileId -> XFTPChunkSpec -> ExceptT XFTPClientError IO ()
|
||||
uploadXFTPChunk c spKey fId chunkSpec =
|
||||
sendXFTPCommand c spKey fId FPUT (Just chunkSpec) >>= \case
|
||||
-- TODO check that body is empty
|
||||
(FROk, _body) -> pure ()
|
||||
(r, _) -> throwError . PCEUnexpectedResponse $ bshow r
|
||||
sendXFTPCommand c spKey fId FPUT (Just chunkSpec) >>= okResponse
|
||||
|
||||
downloadXFTPChunk :: XFTPClient -> C.APrivateSignKey -> XFTPFileId -> XFTPRcvChunkSpec -> ExceptT XFTPClientError IO ()
|
||||
downloadXFTPChunk c rpKey fId chunkSpec@XFTPRcvChunkSpec {filePath} = do
|
||||
@@ -157,6 +153,23 @@ downloadXFTPChunk c rpKey fId chunkSpec@XFTPRcvChunkSpec {filePath} = do
|
||||
_ -> throwError $ PCEResponseError NO_FILE
|
||||
(r, _) -> throwError . PCEUnexpectedResponse $ bshow r
|
||||
|
||||
deleteXFTPChunk :: XFTPClient -> C.APrivateSignKey -> SenderId -> ExceptT XFTPClientError IO ()
|
||||
deleteXFTPChunk c spKey sId = sendXFTPCommand c spKey sId FDEL Nothing >>= okResponse
|
||||
|
||||
ackXFTPChunk :: XFTPClient -> C.APrivateSignKey -> RecipientId -> ExceptT XFTPClientError IO ()
|
||||
ackXFTPChunk c rpKey rId = sendXFTPCommand c rpKey rId FACK Nothing >>= okResponse
|
||||
|
||||
okResponse :: (FileResponse, HTTP2Body) -> ExceptT XFTPClientError IO ()
|
||||
okResponse = \case
|
||||
(FROk, body) -> noFile body ()
|
||||
(r, _) -> throwError . PCEUnexpectedResponse $ bshow r
|
||||
|
||||
-- TODO this currently does not check anything because response size is not set and bodyPart is always Just
|
||||
noFile :: HTTP2Body -> a -> ExceptT XFTPClientError IO a
|
||||
noFile HTTP2Body {bodyPart} a = case bodyPart of
|
||||
Just _ -> pure a -- throwError $ PCEResponseError HAS_FILE
|
||||
_ -> pure a
|
||||
|
||||
-- FADD :: NonEmpty RcvPublicVerifyKey -> FileCommand Sender
|
||||
-- FDEL :: FileCommand Sender
|
||||
-- FACK :: FileCommand Recipient
|
||||
|
||||
@@ -317,6 +317,8 @@ data XFTPErrorType
|
||||
NO_FILE
|
||||
| -- | unexpected file body
|
||||
HAS_FILE
|
||||
| -- | file IO error
|
||||
FILE_IO
|
||||
| -- | internal server error
|
||||
INTERNAL
|
||||
| -- | used internally, never returned by the server (to be removed)
|
||||
@@ -333,6 +335,7 @@ instance Encoding XFTPErrorType where
|
||||
DIGEST -> "DIGEST"
|
||||
NO_FILE -> "NO_FILE"
|
||||
HAS_FILE -> "HAS_FILE"
|
||||
FILE_IO -> "FILE_IO"
|
||||
INTERNAL -> "INTERNAL"
|
||||
DUPLICATE_ -> "DUPLICATE_"
|
||||
|
||||
@@ -346,6 +349,7 @@ instance Encoding XFTPErrorType where
|
||||
"DIGEST" -> pure DIGEST
|
||||
"NO_FILE" -> pure NO_FILE
|
||||
"HAS_FILE" -> pure HAS_FILE
|
||||
"FILE_IO" -> pure FILE_IO
|
||||
"INTERNAL" -> pure INTERNAL
|
||||
"DUPLICATE_" -> pure DUPLICATE_
|
||||
_ -> fail "bad error type"
|
||||
|
||||
@@ -17,6 +17,7 @@ import Control.Monad.Except
|
||||
import Control.Monad.IO.Unlift (MonadUnliftIO)
|
||||
import Control.Monad.Reader
|
||||
import Crypto.Random (getRandomBytes)
|
||||
import Data.Bifunctor (first)
|
||||
import qualified Data.ByteString.Base64.URL as B64
|
||||
import Data.ByteString.Builder (byteString)
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
@@ -38,7 +39,7 @@ import Simplex.FileTransfer.Transport
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import qualified Simplex.Messaging.Crypto.Lazy as LC
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Protocol (CorrId, RcvPublicDhKey)
|
||||
import Simplex.Messaging.Protocol (CorrId, RcvPublicDhKey, RecipientId)
|
||||
import Simplex.Messaging.Server (dummyVerifyCmd, verifyCmdSignature)
|
||||
import Simplex.Messaging.Server.Stats
|
||||
import Simplex.Messaging.Server.StoreLog (StoreLog, closeStoreLog)
|
||||
@@ -176,7 +177,7 @@ verifyXFTPTransmission sig_ signed fId cmd =
|
||||
atomically $ verify <$> getFile st party fId
|
||||
where
|
||||
verify = \case
|
||||
Right (fr, k) -> XFTPReqCmd fr cmd `verifyWith` k
|
||||
Right (fr, k) -> XFTPReqCmd fId fr cmd `verifyWith` k
|
||||
_ -> maybe False (dummyVerifyCmd signed) sig_ `seq` VRFailed
|
||||
req `verifyWith` k = if verifyCmdSignature sig_ signed k then VRVerified req else VRFailed
|
||||
|
||||
@@ -193,12 +194,12 @@ processXFTPRequest HTTP2Body {bodyPart} = \case
|
||||
forM (L.zip rIds rcps) $ \rcp ->
|
||||
ExceptT $ atomically $ addRecipient st sId rcp
|
||||
noFile $ either FRErr (const $ FRSndIds sId rIds) r
|
||||
XFTPReqCmd fr (FileCmd _ cmd) -> case cmd of
|
||||
XFTPReqCmd fId fr (FileCmd _ cmd) -> case cmd of
|
||||
FADD _rcps -> noFile FROk
|
||||
FPUT -> (,Nothing) <$> receiveServerFile fr
|
||||
FDEL -> noFile FROk
|
||||
FDEL -> (,Nothing) <$> deleteServerFile fr
|
||||
FGET rDhKey -> sendServerFile fr rDhKey
|
||||
FACK -> noFile FROk
|
||||
FACK -> (,Nothing) <$> ackFileReception fId fr
|
||||
-- it should never get to the options below, they are passed in other constructors of XFTPRequest
|
||||
FNEW _ _ -> noFile $ FRErr INTERNAL
|
||||
PING -> noFile FRPong
|
||||
@@ -217,7 +218,7 @@ processXFTPRequest HTTP2Body {bodyPart} = \case
|
||||
liftIO $
|
||||
runExceptT (receiveFile getBody (XFTPRcvChunkSpec fPath size digest)) >>= \case
|
||||
Right () -> atomically $ writeTVar filePath (Just fPath) $> FROk
|
||||
Left e -> whenM (doesFileExist fPath) (removeFile fPath) $> FRErr e
|
||||
Left e -> (whenM (doesFileExist fPath) (removeFile fPath) `catch` logFileError) $> FRErr e
|
||||
|
||||
sendServerFile :: FileRec -> RcvPublicDhKey -> M (FileResponse, Maybe ServerFile)
|
||||
sendServerFile FileRec {filePath, fileInfo = FileInfo {size}} rDhKey = do
|
||||
@@ -231,6 +232,25 @@ processXFTPRequest HTTP2Body {bodyPart} = \case
|
||||
_ -> (FRErr INTERNAL, Nothing)
|
||||
_ -> pure (FRErr NO_FILE, Nothing)
|
||||
|
||||
deleteServerFile :: FileRec -> M FileResponse
|
||||
deleteServerFile FileRec {senderId, filePath} = do
|
||||
r <- runExceptT $ do
|
||||
path <- readTVarIO filePath
|
||||
ExceptT $ first (\(_ :: SomeException) -> FILE_IO) <$> try (forM_ path $ \p -> whenM (doesFileExist p) (removeFile p))
|
||||
st <- asks store
|
||||
void $ atomically $ deleteFile st senderId
|
||||
pure FROk
|
||||
either (pure . FRErr) pure r
|
||||
|
||||
logFileError :: SomeException -> IO ()
|
||||
logFileError e = logError $ "Error deleting file: " <> tshow e
|
||||
|
||||
ackFileReception :: RecipientId -> FileRec -> M FileResponse
|
||||
ackFileReception rId fr = do
|
||||
st <- asks store
|
||||
atomically $ deleteRecipient st rId fr
|
||||
pure FROk
|
||||
|
||||
randomId :: (MonadUnliftIO m, MonadReader XFTPEnv m) => Int -> m ByteString
|
||||
randomId n = do
|
||||
gVar <- asks idsDrg
|
||||
|
||||
@@ -14,7 +14,7 @@ import Data.Time.Clock (getCurrentTime)
|
||||
import Data.X509.Validation (Fingerprint (..))
|
||||
import Network.Socket
|
||||
import qualified Network.TLS as T
|
||||
import Simplex.FileTransfer.Protocol (FileCmd, FileInfo)
|
||||
import Simplex.FileTransfer.Protocol (FileCmd, FileInfo, XFTPFileId)
|
||||
import Simplex.FileTransfer.Server.Stats
|
||||
import Simplex.FileTransfer.Server.Store
|
||||
import Simplex.FileTransfer.Server.StoreLog
|
||||
@@ -63,5 +63,5 @@ newXFTPServerEnv config@XFTPServerConfig {storeLogFile, caCertificateFile, certi
|
||||
|
||||
data XFTPRequest
|
||||
= XFTPReqNew FileInfo (NonEmpty RcvPublicVerifyKey)
|
||||
| XFTPReqCmd FileRec FileCmd
|
||||
| XFTPReqCmd XFTPFileId FileRec FileCmd
|
||||
| XFTPReqPing
|
||||
@@ -11,6 +11,7 @@ module Simplex.FileTransfer.Server.Store
|
||||
setFilePath,
|
||||
addRecipient,
|
||||
deleteFile,
|
||||
deleteRecipient,
|
||||
getFile,
|
||||
ackFile,
|
||||
)
|
||||
@@ -84,6 +85,11 @@ deleteFile FileStore {files, recipients} senderId = do
|
||||
pure $ Right ()
|
||||
_ -> pure $ Left AUTH
|
||||
|
||||
deleteRecipient :: FileStore -> RecipientId -> FileRec -> STM ()
|
||||
deleteRecipient FileStore {recipients} rId FileRec {recipientIds} = do
|
||||
TM.delete rId recipients
|
||||
modifyTVar' recipientIds $ S.delete rId
|
||||
|
||||
getFile :: FileStore -> SFileParty p -> XFTPFileId -> STM (Either XFTPErrorType (FileRec, C.APublicVerifyKey))
|
||||
getFile st party fId = case party of
|
||||
SSender -> withFile st fId $ pure . Right . (\f -> (f, sndKey $ fileInfo f))
|
||||
|
||||
Reference in new issue
Block a user