diff options
Diffstat (limited to 'src/nfc/qllcpsocket_maemo6_p.cpp')
-rw-r--r-- | src/nfc/qllcpsocket_maemo6_p.cpp | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/src/nfc/qllcpsocket_maemo6_p.cpp b/src/nfc/qllcpsocket_maemo6_p.cpp new file mode 100644 index 00000000..a2cd15f6 --- /dev/null +++ b/src/nfc/qllcpsocket_maemo6_p.cpp @@ -0,0 +1,638 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qllcpsocket_maemo6_p.h" + +#include "manager_interface.h" +#include "maemo6/adapter_interface_p.h" +#include "maemo6/socketrequestor_p.h" + +#include <QtCore/QSocketNotifier> +#include <QtCore/QAtomicInt> + +#include <errno.h> +#include <signal.h> + +using namespace com::nokia::nfc; + +static QAtomicInt requestorId = 0; +static const char * const requestorBasePath = "/com/nokia/nfc/llcpclient/"; + +QLlcpSocketPrivate::QLlcpSocketPrivate(QLlcpSocket *q) +: q_ptr(q), + m_connection(QDBusConnection::connectToBus(QDBusConnection::SystemBus, QUuid::createUuid())), + m_port(0), m_socketRequestor(0), m_fd(-1), m_readNotifier(0), m_writeNotifier(0), + m_pendingBytes(0), m_state(QLlcpSocket::UnconnectedState), + m_error(QLlcpSocket::UnknownSocketError) +{ +} + +QLlcpSocketPrivate::QLlcpSocketPrivate(const QDBusConnection &connection, int fd, + const QVariantMap &properties) +: q_ptr(0), m_properties(properties), m_connection(connection), m_port(0), m_socketRequestor(0), + m_fd(fd), m_pendingBytes(0), + m_state(QLlcpSocket::ConnectedState), m_error(QLlcpSocket::UnknownSocketError) +{ + m_readNotifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); + connect(m_readNotifier, SIGNAL(activated(int)), this, SLOT(_q_readNotify())); + m_writeNotifier = new QSocketNotifier(m_fd, QSocketNotifier::Write, this); + connect(m_writeNotifier, SIGNAL(activated(int)), this, SLOT(_q_bytesWritten())); +} + +QLlcpSocketPrivate::~QLlcpSocketPrivate() +{ + delete m_readNotifier; + delete m_writeNotifier; +} + +void QLlcpSocketPrivate::connectToService(QNearFieldTarget *target, const QString &serviceUri) +{ + Q_UNUSED(target); + + Q_Q(QLlcpSocket); + + m_state = QLlcpSocket::ConnectingState; + emit q->stateChanged(m_state); + + initializeRequestor(); + + if (m_socketRequestor) { + m_serviceUri = serviceUri; + m_port = 0; + + QString accessKind(QLatin1String("device.llcp.co.client:") + serviceUri); + + m_socketRequestor->requestAccess(m_requestorPath, accessKind); + } else { + setSocketError(QLlcpSocket::SocketResourceError); + + m_state = QLlcpSocket::UnconnectedState; + emit q->stateChanged(m_state); + } +} + +void QLlcpSocketPrivate::disconnectFromService() +{ + Q_Q(QLlcpSocket); + + m_state = QLlcpSocket::ClosingState; + emit q->stateChanged(m_state); + + delete m_readNotifier; + m_readNotifier = 0; + delete m_writeNotifier; + m_writeNotifier = 0; + m_pendingBytes = 0; + ::close(m_fd); + m_fd = -1; + + if (m_socketRequestor) { + QString accessKind(QLatin1String("device.llcp.co.client:") + m_serviceUri); + + Manager manager(QLatin1String("com.nokia.nfc"), QLatin1String("/"), m_connection); + QDBusObjectPath defaultAdapterPath = manager.DefaultAdapter(); + + m_socketRequestor->cancelAccessRequest(m_requestorPath, accessKind); + } + + m_state = QLlcpSocket::UnconnectedState; + q->setOpenMode(QIODevice::NotOpen); + emit q->stateChanged(m_state); + emit q->disconnected(); +} + +bool QLlcpSocketPrivate::bind(quint8 port) +{ + initializeRequestor(); + + if (!m_socketRequestor) + return false; + + m_serviceUri.clear(); + m_port = port; + + const QString accessKind(QLatin1String("device.llcp.cl:") + QString::number(port)); + + m_socketRequestor->requestAccess(m_requestorPath, accessKind); + + return waitForBound(30000); +} + +bool QLlcpSocketPrivate::hasPendingDatagrams() const +{ + return !m_receivedDatagrams.isEmpty(); +} + +qint64 QLlcpSocketPrivate::pendingDatagramSize() const +{ + if (m_receivedDatagrams.isEmpty()) + return -1; + + if (m_state == QLlcpSocket::BoundState) + return m_receivedDatagrams.first().length() - 1; + else + return m_receivedDatagrams.first().length(); +} + +qint64 QLlcpSocketPrivate::writeDatagram(const char *data, qint64 size) +{ + if (m_state != QLlcpSocket::ConnectedState) + return -1; + + return writeData(data, size); +} + +qint64 QLlcpSocketPrivate::writeDatagram(const QByteArray &datagram) +{ + if (m_state != QLlcpSocket::ConnectedState) + return -1; + + if (uint(datagram.length()) > m_properties.value(QLatin1String("RemoteMIU"), 128).toUInt()) + return -1; + + return writeData(datagram.constData(), datagram.size()); +} + +qint64 QLlcpSocketPrivate::readDatagram(char *data, qint64 maxSize, + QNearFieldTarget **target, quint8 *port) +{ + Q_UNUSED(target); + + if (m_state == QLlcpSocket::ConnectedState) { + return readData(data, maxSize); + } else if (m_state == QLlcpSocket::BoundState) { + return readData(data, maxSize, port); + } + + return -1; +} + +qint64 QLlcpSocketPrivate::writeDatagram(const char *data, qint64 size, + QNearFieldTarget *target, quint8 port) +{ + Q_UNUSED(target); + + if (m_state != QLlcpSocket::BoundState) + return -1; + + if (m_properties.value(QLatin1String("RemoteMIU"), 128).toUInt() < size) + return -1; + + if (m_properties.value(QLatin1String("LocalMIU"), 128).toUInt() < size) + return -1; + + QByteArray datagram; + datagram.append(port); + datagram.append(data, size); + + m_pendingBytes += datagram.size() - 1; + m_writeNotifier->setEnabled(true); + + return ::write(m_fd, datagram.constData(), datagram.size()) - 1; +} + +qint64 QLlcpSocketPrivate::writeDatagram(const QByteArray &datagram, + QNearFieldTarget *target, quint8 port) +{ + Q_UNUSED(target); + + if (m_state != QLlcpSocket::BoundState) + return -1; + + if (m_properties.value(QLatin1String("RemoteMIU"), 128).toInt() < datagram.size()) + return -1; + + if (m_properties.value(QLatin1String("LocalMIU"), 128).toInt() < datagram.size()) + return -1; + + QByteArray d; + d.append(port); + d.append(datagram); + + m_pendingBytes += datagram.size() - 1; + m_writeNotifier->setEnabled(true); + + return ::write(m_fd, d.constData(), d.size()) - 1; +} + +QLlcpSocket::SocketError QLlcpSocketPrivate::error() const +{ + return m_error; +} + +QLlcpSocket::SocketState QLlcpSocketPrivate::state() const +{ + return m_state; +} + +qint64 QLlcpSocketPrivate::readData(char *data, qint64 maxlen, quint8 *port) +{ + if (m_receivedDatagrams.isEmpty()) + return 0; + + const QByteArray datagram = m_receivedDatagrams.takeFirst(); + + if (m_state == QLlcpSocket::BoundState) { + if (port) + *port = datagram.at(0); + + qint64 size = qMin(maxlen, qint64(datagram.length() - 1)); + memcpy(data, datagram.constData() + 1, size); + return size; + } else { + if (port) + *port = 0; + + qint64 size = qMin(maxlen, qint64(datagram.length())); + memcpy(data, datagram.constData(), size); + return size; + } +} + +qint64 QLlcpSocketPrivate::writeData(const char *data, qint64 len) +{ + Q_Q(QLlcpSocket); + + qint64 remoteMiu = m_properties.value(QLatin1String("RemoteMIU"), 128).toLongLong(); + qint64 localMiu = m_properties.value(QLatin1String("LocalMIU"), 128).toLongLong(); + qint64 miu = qMin(remoteMiu, localMiu); + + m_writeNotifier->setEnabled(true); + + ssize_t wrote = ::write(m_fd, data, qMin(miu, len)); + if (wrote == -1) { + if (errno == EAGAIN) + return 0; + + setSocketError(QLlcpSocket::RemoteHostClosedError); + q->disconnectFromService(); + return -1; + } + + m_pendingBytes += wrote; + return wrote; +} + +qint64 QLlcpSocketPrivate::bytesAvailable() const +{ + qint64 available = 0; + foreach (const QByteArray &datagram, m_receivedDatagrams) + available += datagram.length(); + + // less source port + if (m_state == QLlcpSocket::BoundState) + available -= m_receivedDatagrams.count(); + + return available; +} + +bool QLlcpSocketPrivate::canReadLine() const +{ + if (m_state == QLlcpSocket::BoundState) + return false; + + foreach (const QByteArray &datagram, m_receivedDatagrams) { + if (datagram.contains('\n')) + return true; + } + + return false; +} + +bool QLlcpSocketPrivate::waitForReadyRead(int msec) +{ + if (bytesAvailable()) + return true; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_fd, &fds); + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + // timeout can not be 0 or else select will return an error. + if (0 == msec) + timeout.tv_usec = 1000; + + int result = -1; + // on Linux timeout will be updated by select, but _not_ on other systems. + QElapsedTimer timer; + timer.start(); + while (!bytesAvailable() && (-1 == msec || timer.elapsed() < msec)) { + result = ::select(m_fd + 1, &fds, 0, 0, &timeout); + if (result > 0) + _q_readNotify(); + + if (-1 == result && errno != EINTR) { + setSocketError(QLlcpSocket::UnknownSocketError); + break; + } + } + + return bytesAvailable(); +} + +bool QLlcpSocketPrivate::waitForBytesWritten(int msec) +{ + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_fd, &fds); + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + // timeout can not be 0 or else select will return an error. + if (0 == msec) + timeout.tv_usec = 1000; + + int result = -1; + // on Linux timeout will be updated by select, but _not_ on other systems. + QElapsedTimer timer; + timer.start(); + while (-1 == msec || timer.elapsed() < msec) { + result = ::select(m_fd + 1, 0, &fds, 0, &timeout); + if (result > 0) + return true; + if (-1 == result && errno != EINTR) { + setSocketError(QLlcpSocket::UnknownSocketError); + return false; + } + } + + // timeout expired + return false; +} + +bool QLlcpSocketPrivate::waitForConnected(int msecs) +{ + if (m_state != QLlcpSocket::ConnectingState) + return m_state == QLlcpSocket::ConnectedState; + + QElapsedTimer timer; + timer.start(); + while (m_state == QLlcpSocket::ConnectingState && (msecs == -1 || timer.elapsed() < msecs)) { + if (!m_socketRequestor->waitForDBusSignal(qMax(msecs - timer.elapsed(), qint64(0)))) { + setSocketError(QLlcpSocket::UnknownSocketError); + break; + } + } + + // Possibly not needed. + QCoreApplication::sendPostedEvents(this, QEvent::MetaCall); + + return m_state == QLlcpSocket::ConnectedState; +} + +bool QLlcpSocketPrivate::waitForDisconnected(int msec) +{ + if (m_state == QLlcpSocket::UnconnectedState) + return true; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_fd, &fds); + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + // timeout can not be 0 or else select will return an error. + if (0 == msec) + timeout.tv_usec = 1000; + + int result = -1; + // on Linux timeout will be updated by select, but _not_ on other systems. + QElapsedTimer timer; + timer.start(); + while (m_state != QLlcpSocket::UnconnectedState && (-1 == msec || timer.elapsed() < msec)) { + result = ::select(m_fd + 1, &fds, 0, 0, &timeout); + if (result > 0) + _q_readNotify(); + + if (-1 == result && errno != EINTR) { + setSocketError(QLlcpSocket::UnknownSocketError); + break; + } + } + + return m_state == QLlcpSocket::UnconnectedState; +} + +bool QLlcpSocketPrivate::waitForBound(int msecs) +{ + if (m_state == QLlcpSocket::BoundState) + return true; + + QElapsedTimer timer; + timer.start(); + while (m_state != QLlcpSocket::BoundState && (msecs == -1 || timer.elapsed() < msecs)) { + if (!m_socketRequestor->waitForDBusSignal(qMax(msecs - timer.elapsed(), qint64(0)))) + return false; + } + + // Possibly not needed. + QCoreApplication::sendPostedEvents(this, QEvent::MetaCall); + + return m_state == QLlcpSocket::BoundState; +} + +void QLlcpSocketPrivate::AccessFailed(const QDBusObjectPath &targetPath, const QString &kind, + const QString &error) +{ + Q_UNUSED(targetPath); + Q_UNUSED(kind); + Q_UNUSED(error); + + Q_Q(QLlcpSocket); + + setSocketError(QLlcpSocket::SocketAccessError); + + m_state = QLlcpSocket::UnconnectedState; + emit q->stateChanged(m_state); +} + +void QLlcpSocketPrivate::AccessGranted(const QDBusObjectPath &targetPath, + const QString &accessKind) +{ + Q_UNUSED(targetPath); + Q_UNUSED(accessKind); +} + +void QLlcpSocketPrivate::Accept(const QDBusVariant &lsap, const QDBusVariant &rsap, + int fd, const QVariantMap &properties) +{ + Q_UNUSED(lsap); + Q_UNUSED(rsap); + Q_UNUSED(fd); + Q_UNUSED(properties); +} + +void QLlcpSocketPrivate::Connect(const QDBusVariant &lsap, const QDBusVariant &rsap, + int fd, const QVariantMap &properties) +{ + Q_UNUSED(lsap); + Q_UNUSED(rsap); + + m_fd = fd; + + m_readNotifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); + connect(m_readNotifier, SIGNAL(activated(int)), this, SLOT(_q_readNotify())); + m_writeNotifier = new QSocketNotifier(m_fd, QSocketNotifier::Write, this); + connect(m_writeNotifier, SIGNAL(activated(int)), this, SLOT(_q_bytesWritten())); + + m_properties = properties; + + Q_Q(QLlcpSocket); + + q->setOpenMode(QIODevice::ReadWrite); + + m_state = QLlcpSocket::ConnectedState; + emit q->stateChanged(m_state); + emit q->connected(); +} + +void QLlcpSocketPrivate::Socket(const QDBusVariant &lsap, int fd, const QVariantMap &properties) +{ + m_fd = fd; + m_port = lsap.variant().toUInt(); + + m_readNotifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); + connect(m_readNotifier, SIGNAL(activated(int)), this, SLOT(_q_readNotify())); + m_writeNotifier = new QSocketNotifier(m_fd, QSocketNotifier::Write, this); + connect(m_writeNotifier, SIGNAL(activated(int)), this, SLOT(_q_bytesWritten())); + + m_properties = properties; + + Q_Q(QLlcpSocket); + + m_state = QLlcpSocket::BoundState; + emit q->stateChanged(m_state); +} + +void QLlcpSocketPrivate::_q_readNotify() +{ + Q_Q(QLlcpSocket); + + QByteArray datagram; + datagram.resize(m_properties.value(QLatin1String("LocalMIU"), 128).toUInt()); + + int readFromDevice = ::read(m_fd, datagram.data(), datagram.size()); + if (readFromDevice <= 0) { + m_readNotifier->setEnabled(false); + + setSocketError(QLlcpSocket::RemoteHostClosedError); + + q->disconnectFromService(); + q->setOpenMode(QIODevice::NotOpen); + } else { + m_receivedDatagrams.append(datagram.left(readFromDevice)); + emit q->readyRead(); + } +} + +void QLlcpSocketPrivate::_q_bytesWritten() +{ + Q_Q(QLlcpSocket); + + m_writeNotifier->setEnabled(false); + + emit q->bytesWritten(m_pendingBytes); + m_pendingBytes = 0; +} + +void QLlcpSocketPrivate::setSocketError(QLlcpSocket::SocketError socketError) +{ + Q_Q(QLlcpSocket); + + QLatin1String c("QLlcpSocket"); + + m_error = socketError; + switch (socketError) { + case QLlcpSocket::UnknownSocketError: + q->setErrorString(QLlcpSocket::tr("%1: Unknown error %2").arg(c).arg(errno)); + break; + case QLlcpSocket::RemoteHostClosedError: + q->setErrorString(QLlcpSocket::tr("%1: Remote closed").arg(c)); + break; + case QLlcpSocket::SocketAccessError: + q->setErrorString(QLlcpSocket::tr("%1: Socket access error")); + break; + case QLlcpSocket::SocketResourceError: + q->setErrorString(QLlcpSocket::tr("%1: Socket resource error")); + break; + } + + emit q->error(m_error); +} + +void QLlcpSocketPrivate::initializeRequestor() +{ + if (m_socketRequestor) + return; + + if (m_requestorPath.isEmpty()) { + m_requestorPath = QLatin1String(requestorBasePath) + + QString::number(requestorId.fetchAndAddOrdered(1)); + } + + Manager manager(QLatin1String("com.nokia.nfc"), QLatin1String("/"), m_connection); + QDBusObjectPath defaultAdapterPath = manager.DefaultAdapter(); + + if (!m_socketRequestor) { + m_socketRequestor = new SocketRequestor(defaultAdapterPath.path(), this); + + connect(m_socketRequestor, SIGNAL(accessFailed(QDBusObjectPath,QString,QString)), + this, SLOT(AccessFailed(QDBusObjectPath,QString,QString))); + connect(m_socketRequestor, SIGNAL(accessGranted(QDBusObjectPath,QString)), + this, SLOT(AccessGranted(QDBusObjectPath,QString))); + connect(m_socketRequestor, SIGNAL(accept(QDBusVariant,QDBusVariant,int,QVariantMap)), + this, SLOT(Accept(QDBusVariant,QDBusVariant,int,QVariantMap))); + connect(m_socketRequestor, SIGNAL(connect(QDBusVariant,QDBusVariant,int,QVariantMap)), + this, SLOT(Connect(QDBusVariant,QDBusVariant,int,QVariantMap))); + connect(m_socketRequestor, SIGNAL(socket(QDBusVariant,int,QVariantMap)), + this, SLOT(Socket(QDBusVariant,int,QVariantMap))); + } +} + +#include "moc_qllcpsocket_maemo6_p.cpp" |