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 }