Commit bdafdfb4 authored by Guillaume Roguez's avatar Guillaume Roguez Committed by Olivier SOLDANO

make TlsSession great again

Or at least independant of underlaying transport...

To make TlsSession able to handle both TLS and DTLS
this patch removes the ICE dependency and replace is
by the generic network ABC class 'GenericTransport'.
As a first step this class is declared in tls_session.h.
Side effects of this change are:

* refactoring of PMTUD procedure: 'MTU' for gnutls has the meaning
  on 'payload-for-gnutls' so this information is now drived by
  the generic transport and not hardcoded anymore.
  The minimal value of probing remains hardcoded, as is a minimum
  given by RFC's documentation and it's based on an IPv4 packet
  associated with UDP protocol.

* getMtu() is now maxPayload() and represent correctly what
  the application must have.

* TlsSession implements itself GenericTransport: we can chain
  GenericTransport instances to construct an overlayed transport
  protocol.

* TlsSession is now considered as non thread-safe for its public API.
  Caller must bring itself this property.
  This permit to remove a redundant mutex in send() operation.
  Note: and it's the case in the only user (SipsIceTransport),
  that why the mutex is redundant in 100% of cases.

Notice the benefit of this genericity refactoring let us
write a unit-test for this TlsSession class without having
an heavy ICE transport to mock-up.

Also ICE transport gained of this by adding a new IceSocketTransport
to replace IceSocket in a near future (need async IO in GenericSocket,
but not required for the moment).

Change-Id: I6f4591ed6c76fa9cb5519c6e9296f8fc3a6798aa
Reviewed-by: default avatarOlivier Soldano <olivier.soldano@savoirfairelinux.com>
parent 2dee5360
......@@ -143,7 +143,8 @@ libring_la_SOURCES = \
base64.cpp \
turn_transport.h \
turn_transport.cpp \
channel.h
channel.h \
generic_io.h
if HAVE_WIN32
libring_la_SOURCES += \
......
/*
* 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 <functional>
#include <vector>
#include <system_error>
#include <cstdint>
#if defined(_MSC_VER)
#include <BaseTsd.h>
using ssize_t = SSIZE_T;
#endif
namespace ring {
template <typename T>
class GenericSocket
{
public:
using ValueType = T;
virtual ~GenericSocket() = default;
using RecvCb = std::function<ssize_t(const ValueType* buf, std::size_t len)>;
/// Set Rx callback
/// \warning This method is here for backward compatibility
/// and because async IO are not implemented yet.
virtual void setOnRecv(RecvCb&& cb) = 0;
virtual bool isReliable() const = 0;
virtual bool isInitiator() const = 0;
/// Return maximum application payload size.
/// This value is negative if the session is not ready to give a valid answer.
/// The value is 0 if such information is irrelevant for the session.
/// If stricly positive, the user must use send() with an input buffer size below or equals
/// to this value if it want to be sure that the transport sent it in an atomic way.
/// Example: in case of non-reliable transport using packet oriented IO,
/// this value gives the maximal size used to send one packet.
virtual int maxPayload() const = 0;
// TODO: make a std::chrono version
virtual bool waitForData(unsigned ms_timeout) const = 0;
/// Write a given amount of data.
/// \param buf data to write.
/// \param len number of bytes to write.
/// \param ec error code set in case of error.
/// \return number of bytes written, 0 is valid.
/// \warning error checking consists in checking if \a !ec is true, not if returned size is 0
/// as a write of 0 could be considered a valid operation.
virtual std::size_t write(const ValueType* buf, std::size_t len, std::error_code& ec) = 0;
/// Read a given amount of data.
/// \param buf data to read.
/// \param len number of bytes to read.
/// \param ec error code set in case of error.
/// \return number of bytes read, 0 is valid.
/// \warning error checking consists in checking if \a !ec is true, not if returned size is 0
/// as a read of 0 could be considered a valid operation (i.e. non-blocking IO).
virtual std::size_t read(ValueType* buf, std::size_t len, std::error_code& ec) = 0;
/// write() adaptor for STL containers
template <typename U>
std::size_t write(const U& obj, std::error_code& ec) {
return write(obj.data(), obj.size() * sizeof(typename U::value_type), ec);
}
/// read() adaptor for STL containers
template <typename U>
std::size_t read(U& storage, std::error_code& ec) {
auto res = read(storage.data(), storage.size() * sizeof(typename U::value_type), ec);
if (!ec)
storage.resize(res);
return res;
}
protected:
GenericSocket() = default;
};
} // namespace ring
......@@ -17,8 +17,9 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef ICE_SOCKET_H
#define ICE_SOCKET_H
#pragma once
#include "generic_io.h"
#include <memory>
#include <functional>
......@@ -51,6 +52,44 @@ class IceSocket
uint16_t getTransportOverhead();
};
/// ICE transport as a GenericSocket.
///
/// \warning Simplified version where we assume that ICE protocol
/// always use UDP over IP over ETHERNET, and doesn't add more header to the UDP payload.
///
class IceSocketTransport final : public GenericSocket<uint8_t>
{
public:
using SocketType = GenericSocket<uint8_t>;
static constexpr uint16_t STANDARD_MTU_SIZE = 1280; // Size in bytes of MTU for IPv6 capable networks
static constexpr uint16_t IPV6_HEADER_SIZE = 40; // Size in bytes of IPv6 packet header
static constexpr uint16_t IPV4_HEADER_SIZE = 20; // Size in bytes of IPv4 packet header
static constexpr uint16_t UDP_HEADER_SIZE = 8; // Size in bytes of UDP header
IceSocketTransport(std::shared_ptr<IceTransport>& ice, int comp_id)
: compId_ {comp_id}
, ice_ {ice} {}
bool isReliable() const override {
return false; // we consider that a ICE transport is never reliable (UDP support only)
}
bool isInitiator() const override;
int maxPayload() const override;
bool waitForData(unsigned ms_timeout) const override;
std::size_t write(const ValueType* buf, std::size_t len, std::error_code& ec) override;
std::size_t read(ValueType* buf, std::size_t len, std::error_code& ec) override;
void setOnRecv(RecvCb&& cb) override;
private:
const int compId_;
std::shared_ptr<IceTransport> ice_;
};
#endif /* ICE_SOCKET_H */
};
......@@ -1176,6 +1176,58 @@ IceTransportFactory::createTransport(const char* name, int component_count,
//==============================================================================
void
IceSocketTransport::setOnRecv(RecvCb&& cb)
{
return ice_->setOnRecv(compId_, cb);
}
bool
IceSocketTransport::isInitiator() const
{
return ice_->isInitiator();
}
int
IceSocketTransport::maxPayload() const
{
auto ip_header_size = (ice_->getRemoteAddress(compId_).getFamily() == AF_INET) ?
IPV4_HEADER_SIZE : IPV6_HEADER_SIZE;
return STANDARD_MTU_SIZE - ip_header_size - UDP_HEADER_SIZE;
}
bool
IceSocketTransport::waitForData(unsigned ms_timeout) const
{
return ice_->waitForData(compId_, ms_timeout) > 0;
}
std::size_t
IceSocketTransport::write(const ValueType* buf, std::size_t len, std::error_code& ec)
{
auto res = ice_->send(compId_, buf, len);
if (res < 0) {
ec.assign(errno, std::generic_category());
return 0;
}
ec.clear();
return res;
}
std::size_t
IceSocketTransport::read(ValueType* buf, std::size_t len, std::error_code& ec)
{
auto res = ice_->recv(compId_, buf, len);
if (res < 0) {
ec.assign(errno, std::generic_category());
return 0;
}
ec.clear();
return res;
}
//==============================================================================
void
IceSocket::close()
{
......
......@@ -21,7 +21,9 @@
#include "sips_transport_ice.h"
#include "ice_socket.h"
#include "ice_transport.h"
#include "manager.h"
#include "sip/sip_utils.h"
#include "logger.h"
......@@ -38,6 +40,7 @@
#include <pj/lock.h>
#include <algorithm>
#include <system_error>
#include <cstring> // std::memset
namespace ring { namespace tls {
......@@ -233,14 +236,16 @@ SipsIceTransport::SipsIceTransport(pjsip_endpoint* endpt,
std::memset(&localCertInfo_, 0, sizeof(pj_ssl_cert_info));
std::memset(&remoteCertInfo_, 0, sizeof(pj_ssl_cert_info));
iceSocket_ = std::make_unique<IceSocketTransport>(ice_, comp_id);
TlsSession::TlsSessionCallbacks cbs = {
/*.onStateChange = */[this](TlsSessionState state){ onTlsStateChange(state); },
/*.onRxData = */[this](std::vector<uint8_t>&& buf){ onRxData(std::move(buf)); },
/*.onCertificatesUpdate = */[this](const gnutls_datum_t* l, const gnutls_datum_t* r,
unsigned int n){ onCertificatesUpdate(l, r, n); },
unsigned int n){ onCertificatesUpdate(l, r, n); },
/*.verifyCertificate = */[this](gnutls_session_t session){ return verifyCertificate(session); }
};
tls_.reset(new TlsSession(ice, comp_id, param, cbs));
tls_ = std::make_unique<TlsSession>(*iceSocket_, param, cbs);
if (pjsip_transport_register(base.tpmgr, &base) != PJ_SUCCESS)
throw std::runtime_error("Can't register PJSIP transport.");
......@@ -323,18 +328,19 @@ SipsIceTransport::handleEvents()
pj_status_t status;
if (!fatal) {
const std::size_t size = tdata->buf.cur - tdata->buf.start;
auto ret = tls_->send(tdata->buf.start, size);
if (gnutls_error_is_fatal(ret)) {
RING_ERR("[TLS] fatal error during sending: %s", gnutls_strerror(ret));
tls_->shutdown();
fatal = true;
std::error_code ec;
status = tls_->write(reinterpret_cast<const uint8_t*>(tdata->buf.start), size, ec);
if (ec) {
status = tls_status_from_err(ec.value());
if (gnutls_error_is_fatal(ec.value())) {
RING_ERR("[TLS] fatal error during sending: %s", gnutls_strerror(ec.value()));
tls_->shutdown();
fatal = true;
}
}
if (ret < 0)
status = tls_status_from_err(ret);
else
status = ret;
} else
} else {
status = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
}
tdata->op_key.tdata = nullptr;
if (tdata->op_key.callback)
......@@ -501,7 +507,7 @@ SipsIceTransport::getInfo(pj_ssl_sock_info* info, bool established)
if (established) {
// Cipher Suite Id
std::array<uint8_t, 2> cs_id;
if (auto cipher_name = tls_->getCurrentCipherSuiteId(cs_id)) {
if (auto cipher_name = tls_->currentCipherSuiteId(cs_id)) {
info->cipher = static_cast<pj_ssl_cipher>((cs_id[0] << 8) | cs_id[1]);
RING_DBG("[TLS] using cipher %s (0x%02X%02X)", cipher_name, cs_id[0], cs_id[1]);
} else
......@@ -673,14 +679,15 @@ SipsIceTransport::send(pjsip_tx_data* tdata, const pj_sockaddr_t* rem_addr,
const std::size_t size = tdata->buf.cur - tdata->buf.start;
std::unique_lock<std::mutex> lk {txMutex_};
if (syncTx_ and txQueue_.empty()) {
auto ret = tls_->send(tdata->buf.start, size);
std::error_code ec;
tls_->write(reinterpret_cast<const uint8_t*>(tdata->buf.start), size, ec);
lk.unlock();
// Shutdown on fatal error, else ignore it
if (gnutls_error_is_fatal(ret)) {
RING_ERR("[TLS] fatal error during sending: %s", gnutls_strerror(ret));
if (ec and gnutls_error_is_fatal(ec.value())) {
RING_ERR("[TLS] fatal error during sending: %s", gnutls_strerror(ec.value()));
tls_->shutdown();
return tls_status_from_err(ret);
return tls_status_from_err(ec.value());
}
return PJ_SUCCESS;
......@@ -697,7 +704,7 @@ SipsIceTransport::send(pjsip_tx_data* tdata, const pj_sockaddr_t* rem_addr,
uint16_t
SipsIceTransport::getTlsSessionMtu()
{
return tls_->getMtu();
return tls_->maxPayload();
}
}} // namespace ring::tls
......@@ -43,6 +43,7 @@
namespace ring {
class IceTransport;
class IceSocketTransport;
} // namespace ring
namespace ring { namespace tls {
......@@ -80,7 +81,7 @@ struct SipsIceTransport
private:
NON_COPYABLE(SipsIceTransport);
const std::shared_ptr<IceTransport> ice_;
std::shared_ptr<IceTransport> ice_;
const int comp_id_;
const std::function<int(unsigned, const gnutls_datum_t*, unsigned)> certCheck_;
IpAddr local_ {};
......@@ -109,6 +110,7 @@ private:
decltype(PJSIP_TP_STATE_DISCONNECTED) state;
};
std::unique_ptr<IceSocketTransport> iceSocket_;
std::unique_ptr<TlsSession> tls_;
std::mutex txMutex_ {};
......
This diff is collapsed.
......@@ -22,6 +22,7 @@
#pragma once
#include "noncopyable.h"
#include "generic_io.h"
#include <gnutls/gnutls.h>
......@@ -32,12 +33,6 @@
#include <chrono>
#include <vector>
#include <array>
#include <cstdint>
namespace ring {
class IceTransport;
class IceSocket;
} // namespace ring
namespace dht { namespace crypto {
struct Certificate;
......@@ -48,20 +43,18 @@ namespace ring { namespace tls {
class DhParams;
static constexpr uint8_t MTUS_TO_TEST = 3; //number of mtus to test in path mtu discovery.
static constexpr int DTLS_MTU {1232}; // (1280 from IPv6 minimum MTU - 40 IPv6 header - 8 UDP header)
static constexpr uint16_t MIN_MTU {512};
enum class TlsSessionState {
enum class TlsSessionState
{
SETUP,
COOKIE, // server only
COOKIE, // only used with non-initiator and non-reliable transport
HANDSHAKE,
MTU_DISCOVERY,
MTU_DISCOVERY, // only used with non-reliable transport
ESTABLISHED,
SHUTDOWN
};
struct TlsParams {
struct TlsParams
{
// User CA list for session credentials
std::string ca_list;
......@@ -83,20 +76,22 @@ struct TlsParams {
unsigned cert_list_size)> cert_check;
};
/**
* TlsSession
*
* Manages a DTLS connection over an ICE transport.
* This implementation uses a Threadloop to manage IO from ICE and TLS states,
* so IO are asynchronous.
*/
class TlsSession {
/// TlsSession
///
/// Manages a TLS/DTLS data transport overlayed on a given generic socket.
///
/// \note API is not thread-safe.
///
class TlsSession : public GenericSocket<uint8_t>
{
public:
using SocketType = GenericSocket<uint8_t>;
using OnStateChangeFunc = std::function<void(TlsSessionState)>;
using OnRxDataFunc = std::function<void(std::vector<uint8_t>&&)>;
using OnCertificatesUpdate = std::function<void(const gnutls_datum_t*, const gnutls_datum_t*, unsigned int)>;
using OnCertificatesUpdate = std::function<void(const gnutls_datum_t*,
const gnutls_datum_t*,
unsigned int)>;
using VerifyCertificate = std::function<int(gnutls_session_t)>;
using TxDataCompleteFunc = std::function<void(std::size_t bytes_sent)>;
// ===> WARNINGS <===
// Following callbacks are called into the FSM thread context
......@@ -108,38 +103,44 @@ public:
VerifyCertificate verifyCertificate;
};
TlsSession(const std::shared_ptr<IceTransport>& ice, int ice_comp_id, const TlsParams& params,
const TlsSessionCallbacks& cbs, bool anonymous=true);
TlsSession(SocketType& transport, const TlsParams& params, const TlsSessionCallbacks& cbs,
bool anonymous=true);
~TlsSession();
// Returns the TLS session type ('server' or 'client')
const char* typeName() const;
/// Return the name of current cipher.
/// Can be called by onStateChange callback when state == ESTABLISHED
/// to obtain the used cypher suite id.
const char* currentCipherSuiteId(std::array<uint8_t, 2>& cs_id) const;
bool isServer() const;
// Request TLS thread to stop and quit. IO are not possible after that.
/// Request TLS thread to stop and quit.
/// \note IO operations return error after this call.
void shutdown();
// Return maximum application payload size in bytes
// Returned value must be checked and considered valid only if not 0 (session is initialized)
unsigned int getMaxPayload() const;
void setOnRecv(RecvCb&& cb) override {
(void)cb;
throw std::logic_error("TlsSession::setOnRecv not implemented");
}
/// Return true if the TLS session type is a server.
bool isInitiator() const override;
bool isReliable() const override;
int maxPayload() const override;
// Can be called by onStateChange callback when state == ESTABLISHED
// to obtain the used cypher suite id.
// Return the name of current cipher.
const char* getCurrentCipherSuiteId(std::array<uint8_t, 2>& cs_id) const;
void connect();
// Asynchronous sending operation. on_send_complete will be called with a positive number
// for number of bytes sent, or negative for errors, or 0 in case of shutdown (end of session).
int async_send(const void* data, std::size_t size, TxDataCompleteFunc on_send_complete);
int async_send(std::vector<uint8_t>&& data, TxDataCompleteFunc on_send_complete);
/// Synchronous writing.
/// Return a positive number for number of bytes write, or 0 and \a ec set in case of error.
std::size_t write(const ValueType* data, std::size_t size, std::error_code& ec) override;
// Synchronous sending operation. Return negative number (gnutls error) or a positive number
// for bytes sent.
ssize_t send(const void* data, std::size_t size);
ssize_t send(const std::vector<uint8_t>& data);
/// Synchronous reading.
/// Return a positive number for number of bytes read, or 0 and \a ec set in case of error.
std::size_t read(ValueType* data, std::size_t size, std::error_code& ec) override;
uint16_t getMtu();
bool waitForData(unsigned) const override {
throw std::logic_error("TlsSession::waitForData not implemented");
}
private:
class TlsSessionImpl;
......
......@@ -182,7 +182,12 @@ SipTransport::getTlsMtu()
auto tls_tr = reinterpret_cast<tls::SipsIceTransport::TransportData*>(transport_.get())->self;
return tls_tr->getTlsSessionMtu();
}
return ring::tls::DTLS_MTU;
return 1232; /* Hardcoded yes (it's the IPv6 value).
* This method is broken by definition.
* A MTU should not be defined at this layer.
* And a correct value should come from the underlying transport itself,
* not from a constant...
*/
}
SipTransportBroker::SipTransportBroker(pjsip_endpoint *endpt,
......
......@@ -29,7 +29,6 @@
#include <pjlib-util.h>
#include <pjlib.h>
#include <stdexcept>
#include <future>
#include <atomic>
#include <thread>
......@@ -46,6 +45,9 @@ namespace ring {
using MutexGuard = std::lock_guard<std::mutex>;
using MutexLock = std::unique_lock<std::mutex>;
inline
namespace {
enum class RelayState
{
NONE,
......@@ -82,31 +84,19 @@ public:
cv_.notify_one();
}
void read(std::vector<char>& output) {
template <typename Duration>
bool wait(Duration timeout) {
MutexLock lk {mutex_};
cv_.wait(lk, [&, this]{
stream_.read(&output[0], output.size());
return stream_.gcount() > 0 or stop_;
});
output.resize(stop_ ? 0 : stream_.gcount());
return cv_.wait_for(lk, timeout, [this]{ return !stream_.eof(); });
}
std::vector<char> readline() {
std::size_t read(char* output, std::size_t size) {
MutexLock lk {mutex_};
std::vector<char> result(3000);
cv_.wait(lk, [&, this] {
if (stop_)
return true;
stream_.getline(&result[0], 3000);
if (stream_) {
result.resize(stream_.gcount());
return result.size() > 0;
}
return false;
cv_.wait(lk, [&, this]{
stream_.read(&output[0], size);
return stream_.gcount() > 0 or stop_;
});
if (stop_)
return {};
return result;
return stop_ ? 0 : stream_.gcount();
}
private:
......@@ -120,6 +110,31 @@ private:
friend void operator <<(std::vector<char>&, PeerChannel&);
};
}
//==============================================================================
template <class Callable, class... Args>
inline void
PjsipCall(Callable& func, Args... args)
{
auto status = func(args...);
if (status != PJ_SUCCESS)
throw sip_utils::PjsipFailure(status);
}
template <class Callable, class... Args>
inline auto
PjsipCallReturn(const Callable& func, Args... args) -> decltype(func(args...))
{
auto res = func(args...);
if (!res)
throw sip_utils::PjsipFailure();
return res;
}
//==============================================================================
class TurnTransportPimpl
{
public:
......@@ -135,6 +150,7 @@ public:
std::map<IpAddr, PeerChannel> peerChannels_;
GenericSocket<uint8_t>::RecvCb onRxDataCb;
TurnTransportParams settings;
pj_caching_pool poolCache {};
pj_pool_t* pool {nullptr};
......@@ -193,7 +209,10 @@ TurnTransportPimpl::onRxData(const uint8_t* pkt, unsigned pkt_len,
return;
}
(channel_it->second) << std::string(reinterpret_cast<const char*>(pkt), pkt_len);
if (onRxDataCb)
onRxDataCb(pkt, pkt_len);
else
(channel_it->second) << std::string(reinterpret_cast<const char*>(pkt), pkt_len);
}
void
......@@ -226,45 +245,10 @@ TurnTransportPimpl::ioJob()
const pj_time_val delay = {0, 10};
pj_ioqueue_poll(stunConfig.ioqueue, &delay);
pj_timer_heap_poll(stunConfig.timer_heap, nullptr);
}
}
class PjsipError final : public std::exception {
public:
PjsipError() = default;
explicit PjsipError(pj_status_t st) : std::exception() {
char err_msg[PJ_ERR_MSG_SIZE];
pj_strerror(st, err_msg, sizeof(err_msg));
what_msg_ += ": ";
what_msg_ += err_msg;
}
const char* what() const noexcept override {
return what_msg_.c_str();
};
private:
std::string what_msg_ {"PJSIP api error"};
};
template <class Callable, class... Args>
inline void
PjsipCall(Callable& func, Args... args)
{
auto status = func(args...);
if (status != PJ_SUCCESS)
throw PjsipError(status);
}
template <class Callable, class... Args>
inline auto
PjsipCallReturn(const Callable& func, Args... args) -> decltype(func(args...))
{
auto res = func(args...);
if (!res)
throw PjsipError();
return res;
}
//==================================================================================================
//==============================================================================
TurnTransport::TurnTransport(const TurnTransportParams& params)
: pimpl_ {new TurnTransportPimpl}
......@@ -354,6 +338,12 @@ TurnTransport::TurnTransport(const TurnTransportParams& params)
TurnTransport::~TurnTransport()
{}
bool
TurnTransport::isInitiator() const
{
return !pimpl_->settings.server;
}
void
TurnTransport::permitPeer(const IpAddr& addr)
{
......@@ -363,6 +353,7 @@ TurnTransport::permitPeer(const IpAddr& addr)
if (addr.getFamily() != pimpl_->peerRelayAddr.getFamily())
throw std::invalid_argument("mismatching peer address family");
sip_utils::register_thread();
PjsipCall(pj_turn_sock_set_perm, pimpl_->relay, 1, addr.pjPtr(), 1);
}
......@@ -400,7 +391,7 @@ TurnTransport::sendto(const IpAddr& peer, const char* const buffer, std::size_t
reinterpret_cast<const pj_uint8_t*>(buffer), length,
peer.pjPtr(), peer.getLength());
if (status != PJ_SUCCESS && status != PJ_EPENDING)
throw PjsipError(status);
throw sip_utils::PjsipFailure(status);
return status == PJ_SUCCESS;
}
......@@ -411,40 +402,84 @@ TurnTransport::sendto(const IpAddr& peer, const std::vector<char>& buffer)
return sendto(peer, &buffer[0], buffer.size());
}
bool
TurnTransport::writelineto(const IpAddr& peer, const char* const buffer, std::size_t length)
std::size_t
TurnTransport::recvfrom(const IpAddr& peer, char* buffer, std::size_t size)
{
if (sendto(peer, buffer, length))
return sendto(peer, "\n", 1);
return false;
MutexLock lk {pimpl_->apiMutex_};
auto& channel = pimpl_->peerChannels_.at(peer);
lk.unlock();
return channel.read(buffer, size);
}
void
TurnTransport::recvfrom(const IpAddr& peer, std::vector<char>& result)
{
if (result.empty())
throw std::runtime_error("TurnTransport::recvfrom() called with an empty output buffer");
auto res = recvfrom(peer, result.data(), result.size());
result.resize(res);
}