blobiohandler.cpp
00001 /*
00002  * This file is part of signon
00003  *
00004  * Copyright (C) 2009-2011 Nokia Corporation.
00005  *
00006  * Contact: Aurel Popirtac <ext-aurel.popirtac@nokia.com>
00007  * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
00008  *
00009  * This library is free software; you can redistribute it and/or
00010  * modify it under the terms of the GNU Lesser General Public License
00011  * version 2.1 as published by the Free Software Foundation.
00012  *
00013  * This library is distributed in the hope that it will be useful, but
00014  * WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
00021  * 02110-1301 USA
00022  */
00023 
00024 #include "blobiohandler.h"
00025 
00026 #include <QDBusArgument>
00027 #include <QBuffer>
00028 #include <QDataStream>
00029 #include <QDebug>
00030 #include <QDataStream>
00031 
00032 #include "SignOn/signonplugincommon.h"
00033 
00034 #define SIGNON_IPC_BUFFER_PAGE_SIZE 16384
00035 
00036 using namespace SignOn;
00037 
00038 BlobIOHandler::BlobIOHandler(QIODevice *readChannel,
00039                              QIODevice *writeChannel,
00040                              QObject *parent):
00041     QObject(parent),
00042     m_readChannel(readChannel),
00043     m_writeChannel(writeChannel),
00044     m_readNotifier(0),
00045     m_blobSize(-1)
00046 {
00047 }
00048 
00049 void BlobIOHandler::setReadChannelSocketNotifier(QSocketNotifier *notifier)
00050 {
00051     if (notifier == 0)
00052         return;
00053 
00054     m_readNotifier = notifier;
00055 }
00056 
00057 bool BlobIOHandler::sendData(const QVariantMap &map)
00058 {
00059     if (m_writeChannel == 0) {
00060         TRACE() << "NULL write channel.";
00061         return false;
00062     }
00063 
00064     QDataStream stream(m_writeChannel);
00065     QByteArray ba = variantMapToByteArray(map);
00066     stream << ba.size();
00067 
00068     QVector<QByteArray> pages = pageByteArray(ba);
00069     for (int i = 0; i < pages.count(); ++i)
00070         stream << pages[i];
00071 
00072     return true;
00073 }
00074 
00075 void BlobIOHandler::setReadNotificationEnabled(bool enabled)
00076 {
00077     if (enabled) {
00078         if (m_readNotifier != 0) {
00079             m_readNotifier->setEnabled(true);
00080             connect(m_readNotifier, SIGNAL(activated(int)),
00081                     this, SLOT(readBlob()));
00082         } else {
00083             connect(m_readChannel, SIGNAL(readyRead()),
00084                     this, SLOT(readBlob()));
00085         }
00086     } else {
00087         if (m_readNotifier != 0) {
00088             disconnect(m_readNotifier, SIGNAL(activated(int)),
00089                        this, SLOT(readBlob()));
00090             m_readNotifier->setEnabled(false);
00091         } else {
00092             disconnect(m_readChannel, SIGNAL(readyRead()),
00093                        this, SLOT(readBlob()));
00094         }
00095     }
00096 }
00097 
00098 void BlobIOHandler::receiveData(int expectedDataSize)
00099 {
00100     m_blobBuffer.clear();
00101     m_blobSize = expectedDataSize;
00102 
00103     //Enable read notification only if more than 1 BLOB page is to be received
00104     //This does not allow duplicate read attempts if only 1 page is available
00105     if (m_blobSize > SIGNON_IPC_BUFFER_PAGE_SIZE)
00106         setReadNotificationEnabled(true);
00107 
00108     readBlob();
00109 }
00110 
00111 void BlobIOHandler::readBlob()
00112 {
00113     QDataStream in(m_readChannel);
00114 
00115     QByteArray fractionBa;
00116     in >> fractionBa;
00117     m_blobBuffer.append(fractionBa);
00118 
00119     //Avoid infinite loops if the other party behaves badly
00120     if ((fractionBa.size() == 0) && (m_blobBuffer.size() < m_blobSize)) {
00121         setReadNotificationEnabled(false);
00122         emit error();
00123         return;
00124     }
00125 
00126     if (m_blobBuffer.size() == m_blobSize) {
00127         QVariantMap sessionDataMap;
00128         sessionDataMap = byteArrayToVariantMap(m_blobBuffer);
00129 
00130         if (m_blobSize > SIGNON_IPC_BUFFER_PAGE_SIZE)
00131             setReadNotificationEnabled(false);
00132 
00133         emit dataReceived(sessionDataMap);
00134     }
00135 }
00136 
00137 QVariantMap expandDBusArgumentValue(const QVariant &value, bool *success)
00138 {
00139     // first, convert the QDBusArgument to a map
00140     QDBusArgument dbusValue = value.value<QDBusArgument>();
00141     QVariantMap converted;
00142     if (dbusValue.currentType() == QDBusArgument::MapType &&
00143         // We only care about a{sv}
00144         dbusValue.currentSignature() == "a{sv}") {
00145         converted = qdbus_cast<QVariantMap>(dbusValue);
00146     } else {
00147         *success = false;
00148         return QVariantMap();
00149     }
00150 
00151     // Then, check each value of the converted map
00152     // and if any QDBusArgument is a value, convert that.
00153     QVariantMap returnValue;
00154     QVariantMap::const_iterator i;
00155     for (i = converted.constBegin(); i != converted.constEnd(); ++i) {
00156         if (qstrcmp(i.value().typeName(), "QDBusArgument") == 0) {
00157             QVariantMap convertedValue = expandDBusArgumentValue(i.value(), success);
00158             if (*success == false) {
00159                 //bail out to prevent error in serialization
00160                 return QVariantMap();
00161             }
00162             returnValue.insert(i.key(), convertedValue);
00163         } else {
00164             returnValue.insert(i.key(), i.value());
00165         }
00166     }
00167 
00168     return returnValue;
00169 }
00170 
00171 static QVariantMap filterOutComplexTypes(const QVariantMap &map)
00172 {
00173     QVariantMap filteredMap;
00174     QVariantMap::const_iterator i;
00175     for (i = map.constBegin(); i != map.constEnd(); i++) {
00176         if (qstrcmp(i.value().typeName(), "QDBusArgument") == 0) {
00177             bool success = true;
00178             QVariantMap convertedMap = expandDBusArgumentValue(i.value(), &success);
00179             if (success == false) {
00180                 /* QDBusArgument are complex types; there is no QDataStream
00181                  * serialization for them, so keeping them in the map would
00182                  * make the serialization fail for the whole map, if we are
00183                  * unable to convert to a QVariantMap.
00184                  * Therefore, skip them. */
00185                 BLAME() << "Found non-map QDBusArgument in data; skipping.";
00186                 continue;
00187             }
00188             filteredMap.insert(i.key(), convertedMap);
00189         } else {
00190             filteredMap.insert(i.key(), i.value());
00191         }
00192     }
00193     return filteredMap;
00194 }
00195 
00196 QByteArray BlobIOHandler::variantMapToByteArray(const QVariantMap &map)
00197 {
00198     QBuffer buffer;
00199     if (!buffer.open(QIODevice::WriteOnly))
00200         BLAME() << "Buffer opening failed.";
00201 
00202     QDataStream stream(&buffer);
00203     stream << filterOutComplexTypes(map);
00204     buffer.close();
00205 
00206     return buffer.data();
00207 }
00208 
00209 QVariantMap BlobIOHandler::byteArrayToVariantMap(const QByteArray &array)
00210 {
00211     QByteArray nonConst = array;
00212     QBuffer buffer(&nonConst);
00213     if (!buffer.open(QIODevice::ReadOnly))
00214         BLAME() << "Buffer opening failed.";
00215 
00216     buffer.reset();
00217     QDataStream stream(&buffer);
00218     QVariantMap map;
00219     stream >> map;
00220     buffer.close();
00221 
00222     return map;
00223 }
00224 
00225 QVector<QByteArray> BlobIOHandler::pageByteArray(const QByteArray &array)
00226 {
00227     QVector<QByteArray> dataPages;
00228     QByteArray ba = array;
00229     QBuffer pagingBuffer(&ba);
00230 
00231     if (!pagingBuffer.open(QIODevice::ReadOnly))
00232         BLAME() << "Error while paging BLOB. Buffer opening failed.";
00233 
00234     while (!pagingBuffer.atEnd()) {
00235         QByteArray page = pagingBuffer.read(SIGNON_IPC_BUFFER_PAGE_SIZE);
00236         dataPages.append(page);
00237     }
00238     pagingBuffer.close();
00239 
00240     return dataPages;
00241 }