File indexing completed on 2024-05-12 17:15:12

0001 /*
0002    Copyright (C) 2013 Andreas Hartmetz <ahartmetz@gmail.com>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LGPL.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 
0019    Alternatively, this file is available under the Mozilla Public License
0020    Version 1.1.  You may obtain a copy of the License at
0021    http://www.mozilla.org/MPL/
0022 */
0023 
0024 #include "eavesdroppermodel.h"
0025 
0026 #include "arguments.h"
0027 #include "message.h"
0028 
0029 #include <QDataStream>
0030 #include <QFile>
0031 
0032 enum {
0033     TypeColumn = 0,
0034     RoundtripTimeColumn,
0035     MethodColumn,
0036     InterfaceColumn,
0037     PathColumn,
0038     SenderColumn,
0039     DestinationColumn,
0040     ColumnCount
0041 };
0042 
0043 MessageRecord::MessageRecord(Message *msg, qint64 time)
0044    : message(msg),
0045      otherMessageIndex(-1),
0046      timestamp(time)
0047 {}
0048 
0049 MessageRecord::MessageRecord()
0050    : message(nullptr),
0051      otherMessageIndex(-1),
0052      timestamp(-1)
0053 {}
0054 
0055 QString MessageRecord::type() const
0056 {
0057     switch (message->type()) {
0058     case Message::MethodCallMessage:
0059         return QObject::tr("Call");
0060     case Message::MethodReturnMessage:
0061         return QObject::tr("Return");
0062     case Message::ErrorMessage:
0063         return QObject::tr("Error");
0064     case Message::SignalMessage:
0065         return QObject::tr("Signal");
0066     case Message::InvalidMessage:
0067         return QString::fromLatin1("???");
0068     }
0069     Q_ASSERT(false);
0070     return QString();
0071 }
0072 
0073 bool MessageRecord::isAwaitingReply() const
0074 {
0075     return message->type() == Message::MethodCallMessage &&
0076            message->expectsReply() && otherMessageIndex < 0;
0077 }
0078 
0079 bool MessageRecord::isReplyToKnownCall() const
0080 {
0081     return otherMessageIndex >= 0 && (message->type() == Message::MethodReturnMessage ||
0082                                       message->type() == Message::ErrorMessage);
0083 }
0084 
0085 uint32 MessageRecord::conversationSerial() const
0086 {
0087     if (message->type() == Message::MethodReturnMessage || message->type() == Message::ErrorMessage) {
0088         return message->replySerial();
0089     }
0090     return message->serial();
0091 }
0092 
0093 QString MessageRecord::conversationMethod(const std::vector<MessageRecord> &container) const
0094 {
0095     std::string method;
0096     if (isReplyToKnownCall()) {
0097         method = container[otherMessageIndex].message->method();
0098     } else {
0099         method = message->method();
0100     }
0101     return QString::fromStdString(method);
0102 }
0103 
0104 qint64 MessageRecord::conversationStartTime(const std::vector<MessageRecord> &container) const
0105 {
0106     if (isReplyToKnownCall()) {
0107         return container[otherMessageIndex].timestamp;
0108     }
0109     return timestamp;
0110 }
0111 
0112 qint64 MessageRecord::roundtripTime(const std::vector<MessageRecord> &container) const
0113 {
0114     if (isReplyToKnownCall()) {
0115         return timestamp - container[otherMessageIndex].timestamp;
0116     }
0117     return -1;
0118 }
0119 
0120 static bool isNumericAddress(const std::string &address)
0121 {
0122     return !address.empty() && address[0] == ':';
0123 }
0124 
0125 QString MessageRecord::niceSender(const std::vector<MessageRecord> &container) const
0126 {
0127     // this does something like ":1.2" -> ":1.2 (org.freedesktop.fooInterface)"
0128     std::string sender = message->sender();
0129     if (isNumericAddress(sender) && isReplyToKnownCall()) {
0130         const std::string otherDest = container[otherMessageIndex].message->destination();
0131         if (!otherDest.empty() && otherDest[0] != ':') {
0132             sender += " (";
0133             sender += otherDest;
0134             sender += ')';
0135         }
0136     }
0137     return QString::fromStdString(sender);
0138 }
0139 
0140 bool MessageRecord::couldHaveNicerDestination(const std::vector<MessageRecord> &container) const
0141 {
0142     // see niceDestination; this returns true if the "raw" destination is *not* of the :n.m type
0143     // and the other (i.e. reply) message's sender *is* o the :n.m type
0144     if (message->type() != Message::MethodCallMessage || otherMessageIndex < 0) {
0145         return false;
0146     }
0147     const std::string dest = message->destination();
0148     if (isNumericAddress(dest)) {
0149         return false;
0150     }
0151     const std::string otherSender = container[otherMessageIndex].message->sender();
0152     return !otherSender.empty() && otherSender[0] == ':';
0153 }
0154 
0155 QString MessageRecord::niceDestination(const std::vector<MessageRecord> &container) const
0156 {
0157     // this does something like "org.freedesktop.fooInterface" -> "org.freedesktop.fooInterface (:1.2)"
0158     std::string dest = message->destination();
0159     if (couldHaveNicerDestination(container)) {
0160         dest += " (";
0161         dest += container[otherMessageIndex].message->sender();
0162         dest += ')';
0163     }
0164     return QString::fromStdString(dest);
0165 }
0166 
0167 EavesdropperModel::EavesdropperModel(QObject *parent)
0168    : QAbstractItemModel(parent),
0169      m_worker(this),
0170      m_isRecording(true)
0171 {
0172 }
0173 
0174 EavesdropperModel::~EavesdropperModel()
0175 {
0176     clearInternal();
0177 }
0178 
0179 void EavesdropperModel::addMessage(Message *message, qint64 timestamp)
0180 {
0181     if (!m_isRecording) {
0182         return;
0183     }
0184     beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
0185     m_messages.push_back(MessageRecord(message, timestamp));
0186 
0187     const uint currentMessageIndex = m_messages.size() - 1;
0188 
0189     // Connect responses with previously spotted calls because information from one is useful for the other.
0190     // We must match the call sender with the reply receiver, instead of the call receiver with the reply
0191     // sender, because calls can go to well-known addresses that are only resolved to a concrete endpoint
0192     // by the bus daemon.
0193 
0194     if (message->type() == Message::MethodCallMessage) {
0195         // the NO_REPLY_EXPECTED flag does *not* forbid a reply, so we disregard the flag
0196         // ### it would be nice to clean up m_callsAwaitingResponse periodically, but we allocate
0197         //     memory that is not freed before shutdown left and right so it doesn't make much of
0198         //     a difference. it does make a difference when serials overflow.
0199         m_callsAwaitingResponse[Call(message->serial(), message->sender())] = currentMessageIndex;
0200     } else if (message->type() == Message::MethodReturnMessage || message->type() == Message::ErrorMessage) {
0201         Call key(message->replySerial(), message->destination());
0202         std::map<Call, uint32>::iterator it = m_callsAwaitingResponse.find(key);
0203         // we could have missed the initial call because it happened before we connected to the bus...
0204         // theoretically we could assert the presence of the call after one d-bus timeout has passed
0205         if (it != m_callsAwaitingResponse.end()) {
0206             const uint originalMessageIndex = it->second;
0207             m_messages.back().otherMessageIndex = originalMessageIndex;
0208             m_messages[originalMessageIndex].otherMessageIndex = currentMessageIndex;
0209             m_callsAwaitingResponse.erase(it);
0210             if (m_messages[originalMessageIndex].couldHaveNicerDestination(m_messages)) {
0211                 const QModelIndex index = createIndex(originalMessageIndex, DestinationColumn);
0212                 emit dataChanged(index, index);
0213             }
0214         }
0215     }
0216     endInsertRows();
0217 }
0218 
0219 QVariant EavesdropperModel::data(const QModelIndex &index, int role) const
0220 {
0221     if (role == Qt::DisplayRole) {
0222         Q_ASSERT(size_t(index.row()) < m_messages.size());
0223         const MessageRecord &mr = m_messages[index.row()];
0224         switch (index.column()) {
0225         case TypeColumn:
0226             return mr.type();
0227         case RoundtripTimeColumn: {
0228             qint64 rtt = mr.roundtripTime(m_messages);
0229             Q_ASSERT(rtt >= -1); // QElapsedTimer should give us monotonic time
0230             if (rtt == -1) {
0231                 break;  // no data for a message that doesn't or can't have a reply
0232             }
0233             return static_cast<double>(rtt) / 1000000.0; // nsecs / 1E6 -> milliseconds
0234         }
0235         case MethodColumn:
0236             return mr.conversationMethod(m_messages);
0237         case InterfaceColumn:
0238             return QString::fromStdString(mr.message->interface());
0239         case PathColumn:
0240             return QString::fromStdString(mr.message->path());
0241         case SenderColumn:
0242             return mr.niceSender(m_messages);
0243         case DestinationColumn:
0244             return mr.niceDestination(m_messages);
0245         default:
0246             break;
0247         }
0248     }
0249     return QVariant();
0250 }
0251 
0252 QVariant EavesdropperModel::headerData(int section, Qt::Orientation orientation, int role) const
0253 {
0254     if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
0255         switch (section) {
0256         case TypeColumn:
0257             return tr("Type");
0258         case RoundtripTimeColumn:
0259             return tr("Latency [ms]");
0260         case MethodColumn:
0261             return tr("Method");
0262         case InterfaceColumn:
0263             return tr("Interface");
0264         case PathColumn:
0265             return tr("Path");
0266         case SenderColumn:
0267             return tr("Sender");
0268         case DestinationColumn:
0269             return tr("Destination");
0270         default:
0271             break;
0272         }
0273     }
0274     return QVariant();
0275 }
0276 
0277 QModelIndex EavesdropperModel::index(int row, int column, const QModelIndex &parent) const
0278 {
0279     if (parent.isValid()) {
0280         Q_ASSERT(parent.model() == this);
0281         return QModelIndex();
0282     }
0283     return createIndex(row, column);
0284 }
0285 
0286 bool EavesdropperModel::hasChildren (const QModelIndex &parent) const
0287 {
0288     return !parent.isValid();
0289 }
0290 
0291 QModelIndex EavesdropperModel::parent(const QModelIndex &child) const
0292 {
0293     Q_ASSERT(!child.isValid() || child.model() == this); // ### why does it crash with &&?
0294     Q_UNUSED(child);
0295     return QModelIndex();
0296 }
0297 
0298 int EavesdropperModel::rowCount(const QModelIndex &/* parent */) const
0299 {
0300     return m_messages.size();
0301 }
0302 
0303 int EavesdropperModel::columnCount(const QModelIndex &/* parent */) const
0304 {
0305     return ColumnCount;
0306 }
0307 
0308 void EavesdropperModel::setRecording(bool recording)
0309 {
0310     // We could stop the eavesdropper thread when not recording, but it doesn't seem worth the effort.
0311     m_isRecording = recording;
0312 }
0313 
0314 void EavesdropperModel::clear()
0315 {
0316     beginResetModel();
0317     clearInternal();
0318     endResetModel();
0319 }
0320 
0321 void EavesdropperModel::clearInternal()
0322 {
0323     m_callsAwaitingResponse.clear();
0324     // This is easier than making MessageRecord clean up after itself - it would need refcounting in order
0325     // to avoid accidentally deleting the message in many common situations.
0326     for (MessageRecord &msgRecord : m_messages) {
0327         delete msgRecord.message;
0328         msgRecord.message = nullptr;
0329     }
0330     m_messages.clear();
0331 }
0332 
0333 // TODO make the header 32 (or 16) bytes long in the next version - right now, the alignment doesn't
0334 // make a big difference because message data is copied at least once, but still - would be cleaner.
0335 static const char *fileHeader = "Dferry binary DBus dump v0001";
0336 
0337 void EavesdropperModel::saveToFile(const QString &path)
0338 {
0339     QFile file(path);
0340     file.open(QIODevice::WriteOnly | QIODevice::Truncate);
0341     file.write(fileHeader);
0342 
0343     for (MessageRecord &msgRecord : m_messages) {
0344         std::vector<byte> msgData = msgRecord.message->save();
0345 
0346         // auxiliary data from MessageRecord, length prefix
0347         {
0348             QDataStream auxStream(&file);
0349             auxStream.setVersion(12);
0350             auxStream << msgRecord.otherMessageIndex;
0351             auxStream << msgRecord.timestamp;
0352             auxStream << quint32(msgData.size());
0353         }
0354 
0355         // serialized message just like it would appear on the bus
0356         file.write(reinterpret_cast<const char*>(&msgData[0]), msgData.size());
0357     }
0358 }
0359 
0360 bool EavesdropperModel::loadFromFile(const QString &path)
0361 {
0362     QFile file(path);
0363     file.open(QIODevice::ReadOnly);
0364     if (file.read(strlen(fileHeader)) != QByteArray(fileHeader)) {
0365         return false;
0366     }
0367 
0368     std::vector<MessageRecord> loadedRecords;
0369     while (!file.atEnd()) {
0370         MessageRecord record;
0371         quint32 messageDataSize;
0372         // auxiliary data from MessageRecord, length prefix
0373         {
0374             QDataStream auxStream(&file);
0375             auxStream.setVersion(12);
0376             auxStream >> record.otherMessageIndex;
0377             auxStream >> record.timestamp;
0378             auxStream >> messageDataSize;
0379         }
0380         if (file.atEnd()) {
0381             return false;
0382         }
0383 
0384         // serialized message just like it would appear on the bus
0385         chunk msgBuf;
0386         msgBuf.length = messageDataSize;
0387         msgBuf.ptr = reinterpret_cast<byte *>(malloc(msgBuf.length));
0388         if (file.read(reinterpret_cast<char*>(msgBuf.ptr), messageDataSize) != messageDataSize) {
0389             return false;
0390         }
0391         record.message = new Message();
0392         record.message->deserializeAndTake(msgBuf);
0393 
0394         loadedRecords.push_back(record);
0395     }
0396 
0397     beginResetModel();
0398     clearInternal();
0399     m_messages = loadedRecords;
0400     endResetModel();
0401     // TODO disable capture or make sure that our call-reply matching features work in the
0402     //      presence of data loaded from a different session...
0403     return true;
0404 }