Commit 755347dd authored by Guillaume Roguez's avatar Guillaume Roguez Committed by Olivier SOLDANO

data transfer: first implementation

First implementation of Reliable Data Transfer feature in Ring.

This implementation is a draft, comes with a Python script
tools/dringctrl/sendfile.py to play with and doesn't
implement all the API as described into "datatransfer: API proposal"
commit.
This version uses TLS over TCP-TURN sockets to encrypt data.

Transfers require a TURN server with TCP peer connections
as described by the RFC-6062.

Testing: Currently only sendFile API is implemented and data are
saved into a temporary file saved in "/tmp/ring_XXXXXX",
where XXXXXX are replace by mkstemp() command.

Change-Id: I5b8f48432edd58df5046e368a99f58ea44046dcd
Reviewed-by: default avatarOlivier Soldano <olivier.soldano@savoirfairelinux.com>
parent bdafdfb4
......@@ -1395,6 +1395,29 @@
</arg>
</method>
<method name="sendFile" tp:name-for-bindings="sendFile">
<tp:added version="4.2.0"/>
<arg type="t" name="DataTransferId" direction="out">
</arg>
<arg type="s" name="accountID" direction="in">
</arg>
<arg type="s" name="peer_uri" direction="in">
<tp:docstring>
RingID of request's recipient.
</tp:docstring>
</arg>
<arg type="s" name="file_path" direction="in"></arg>
<arg type="s" name="display_name" direction="in"></arg>
</method>
<method name="dataTransferInfo" tp:name-for-bindings="dataTransferInfo">
<tp:added version="4.2.0"/>
<arg type="(buttss)" name="DataTransferInfo" direction="out">
</arg>
<arg type="t" name="DataTransferId" direction="in">
</arg>
</method>
<signal name="mediaParametersChanged" tp:name-for-bindings="mediaParametersChanged">
<tp:added version="2.3.0"/>
<tp:docstring>
......@@ -1436,6 +1459,21 @@
</arg>
</signal>
<signal name="dataTransferEvent" tp:name-for-bindings="dataTransferEvent">
<tp:added version="4.2.0"/>
<tp:docstring>
Notify clients when a data transfer state change.
</tp:docstring>
<arg type="t" name="id">
<tp:docstring>
Data transfer unique id.
</tp:docstring>
</arg>
<arg type="i" name="code">
<tp:docstring>
A DRing::DataTransferEventCode code
</tp:docstring>
</arg>
</signal>
</interface>
</node>
......@@ -42,6 +42,8 @@
#include "dbuspresencemanager.h"
#include "presencemanager_interface.h"
#include "datatransfer_interface.h"
#ifdef RING_VIDEO
#include "dbusvideomanager.h"
#include "videomanager_interface.h"
......@@ -128,6 +130,7 @@ DBusClient::initLibrary(int flags)
using DRing::ConfigurationSignal;
using DRing::PresenceSignal;
using DRing::AudioSignal;
using DRing::DataTransferSignal;
using SharedCallback = std::shared_ptr<DRing::CallbackWrapperBase>;
......@@ -203,6 +206,10 @@ DBusClient::initLibrary(int flags)
exportable_callback<AudioSignal::DeviceEvent>(bind(&DBusConfigurationManager::audioDeviceEvent, confM)),
};
const std::map<std::string, SharedCallback> dataXferEvHandlers = {
exportable_callback<DataTransferSignal::DataTransferEvent>(bind(&DBusConfigurationManager::dataTransferEvent, confM, _1, _2)),
};
#ifdef RING_VIDEO
// Video event handlers
const std::map<std::string, SharedCallback> videoEvHandlers = {
......@@ -219,6 +226,7 @@ DBusClient::initLibrary(int flags)
registerConfHandlers(configEvHandlers);
registerPresHandlers(presEvHandlers);
registerPresHandlers(audioEvHandlers);
registerDataXferHandlers(dataXferEvHandlers);
#ifdef RING_VIDEO
registerVideoHandlers(videoEvHandlers);
#endif
......
......@@ -24,6 +24,7 @@
#include "dbusconfigurationmanager.h"
#include "configurationmanager_interface.h"
#include "datatransfer_interface.h"
#include "media/audio/audiolayer.h"
......@@ -612,3 +613,24 @@ DBusConfigurationManager::connectivityChanged()
{
DRing::connectivityChanged();
}
auto
DBusConfigurationManager::sendFile(const std::string& account_id, const std::string& peer_uri,
const std::string& file_path, const std::string& display_name) -> decltype(DRing::sendFile(account_id, peer_uri, file_path, display_name))
{
return DRing::sendFile(account_id, peer_uri, file_path, display_name);
}
DBus::Struct<bool, uint32_t, uint64_t, uint64_t, std::string, std::string>
DBusConfigurationManager::dataTransferInfo(const DRing::DataTransferId& id)
{
DBus::Struct<bool, uint32_t, uint64_t, uint64_t, std::string, std::string> out;
auto info = DRing::dataTransferInfo(id);
out._1 = info.isOutgoing;
out._2 = uint32_t(info.lastEvent);
out._3 = info.totalSize;
out._4 = info.bytesProgress;
out._5 = info.displayName;
out._6 = info.path;
return out;
}
......@@ -31,6 +31,8 @@
#include "dbus_cpp.h"
#include "dring/datatransfer_interface.h"
#if __GNUC__ >= 5 || (__GNUC__ >=4 && __GNUC_MINOR__ >= 6)
/* This warning option only exists for gcc 4.6.0 and greater. */
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
......@@ -151,6 +153,9 @@ class DBusConfigurationManager :
int exportAccounts(const std::vector<std::string>& accountIDs, const std::string& filepath, const std::string& password);
int importAccounts(const std::string& archivePath, const std::string& password);
void connectivityChanged();
DRing::DataTransferId sendFile(const std::string& account_id, const std::string& peer_uri,
const std::string& file_path, const std::string& display_name);
DBus::Struct<bool, uint32_t, uint64_t, uint64_t, std::string, std::string> dataTransferInfo(const DRing::DataTransferId& id);
};
#endif // __RING_DBUSCONFIGURATIONMANAGER_H__
......@@ -2,7 +2,7 @@ dnl Ring - configure.ac for automake 1.9 and autoconf 2.59
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ([2.65])
AC_INIT([Ring Daemon],[4.1.0],[ring@gnu.org],[ring])
AC_INIT([Ring Daemon],[4.2.0],[ring@gnu.org],[ring])
AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2017]])
AC_REVISION([$Revision$])
......
......@@ -31,7 +31,7 @@ PROJECT_NAME = "Ring Daemon"
# This could be handy for archiving the generated documentation or
# if some version control system is used.
PROJECT_NUMBER = 4.1.0
PROJECT_NUMBER = 4.2.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer
......
......@@ -139,12 +139,18 @@ libring_la_SOURCES = \
rational.h \
smartools.cpp \
smartools.h \
base64.h \
base64.cpp \
turn_transport.h \
turn_transport.cpp \
channel.h \
generic_io.h
base64.h \
base64.cpp \
turn_transport.h \
turn_transport.cpp \
channel.h \
peer_connection.cpp \
peer_connection.h \
data_transfer.cpp \
data_transfer.h \
ftp_server.cpp \
ftp_server.h \
generic_io.h
if HAVE_WIN32
libring_la_SOURCES += \
......
......@@ -104,7 +104,7 @@ Account::Account(const std::string &accountID)
{
random_device rdev;
std::seed_seq seed {rdev(), rdev()};
rand_.seed(seed);
rand.seed(seed);
// Initialize the codec order, used when creating a new account
loadDefaultCodecs();
......
......@@ -296,7 +296,7 @@ class Account : public Serializable, public std::enable_shared_from_this<Account
* Random generator engine
* Logical account state shall never rely on the state of the random generator.
*/
mutable std::mt19937_64 rand_;
mutable std::mt19937_64 rand;
/**
* Inform the account that the network status has changed.
......
......@@ -16,6 +16,7 @@ libclient_la_SOURCES = \
ring_signal.cpp \
callmanager.cpp \
configurationmanager.cpp \
datatransfer.cpp \
$(PRESENCE_SRC) \
$(VIDEO_SRC)
......
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "datatransfer_interface.h"
#include "manager.h"
#include "data_transfer.h"
#include "client/ring_signal.h"
namespace DRing {
void
registerDataXferHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapperBase>>& handlers)
{
auto& handlers_ = ring::getSignalHandlers();
for (const auto& item : handlers) {
auto iter = handlers_.find(item.first);
if (iter == handlers_.end()) {
RING_ERR("Signal %s not supported", item.first.c_str());
continue;
}
iter->second = std::move(item.second);
}
}
DataTransferId
sendFile(const std::string& account_id,
const std::string& peer_uri,
const std::string& file_path,
const std::string& display_name)
{
return ring::Manager::instance().dataTransfers->sendFile(
account_id, peer_uri, file_path, display_name.empty() ? file_path : display_name);
}
void
acceptFileTransfer(const DataTransferId& id,
const std::string& file_path,
std::size_t offset)
{
ring::Manager::instance().dataTransfers->acceptAsFile(id, file_path, offset);
}
void
cancelDataTransfer(const DataTransferId& id)
{
ring::Manager::instance().dataTransfers->cancel(id);
}
std::streamsize
dataTransferBytesSent(const DataTransferId& id)
{
return ring::Manager::instance().dataTransfers->bytesSent(id);
}
DataTransferInfo
dataTransferInfo(const DataTransferId& id)
{
return ring::Manager::instance().dataTransfers->info(id);
}
} // namespace DRing
......@@ -93,6 +93,9 @@ getSignalHandlers()
/* Audio */
exported_callback<DRing::AudioSignal::DeviceEvent>(),
/* DataTransfer */
exported_callback<DRing::DataTransferSignal::DataTransferEvent>(),
#ifdef RING_VIDEO
/* Video */
exported_callback<DRing::VideoSignal::DeviceEvent>(),
......
......@@ -27,6 +27,7 @@
#include "callmanager_interface.h"
#include "configurationmanager_interface.h"
#include "presencemanager_interface.h"
#include "datatransfer_interface.h"
#ifdef RING_VIDEO
#include "videomanager_interface.h"
......
This diff is collapsed.
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "dring/datatransfer_interface.h"
#include <memory>
#include <string>
namespace ring {
/// Front-end to data transfer service
class DataTransferFacade
{
public:
DataTransferFacade();
~DataTransferFacade();
/// Send a file to a peer.
/// Open a file and send its contents over a reliable connection
/// to given peer using the protocol from given account.
/// This method fails immediately if the file cannot be open in binary read mode,
/// if the account doesn't exist or if it doesn't support data transfer.
/// Remaining actions are operated asynchronously, so events are given by signals.
/// \return a unique data transfer identifier.
/// \except std::invalid_argument account doesn't exist or don't support data transfer.
/// \except std::ios_base::failure in case of open file errors.
DRing::DataTransferId sendFile(const std::string& account_id,
const std::string& peer_uri,
const std::string& file_path,
const std::string& display_name);
/// Accept an incoming transfer and send data into given file.
void acceptAsFile(const DRing::DataTransferId& id,
const std::string& file_path,
std::size_t offset);
/// Abort a transfer.
/// The transfer id is abort and removed. The id is not longer valid after the call.
void cancel(const DRing::DataTransferId& id);
/// \return a copy of all information about a data transfer
DRing::DataTransferInfo info(const DRing::DataTransferId& id) const;
/// \return number of bytes sent by a data transfer
/// \note this method is fatest than info()
std::streamsize bytesSent(const DRing::DataTransferId& id) const;
/// Create an IncomingFileTransfer object.
/// \return a filename to open where incoming data will be written or an empty string
/// in case of refusal.
std::string onIncomingFileRequest(const std::string& display_name, std::size_t offset);
private:
class Impl;
std::unique_ptr<Impl> pimpl_;
};
} // namespace ring
......@@ -33,25 +33,27 @@ namespace DRing {
using DataTransferId = uint64_t;
enum class DataTransferEventCode : uint32_t {
CREATED,
UNSUPPORTED,
WAIT_PEER_ACCEPTANCE,
WAIT_HOST_ACCEPTANCE,
ONGOING,
FINISHED,
CLOSED_BY_HOST,
CLOSED_BY_PEER,
INVALID_PATHNAME,
UNJOINABLE_PEER,
enum class DataTransferEventCode : uint32_t
{
created,
unsupported,
wait_peer_acceptance,
wait_host_acceptance,
ongoing,
finished,
closed_by_host,
closed_by_peer,
invalid_pathname,
unjoinable_peer,
};
struct DataTransferInfo {
struct DataTransferInfo
{
bool isOutgoing; ///< Outgoing or Incoming?
DataTransferEventCode lastEvent {DataTransferEventCode::CREATED}; ///< Latest event code sent to the user
std::string displayName; ///< Human oriented transfer name
DataTransferEventCode lastEvent { DataTransferEventCode::created }; ///< Latest event code sent to the user
std::size_t totalSize {0} ; ///< Total number of bytes to sent/receive, 0 if not known
std::streamsize bytesProgress {0}; ///< Number of bytes sent/received
std::string displayName; ///< Human oriented transfer name
std::string path; ///< associated local file path if supported (empty, if not)
};
......@@ -125,8 +127,10 @@ std::streamsize dataTransferBytesSent(const DataTransferId& id);
void registerDataXferHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapperBase>>&);
// Signals
struct DataTransferSignal {
struct DataTransferEvent {
struct DataTransferSignal
{
struct DataTransferEvent
{
constexpr static const char* name = "DataTransferEvent";
using cb_type = void(const DataTransferId& transferId, int eventCode);
};
......
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ftp_server.h"
#include "logger.h"
#include "string_utils.h"
#include "data_transfer.h"
#include "manager.h"
#include <algorithm>
#include <array>
#include <stdexcept>
#include <iterator>
#include <cstdlib> // strtoull
namespace ring {
//==============================================================================
FtpServer::FtpServer()
: Stream()
{}
DRing::DataTransferId
FtpServer::getId() const
{
return 0;
}
void
FtpServer::close() noexcept
{
out_.close();
RING_WARN() << "[FTP] server closed";
}
bool
FtpServer::startNewFile()
{
// Request filename from client (WARNING: synchrone call!)
auto filename = Manager::instance().dataTransfers->onIncomingFileRequest(displayName_, 0 /* TODO: offset */);
if (filename.empty())
return false;
out_.open(&filename[0], std::ios::binary);
if (!out_)
throw std::system_error(errno, std::generic_category());
RING_WARN() << "[FTP] Receiving file " << filename;
return true;
}
void
FtpServer::closeCurrentFile()
{
out_.close();
RING_WARN() << "[FTP] File received, " << rx_ << " byte(s)";
rx_ = fileSize_ = 0;
}
bool
FtpServer::write(const std::vector<uint8_t>& buffer)
{
switch (state_) {
case FtpState::PARSE_HEADERS:
if (parseStream(buffer)) {
if (!startNewFile()) {
headerStream_.clear();
headerStream_.str({}); // reset
return true;
}
state_ = FtpState::READ_DATA;
while (headerStream_) {
headerStream_.read(&line_[0], line_.size());
out_.write(&line_[0], headerStream_.gcount());
rx_ += headerStream_.gcount();
if (rx_ >= fileSize_) {
closeCurrentFile();
state_ = FtpState::PARSE_HEADERS;
}
}
headerStream_.clear();
headerStream_.str({}); // reset
}
break;
case FtpState::READ_DATA:
out_.write(reinterpret_cast<const char*>(&buffer[0]), buffer.size());
rx_ += buffer.size();
if (rx_ >= fileSize_) {
closeCurrentFile();
state_ = FtpState::PARSE_HEADERS;
}
break;
default: break;
}
return true; // server always alive
}
bool
FtpServer::parseStream(const std::vector<uint8_t>& buffer)
{
headerStream_ << std::string(std::begin(buffer), std::end(buffer));
// Simple line stream parser
while (headerStream_.getline(&line_[0], line_.size())) {
if (parseLine(std::string(&line_[0], headerStream_.gcount()-1)))
return true; // headers EOF, data may remain in headerStream_
}
if (headerStream_.fail())
throw std::runtime_error("[FTP] header parsing error");
headerStream_.clear();
return false; // need more data
}
bool
FtpServer::parseLine(const std::string& line)
{
if (line.empty())
return true;
// Valid line found, parse it as "key: value" and store until end of headers detection
const auto& sep_pos = line.find(':');
if (sep_pos == std::string::npos)
throw std::runtime_error("[FTP] stream protocol error: bad format");
handleHeader(trim(line.substr(0, sep_pos)), trim(line.substr(sep_pos+1)));
return false;
}
void
FtpServer::handleHeader(const std::string& key, const std::string& value)
{
RING_DBG() << "[FTP] header: '" << key << "' = '"<< value << "'";
if (key == "Content-Length") {
fileSize_ = std::strtoull(&value[0], nullptr, 10);
} else if (key == "Display-Name") {
displayName_ = value;
}
}
} // namespace ring
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "peer_connection.h"
#include <vector>
#include <array>
#include <fstream>
#include <sstream>
namespace ring {
class FtpServer final : public Stream
{
public:
FtpServer();
bool write(const std::vector<uint8_t>& buffer) override;
DRing::DataTransferId getId() const override;
void close() noexcept override;
private:
bool parseStream(const std::vector<uint8_t>&);
bool parseLine(const std::string&);
void handleHeader(const std::string&, const std::string&);
bool startNewFile();
void closeCurrentFile();
enum class FtpState {
PARSE_HEADERS,
READ_DATA,
};
std::ofstream out_;
std::size_t fileSize_ {0};
std::size_t rx_ {0};
std::stringstream headerStream_;
std::string displayName_;
std::array<char, 1000> line_;
FtpState state_ {FtpState::PARSE_HEADERS};
};
} // namespace ring
......@@ -46,7 +46,7 @@ MessageEngine::sendMessage(const std::string& to, const std::map<std::string, st
{
std::lock_guard<std::mutex> lock(messagesMutex_);
do {
token = udist(account_.rand_);
token = udist(account_.rand);
} while (messages_.find(token) != messages_.end());
auto m = messages_.emplace(token, Message{});
m.first->second.to = to;
......
......@@ -79,6 +79,8 @@ using random_device = dht::crypto::random_device;
#include "video/sinkclient.h"
#include "audio/tonecontrol.h"
#include "data_transfer.h"
#include <cerrno>
#include <ctime>
#include <cstdlib>
......@@ -662,6 +664,7 @@ Manager::Manager()
#endif
, callFactory()
, accountFactory()
, dataTransfers(std::make_unique<DataTransferFacade>())
, pimpl_ (new ManagerPimpl(*this))
{}
......
......@@ -52,6 +52,7 @@ class VideoManager;
class Conference;
class AudioLoop;
class IceTransportFactory;
class DataTransferFacade;
/** Manager (controller) of Ring daemon */
class Manager {
......@@ -890,6 +891,8 @@ class Manager {
std::atomic<unsigned> dhtLogLevel {0}; // default = disable
AccountFactory accountFactory;
std::unique_ptr<DataTransferFacade> dataTransfers;
private:
Manager();
~Manager();
......
This diff is collapsed.
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the