diff options
Diffstat (limited to 'qt4/TelepathyQt4/outgoing-stream-tube-channel.cpp')
-rw-r--r-- | qt4/TelepathyQt4/outgoing-stream-tube-channel.cpp | 821 |
1 files changed, 821 insertions, 0 deletions
diff --git a/qt4/TelepathyQt4/outgoing-stream-tube-channel.cpp b/qt4/TelepathyQt4/outgoing-stream-tube-channel.cpp new file mode 100644 index 000000000..4169053bf --- /dev/null +++ b/qt4/TelepathyQt4/outgoing-stream-tube-channel.cpp @@ -0,0 +1,821 @@ +/** + * This file is part of TelepathyQt4 + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt4/OutgoingStreamTubeChannel> +#include "TelepathyQt4/outgoing-stream-tube-channel-internal.h" + +#include "TelepathyQt4/_gen/outgoing-stream-tube-channel.moc.hpp" +#include "TelepathyQt4/_gen/outgoing-stream-tube-channel-internal.moc.hpp" + +#include "TelepathyQt4/debug-internal.h" +#include "TelepathyQt4/types-internal.h" + +#include <TelepathyQt4/Connection> +#include <TelepathyQt4/ContactManager> +#include <TelepathyQt4/PendingContacts> +#include <TelepathyQt4/PendingFailure> +#include <TelepathyQt4/Types> + +#include <QHostAddress> +#include <QTcpServer> +#include <QLocalServer> + +namespace Tp +{ + +PendingOpenTube::Private::Private(const QVariantMap ¶meters, PendingOpenTube *parent) + : parent(parent), + parameters(parameters) +{ +} + +PendingOpenTube::PendingOpenTube( + PendingVoid *offerOperation, + const QVariantMap ¶meters, + const OutgoingStreamTubeChannelPtr &object) + : PendingOperation(object), + mPriv(new Private(parameters, this)) +{ + mPriv->tube = object; + + // FIXME: connect to channel invalidation here also + + debug() << "Calling StreamTube.Offer"; + if (offerOperation->isFinished()) { + onOfferFinished(offerOperation); + } else { + // Connect the pending void + connect(offerOperation, SIGNAL(finished(Tp::PendingOperation*)), + this, SLOT(onOfferFinished(Tp::PendingOperation*))); + } +} + +PendingOpenTube::~PendingOpenTube() +{ + delete mPriv; +} + +void PendingOpenTube::onOfferFinished(PendingOperation *op) +{ + if (op->isError()) { + warning().nospace() << "StreamTube.Offer failed with " << + op->errorName() << ": " << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + return; + } + + debug() << "StreamTube.Offer returned successfully"; + + // It might have been already opened - check + if (mPriv->tube->state() != TubeChannelStateOpen) { + debug() << "Awaiting tube to be opened"; + // Wait until the tube gets opened on the other side + connect(mPriv->tube.data(), + SIGNAL(stateChanged(Tp::TubeChannelState)), + SLOT(onTubeStateChanged(Tp::TubeChannelState))); + } + + onTubeStateChanged(mPriv->tube->state()); +} + +void PendingOpenTube::onTubeStateChanged(TubeChannelState state) +{ + if (state == TubeChannelStateOpen) { + debug() << "Tube is now opened"; + // Inject the parameters into the tube + mPriv->tube->setParameters(mPriv->parameters); + // The tube is ready: let's notify + setFinished(); + } else { + if (state != TubeChannelStateRemotePending) { + warning() << "Offering tube failed with" << TP_QT4_ERROR_CONNECTION_REFUSED; + // Something happened + setFinishedWithError(TP_QT4_ERROR_CONNECTION_REFUSED, + QLatin1String("The connection to this tube was refused")); + } else { + debug() << "Awaiting remote to accept the tube"; + } + } +} + +QueuedContactFactory::QueuedContactFactory(Tp::ContactManagerPtr contactManager, QObject* parent) + : QObject(parent), + m_isProcessing(false), + m_manager(contactManager) +{ +} + +QueuedContactFactory::~QueuedContactFactory() +{ +} + +void QueuedContactFactory::processNextRequest() +{ + if (m_isProcessing || m_queue.isEmpty()) { + // Return, nothing to do + return; + } + + m_isProcessing = true; + + Entry entry = m_queue.dequeue(); + + // TODO: pass id hints to ContactManager if we ever gain support to retrieve contact ids + // from NewRemoteConnection. + PendingContacts *pc = m_manager->contactsForHandles(entry.handles); + pc->setProperty("__TpQt4__QueuedContactFactoryUuid", entry.uuid.toString()); + connect(pc, SIGNAL(finished(Tp::PendingOperation*)), + this, SLOT(onPendingContactsFinished(Tp::PendingOperation*))); +} + +QUuid QueuedContactFactory::appendNewRequest(const Tp::UIntList &handles) +{ + // Create a new entry + Entry entry; + entry.uuid = QUuid::createUuid(); + entry.handles = handles; + m_queue.enqueue(entry); + + // Check if we can process a request + processNextRequest(); + + // Return the UUID + return entry.uuid; +} + +void QueuedContactFactory::onPendingContactsFinished(PendingOperation *op) +{ + PendingContacts *pc = qobject_cast<PendingContacts*>(op); + + QUuid uuid = QUuid(pc->property("__TpQt4__QueuedContactFactoryUuid").toString()); + + emit contactsRetrieved(uuid, pc->contacts()); + + // No longer processing + m_isProcessing = false; + + // Go for next one + processNextRequest(); +} + +OutgoingStreamTubeChannel::Private::Private(OutgoingStreamTubeChannel *parent) + : parent(parent), + queuedContactFactory(new QueuedContactFactory(parent->connection()->contactManager(), parent)) +{ +} + +/** + * \class OutgoingStreamTubeChannel + * \ingroup clientchannel + * \headerfile TelepathyQt4/outgoing-stream-tube-channel.h <TelepathyQt4/OutgoingStreamTubeChannel> + * + * \brief The OutgoingStreamTubeChannel class represents an outgoing Telepathy channel + * of type StreamTube. + * + * Outgoing (locally initiated/requested) tubes are initially in the #TubeChannelStateNotOffered state. The + * various offer methods in this class can be used to offer a local listening TCP or Unix socket for + * the tube's target to connect to, at which point the tube becomes #TubeChannelStateRemotePending. + * If the target accepts the connection request, the state goes #TubeChannelStateOpen and the + * connection manager will start tunneling any incoming connections from the recipient side to the + * local service. + */ + +/** + * Feature representing the core that needs to become ready to make the + * OutgoingStreamTubeChannel object usable. + * + * This is currently the same as StreamTubeChannel::FeatureCore, but may change to include more. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature OutgoingStreamTubeChannel::FeatureCore = + Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 0); // ST::FeatureCore + +/** + * Create a new OutgoingStreamTubeChannel object. + * + * \param connection Connection owning this channel, and specifying the + * service. + * \param objectPath The channel object path. + * \param immutableProperties The channel immutable properties. + * \return A OutgoingStreamTubeChannelPtr object pointing to the newly created + * OutgoingStreamTubeChannel object. + */ +OutgoingStreamTubeChannelPtr OutgoingStreamTubeChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return OutgoingStreamTubeChannelPtr(new OutgoingStreamTubeChannel(connection, objectPath, + immutableProperties, OutgoingStreamTubeChannel::FeatureCore)); +} + +/** + * Construct a new OutgoingStreamTubeChannel object. + * + * \param connection Connection owning this channel, and specifying the + * service. + * \param objectPath The channel object path. + * \param immutableProperties The channel immutable properties. + * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should + * depend on OutgoingStreamTubeChannel::FeatureCore. + */ +OutgoingStreamTubeChannel::OutgoingStreamTubeChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : StreamTubeChannel(connection, objectPath, + immutableProperties, coreFeature), + mPriv(new Private(this)) +{ + connect(mPriv->queuedContactFactory, + SIGNAL(contactsRetrieved(QUuid,QList<Tp::ContactPtr>)), + this, + SLOT(onContactsRetrieved(QUuid,QList<Tp::ContactPtr>))); +} + +/** + * Class destructor. + */ +OutgoingStreamTubeChannel::~OutgoingStreamTubeChannel() +{ + delete mPriv; +} + +/** + * Offer a TCP socket over this stream tube. + * + * This method offers a TCP socket over this tube. The socket's address is given as + * a QHostAddress and a numerical port in native byte order. + * + * If your application uses QTcpServer as the local TCP server implementation, you can use the + * offerTcpSocket(const QTcpServer *, const QVariantMap &) overload instead to more easily pass the + * server's listen address. + * + * It is guaranteed that when the PendingOperation returned by this method will be completed, + * the tube will be opened and ready to be used. + * + * Connection managers adhering to the \telepathy_spec should always support offering IPv4 TCP + * sockets. IPv6 sockets are only supported if supportsIPv6SocketsOnLocalhost() is \c true. + * + * Note that the library will try to use #SocketAccessControlPort access control whenever possible, + * as it allows to map connections to users based on their source addresses. If + * supportsIPv4SocketsWithSpecifiedAddress() or supportsIPv6SocketsWithSpecifiedAddress() for IPv4 + * and IPv6 sockets respectively is \c false, this feature is not available, and the + * connectionsForSourceAddresses() map won't contain useful distinct keys. + * + * Arbitrary parameters can be associated with the offer to bootstrap legacy protocols; these will + * in particular be available as IncomingStreamTubeChannel::parameters() for a tube receiver + * implemented using TelepathyQt4 in the other end. + * + * This method requires OutgoingStreamTubeChannel::FeatureCore to be ready. + * + * \param address A valid IPv4 or IPv6 address pointing to an existing socket. + * \param port The port the socket is listening for connections to. + * \param parameters A dictionary of arbitrary parameters to send with the tube offer. + * \return A PendingOperation which will emit PendingOperation::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + */ +PendingOperation *OutgoingStreamTubeChannel::offerTcpSocket( + const QHostAddress &address, + quint16 port, + const QVariantMap ¶meters) +{ + if (!isReady(OutgoingStreamTubeChannel::FeatureCore)) { + warning() << "OutgoingStreamTubeChannel::FeatureCore must be ready before " + "calling offerTube"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + OutgoingStreamTubeChannelPtr(this)); + } + + // The tube must be not offered + if (state() != TubeChannelStateNotOffered) { + warning() << "You can not expose more than a socket for each Stream Tube"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel busy"), + OutgoingStreamTubeChannelPtr(this)); + } + + SocketAccessControl accessControl = SocketAccessControlLocalhost; + // Check if port is supported + + // In this specific overload, we're handling an IPv4/IPv6 socket + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + // IPv4 case + SocketAccessControl accessControl; + // Do some heuristics to find out the best access control.We always prefer port for tracking + // connections and source addresses. + if (supportsIPv4SocketsWithSpecifiedAddress()) { + accessControl = SocketAccessControlPort; + } else if (supportsIPv4SocketsOnLocalhost()) { + accessControl = SocketAccessControlLocalhost; + } else { + // There are no combinations supported for this socket + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + OutgoingStreamTubeChannelPtr(this)); + } + + setAddressType(SocketAddressTypeIPv4); + setAccessControl(accessControl); + setIpAddress(qMakePair<QHostAddress, quint16>(address, port)); + + SocketAddressIPv4 addr; + addr.address = address.toString(); + addr.port = port; + + PendingVoid *pv = new PendingVoid( + interface<Client::ChannelTypeStreamTubeInterface>()->Offer( + SocketAddressTypeIPv4, + QDBusVariant(QVariant::fromValue(addr)), + accessControl, + parameters), + OutgoingStreamTubeChannelPtr(this)); + PendingOpenTube *op = new PendingOpenTube(pv, parameters, + OutgoingStreamTubeChannelPtr(this)); + return op; + } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { + // IPv6 case + // Do some heuristics to find out the best access control.We always prefer port for tracking + // connections and source addresses. + if (supportsIPv6SocketsWithSpecifiedAddress()) { + accessControl = SocketAccessControlPort; + } else if (supportsIPv6SocketsOnLocalhost()) { + accessControl = SocketAccessControlLocalhost; + } else { + // There are no combinations supported for this socket + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + OutgoingStreamTubeChannelPtr(this)); + } + + setAddressType(SocketAddressTypeIPv6); + setAccessControl(accessControl); + setIpAddress(qMakePair<QHostAddress, quint16>(address, port)); + + SocketAddressIPv6 addr; + addr.address = address.toString(); + addr.port = port; + + PendingVoid *pv = new PendingVoid( + interface<Client::ChannelTypeStreamTubeInterface>()->Offer( + SocketAddressTypeIPv6, + QDBusVariant(QVariant::fromValue(addr)), + accessControl, + parameters), + OutgoingStreamTubeChannelPtr(this)); + PendingOpenTube *op = new PendingOpenTube(pv, parameters, + OutgoingStreamTubeChannelPtr(this)); + return op; + } else { + // We're handling an IPv4/IPv6 socket only + warning() << "offerTube can be called only with a QHostAddress representing " + "an IPv4 or IPv6 address"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid host given"), + OutgoingStreamTubeChannelPtr(this)); + } + +} + +/** + * Offer a TCP socket over this stream tube. + * + * Otherwise identical to offerTcpSocket(const QHostAddress &, quint16, const QVariantMap &), but + * allows passing the local service's address in an already listening QTcpServer. + * + * \param server A valid QTcpServer, which should be already listening for incoming connections. + * \param parameters A dictionary of arbitrary parameters to send with the tube offer. + * \return A PendingOperation which will emit PendingOperation::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + */ +PendingOperation *OutgoingStreamTubeChannel::offerTcpSocket( + const QTcpServer *server, + const QVariantMap ¶meters) +{ + // In this overload, we're handling a superset of QHostAddress. + // Let's redirect the call to QHostAddress's overload + return offerTcpSocket(server->serverAddress(), server->serverPort(), + parameters); +} + +/** + * Offer an Unix socket over this stream tube. + * + * This method offers an Unix socket over this stream tube. The socket address is given as a + * a QString, which should contain the path to the socket. Abstract Unix sockets are also supported, + * and are given as addresses prefixed with a \c NUL byte. + * + * If your application uses QLocalServer as the local Unix server implementation, you can use the + * offerUnixSocket(const QLocalServer *, const QVariantMap &, bool) overload instead to more easily + * pass the server's listen address. + * + * Note that only connection managers for which supportsUnixSocketsOnLocalhost() or + * supportsAbstractUnixSocketsOnLocalhost() is \c true support exporting Unix sockets. + * + * If supportsUnixSocketsWithCredentials() or supportsAbstractUnixSocketsWithCredentials(), as + * appropriate, returns \c true, the \c requireCredentials parameter can be set to \c true to make + * the connection manager pass an SCM_CREDS or SCM_CREDENTIALS message as supported by the platform + * when making a new connection. This enables preventing other local users from connecting to the + * service, but might not be possible to use with all protocols as the message is in-band in the + * data stream. + * + * Arbitrary parameters can be associated with the offer to bootstrap legacy protocols; these will + * in particular be available as IncomingStreamTubeChannel::parameters() for a tube receiver + * implemented using TelepathyQt4 in the other end. + * + * This method requires OutgoingStreamTubeChannel::FeatureCore to be ready. + * + * \param address A valid path to an existing Unix socket or abstract Unix socket. + * \param parameters A dictionary of arbitrary parameters to send with the tube offer. + * \param requireCredentials Whether the server requires a SCM_CREDS or SCM_CREDENTIALS message + * upon connection. + * \return A PendingOperation which will emit PendingOperation::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + */ +PendingOperation *OutgoingStreamTubeChannel::offerUnixSocket( + const QString &socketAddress, + const QVariantMap ¶meters, + bool requireCredentials) +{ + SocketAccessControl accessControl = requireCredentials ? + SocketAccessControlCredentials : + SocketAccessControlLocalhost; + + if (!isReady(OutgoingStreamTubeChannel::FeatureCore)) { + warning() << "OutgoingStreamTubeChannel::FeatureCore must be ready before " + "calling offerTube"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + OutgoingStreamTubeChannelPtr(this)); + } + + // The tube must be not offered + if (state() != TubeChannelStateNotOffered) { + warning() << "You can not expose more than a socket for each Stream Tube"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel busy"), OutgoingStreamTubeChannelPtr(this)); + } + + // In this specific overload, we're handling an Unix/AbstractUnix socket + if (socketAddress.startsWith(QLatin1Char('\0'))) { + // Abstract Unix socket case + // Check if the combination type/access control is supported + if ((accessControl == SocketAccessControlLocalhost && + !supportsAbstractUnixSocketsOnLocalhost()) || + (accessControl == SocketAccessControlCredentials && + !supportsAbstractUnixSocketsWithCredentials()) ) { + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + OutgoingStreamTubeChannelPtr(this)); + } + + setAddressType(SocketAddressTypeAbstractUnix); + setAccessControl(accessControl); + setLocalAddress(socketAddress); + + PendingVoid *pv = new PendingVoid( + interface<Client::ChannelTypeStreamTubeInterface>()->Offer( + SocketAddressTypeAbstractUnix, + QDBusVariant(QVariant(socketAddress.toLatin1())), + accessControl, + parameters), + OutgoingStreamTubeChannelPtr(this)); + PendingOpenTube *op = new PendingOpenTube(pv, parameters, + OutgoingStreamTubeChannelPtr(this)); + return op; + } else { + // Unix socket case + // Check if the combination type/access control is supported + if ((accessControl == SocketAccessControlLocalhost && + !supportsUnixSocketsOnLocalhost()) || + (accessControl == SocketAccessControlCredentials && + !supportsUnixSocketsWithCredentials()) || + (accessControl != SocketAccessControlLocalhost && + accessControl != SocketAccessControlCredentials) ) { + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + OutgoingStreamTubeChannelPtr(this)); + } + + setAddressType(SocketAddressTypeUnix); + setAccessControl(accessControl); + setLocalAddress(socketAddress); + + PendingVoid *pv = new PendingVoid( + interface<Client::ChannelTypeStreamTubeInterface>()->Offer( + SocketAddressTypeUnix, + QDBusVariant(QVariant(socketAddress.toLatin1())), + accessControl, + parameters), + OutgoingStreamTubeChannelPtr(this)); + PendingOpenTube *op = new PendingOpenTube(pv, parameters, + OutgoingStreamTubeChannelPtr(this)); + return op; + } +} + +/** + * Offer an Unix socket over the tube. + * + * Otherwise identical to offerUnixSocket(const QString &, const QVariantMap &, bool), but allows + * passing the local service's address as an already listening QLocalServer. + * + * This method requires OutgoingStreamTubeChannel::FeatureCore to be ready. + * + * \param server A valid QLocalServer, which should be already listening for incoming connections. + * \param parameters A dictionary of arbitrary parameters to send with the tube offer. + * \param requireCredentials Whether the server should require a SCM_CRED or SCM_CREDENTIALS message + * upon connection. + * \return A PendingOperation which will emit PendingOperation::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + * \sa StreamTubeChannel::supportsUnixSocketsOnLocalhost(), + * StreamTubeChannel::supportsUnixSocketsWithCredentials(), + * StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost(), + * StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials() + */ +PendingOperation *OutgoingStreamTubeChannel::offerUnixSocket( + const QLocalServer *server, + const QVariantMap ¶meters, + bool requireCredentials) +{ + // In this overload, we're handling a superset of a local socket + // Let's redirect the call to QString's overload + return offerUnixSocket(server->fullServerName(), parameters, requireCredentials); +} + +/** + * Return a map from a source address to the corresponding connections ids. + * + * The connection ids retrieved here can be used to map a source address + * which connected to your socket to a connection ID (for error reporting) and further, to a contact + * (by using contactsForConnections()). + * + * This method is only useful if a TCP socket was offered on this tube and the connection manager + * supports #SocketAccessControlPort, which can be discovered using + * supportsIPv4SocketsWithSpecifiedAddress() and supportsIPv6SocketsWithSpecifiedAddress() for IPv4 + * and IPv6 sockets respectively. + * + * Note that this function will only return valid data after the tube has been opened. + * + * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready. + * + * \return The map from source addresses as (QHostAddress, port in native byte order) pairs to the + * corresponding connection ids. + * \sa connectionsForCredentials() + */ +QHash<QPair<QHostAddress, quint16>, uint> OutgoingStreamTubeChannel::connectionsForSourceAddresses() const +{ + if (addressType() != SocketAddressTypeIPv4 && addressType() != SocketAddressTypeIPv6) { + warning() << "OutgoingStreamTubeChannel::connectionsForSourceAddresses() makes sense " + "just when offering a TCP socket"; + return QHash<QPair<QHostAddress, quint16>, uint>(); + } + + if (isValid() || !isDroppingConnections() || + !requestedFeatures().contains(StreamTubeChannel::FeatureConnectionMonitoring)) { + if (!isReady(StreamTubeChannel::FeatureConnectionMonitoring)) { + warning() << "StreamTubeChannel::FeatureConnectionMonitoring must be ready before " + " calling connectionsForSourceAddresses"; + return QHash<QPair<QHostAddress, quint16>, uint>(); + } + + if (state() != TubeChannelStateOpen) { + warning() << "OutgoingStreamTubeChannel::connectionsForSourceAddresses() makes sense " + "just when the tube is open"; + return QHash<QPair<QHostAddress, quint16>, uint>(); + } + } + + return mPriv->connectionsForSourceAddresses; +} + +/** + * Return a map from a credential byte to the corresponding connections ids. + * + * The connection ids retrieved here can be used to map a source address + * which connected to your socket to a connection ID (for error reporting) and further, to a contact + * (by using contactsForConnections()). + * + * This method is only useful if this tube was offered using an Unix socket and passing credential + * bytes was enabled (\c requireCredentials == true). + * + * Note that this function will only return valid data after the tube has been opened. + * + * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready. + * + * \return The map from credential bytes to the corresponding connection ids. + * \sa connectionsForSourceAddresses() + */ +QHash<uchar, uint> OutgoingStreamTubeChannel::connectionsForCredentials() const +{ + if (addressType() != SocketAddressTypeUnix && addressType() != SocketAddressTypeAbstractUnix) { + warning() << "OutgoingStreamTubeChannel::connectionsForCredentials() makes sense " + "just when offering an Unix socket"; + return QHash<uchar, uint>(); + } + + if (accessControl() != SocketAccessControlCredentials) { + warning() << "OutgoingStreamTubeChannel::connectionsForCredentials() makes sense " + "just when offering an Unix socket requiring credentials"; + return QHash<uchar, uint>(); + } + + if (isValid() || !isDroppingConnections() || + !requestedFeatures().contains(StreamTubeChannel::FeatureConnectionMonitoring)) { + if (!isReady(StreamTubeChannel::FeatureConnectionMonitoring)) { + warning() << "StreamTubeChannel::FeatureConnectionMonitoring must be ready before " + "calling OutgoingStreamTubeChannel::connectionsForCredentials()"; + return QHash<uchar, uint>(); + } + + if (state() != TubeChannelStateOpen) { + warning() << "OutgoingStreamTubeChannel::connectionsForCredentials() makes sense " + "just when the tube is opened"; + return QHash<uchar, uint>(); + } + } + + return mPriv->connectionsForCredentials; +} + +/** + * Return a map from connection ids to the associated contact. + * + * Note that this function will only return valid data after the tube has been opened. + * + * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready. + + * \return The map from connection ids to pointer to Contact objects. + * \sa connectionsForSourceAddresses(), connectionsForCredentials(), + * StreamTubeChannel::addressType() + */ +QHash<uint, ContactPtr> OutgoingStreamTubeChannel::contactsForConnections() const +{ + if (isValid() || !isDroppingConnections() || + !requestedFeatures().contains(StreamTubeChannel::FeatureConnectionMonitoring)) { + if (!isReady(StreamTubeChannel::FeatureConnectionMonitoring)) { + warning() << "StreamTubeChannel::FeatureConnectionMonitoring must be ready before " + "calling contactsForConnections"; + return QHash<uint, ContactPtr>(); + } + + if (state() != TubeChannelStateOpen) { + warning() << "OutgoingStreamTubeChannel::contactsForConnections() makes sense " + "just when the tube is open"; + return QHash<uint, ContactPtr>(); + } + } + + return mPriv->contactsForConnections; +} + +void OutgoingStreamTubeChannel::onNewRemoteConnection( + uint contactId, + const QDBusVariant ¶meter, + uint connectionId) +{ + // Request the handles from our queued contact factory + QUuid uuid = mPriv->queuedContactFactory->appendNewRequest(UIntList() << contactId); + + // Add a pending connection + mPriv->pendingNewConnections.insert(uuid, qMakePair(connectionId, parameter)); +} + +void OutgoingStreamTubeChannel::onContactsRetrieved( + const QUuid &uuid, + const QList<Tp::ContactPtr> &contacts) +{ + if (!isValid()) { + debug() << "Invalidated OutgoingStreamTubeChannel not emitting queued connection event"; + return; + } + + if (!mPriv->pendingNewConnections.contains(uuid)) { + if (mPriv->pendingClosedConnections.contains(uuid)) { + // closed connection + Private::ClosedConnection conn = mPriv->pendingClosedConnections.take(uuid); + + // First, do removeConnection() so connectionClosed is emitted, and anybody connected to it + // (like StreamTubeServer) has a chance to recover the source address / contact + removeConnection(conn.id, conn.error, conn.message); + + // Remove stuff from our hashes + mPriv->contactsForConnections.remove(conn.id); + + QHash<QPair<QHostAddress, quint16>, uint>::iterator srcAddrIter = + mPriv->connectionsForSourceAddresses.begin(); + while (srcAddrIter != mPriv->connectionsForSourceAddresses.end()) { + if (srcAddrIter.value() == conn.id) { + srcAddrIter = mPriv->connectionsForSourceAddresses.erase(srcAddrIter); + } else { + ++srcAddrIter; + } + } + + QHash<uchar, uint>::iterator credIter = mPriv->connectionsForCredentials.begin(); + while (credIter != mPriv->connectionsForCredentials.end()) { + if (credIter.value() == conn.id) { + credIter = mPriv->connectionsForCredentials.erase(credIter); + } else { + ++credIter; + } + } + } else { + warning() << "No pending connections found in OSTC" << objectPath() << "for contacts" + << contacts; + } + + return; + } + + // new connection + QPair<uint, QDBusVariant> connectionProperties = mPriv->pendingNewConnections.take(uuid); + + // Add it to our connections hash + foreach (const Tp::ContactPtr &contact, contacts) { + mPriv->contactsForConnections.insert(connectionProperties.first, contact); + } + + QPair<QHostAddress, quint16> address; + address.first = QHostAddress::Null; + + // Now let's try to track the parameter + if (addressType() == SocketAddressTypeIPv4) { + // Try a qdbus_cast to our address struct: we're shielded from crashes + // thanks to our specification + SocketAddressIPv4 addr = + qdbus_cast<Tp::SocketAddressIPv4>(connectionProperties.second.variant()); + address.first = QHostAddress(addr.address); + address.second = addr.port; + } else if (addressType() == SocketAddressTypeIPv6) { + SocketAddressIPv6 addr = + qdbus_cast<Tp::SocketAddressIPv6>(connectionProperties.second.variant()); + address.first = QHostAddress(addr.address); + address.second = addr.port; + } else if (addressType() == SocketAddressTypeUnix || + addressType() == SocketAddressTypeAbstractUnix) { + if (accessControl() == SocketAccessControlCredentials) { + uchar credentialByte = qdbus_cast<uchar>(connectionProperties.second.variant()); + mPriv->connectionsForCredentials.insertMulti(credentialByte, connectionProperties.first); + } + } + + if (address.first != QHostAddress::Null) { + // We can map it to a source address as well + mPriv->connectionsForSourceAddresses.insertMulti(address, connectionProperties.first); + } + + // Time for us to emit the signal + addConnection(connectionProperties.first); +} + +// This replaces the base class onConnectionClosed() slot, but unlike a virtual function, is ABI +// compatible +void OutgoingStreamTubeChannel::onConnectionClosed(uint connectionId, + const QString &errorName, const QString &errorMessage) +{ + // Insert a fake request to our queued contact factory to make the close events properly ordered + // with new connection events + QUuid uuid = mPriv->queuedContactFactory->appendNewRequest(UIntList()); + + // Add a pending connection close + mPriv->pendingClosedConnections.insert(uuid, + Private::ClosedConnection(connectionId, errorName, errorMessage)); +} + +} |