From 14f286bab81ff2415afe9919531ee7337482b1b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Wed, 21 Apr 2021 11:58:55 -0400
Subject: [PATCH] datatransfer: implement downloadFile logic

Add mechanism to re-ask for transfers and sendProfile
with new logic

GitLab: #524
Change-Id: I9f10328ff0e2bd9128e2d7d1afd7fa272dd14cdf
---
 .../cx.ring.Ring.ConfigurationManager.xml     |  44 +-
 bin/dbus/dbusclient.cpp                       |   2 +-
 bin/dbus/dbusconfigurationmanager.cpp         |  63 +-
 bin/dbus/dbusconfigurationmanager.h           |  42 +-
 bin/jni/datatransfer.i                        |  40 +-
 bin/jni/jni_interface.i                       |   2 +-
 src/account.h                                 |   9 -
 src/client/datatransfer.cpp                   |  94 +-
 src/data_transfer.cpp                         | 620 ++++++++++---
 src/data_transfer.h                           | 187 +++-
 src/dring/datatransfer_interface.h            |  47 +-
 src/fileutils.cpp                             |  32 +-
 src/fileutils.h                               |   2 +
 src/ftp_server.cpp                            |   2 +-
 src/jamidht/connectionmanager.cpp             |  49 +-
 src/jamidht/connectionmanager.h               |   9 +-
 src/jamidht/conversation.cpp                  | 138 ++-
 src/jamidht/conversation.h                    |  55 +-
 src/jamidht/jamiaccount.cpp                   | 874 ++++++++++--------
 src/jamidht/jamiaccount.h                     | 114 ++-
 src/jamidht/multiplexed_socket.cpp            |   6 +-
 src/sip/sipaccountbase.cpp                    |  34 +-
 src/sip/sipaccountbase.h                      |   1 -
 test/unitTest/conversation/compability.cpp    |  10 +-
 test/unitTest/fileTransfer/fileTransfer.cpp   | 758 ++++++++-------
 25 files changed, 2108 insertions(+), 1126 deletions(-)

diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 2d2ebcf5db..2425d163c6 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -1584,29 +1584,38 @@
        </method>
 
        <method name="sendFile" tp:name-for-bindings="sendFile">
+           <tp:added version="10.0.0"/>
+           <arg type="s" name="accountId" direction="in"/>
+           <arg type="s" name="conversationId" direction="in"/>
+           <arg type="s" name="filePath" direction="in"/>
+           <arg type="s" name="fileDisplayName" direction="in"/>
+           <arg type="s" name="parent" direction="in"/>
+       </method>
+
+       <method name="sendFileLegacy" tp:name-for-bindings="sendFileLegacy">
            <tp:added version="10.0.0"/>
            <arg type="u" name="dataTransferError" direction="out"/>
            <arg type="(suuxxssssss)" name="DataTransferInfo" direction="in"/>
-           <arg type="t" name="dataTransferId" direction="out"/>
            <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="DataTransferInfo"/>
+           <arg type="t" name="dataTransferId" direction="out"/>
        </method>
 
        <method name="dataTransferInfo" tp:name-for-bindings="dataTransferInfo">
            <tp:added version="10.0.0"/>
            <arg type="u" name="dataTransferError" direction="out"/>
            <arg type="s" name="accountId" direction="in"/>
-           <arg type="s" name="to" direction="in"/>
-           <arg type="t" name="dataTransferId" direction="in"/>
+           <arg type="s" name="fileId" direction="in"/>
            <arg type="(suuxxssssss)" name="dataTransferInfo" direction="out"/>
            <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DataTransferInfo"/>
        </method>
 
-       <method name="dataTransferBytesProgress" tp:name-for-bindings="dataTransferBytesProgress">
+       <method name="fileTransferInfo" tp:name-for-bindings="fileTransferInfo">
            <tp:added version="10.0.0"/>
            <arg type="u" name="dataTransferError" direction="out"/>
            <arg type="s" name="accountId" direction="in"/>
            <arg type="s" name="to" direction="in"/>
-           <arg type="t" name="dataTransferId" direction="in"/>
+           <arg type="s" name="fileId" direction="in"/>
+           <arg type="s" name="path" direction="out"/>
            <arg type="x" name="totalSize" direction="out"/>
            <arg type="x" name="bytesProgress" direction="out"/>
        </method>
@@ -1615,17 +1624,17 @@
            <tp:added version="10.0.0"/>
            <arg type="u" name="dataTransferError" direction="out"/>
            <arg type="s" name="accountId" direction="in"/>
-           <arg type="s" name="to" direction="in"/>
-           <arg type="t" name="dataTransferId" direction="in"/>
+           <arg type="s" name="fileId" direction="in"/>
            <arg type="s" name="filePath" direction="in"/>
-           <arg type="x" name="offset" direction="in"/>
        </method>
 
-       <method name="askForTransfer" tp:name-for-bindings="askForTransfer">
+       <method name="downloadFile" tp:name-for-bindings="downloadFile">
            <tp:added version="10.1.0"/>
+           <arg type="b" name="result" direction="out"/>
            <arg type="s" name="accountId" direction="in"/>
-           <arg type="s" name="conversationUri" direction="in"/>
+           <arg type="s" name="conversationId" direction="in"/>
            <arg type="s" name="interactionId" direction="in"/>
+           <arg type="s" name="fileId" direction="in"/>
            <arg type="s" name="path" direction="in"/>
        </method>
 
@@ -1634,7 +1643,7 @@
            <arg type="u" name="dataTransferError" direction="out"/>
            <arg type="s" name="accountId" direction="in"/>
            <arg type="s" name="to" direction="in"/>
-           <arg type="t" name="dataTransferId" direction="in"/>
+           <arg type="s" name="fileId" direction="in"/>
        </method>
 
        <method name="monitor" tp:name-for-bindings="monitor">
@@ -1829,14 +1838,19 @@
                    An account id.
                </tp:docstring>
            </arg>
-           <arg type="s" name="to">
+           <arg type="s" name="conversationId">
+               <tp:docstring>
+                   Conversation id (empty for non swarm)
+               </tp:docstring>
+           </arg>
+           <arg type="s" name="interactionId">
                <tp:docstring>
-                   The peer or conversation id.
+                   Interaction id (empty for non swarm)
                </tp:docstring>
            </arg>
-           <arg type="t" name="id">
+           <arg type="s" name="fileId">
                <tp:docstring>
-                   Data transfer unique id.
+                   File transfer unique id.
                </tp:docstring>
            </arg>
            <arg type="i" name="code">
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index ea716ce0fb..c4fe96441d 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -293,7 +293,7 @@ DBusClient::initLibrary(int flags)
 
     const std::map<std::string, SharedCallback> dataXferEvHandlers = {
         exportable_callback<DataTransferSignal::DataTransferEvent>(
-            bind(&DBusConfigurationManager::dataTransferEvent, confM, _1, _2, _3, _4)),
+            bind(&DBusConfigurationManager::dataTransferEvent, confM, _1, _2, _3, _4, _5)),
     };
 
     const std::map<std::string, SharedCallback> convEvHandlers = {
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index 515907e47a..6ff5f7315c 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -745,9 +745,9 @@ DBusConfigurationManager::connectivityChanged()
 }
 
 void
-DBusConfigurationManager::sendFile(const RingDBusDataTransferInfo& in,
-                                   uint32_t& error,
-                                   DRing::DataTransferId& id)
+DBusConfigurationManager::sendFileLegacy(const RingDBusDataTransferInfo& in,
+                                         uint32_t& error,
+                                         DRing::DataTransferId& id)
 {
     DRing::DataTransferInfo info;
     info.accountId = in._1;
@@ -761,18 +761,27 @@ DBusConfigurationManager::sendFile(const RingDBusDataTransferInfo& in,
     info.displayName = in._9;
     info.path = in._10;
     info.mimetype = in._11;
-    error = uint32_t(DRing::sendFile(info, id));
+    error = uint32_t(DRing::sendFileLegacy(info, id));
+}
+
+void
+DBusConfigurationManager::sendFile(const std::string& accountId,
+                                   const std::string& conversationId,
+                                   const std::string& path,
+                                   const std::string& displayName,
+                                   const std::string& parent)
+{
+    DRing::sendFile(accountId, conversationId, path, displayName, parent);
 }
 
 void
 DBusConfigurationManager::dataTransferInfo(const std::string& accountId,
-                                           const std::string& conversationId,
-                                           const DRing::DataTransferId& id,
+                                           const std::string& fileId,
                                            uint32_t& error,
                                            RingDBusDataTransferInfo& out)
 {
     DRing::DataTransferInfo info;
-    auto res = DRing::dataTransferInfo(accountId, conversationId, id, info);
+    auto res = DRing::dataTransferInfo(accountId, fileId, info);
     if (res == DRing::DataTransferError::success) {
         out._1 = info.accountId;
         out._2 = uint32_t(info.lastEvent);
@@ -790,42 +799,42 @@ DBusConfigurationManager::dataTransferInfo(const std::string& accountId,
 }
 
 void
-DBusConfigurationManager::dataTransferBytesProgress(const std::string& accountId,
-                                                    const std::string& conversationId,
-                                                    const uint64_t& id,
-                                                    uint32_t& error,
-                                                    int64_t& total,
-                                                    int64_t& progress)
+DBusConfigurationManager::fileTransferInfo(const std::string& accountId,
+                                           const std::string& conversationId,
+                                           const std::string& fileId,
+                                           uint32_t& error,
+                                           std::string& path,
+                                           int64_t& total,
+                                           int64_t& progress)
 {
     error = uint32_t(
-        DRing::dataTransferBytesProgress(accountId, conversationId, id, total, progress));
+        DRing::fileTransferInfo(accountId, conversationId, fileId, path, total, progress));
 }
 
 uint32_t
 DBusConfigurationManager::acceptFileTransfer(const std::string& accountId,
-                                             const std::string& conversationId,
-                                             const uint64_t& id,
-                                             const std::string& file_path,
-                                             const int64_t& offset)
+                                             const std::string& fileId,
+                                             const std::string& file_path)
 {
-    return uint32_t(DRing::acceptFileTransfer(accountId, conversationId, id, file_path, offset));
+    return uint32_t(DRing::acceptFileTransfer(accountId, fileId, file_path));
 }
 
-void
-DBusConfigurationManager::askForTransfer(const std::string& accountId,
-                                         const std::string& conversationUri,
-                                         const std::string& interactionId,
-                                         const std::string& path)
+bool
+DBusConfigurationManager::downloadFile(const std::string& accountId,
+                                       const std::string& conversationUri,
+                                       const std::string& interactionId,
+                                       const std::string& fileId,
+                                       const std::string& path)
 {
-    DRing::askForTransfer(accountId, conversationUri, interactionId, path);
+    return DRing::downloadFile(accountId, conversationUri, interactionId, fileId, path);
 }
 
 uint32_t
 DBusConfigurationManager::cancelDataTransfer(const std::string& accountId,
                                              const std::string& conversationId,
-                                             const uint64_t& id)
+                                             const std::string& fileId)
 {
-    return uint32_t(DRing::cancelDataTransfer(accountId, conversationId, id));
+    return uint32_t(DRing::cancelDataTransfer(accountId, conversationId, fileId));
 }
 
 std::string
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index 5f7e7abb15..2228ba20b2 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -215,30 +215,36 @@ public:
                                                          const std::string& uri);
     std::vector<std::map<std::string, std::string>> getContacts(const std::string& accountId);
     void connectivityChanged();
-    void sendFile(const RingDBusDataTransferInfo& info, uint32_t& error, DRing::DataTransferId& id);
+    void sendFileLegacy(const RingDBusDataTransferInfo& info,
+                        uint32_t& error,
+                        DRing::DataTransferId& id);
+    void sendFile(const std::string& accountId,
+                  const std::string& conversationId,
+                  const std::string& path,
+                  const std::string& displayName,
+                  const std::string& parent);
     void dataTransferInfo(const std::string& accountId,
-                          const std::string& conversationId,
-                          const DRing::DataTransferId& id,
+                          const std::string& fileId,
                           uint32_t& error,
                           RingDBusDataTransferInfo& info);
-    void dataTransferBytesProgress(const std::string& accountId,
-                                   const std::string& conversationId,
-                                   const uint64_t& id,
-                                   uint32_t& error,
-                                   int64_t& total,
-                                   int64_t& progress);
+    void fileTransferInfo(const std::string& accountId,
+                          const std::string& conversationId,
+                          const std::string& fileId,
+                          uint32_t& error,
+                          std::string& path,
+                          int64_t& total,
+                          int64_t& progress);
     uint32_t acceptFileTransfer(const std::string& accountId,
-                                const std::string& conversationId,
-                                const uint64_t& id,
-                                const std::string& file_path,
-                                const int64_t& offset);
-    void askForTransfer(const std::string& accountId,
-                        const std::string& conversationUri,
-                        const std::string& interactionId,
-                        const std::string& path);
+                                const std::string& fileId,
+                                const std::string& file_path);
+    bool downloadFile(const std::string& accountId,
+                      const std::string& conversationId,
+                      const std::string& interactionId,
+                      const std::string& fileId,
+                      const std::string& path);
     uint32_t cancelDataTransfer(const std::string& accountId,
                                 const std::string& conversationId,
-                                const uint64_t& id);
+                                const std::string& fileId);
     bool isAudioMeterActive(const std::string& id);
     void setAudioMeterState(const std::string& id, const bool& state);
     void setDefaultModerator(const std::string& accountID,
diff --git a/bin/jni/datatransfer.i b/bin/jni/datatransfer.i
index 469e2e8793..cdc9693c14 100644
--- a/bin/jni/datatransfer.i
+++ b/bin/jni/datatransfer.i
@@ -33,12 +33,34 @@
 class DataTransferCallback {
 public:
     virtual ~DataTransferCallback(){}
-    virtual void dataTransferEvent(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId transferId, int eventCode){}
+    virtual void dataTransferEvent(const std::string& accountId, const std::string& conversationId, const std::string& interactionId, const std::string& fileId, int eventCode){}
 };
 %}
 
 %feature("director") DataTransferCallback;
 
+%typemap(jstype) std::string& OUTPUT "String[]"
+%typemap(jtype) std::string& OUTPUT "String[]"
+%typemap(jni) std::string& OUTPUT "jobjectArray"
+%typemap(javain)  std::string& OUTPUT "$javainput"
+%typemap(in) std::string& OUTPUT (std::string temp) {
+  if (!$input) {
+    SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "array null");
+    return $null;
+  }
+  if (JCALL1(GetArrayLength, jenv, $input) == 0) {
+    SWIG_JavaThrowException(jenv, SWIG_JavaIndexOutOfBoundsException, "Array must contain at least 1 element");
+  }
+  $1 = &temp;
+}
+%typemap(argout) std::string& OUTPUT {
+  jstring jvalue = JCALL1(NewStringUTF, jenv, temp$argnum.c_str());
+  JCALL3(SetObjectArrayElement, jenv, $input, 0, jvalue);
+}
+%apply std::string& OUTPUT { std::string& path_out }
+%apply int64_t& OUTPUT { int64_t& total_out }
+%apply int64_t& OUTPUT { int64_t& progress_out }
+
 namespace DRing {
 
   struct DataTransferInfo
@@ -56,17 +78,19 @@ namespace DRing {
     std::string mimetype;
   };
 
-  DRing::DataTransferError sendFile(const DRing::DataTransferInfo info, DRing::DataTransferId& id);
-  DRing::DataTransferError acceptFileTransfer(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId id, const std::string file_path, int64_t offset);
-  void askForTransfer(const std::string& accountId, const std::string& conversationUri, const std::string& interactionId, const std::string& path);
-  DRing::DataTransferError cancelDataTransfer(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId id);
-  DRing::DataTransferError dataTransferInfo(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId id, DRing::DataTransferInfo &info);
-  DRing::DataTransferError dataTransferBytesProgress(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId id, int64_t &total, int64_t &progress);
+  void sendFile(const std::string& accountId, const std::string& conversationId, const std::string& path, const std::string& displayName, const std::string& parent);
+
+  DRing::DataTransferError sendFileLegacy(const DRing::DataTransferInfo info, DRing::DataTransferId& id);
+  DRing::DataTransferError acceptFileTransfer(const std::string& accountId, const std::string& fileId, const std::string& file_path);
+  uint64_t downloadFile(const std::string& accountId, const std::string& conversationId, const std::string& interactionId,const std::string& fileId, const std::string& path);
+  DRing::DataTransferError cancelDataTransfer(const std::string& accountId, const std::string& conversationId, const std::string& fileId);
+  DRing::DataTransferError dataTransferInfo(const std::string& accountId, const std::string& fileId, DRing::DataTransferInfo &info);
+  DRing::DataTransferError fileTransferInfo(const std::string& accountId, const std::string& conversationId, const std::string& fileId, std::string &path, int64_t &total, int64_t &progress);
 
 }
 
 class DataTransferCallback {
 public:
     virtual ~DataTransferCallback(){}
-    virtual void dataTransferEvent(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId transferId, int eventCode){}
+    virtual void dataTransferEvent(const std::string& accountId, const std::string& conversationId, const std::string& interactionId, const std::string& fileId, int eventCode){}
 };
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index 0c3e2dd30e..2df38879e2 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -302,7 +302,7 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
     };
 
     const std::map<std::string, SharedCallback> dataTransferEvHandlers = {
-        exportable_callback<DataTransferSignal::DataTransferEvent>(bind(&DataTransferCallback::dataTransferEvent, dataM, _1, _2, _3, _4)),
+        exportable_callback<DataTransferSignal::DataTransferEvent>(bind(&DataTransferCallback::dataTransferEvent, dataM, _1, _2, _3, _4, _5)),
     };
 
     const std::map<std::string, SharedCallback> videoEvHandlers = {
diff --git a/src/account.h b/src/account.h
index 895efdf432..6aa17129d5 100644
--- a/src/account.h
+++ b/src/account.h
@@ -181,10 +181,6 @@ public:
 
     virtual void setIsComposing(const std::string& /*conversationUri*/, bool /*isWriting*/) {};
 
-    virtual void askForTransfer(const std::string& /*conversationUri*/,
-                                const std::string& /*interactionId*/,
-                                const std::string& /*path*/) {};
-
     virtual void onIsComposing(const std::string& /*conversationId*/,
                                const std::string& /*peer*/,
                                bool /*isWriting*/);
@@ -322,11 +318,6 @@ public:
                                 const std::string& /*conversationId*/,
                                 const std::string& /*commitId*/) {};
 
-    virtual void onAskForTransfer(const std::string& /*peer*/,
-                                  const std::string& /*deviceId*/,
-                                  const std::string& /*conversationId*/,
-                                  const std::string& /*interactionId*/) {};
-
     // Invites
     virtual void onConversationRequest(const std::string& /*from*/, const Json::Value&) {};
     virtual void onNeedConversationRequest(const std::string& /*from*/,
diff --git a/src/client/datatransfer.cpp b/src/client/datatransfer.cpp
index 230125f096..7383d6e718 100644
--- a/src/client/datatransfer.cpp
+++ b/src/client/datatransfer.cpp
@@ -34,80 +34,104 @@ registerDataXferHandlers(const std::map<std::string, std::shared_ptr<CallbackWra
 }
 
 DataTransferError
-sendFile(const DataTransferInfo& info, DataTransferId& id) noexcept
+sendFileLegacy(const DataTransferInfo& info, DataTransferId& tid) noexcept
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(info.accountId)) {
-        auto to = info.conversationId;
-        if (to.empty())
-            to = info.peer;
-        id = acc->sendFile(to, info.path);
-        if (id != 0)
-            return DRing::DataTransferError::success;
+        tid = acc->sendFile(info.peer, info.path);
+        return DRing::DataTransferError::success;
     }
+
     return DRing::DataTransferError::invalid_argument;
 }
 
+void
+sendFile(const std::string& accountId,
+         const std::string& conversationId,
+         const std::string& path,
+         const std::string& displayName,
+         const std::string& parent) noexcept
+{
+    if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) {
+        acc->sendFile(conversationId, path, displayName, parent);
+    }
+}
+
 DataTransferError
 acceptFileTransfer(const std::string& accountId,
-                   const std::string& conversationId,
-                   const DataTransferId& id,
-                   const std::string& file_path,
-                   int64_t offset) noexcept
+                   const std::string& fileId,
+                   const std::string& file_path) noexcept
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) {
-        return acc->acceptFile(conversationId, id, file_path, offset)
-                   ? DRing::DataTransferError::success
-                   : DRing::DataTransferError::invalid_argument;
+        if (auto dt = acc->dataTransfer()) {
+            try {
+                return dt->acceptFile(std::stoull(fileId), file_path)
+                           ? DRing::DataTransferError::success
+                           : DRing::DataTransferError::invalid_argument;
+            } catch (...) {
+                JAMI_ERR() << "Invalid file Id" << fileId;
+            }
+        }
     }
     return DRing::DataTransferError::invalid_argument;
 }
 
-void
-askForTransfer(const std::string& accountId,
-               const std::string& conversationUri,
-               const std::string& interactionId,
-               const std::string& path) noexcept
+bool
+downloadFile(const std::string& accountId,
+             const std::string& conversationId,
+             const std::string& interactionId,
+             const std::string& fileId,
+             const std::string& path) noexcept
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        acc->askForTransfer(conversationUri, interactionId, path);
+        return acc->downloadFile(conversationId, interactionId, fileId, path);
+    return {};
 }
 
 DataTransferError
 cancelDataTransfer(const std::string& accountId,
                    const std::string& conversationId,
-                   const DataTransferId& id) noexcept
+                   const std::string& fileId) noexcept
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) {
-        return acc->cancel(conversationId, id) ? DRing::DataTransferError::success
-                                               : DRing::DataTransferError::invalid_argument;
+        if (auto dt = acc->dataTransfer(conversationId))
+            return dt->cancel(fileId) ? DRing::DataTransferError::success
+                                      : DRing::DataTransferError::invalid_argument;
     }
     return DRing::DataTransferError::invalid_argument;
 }
 
 DataTransferError
-dataTransferBytesProgress(const std::string& accountId,
-                          const std::string& conversationId,
-                          const DataTransferId& id,
-                          int64_t& total,
-                          int64_t& progress) noexcept
+fileTransferInfo(const std::string& accountId,
+                 const std::string& conversationId,
+                 const std::string& fileId,
+                 std::string& path,
+                 int64_t& total,
+                 int64_t& progress) noexcept
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) {
-        return acc->bytesProgress(conversationId, id, total, progress)
-                   ? DRing::DataTransferError::success
-                   : DRing::DataTransferError::invalid_argument;
+        if (auto dt = acc->dataTransfer(conversationId))
+            return dt->info(fileId, path, total, progress)
+                       ? DRing::DataTransferError::success
+                       : DRing::DataTransferError::invalid_argument;
     }
     return DRing::DataTransferError::invalid_argument;
 }
 
 DataTransferError
 dataTransferInfo(const std::string& accountId,
-                 const std::string& conversationId,
-                 const DataTransferId& id,
+                 const std::string& fileId,
                  DataTransferInfo& info) noexcept
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) {
-        return acc->info(conversationId, id, info) ? DRing::DataTransferError::success
-                                                   : DRing::DataTransferError::invalid_argument;
+        if (auto dt = acc->dataTransfer()) {
+            try {
+                return dt->info(std::stoull(fileId), info)
+                           ? DRing::DataTransferError::success
+                           : DRing::DataTransferError::invalid_argument;
+            } catch (...) {
+                JAMI_ERR() << "Invalid fileId: " << fileId;
+            }
+        }
     }
     return DRing::DataTransferError::invalid_argument;
 }
diff --git a/src/data_transfer.cpp b/src/data_transfer.cpp
index efe706bad4..c977b542ba 100644
--- a/src/data_transfer.cpp
+++ b/src/data_transfer.cpp
@@ -39,14 +39,16 @@
 #include <mutex>
 #include <future>
 #include <atomic>
-#include <cstdlib> // mkstemp
+#include <charconv> // std::from_chars
+#include <cstdlib>  // mkstemp
+#include <filesystem>
 
 #include <opendht/rng.h>
 #include <opendht/thread_pool.h>
 
 namespace jami {
 
-static DRing::DataTransferId
+DRing::DataTransferId
 generateUID()
 {
     thread_local dht::crypto::random_device rd;
@@ -143,7 +145,11 @@ DataTransfer::emit(DRing::DataTransferEventCode code) const
     if (internalCompletionCb_)
         return; // VCard transfer is just for the daemon
     runOnMainThread([id = id, code, accountId, to]() {
-        emitSignal<DRing::DataTransferSignal::DataTransferEvent>(accountId, to, id, uint32_t(code));
+        emitSignal<DRing::DataTransferSignal::DataTransferEvent>(accountId,
+                                                                 "",
+                                                                 "",
+                                                                 std::to_string(id),
+                                                                 uint32_t(code));
     });
 }
 
@@ -319,10 +325,11 @@ private:
 
     void sendHeader() const
     {
-        auto header = fmt::format(
-            "Content-Length: {}\n"
-            "Display-Name: {}\n"
-            "Offset: 0\n\n", info_.totalSize, info_.displayName);
+        auto header = fmt::format("Content-Length: {}\n"
+                                  "Display-Name: {}\n"
+                                  "Offset: 0\n\n",
+                                  info_.totalSize,
+                                  info_.displayName);
         headerSent_ = true;
         emit(DRing::DataTransferEventCode::wait_peer_acceptance);
         if (onRecvCb_)
@@ -376,7 +383,7 @@ SubOutgoingFileTransfer::SubOutgoingFileTransfer(DRing::DataTransferId tid,
     , peerUri_(peerUri)
 {
     info_ = metaInfo_->info();
-    fileutils::openStream(input_, info_.path, std::ios::binary);
+    fileutils::openStream(input_, info_.path, std::ios::in | std::ios::binary);
     if (!input_)
         throw std::runtime_error("input file open failed");
     metaInfo_->addLinkedTransfer(this);
@@ -549,8 +556,6 @@ public:
 
     void accept(const std::string&, std::size_t offset) override;
 
-    void setVerifyShaSum(const OnVerifyCb& vcb) { vcb_ = std::move(vcb); }
-
     bool write(std::string_view data) override;
 
     void setFilename(const std::string& filename);
@@ -566,7 +571,6 @@ private:
     IncomingFileTransfer() = delete;
 
     DRing::DataTransferId internalId_;
-    OnVerifyCb vcb_ {};
 
     std::ofstream fout_;
     std::mutex cbMtx_ {};
@@ -650,14 +654,6 @@ IncomingFileTransfer::close() noexcept
 
     JAMI_DBG() << "[FTP] file closed, rx " << info_.bytesProgress << " on " << info_.totalSize;
     if (info_.bytesProgress >= info_.totalSize) {
-        if (vcb_) {
-            if (!vcb_(fileutils::sha3File(info_.path))) {
-                JAMI_DBG() << "[FTP] Incorrect sha3sum - erasing " << info_.path;
-                fileutils::remove(info_.path, true);
-                emit(DRing::DataTransferEventCode::invalid);
-                return;
-            }
-        }
         if (internalCompletionCb_)
             internalCompletionCb_(info_.path);
         emit(DRing::DataTransferEventCode::finished);
@@ -695,45 +691,251 @@ IncomingFileTransfer::write(std::string_view buffer)
 }
 
 //==============================================================================
+//                                 With Swarm
+//==============================================================================
+
+FileInfo::FileInfo(const std::shared_ptr<ChannelSocket>& channel,
+                   const std::string& fileId,
+                   const std::string& interactionId,
+                   const DRing::DataTransferInfo& info)
+    : fileId_(fileId)
+    , interactionId_(interactionId)
+    , info_(info)
+    , channel_(channel)
+{}
+
+void
+FileInfo::emit(DRing::DataTransferEventCode code)
+{
+    if (finishedCb_ && code >= DRing::DataTransferEventCode::finished)
+        finishedCb_(uint32_t(code));
+    if (fileId_ != "profile.vcf") {
+        // Else it's an internal transfer
+        runOnMainThread([info = info_, iid = interactionId_, fid = fileId_, code]() {
+            emitSignal<DRing::DataTransferSignal::DataTransferEvent>(info.accountId,
+                                                                     info.conversationId,
+                                                                     iid,
+                                                                     fid,
+                                                                     uint32_t(code));
+        });
+    }
+}
 
-struct WaitingRequest
+OutgoingFile::OutgoingFile(const std::shared_ptr<ChannelSocket>& channel,
+                           const std::string& fileId,
+                           const std::string& interactionId,
+                           const DRing::DataTransferInfo& info,
+                           size_t start,
+                           size_t end)
+    : FileInfo(channel, fileId, interactionId, info)
+    , start_(start)
+    , end_(end)
 {
-    std::string sha3sum;
-    std::string path;
-};
+    if (!fileutils::isFile(info_.path)) {
+        channel_->shutdown();
+        return;
+    }
+    fileutils::openStream(stream_, info_.path, std::ios::binary | std::ios::in);
+    if (!stream_ || !stream_.is_open()) {
+        channel_->shutdown();
+        return;
+    }
+}
+
+OutgoingFile::~OutgoingFile()
+{
+    if (stream_ && stream_.is_open())
+        stream_.close();
+    if (channel_)
+        channel_->shutdown();
+}
+
+void
+OutgoingFile::process()
+{
+    if (!channel_ or !stream_ or !stream_.is_open())
+        return;
+    auto correct = false;
+    stream_.seekg(start_, std::ios::beg);
+    try {
+        std::vector<char> buffer(UINT16_MAX, 0);
+        std::error_code ec;
+        auto pos = start_;
+        while (!stream_.eof()) {
+            stream_.read(buffer.data(),
+                         end_ > start_ ? std::min(end_ - pos, buffer.size()) : buffer.size());
+            auto gcount = stream_.gcount();
+            pos += gcount;
+            channel_->write(reinterpret_cast<const uint8_t*>(buffer.data()), gcount, ec);
+            if (ec)
+                break;
+        }
+        if (!ec)
+            correct = true;
+        stream_.close();
+    } catch (...) {
+    }
+    if (!isUserCancelled_) {
+        auto code = correct ? DRing::DataTransferEventCode::finished
+                            : DRing::DataTransferEventCode::closed_by_peer;
+        emit(code);
+    }
+}
+
+void
+OutgoingFile::cancel()
+{
+    // Remove link, not original file
+    auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + "conversation_data"
+                + DIR_SEPARATOR_STR + info_.accountId + DIR_SEPARATOR_STR + info_.conversationId
+                + DIR_SEPARATOR_STR + fileId_;
+    if (fileutils::isSymLink(path))
+        fileutils::remove(path);
+    isUserCancelled_ = true;
+    emit(DRing::DataTransferEventCode::closed_by_host);
+}
+
+IncomingFile::IncomingFile(const std::shared_ptr<ChannelSocket>& channel,
+                           const DRing::DataTransferInfo& info,
+                           const std::string& fileId,
+                           const std::string& interactionId,
+                           const std::string& sha3Sum)
+    : FileInfo(channel, fileId, interactionId, info)
+    , sha3Sum_(sha3Sum)
+{
+    fileutils::openStream(stream_, info_.path, std::ios::binary | std::ios::out);
+    if (!stream_)
+        return;
+
+    emit(DRing::DataTransferEventCode::ongoing);
+}
+
+IncomingFile::~IncomingFile()
+{
+    if (channel_)
+        channel_->setOnRecv({});
+    if (stream_ && stream_.is_open())
+        stream_.close();
+    if (channel_)
+        channel_->shutdown();
+}
+
+void
+IncomingFile::cancel()
+{
+    isUserCancelled_ = true;
+    emit(DRing::DataTransferEventCode::closed_by_peer);
+    if (channel_)
+        channel_->shutdown();
+}
+
+void
+IncomingFile::process()
+{
+    channel_->setOnRecv([this](const uint8_t* buf, size_t len) {
+        if (stream_.is_open())
+            stream_ << std::string_view((const char*) buf, len);
+        info_.bytesProgress = stream_.tellp();
+        return len;
+    });
+    channel_->onShutdown([this] {
+        auto correct = sha3Sum_.empty();
+        if (!correct) {
+            if (stream_ && stream_.is_open())
+                stream_.close();
+            // Verify shaSum
+            auto sha3Sum = fileutils::sha3File(info_.path);
+            if (sha3Sum_ == sha3Sum) {
+                JAMI_INFO() << "New file received: " << info_.path;
+                correct = true;
+            } else {
+                JAMI_WARN() << "Remove file, invalid sha3sum detected for " << info_.path;
+                fileutils::remove(info_.path, true);
+            }
+        }
+        if (isUserCancelled_)
+            return;
+        auto code = correct ? DRing::DataTransferEventCode::finished
+                            : DRing::DataTransferEventCode::closed_by_host;
+        emit(code);
+    });
+}
+
+//==============================================================================
 
 class TransferManager::Impl
 {
 public:
-    Impl(const std::string& accountId, const std::string& to, bool isConversation)
+    Impl(const std::string& accountId, const std::string& to)
         : accountId_(accountId)
         , to_(to)
-        , isConversation_(isConversation)
-    {}
+    {
+        if (!to_.empty()) {
+            conversationDataPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId_
+                                    + DIR_SEPARATOR_STR + "conversation_data" + DIR_SEPARATOR_STR
+                                    + to_;
+            fileutils::check_dir(conversationDataPath_.c_str());
+            waitingPath_ = conversationDataPath_ + DIR_SEPARATOR_STR + "waiting";
+        }
+        loadWaiting();
+    }
+
+    ~Impl()
+    {
+        std::lock_guard<std::mutex> lk {mapMutex_};
+        for (const auto& [channel, _of] : outgoings_) {
+            channel->shutdown();
+        }
+        outgoings_.clear();
+        incomings_.clear();
+        vcards_.clear();
+    }
+
+    void loadWaiting()
+    {
+        try {
+            // read file
+            auto file = fileutils::loadFile(waitingPath_);
+            // load values
+            msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
+            std::lock_guard<std::mutex> lk {mapMutex_};
+            oh.get().convert(waitingIds_);
+        } catch (const std::exception& e) {
+            return;
+        }
+    }
+    void saveWaiting()
+    {
+        std::ofstream file(waitingPath_, std::ios::trunc | std::ios::binary);
+        msgpack::pack(file, waitingIds_);
+    }
 
     std::string accountId_ {};
     std::string to_ {};
-    bool isConversation_ {true};
+    std::string waitingPath_ {};
+    std::string conversationDataPath_ {};
 
-    std::mutex mapMutex_ {};
+    // Pre swarm
     std::map<DRing::DataTransferId, std::shared_ptr<OutgoingFileTransfer>> oMap_ {};
     std::map<DRing::DataTransferId, std::shared_ptr<IncomingFileTransfer>> iMap_ {};
-    std::map<DRing::DataTransferId, WaitingRequest> waitingIds_ {};
+
+    std::mutex mapMutex_ {};
+    std::map<std::string, WaitingRequest> waitingIds_ {};
+    std::map<std::shared_ptr<ChannelSocket>, std::shared_ptr<OutgoingFile>> outgoings_ {};
+    std::map<std::string, std::shared_ptr<IncomingFile>> incomings_ {};
+    std::map<std::string, std::shared_ptr<IncomingFile>> vcards_ {};
 };
 
-TransferManager::TransferManager(const std::string& accountId,
-                                 const std::string& to,
-                                 bool isConversation)
-    : pimpl_ {std::make_unique<Impl>(accountId, to, isConversation)}
+TransferManager::TransferManager(const std::string& accountId, const std::string& to)
+    : pimpl_ {std::make_unique<Impl>(accountId, to)}
 {}
 
 TransferManager::~TransferManager() {}
 
 DRing::DataTransferId
 TransferManager::sendFile(const std::string& path,
-                          const InternalCompletionCb& icb,
-                          const std::string& deviceId,
-                          DRing::DataTransferId resendId)
+                          const std::string& peer,
+                          const InternalCompletionCb& icb)
 {
     // IMPLEMENTATION NOTE: requestPeerConnection() may call the given callback a multiple time.
     // This happen when multiple agents handle communications of the given peer for the given
@@ -743,17 +945,14 @@ TransferManager::sendFile(const std::string& path,
         return {};
     }
 
-    auto tid = resendId ? resendId : generateUID();
+    auto tid = generateUID();
     std::size_t found = path.find_last_of(DIR_SEPARATOR_CH);
     auto filename = path.substr(found + 1);
 
     DRing::DataTransferInfo info;
     info.accountId = pimpl_->accountId_;
     info.author = account->getUsername();
-    if (pimpl_->isConversation_) {
-        info.conversationId = pimpl_->to_;
-    } else
-        info.peer = pimpl_->to_;
+    info.peer = peer;
     info.path = path;
     info.displayName = filename;
     info.bytesProgress = 0;
@@ -791,8 +990,7 @@ TransferManager::sendFile(const std::string& path,
                     transfer->cancel();
                     transfer->close();
                 }
-            },
-            !resendId /* only add to history if we not resend a file */);
+            });
     } catch (const std::exception& ex) {
         JAMI_ERR() << "[XFER] exception during sendFile(): " << ex.what();
         return {};
@@ -814,21 +1012,71 @@ TransferManager::acceptFile(const DRing::DataTransferId& id, const std::string&
     return true;
 }
 
+void
+TransferManager::transferFile(const std::shared_ptr<ChannelSocket>& channel,
+                              const std::string& fileId,
+                              const std::string& interactionId,
+                              const std::string& path,
+                              size_t start,
+                              size_t end)
+{
+    std::lock_guard<std::mutex> lk {pimpl_->mapMutex_};
+    if (pimpl_->outgoings_.find(channel) != pimpl_->outgoings_.end())
+        return;
+    DRing::DataTransferInfo info;
+    info.accountId = pimpl_->accountId_;
+    info.conversationId = pimpl_->to_;
+    info.path = path;
+    auto f = std::make_shared<OutgoingFile>(channel, fileId, interactionId, info, start, end);
+    f->onFinished([w = weak(), channel](uint32_t) {
+        // schedule destroy outgoing transfer as not needed
+        dht::ThreadPool().computation().run([w, channel] {
+            if (auto sthis_ = w.lock()) {
+                auto& pimpl = sthis_->pimpl_;
+                std::lock_guard<std::mutex> lk {pimpl->mapMutex_};
+                auto itO = pimpl->outgoings_.find(channel);
+                if (itO != pimpl->outgoings_.end())
+                    pimpl->outgoings_.erase(itO);
+            }
+        });
+    });
+    pimpl_->outgoings_.emplace(channel, f);
+    dht::ThreadPool::io().run([w = std::weak_ptr<OutgoingFile>(f)] {
+        if (auto of = w.lock())
+            of->process();
+    });
+}
+
 bool
-TransferManager::cancel(const DRing::DataTransferId& id)
+TransferManager::cancel(const std::string& fileId)
 {
+    std::shared_ptr<ChannelSocket> channel;
     std::lock_guard<std::mutex> lk {pimpl_->mapMutex_};
-    auto it = pimpl_->iMap_.find(id);
-    if (it != pimpl_->iMap_.end()) {
-        if (it->second)
-            it->second->close();
+    if (!pimpl_->to_.empty()) {
+        // Note: For now, there is no cancel for outgoings.
+        // The client can just remove the file.
+        auto itC = pimpl_->incomings_.find(fileId);
+        if (itC == pimpl_->incomings_.end())
+            return false;
+        itC->second->cancel();
         return true;
     }
-    auto itO = pimpl_->oMap_.find(id);
-    if (itO != pimpl_->oMap_.end()) {
-        if (itO->second)
-            itO->second->close();
-        return true;
+    // Else, this is fallack.
+    try {
+        auto it = pimpl_->iMap_.find(std::stoull(fileId));
+        if (it != pimpl_->iMap_.end()) {
+            if (it->second)
+                it->second->close();
+            return true;
+        }
+        auto itO = pimpl_->oMap_.find(std::stoull(fileId));
+        if (itO != pimpl_->oMap_.end()) {
+            if (itO->second)
+                itO->second->close();
+            return true;
+        }
+    } catch (...) {
+        JAMI_ERR() << "Invalid fileId: " << fileId;
     }
     return false;
 }
@@ -836,40 +1084,58 @@ TransferManager::cancel(const DRing::DataTransferId& id)
 bool
 TransferManager::info(const DRing::DataTransferId& id, DRing::DataTransferInfo& info) const noexcept
 {
-    std::lock_guard<std::mutex> lk {pimpl_->mapMutex_};
-    auto it = pimpl_->iMap_.find(id);
-    if (it != pimpl_->iMap_.end()) {
+    std::unique_lock<std::mutex> lk {pimpl_->mapMutex_};
+    if (!pimpl_->to_.empty())
+        return false;
+    // Else it's fallback
+    if (auto it = pimpl_->iMap_.find(id); it != pimpl_->iMap_.end()) {
         if (it->second)
             it->second->info(info);
         return true;
     }
-    auto itO = pimpl_->oMap_.find(id);
-    if (itO != pimpl_->oMap_.end()) {
-        if (itO->second)
-            itO->second->info(info);
+    if (auto it = pimpl_->oMap_.find(id); it != pimpl_->oMap_.end()) {
+        if (it->second)
+            it->second->info(info);
         return true;
     }
     return false;
 }
 
 bool
-TransferManager::bytesProgress(const DRing::DataTransferId& id,
-                               int64_t& total,
-                               int64_t& progress) const noexcept
+TransferManager::info(const std::string& fileId,
+                      std::string& path,
+                      int64_t& total,
+                      int64_t& progress) const noexcept
 {
-    std::lock_guard<std::mutex> lk {pimpl_->mapMutex_};
-    auto it = pimpl_->iMap_.find(id);
-    if (it != pimpl_->iMap_.end()) {
-        if (it->second)
-            it->second->bytesProgress(total, progress);
+    std::unique_lock<std::mutex> lk {pimpl_->mapMutex_};
+    if (pimpl_->to_.empty())
+        return false;
+
+    auto itI = pimpl_->incomings_.find(fileId);
+    auto itW = pimpl_->waitingIds_.find(fileId);
+    path = this->path(fileId);
+    if (itI != pimpl_->incomings_.end()) {
+        total = itI->second->info().totalSize;
+        progress = itI->second->info().bytesProgress;
         return true;
-    }
-    auto itO = pimpl_->oMap_.find(id);
-    if (itO != pimpl_->oMap_.end()) {
-        if (itO->second)
-            itO->second->bytesProgress(total, progress);
+    } else if (fileutils::isFile(path)) {
+        std::ifstream transfer(path, std::ios::binary);
+        transfer.seekg(0, std::ios::end);
+        progress = transfer.tellg();
+        if (itW != pimpl_->waitingIds_.end()) {
+            total = itW->second.totalSize;
+        } else {
+            // If not waiting it's finished
+            total = progress;
+        }
+        return true;
+    } else if (itW != pimpl_->waitingIds_.end()) {
+        total = itW->second.totalSize;
+        progress = 0;
         return true;
     }
+    // Else we don't know infos there.
+    progress = 0;
     return false;
 }
 
@@ -879,65 +1145,175 @@ TransferManager::onIncomingFileRequest(const DRing::DataTransferInfo& info,
                                        const std::function<void(const IncomingFileInfo&)>& cb,
                                        const InternalCompletionCb& icb)
 {
-    std::string filename;
-    std::shared_ptr<IncomingFileTransfer> transfer;
+    auto transfer = std::make_shared<IncomingFileTransfer>(info, id, icb);
     {
         std::lock_guard<std::mutex> lk {pimpl_->mapMutex_};
-        auto it = pimpl_->iMap_.find(id);
-        if (it != pimpl_->iMap_.end()) {
-            // If the transfer is already in progress (aka not finished)
-            // we do not need to accept the request and can ignore it.
-            if (!it->second->isFinished()) {
-                JAMI_DBG("Declining request for %lu. Already downloading the file", id);
-                if (cb)
-                    cb({id, nullptr});
-                return;
-            }
-            // Else, we can handle a new incoming transfer
-            pimpl_->iMap_.erase(it);
-        }
-        // If we wait this id, we can accept it
-        auto itW = pimpl_->waitingIds_.find(id);
-        if (itW != pimpl_->waitingIds_.end())
-            filename = itW->second.path;
-        transfer = std::make_shared<IncomingFileTransfer>(info, id, icb);
-        auto p = pimpl_->iMap_.emplace(id, transfer);
-    }
-
-    transfer->setVerifyShaSum([this, id](const std::string& sha3sum) {
-        std::lock_guard<std::mutex> lk(pimpl_->mapMutex_);
-        auto it = pimpl_->waitingIds_.find(id);
-        if (it == pimpl_->waitingIds_.end())
-            return true; // No validation to do
-        auto res = it->second.sha3sum == sha3sum;
-        pimpl_->waitingIds_.erase(it);
-        return res;
-    });
+        pimpl_->iMap_.emplace(id, transfer);
+    }
     transfer->emit(DRing::DataTransferEventCode::created);
-    if (!filename.empty()) {
-        transfer->setFilename(filename);
-        if (transfer->start()) {
-            JAMI_DBG("Accepting request for %lu. Download %s", id, filename.c_str());
-            if (cb)
-                cb({id, std::static_pointer_cast<Stream>(transfer)});
-        }
-    } else {
-        transfer->requestFilename([transfer, id, cb = std::move(cb)](const std::string& filename) {
-            if (!filename.empty() && transfer->start())
-                cb({id, std::static_pointer_cast<Stream>(transfer)});
-            else
-                cb({id, nullptr});
+    transfer->requestFilename([transfer, id, cb = std::move(cb)](const std::string& filename) {
+        if (!filename.empty() && transfer->start())
+            cb({id, std::static_pointer_cast<Stream>(transfer)});
+        else
+            cb({id, nullptr});
+    });
+}
+
+void
+TransferManager::waitForTransfer(const std::string& fileId,
+                                 const std::string& interactionId,
+                                 const std::string& sha3sum,
+                                 const std::string& path,
+                                 std::size_t total)
+{
+    std::unique_lock<std::mutex> lk(pimpl_->mapMutex_);
+    auto itW = pimpl_->waitingIds_.find(fileId);
+    if (itW != pimpl_->waitingIds_.end())
+        return;
+    pimpl_->waitingIds_[fileId] = {fileId, interactionId, sha3sum, path, total};
+    JAMI_DBG() << "Wait for " << fileId;
+    if (!pimpl_->to_.empty())
+        pimpl_->saveWaiting();
+    lk.unlock();
+    emitSignal<DRing::DataTransferSignal::DataTransferEvent>(
+        pimpl_->accountId_,
+        pimpl_->to_,
+        interactionId,
+        fileId,
+        uint32_t(DRing::DataTransferEventCode::wait_peer_acceptance));
+}
+
+void
+TransferManager::onIncomingFileTransfer(const std::string& fileId,
+                                        const std::shared_ptr<ChannelSocket>& channel)
+{
+    std::lock_guard<std::mutex> lk(pimpl_->mapMutex_);
+    // Check if not already an incoming file for this id and that we are waiting this file
+    auto itC = pimpl_->incomings_.find(fileId);
+    if (itC != pimpl_->incomings_.end()) {
+        channel->shutdown();
+        return;
+    }
+    auto itW = pimpl_->waitingIds_.find(fileId);
+    if (itW == pimpl_->waitingIds_.end()) {
+        channel->shutdown();
+        return;
+    }
+
+    auto symlinkPath = path(fileId);
+
+    DRing::DataTransferInfo info;
+    info.accountId = pimpl_->accountId_;
+    info.conversationId = pimpl_->to_;
+    info.path = itW->second.path;
+    if (info.path.empty())
+        info.path = symlinkPath;
+    info.totalSize = itW->second.totalSize;
+    info.bytesProgress = 0;
+
+    // Create symlink for future transfers
+    if (info.path != symlinkPath && !fileutils::isSymLink(symlinkPath))
+        fileutils::createSymLink(symlinkPath, info.path);
+
+    auto ifile = std::make_shared<IncomingFile>(std::move(channel),
+                                                info,
+                                                fileId,
+                                                itW->second.interactionId,
+                                                itW->second.sha3sum);
+    auto res = pimpl_->incomings_.emplace(fileId, std::move(ifile));
+    if (res.second) {
+        res.first->second->onFinished([w = weak(), fileId](uint32_t code) {
+            // schedule destroy transfer as not needed
+            dht::ThreadPool().computation().run([w, fileId, code] {
+                if (auto sthis_ = w.lock()) {
+                    auto& pimpl = sthis_->pimpl_;
+                    std::lock_guard<std::mutex> lk {pimpl->mapMutex_};
+                    auto itO = pimpl->incomings_.find(fileId);
+                    if (itO != pimpl->incomings_.end())
+                        pimpl->incomings_.erase(itO);
+                    if (code == uint32_t(DRing::DataTransferEventCode::finished)) {
+                        auto itW = pimpl->waitingIds_.find(fileId);
+                        if (itW != pimpl->waitingIds_.end()) {
+                            pimpl->waitingIds_.erase(itW);
+                            pimpl->saveWaiting();
+                        }
+                    }
+                }
+            });
         });
+        res.first->second->process();
     }
 }
 
+std::string
+TransferManager::path(const std::string& fileId) const
+{
+    return pimpl_->conversationDataPath_ + DIR_SEPARATOR_STR + fileId;
+}
+
 void
-TransferManager::waitForTransfer(const DRing::DataTransferId& id,
-                                 const std::string& sha3sum,
-                                 const std::string& path)
+TransferManager::onIncomingProfile(const std::shared_ptr<ChannelSocket>& channel)
+{
+    if (!channel)
+        return;
+    auto deviceId = channel->deviceId().toString();
+    std::lock_guard<std::mutex> lk(pimpl_->mapMutex_);
+    // Check if not already an incoming file for this id and that we are waiting this file
+    auto itV = pimpl_->vcards_.find(deviceId);
+    if (itV != pimpl_->vcards_.end()) {
+        channel->shutdown();
+        return;
+    }
+
+    DRing::DataTransferInfo info;
+    info.accountId = pimpl_->accountId_;
+    info.conversationId = pimpl_->to_;
+    info.path = fileutils::get_cache_dir() + DIR_SEPARATOR_STR + pimpl_->accountId_
+                + DIR_SEPARATOR_STR + "vcard" + DIR_SEPARATOR_STR + deviceId;
+
+    auto ifile = std::make_shared<IncomingFile>(std::move(channel), info, "profile.vcf", "");
+    auto res = pimpl_->vcards_.emplace(deviceId, std::move(ifile));
+    if (res.second) {
+        res.first->second->onFinished([w = weak(),
+                                       deviceId = std::move(deviceId),
+                                       accountId = pimpl_->accountId_,
+                                       path = info.path](uint32_t code) {
+            // schedule destroy transfer as not needed
+            dht::ThreadPool().computation().run([w,
+                                                 deviceId = std::move(deviceId),
+                                                 accountId = std::move(accountId),
+                                                 path = std::move(path),
+                                                 code] {
+                if (auto sthis_ = w.lock()) {
+                    auto& pimpl = sthis_->pimpl_;
+                    std::lock_guard<std::mutex> lk {pimpl->mapMutex_};
+                    auto itO = pimpl->vcards_.find(deviceId);
+                    if (itO != pimpl->vcards_.end())
+                        pimpl->vcards_.erase(itO);
+                    if (code == uint32_t(DRing::DataTransferEventCode::finished)) {
+                        auto cert = tls::CertificateStore::instance().getCertificate(deviceId);
+                        if (!cert)
+                            return emitSignal<DRing::ConfigurationSignal::ProfileReceived>(
+                                accountId, cert->getIssuerUID(), path);
+                    }
+                }
+            });
+        });
+        res.first->second->process();
+    }
+}
+
+std::vector<WaitingRequest>
+TransferManager::waitingRequests() const
 {
+    std::vector<WaitingRequest> res;
     std::lock_guard<std::mutex> lk(pimpl_->mapMutex_);
-    pimpl_->waitingIds_[id] = {sha3sum, path};
+    for (const auto& [fileId, req] : pimpl_->waitingIds_) {
+        auto itC = pimpl_->incomings_.find(fileId);
+        if (itC == pimpl_->incomings_.end())
+            res.emplace_back(req);
+    }
+    return res;
 }
 
 } // namespace jami
diff --git a/src/data_transfer.h b/src/data_transfer.h
index 75c6be8c6d..9b7aae9054 100644
--- a/src/data_transfer.h
+++ b/src/data_transfer.h
@@ -21,13 +21,18 @@
 #pragma once
 
 #include "dring/datatransfer_interface.h"
+#include "jamidht/multiplexed_socket.h"
 #include "noncopyable.h"
 
 #include <memory>
 #include <string>
+#include <fstream>
+#include <optional>
 
 namespace jami {
 
+DRing::DataTransferId generateUID();
+
 class Stream;
 
 struct IncomingFileInfo
@@ -36,41 +41,115 @@ struct IncomingFileInfo
     std::shared_ptr<Stream> stream;
 };
 
+struct WaitingRequest
+{
+    std::string fileId;
+    std::string interactionId;
+    std::string sha3sum;
+    std::string path;
+    std::size_t totalSize;
+    MSGPACK_DEFINE(fileId, interactionId, sha3sum, path, totalSize)
+};
+
 typedef std::function<void(const std::string&)> InternalCompletionCb;
-typedef std::function<bool(const std::string&)> OnVerifyCb;
 typedef std::function<void(const DRing::DataTransferId&, const DRing::DataTransferEventCode&)>
     OnStateChangedCb;
 
-class TransferManager
+class FileInfo
 {
 public:
-    TransferManager(const std::string& accountId, const std::string& to, bool isConversation = true);
+    FileInfo(const std::shared_ptr<ChannelSocket>& channel,
+             const std::string& fileId,
+             const std::string& interactionId,
+             const DRing::DataTransferInfo& info);
+    virtual ~FileInfo() {}
+    virtual void process() = 0;
+    std::shared_ptr<ChannelSocket> channel() const { return channel_; }
+    DRing::DataTransferInfo info() const { return info_; }
+    virtual void cancel() = 0;
+    void onFinished(std::function<void(uint32_t)>&& cb) { finishedCb_ = std::move(cb); }
+    void emit(DRing::DataTransferEventCode code);
+
+protected:
+    std::atomic_bool isUserCancelled_ {false};
+    std::string fileId_ {};
+    std::string interactionId_ {};
+    DRing::DataTransferInfo info_ {};
+    std::shared_ptr<ChannelSocket> channel_ {};
+    std::function<void(uint32_t)> finishedCb_ {};
+};
+
+class IncomingFile : public FileInfo
+{
+public:
+    IncomingFile(const std::shared_ptr<ChannelSocket>& channel,
+                 const DRing::DataTransferInfo& info,
+                 const std::string& fileId,
+                 const std::string& interactionId,
+                 const std::string& sha3Sum = "");
+    ~IncomingFile();
+    void process() override;
+    void cancel() override;
+
+private:
+    std::ofstream stream_;
+    std::string sha3Sum_ {};
+};
+
+class OutgoingFile : public FileInfo
+{
+public:
+    OutgoingFile(const std::shared_ptr<ChannelSocket>& channel,
+                 const std::string& fileId,
+                 const std::string& interactionId,
+                 const DRing::DataTransferInfo& info,
+                 size_t start = 0,
+                 size_t end = 0);
+    ~OutgoingFile();
+    void process() override;
+    void cancel() override;
+
+private:
+    std::ifstream stream_;
+    size_t start_ {0};
+    size_t end_ {0};
+};
+
+class TransferManager : public std::enable_shared_from_this<TransferManager>
+{
+public:
+    TransferManager(const std::string& accountId, const std::string& to);
     ~TransferManager();
 
     /**
      * Send a file
      * @param path      of the file
+     * @param peer      DeviceId for vcard or dest
      * @param icb       used for internal files (like vcard)
-     * @param deviceId  if we only want to transmit to one device
-     * @param resendId  if we need to resend a file, just specify previous id there.
      */
-    DRing::DataTransferId sendFile(const std::string& path,
-                                   const InternalCompletionCb& icb = {},
-                                   const std::string& deviceId = {},
-                                   DRing::DataTransferId resendId = {0});
+    /*[[deprecated("Non swarm method")]]*/ DRing::DataTransferId sendFile(
+        const std::string& path, const std::string& peer, const InternalCompletionCb& icb = {});
 
     /**
      * Accepts a transfer
      * @param id        of the transfer
      * @param path      of the file
      */
-    bool acceptFile(const DRing::DataTransferId& id, const std::string& path);
+    /*[[deprecated("Non swarm method")]]*/ bool acceptFile(const DRing::DataTransferId& id,
+                                                           const std::string& path);
 
     /**
-     * Refuse a transfer
+     * Inform the transfer manager that a new file is incoming
+     * @param info      of the transfer
      * @param id        of the transfer
+     * @param cb        callback to trigger when connected
+     * @param icb       used for vcard
      */
-    bool cancel(const DRing::DataTransferId& id);
+    /*[[deprecated("Non swarm method")]]*/ void onIncomingFileRequest(
+        const DRing::DataTransferInfo& info,
+        const DRing::DataTransferId& id,
+        const std::function<void(const IncomingFileInfo&)>& cb,
+        const InternalCompletionCb& icb = {});
 
     /**
      * Get current transfer infos
@@ -78,42 +157,84 @@ public:
      * @param info      to fill
      * @return if found
      */
-    bool info(const DRing::DataTransferId& id, DRing::DataTransferInfo& info) const noexcept;
+    /*[[deprecated("Non swarm method")]]*/ bool info(const DRing::DataTransferId& id,
+                                                     DRing::DataTransferInfo& info) const noexcept;
 
     /**
-     * Get current transfer progress
+     * Send a file to a channel
+     * @param channel       channel to use
+     * @param fileId        fileId of the transfer
+     * @param interactionId interactionId of the transfer
+     * @param path          path of the file
+     * @param start         start offset
+     * @param end           end
+     */
+    void transferFile(const std::shared_ptr<ChannelSocket>& channel,
+                      const std::string& fileId,
+                      const std::string& interactionId,
+                      const std::string& path,
+                      size_t start = 0,
+                      size_t end = 0);
+
+    /**
+     * Refuse a transfer
      * @param id        of the transfer
-     * @param total     size
-     * @param progress  current progress
-     * @return if found
      */
-    bool bytesProgress(const DRing::DataTransferId& id,
-                       int64_t& total,
-                       int64_t& progress) const noexcept;
+    bool cancel(const std::string& fileId);
 
     /**
-     * Inform the transfer manager that a new file is incoming
-     * @param info      of the transfer
+     * Get current transfer info
      * @param id        of the transfer
-     * @param cb        callback to trigger when connected
-     * @param icb       used for vcard
+     * @param total     size
+     * @param path      path of the file
+     * @param progress  current progress
+     * @return if found
      */
-    void onIncomingFileRequest(const DRing::DataTransferInfo& info,
-                               const DRing::DataTransferId& id,
-                               const std::function<void(const IncomingFileInfo&)>& cb,
-                               const InternalCompletionCb& icb = {});
+    bool info(const std::string& fileId,
+              std::string& path,
+              int64_t& total,
+              int64_t& progress) const noexcept;
 
     /**
      * Inform the transfer manager that a transfer is waited (and will be automatically accepted)
-     * @param id        of the transfer
-     * @param sha3sum   attended sha3sum
-     * @param path      where the file will be downloaded
+     * @param id              of the transfer
+     * @param interactionId   linked interaction
+     * @param sha3sum         attended sha3sum
+     * @param path            where the file will be downloaded
+     * @param total           total size of the file
      */
-    void waitForTransfer(const DRing::DataTransferId& id,
+    void waitForTransfer(const std::string& fileId,
+                         const std::string& interactionId,
                          const std::string& sha3sum,
-                         const std::string& path);
+                         const std::string& path,
+                         std::size_t total);
+
+    /**
+     * Handle incoming transfer
+     * @param id        Related id
+     * @param channel   Related channel
+     */
+    void onIncomingFileTransfer(const std::string& fileId,
+                                const std::shared_ptr<ChannelSocket>& channel);
+
+    /**
+     * Retrieve path of a file
+     * @param id
+     */
+    std::string path(const std::string& fileId) const;
+
+    /**
+     * Retrieve waiting files
+     * @return waiting list
+     */
+    std::vector<WaitingRequest> waitingRequests() const;
+    void onIncomingProfile(const std::shared_ptr<ChannelSocket>& channel);
 
 private:
+    std::weak_ptr<TransferManager> weak()
+    {
+        return std::static_pointer_cast<TransferManager>(shared_from_this());
+    }
     NON_COPYABLE(TransferManager);
     class Impl;
     std::unique_ptr<Impl> pimpl_;
diff --git a/src/dring/datatransfer_interface.h b/src/dring/datatransfer_interface.h
index ce59a3b8d7..c462ff36ad 100644
--- a/src/dring/datatransfer_interface.h
+++ b/src/dring/datatransfer_interface.h
@@ -108,7 +108,14 @@ struct DRING_PUBLIC DataTransferInfo
 /// DataTransferEvent signal for such event. There is no reserved or special values on
 /// DataTransferId type.
 ///
-DRING_PUBLIC DataTransferError sendFile(const DataTransferInfo& info, DataTransferId& id) noexcept;
+DRING_PUBLIC DataTransferError sendFileLegacy(const DataTransferInfo& info,
+                                              DataTransferId& tid) noexcept;
+
+DRING_PUBLIC void sendFile(const std::string& accountId,
+                           const std::string& conversationId,
+                           const std::string& path,
+                           const std::string& displayName,
+                           const std::string& parent) noexcept;
 
 /// Accept an incoming file transfer.
 ///
@@ -119,30 +126,27 @@ DRING_PUBLIC DataTransferError sendFile(const DataTransferInfo& info, DataTransf
 ///
 /// \param id data transfer identification value as given by a DataTransferEvent signal.
 /// \param file_path file path going to be open in binary write mode to put incoming data.
-/// \param offset used to indicate the remote side about the number of bytes already received in
-/// a previous transfer session, useful in transfer continuation mode.
 ///
 /// \return DataTransferError::invalid_argument if id is unknown.
 /// \note unknown \a id results to a no-op call.
 ///
 DRING_PUBLIC DataTransferError acceptFileTransfer(const std::string& accountId,
-                                                  const std::string& conversationId,
-                                                  const DataTransferId& id,
-                                                  const std::string& file_path,
-                                                  int64_t offset) noexcept;
+                                                  const std::string& fileId,
+                                                  const std::string& file_path) noexcept;
 
 /// Asks for retransferring a file. Generally this means that the file is missing
 /// from the conversation
 ///
 /// \param accountId
 /// \param conversationId
-/// \param interactionId
+/// \param fileId
 /// \param path
 ///
-DRING_PUBLIC void askForTransfer(const std::string& accountId,
-                                 const std::string& conversationUri,
-                                 const std::string& interactionId,
-                                 const std::string& path) noexcept;
+DRING_PUBLIC bool downloadFile(const std::string& accountId,
+                               const std::string& conversationId,
+                               const std::string& interactionId,
+                               const std::string& fileId,
+                               const std::string& path) noexcept;
 
 /// Refuse or abort an outgoing or an incoming file transfer.
 ///
@@ -158,7 +162,7 @@ DRING_PUBLIC void askForTransfer(const std::string& accountId,
 ///
 DataTransferError cancelDataTransfer(const std::string& accountId,
                                      const std::string& conversationId,
-                                     const DataTransferId& id) noexcept DRING_PUBLIC;
+                                     const std::string& fileId) noexcept DRING_PUBLIC;
 
 /// Return some information on given data transfer.
 ///
@@ -169,8 +173,7 @@ DataTransferError cancelDataTransfer(const std::string& accountId,
 /// \note \a info structure is in undefined state in case of error.
 ///
 DRING_PUBLIC DataTransferError dataTransferInfo(const std::string& accountId,
-                                                const std::string& conversationId,
-                                                const DataTransferId& id,
+                                                const std::string& fileId,
                                                 DataTransferInfo& info) noexcept;
 
 /// Return the amount of sent/received bytes of an existing data transfer.
@@ -182,11 +185,12 @@ DRING_PUBLIC DataTransferError dataTransferInfo(const std::string& accountId,
 /// \return DataTransferError::success if \a total and \a progress is set with valid values.
 /// DataTransferError::invalid_argument if the id is unknown.
 ///
-DRING_PUBLIC DataTransferError dataTransferBytesProgress(const std::string& accountId,
-                                                         const std::string& conversationId,
-                                                         const DataTransferId& id,
-                                                         int64_t& total,
-                                                         int64_t& progress) noexcept;
+DRING_PUBLIC DataTransferError fileTransferInfo(const std::string& accountId,
+                                                const std::string& conversationId,
+                                                const std::string& fileId,
+                                                std::string& path,
+                                                int64_t& total,
+                                                int64_t& progress) noexcept;
 
 // Signals
 struct DRING_PUBLIC DataTransferSignal
@@ -196,7 +200,8 @@ struct DRING_PUBLIC DataTransferSignal
         constexpr static const char* name = "DataTransferEvent";
         using cb_type = void(const std::string& accountId,
                              const std::string& conversationId,
-                             const DataTransferId& transferId,
+                             const std::string& interactionId,
+                             const std::string& fileId,
                              int eventCode);
     };
 };
diff --git a/src/fileutils.cpp b/src/fileutils.cpp
index 74a89d4cb5..c802152103 100644
--- a/src/fileutils.cpp
+++ b/src/fileutils.cpp
@@ -65,6 +65,7 @@
 #ifndef _WIN32
 #include <pwd.h>
 #else
+#include <filesystem>
 #include <shlobj.h>
 #define NAME_MAX 255
 #endif
@@ -76,7 +77,6 @@
 
 #include <sstream>
 #include <fstream>
-#include <filesystem>
 #include <iostream>
 #include <stdexcept>
 #include <limits>
@@ -317,11 +317,29 @@ writeTime(const std::string& path)
 }
 
 void
-createSymLink(const std::string& src, const std::string& dest)
+createSymLink(const std::string& linkFile, const std::string& target)
 {
-    check_dir(std::string(std::filesystem::path(src).parent_path()).c_str());
-    auto absolute_dest = std::string(std::filesystem::absolute(std::filesystem::path(dest)));
-    std::filesystem::create_symlink(absolute_dest, src);
+    auto sep = target.find_last_of('/');
+    if (sep != std::string::npos)
+        check_dir(target.substr(0, sep).c_str());
+#ifndef _WIN32
+    symlink(target.c_str(), linkFile.c_str());
+#else
+    std::error_code ec;
+    std::filesystem::create_symlink(target, linkFile, ec);
+#endif
+}
+
+std::string
+getFileExtension(const std::string& filename)
+{
+    std::string result = "";
+    auto sep = filename.find_last_of('.');
+    if (sep != std::string::npos && sep != filename.size() - 1)
+        result = filename.substr(sep + 1);
+    if (result.size() >= 8)
+        return {};
+    return result;
 }
 
 bool
@@ -961,7 +979,7 @@ size(const std::string& path)
     std::ifstream file;
     int64_t size;
     try {
-        openStream(file, path);
+        openStream(file, path, std::ios::binary | std::ios::in);
         file.seekg(0, std::ios_base::end);
         size = file.tellg();
         file.close();
@@ -980,7 +998,7 @@ sha3File(const std::string& path)
     try {
         if (!fileutils::isFile(path))
             return {};
-        openStream(file, path);
+        openStream(file, path, std::ios::binary | std::ios::in);
         if (!file)
             return {};
         std::vector<char> buffer(8192, 0);
diff --git a/src/fileutils.h b/src/fileutils.h
index c8f380d6fa..677ea18fed 100644
--- a/src/fileutils.h
+++ b/src/fileutils.h
@@ -87,6 +87,8 @@ std::chrono::system_clock::time_point writeTime(const std::string& path);
 
 void createSymLink(const std::string& src, const std::string& dest);
 
+std::string getFileExtension(const std::string& filename);
+
 /**
  * Read content of the directory.
  * The result is a list of relative (to @param dir) paths of all entries
diff --git a/src/ftp_server.cpp b/src/ftp_server.cpp
index 4af5d2f62e..385225fb3b 100644
--- a/src/ftp_server.cpp
+++ b/src/ftp_server.cpp
@@ -79,7 +79,7 @@ FtpServer::startNewFile()
         to = info_.peer;
 
     if (auto acc = Manager::instance().getAccount<JamiAccount>(info_.accountId)) {
-        acc->onIncomingFileRequest(
+        acc->dataTransfer()->onIncomingFileRequest(
             info_,
             transferId_,
             [w = weak()](const IncomingFileInfo& fileInfo) {
diff --git a/src/jamidht/connectionmanager.cpp b/src/jamidht/connectionmanager.cpp
index 12f474cbd7..c82fa823ea 100644
--- a/src/jamidht/connectionmanager.cpp
+++ b/src/jamidht/connectionmanager.cpp
@@ -114,10 +114,14 @@ public:
                                  const std::string& name,
                                  const dht::Value::Id& vid,
                                  const std::shared_ptr<dht::crypto::Certificate>& cert);
-    void connectDevice(const DeviceId& deviceId, const std::string& uri, ConnectCallback cb);
+    void connectDevice(const DeviceId& deviceId,
+                       const std::string& uri,
+                       ConnectCallback cb,
+                       bool noNewSocket = false);
     void connectDevice(const std::shared_ptr<dht::crypto::Certificate>& cert,
                        const std::string& name,
-                       ConnectCallback cb);
+                       ConnectCallback cb,
+                       bool noNewSocket = false);
     /**
      * Send a ChannelRequest on the TLS socket. Triggers cb when ready
      * @param sock      socket used to send the request
@@ -378,7 +382,8 @@ ConnectionManager::Impl::connectDeviceOnNegoDone(
 void
 ConnectionManager::Impl::connectDevice(const DeviceId& deviceId,
                                        const std::string& name,
-                                       ConnectCallback cb)
+                                       ConnectCallback cb,
+                                       bool noNewSocket)
 {
     if (!account.dht()) {
         cb(nullptr, deviceId);
@@ -389,7 +394,7 @@ ConnectionManager::Impl::connectDevice(const DeviceId& deviceId,
         return;
     }
     account.findCertificate(deviceId,
-                            [w = weak(), deviceId, name, cb = std::move(cb)](
+                            [w = weak(), deviceId, name, cb = std::move(cb), noNewSocket](
                                 const std::shared_ptr<dht::crypto::Certificate>& cert) {
                                 if (!cert) {
                                     JAMI_ERR("Invalid certificate found for device %s",
@@ -398,7 +403,7 @@ ConnectionManager::Impl::connectDevice(const DeviceId& deviceId,
                                     return;
                                 }
                                 if (auto shared = w.lock()) {
-                                    shared->connectDevice(cert, name, std::move(cb));
+                                    shared->connectDevice(cert, name, std::move(cb), noNewSocket);
                                 }
                             });
 }
@@ -406,10 +411,15 @@ ConnectionManager::Impl::connectDevice(const DeviceId& deviceId,
 void
 ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certificate>& cert,
                                        const std::string& name,
-                                       ConnectCallback cb)
+                                       ConnectCallback cb,
+                                       bool noNewSocket)
 {
     // Avoid dht operation in a DHT callback to avoid deadlocks
-    runOnMainThread([w = weak(), name = std::move(name), cert = std::move(cert), cb = std::move(cb)] {
+    runOnMainThread([w = weak(),
+                     name = std::move(name),
+                     cert = std::move(cert),
+                     cb = std::move(cb),
+                     noNewSocket] {
         auto deviceId = cert->getId();
         auto sthis = w.lock();
         if (!sthis || sthis->isDestroying_) {
@@ -460,6 +470,12 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif
             JAMI_DBG("Already connecting to %s, wait for the ICE negotiation", deviceId.to_c_str());
             return;
         }
+        if (noNewSocket) {
+            // If no new socket is specified, we don't try to generate a new socket
+            for (const auto& pending : sthis->extractPendingCallbacks(deviceId))
+                pending.cb(nullptr, deviceId);
+            return;
+        }
 
         // Note: used when the ice negotiation fails to erase
         // all stored structures.
@@ -542,8 +558,9 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif
             ice_config.master = false;
             ice_config.streamsCount = JamiAccount::ICE_STREAMS_COUNT;
             ice_config.compCountPerStream = JamiAccount::ICE_COMP_COUNT_PER_STREAM;
-            info->ice_ = Manager::instance().getIceTransportFactory().createUTransport(
-                sthis->account.getAccountID().c_str(), ice_config);
+            info->ice_ = Manager::instance()
+                             .getIceTransportFactory()
+                             .createUTransport(sthis->account.getAccountID().c_str(), ice_config);
 
             if (!info->ice_) {
                 JAMI_ERR("Cannot initialize ICE session.");
@@ -894,8 +911,8 @@ ConnectionManager::Impl::onDhtPeerRequest(const PeerConnectionRequest& req,
         ice_config.compCountPerStream = JamiAccount::ICE_COMP_COUNT_PER_STREAM;
         ice_config.master = true;
         info->ice_ = Manager::instance()
-                        .getIceTransportFactory()
-                        .createUTransport(shared->account.getAccountID().c_str(), ice_config);
+                         .getIceTransportFactory()
+                         .createUTransport(shared->account.getAccountID().c_str(), ice_config);
         if (not info->ice_) {
             JAMI_ERR("Cannot initialize ICE session.");
             if (shared->connReadyCb_)
@@ -965,17 +982,19 @@ ConnectionManager::~ConnectionManager()
 void
 ConnectionManager::connectDevice(const DeviceId& deviceId,
                                  const std::string& name,
-                                 ConnectCallback cb)
+                                 ConnectCallback cb,
+                                 bool noNewSocket)
 {
-    pimpl_->connectDevice(deviceId, name, std::move(cb));
+    pimpl_->connectDevice(deviceId, name, std::move(cb), noNewSocket);
 }
 
 void
 ConnectionManager::connectDevice(const std::shared_ptr<dht::crypto::Certificate>& cert,
                                  const std::string& name,
-                                 ConnectCallback cb)
+                                 ConnectCallback cb,
+                                 bool noNewSocket)
 {
-    pimpl_->connectDevice(cert, name, std::move(cb));
+    pimpl_->connectDevice(cert, name, std::move(cb), noNewSocket);
 }
 
 bool
diff --git a/src/jamidht/connectionmanager.h b/src/jamidht/connectionmanager.h
index 9239601a7d..8f2c649284 100644
--- a/src/jamidht/connectionmanager.h
+++ b/src/jamidht/connectionmanager.h
@@ -84,11 +84,16 @@ public:
      * @param deviceId      Remote device
      * @param name          Name of the channel
      * @param cb            Callback called when socket is ready ready
+     * @param noNewSocket   Do not negotiate a new socekt if there is none
      */
-    void connectDevice(const DeviceId& deviceId, const std::string& name, ConnectCallback cb);
+    void connectDevice(const DeviceId& deviceId,
+                       const std::string& name,
+                       ConnectCallback cb,
+                       bool noNewSocket = false);
     void connectDevice(const std::shared_ptr<dht::crypto::Certificate>& cert,
                        const std::string& name,
-                       ConnectCallback cb);
+                       ConnectCallback cb,
+                       bool noNewSocket = false);
 
     /**
      * Check if we are already connecting to a device with a specific name
diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index 793c680468..b5a2948e18 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -24,6 +24,7 @@
 #include "conversationrepository.h"
 #include "client/ring_signal.h"
 
+#include <charconv>
 #include <json/json.h>
 #include <string_view>
 #include <opendht/thread_pool.h>
@@ -118,6 +119,9 @@ public:
         if (!repository_) {
             throw std::logic_error("Couldn't create repository");
         }
+        if (auto shared = account_.lock())
+            transferManager_ = std::make_shared<TransferManager>(shared->getAccountID(),
+                                                                 repository_->id());
     }
 
     Impl(const std::weak_ptr<JamiAccount>& account, const std::string& conversationId)
@@ -127,6 +131,9 @@ public:
         if (!repository_) {
             throw std::logic_error("Couldn't create repository");
         }
+        if (auto shared = account_.lock())
+            transferManager_ = std::make_shared<TransferManager>(shared->getAccountID(),
+                                                                 repository_->id());
     }
 
     Impl(const std::weak_ptr<JamiAccount>& account,
@@ -144,6 +151,9 @@ public:
             }
             throw std::logic_error("Couldn't clone repository");
         }
+        if (auto shared = account_.lock())
+            transferManager_ = std::make_shared<TransferManager>(shared->getAccountID(),
+                                                                 repository_->id());
     }
     ~Impl() = default;
 
@@ -237,6 +247,7 @@ public:
     std::mutex pullcbsMtx_ {};
     std::set<std::string> fetchingRemotes_ {}; // store current remote in fetch
     std::deque<std::tuple<std::string, std::string, OnPullCb>> pullcbs_ {};
+    std::shared_ptr<TransferManager> transferManager_ {};
 };
 
 bool
@@ -320,6 +331,13 @@ Conversation::Impl::convCommitToMap(const ConversationCommit& commit) const
             JAMI_WARN("%s", err.c_str());
         }
     }
+    if (type == "application/data-transfer+json") {
+        // Avoid the client to do the concatenation
+        message["fileId"] = commit.id + "_" + message["tid"];
+        auto extension = fileutils::getFileExtension(message["displayName"]);
+        if (!extension.empty())
+            message["fileId"] += "." + extension;
+    }
     auto linearizedParent = repository_->linearizedParent(commit.id);
     message["id"] = commit.id;
     message["parents"] = parents;
@@ -698,12 +716,12 @@ Conversation::mergeHistory(const std::string& uri)
 }
 
 void
-Conversation::pull(const std::string& uri, OnPullCb&& cb, std::string commitId)
+Conversation::pull(const std::string& deviceId, OnPullCb&& cb, std::string commitId)
 {
     std::lock_guard<std::mutex> lk(pimpl_->pullcbsMtx_);
     auto isInProgress = not pimpl_->pullcbs_.empty();
     pimpl_->pullcbs_.emplace_back(
-        std::make_tuple<std::string, std::string, OnPullCb>(std::string(uri),
+        std::make_tuple<std::string, std::string, OnPullCb>(std::string(deviceId),
                                                             std::move(commitId),
                                                             std::move(cb)));
     if (isInProgress)
@@ -775,6 +793,28 @@ Conversation::pull(const std::string& uri, OnPullCb&& cb, std::string commitId)
     });
 }
 
+void
+Conversation::sync(const std::string& member,
+                   const std::string& deviceId,
+                   OnPullCb&& cb,
+                   std::string commitId)
+{
+    JAMI_INFO() << "Sync " << id() << " with " << deviceId;
+    pull(deviceId, std::move(cb), commitId);
+    // For waiting request, downloadFile
+    for (const auto& wr : dataTransfer()->waitingRequests())
+        downloadFile(wr.interactionId, wr.fileId, wr.path, member, deviceId);
+    // VCard sync for member
+    if (auto account = pimpl_->account_.lock()) {
+        if (not account->needToSendProfile(deviceId)) {
+            JAMI_INFO() << "Peer " << deviceId << " already got an up-to-date vcard";
+            return;
+        }
+        // We need a new channel
+        account->transferFile(id(), std::string(account->profilePath()), deviceId, "profile.vcf", "");
+    }
+}
+
 std::map<std::string, std::string>
 Conversation::generateInvitation() const
 {
@@ -871,4 +911,98 @@ Conversation::vCard() const
     return {};
 }
 
+std::shared_ptr<TransferManager>
+Conversation::dataTransfer() const
+{
+    return pimpl_->transferManager_;
+}
+
+bool
+Conversation::onFileChannelRequest(const std::string& member,
+                                   const std::string& fileId,
+                                   bool verifyShaSum) const
+{
+    if (!isMember(member))
+        return false;
+
+    auto account = pimpl_->account_.lock();
+    if (!account)
+        return false;
+
+    auto sep = fileId.find('_');
+    if (sep == std::string::npos)
+        return false;
+
+    auto interactionId = fileId.substr(0, sep);
+    auto commit = getCommit(interactionId);
+    if (commit == std::nullopt || commit->find("type") == commit->end()
+        || commit->find("tid") == commit->end() || commit->find("sha3sum") == commit->end()
+        || commit->at("type") != "application/data-transfer+json")
+        return false;
+
+    auto path = dataTransfer()->path(fileId);
+
+    if (!fileutils::isFile(path)) {
+        // Check if dangling symlink
+        if (fileutils::isSymLink(path)) {
+            fileutils::remove(path, true);
+        }
+        JAMI_DBG("[Account %s] %s asked for non existing file %s in %s",
+                 account->getAccountID().c_str(),
+                 member.c_str(),
+                 fileId.c_str(),
+                 id().c_str());
+        return false;
+    }
+    // Check that our file is correct before sending
+    if (verifyShaSum && commit->at("sha3sum") != fileutils::sha3File(path)) {
+        JAMI_DBG("[Account %s] %s asked for file %s in %s, but our version is not complete",
+                 account->getAccountID().c_str(),
+                 member.c_str(),
+                 fileId.c_str(),
+                 id().c_str());
+        return false;
+    }
+    return true;
+}
+
+bool
+Conversation::downloadFile(const std::string& interactionId,
+                           const std::string& fileId,
+                           const std::string& path,
+                           const std::string&,
+                           const std::string& deviceId,
+                           std::size_t start,
+                           std::size_t end)
+{
+    auto commit = getCommit(interactionId);
+    if (commit == std::nullopt || commit->find("type") == commit->end()
+        || commit->find("sha3sum") == commit->end() || commit->find("tid") == commit->end()
+        || commit->at("type") != "application/data-transfer+json") {
+        JAMI_ERR() << "Cannot download file without linked interaction " << fileId;
+        return false;
+    }
+    auto sha3sum = commit->at("sha3sum");
+    auto size_str = commit->at("totalSize");
+    std::size_t totalSize;
+    std::from_chars(size_str.data(), size_str.data() + size_str.size(), totalSize);
+
+    // Be sure to not lock conversation
+    dht::ThreadPool().io().run(
+        [w = weak(), deviceId, fileId, interactionId, sha3sum, path, totalSize, start, end] {
+            if (auto shared = w.lock()) {
+                auto acc = shared->pimpl_->account_.lock();
+                if (!acc)
+                    return;
+                shared->dataTransfer()->waitForTransfer(fileId,
+                                                        interactionId,
+                                                        sha3sum,
+                                                        path,
+                                                        totalSize);
+                acc->askForFileChannel(shared->id(), deviceId, fileId, start, end);
+            }
+        });
+    return true;
+}
+
 } // namespace jami
\ No newline at end of file
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index 84719ad023..d63046b494 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -27,6 +27,8 @@
 #include <json/json.h>
 #include <msgpack.hpp>
 
+#include "dring/datatransfer_interface.h"
+
 namespace jami {
 
 /**
@@ -71,6 +73,8 @@ struct ConvInfo
 
 class JamiAccount;
 class ConversationRepository;
+class TransferManager;
+class ChannelSocket;
 enum class ConversationMode;
 
 using OnPullCb = std::function<void(bool fetchOk)>;
@@ -177,11 +181,22 @@ public:
 
     /**
      * Fetch and merge from peer
-     * @param uri       Peer
+     * @param deviceId  Peer device
      * @param cb        On pulled callback
      * @param commitId  Commit id that triggered this fetch
      */
-    void pull(const std::string& uri, OnPullCb&& cb, std::string commitId = "");
+    void pull(const std::string& deviceId, OnPullCb&& cb, std::string commitId = "");
+    /**
+     * Fetch new commits and re-ask for waiting files
+     * @param member
+     * @param deviceId
+     * @param cb        cf pull()
+     * @param commitId  cf pull()
+     */
+    void sync(const std::string& member,
+              const std::string& deviceId,
+              OnPullCb&& cb,
+              std::string commitId = "");
 
     /**
      * Generate an invitation to send to new contacts
@@ -240,6 +255,42 @@ public:
     std::map<std::string, std::string> infos() const;
     std::vector<uint8_t> vCard() const;
 
+    /////// File transfer
+
+    /**
+     * Access to transfer manager
+     */
+    std::shared_ptr<TransferManager> dataTransfer() const;
+
+    /**
+     * Choose if we can accept channel request
+     * @param member        member to check
+     * @param fileId        file transfer to check (needs to be waiting)
+     * @param verifyShaSum  for debug only
+     * @return if we accept the channel request
+     */
+    bool onFileChannelRequest(const std::string& member,
+                              const std::string& fileId,
+                              bool verifyShaSum = true) const;
+    /**
+     * Adds a file to the waiting list and ask members
+     * @param interactionId     Related interaction id
+     * @param fileId            Related id
+     * @param path              Destination
+     * @param member            Member if we know from who to pull file
+     * @param deviceId          Device if we know from who to pull file
+     * @param start             Offset (unused for now)
+     * @param end               Offset (unused)
+     * @return id of the file
+     */
+    bool downloadFile(const std::string& interactionId,
+                      const std::string& fileId,
+                      const std::string& path,
+                      const std::string& member = "",
+                      const std::string& deviceId = "",
+                      std::size_t start = 0,
+                      std::size_t end = 0);
+
 private:
     std::shared_ptr<Conversation> shared()
     {
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index f663caca88..edea76f3d7 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -314,6 +314,7 @@ JamiAccount::JamiAccount(const std::string& accountID, bool /* presenceEnabled *
 
     proxyListUrl_ = DHT_DEFAULT_PROXY_LIST_URL;
     proxyServer_ = DHT_DEFAULT_PROXY;
+    nonSwarmTransferManager_ = std::make_shared<TransferManager>(getAccountID(), "");
 
     try {
         std::istringstream is(fileutils::loadCacheTextFile(cachePath_ + DIR_SEPARATOR_STR "dhtproxy",
@@ -359,10 +360,6 @@ JamiAccount::shutdownConnections()
                                        std::move(connectionManager_))] {});
         connectionManager_.reset();
     }
-    {
-        std::unique_lock<std::mutex> lk(transferMutex_);
-        transferManagers_.clear();
-    }
     {
         std::unique_lock<std::mutex> lk(syncConnectionsMtx_);
         syncConnections_.clear();
@@ -379,8 +376,8 @@ JamiAccount::flush()
     // Class base method
     SIPAccountBase::flush();
 
-    fileutils::removeAll(dataPath_);
     fileutils::removeAll(cachePath_);
+    fileutils::removeAll(dataPath_);
     fileutils::removeAll(idPath_, true);
 }
 
@@ -1188,12 +1185,25 @@ JamiAccount::loadAccount(const std::string& archive_password,
                     return;
                 }
             }
+            if (saveReq && !conversationId.empty()) {
+                ConversationRequest req;
+                req.from = uri;
+                req.conversationId = conversationId;
+                req.received = std::time(nullptr);
+                auto details = vCard::utils::toMap(
+                    std::string_view(reinterpret_cast<const char*>(payload.data()), payload.size()));
+                req.metadatas = ConversationRepository::infosFromVCard(details);
+                accountManager_->addConversationRequest(conversationId, std::move(req));
+                emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(getAccountID(),
+                                                                                   conversationId,
+                                                                                   req.toMap());
+            }
             emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(getAccountID(),
                                                                          conversationId,
                                                                          uri,
                                                                          payload,
                                                                          received);
-            if (!saveReq && !conversationId.empty())
+            if (!saveReq || conversationId.empty())
                 return;
             dht::ThreadPool::io().run(
                 [w = weak(), uri, payload = std::move(payload), received, conversationId] {
@@ -1927,7 +1937,7 @@ JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy)
         return;
     }
     buddy.listenToken
-        = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&&, bool expired) {
+        = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&& dev, bool expired) {
               bool wasConnected, isConnected;
               {
                   std::lock_guard<std::mutex> lock(buddyInfoMtx);
@@ -1944,6 +1954,7 @@ JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy)
               if (not expired) {
                   // Retry messages every time a new device announce its presence
                   messageEngine_.onPeerOnline(h.toString());
+                  requestSIPConnection(h.toString(), dev.dev);
               }
               if (isConnected and not wasConnected) {
                   onTrackedBuddyOnline(h);
@@ -1997,6 +2008,22 @@ JamiAccount::onTrackedBuddyOffline(const dht::InfoHash& contactId)
                                                             "");
 }
 
+void
+JamiAccount::syncConversations(const std::string& peer, const std::string& deviceId)
+{
+    // Sync conversations where peer is member
+    std::set<std::string> convIds;
+    {
+        std::unique_lock<std::mutex> lk(conversationsMtx_);
+        for (const auto& [cid, conv] : conversations_) {
+            if (conv->isMember(peer, false))
+                convIds.emplace(cid);
+        }
+    }
+    for (const auto& cid : convIds)
+        fetchNewCommits(peer, deviceId, cid);
+}
+
 void
 JamiAccount::doRegister_()
 {
@@ -2153,35 +2180,37 @@ JamiAccount::doRegister_()
         context.identityAnnouncedCb = [this](bool ok) {
             if (!ok)
                 return;
-            accountManager_->startSync([this](const std::shared_ptr<dht::crypto::Certificate>& crt) {
-                if (!crt)
-                    return;
-                auto deviceId = crt->getId().toString();
-                if (accountManager_->getInfo()->deviceId == deviceId)
-                    return;
+            accountManager_->startSync(
+                [this](const std::shared_ptr<dht::crypto::Certificate>& crt) {
+                    if (!crt)
+                        return;
+                    auto deviceId = crt->getId().toString();
+                    if (accountManager_->getInfo()->deviceId == deviceId)
+                        return;
 
-                std::lock_guard<std::mutex> lk(connManagerMtx_);
-                if (!connectionManager_)
-                    connectionManager_ = std::make_unique<ConnectionManager>(*this);
-                auto channelName = "sync://" + deviceId;
-                if (connectionManager_->isConnecting(crt->getId(), channelName)) {
-                    JAMI_INFO("[Account %s] Already connecting to %s",
-                              getAccountID().c_str(),
-                              deviceId.c_str());
-                    return;
-                }
-                connectionManager_->connectDevice(crt,
-                                                  channelName,
-                                                  [this](std::shared_ptr<ChannelSocket> socket,
-                                                         const DeviceId& deviceId) {
-                                                      if (socket)
-                                                          syncWith(deviceId.toString(), socket);
-                                                  });
-            }, [this] {
-                deviceAnnounced_ = true;
-                emitSignal<DRing::ConfigurationSignal::VolatileDetailsChanged>(
-                    accountID_, getVolatileAccountDetails());
-            });
+                    std::lock_guard<std::mutex> lk(connManagerMtx_);
+                    if (!connectionManager_)
+                        connectionManager_ = std::make_unique<ConnectionManager>(*this);
+                    auto channelName = "sync://" + deviceId;
+                    if (connectionManager_->isConnecting(crt->getId(), channelName)) {
+                        JAMI_INFO("[Account %s] Already connecting to %s",
+                                  getAccountID().c_str(),
+                                  deviceId.c_str());
+                        return;
+                    }
+                    connectionManager_->connectDevice(crt,
+                                                      channelName,
+                                                      [this](std::shared_ptr<ChannelSocket> socket,
+                                                             const DeviceId& deviceId) {
+                                                          if (socket)
+                                                              syncWith(deviceId.toString(), socket);
+                                                      });
+                },
+                [this] {
+                    deviceAnnounced_ = true;
+                    emitSignal<DRing::ConfigurationSignal::VolatileDetailsChanged>(
+                        accountID_, getVolatileAccountDetails());
+                });
         };
 
         setRegistrationState(RegistrationState::TRYING);
@@ -2246,15 +2275,53 @@ JamiAccount::doRegister_()
                 auto result = fut.get();
                 return result;
             } else if (isFile or isVCard) {
-                auto tid_str = isFile ? name.substr(7) : name.substr(8);
-                uint64_t tid;
-                std::istringstream iss(tid_str);
-                iss >> tid;
+                auto tid = isFile ? name.substr(7) : name.substr(8);
                 std::lock_guard<std::mutex> lk(transfersMtx_);
-                incomingFileTransfers_.emplace(tid_str);
+                incomingFileTransfers_.emplace(tid);
                 return true;
             } else if (isDataTransfer) {
-                return true; // Nothing to do there, will pass the signal when the co will be ready
+                // Check if sync request is from same account
+                std::promise<bool> accept;
+                std::future<bool> fut = accept.get_future();
+
+                auto idstr = name.substr(16);
+                auto sep = idstr.find('/');
+                auto lastSep = idstr.find_last_of('/');
+                auto conversationId = idstr.substr(0, sep);
+                auto fileHost = idstr.substr(sep + 1, lastSep - sep - 1);
+                auto fileId = idstr.substr(lastSep + 1);
+                if (fileHost == currentDeviceId())
+                    return false;
+
+                sep = fileId.find_last_of('?');
+                if (sep != std::string::npos) {
+                    fileId = fileId.substr(0, sep);
+                }
+
+                accountManager_->findCertificate(
+                    deviceId, [&](const std::shared_ptr<dht::crypto::Certificate>& cert) {
+                        if (not cert) {
+                            accept.set_value(false);
+                            return;
+                        }
+                        // Check if peer is member of the conversation
+                        std::unique_lock<std::mutex> lk(conversationsMtx_);
+                        auto conversation = conversations_.find(conversationId);
+                        if (conversation == conversations_.end() or not conversation->second) {
+                            accept.set_value(false);
+                            return;
+                        }
+                        if (fileId == "profile.vcf") {
+                            accept.set_value(conversation->second->isMember(cert->getIssuerUID()));
+                        } else {
+                            accept.set_value(
+                                conversation->second->onFileChannelRequest(cert->getIssuerUID(),
+                                                                           fileId,
+                                                                           !noSha3sumVerification_));
+                        }
+                    });
+                fut.wait();
+                return fut.get();
             }
             return false;
         });
@@ -2273,17 +2340,14 @@ JamiAccount::doRegister_()
                 } else if (name.find("sync://") == 0) {
                     cacheSyncConnection(std::move(channel), peerId, deviceId);
                 } else if (isFile or isVCard) {
-                    auto tid_str = isFile ? name.substr(7) : name.substr(8);
+                    auto tid = isFile ? name.substr(7) : name.substr(8);
                     std::unique_lock<std::mutex> lk(transfersMtx_);
-                    auto it = incomingFileTransfers_.find(tid_str);
+                    auto it = incomingFileTransfers_.find(tid);
                     // Note, outgoing file transfers are ignored.
                     if (it == incomingFileTransfers_.end())
                         return;
                     incomingFileTransfers_.erase(it);
                     lk.unlock();
-                    uint64_t tid;
-                    std::istringstream iss(tid_str);
-                    iss >> tid;
                     std::function<void(const std::string&)> cb;
                     if (isVCard)
                         cb = [peerId, accountId = getAccountID()](const std::string& path) {
@@ -2292,29 +2356,17 @@ JamiAccount::doRegister_()
                                                                                     path);
                         };
 
-                    std::unique_lock<std::mutex> lk2(transferMutex_);
-                    auto itManager = transferManagers_.find(peerId);
-                    if (itManager == transferManagers_.end()) {
-                        std::string accId = getAccountID();
-                        auto res = transferManagers_.emplace(std::piecewise_construct,
-                                                             std::forward_as_tuple(peerId),
-                                                             std::forward_as_tuple(accId,
-                                                                                   peerId,
-                                                                                   false));
-                        if (!res.second) {
-                            JAMI_ERR("Couldn't create manager for peer %s", peerId.c_str());
-                            return;
-                        }
-                        itManager = res.first;
-                    }
-
                     DRing::DataTransferInfo info;
                     info.accountId = getAccountID();
                     info.peer = peerId;
-                    dhtPeerConnector_->onIncomingConnection(info,
-                                                            tid,
-                                                            std::move(channel),
-                                                            std::move(cb));
+                    try {
+                        dhtPeerConnector_->onIncomingConnection(info,
+                                                                std::stoull(tid),
+                                                                std::move(channel),
+                                                                std::move(cb));
+                    } catch (...) {
+                        JAMI_ERR() << "Invalid tid: " << tid;
+                    }
 
                 } else if (name.find("git://") == 0) {
                     auto sep = name.find_last_of('/');
@@ -2339,6 +2391,7 @@ JamiAccount::doRegister_()
                                       "conversation (%s)",
                                       getAccountID().c_str(),
                                       conversationId.c_str());
+                            channel->shutdown();
                             return;
                         }
                         if (conversation->second->isBanned(remoteDevice)) {
@@ -2346,6 +2399,7 @@ JamiAccount::doRegister_()
                                       getAccountID().c_str(),
                                       remoteDevice.c_str(),
                                       conversationId.c_str());
+                            channel->shutdown();
                             return;
                         }
                     }
@@ -2394,32 +2448,48 @@ JamiAccount::doRegister_()
                     auto fileId = idstr.substr(lastSep + 1);
                     if (fileHost == currentDeviceId()) // This means we are the host, so the file is
                                                        // outgoing, ignore
+                    {
                         return;
-                    std::unique_lock<std::mutex> lk(transferMutex_);
-                    auto it = transferManagers_.find(conversationId);
-                    if (it == transferManagers_.end()) {
-                        std::string accId = getAccountID();
-                        auto res = transferManagers_.emplace(std::piecewise_construct,
-                                                             std::forward_as_tuple(conversationId),
-                                                             std::forward_as_tuple(accId,
-                                                                                   conversationId));
-                        if (!res.second) {
-                            JAMI_ERR("Couldn't create manager for conversation %s",
-                                     conversationId.c_str());
-                            return;
+                    }
+
+                    sep = fileId.find_last_of('?');
+                    std::string arguments;
+                    if (sep != std::string::npos) {
+                        arguments = fileId.substr(sep + 1);
+                        fileId = fileId.substr(0, sep);
+                    }
+
+                    if (fileId == "profile.vcf") {
+                        std::string path = fileutils::sha3File(idPath_ + DIR_SEPARATOR_STR
+                                                               + "profile.vcf");
+                        dataTransfer()->transferFile(channel, fileId, "", path);
+                        return;
+                    }
+                    auto dt = dataTransfer(conversationId);
+                    sep = fileId.find('_');
+                    if (!dt or sep == std::string::npos) {
+                        channel->shutdown();
+                        return;
+                    }
+                    auto interactionId = fileId.substr(0, sep);
+                    std::string path = dt->path(fileId);
+                    auto start = 0u, end = 0u;
+                    for (const auto arg : jami::split_string(arguments, '&')) {
+                        auto keyVal = jami::split_string(arg, '=');
+                        if (keyVal.size() == 2) {
+                            if (keyVal[0] == "start") {
+                                std::from_chars(keyVal[1].data(),
+                                                keyVal[1].data() + keyVal[1].size(),
+                                                start);
+                            } else if (keyVal[0] == "end") {
+                                std::from_chars(keyVal[1].data(),
+                                                keyVal[1].data() + keyVal[1].size(),
+                                                end);
+                            }
                         }
-                        it = res.first;
                     }
-                    uint64_t tid;
-                    std::istringstream iss(fileId);
-                    iss >> tid;
 
-                    DRing::DataTransferInfo info;
-                    info.accountId = getAccountID();
-                    info.author = peerId;
-                    info.peer = peerId;
-                    info.conversationId = conversationId;
-                    dhtPeerConnector_->onIncomingConnection(info, tid, std::move(channel));
+                    dt->transferFile(channel, fileId, interactionId, path, start, end);
                 }
             }
         });
@@ -2495,6 +2565,7 @@ JamiAccount::doRegister_()
                                                     if (!shared)
                                                         return;
                                                     shared->cloneConversation(dev.toString(),
+                                                                              member,
                                                                               convId);
                                                 });
                                     },
@@ -2541,12 +2612,14 @@ JamiAccount::doUnregister(std::function<void(bool)> released_cb)
     bool shutdown_complete {false};
 
     JAMI_WARN("[Account %s] unregistering account %p", getAccountID().c_str(), this);
-    dht_->shutdown([&] {
-        JAMI_WARN("[Account %s] dht shutdown complete", getAccountID().c_str());
-        std::lock_guard<std::mutex> lock(mtx);
-        shutdown_complete = true;
-        cv.notify_all();
-    }, true);
+    dht_->shutdown(
+        [&] {
+            JAMI_WARN("[Account %s] dht shutdown complete", getAccountID().c_str());
+            std::lock_guard<std::mutex> lock(mtx);
+            shutdown_complete = true;
+            cv.notify_all();
+        },
+        true);
 
     {
         std::lock_guard<std::mutex> lk(pendingCallsMutex_);
@@ -2567,7 +2640,7 @@ JamiAccount::doUnregister(std::function<void(bool)> released_cb)
 
     {
         std::unique_lock<std::mutex> lock(mtx);
-        cv.wait(lock, [&]{ return shutdown_complete; });
+        cv.wait(lock, [&] { return shutdown_complete; });
     }
     dht_->join();
     setRegistrationState(RegistrationState::UNREGISTERED);
@@ -2720,8 +2793,9 @@ JamiAccount::saveTreatedMessages() const
             auto& this_ = *sthis;
             std::lock_guard<std::mutex> lock(this_.messageMutex_);
             fileutils::check_dir(this_.cachePath_.c_str());
-            saveIdList<decltype(this_.treatedMessages_)>(this_.cachePath_ + DIR_SEPARATOR_STR "treatedMessages",
-                                    this_.treatedMessages_);
+            saveIdList<decltype(this_.treatedMessages_)>(this_.cachePath_
+                                                             + DIR_SEPARATOR_STR "treatedMessages",
+                                                         this_.treatedMessages_);
         }
     });
 }
@@ -3385,6 +3459,67 @@ JamiAccount::sendTextMessage(const std::string& to,
         std::chrono::minutes(1));
 }
 
+void
+JamiAccount::sendSIPMessageToDevice(const std::string& to,
+                                    const DeviceId& deviceId,
+                                    const std::map<std::string, std::string>& payloads)
+{
+    std::lock_guard<std::mutex> lk(sipConnsMtx_);
+    sip_utils::register_thread();
+
+    for (auto it = sipConns_.begin(); it != sipConns_.end();) {
+        auto& [key, value] = *it;
+        if (key.first == to && key.second != deviceId) {
+            ++it;
+            continue;
+        }
+        auto& conn = value.back();
+        auto& channel = conn.channel;
+
+        // Set input token into callback
+        std::unique_ptr<TextMessageCtx> ctx {std::make_unique<TextMessageCtx>()};
+        ctx->acc = weak();
+        ctx->to = to;
+        ctx->deviceId = key.second;
+        ctx->channel = channel;
+
+        try {
+            auto res = sendSIPMessage(
+                conn, to, ctx.release(), {}, payloads, [](void* token, pjsip_event* event) {
+                    std::unique_ptr<TextMessageCtx> c {(TextMessageCtx*) token};
+                    auto code = event->body.tsx_state.tsx->status_code;
+                    auto acc = c->acc.lock();
+                    if (not acc)
+                        return;
+
+                    if (code == PJSIP_SC_OK) {
+                        std::unique_lock<std::mutex> l(c->confirmation->lock);
+                        c->confirmation->replied = true;
+                        l.unlock();
+                        if (!c->onlyConnected)
+                            acc->messageEngine_.onMessageSent(c->to, c->id, true);
+                    } else {
+                        JAMI_WARN("Timeout when send a message, close current connection");
+                        acc->shutdownSIPConnection(c->channel, c->to, c->deviceId);
+                    }
+                });
+            if (!res) {
+                ++it;
+                continue;
+            }
+            break;
+        } catch (const std::runtime_error& ex) {
+            JAMI_WARN("%s", ex.what());
+            ++it;
+            // Remove connection in incorrect state
+            shutdownSIPConnection(channel, to, key.second);
+            continue;
+        }
+
+        ++it;
+    }
+}
+
 void
 JamiAccount::onIsComposing(const std::string& conversationId,
                            const std::string& peer,
@@ -3460,31 +3595,10 @@ JamiAccount::requestConnection(
     bool isVCard,
     const std::function<void(const std::shared_ptr<ChanneledOutgoingTransfer>&)>&
         channeledConnectedCb,
-    const std::function<void(const std::string&)>& onChanneledCancelled,
-    bool addToHistory)
+    const std::function<void(const std::string&)>& onChanneledCancelled)
 {
     if (not dhtPeerConnector_) {
-        runOnMainThread([w = weak(), onChanneledCancelled, info] {
-            if (auto shared = w.lock()) {
-                if (!info.conversationId.empty()) {
-                    std::unique_lock<std::mutex> lk(shared->conversationsMtx_);
-                    auto it = shared->conversations_.find(info.conversationId);
-                    if (it == shared->conversations_.end()) {
-                        JAMI_ERR("Conversation %s doesn't exist", info.conversationId.c_str());
-                        return;
-                    }
-                    auto isOneToOne = it->second->mode() == ConversationMode::ONE_TO_ONE;
-
-                    auto members = it->second->getMembers(
-                        isOneToOne /* In this case, also send to the invited */);
-                    lk.unlock();
-                    for (const auto& member : members)
-                        onChanneledCancelled(member.at("uri"));
-                } else {
-                    onChanneledCancelled(info.peer);
-                }
-            }
-        });
+        runOnMainThread([onChanneledCancelled, info] { onChanneledCancelled(info.peer); });
         return;
     }
     dhtPeerConnector_->requestConnection(info,
@@ -3492,22 +3606,6 @@ JamiAccount::requestConnection(
                                          isVCard,
                                          channeledConnectedCb,
                                          onChanneledCancelled);
-    if (!info.conversationId.empty() && addToHistory) {
-        // NOTE: this sendMessage is in a computation thread because
-        // sha3sum can take quite some time to computer if the user decide
-        // to send a big file
-        dht::ThreadPool::computation().run([w = weak(), info, tid]() {
-            if (auto shared = w.lock()) {
-                Json::Value value;
-                value["tid"] = std::to_string(tid);
-                value["displayName"] = info.displayName;
-                value["totalSize"] = std::to_string(fileutils::size(info.path));
-                value["sha3sum"] = fileutils::sha3File(info.path);
-                value["type"] = "application/data-transfer+json";
-                shared->sendMessage(info.conversationId, value);
-            }
-        });
-    }
 }
 
 void
@@ -4063,19 +4161,21 @@ JamiAccount::sendMessage(const std::string& conversationId,
                          const std::string& message,
                          const std::string& parent,
                          const std::string& type,
-                         bool announce)
+                         bool announce,
+                         const OnDoneCb& cb)
 {
     Json::Value json;
     json["body"] = message;
     json["type"] = type;
-    sendMessage(conversationId, json, parent, announce);
+    sendMessage(conversationId, json, parent, announce, cb);
 }
 
 void
 JamiAccount::sendMessage(const std::string& conversationId,
                          const Json::Value& value,
                          const std::string& parent,
-                         bool announce)
+                         bool announce,
+                         const OnDoneCb& cb)
 {
     std::lock_guard<std::mutex> lk(conversationsMtx_);
     auto conversation = conversations_.find(conversationId);
@@ -4083,7 +4183,10 @@ JamiAccount::sendMessage(const std::string& conversationId,
         conversation->second->sendMessage(
             value,
             parent,
-            [w = weak(), conversationId, announce](bool ok, const std::string& commitId) {
+            [w = weak(), conversationId, announce, cb = std::move(cb)](bool ok,
+                                                                       const std::string& commitId) {
+                if (cb)
+                    cb(ok, commitId);
                 if (!announce)
                     return;
                 if (ok) {
@@ -4168,65 +4271,6 @@ JamiAccount::onNewGitCommit(const std::string& peer,
     fetchNewCommits(peer, deviceId, conversationId, commitId);
 }
 
-void
-JamiAccount::onAskForTransfer(const std::string& peer,
-                              const std::string& deviceId,
-                              const std::string& conversationId,
-                              const std::string& interactionId)
-{
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    auto conversation = conversations_.find(conversationId);
-    if (conversation == conversations_.end() or not conversation->second)
-        return;
-
-    if (!conversation->second->isMember(peer, true))
-        return;
-
-    auto commit = conversation->second->getCommit(interactionId);
-    if (commit == std::nullopt || commit->find("type") == commit->end()
-        || commit->find("tid") == commit->end() || commit->find("sha3sum") == commit->end()
-        || commit->at("type") != "application/data-transfer+json")
-        return;
-    DRing::DataTransferId tid;
-    auto tid_str = commit->at("tid");
-    std::from_chars(tid_str.data(), tid_str.data() + tid_str.size(), tid);
-    auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID() + DIR_SEPARATOR_STR
-                + "conversation_data" + DIR_SEPARATOR_STR + conversationId + DIR_SEPARATOR_STR
-                + tid_str;
-    if (!fileutils::isFile(path)) {
-        // Check if dangling symlink
-        if (fileutils::isSymLink(path)) {
-            fileutils::remove(path, true);
-        }
-        JAMI_DBG("[Account %s] %s asked for non existing file %s in %s",
-                 getAccountID().c_str(),
-                 peer.c_str(),
-                 interactionId.c_str(),
-                 conversationId.c_str());
-        return;
-    }
-    // Check that our file is correct before sending
-    if (!noSha3sumVerification_ && commit->at("sha3sum") != fileutils::sha3File(path)) {
-        JAMI_DBG("[Account %s] %s asked for file %s in %s, but our version is not complete",
-                 getAccountID().c_str(),
-                 peer.c_str(),
-                 interactionId.c_str(),
-                 conversationId.c_str());
-        return;
-    }
-
-    JAMI_WARN("[Account %s] %s asked for file %s in %s",
-              getAccountID().c_str(),
-              peer.c_str(),
-              interactionId.c_str(),
-              conversationId.c_str());
-    dht::ThreadPool::io().run([w = weak(), conversationId, path, deviceId, tid] {
-        if (auto shared = w.lock()) {
-            shared->sendFile(conversationId, path, {}, deviceId, tid);
-        }
-    });
-}
-
 void
 JamiAccount::fetchNewCommits(const std::string& peer,
                              const std::string& deviceId,
@@ -4264,7 +4308,8 @@ JamiAccount::fetchNewCommits(const std::string& peer,
         }
 
         if (hasGitSocket(deviceId, conversationId)) {
-            conversation->second->pull(
+            conversation->second->sync(
+                peer,
                 deviceId,
                 [peer, deviceId, conversationId, commitId, w = weak()](bool ok) {
                     auto shared = w.lock();
@@ -4301,50 +4346,53 @@ JamiAccount::fetchNewCommits(const std::string& peer,
             connectionManager_->connectDevice(
                 DeviceId(deviceId),
                 "git://" + deviceId + "/" + conversationId,
-                [this, conversationId, commitId](std::shared_ptr<ChannelSocket> socket,
-                                                 const DeviceId& deviceId) {
-                    dht::ThreadPool::io().run(
-                        [w = weak(), conversationId, socket = std::move(socket), deviceId, commitId] {
-                            auto shared = w.lock();
-                            if (!shared)
-                                return;
-                            std::unique_lock<std::mutex> lk(shared->conversationsMtx_);
-                            auto conversation = shared->conversations_.find(conversationId);
-                            if (!conversation->second)
-                                return;
-                            if (socket) {
-                                shared->addGitSocket(deviceId.toString(), conversationId, socket);
-
-                                conversation->second->pull(
-                                    deviceId.toString(),
-                                    [deviceId, conversationId, w](bool ok) {
-                                        auto shared = w.lock();
-                                        if (!shared)
-                                            return;
-                                        if (!ok) {
-                                            JAMI_WARN("[Account %s] Could not fetch new commit "
-                                                      "from %s for %s",
-                                                      shared->getAccountID().c_str(),
-                                                      deviceId.to_c_str(),
-                                                      conversationId.c_str());
-                                            shared->removeGitSocket(deviceId.toString(),
-                                                                    conversationId);
-                                        }
-                                    },
-                                    commitId);
-                            } else {
-                                JAMI_ERR("[Account %s] Couldn't open a new git channel with %s for "
-                                         "conversation %s",
-                                         shared->getAccountID().c_str(),
-                                         deviceId.to_c_str(),
-                                         conversationId.c_str());
-                            }
-                            {
-                                std::lock_guard<std::mutex> lk(
-                                    shared->pendingConversationsFetchMtx_);
-                                shared->pendingConversationsFetch_.erase(conversationId);
-                            }
-                        });
+                [this, conversationId, commitId, peer](std::shared_ptr<ChannelSocket> socket,
+                                                       const DeviceId& deviceId) {
+                    dht::ThreadPool::io().run([w = weak(),
+                                               conversationId,
+                                               socket = std::move(socket),
+                                               peer,
+                                               deviceId,
+                                               commitId] {
+                        auto shared = w.lock();
+                        if (!shared)
+                            return;
+                        std::unique_lock<std::mutex> lk(shared->conversationsMtx_);
+                        auto conversation = shared->conversations_.find(conversationId);
+                        if (!conversation->second)
+                            return;
+                        if (socket) {
+                            shared->addGitSocket(deviceId.toString(), conversationId, socket);
+
+                            conversation->second->sync(
+                                peer,
+                                deviceId.toString(),
+                                [deviceId, conversationId, w](bool ok) {
+                                    auto shared = w.lock();
+                                    if (!shared)
+                                        return;
+                                    if (!ok) {
+                                        JAMI_WARN("[Account %s] Could not fetch new commit "
+                                                  "from %s for %s",
+                                                  shared->getAccountID().c_str(),
+                                                  deviceId.to_c_str(),
+                                                  conversationId.c_str());
+                                        shared->removeGitSocket(deviceId.toString(), conversationId);
+                                    }
+                                },
+                                commitId);
+                        } else {
+                            JAMI_ERR("[Account %s] Couldn't open a new git channel with %s for "
+                                     "conversation %s",
+                                     shared->getAccountID().c_str(),
+                                     deviceId.to_c_str(),
+                                     conversationId.c_str());
+                        }
+                        {
+                            std::lock_guard<std::mutex> lk(shared->pendingConversationsFetchMtx_);
+                            shared->pendingConversationsFetch_.erase(conversationId);
+                        }
+                    });
                 });
         }
     } else {
@@ -4362,7 +4410,7 @@ JamiAccount::fetchNewCommits(const std::string& peer,
             return;
         for (const auto& ci : infos->conversations) {
             if (ci.id == conversationId) {
-                cloneConversation(deviceId, conversationId);
+                cloneConversation(deviceId, peer, conversationId);
                 return;
             }
         }
@@ -4760,6 +4808,12 @@ JamiAccount::sendProfile(const std::string& deviceId)
     }
 }
 
+std::string
+JamiAccount::profilePath() const
+{
+    return idPath_ + DIR_SEPARATOR_STR + "profile.vcf";
+}
+
 void
 JamiAccount::cacheSIPConnection(std::shared_ptr<ChannelSocket>&& socket,
                                 const std::string& peerId,
@@ -4804,6 +4858,8 @@ JamiAccount::cacheSIPConnection(std::shared_ptr<ChannelSocket>&& socket,
 
     sendProfile(deviceId.toString());
 
+    syncConversations(peerId, deviceId.toString());
+
     // Retry messages
     messageEngine_.onPeerOnline(peerId);
 
@@ -4884,7 +4940,7 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket,
         }
     });
 
-    socket->setOnRecv([this, deviceId](const uint8_t* buf, size_t len) {
+    socket->setOnRecv([this, deviceId, peerId](const uint8_t* buf, size_t len) {
         if (!buf)
             return len;
 
@@ -4915,7 +4971,7 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket,
                         }
                     }
                     if (!wasRemoved)
-                        cloneConversation(deviceId, convId);
+                        cloneConversation(deviceId, peerId, convId);
                 } else {
                     {
                         std::lock_guard<std::mutex> lk(conversationsMtx_);
@@ -5125,6 +5181,18 @@ JamiAccount::loadConversations()
     JAMI_INFO("[Account %s] Conversations loaded!", getAccountID().c_str());
 }
 
+std::shared_ptr<TransferManager>
+JamiAccount::dataTransfer(const std::string& id) const
+{
+    if (id.empty())
+        return nonSwarmTransferManager_;
+    std::unique_lock<std::mutex> lk(conversationsMtx_);
+    auto it = conversations_.find(id);
+    if (it != conversations_.end() && it->second)
+        return it->second->dataTransfer();
+    return nullptr;
+}
+
 void
 JamiAccount::removeRepository(const std::string& conversationId, bool sync, bool force)
 {
@@ -5229,7 +5297,9 @@ JamiAccount::monitor() const
 }
 
 void
-JamiAccount::cloneConversation(const std::string& deviceId, const std::string& convId)
+JamiAccount::cloneConversation(const std::string& deviceId,
+                               const std::string&,
+                               const std::string& convId)
 {
     JAMI_DBG("[Account %s] Clone conversation on device %s",
              getAccountID().c_str(),
@@ -5284,192 +5354,182 @@ JamiAccount::cloneConversation(const std::string& deviceId, const std::string& c
     }
 }
 
-DRing::DataTransferId
-JamiAccount::sendFile(const std::string& to,
+void
+JamiAccount::sendFile(const std::string& conversationId,
                       const std::string& path,
-                      const InternalCompletionCb& icb,
-                      const std::string& deviceId,
-                      DRing::DataTransferId resendId)
+                      const std::string& name,
+                      const std::string& parent)
 {
-    // TODO erase manager when remove conversation or contact
     if (!fileutils::isFile(path)) {
         JAMI_ERR() << "invalid filename '" << path << "'";
-        return {};
-    }
-
-    bool isConversation;
-    {
-        std::lock_guard<std::mutex> lk(conversationsMtx_);
-        isConversation = conversations_.find(to) != conversations_.end();
-    }
-
-    std::unique_lock<std::mutex> lk(transferMutex_);
-    auto it = transferManagers_.find(to);
-    if (it == transferManagers_.end()) {
-        std::string accId = getAccountID();
-        auto res = transferManagers_.emplace(std::piecewise_construct,
-                                             std::forward_as_tuple(to),
-                                             std::forward_as_tuple(accId, to, isConversation));
-        if (!res.second) {
-            JAMI_ERR("Couldn't send file %s to %s", path.c_str(), to.c_str());
-            return {};
-        }
-        it = res.first;
+        return;
     }
-
-    auto tid = it->second.sendFile(path, icb, deviceId, resendId);
-
-    if (isConversation) {
-        // Create a symlink to answer to re-ask
-        auto symlinkPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID()
-                           + DIR_SEPARATOR_STR + "conversation_data" + DIR_SEPARATOR_STR + to
-                           + DIR_SEPARATOR_STR + std::to_string(tid);
-        if (path != symlinkPath && !fileutils::isSymLink(symlinkPath)) {
-            fileutils::createSymLink(symlinkPath, path);
+    // NOTE: this sendMessage is in a computation thread because
+    // sha3sum can take quite some time to computer if the user decide
+    // to send a big file
+    dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, parent]() {
+        if (auto shared = w.lock()) {
+            Json::Value value;
+            auto tid = jami::generateUID();
+            value["tid"] = std::to_string(tid);
+            std::size_t found = path.find_last_of(DIR_SEPARATOR_CH);
+            value["displayName"] = name.empty() ? path.substr(found + 1) : name;
+            value["totalSize"] = std::to_string(fileutils::size(path));
+            value["sha3sum"] = fileutils::sha3File(path);
+            value["type"] = "application/data-transfer+json";
+
+            shared->sendMessage(conversationId,
+                                value,
+                                parent,
+                                true,
+                                [accId = shared->getAccountID(),
+                                 conversationId,
+                                 tid,
+                                 path](bool, const std::string& commitId) {
+                                    // Create a symlink to answer to re-ask
+                                    auto symlinkPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR
+                                                       + accId + DIR_SEPARATOR_STR
+                                                       + "conversation_data" + DIR_SEPARATOR_STR
+                                                       + conversationId + DIR_SEPARATOR_STR
+                                                       + commitId + "_" + std::to_string(tid);
+                                    auto extension = fileutils::getFileExtension(path);
+                                    if (!extension.empty())
+                                        symlinkPath += "." + extension;
+                                    if (path != symlinkPath && !fileutils::isSymLink(symlinkPath))
+                                        fileutils::createSymLink(symlinkPath, path);
+                                });
         }
-    }
-    return tid;
+    });
 }
 
-void
-JamiAccount::askForTransfer(const std::string& conversationUri,
-                            const std::string& interactionId,
-                            const std::string& path)
+DRing::DataTransferId
+JamiAccount::sendFile(const std::string& peer,
+                      const std::string& path,
+                      const InternalCompletionCb& icb)
 {
-    Uri uri(conversationUri);
-    if (uri.scheme() != Uri::Scheme::SWARM)
-        return;
-    const auto& conversationId = uri.authority();
-    DRing::DataTransferId tid;
-    std::string sha3sum = {};
-    {
-        std::lock_guard<std::mutex> lk(conversationsMtx_);
-        auto conversation = conversations_.find(conversationId);
-        if (conversation == conversations_.end() || !conversation->second)
-            return;
-        auto commit = conversation->second->getCommit(interactionId);
-        if (commit == std::nullopt || commit->find("type") == commit->end()
-            || commit->find("sha3sum") == commit->end() || commit->find("tid") == commit->end()
-            || commit->at("type") != "application/data-transfer+json")
-            return;
-        sha3sum = commit->at("sha3sum");
-        auto tid_str = commit->at("tid");
-        std::from_chars(tid_str.data(), tid_str.data() + tid_str.size(), tid);
-    }
-
-    std::unique_lock<std::mutex> lk(transferMutex_);
-    auto it = transferManagers_.find(conversationId);
-    if (it == transferManagers_.end()) {
-        auto res = transferManagers_.emplace(std::piecewise_construct,
-                                             std::forward_as_tuple(conversationId),
-                                             std::forward_as_tuple(getAccountID(), conversationId));
-        if (!res.second) {
-            JAMI_ERR("Couldn't create manager for conversation %s", conversationId.c_str());
-            return;
-        }
-        it = res.first;
+    if (!fileutils::isFile(path)) {
+        JAMI_ERR() << "invalid filename '" << path << "'";
+        return {};
     }
-    it->second.waitForTransfer(tid, sha3sum, path);
 
-    Json::Value askTransferValue;
-    askTransferValue["conversation"] = conversationId;
-    askTransferValue["interaction"] = interactionId;
-    askTransferValue["deviceId"] = std::string(currentDeviceId());
-    Json::StreamWriterBuilder builder;
-    sendInstantMessage(conversationId,
-                       {{MIME_TYPE_ASK_TRANSFER, Json::writeString(builder, askTransferValue)}});
+    return nonSwarmTransferManager_->sendFile(path, peer, icb);
 }
 
 void
-JamiAccount::onIncomingFileRequest(const DRing::DataTransferInfo& info,
-                                   const DRing::DataTransferId& id,
-                                   const std::function<void(const IncomingFileInfo&)>& cb,
-                                   const InternalCompletionCb& icb)
-{
-    auto to = info.conversationId.empty() ? info.peer : info.conversationId;
-    std::unique_lock<std::mutex> lk(transferMutex_);
-    auto it = transferManagers_.find(to);
-    if (it == transferManagers_.end()) {
-        JAMI_ERR("Couldn't accept file %lu to %s", id, to.c_str());
+JamiAccount::transferFile(const std::string& conversationId,
+                          const std::string& path,
+                          const std::string& deviceId,
+                          const std::string& fileId,
+                          const std::string& interactionId,
+                          size_t start,
+                          size_t end)
+{
+    auto channelName = "data-transfer://" + conversationId + "/" + currentDeviceId() + "/" + fileId;
+    std::lock_guard<std::mutex> lkCM(connManagerMtx_);
+    if (!connectionManager_)
         return;
-    }
-
-    it->second.onIncomingFileRequest(info, id, cb, icb);
-}
-
-bool
-JamiAccount::acceptFile(const std::string& to,
-                        DRing::DataTransferId id,
-                        const std::string& path,
-                        int64_t)
-{
-    std::unique_lock<std::mutex> lk(transferMutex_);
-    auto it = transferManagers_.find(to);
-    if (it == transferManagers_.end()) {
-        JAMI_ERR("Couldn't accept file %s to %s", path.c_str(), to.c_str());
-        return false;
-    }
-
-    auto res = it->second.acceptFile(id, path);
-
-    bool isConversation;
-    {
-        std::lock_guard<std::mutex> lk(conversationsMtx_);
-        isConversation = conversations_.find(to) != conversations_.end();
-    }
-    if (isConversation) {
-        // Create a symlink to answer to re-ask
-        auto symlinkPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID()
-                           + DIR_SEPARATOR_STR + "conversation_data" + DIR_SEPARATOR_STR + to
-                           + DIR_SEPARATOR_STR + std::to_string(id);
-        if (path != symlinkPath && !fileutils::isSymLink(symlinkPath)) {
-            fileutils::createSymLink(symlinkPath, path);
-        }
-    }
-
-    return res;
+    connectionManager_->connectDevice(
+        DeviceId(deviceId),
+        channelName,
+        [this, conversationId, path = std::move(path), fileId, interactionId, start, end](
+            std::shared_ptr<ChannelSocket> socket, const DeviceId&) {
+            if (!socket)
+                return;
+            dht::ThreadPool::io().run([w = weak(),
+                                       path = std::move(path),
+                                       socket = std::move(socket),
+                                       conversationId = std::move(conversationId),
+                                       fileId,
+                                       interactionId,
+                                       start,
+                                       end] {
+                if (auto shared = w.lock())
+                    if (auto dt = shared->dataTransfer(conversationId))
+                        dt->transferFile(socket, fileId, interactionId, path, start, end);
+            });
+        });
 }
 
 bool
-JamiAccount::cancel(const std::string& to, DRing::DataTransferId id)
+JamiAccount::downloadFile(const std::string& conversationId,
+                          const std::string& interactionId,
+                          const std::string& fileId,
+                          const std::string& path,
+                          size_t start,
+                          size_t end)
 {
-    std::unique_lock<std::mutex> lk(transferMutex_);
-    auto it = transferManagers_.find(to);
-    if (it == transferManagers_.end()) {
-        JAMI_ERR("Couldn't cancel file %lu to %s", id, to.c_str());
+    std::string sha3sum = {};
+    std::lock_guard<std::mutex> lk(conversationsMtx_);
+    auto conversation = conversations_.find(conversationId);
+    if (conversation == conversations_.end() || !conversation->second)
         return false;
-    }
 
-    return it->second.cancel(id);
+    return conversation->second->downloadFile(interactionId, fileId, path, "", "", start, end);
 }
 
-bool
-JamiAccount::info(const std::string& to, DRing::DataTransferId id, DRing::DataTransferInfo& info)
+void
+JamiAccount::askForFileChannel(const std::string& conversationId,
+                               const std::string& deviceId,
+                               const std::string& fileId,
+                               size_t start,
+                               size_t end)
 {
-    std::unique_lock<std::mutex> lk(transferMutex_);
-    auto it = transferManagers_.find(to);
-    if (it == transferManagers_.end()) {
-        JAMI_ERR("Couldn't get file %lu to %s", id, to.c_str());
-        return false;
-    }
+    auto tryDevice = [=](const auto& did) {
+        std::lock_guard<std::mutex> lkCM(connManagerMtx_);
+        if (!connectionManager_)
+            return;
 
-    return it->second.info(id, info);
-}
+        auto channelName = "data-transfer://" + conversationId + "/" + currentDeviceId() + "/"
+                           + fileId;
+        if (start != 0 || end != 0) {
+            channelName += "?start=" + std::to_string(start) + "&end=" + std::to_string(end);
+        }
+        // We can avoid to negotiate new sessions, as the file notif
+        // probably come from an online device or last connected device.
+        connectionManager_->connectDevice(
+            did,
+            channelName,
+            [this, conversationId, fileId](std::shared_ptr<ChannelSocket> channel, const DeviceId&) {
+                if (!channel)
+                    return;
+                dht::ThreadPool::io().run([w = weak(), conversationId, channel, fileId] {
+                    auto shared = w.lock();
+                    if (!shared)
+                        return;
+                    auto dt = shared->dataTransfer(conversationId);
+                    if (!dt)
+                        return;
+                    if (fileId == "profile.vcf")
+                        dt->onIncomingProfile(channel);
+                    else
+                        dt->onIncomingFileTransfer(fileId, channel);
+                });
+            },
+            false);
+    };
 
-bool
-JamiAccount::bytesProgress(const std::string& to,
-                           DRing::DataTransferId id,
-                           int64_t& total,
-                           int64_t& progress)
-{
-    std::unique_lock<std::mutex> lk(transferMutex_);
-    auto it = transferManagers_.find(to);
-    if (it == transferManagers_.end()) {
-        JAMI_ERR("Couldn't get file %lu to %s", id, to.c_str());
-        return false;
+    if (!deviceId.empty()) {
+        // Only ask for device
+        tryDevice(DeviceId(deviceId));
+    } else {
+        // Only ask for connected devices. For others we will try
+        // on new peer online
+        std::vector<std::string> members;
+        {
+            std::lock_guard<std::mutex> lk(conversationsMtx_);
+            auto conversation = conversations_.find(conversationId);
+            if (conversation != conversations_.end() && conversation->second) {
+                for (const auto& m : conversation->second->getMembers()) {
+                    members.emplace_back(m.at("uri"));
+                }
+            }
+        }
+        for (const auto& member : members) {
+            accountManager_->forEachDevice(dht::InfoHash(member),
+                                           [this, tryDevice = std::move(tryDevice)](
+                                               const DeviceId& dev) { tryDevice(dev); });
+        }
     }
-
-    return it->second.bytesProgress(id, total, progress);
 }
 
 } // namespace jami
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 251b415c13..6b4c07391f 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -340,6 +340,9 @@ public:
                              const std::map<std::string, std::string>& payloads) override;
     void sendInstantMessage(const std::string& convId,
                             const std::map<std::string, std::string>& msg) override;
+    void sendSIPMessageToDevice(const std::string& to,
+                                const DeviceId& deviceId,
+                                const std::map<std::string, std::string>& payloads);
     void onIsComposing(const std::string& conversationId,
                        const std::string& peer,
                        bool isWriting) override;
@@ -380,7 +383,6 @@ public:
      * @param isVcard if transfer is a vcard transfer
      * @param channeledConnectedCb callback when channel is connected
      * @param onChanneledCancelled callback when channel is canceled
-     * @param addToHistory if we need to add the transfer to the history
      */
     void requestConnection(
         const DRing::DataTransferInfo& info,
@@ -388,8 +390,7 @@ public:
         bool isVCard,
         const std::function<void(const std::shared_ptr<ChanneledOutgoingTransfer>&)>&
             channeledConnectedCb,
-        const std::function<void(const std::string&)>& onChanneledCancelled,
-        bool addToHistory);
+        const std::function<void(const std::string&)>& onChanneledCancelled);
 
     ///
     /// Close a E2E connection between a given peer and a given transfer id.
@@ -548,12 +549,14 @@ public:
     void sendMessage(const std::string& conversationId,
                      const Json::Value& value,
                      const std::string& parent = "",
-                     bool announce = true);
+                     bool announce = true,
+                     const OnDoneCb& cb = {});
     void sendMessage(const std::string& conversationId,
                      const std::string& message,
                      const std::string& parent = "",
                      const std::string& type = "text/plain",
-                     bool announce = true);
+                     bool announce = true,
+                     const OnDoneCb& cb = {});
     /**
      * Add to the related conversation the call history message
      * @param uri           Peer number
@@ -569,18 +572,6 @@ public:
                         const std::string& deviceId,
                         const std::string& conversationId,
                         const std::string& commitId) override;
-
-    /**
-     * When a new transfer is asked by a peer
-     * @param peer              account's uri of the peer
-     * @param deviceId          peer device
-     * @param conversationId    related conversation
-     * @param interactionId     interaction corresponding to the transfer asked.
-     */
-    virtual void onAskForTransfer(const std::string& peer,
-                                  const std::string& deviceId,
-                                  const std::string& conversationId,
-                                  const std::string& interactionId) override;
     /**
      * Pull remote device (do not do it if commitId is already in the current repo)
      * @param peer              Contact URI
@@ -606,42 +597,66 @@ public:
      * @param deviceId
      * @param convId
      */
-    void cloneConversation(const std::string& deviceId, const std::string& convId);
+    void cloneConversation(const std::string& deviceId,
+                           const std::string& peer,
+                           const std::string& convId);
 
     // File transfer
-    DRing::DataTransferId sendFile(const std::string& to,
+    void sendFile(const std::string& conversationId,
+                  const std::string& path,
+                  const std::string& name,
+                  const std::string& parent);
+
+    // non-swarm version
+    DRing::DataTransferId sendFile(const std::string& peer,
                                    const std::string& path,
-                                   const InternalCompletionCb& icb = {},
-                                   const std::string& deviceId = {},
-                                   DRing::DataTransferId resendId = {});
+                                   const InternalCompletionCb& icb = {});
+
+    void transferFile(const std::string& conversationId,
+                      const std::string& path,
+                      const std::string& deviceId,
+                      const std::string& fileId,
+                      const std::string& interactionId,
+                      size_t start = 0,
+                      size_t end = 0);
     /**
      * Ask conversation's members to send back a previous transfer to this deviec
-     * @param conversationUri   Related conversation
+     * @param conversationId    Related conversation
      * @param interactionId     Related interaction
+     * @param fileId            Related fileId
      * @param path              where to download the file
      */
-    void askForTransfer(const std::string& conversationUri,
-                        const std::string& interactionId,
-                        const std::string& path) override;
-
-    void onIncomingFileRequest(const DRing::DataTransferInfo& info,
-                               const DRing::DataTransferId& id,
-                               const std::function<void(const IncomingFileInfo&)>& cb,
-                               const InternalCompletionCb& icb);
-
-    bool acceptFile(const std::string& to,
-                    DRing::DataTransferId id,
-                    const std::string& path,
-                    int64_t progress);
-
-    bool cancel(const std::string& to, DRing::DataTransferId id);
-    bool info(const std::string& to, DRing::DataTransferId id, DRing::DataTransferInfo& info);
-    bool bytesProgress(const std::string& to,
-                       DRing::DataTransferId id,
-                       int64_t& total,
-                       int64_t& progress);
+    bool downloadFile(const std::string& conversationId,
+                      const std::string& interactionId,
+                      const std::string& fileId,
+                      const std::string& path,
+                      size_t start = 0,
+                      size_t end = 0);
+
+    void askForFileChannel(const std::string& conversationId,
+                           const std::string& deviceId,
+                           const std::string& fileId,
+                           size_t start = 0,
+                           size_t end = 0);
+
     void loadConversations();
 
+    /**
+     * Retrieve linked transfer manager
+     * @param id    conversationId or empty for fallback
+     * @return linked transfer manager
+     */
+    std::shared_ptr<TransferManager> dataTransfer(const std::string& id = "") const;
+
+    /**
+     * Send Profile via cached SIP connection
+     * @param deviceId      Device that will receive the profile
+     */
+    // Note: when swarm will be merged, this can be moved in transferManager
+    bool needToSendProfile(const std::string& deviceId);
+
+    std::string profilePath() const;
+
 private:
     NON_COPYABLE(JamiAccount);
 
@@ -711,6 +726,11 @@ private:
      */
     void onTrackedBuddyOnline(const dht::InfoHash&);
 
+    /**
+     * Sync conversations with detected peer
+     */
+    void syncConversations(const std::string& peer, const std::string& deviceId);
+
     /**
      * Maps require port via UPnP and other async ops
      */
@@ -992,11 +1012,6 @@ private:
                         const std::map<std::string, std::string>& data,
                         pjsip_endpt_send_callback cb);
 
-    /**
-     * Send Profile via cached SIP connection
-     * @param deviceId      Device that will receive the profile
-     */
-    bool needToSendProfile(const std::string& deviceId);
     /**
      * Send Profile via cached SIP connection
      * @param deviceId      Device that will receive the profile
@@ -1047,8 +1062,9 @@ private:
     std::atomic_bool deviceAnnounced_ {false};
 
     //// File transfer
+    std::shared_ptr<TransferManager> nonSwarmTransferManager_;
     std::mutex transferMutex_ {};
-    std::map<std::string, TransferManager> transferManagers_ {};
+    std::map<std::string, std::shared_ptr<TransferManager>> transferManagers_ {};
 
     bool noSha3sumVerification_ {false};
 };
diff --git a/src/jamidht/multiplexed_socket.cpp b/src/jamidht/multiplexed_socket.cpp
index 8345e81105..4192701a8f 100644
--- a/src/jamidht/multiplexed_socket.cpp
+++ b/src/jamidht/multiplexed_socket.cpp
@@ -484,7 +484,7 @@ MultiplexedSocket::Impl::handleChannelPacket(uint16_t channel, std::vector<uint8
         }
     } else if (pkt.size() != 0) {
         std::string p = std::string(pkt.begin(), pkt.end());
-        JAMI_WARN("Non existing channel: %u - %.*s", channel, (int) pkt.size(), p.c_str());
+        JAMI_WARN("Non existing channel: %u", channel);
     }
 }
 
@@ -965,6 +965,10 @@ ChannelSocket::read(ValueType* buf, std::size_t len, std::error_code& ec)
 std::size_t
 ChannelSocket::write(const ValueType* buf, std::size_t len, std::error_code& ec)
 {
+    if (pimpl_->isShutdown_) {
+        ec = std::make_error_code(std::errc::broken_pipe);
+        return -1;
+    }
     if (auto ep = pimpl_->endpoint.lock()) {
         std::size_t sent = 0;
         do {
diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp
index f60abdda0c..683da76ffc 100644
--- a/src/sip/sipaccountbase.cpp
+++ b/src/sip/sipaccountbase.cpp
@@ -133,20 +133,23 @@ getIsComposing(const std::string& conversationId, bool isWriting)
     return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
                        "<isComposing><state>{}</state>{}</isComposing>",
                        isWriting ? "active"sv : "idle"sv,
-                       conversationId.empty()? "" : "<conversation>" + conversationId + "</conversation>");
+                       conversationId.empty()
+                           ? ""
+                           : "<conversation>" + conversationId + "</conversation>");
 }
 
 std::string
 getDisplayed(const std::string& conversationId, const std::string& messageId)
 {
     // implementing https://tools.ietf.org/rfc/rfc5438.txt
-    return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
-                       "<imdn><message-id>{}</message-id>\n"
-                       "{}"
-                       "<display-notification><status><displayed/></status></display-notification>\n"
-                       "</imdn>",
-                       messageId,
-                       conversationId.empty()? "" : "<conversation>" + conversationId + "</conversation>");
+    return fmt::format(
+        "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
+        "<imdn><message-id>{}</message-id>\n"
+        "{}"
+        "<display-notification><status><displayed/></status></display-notification>\n"
+        "</imdn>",
+        messageId,
+        conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>");
 }
 
 void
@@ -549,7 +552,6 @@ SIPAccountBase::onTextMessage(const std::string& id,
                     && matched_pattern[1].matched) {
                     conversationId = matched_pattern[1];
                 }
-                JAMI_WARN("@@@ %s", m.second.c_str());
                 onIsComposing(conversationId, from, isComposing);
                 if (payloads.size() == 1)
                     return;
@@ -647,20 +649,6 @@ SIPAccountBase::onTextMessage(const std::string& id,
             // check if we have a swarm created. It can be the case
             // when the trust request confirm was not received.
             checkIfRemoveForCompat(from);
-        } else if (m.first == MIME_TYPE_ASK_TRANSFER) {
-            Json::Value json;
-            std::string err;
-            Json::CharReaderBuilder rbuilder;
-            auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-            if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) {
-                JAMI_ERR("Can't parse server response: %s", err.c_str());
-                return;
-            }
-            onAskForTransfer(from,
-                             json["deviceId"].asString(),
-                             json["conversation"].asString(),
-                             json["interaction"].asString());
-            return;
         }
     }
 
diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h
index b4071cdb71..c96e92997b 100644
--- a/src/sip/sipaccountbase.h
+++ b/src/sip/sipaccountbase.h
@@ -52,7 +52,6 @@ struct pjmedia_sdp_session;
 }
 
 static constexpr const char MIME_TYPE_TEXT_PLAIN[] {"text/plain"};
-static constexpr const char MIME_TYPE_ASK_TRANSFER[] {"application/data-transfer-request+json"};
 
 namespace jami {
 
diff --git a/test/unitTest/conversation/compability.cpp b/test/unitTest/conversation/compability.cpp
index c3a6529e5a..70eabf950c 100644
--- a/test/unitTest/conversation/compability.cpp
+++ b/test/unitTest/conversation/compability.cpp
@@ -371,15 +371,7 @@ CompabilityTest::testSendFileCompatibility()
     sendFile.close();
 
     // Send File
-    DRing::DataTransferInfo info;
-    uint64_t id;
-    info.accountId = aliceAccount->getAccountID();
-    info.conversationId = convId;
-    info.path = "SEND";
-    info.displayName = "SEND";
-    info.bytesProgress = 0;
-
-    CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success);
+    DRing::sendFile(aliceId, convId, "SEND", "SEND", "");
 
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return successfullyReceive; });
     std::remove("SEND");
diff --git a/test/unitTest/fileTransfer/fileTransfer.cpp b/test/unitTest/fileTransfer/fileTransfer.cpp
index 223aefe6c8..10eefeb87f 100644
--- a/test/unitTest/fileTransfer/fileTransfer.cpp
+++ b/test/unitTest/fileTransfer/fileTransfer.cpp
@@ -61,23 +61,28 @@ public:
 
 private:
     void testFileTransfer();
+    void testDataTransferInfo();
     void testMultipleFileTransfer();
     void testConversationFileTransfer();
     void testFileTransferInConversation();
-    void testReaskForTransfer();
     void testBadSha3sumOut();
     void testBadSha3sumIn();
     void testAskToMultipleParticipants();
+    void testCancelInTransfer();
+    void testCancelOutTransfer();
+    void testTransferInfo();
 
     CPPUNIT_TEST_SUITE(FileTransferTest);
     CPPUNIT_TEST(testFileTransfer);
+    CPPUNIT_TEST(testDataTransferInfo);
     CPPUNIT_TEST(testMultipleFileTransfer);
     CPPUNIT_TEST(testConversationFileTransfer);
     CPPUNIT_TEST(testFileTransferInConversation);
-    CPPUNIT_TEST(testReaskForTransfer);
     CPPUNIT_TEST(testBadSha3sumOut);
     CPPUNIT_TEST(testBadSha3sumIn);
     CPPUNIT_TEST(testAskToMultipleParticipants);
+    CPPUNIT_TEST(testCancelInTransfer);
+    CPPUNIT_TEST(testTransferInfo);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -129,19 +134,101 @@ FileTransferTest::testFileTransfer()
     std::condition_variable cv2;
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
     bool transferWaiting = false, transferFinished = false;
-    DRing::DataTransferId finalId;
+    std::string finalId;
     // Watch signals
     confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
-        [&](const std::string& accountId, const std::string&, const long unsigned int& id, int code) {
+        [&](const std::string& accountId,
+            const std::string&,
+            const std::string&,
+            const std::string& fileId,
+            int code) {
+            if (accountId == bobId
+                && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) {
+                transferWaiting = true;
+                finalId = fileId;
+                cv.notify_one();
+            } else if (accountId == aliceId
+                       && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
+                transferFinished = true;
+                finalId = fileId;
+                cv.notify_one();
+            }
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    // Create file to send
+    std::ofstream sendFile("SEND");
+    CPPUNIT_ASSERT(sendFile.is_open());
+    sendFile << std::string(64000, 'A');
+    sendFile.close();
+
+    // Send File
+    DRing::DataTransferInfo info;
+    uint64_t id;
+    info.accountId = aliceAccount->getAccountID();
+    info.peer = bobUri;
+    info.path = "SEND";
+    info.displayName = "SEND";
+    info.bytesProgress = 0;
+    CPPUNIT_ASSERT(DRing::sendFileLegacy(info, id) == DRing::DataTransferError::success);
+
+    cv.wait_for(lk, std::chrono::seconds(30));
+    CPPUNIT_ASSERT(transferWaiting);
+
+    auto rcv_path = "RECV";
+    CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, finalId, rcv_path)
+                   == DRing::DataTransferError::success);
+
+    // Wait 2 times, both sides will got a finished status
+    cv.wait_for(lk, std::chrono::seconds(30));
+    cv.wait_for(lk, std::chrono::seconds(30));
+    CPPUNIT_ASSERT(transferFinished);
+
+    CPPUNIT_ASSERT(compare(info.path, rcv_path));
+
+    // TODO FIX ME. The ICE take some time to stop and it doesn't seems to like
+    // when stopping the daemon and removing the accounts to soon.
+    std::remove("SEND");
+    std::remove("RECV");
+    JAMI_INFO("Waiting....");
+    std::this_thread::sleep_for(std::chrono::seconds(3));
+}
+
+void
+FileTransferTest::testDataTransferInfo()
+{
+    std::this_thread::sleep_for(std::chrono::seconds(5));
+    // TODO remove. This sleeps is because it take some time for the DHT to be connected
+    // and account announced
+    JAMI_INFO("Waiting....");
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::condition_variable cv2;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool transferWaiting = false, transferFinished = false;
+    std::string finalId;
+    // Watch signals
+    confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
+        [&](const std::string& accountId,
+            const std::string&,
+            const std::string&,
+            const std::string& fileId,
+            int code) {
             if (accountId == bobId
                 && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) {
                 transferWaiting = true;
-                finalId = id;
+                finalId = fileId;
                 cv.notify_one();
             } else if (accountId == aliceId
                        && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
                 transferFinished = true;
-                finalId = id;
+                finalId = fileId;
                 cv.notify_one();
             }
         }));
@@ -161,13 +248,27 @@ FileTransferTest::testFileTransfer()
     info.path = "SEND";
     info.displayName = "SEND";
     info.bytesProgress = 0;
-    CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success);
+    CPPUNIT_ASSERT(DRing::sendFileLegacy(info, id) == DRing::DataTransferError::success);
 
     cv.wait_for(lk, std::chrono::seconds(30));
     CPPUNIT_ASSERT(transferWaiting);
 
+    CPPUNIT_ASSERT(DRing::dataTransferInfo(bobId, std::to_string(id), info)
+                   == DRing::DataTransferError::success);
+
+    CPPUNIT_ASSERT(info.lastEvent == DRing::DataTransferEventCode::wait_host_acceptance);
+    CPPUNIT_ASSERT(info.bytesProgress == 0);
+    CPPUNIT_ASSERT(info.totalSize == 64000);
+
+    CPPUNIT_ASSERT(DRing::dataTransferInfo(aliceId, std::to_string(id), info)
+                   == DRing::DataTransferError::success);
+
+    CPPUNIT_ASSERT(info.lastEvent == DRing::DataTransferEventCode::wait_peer_acceptance);
+    CPPUNIT_ASSERT(info.bytesProgress == 0);
+    CPPUNIT_ASSERT(info.totalSize == 64000);
+
     auto rcv_path = "RECV";
-    CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, aliceUri, finalId, rcv_path, 0)
+    CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, finalId, rcv_path)
                    == DRing::DataTransferError::success);
 
     // Wait 2 times, both sides will got a finished status
@@ -177,6 +278,13 @@ FileTransferTest::testFileTransfer()
 
     CPPUNIT_ASSERT(compare(info.path, rcv_path));
 
+    CPPUNIT_ASSERT(DRing::dataTransferInfo(bobId, std::to_string(id), info)
+                   == DRing::DataTransferError::success);
+
+    CPPUNIT_ASSERT(info.lastEvent == DRing::DataTransferEventCode::finished);
+    CPPUNIT_ASSERT(info.bytesProgress == 64000);
+    CPPUNIT_ASSERT(info.totalSize == 64000);
+
     // TODO FIX ME. The ICE take some time to stop and it doesn't seems to like
     // when stopping the daemon and removing the accounts to soon.
     std::remove("SEND");
@@ -199,19 +307,23 @@ FileTransferTest::testMultipleFileTransfer()
     std::condition_variable cv2;
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
     bool transferWaiting = false, transferFinished = false;
-    DRing::DataTransferId finalId;
+    std::string finalId;
     // Watch signals
     confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
-        [&](const std::string& accountId, const std::string&, const long unsigned int& id, int code) {
+        [&](const std::string& accountId,
+            const std::string&,
+            const std::string&,
+            const std::string& fileId,
+            int code) {
             if (accountId == bobId
                 && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) {
                 transferWaiting = true;
-                finalId = id;
+                finalId = fileId;
                 cv.notify_one();
             } else if (accountId == aliceId
                        && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
                 transferFinished = true;
-                finalId = id;
+                finalId = fileId;
                 cv.notify_one();
             }
         }));
@@ -235,14 +347,14 @@ FileTransferTest::testMultipleFileTransfer()
     info.path = "SEND";
     info.displayName = "SEND";
     info.bytesProgress = 0;
-    CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success);
+    CPPUNIT_ASSERT(DRing::sendFileLegacy(info, id) == DRing::DataTransferError::success);
 
     cv.wait_for(lk, std::chrono::seconds(30));
     CPPUNIT_ASSERT(transferWaiting);
     transferWaiting = false;
 
     auto rcv_path = "RECV";
-    CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, aliceUri, finalId, rcv_path, 0)
+    CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, finalId, rcv_path)
                    == DRing::DataTransferError::success);
 
     // Wait 2 times, both sides will got a finished status
@@ -259,13 +371,13 @@ FileTransferTest::testMultipleFileTransfer()
     info2.path = "SEND2";
     info2.displayName = "SEND2";
     info2.bytesProgress = 0;
-    CPPUNIT_ASSERT(DRing::sendFile(info2, id) == DRing::DataTransferError::success);
+    CPPUNIT_ASSERT(DRing::sendFileLegacy(info2, id) == DRing::DataTransferError::success);
 
     cv.wait_for(lk, std::chrono::seconds(30));
     CPPUNIT_ASSERT(transferWaiting);
 
     rcv_path = "RECV2";
-    CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, aliceUri, finalId, rcv_path, 0)
+    CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, finalId, rcv_path)
                    == DRing::DataTransferError::success);
 
     // Wait 2 times, both sides will got a finished status
@@ -316,23 +428,28 @@ FileTransferTest::testConversationFileTransfer()
     confHandlers.clear();
     DRing::unregisterSignalHandlers();
 
-    auto messageReceivedAlice = 0;
-    auto messageReceivedBob = 0;
-    auto messageReceivedCarla = 0;
     auto requestReceived = 0;
     auto conversationReady = 0;
-    long unsigned int hostAcceptanceBob = {0}, hostAcceptanceCarla = {0};
-    std::vector<long unsigned int> peerAcceptance = {}, finished = {};
+    auto memberJoined = 0;
+    std::string tidBob, tidCarla, iidBob, iidCarla;
+    std::string hostAcceptanceBob = {}, hostAcceptanceCarla = {};
+    std::vector<std::string> peerAcceptance = {}, finished = {};
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& /* conversationId */,
-            std::map<std::string, std::string> /*message*/) {
-            if (accountId == aliceId)
-                messageReceivedAlice += 1;
-            if (accountId == bobId)
-                messageReceivedBob += 1;
-            if (accountId == carlaId)
-                messageReceivedCarla += 1;
+            std::map<std::string, std::string> message) {
+            if (message["type"] == "application/data-transfer+json") {
+                if (accountId == bobId) {
+                    tidBob = message["fileId"];
+                    iidBob = message["id"];
+                } else if (accountId == carlaId) {
+                    tidCarla = message["fileId"];
+                    iidCarla = message["id"];
+                }
+            } else if (accountId == aliceId && message["type"] == "member"
+                       && message["action"] == "join") {
+                memberJoined += 1;
+            }
             cv.notify_one();
         }));
     confHandlers.insert(
@@ -351,19 +468,23 @@ FileTransferTest::testConversationFileTransfer()
                 cv.notify_one();
         }));
     confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
-        [&](const std::string& accountId, const std::string&, const long unsigned int& id, int code) {
+        [&](const std::string& accountId,
+            const std::string&,
+            const std::string&,
+            const std::string& fileId,
+            int code) {
             if (code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) {
                 if (accountId == bobId)
-                    hostAcceptanceBob = id;
+                    hostAcceptanceBob = fileId;
                 else if (accountId == carlaId)
-                    hostAcceptanceCarla = id;
+                    hostAcceptanceCarla = fileId;
                 cv.notify_one();
             } else if (code
                        == static_cast<int>(DRing::DataTransferEventCode::wait_peer_acceptance)) {
-                peerAcceptance.emplace_back(id);
+                peerAcceptance.emplace_back(fileId);
                 cv.notify_one();
             } else if (code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
-                finished.emplace_back(id);
+                finished.emplace_back(fileId);
                 cv.notify_one();
             }
         }));
@@ -375,49 +496,35 @@ FileTransferTest::testConversationFileTransfer()
     aliceAccount->addConversationMember(convId, carlaUri);
     cv.wait_for(lk, std::chrono::seconds(60), [&]() { return requestReceived == 2; });
 
-    messageReceivedAlice = 0;
     bobAccount->acceptConversationRequest(convId);
     carlaAccount->acceptConversationRequest(convId);
     cv.wait_for(lk, std::chrono::seconds(30), [&]() {
-        return conversationReady == 3 && messageReceivedAlice >= 2;
+        return conversationReady == 3 && memberJoined == 2;
     });
 
     // Send file
     std::ofstream sendFile("SEND");
     CPPUNIT_ASSERT(sendFile.is_open());
-    // Avoid ASAN error on big alloc   sendFile << std::string("A", 64000);
-    for (int i = 0; i < 64000; ++i)
-        sendFile << "A";
+    sendFile << std::string(64000, 'A');
     sendFile.close();
 
-    // Send File
-    DRing::DataTransferInfo info;
-    uint64_t id;
-    info.accountId = aliceAccount->getAccountID();
-    info.conversationId = convId;
-    info.path = "SEND";
-    info.displayName = "SEND";
-    info.bytesProgress = 0;
-
-    CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success);
+    DRing::sendFile(aliceId, convId, "SEND", "SEND", "");
 
-    cv.wait_for(lk, std::chrono::seconds(45), [&]() {
-        return peerAcceptance.size() == 1 && hostAcceptanceBob != 0 && hostAcceptanceCarla != 0;
-    });
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(45), [&]() {
+        return !tidBob.empty() && !tidCarla.empty();
+    }));
 
-    CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, convId, hostAcceptanceBob, "RCV", 0)
-                   == DRing::DataTransferError::success);
-    CPPUNIT_ASSERT(DRing::acceptFileTransfer(carlaId, convId, hostAcceptanceCarla, "RCV2", 0)
-                   == DRing::DataTransferError::success);
+    DRing::downloadFile(bobId, convId, iidBob, tidBob, "RCV");
+    DRing::downloadFile(carlaId, convId, iidCarla, tidCarla, "RCV2");
 
-    cv.wait_for(lk, std::chrono::seconds(45), [&]() { return finished.size() == 3; });
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, std::chrono::seconds(45), [&]() { return finished.size() == 3; }));
 
     std::remove("SEND");
     std::remove("RCV");
     std::remove("RCV2");
 
     DRing::unregisterSignalHandlers();
-    std::this_thread::sleep_for(std::chrono::seconds(10));
 }
 
 void
@@ -437,17 +544,22 @@ FileTransferTest::testFileTransferInConversation()
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
     bool requestReceived = false;
     bool conversationReady = false;
+    bool bobJoined = false;
+    std::string tidBob, iidBob;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& /* conversationId */,
-            std::map<std::string, std::string> /*message*/) {
-            if (accountId == bobId) {
-                messageBobReceived += 1;
-            } else {
-                messageAliceReceived += 1;
+            std::map<std::string, std::string> message) {
+            if (message["type"] == "application/data-transfer+json") {
+                if (accountId == bobId) {
+                    tidBob = message["fileId"];
+                    iidBob = message["id"];
+                }
+            }
+            if (accountId == aliceId && message["type"] == "member" && message["action"] == "join") {
+                bobJoined = true;
             }
             cv.notify_one();
         }));
@@ -466,25 +578,22 @@ FileTransferTest::testFileTransferInConversation()
                 cv.notify_one();
             }
         }));
-    bool transferWaiting = false, transferFinished = false;
-    DRing::DataTransferId finalId;
+    bool transferAFinished = false, transferBFinished = false;
     // Watch signals
     confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
         [&](const std::string& accountId,
             const std::string& conversationId,
-            const long unsigned int& id,
+            const std::string&,
+            const std::string&,
             int code) {
-            if (accountId == bobId && conversationId == convId
-                && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) {
-                transferWaiting = true;
-                finalId = id;
-                cv.notify_one();
-            } else if (accountId == aliceId && conversationId == convId
-                       && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
-                transferFinished = true;
-                finalId = id;
-                cv.notify_one();
+            if (code == static_cast<int>(DRing::DataTransferEventCode::finished)
+                && conversationId == convId) {
+                if (accountId == aliceId)
+                    transferAFinished = true;
+                else if (accountId == bobId)
+                    transferBFinished = true;
             }
+            cv.notify_one();
         }));
     DRing::registerSignalHandlers(confHandlers);
 
@@ -493,32 +602,25 @@ FileTransferTest::testFileTransferInConversation()
 
     bobAccount->acceptConversationRequest(convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
-        return conversationReady && messageAliceReceived == 1;
+        return conversationReady && bobJoined;
     }));
 
     // Create file to send
     std::ofstream sendFile("SEND");
     CPPUNIT_ASSERT(sendFile.is_open());
-    // Avoid ASAN error on big alloc   sendFile << std::string("A", 64000);
-    for (int i = 0; i < 64000; ++i)
-        sendFile << "A";
+    sendFile << std::string(64000, 'A');
     sendFile.close();
 
-    // Send File
-    DRing::DataTransferInfo info;
-    uint64_t id;
-    info.accountId = aliceAccount->getAccountID();
-    info.conversationId = convId;
-    info.path = "SEND";
-    info.displayName = "SEND";
-    info.bytesProgress = 0;
-    CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success);
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferWaiting; }));
-    std::this_thread::sleep_for(std::chrono::seconds(30));
+    DRing::sendFile(aliceId, convId, "SEND", "SEND", "");
 
-    CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, convId, id, "RECV", 0)
-                   == DRing::DataTransferError::success);
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferFinished; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !tidBob.empty(); }));
+
+    transferAFinished = false;
+    transferBFinished = false;
+    DRing::downloadFile(bobId, convId, iidBob, tidBob, "RECV");
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return transferAFinished && transferBFinished;
+    }));
 
     std::remove("SEND");
     std::remove("RECV");
@@ -527,7 +629,7 @@ FileTransferTest::testFileTransferInConversation()
 }
 
 void
-FileTransferTest::testReaskForTransfer()
+FileTransferTest::testBadSha3sumOut()
 {
     std::this_thread::sleep_for(std::chrono::seconds(5));
     // TODO remove. This sleeps is because it take some time for the DHT to be connected
@@ -546,14 +648,15 @@ FileTransferTest::testReaskForTransfer()
     bool requestReceived = false;
     bool conversationReady = false;
     bool memberJoin = false;
-    std::string mid = {};
+    std::string mid = {}, iid;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& /* conversationId */,
             std::map<std::string, std::string> message) {
             if (accountId == bobId) {
                 if (message["type"] == "application/data-transfer+json") {
-                    mid = message["id"];
+                    mid = message["fileId"];
+                    iid = message["id"];
                 }
             }
             cv.notify_one();
@@ -573,25 +676,22 @@ FileTransferTest::testReaskForTransfer()
                 cv.notify_one();
             }
         }));
-    bool transferWaiting = false, transferFinished = false;
-    DRing::DataTransferId finalId;
+    bool transferAFinished = false, transferBFinished = false;
     // Watch signals
     confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
         [&](const std::string& accountId,
             const std::string& conversationId,
-            const long unsigned int& id,
+            const std::string&,
+            const std::string&,
             int code) {
-            if (accountId == bobId && conversationId == convId
-                && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) {
-                transferWaiting = true;
-                finalId = id;
-                cv.notify_one();
-            } else if (accountId == aliceId && conversationId == convId
-                       && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
-                transferFinished = true;
-                finalId = id;
-                cv.notify_one();
+            if (conversationId == convId
+                && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
+                if (accountId == aliceId)
+                    transferAFinished = true;
+                if (accountId == bobId)
+                    transferBFinished = true;
             }
+            cv.notify_one();
         }));
     confHandlers.insert(
         DRing::exportable_callback<DRing::ConversationSignal::ConversationMemberEvent>(
@@ -618,43 +718,35 @@ FileTransferTest::testReaskForTransfer()
     // Create file to send
     std::ofstream sendFile("SEND");
     CPPUNIT_ASSERT(sendFile.is_open());
-    // Avoid ASAN error on big alloc   sendFile << std::string("A", 64000);
-    for (int i = 0; i < 64000; ++i)
-        sendFile << "A";
+    sendFile << std::string(64000, 'A');
     sendFile.close();
 
-    // Send File
-    DRing::DataTransferInfo info;
-    uint64_t id;
-    info.accountId = aliceAccount->getAccountID();
-    info.conversationId = convId;
-    info.path = "SEND";
-    info.displayName = "SEND";
-    info.bytesProgress = 0;
-    CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success);
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferWaiting; }));
-    std::this_thread::sleep_for(std::chrono::seconds(5));
+    DRing::sendFile(aliceId, convId, "SEND", "SEND", "");
 
-    CPPUNIT_ASSERT(DRing::cancelDataTransfer(bobId, convId, id)
-                   == DRing::DataTransferError::success);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !mid.empty(); }));
 
-    std::this_thread::sleep_for(std::chrono::seconds(10));
-    CPPUNIT_ASSERT(mid != "");
-    transferWaiting = false;
-    transferFinished = false;
-    DRing::askForTransfer(bobId, "swarm:" + convId, mid, "RECV");
+    // modifiy file
+    sendFile = std::ofstream("SEND");
+    CPPUNIT_ASSERT(sendFile.is_open());
+    // Avoid ASAN error on big alloc   sendFile << std::string("B", 64000);
+    for (int i = 0; i < 64000; ++i)
+        sendFile << "B";
+    sendFile.close();
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferFinished; }));
-    CPPUNIT_ASSERT(fileutils::isFile("RECV"));
+    transferAFinished = false;
+    transferBFinished = false;
+    DRing::downloadFile(bobId, convId, iid, mid, "RECV");
+
+    // The file transfer will not be sent as modified
+    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferAFinished; }));
+    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBFinished; }));
 
     std::remove("SEND");
-    std::remove("RECV");
     DRing::unregisterSignalHandlers();
-    std::this_thread::sleep_for(std::chrono::seconds(5));
 }
 
 void
-FileTransferTest::testBadSha3sumOut()
+FileTransferTest::testBadSha3sumIn()
 {
     std::this_thread::sleep_for(std::chrono::seconds(5));
     // TODO remove. This sleeps is because it take some time for the DHT to be connected
@@ -673,14 +765,15 @@ FileTransferTest::testBadSha3sumOut()
     bool requestReceived = false;
     bool conversationReady = false;
     bool memberJoin = false;
-    std::string mid = {};
+    std::string mid = {}, iid;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& /* conversationId */,
             std::map<std::string, std::string> message) {
             if (accountId == bobId) {
                 if (message["type"] == "application/data-transfer+json") {
-                    mid = message["id"];
+                    mid = message["fileId"];
+                    iid = message["id"];
                 }
             }
             cv.notify_one();
@@ -700,25 +793,22 @@ FileTransferTest::testBadSha3sumOut()
                 cv.notify_one();
             }
         }));
-    bool transferWaiting = false, transferFinished = false;
-    DRing::DataTransferId finalId;
+    bool transferAFinished = false, transferBFinished = false;
     // Watch signals
     confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
         [&](const std::string& accountId,
             const std::string& conversationId,
-            const long unsigned int& id,
+            const std::string&,
+            const std::string&,
             int code) {
-            if (accountId == bobId && conversationId == convId
-                && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) {
-                transferWaiting = true;
-                finalId = id;
-                cv.notify_one();
-            } else if (accountId == aliceId && conversationId == convId
-                       && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
-                transferFinished = true;
-                finalId = id;
-                cv.notify_one();
+            if (conversationId == convId
+                && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
+                if (accountId == aliceId)
+                    transferAFinished = true;
+                if (accountId == bobId)
+                    transferBFinished = true;
             }
+            cv.notify_one();
         }));
     confHandlers.insert(
         DRing::exportable_callback<DRing::ConversationSignal::ConversationMemberEvent>(
@@ -738,57 +828,41 @@ FileTransferTest::testBadSha3sumOut()
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     bobAccount->acceptConversationRequest(convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
         return conversationReady && memberJoin;
     }));
 
     // Create file to send
     std::ofstream sendFile("SEND");
     CPPUNIT_ASSERT(sendFile.is_open());
-    // Avoid ASAN error on big alloc   sendFile << std::string("A", 64000);
-    for (int i = 0; i < 64000; ++i)
-        sendFile << "A";
+    sendFile << std::string(64000, 'A');
     sendFile.close();
 
-    // Send File
-    DRing::DataTransferInfo info;
-    uint64_t id;
-    info.accountId = aliceAccount->getAccountID();
-    info.conversationId = convId;
-    info.path = "SEND";
-    info.displayName = "SEND";
-    info.bytesProgress = 0;
-    CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success);
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferWaiting; }));
-    std::this_thread::sleep_for(std::chrono::seconds(5));
-
-    CPPUNIT_ASSERT(DRing::cancelDataTransfer(bobId, convId, id)
-                   == DRing::DataTransferError::success);
+    aliceAccount->noSha3sumVerification(true);
+    DRing::sendFile(aliceId, convId, "SEND", "SEND", "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !mid.empty(); }));
 
-    std::this_thread::sleep_for(std::chrono::seconds(10));
     // modifiy file
     sendFile = std::ofstream("SEND");
     CPPUNIT_ASSERT(sendFile.is_open());
     // Avoid ASAN error on big alloc   sendFile << std::string("B", 64000);
-    for (int i = 0; i < 64000; ++i)
-        sendFile << "B";
+    sendFile << std::string(64000, 'B');
     sendFile.close();
 
-    CPPUNIT_ASSERT(mid != "");
-    transferWaiting = false;
-    transferFinished = false;
-    DRing::askForTransfer(bobId, "swarm:" + convId, mid, "RECV");
+    transferAFinished = false;
+    transferBFinished = false;
+    DRing::downloadFile(bobId, convId, iid, mid, "RECV");
 
-    // The file transfer will not be sent as modified
-    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferFinished; }));
+    // The file transfer will be sent but refused by bob
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferAFinished; }));
+    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBFinished; }));
 
     std::remove("SEND");
     DRing::unregisterSignalHandlers();
-    std::this_thread::sleep_for(std::chrono::seconds(5));
 }
 
 void
-FileTransferTest::testBadSha3sumIn()
+FileTransferTest::testAskToMultipleParticipants()
 {
     std::this_thread::sleep_for(std::chrono::seconds(5));
     // TODO remove. This sleeps is because it take some time for the DHT to be connected
@@ -796,8 +870,10 @@ FileTransferTest::testBadSha3sumIn()
     JAMI_INFO("Waiting....");
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto bobUri = bobAccount->getUsername();
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto aliceUri = aliceAccount->getUsername();
+    auto bobUri = bobAccount->getUsername();
+    auto carlaUri = carlaAccount->getUsername();
     auto convId = aliceAccount->startConversation();
 
     std::mutex mtx;
@@ -807,14 +883,18 @@ FileTransferTest::testBadSha3sumIn()
     bool requestReceived = false;
     bool conversationReady = false;
     bool memberJoin = false;
-    std::string mid = {};
+    std::string bobTid, carlaTid, iidBob, iidCarla;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& /* conversationId */,
             std::map<std::string, std::string> message) {
-            if (accountId == bobId) {
-                if (message["type"] == "application/data-transfer+json") {
-                    mid = message["id"];
+            if (message["type"] == "application/data-transfer+json") {
+                if (accountId == bobId) {
+                    bobTid = message["fileId"];
+                    iidBob = message["id"];
+                } else if (accountId == carlaId) {
+                    carlaTid = message["fileId"];
+                    iidCarla = message["id"];
                 }
             }
             cv.notify_one();
@@ -829,29 +909,25 @@ FileTransferTest::testBadSha3sumIn()
             }));
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
         [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
+            if (accountId == bobId || accountId == carlaId) {
                 conversationReady = true;
                 cv.notify_one();
             }
         }));
-    bool transferWaiting = false, transferInFinished = false, transferOutFinished = false;
-    DRing::DataTransferId finalId;
+    bool transferBFinished = false, transferCFinished = false;
     // Watch signals
     confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
         [&](const std::string& accountId,
             const std::string& conversationId,
-            const long unsigned int& id,
+            const std::string&,
+            const std::string&,
             int code) {
-            finalId = id;
-            if (accountId == bobId && conversationId == convId
-                && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) {
-                transferWaiting = true;
-            } else if (accountId == bobId && conversationId == convId
-                       && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
-                transferInFinished = true;
-            } else if (accountId == aliceId && conversationId == convId
-                       && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
-                transferOutFinished = true;
+            if (conversationId == convId
+                && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
+                if (accountId == carlaId)
+                    transferCFinished = true;
+                if (accountId == bobId)
+                    transferBFinished = true;
             }
             cv.notify_one();
         }));
@@ -861,8 +937,8 @@ FileTransferTest::testBadSha3sumIn()
                 const std::string& conversationId,
                 const std::string& uri,
                 int event) {
-                if (accountId == aliceId && conversationId == convId && uri == bobUri
-                    && event == 1) {
+                if (accountId == aliceId && conversationId == convId
+                    && (uri == bobUri || uri == carlaUri) && event == 1) {
                     memberJoin = true;
                 }
                 cv.notify_one();
@@ -873,61 +949,52 @@ FileTransferTest::testBadSha3sumIn()
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     bobAccount->acceptConversationRequest(convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
         return conversationReady && memberJoin;
     }));
 
-    // Create file to send
-    std::ofstream sendFile("SEND");
-    CPPUNIT_ASSERT(sendFile.is_open());
-    // Avoid ASAN error on big alloc   sendFile << std::string("A", 64000);
-    for (int i = 0; i < 64000; ++i)
-        sendFile << "A";
-    sendFile.close();
+    requestReceived = false;
+    conversationReady = false;
+    memberJoin = false;
 
-    // Send File
-    DRing::DataTransferInfo info;
-    uint64_t id;
-    info.accountId = aliceAccount->getAccountID();
-    info.conversationId = convId;
-    info.path = "SEND";
-    info.displayName = "SEND";
-    info.bytesProgress = 0;
-    CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success);
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferWaiting; }));
-    std::this_thread::sleep_for(std::chrono::seconds(5));
+    aliceAccount->addConversationMember(convId, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    CPPUNIT_ASSERT(DRing::cancelDataTransfer(bobId, convId, id)
-                   == DRing::DataTransferError::success);
+    carlaAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
+        return conversationReady && memberJoin;
+    }));
 
-    std::this_thread::sleep_for(std::chrono::seconds(10));
-    // modifiy file
-    sendFile = std::ofstream("SEND");
+    // Create file to send
+    std::ofstream sendFile("SEND");
     CPPUNIT_ASSERT(sendFile.is_open());
-    // Avoid ASAN error on big alloc   sendFile << std::string("B", 64000);
-    for (int i = 0; i < 64000; ++i)
-        sendFile << "B";
+    sendFile << std::string(64000, 'A');
     sendFile.close();
 
-    CPPUNIT_ASSERT(mid != "");
-    transferWaiting = false;
-    transferInFinished = false;
-    transferOutFinished = false;
-    DRing::askForTransfer(bobId, "swarm:" + convId, mid, "RECV");
-    aliceAccount->noSha3sumVerification(true);
+    DRing::sendFile(aliceId, convId, "SEND", "SEND", "");
 
-    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() {
-        return transferOutFinished && transferInFinished;
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return !bobTid.empty() && !carlaTid.empty();
     }));
-    CPPUNIT_ASSERT(!fileutils::isFile("RECV"));
+
+    transferCFinished = false;
+    DRing::downloadFile(carlaId, convId, iidCarla, carlaTid, "RECV2");
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferCFinished; }));
+    CPPUNIT_ASSERT(fileutils::isFile("RECV2"));
+
+    transferBFinished = false;
+    DRing::downloadFile(bobId, convId, iidBob, bobTid, "RECV");
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBFinished; }));
+    CPPUNIT_ASSERT(fileutils::isFile("RECV"));
 
     std::remove("SEND");
+    std::remove("RECV");
+    std::remove("RECV2");
     DRing::unregisterSignalHandlers();
-    std::this_thread::sleep_for(std::chrono::seconds(5));
 }
 
 void
-FileTransferTest::testAskToMultipleParticipants()
+FileTransferTest::testCancelInTransfer()
 {
     std::this_thread::sleep_for(std::chrono::seconds(5));
     // TODO remove. This sleeps is because it take some time for the DHT to be connected
@@ -935,10 +1002,8 @@ FileTransferTest::testAskToMultipleParticipants()
     JAMI_INFO("Waiting....");
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto carlaUri = carlaAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
     auto convId = aliceAccount->startConversation();
 
     std::mutex mtx;
@@ -947,17 +1012,21 @@ FileTransferTest::testAskToMultipleParticipants()
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
     bool requestReceived = false;
     bool conversationReady = false;
-    bool memberJoin = false;
-    std::string mid = {};
+    bool bobJoined = false;
+    std::string tidBob, iidBob;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& /* conversationId */,
             std::map<std::string, std::string> message) {
-            if (accountId == bobId) {
-                if (message["type"] == "application/data-transfer+json") {
-                    mid = message["id"];
+            if (message["type"] == "application/data-transfer+json") {
+                if (accountId == bobId) {
+                    iidBob = message["id"];
+                    tidBob = message["fileId"];
                 }
             }
+            if (accountId == aliceId && message["type"] == "member" && message["action"] == "join") {
+                bobJoined = true;
+            }
             cv.notify_one();
         }));
     confHandlers.insert(
@@ -970,53 +1039,30 @@ FileTransferTest::testAskToMultipleParticipants()
             }));
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
         [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId || accountId == carlaId) {
+            if (accountId == bobId) {
                 conversationReady = true;
                 cv.notify_one();
             }
         }));
-    bool transferWaiting = false, transferFinished = false, transferCarlaFinished = false;
-    long unsigned int hostAcceptanceBob = {0}, hostAcceptanceCarla = {0};
-    DRing::DataTransferId finalId;
+    bool transferBOngoing = false, transferBFinished = false;
     // Watch signals
     confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
         [&](const std::string& accountId,
             const std::string& conversationId,
-            const long unsigned int& id,
+            const std::string&,
+            const std::string&,
             int code) {
-            if (code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) {
+            if (code == static_cast<int>(DRing::DataTransferEventCode::ongoing)
+                && conversationId == convId) {
                 if (accountId == bobId)
-                    hostAcceptanceBob = id;
-                else if (accountId == carlaId)
-                    hostAcceptanceCarla = id;
-            } else if (accountId == bobId && conversationId == convId
-                       && code
-                              == static_cast<int>(
-                                  DRing::DataTransferEventCode::wait_host_acceptance)) {
-                transferWaiting = true;
-                hostAcceptanceBob = id;
-            } else if (accountId == bobId && conversationId == convId
-                       && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
-                transferFinished = true;
-                finalId = id;
-            } else if (accountId == carlaId && conversationId == convId
-                       && code == static_cast<int>(DRing::DataTransferEventCode::finished)) {
-                transferCarlaFinished = true;
+                    transferBOngoing = true;
+            } else if (code >= static_cast<int>(DRing::DataTransferEventCode::finished)
+                       && conversationId == convId) {
+                if (accountId == bobId)
+                    transferBFinished = true;
             }
             cv.notify_one();
         }));
-    confHandlers.insert(
-        DRing::exportable_callback<DRing::ConversationSignal::ConversationMemberEvent>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                const std::string& uri,
-                int event) {
-                if (accountId == aliceId && conversationId == convId
-                    && (uri == bobUri || uri == carlaUri) && event == 1) {
-                    memberJoin = true;
-                }
-                cv.notify_one();
-            }));
     DRing::registerSignalHandlers(confHandlers);
 
     aliceAccount->addConversationMember(convId, bobUri);
@@ -1024,63 +1070,141 @@ FileTransferTest::testAskToMultipleParticipants()
 
     bobAccount->acceptConversationRequest(convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
-        return conversationReady && memberJoin;
+        return conversationReady && bobJoined;
     }));
 
-    requestReceived = false;
-    conversationReady = false;
-    memberJoin = false;
+    // Create file to send
+    std::ofstream sendFile("SEND");
+    CPPUNIT_ASSERT(sendFile.is_open());
+    sendFile << std::string(64000, 'A');
+    sendFile.close();
 
-    JAMI_ERR("@@@");
+    DRing::sendFile(aliceId, convId, "SEND", "SEND", "");
 
-    aliceAccount->addConversationMember(convId, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !tidBob.empty(); }));
+
+    transferBOngoing = false;
+    CPPUNIT_ASSERT(DRing::downloadFile(bobId, convId, iidBob, tidBob, "RECV"));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBOngoing; }));
+    transferBFinished = false;
+    DRing::cancelDataTransfer(bobId, convId, tidBob);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBFinished; }));
+    CPPUNIT_ASSERT(!fileutils::isFile("RECV"));
+
+    std::remove("SEND");
+    DRing::unregisterSignalHandlers();
+}
+
+void
+FileTransferTest::testTransferInfo()
+{
+    std::this_thread::sleep_for(std::chrono::seconds(5));
+    // TODO remove. This sleeps is because it take some time for the DHT to be connected
+    // and account announced
+    JAMI_INFO("Waiting....");
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+    auto convId = aliceAccount->startConversation();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool requestReceived = false;
+    bool conversationReady = false;
+    bool bobJoined = false;
+    std::string tidBob, iidBob;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            std::map<std::string, std::string> message) {
+            if (message["type"] == "application/data-transfer+json") {
+                if (accountId == bobId) {
+                    tidBob = message["fileId"];
+                    iidBob = message["id"];
+                }
+            }
+            if (accountId == aliceId && message["type"] == "member" && message["action"] == "join") {
+                bobJoined = true;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == bobId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    bool transferAFinished = false, transferBFinished = false;
+    // Watch signals
+    confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            const std::string&,
+            const std::string&,
+            int code) {
+            if (code == static_cast<int>(DRing::DataTransferEventCode::finished)
+                && conversationId == convId) {
+                if (accountId == aliceId)
+                    transferAFinished = true;
+                else if (accountId == bobId)
+                    transferBFinished = true;
+            }
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    aliceAccount->addConversationMember(convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    carlaAccount->acceptConversationRequest(convId);
+    bobAccount->acceptConversationRequest(convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
-        return conversationReady && memberJoin;
+        return conversationReady && bobJoined;
     }));
 
     // Create file to send
     std::ofstream sendFile("SEND");
     CPPUNIT_ASSERT(sendFile.is_open());
-    // Avoid ASAN error on big alloc   sendFile << std::string("A", 64000);
-    for (int i = 0; i < 64000; ++i)
-        sendFile << "A";
+    sendFile << std::string(64000, 'A');
     sendFile.close();
 
-    // Send File
-    DRing::DataTransferInfo info;
-    uint64_t id;
-    info.accountId = aliceAccount->getAccountID();
-    info.conversationId = convId;
-    info.path = "SEND";
-    info.displayName = "SEND";
-    info.bytesProgress = 0;
-    CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success);
+    DRing::sendFile(aliceId, convId, "SEND", "SEND", "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !tidBob.empty(); }));
+
+    int64_t totalSize, bytesProgress;
+    std::string path;
+    CPPUNIT_ASSERT(DRing::fileTransferInfo(bobId, convId, tidBob, path, totalSize, bytesProgress)
+                   == DRing::DataTransferError::invalid_argument);
+    CPPUNIT_ASSERT(bytesProgress == 0);
+    CPPUNIT_ASSERT(!fileutils::isFile(path));
+    // No check for total as not started
+
+    transferAFinished = false;
+    transferBFinished = false;
+    DRing::downloadFile(bobId, convId, iidBob, tidBob, "RECV");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
-        return hostAcceptanceBob != 0 && hostAcceptanceCarla != 0;
+        return transferAFinished && transferBFinished;
     }));
-
-    CPPUNIT_ASSERT(DRing::cancelDataTransfer(bobId, convId, hostAcceptanceBob)
-                   == DRing::DataTransferError::success);
-    CPPUNIT_ASSERT(DRing::acceptFileTransfer(carlaId, convId, hostAcceptanceCarla, "RECV2", 0)
+    CPPUNIT_ASSERT(DRing::fileTransferInfo(bobId, convId, tidBob, path, totalSize, bytesProgress)
                    == DRing::DataTransferError::success);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferCarlaFinished; }));
-    CPPUNIT_ASSERT(fileutils::isFile("RECV2"));
 
-    std::this_thread::sleep_for(std::chrono::seconds(10));
-    CPPUNIT_ASSERT(mid != "");
-    transferFinished = false;
-    DRing::askForTransfer(bobId, "swarm:" + convId, mid, "RECV");
-
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferFinished; }));
-    CPPUNIT_ASSERT(fileutils::isFile("RECV"));
+    CPPUNIT_ASSERT(bytesProgress == 64000);
+    CPPUNIT_ASSERT(totalSize == 64000);
+    CPPUNIT_ASSERT(fileutils::isFile(path));
 
     std::remove("SEND");
     std::remove("RECV");
-    std::remove("RECV2");
     DRing::unregisterSignalHandlers();
     std::this_thread::sleep_for(std::chrono::seconds(5));
 }
-- 
GitLab