mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 11:17:38 +02:00
Use WebSocket address and port as WebRTC data channel ID
This commit is contained in:
parent
b5867fef48
commit
e9776541bd
12 changed files with 118 additions and 164 deletions
|
@ -891,16 +891,7 @@ void DomainServer::routeWebRTCSignalingMessage(const QJsonObject& json) {
|
||||||
if (json.value("to").toString() == NodeType::DomainServer) {
|
if (json.value("to").toString() == NodeType::DomainServer) {
|
||||||
emit webrtcSignalingMessageForDomainServer(json);
|
emit webrtcSignalingMessageForDomainServer(json);
|
||||||
} else {
|
} else {
|
||||||
// Insert the WebRTC data channel ID for the assignment client to use.
|
sendWebRTCSignalingMessageToAssignmentClient(json);
|
||||||
auto webrtcSocket = DependencyManager::get<LimitedNodeList>()->getWebRTCSocket();
|
|
||||||
auto channelID = webrtcSocket->getDataChannelIDForWebSocket((quint16)json.value("from").toInt());
|
|
||||||
if (channelID == 0 && json.value("echo").isUndefined()) { // Let echo messages through without a domain connection.
|
|
||||||
qCritical() << "WebRTC data channel ID not found for assignment client signaling!";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QJsonObject jsonModified = json;
|
|
||||||
jsonModified.insert("channel", QJsonValue(channelID));
|
|
||||||
sendWebRTCSignalingMessageToAssignmentClient(jsonModified);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,10 @@ QString SockAddr::toString() const {
|
||||||
return socketTypeToString(_socketType) + " " + _address.toString() + ":" + QString::number(_port);
|
return socketTypeToString(_socketType) + " " + _address.toString() + ":" + QString::number(_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SockAddr::toShortString() const {
|
||||||
|
return _address.toString() + ":" + QString::number(_port);
|
||||||
|
}
|
||||||
|
|
||||||
bool SockAddr::hasPrivateAddress() const {
|
bool SockAddr::hasPrivateAddress() const {
|
||||||
// an address is private if it is loopback or falls in any of the RFC1918 address spaces
|
// an address is private if it is loopback or falls in any of the RFC1918 address spaces
|
||||||
const QPair<QHostAddress, int> TWENTY_FOUR_BIT_BLOCK = { QHostAddress("10.0.0.0"), 8 };
|
const QPair<QHostAddress, int> TWENTY_FOUR_BIT_BLOCK = { QHostAddress("10.0.0.0"), 8 };
|
||||||
|
|
|
@ -56,6 +56,7 @@ public:
|
||||||
static int unpackSockAddr(const unsigned char* packetData, SockAddr& unpackDestSockAddr);
|
static int unpackSockAddr(const unsigned char* packetData, SockAddr& unpackDestSockAddr);
|
||||||
|
|
||||||
QString toString() const;
|
QString toString() const;
|
||||||
|
QString toShortString() const;
|
||||||
|
|
||||||
bool hasPrivateAddress() const; // checks if the address behind this sock addr is private per RFC 1918
|
bool hasPrivateAddress() const; // checks if the address behind this sock addr is private per RFC 1918
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ qint64 NetworkSocket::writeDatagram(const QByteArray& datagram, const SockAddr&
|
||||||
return _udpSocket.writeDatagram(datagram, sockAddr.getAddress(), sockAddr.getPort());
|
return _udpSocket.writeDatagram(datagram, sockAddr.getAddress(), sockAddr.getPort());
|
||||||
#if defined(WEBRTC_DATA_CHANNELS)
|
#if defined(WEBRTC_DATA_CHANNELS)
|
||||||
case SocketType::WebRTC:
|
case SocketType::WebRTC:
|
||||||
return _webrtcSocket.writeDatagram(datagram, sockAddr.getPort());
|
return _webrtcSocket.writeDatagram(datagram, sockAddr);
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
qCCritical(networking) << "Socket type not specified in writeDatagram() address";
|
qCCritical(networking) << "Socket type not specified in writeDatagram() address";
|
||||||
|
@ -141,13 +141,13 @@ qint64 NetworkSocket::writeDatagram(const QByteArray& datagram, const SockAddr&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 NetworkSocket::bytesToWrite(SocketType socketType, quint16 port) const {
|
qint64 NetworkSocket::bytesToWrite(SocketType socketType, const SockAddr& address) const {
|
||||||
switch (socketType) {
|
switch (socketType) {
|
||||||
case SocketType::UDP:
|
case SocketType::UDP:
|
||||||
return _udpSocket.bytesToWrite();
|
return _udpSocket.bytesToWrite();
|
||||||
#if defined(WEBRTC_DATA_CHANNELS)
|
#if defined(WEBRTC_DATA_CHANNELS)
|
||||||
case SocketType::WebRTC:
|
case SocketType::WebRTC:
|
||||||
return _webrtcSocket.bytesToWrite(port);
|
return _webrtcSocket.bytesToWrite(address);
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
qCCritical(networking) << "Socket type not specified in bytesToWrite()";
|
qCCritical(networking) << "Socket type not specified in bytesToWrite()";
|
||||||
|
|
|
@ -80,9 +80,10 @@ public:
|
||||||
/// @brief Gets the number of bytes waiting to be written.
|
/// @brief Gets the number of bytes waiting to be written.
|
||||||
/// @details For UDP, there's a single buffer used for all destinations. For WebRTC, each destination has its own buffer.
|
/// @details For UDP, there's a single buffer used for all destinations. For WebRTC, each destination has its own buffer.
|
||||||
/// @param socketType The type of socket for which to get the number of bytes waiting to be written.
|
/// @param socketType The type of socket for which to get the number of bytes waiting to be written.
|
||||||
/// @param port If a WebRTC socket, the data channel for which to get the number of bytes waiting.
|
/// @param address If a WebRTCSocket, the destination address for which to get the number of bytes waiting.
|
||||||
|
/// @param port If a WebRTC socket, the destination port for which to get the number of bytes waiting.
|
||||||
/// @return The number of bytes waiting to be written.
|
/// @return The number of bytes waiting to be written.
|
||||||
qint64 bytesToWrite(SocketType socketType, quint16 port = 0) const;
|
qint64 bytesToWrite(SocketType socketType, const SockAddr& address = SockAddr()) const;
|
||||||
|
|
||||||
|
|
||||||
/// @brief Gets whether there is a pending datagram waiting to be read.
|
/// @brief Gets whether there is a pending datagram waiting to be read.
|
||||||
|
|
|
@ -256,7 +256,7 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const SockAddr& sockAdd
|
||||||
}
|
}
|
||||||
qint64 bytesWritten = _networkSocket.writeDatagram(datagram, sockAddr);
|
qint64 bytesWritten = _networkSocket.writeDatagram(datagram, sockAddr);
|
||||||
|
|
||||||
int pending = _networkSocket.bytesToWrite(socketType, sockAddr.getPort());
|
int pending = _networkSocket.bytesToWrite(socketType, sockAddr);
|
||||||
if (bytesWritten < 0 || pending) {
|
if (bytesWritten < 0 || pending) {
|
||||||
int wsaError = 0;
|
int wsaError = 0;
|
||||||
static std::atomic<int> previousWsaError (0);
|
static std::atomic<int> previousWsaError (0);
|
||||||
|
|
|
@ -131,13 +131,12 @@ void WDCDataChannelObserver::OnMessage(const DataBuffer& buffer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
WDCConnection::WDCConnection(WebRTCDataChannels* parent, quint16 webSocketID, int dataChannelID) :
|
WDCConnection::WDCConnection(WebRTCDataChannels* parent, const QString& dataChannelID) :
|
||||||
_parent(parent),
|
_parent(parent),
|
||||||
_webSocketID(webSocketID),
|
|
||||||
_dataChannelID(dataChannelID)
|
_dataChannelID(dataChannelID)
|
||||||
{
|
{
|
||||||
#ifdef WEBRTC_DEBUG
|
#ifdef WEBRTC_DEBUG
|
||||||
qCDebug(networking_webrtc) << "WDCConnection::WDCConnection() :" << webSocketID << dataChannelID;
|
qCDebug(networking_webrtc) << "WDCConnection::WDCConnection() :" << dataChannelID;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Create observers.
|
// Create observers.
|
||||||
|
@ -197,7 +196,7 @@ void WDCConnection::sendAnswer(SessionDescriptionInterface* description) {
|
||||||
|
|
||||||
QJsonObject jsonObject;
|
QJsonObject jsonObject;
|
||||||
jsonObject.insert("from", QString(_parent->getNodeType()));
|
jsonObject.insert("from", QString(_parent->getNodeType()));
|
||||||
jsonObject.insert("to", _webSocketID);
|
jsonObject.insert("to", _dataChannelID);
|
||||||
jsonObject.insert("data", jsonWebRTCPayload);
|
jsonObject.insert("data", jsonWebRTCPayload);
|
||||||
|
|
||||||
_parent->sendSignalingMessage(jsonObject);
|
_parent->sendSignalingMessage(jsonObject);
|
||||||
|
@ -251,7 +250,7 @@ void WDCConnection::sendIceCandidate(const IceCandidateInterface* candidate) {
|
||||||
|
|
||||||
QJsonObject jsonObject;
|
QJsonObject jsonObject;
|
||||||
jsonObject.insert("from", QString(_parent->getNodeType()));
|
jsonObject.insert("from", QString(_parent->getNodeType()));
|
||||||
jsonObject.insert("to", _webSocketID);
|
jsonObject.insert("to", _dataChannelID);
|
||||||
jsonObject.insert("data", jsonWebRTCData);
|
jsonObject.insert("data", jsonWebRTCData);
|
||||||
QJsonDocument jsonDocument = QJsonDocument(jsonObject);
|
QJsonDocument jsonDocument = QJsonDocument(jsonObject);
|
||||||
|
|
||||||
|
@ -328,7 +327,9 @@ void WDCConnection::onDataChannelMessageReceived(const DataBuffer& buffer) {
|
||||||
#ifdef WEBRTC_DEBUG
|
#ifdef WEBRTC_DEBUG
|
||||||
qCDebug(networking_webrtc) << "Echo message back";
|
qCDebug(networking_webrtc) << "Echo message back";
|
||||||
#endif
|
#endif
|
||||||
_parent->sendDataMessage(_dataChannelID, byteArray); // Use parent method to exercise the code stack.
|
auto addressParts = _dataChannelID.split(":");
|
||||||
|
auto address = SockAddr(SocketType::WebRTC, QHostAddress(addressParts[0]), addressParts[1].toInt());
|
||||||
|
_parent->sendDataMessage(address, byteArray); // Use parent method to exercise the code stack.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,58 +421,35 @@ WebRTCDataChannels::~WebRTCDataChannels() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebRTCDataChannels::reset() {
|
void WebRTCDataChannels::reset() {
|
||||||
QHashIterator<quint16, WDCConnection*> i(_connectionsByDataChannel);
|
QHashIterator<QString, WDCConnection*> i(_connectionsByID);
|
||||||
while (i.hasNext()) {
|
while (i.hasNext()) {
|
||||||
i.next();
|
i.next();
|
||||||
delete i.value();
|
delete i.value();
|
||||||
}
|
}
|
||||||
_connectionsByWebSocket.clear();
|
_connectionsByID.clear();
|
||||||
_connectionsByDataChannel.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 WebRTCDataChannels::getNewDataChannelID() {
|
void WebRTCDataChannels::onDataChannelOpened(WDCConnection* connection, const QString& dataChannelID) {
|
||||||
// The first data channel ID is 1.
|
|
||||||
static const int QUINT16_LIMIT = std::numeric_limits<uint16_t>::max() + 1;
|
|
||||||
_lastDataChannelID = std::max((_lastDataChannelID + 1) % QUINT16_LIMIT, 1);
|
|
||||||
#ifdef WEBRTC_DEBUG
|
|
||||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::getNewDataChannelID() :" << _lastDataChannelID;
|
|
||||||
#endif
|
|
||||||
return _lastDataChannelID;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WebRTCDataChannels::getDataChannelIDForWebSocket(quint16 webSocketID) const {
|
|
||||||
#ifdef WEBRTC_DEBUG
|
|
||||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::getDataChannelIDForWebSocket() :" << webSocketID;
|
|
||||||
#endif
|
|
||||||
auto connection = _connectionsByWebSocket.value(webSocketID);
|
|
||||||
if (!connection) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return connection->getDataChannelID();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebRTCDataChannels::onDataChannelOpened(WDCConnection* connection, quint16 dataChannelID) {
|
|
||||||
#ifdef WEBRTC_DEBUG
|
#ifdef WEBRTC_DEBUG
|
||||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::onDataChannelOpened() :" << dataChannelID;
|
qCDebug(networking_webrtc) << "WebRTCDataChannels::onDataChannelOpened() :" << dataChannelID;
|
||||||
#endif
|
#endif
|
||||||
_connectionsByDataChannel.insert(dataChannelID, connection);
|
_connectionsByID.insert(dataChannelID, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebRTCDataChannels::onSignalingMessage(const QJsonObject& message) {
|
void WebRTCDataChannels::onSignalingMessage(const QJsonObject& message) {
|
||||||
#ifdef WEBRTC_DEBUG
|
#ifdef WEBRTC_DEBUG
|
||||||
qCDebug(networking_webrtc) << "WebRTCDataChannel::onSignalingMessage()" << message << message.value("channel");
|
qCDebug(networking_webrtc) << "WebRTCDataChannel::onSignalingMessage()" << message;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Validate message.
|
// Validate message.
|
||||||
const int MAX_DEBUG_DETAIL_LENGTH = 64;
|
const int MAX_DEBUG_DETAIL_LENGTH = 64;
|
||||||
|
const QRegularExpression DATA_CHANNEL_ID_REGEX{ "^[1-9]\\d*\\.\\d+\\.\\d+\\.\\d+:\\d+$" };
|
||||||
auto data = message.value("data").isObject() ? message.value("data").toObject() : QJsonObject();
|
auto data = message.value("data").isObject() ? message.value("data").toObject() : QJsonObject();
|
||||||
int from = message.value("from").isDouble() ? (quint16)(message.value("from").toInt()) : 0;
|
auto from = message.value("from").toString();
|
||||||
auto to = NodeType::fromChar(message.value("to").toString().at(0));
|
auto to = NodeType::fromChar(message.value("to").toString().at(0));
|
||||||
int channel = message.value("channel").isDouble() ? (int)(message.value("channel").toInt()) : 0;
|
if (!DATA_CHANNEL_ID_REGEX.match(from).hasMatch() || to == NodeType::Unassigned
|
||||||
|
|
||||||
if (from <= 0 || from > MAXUINT16 || to == NodeType::Unassigned || channel < 0 || channel > MAXUINT16
|
|
||||||
|| !data.contains("description") && !data.contains("candidate")) {
|
|| !data.contains("description") && !data.contains("candidate")) {
|
||||||
qCWarning(networking_webrtc) << "Unexpected signaling message:"
|
qCWarning(networking_webrtc) << "Invalid or unexpected signaling message:"
|
||||||
<< QJsonDocument(message).toJson(QJsonDocument::Compact).left(MAX_DEBUG_DETAIL_LENGTH);
|
<< QJsonDocument(message).toJson(QJsonDocument::Compact).left(MAX_DEBUG_DETAIL_LENGTH);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -481,16 +459,11 @@ void WebRTCDataChannels::onSignalingMessage(const QJsonObject& message) {
|
||||||
|
|
||||||
// Find or create a connection.
|
// Find or create a connection.
|
||||||
WDCConnection* connection;
|
WDCConnection* connection;
|
||||||
if (_connectionsByWebSocket.contains(from)) {
|
if (_connectionsByID.contains(from)) {
|
||||||
connection = _connectionsByWebSocket.value(from);
|
connection = _connectionsByID.value(from);
|
||||||
} else {
|
} else {
|
||||||
// Assignment clients use the same data channel ID as the domain server, which is provided in the "channel" property.
|
connection = new WDCConnection(this, from);
|
||||||
// The domain server creates a new data channel ID.
|
_connectionsByID.insert(from, connection);
|
||||||
if (channel == 0) {
|
|
||||||
channel = getNewDataChannelID();
|
|
||||||
}
|
|
||||||
connection = new WDCConnection(this, from, channel);
|
|
||||||
_connectionsByWebSocket.insert(from, connection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the remote description and reply with an answer.
|
// Set the remote description and reply with an answer.
|
||||||
|
@ -519,41 +492,41 @@ void WebRTCDataChannels::sendSignalingMessage(const QJsonObject& message) {
|
||||||
emit signalingMessage(message);
|
emit signalingMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebRTCDataChannels::emitDataMessage(int dataChannelID, const QByteArray& byteArray) {
|
void WebRTCDataChannels::emitDataMessage(const QString& dataChannelID, const QByteArray& byteArray) {
|
||||||
#ifdef WEBRTC_DEBUG
|
#ifdef WEBRTC_DEBUG
|
||||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::emitDataMessage() :" << dataChannelID << byteArray.toHex()
|
qCDebug(networking_webrtc) << "WebRTCDataChannels::emitDataMessage() :" << dataChannelID << byteArray.toHex()
|
||||||
<< byteArray.length();
|
<< byteArray.length();
|
||||||
#endif
|
#endif
|
||||||
emit dataMessage(dataChannelID, byteArray);
|
auto addressParts = dataChannelID.split(":");
|
||||||
|
auto address = SockAddr(SocketType::WebRTC, QHostAddress(addressParts[0]), addressParts[1].toInt());
|
||||||
|
emit dataMessage(address, byteArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebRTCDataChannels::sendDataMessage(int dataChannelID, const QByteArray& byteArray) {
|
bool WebRTCDataChannels::sendDataMessage(const SockAddr& destination, const QByteArray& byteArray) {
|
||||||
|
auto dataChannelID = destination.toShortString();
|
||||||
#ifdef WEBRTC_DEBUG
|
#ifdef WEBRTC_DEBUG
|
||||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::sendDataMessage() :" << dataChannelID;
|
qCDebug(networking_webrtc) << "WebRTCDataChannels::sendDataMessage() :" << dataChannelID;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Find connection.
|
if (!_connectionsByID.contains(dataChannelID)) {
|
||||||
if (!_connectionsByDataChannel.contains(dataChannelID)) {
|
|
||||||
qCWarning(networking_webrtc) << "Could not find WebRTC data channel to send message on!";
|
qCWarning(networking_webrtc) << "Could not find WebRTC data channel to send message on!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto connection = _connectionsByDataChannel.value(dataChannelID);
|
auto connection = _connectionsByID.value(dataChannelID);
|
||||||
DataBuffer buffer(byteArray.toStdString(), true);
|
DataBuffer buffer(byteArray.toStdString(), true);
|
||||||
return connection->sendDataMessage(buffer);
|
return connection->sendDataMessage(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Gets the number of bytes waiting to be written on a data channel.
|
qint64 WebRTCDataChannels::getBufferedAmount(const SockAddr& address) const {
|
||||||
/// @param port The data channel ID.
|
auto dataChannelID = address.toShortString();
|
||||||
/// @return The number of bytes waiting to be written on the data channel; 0 if the channel doesn't exist.
|
if (!_connectionsByID.contains(dataChannelID)) {
|
||||||
qint64 WebRTCDataChannels::getBufferedAmount(int dataChannelID) const {
|
|
||||||
if (!_connectionsByDataChannel.contains(dataChannelID)) {
|
|
||||||
#ifdef WEBRTC_DEBUG
|
#ifdef WEBRTC_DEBUG
|
||||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::getBufferedAmount() : Channel doesn't exist:" << dataChannelID;
|
qCDebug(networking_webrtc) << "WebRTCDataChannels::getBufferedAmount() : Channel doesn't exist:" << dataChannelID;
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
auto connection = _connectionsByDataChannel.value(dataChannelID);
|
auto connection = _connectionsByID.value(dataChannelID);
|
||||||
return connection->getBufferedAmount();
|
return connection->getBufferedAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,10 +573,9 @@ void WebRTCDataChannels::closePeerConnectionNow(WDCConnection* connection) {
|
||||||
|
|
||||||
// Delete the WDCConnection.
|
// Delete the WDCConnection.
|
||||||
#ifdef WEBRTC_DEBUG
|
#ifdef WEBRTC_DEBUG
|
||||||
qCDebug(networking_webrtc) << "Dispose of connection for channel ID:" << connection->getDataChannelID();
|
qCDebug(networking_webrtc) << "Dispose of connection for channel:" << connection->getDataChannelID();
|
||||||
#endif
|
#endif
|
||||||
_connectionsByWebSocket.remove(connection->getWebSocketID());
|
_connectionsByID.remove(connection->getDataChannelID());
|
||||||
_connectionsByDataChannel.remove(connection->getDataChannelID());
|
|
||||||
delete connection;
|
delete connection;
|
||||||
#ifdef WEBRTC_DEBUG
|
#ifdef WEBRTC_DEBUG
|
||||||
qCDebug(networking_webrtc) << "Disposed of connection";
|
qCDebug(networking_webrtc) << "Disposed of connection";
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#define emit
|
#define emit
|
||||||
|
|
||||||
#include "../NodeType.h"
|
#include "../NodeType.h"
|
||||||
|
#include "../SockAddr.h"
|
||||||
|
|
||||||
class WebRTCDataChannels;
|
class WebRTCDataChannels;
|
||||||
class WDCConnection;
|
class WDCConnection;
|
||||||
|
@ -128,17 +129,12 @@ public:
|
||||||
|
|
||||||
/// @brief Constructs a new WDCConnection and opens a WebRTC data connection.
|
/// @brief Constructs a new WDCConnection and opens a WebRTC data connection.
|
||||||
/// @param parent The parent WebRTCDataChannels object.
|
/// @param parent The parent WebRTCDataChannels object.
|
||||||
/// @param webSocketID The signaling channel that initiated the opening of the WebRTC data channel.
|
/// @param dataChannelID The data channel ID.
|
||||||
/// @param dataChannelID - The WebRTC data channel ID to assign to this connection.
|
WDCConnection(WebRTCDataChannels* parent, const QString& dataChannelID);
|
||||||
WDCConnection(WebRTCDataChannels* parent, quint16 webSocketID, int dataChannelID);
|
|
||||||
|
|
||||||
/// @brief Gets the WebSocket ID.
|
/// @brief Gets the data channel ID.
|
||||||
/// @return The ID of the WebSocket.
|
/// @return The data channel ID.
|
||||||
quint16 getWebSocketID() const { return _webSocketID; }
|
QString getDataChannelID() const { return _dataChannelID; }
|
||||||
|
|
||||||
/// @brief Gets the WebRTC data channel ID.
|
|
||||||
/// @return The WebRTC data channel ID. `-1` if not open yet.
|
|
||||||
int getDataChannelID() const { return _dataChannelID; }
|
|
||||||
|
|
||||||
|
|
||||||
/// @brief Sets the remote session description received from the remote client via the signaling channel.
|
/// @brief Sets the remote session description received from the remote client via the signaling channel.
|
||||||
|
@ -160,7 +156,7 @@ public:
|
||||||
/// @param data The ICE candidate.
|
/// @param data The ICE candidate.
|
||||||
void addIceCandidate(QJsonObject& data);
|
void addIceCandidate(QJsonObject& data);
|
||||||
|
|
||||||
/// @brief Sends an ICE candidate to the remote vlient via the signaling channel.
|
/// @brief Sends an ICE candidate to the remote client via the signaling channel.
|
||||||
/// @param candidate The ICE candidate.
|
/// @param candidate The ICE candidate.
|
||||||
void sendIceCandidate(const webrtc::IceCandidateInterface* candidate);
|
void sendIceCandidate(const webrtc::IceCandidateInterface* candidate);
|
||||||
|
|
||||||
|
@ -195,8 +191,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WebRTCDataChannels* _parent;
|
WebRTCDataChannels* _parent;
|
||||||
quint16 _webSocketID { 0 };
|
QString _dataChannelID;
|
||||||
int _dataChannelID { -1 };
|
|
||||||
|
|
||||||
rtc::scoped_refptr<WDCSetSessionDescriptionObserver> _setSessionDescriptionObserver { nullptr };
|
rtc::scoped_refptr<WDCSetSessionDescriptionObserver> _setSessionDescriptionObserver { nullptr };
|
||||||
rtc::scoped_refptr<WDCCreateSessionDescriptionObserver> _createSessionDescriptionObserver { nullptr };
|
rtc::scoped_refptr<WDCCreateSessionDescriptionObserver> _createSessionDescriptionObserver { nullptr };
|
||||||
|
@ -221,6 +216,9 @@ private:
|
||||||
/// Additionally, for debugging purposes, instead of containing a Vircadia protocol payload, a WebRTC message may be an echo
|
/// Additionally, for debugging purposes, instead of containing a Vircadia protocol payload, a WebRTC message may be an echo
|
||||||
/// request. This is bounced back to the client.
|
/// request. This is bounced back to the client.
|
||||||
///
|
///
|
||||||
|
/// A WebRTC data channel is identified by the IP address and port of the client WebSocket that was used when opening the data
|
||||||
|
/// channel - this is considered to be the WebRTC data channel's address. The IP address and port of the actual WebRTC
|
||||||
|
/// connection is not used.
|
||||||
class WebRTCDataChannels : public QObject {
|
class WebRTCDataChannels : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@ -242,41 +240,30 @@ public:
|
||||||
/// @brief Immediately closes all connections and resets the socket.
|
/// @brief Immediately closes all connections and resets the socket.
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
/// @brief Gets a new data channel ID to uniquely identify a WDCConnection.
|
|
||||||
/// @details This ID is assigned by WebRTCDataChannels; it is <em>not</em> the WebRTC data channel ID because that is only
|
|
||||||
/// unique within a peer connection.
|
|
||||||
/// @return A new data channel ID.
|
|
||||||
quint16 getNewDataChannelID();
|
|
||||||
|
|
||||||
/// @brief Gets the data channel ID associated with a WebSocket.
|
|
||||||
/// @param webSocketID The WebSocket.
|
|
||||||
/// @return The data channel ID associated with the WebSocket if found, `0` if the WebSocket was not found.
|
|
||||||
int getDataChannelIDForWebSocket(quint16 webSocketID) const;
|
|
||||||
|
|
||||||
/// @brief Handles a WebRTC data channel opening.
|
/// @brief Handles a WebRTC data channel opening.
|
||||||
/// @param connection The WebRTC data channel connection.
|
/// @param connection The WebRTC data channel connection.
|
||||||
/// @param dataChannelID The WebRTC data channel ID.
|
/// @param dataChannelID The IP address and port of the signaling WebSocket that the client used to connect, `"n.n.n.n:n"`.
|
||||||
void onDataChannelOpened(WDCConnection* connection, quint16 dataChannelID);
|
void onDataChannelOpened(WDCConnection* connection, const QString& dataChannelID);
|
||||||
|
|
||||||
/// @brief Emits a signalingMessage to be sent to the Interface client.
|
/// @brief Emits a signalingMessage to be sent to the Interface client.
|
||||||
/// @param message The WebRTC signaling message to send.
|
/// @param message The WebRTC signaling message to send.
|
||||||
void sendSignalingMessage(const QJsonObject& message);
|
void sendSignalingMessage(const QJsonObject& message);
|
||||||
|
|
||||||
/// @brief Emits a dataMessage received from the Interface client.
|
/// @brief Emits a dataMessage received from the Interface client.
|
||||||
/// @param dataChannelID The WebRTC data channel the message was received on.
|
/// @param dataChannelID The IP address and port of the signaling WebSocket that the client used to connect, `"n.n.n.n:n"`.
|
||||||
/// @param byteArray The data message received.
|
/// @param byteArray The data message received.
|
||||||
void emitDataMessage(int dataChannelID, const QByteArray& byteArray);
|
void emitDataMessage(const QString& dataChannelID, const QByteArray& byteArray);
|
||||||
|
|
||||||
/// @brief Sends a data message to an Interface client.
|
/// @brief Sends a data message to an Interface client.
|
||||||
/// @param dataChannelID The WebRTC channel ID of the Interface client.
|
/// @param dataChannelID The IP address and port of the signaling WebSocket that the client used to connect, `"n.n.n.n:n"`.
|
||||||
/// @param message The data message to send.
|
/// @param message The data message to send.
|
||||||
/// @return `true` if the data message was sent, otherwise `false`.
|
/// @return `true` if the data message was sent, otherwise `false`.
|
||||||
bool sendDataMessage(int dataChannelID, const QByteArray& message);
|
bool sendDataMessage(const SockAddr& destination, const QByteArray& message);
|
||||||
|
|
||||||
/// @brief Gets the number of bytes waiting to be sent on a data channel.
|
/// @brief Gets the number of bytes waiting to be sent on a data channel.
|
||||||
/// @param dataChannelID The data channel ID.
|
/// @param address The address of the signaling WebSocket that the client used to connect.
|
||||||
/// @return The number of bytes waiting to be sent on the data channel.
|
/// @return The number of bytes waiting to be sent on the data channel.
|
||||||
qint64 getBufferedAmount(int dataChannelID) const;
|
qint64 getBufferedAmount(const SockAddr& address) const;
|
||||||
|
|
||||||
/// @brief Creates a new WebRTC peer connection for connecting to an Interface client.
|
/// @brief Creates a new WebRTC peer connection for connecting to an Interface client.
|
||||||
/// @param peerConnectionObserver An observer to monitor the WebRTC peer connection.
|
/// @param peerConnectionObserver An observer to monitor the WebRTC peer connection.
|
||||||
|
@ -311,9 +298,9 @@ signals:
|
||||||
|
|
||||||
/// @brief A WebRTC data message received from the Interface client.
|
/// @brief A WebRTC data message received from the Interface client.
|
||||||
/// @details This message is for handling at a higher level in the Vircadia protocol.
|
/// @details This message is for handling at a higher level in the Vircadia protocol.
|
||||||
/// @param dataChannelID The WebRTC data channel ID.
|
/// @param address The address of the signaling WebSocket that the client used to connect.
|
||||||
/// @param byteArray The Vircadia protocol message.
|
/// @param byteArray The Vircadia protocol message.
|
||||||
void dataMessage(int dataChannelID, const QByteArray& byteArray);
|
void dataMessage(const SockAddr& address, const QByteArray& byteArray);
|
||||||
|
|
||||||
/// @brief Signals that the peer connection for a WebRTC data channel should be closed.
|
/// @brief Signals that the peer connection for a WebRTC data channel should be closed.
|
||||||
/// @details Used by {@link WebRTCDataChannels.closePeerConnection}.
|
/// @details Used by {@link WebRTCDataChannels.closePeerConnection}.
|
||||||
|
@ -332,10 +319,9 @@ private:
|
||||||
|
|
||||||
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> _peerConnectionFactory { nullptr };
|
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> _peerConnectionFactory { nullptr };
|
||||||
|
|
||||||
quint16 _lastDataChannelID { 0 };
|
QHash<QString, WDCConnection*> _connectionsByID; // <client data channel ID, WDCConnection>
|
||||||
|
// The client's WebSocket IP and port is used as the data channel ID to uniquely identify each.
|
||||||
QHash<quint16, WDCConnection*> _connectionsByWebSocket;
|
// The WebSocket IP address and port is formatted as "n.n.n.n:n", the same as used in WebRTCSignalingServer.
|
||||||
QHash<quint16, WDCConnection*> _connectionsByDataChannel;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,8 @@ void WebRTCSignalingServer::webSocketTextMessageReceived(const QString& message)
|
||||||
source->sendTextMessage(echo);
|
source->sendTextMessage(echo);
|
||||||
} else {
|
} else {
|
||||||
// WebRTC message or assignment client echo request. (Send both to target.)
|
// WebRTC message or assignment client echo request. (Send both to target.)
|
||||||
json.insert("from", source->peerPort());
|
auto from = source->peerAddress().toString() + ":" + QString::number(source->peerPort());
|
||||||
|
json.insert("from", from);
|
||||||
emit messageReceived(json);
|
emit messageReceived(json);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -71,9 +72,9 @@ void WebRTCSignalingServer::webSocketTextMessageReceived(const QString& message)
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebRTCSignalingServer::sendMessage(const QJsonObject& message) {
|
void WebRTCSignalingServer::sendMessage(const QJsonObject& message) {
|
||||||
quint16 destinationPort = message.value("to").toInt();
|
auto destinationAddress = message.value("to").toString();
|
||||||
if (_webSockets.contains(destinationPort)) {
|
if (_webSockets.contains(destinationAddress)) {
|
||||||
_webSockets.value(destinationPort)->sendTextMessage(QString(QJsonDocument(message).toJson()));
|
_webSockets.value(destinationAddress)->sendTextMessage(QString(QJsonDocument(message).toJson()));
|
||||||
} else {
|
} else {
|
||||||
qCWarning(networking_webrtc) << "Failed to find WebSocket for outgoing WebRTC signaling message.";
|
qCWarning(networking_webrtc) << "Failed to find WebSocket for outgoing WebRTC signaling message.";
|
||||||
}
|
}
|
||||||
|
@ -82,7 +83,9 @@ void WebRTCSignalingServer::sendMessage(const QJsonObject& message) {
|
||||||
void WebRTCSignalingServer::webSocketDisconnected() {
|
void WebRTCSignalingServer::webSocketDisconnected() {
|
||||||
auto source = qobject_cast<QWebSocket*>(sender());
|
auto source = qobject_cast<QWebSocket*>(sender());
|
||||||
if (source) {
|
if (source) {
|
||||||
_webSockets.remove(source->peerPort());
|
auto address = source->peerAddress().toString() + ":" + QString::number(source->peerPort());
|
||||||
|
delete _webSockets.value(address);
|
||||||
|
_webSockets.remove(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +93,8 @@ void WebRTCSignalingServer::newWebSocketConnection() {
|
||||||
auto webSocket = _webSocketServer->nextPendingConnection();
|
auto webSocket = _webSocketServer->nextPendingConnection();
|
||||||
connect(webSocket, &QWebSocket::textMessageReceived, this, &WebRTCSignalingServer::webSocketTextMessageReceived);
|
connect(webSocket, &QWebSocket::textMessageReceived, this, &WebRTCSignalingServer::webSocketTextMessageReceived);
|
||||||
connect(webSocket, &QWebSocket::disconnected, this, &WebRTCSignalingServer::webSocketDisconnected);
|
connect(webSocket, &QWebSocket::disconnected, this, &WebRTCSignalingServer::webSocketDisconnected);
|
||||||
_webSockets.insert(webSocket->peerPort(), webSocket);
|
auto webSocketAddress = webSocket->peerAddress().toString() + ":" + QString::number(webSocket->peerPort());
|
||||||
|
_webSockets.insert(webSocketAddress, webSocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // WEBRTC_DATA_CHANNELS
|
#endif // WEBRTC_DATA_CHANNELS
|
||||||
|
|
|
@ -39,19 +39,20 @@
|
||||||
/// signaling `data` payload or an `echo` request:
|
/// signaling `data` payload or an `echo` request:
|
||||||
///
|
///
|
||||||
/// | Interface -> Server ||
|
/// | Interface -> Server ||
|
||||||
/// | -------- | -----------------------|
|
/// | -------- | ---------------------------------------- |
|
||||||
/// | `to` | NodeType |
|
/// | `to` | NodeType |
|
||||||
/// | `from` | WebSocket port number* |
|
/// | `from` | WebSocket IP address & port, "n.n.n.n:n" |
|
||||||
/// | [`data`] | WebRTC payload |
|
/// | [`data`] | WebRTC signaling payload |
|
||||||
/// | [`echo`] | Echo request |
|
/// | [`echo`] | Echo request |
|
||||||
/// * The `from` field is filled in by the WebRTCSignalingServer.
|
///
|
||||||
|
/// `*` The `from` field is filled in upon receipt by the WebRTCSignalingServer.
|
||||||
///
|
///
|
||||||
/// | Server -> Interface ||
|
/// | Server -> Interface ||
|
||||||
/// | -------- | --------------------- |
|
/// | -------- | ---------------------------------------- |
|
||||||
/// | `to` | WebSocket port number |
|
/// | `to` | WebSocket IP address & port, "n.n.n.n:n" |
|
||||||
/// | `from` | NodeType |
|
/// | `from` | NodeType |
|
||||||
/// | [`data`] | WebRTC payload |
|
/// | [`data`] | WebRTC signaling payload |
|
||||||
/// | [`echo`] | Echo response |
|
/// | [`echo`] | Echo response |
|
||||||
///
|
///
|
||||||
class WebRTCSignalingServer : public QObject {
|
class WebRTCSignalingServer : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -97,7 +98,9 @@ private:
|
||||||
QHostAddress _address;
|
QHostAddress _address;
|
||||||
quint16 _port { 0 };
|
quint16 _port { 0 };
|
||||||
|
|
||||||
QHash<quint16, QWebSocket*> _webSockets; // client WebSocket port, client WebSocket object
|
QHash<QString, QWebSocket*> _webSockets; // <client WebSocket IP address and port, client connection WebSocket object>
|
||||||
|
// The WebSocket IP address and port is formatted as "n.n.n.n:n".
|
||||||
|
// A QString is used rather than a SockAddr, to make signaling easier.
|
||||||
|
|
||||||
QTimer* _isWebSocketServerListeningTimer;
|
QTimer* _isWebSocketServerListeningTimer;
|
||||||
};
|
};
|
||||||
|
|
|
@ -78,17 +78,17 @@ void WebRTCSocket::abort() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
qint64 WebRTCSocket::writeDatagram(const QByteArray& datagram, quint16 port) {
|
qint64 WebRTCSocket::writeDatagram(const QByteArray& datagram, const SockAddr& destination) {
|
||||||
clearError();
|
clearError();
|
||||||
if (_dataChannels.sendDataMessage(port, datagram)) {
|
if (_dataChannels.sendDataMessage(destination, datagram)) {
|
||||||
return datagram.length();
|
return datagram.length();
|
||||||
}
|
}
|
||||||
setError(QAbstractSocket::SocketError::UnknownSocketError, "Failed to write datagram");
|
setError(QAbstractSocket::SocketError::UnknownSocketError, "Failed to write datagram");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 WebRTCSocket::bytesToWrite(quint16 port) const {
|
qint64 WebRTCSocket::bytesToWrite(const SockAddr& destination) const {
|
||||||
return _dataChannels.getBufferedAmount(port);
|
return _dataChannels.getBufferedAmount(destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,12 +114,11 @@ qint64 WebRTCSocket::readDatagram(char* data, qint64 maxSize, QHostAddress* addr
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address) {
|
if (address) {
|
||||||
// WEBRTC TODO: Use signaling channel's remote WebSocket address? Or remote data channel address?
|
*address = datagram.first.getAddress();
|
||||||
*address = QHostAddress::AnyIPv4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (port) {
|
if (port) {
|
||||||
*port = datagram.first;
|
*port = datagram.first.getPort();
|
||||||
}
|
}
|
||||||
|
|
||||||
return length;
|
return length;
|
||||||
|
@ -148,14 +147,9 @@ void WebRTCSocket::clearError() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void WebRTCSocket::onDataChannelReceivedMessage(int dataChannelID, const QByteArray& message) {
|
void WebRTCSocket::onDataChannelReceivedMessage(const SockAddr& source, const QByteArray& message) {
|
||||||
_receivedQueue.enqueue(QPair<int, QByteArray>(dataChannelID, message));
|
_receivedQueue.enqueue(QPair<SockAddr, QByteArray>(source, message));
|
||||||
emit readyRead();
|
emit readyRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int WebRTCSocket::getDataChannelIDForWebSocket(quint16 webSocketID) const {
|
|
||||||
return _dataChannels.getDataChannelIDForWebSocket(webSocketID);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // WEBRTC_DATA_CHANNELS
|
#endif // WEBRTC_DATA_CHANNELS
|
||||||
|
|
|
@ -24,6 +24,10 @@
|
||||||
|
|
||||||
|
|
||||||
/// @brief Provides a QUdpSocket-style interface for using WebRTCDataChannels.
|
/// @brief Provides a QUdpSocket-style interface for using WebRTCDataChannels.
|
||||||
|
///
|
||||||
|
/// @details A WebRTC data channel is identified by the IP address and port of the client WebSocket that was used when opening
|
||||||
|
/// the data channel - this is considered to be the WebRTC data channel's address. The IP address and port of the actual WebRTC
|
||||||
|
/// connection is not used.
|
||||||
class WebRTCSocket : public QObject {
|
class WebRTCSocket : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@ -81,16 +85,16 @@ public:
|
||||||
qintptr socketDescriptor() const { return -1; }
|
qintptr socketDescriptor() const { return -1; }
|
||||||
|
|
||||||
|
|
||||||
/// @brief Sends a datagram to the host on a data channel.
|
/// @brief Sends a datagram.
|
||||||
/// @param datagram The datagram to send.
|
/// @param datagram The datagram to send.
|
||||||
/// @param port The data channel ID.
|
/// @param destination The destination WebRTC data channel address.
|
||||||
/// @return The number of bytes if successfully sent, otherwise <code>-1</code>.
|
/// @return The number of bytes if successfully sent, otherwise <code>-1</code>.
|
||||||
qint64 writeDatagram(const QByteArray& datagram, quint16 port);
|
qint64 writeDatagram(const QByteArray& datagram, const SockAddr& destination);
|
||||||
|
|
||||||
/// @brief Gets the number of bytes waiting to be written.
|
/// @brief Gets the number of bytes waiting to be written.
|
||||||
/// @param port The data channel ID.
|
/// @param destination The destination WebRTC data channel address.
|
||||||
/// @return The number of bytes waiting to be written.
|
/// @return The number of bytes waiting to be written.
|
||||||
qint64 bytesToWrite(quint16 port) const;
|
qint64 bytesToWrite(const SockAddr& destination) const;
|
||||||
|
|
||||||
/// @brief Gets whether there's a datagram waiting to be read.
|
/// @brief Gets whether there's a datagram waiting to be read.
|
||||||
/// @return <code>true</code> if there's a datagram waiting to be read, <code>false</code> if there isn't.
|
/// @return <code>true</code> if there's a datagram waiting to be read, <code>false</code> if there isn't.
|
||||||
|
@ -104,8 +108,8 @@ public:
|
||||||
/// @details Any remaining data in the datagram is lost.
|
/// @details Any remaining data in the datagram is lost.
|
||||||
/// @param data The destination to read the datagram into.
|
/// @param data The destination to read the datagram into.
|
||||||
/// @param maxSize The maximum number of bytes to read.
|
/// @param maxSize The maximum number of bytes to read.
|
||||||
/// @param address The destination to put the IP address that the datagram was read from. (Not currently set.)
|
/// @param address The destination to put the WebRTC data channel's IP address.
|
||||||
/// @param port The destination to put the data channel ID that the datagram was read from.
|
/// @param port The destination to put the WebRTC data channel's port.
|
||||||
/// @return The number of bytes read on success; <code>-1</code> if reading unsuccessful.
|
/// @return The number of bytes read on success; <code>-1</code> if reading unsuccessful.
|
||||||
qint64 readDatagram(char* data, qint64 maxSize, QHostAddress* address = nullptr, quint16* port = nullptr);
|
qint64 readDatagram(char* data, qint64 maxSize, QHostAddress* address = nullptr, quint16* port = nullptr);
|
||||||
|
|
||||||
|
@ -118,19 +122,13 @@ public:
|
||||||
/// @return The description of the error that last occurred.
|
/// @return The description of the error that last occurred.
|
||||||
QString errorString() const;
|
QString errorString() const;
|
||||||
|
|
||||||
|
|
||||||
/// @brief Gets the data channel ID associated with a WebSocket.
|
|
||||||
/// @param webSocketID
|
|
||||||
/// @return The data channel ID associated with the WebSocket if found, `0` if the WebSocket was not found.
|
|
||||||
int getDataChannelIDForWebSocket(quint16 webSocketID) const;
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
/// @brief Handles the WebRTC data channel receiving a message.
|
/// @brief Handles the WebRTC data channel receiving a message.
|
||||||
/// @details Queues the message to be read via readDatagram.
|
/// @details Queues the message to be read via readDatagram.
|
||||||
/// @param dataChannelID The data channel that the message was received on.
|
/// @param source The WebRTC data channel that the message was received on.
|
||||||
/// @param message The message that was received.
|
/// @param message The message that was received.
|
||||||
void onDataChannelReceivedMessage(int dataChannelID, const QByteArray& message);
|
void onDataChannelReceivedMessage(const SockAddr& source, const QByteArray& message);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
@ -159,7 +157,7 @@ private:
|
||||||
|
|
||||||
bool _isBound { false };
|
bool _isBound { false };
|
||||||
|
|
||||||
QQueue<QPair<int, QByteArray>> _receivedQueue; // Messages received are queued for reading from the "socket".
|
QQueue<QPair<SockAddr, QByteArray>> _receivedQueue; // Messages received are queued for reading from the "socket".
|
||||||
|
|
||||||
QAbstractSocket::SocketError _lastErrorType { QAbstractSocket::UnknownSocketError };
|
QAbstractSocket::SocketError _lastErrorType { QAbstractSocket::UnknownSocketError };
|
||||||
QString _lastErrorString;
|
QString _lastErrorString;
|
||||||
|
|
Loading…
Reference in a new issue