| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | #include "baselineprotocol.h" |
| 29 | #include <QLibraryInfo> |
| 30 | #include <QImage> |
| 31 | #include <QBuffer> |
| 32 | #include <QHostInfo> |
| 33 | #include <QSysInfo> |
| 34 | #if QT_CONFIG(process) |
| 35 | # include <QProcess> |
| 36 | #endif |
| 37 | #include <QFileInfo> |
| 38 | #include <QDir> |
| 39 | #include <QTime> |
| 40 | #include <QPointer> |
| 41 | #include <QRegExp> |
| 42 | |
| 43 | const QString PI_Project(QLS("Project" )); |
| 44 | const QString PI_TestCase(QLS("TestCase" )); |
| 45 | const QString PI_HostName(QLS("HostName" )); |
| 46 | const QString PI_HostAddress(QLS("HostAddress" )); |
| 47 | const QString PI_OSName(QLS("OSName" )); |
| 48 | const QString PI_OSVersion(QLS("OSVersion" )); |
| 49 | const QString PI_QtVersion(QLS("QtVersion" )); |
| 50 | const QString PI_QtBuildMode(QLS("QtBuildMode" )); |
| 51 | const QString PI_GitCommit(QLS("GitCommit" )); |
| 52 | const QString PI_QMakeSpec(QLS("QMakeSpec" )); |
| 53 | const QString PI_PulseGitBranch(QLS("PulseGitBranch" )); |
| 54 | const QString PI_PulseTestrBranch(QLS("PulseTestrBranch" )); |
| 55 | |
| 56 | #ifndef QMAKESPEC |
| 57 | #define QMAKESPEC "Unknown" |
| 58 | #endif |
| 59 | |
| 60 | #if defined(Q_OS_WIN) |
| 61 | #include <QtCore/qt_windows.h> |
| 62 | #endif |
| 63 | #if defined(Q_OS_UNIX) |
| 64 | #include <time.h> |
| 65 | #endif |
| 66 | void BaselineProtocol::sysSleep(int ms) |
| 67 | { |
| 68 | #if defined(Q_OS_WIN) |
| 69 | # ifndef Q_OS_WINRT |
| 70 | Sleep(DWORD(ms)); |
| 71 | # else |
| 72 | WaitForSingleObjectEx(GetCurrentThread(), ms, false); |
| 73 | # endif |
| 74 | #else |
| 75 | struct timespec ts = { .tv_sec: ms / 1000, .tv_nsec: (ms % 1000) * 1000 * 1000 }; |
| 76 | nanosleep(requested_time: &ts, NULL); |
| 77 | #endif |
| 78 | } |
| 79 | |
| 80 | PlatformInfo::PlatformInfo() |
| 81 | : QMap<QString, QString>(), adHoc(true) |
| 82 | { |
| 83 | } |
| 84 | |
| 85 | PlatformInfo PlatformInfo::localHostInfo() |
| 86 | { |
| 87 | PlatformInfo pi; |
| 88 | pi.insert(akey: PI_HostName, avalue: QHostInfo::localHostName()); |
| 89 | pi.insert(akey: PI_QtVersion, QLS(qVersion())); |
| 90 | pi.insert(akey: PI_QMakeSpec, avalue: QString(QLS(QMAKESPEC)).remove(rx: QRegExp(QLS("^.*mkspecs/" )))); |
| 91 | #if QT_VERSION >= 0x050000 |
| 92 | pi.insert(akey: PI_QtBuildMode, avalue: QLibraryInfo::isDebugBuild() ? QLS("QtDebug" ) : QLS("QtRelease" )); |
| 93 | #endif |
| 94 | #if defined(Q_OS_LINUX) && QT_CONFIG(process) |
| 95 | pi.insert(akey: PI_OSName, QLS("Linux" )); |
| 96 | #elif defined(Q_OS_WIN) |
| 97 | pi.insert(PI_OSName, QLS("Windows" )); |
| 98 | #elif defined(Q_OS_DARWIN) |
| 99 | pi.insert(PI_OSName, QLS("Darwin" )); |
| 100 | #else |
| 101 | pi.insert(PI_OSName, QLS("Other" )); |
| 102 | #endif |
| 103 | pi.insert(akey: PI_OSVersion, avalue: QSysInfo::kernelVersion()); |
| 104 | |
| 105 | #if QT_CONFIG(process) |
| 106 | QProcess git; |
| 107 | QString cmd; |
| 108 | QStringList args; |
| 109 | #if defined(Q_OS_WIN) |
| 110 | cmd = QLS("cmd.exe" ); |
| 111 | args << QLS("/c" ) << QLS("git" ); |
| 112 | #else |
| 113 | cmd = QLS("git" ); |
| 114 | #endif |
| 115 | args << QLS("log" ) << QLS("--max-count=1" ) << QLS("--pretty=%H [%an] [%ad] %s" ); |
| 116 | git.start(program: cmd, arguments: args); |
| 117 | git.waitForFinished(msecs: 3000); |
| 118 | if (!git.exitCode()) |
| 119 | pi.insert(akey: PI_GitCommit, avalue: QString::fromLocal8Bit(str: git.readAllStandardOutput().constData()).simplified()); |
| 120 | else |
| 121 | pi.insert(akey: PI_GitCommit, QLS("Unknown" )); |
| 122 | |
| 123 | QByteArray gb = qgetenv(varName: "PULSE_GIT_BRANCH" ); |
| 124 | if (!gb.isEmpty()) { |
| 125 | pi.insert(akey: PI_PulseGitBranch, avalue: QString::fromLatin1(str: gb)); |
| 126 | pi.setAdHocRun(false); |
| 127 | } |
| 128 | QByteArray tb = qgetenv(varName: "PULSE_TESTR_BRANCH" ); |
| 129 | if (!tb.isEmpty()) { |
| 130 | pi.insert(akey: PI_PulseTestrBranch, avalue: QString::fromLatin1(str: tb)); |
| 131 | pi.setAdHocRun(false); |
| 132 | } |
| 133 | if (!qgetenv(varName: "JENKINS_HOME" ).isEmpty()) { |
| 134 | pi.setAdHocRun(false); |
| 135 | gb = qgetenv(varName: "GIT_BRANCH" ); |
| 136 | if (!gb.isEmpty()) { |
| 137 | // FIXME: the string "Pulse" should be eliminated, since that is not the used tool. |
| 138 | pi.insert(akey: PI_PulseGitBranch, avalue: QString::fromLatin1(str: gb)); |
| 139 | } |
| 140 | } |
| 141 | #endif // QT_CONFIG(process) |
| 142 | |
| 143 | return pi; |
| 144 | } |
| 145 | |
| 146 | |
| 147 | PlatformInfo::PlatformInfo(const PlatformInfo &other) |
| 148 | : QMap<QString, QString>(other) |
| 149 | { |
| 150 | orides = other.orides; |
| 151 | adHoc = other.adHoc; |
| 152 | } |
| 153 | |
| 154 | |
| 155 | PlatformInfo &PlatformInfo::operator=(const PlatformInfo &other) |
| 156 | { |
| 157 | QMap<QString, QString>::operator=(other); |
| 158 | orides = other.orides; |
| 159 | adHoc = other.adHoc; |
| 160 | return *this; |
| 161 | } |
| 162 | |
| 163 | |
| 164 | void PlatformInfo::addOverride(const QString& key, const QString& value) |
| 165 | { |
| 166 | orides.append(t: key); |
| 167 | orides.append(t: value); |
| 168 | } |
| 169 | |
| 170 | |
| 171 | QStringList PlatformInfo::overrides() const |
| 172 | { |
| 173 | return orides; |
| 174 | } |
| 175 | |
| 176 | |
| 177 | void PlatformInfo::setAdHocRun(bool isAdHoc) |
| 178 | { |
| 179 | adHoc = isAdHoc; |
| 180 | } |
| 181 | |
| 182 | |
| 183 | bool PlatformInfo::isAdHocRun() const |
| 184 | { |
| 185 | return adHoc; |
| 186 | } |
| 187 | |
| 188 | |
| 189 | QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pi) |
| 190 | { |
| 191 | stream << static_cast<const QMap<QString, QString>&>(pi); |
| 192 | stream << pi.orides << pi.adHoc; |
| 193 | return stream; |
| 194 | } |
| 195 | |
| 196 | |
| 197 | QDataStream & operator>> (QDataStream &stream, PlatformInfo &pi) |
| 198 | { |
| 199 | stream >> static_cast<QMap<QString, QString>&>(pi); |
| 200 | stream >> pi.orides >> pi.adHoc; |
| 201 | return stream; |
| 202 | } |
| 203 | |
| 204 | |
| 205 | ImageItem &ImageItem::operator=(const ImageItem &other) |
| 206 | { |
| 207 | testFunction = other.testFunction; |
| 208 | itemName = other.itemName; |
| 209 | itemChecksum = other.itemChecksum; |
| 210 | status = other.status; |
| 211 | image = other.image; |
| 212 | imageChecksums = other.imageChecksums; |
| 213 | return *this; |
| 214 | } |
| 215 | |
| 216 | // Defined in lookup3.c: |
| 217 | void hashword2 ( |
| 218 | const quint32 *k, /* the key, an array of quint32 values */ |
| 219 | size_t length, /* the length of the key, in quint32s */ |
| 220 | quint32 *pc, /* IN: seed OUT: primary hash value */ |
| 221 | quint32 *pb); /* IN: more seed OUT: secondary hash value */ |
| 222 | |
| 223 | quint64 ImageItem::computeChecksum(const QImage &image) |
| 224 | { |
| 225 | QImage img(image); |
| 226 | const int bpl = img.bytesPerLine(); |
| 227 | const int padBytes = bpl - (img.width() * img.depth() / 8); |
| 228 | if (padBytes) { |
| 229 | uchar *p = img.bits() + bpl - padBytes; |
| 230 | const int h = img.height(); |
| 231 | for (int y = 0; y < h; ++y) { |
| 232 | memset(s: p, c: 0, n: padBytes); |
| 233 | p += bpl; |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | quint32 h1 = 0xfeedbacc; |
| 238 | quint32 h2 = 0x21604894; |
| 239 | hashword2(k: (const quint32 *)img.constBits(), length: img.sizeInBytes()/4, pc: &h1, pb: &h2); |
| 240 | return (quint64(h1) << 32) | h2; |
| 241 | } |
| 242 | |
| 243 | #if 0 |
| 244 | QString ImageItem::engineAsString() const |
| 245 | { |
| 246 | switch (engine) { |
| 247 | case Raster: |
| 248 | return QLS("Raster" ); |
| 249 | break; |
| 250 | case OpenGL: |
| 251 | return QLS("OpenGL" ); |
| 252 | break; |
| 253 | default: |
| 254 | break; |
| 255 | } |
| 256 | return QLS("Unknown" ); |
| 257 | } |
| 258 | |
| 259 | QString ImageItem::formatAsString() const |
| 260 | { |
| 261 | static const int numFormats = 16; |
| 262 | static const char *formatNames[numFormats] = { |
| 263 | "Invalid" , |
| 264 | "Mono" , |
| 265 | "MonoLSB" , |
| 266 | "Indexed8" , |
| 267 | "RGB32" , |
| 268 | "ARGB32" , |
| 269 | "ARGB32-Premult" , |
| 270 | "RGB16" , |
| 271 | "ARGB8565-Premult" , |
| 272 | "RGB666" , |
| 273 | "ARGB6666-Premult" , |
| 274 | "RGB555" , |
| 275 | "ARGB8555-Premult" , |
| 276 | "RGB888" , |
| 277 | "RGB444" , |
| 278 | "ARGB4444-Premult" |
| 279 | }; |
| 280 | if (renderFormat < 0 || renderFormat >= numFormats) |
| 281 | return QLS("UnknownFormat" ); |
| 282 | return QLS(formatNames[renderFormat]); |
| 283 | } |
| 284 | #endif |
| 285 | |
| 286 | void ImageItem::writeImageToStream(QDataStream &out) const |
| 287 | { |
| 288 | if (image.isNull() || image.format() == QImage::Format_Invalid) { |
| 289 | out << quint8(0); |
| 290 | return; |
| 291 | } |
| 292 | out << quint8('Q') << quint8(image.format()); |
| 293 | out << quint8(QSysInfo::ByteOrder) << quint8(0); // pad to multiple of 4 bytes |
| 294 | out << quint32(image.width()) << quint32(image.height()) << quint32(image.bytesPerLine()); |
| 295 | out << qCompress(data: reinterpret_cast<const uchar *>(image.constBits()), |
| 296 | nbytes: int(image.sizeInBytes())); |
| 297 | //# can be followed by colormap for formats that use it |
| 298 | } |
| 299 | |
| 300 | void ImageItem::readImageFromStream(QDataStream &in) |
| 301 | { |
| 302 | quint8 hdr, fmt, endian, pad; |
| 303 | quint32 width, height, bpl; |
| 304 | QByteArray data; |
| 305 | |
| 306 | in >> hdr; |
| 307 | if (hdr != 'Q') { |
| 308 | image = QImage(); |
| 309 | return; |
| 310 | } |
| 311 | in >> fmt >> endian >> pad; |
| 312 | if (!fmt || fmt >= QImage::NImageFormats) { |
| 313 | image = QImage(); |
| 314 | return; |
| 315 | } |
| 316 | if (endian != QSysInfo::ByteOrder) { |
| 317 | qWarning(msg: "ImageItem cannot read streamed image with different endianness" ); |
| 318 | image = QImage(); |
| 319 | return; |
| 320 | } |
| 321 | in >> width >> height >> bpl; |
| 322 | in >> data; |
| 323 | data = qUncompress(data); |
| 324 | QImage res((const uchar *)data.constData(), width, height, bpl, QImage::Format(fmt)); |
| 325 | image = res.copy(); //# yuck, seems there is currently no way to avoid data copy |
| 326 | } |
| 327 | |
| 328 | QDataStream & operator<< (QDataStream &stream, const ImageItem &ii) |
| 329 | { |
| 330 | stream << ii.testFunction << ii.itemName << ii.itemChecksum << quint8(ii.status) << ii.imageChecksums << ii.misc; |
| 331 | ii.writeImageToStream(out&: stream); |
| 332 | return stream; |
| 333 | } |
| 334 | |
| 335 | QDataStream & operator>> (QDataStream &stream, ImageItem &ii) |
| 336 | { |
| 337 | quint8 encStatus; |
| 338 | stream >> ii.testFunction >> ii.itemName >> ii.itemChecksum >> encStatus >> ii.imageChecksums >> ii.misc; |
| 339 | ii.status = ImageItem::ItemStatus(encStatus); |
| 340 | ii.readImageFromStream(in&: stream); |
| 341 | return stream; |
| 342 | } |
| 343 | |
| 344 | BaselineProtocol::BaselineProtocol() |
| 345 | { |
| 346 | } |
| 347 | |
| 348 | BaselineProtocol::~BaselineProtocol() |
| 349 | { |
| 350 | disconnect(); |
| 351 | } |
| 352 | |
| 353 | bool BaselineProtocol::disconnect() |
| 354 | { |
| 355 | socket.close(); |
| 356 | return (socket.state() == QTcpSocket::UnconnectedState) ? true : socket.waitForDisconnected(msecs: Timeout); |
| 357 | } |
| 358 | |
| 359 | |
| 360 | bool BaselineProtocol::connect(const QString &testCase, bool *dryrun, const PlatformInfo& clientInfo) |
| 361 | { |
| 362 | errMsg.clear(); |
| 363 | QByteArray serverName(qgetenv(varName: "QT_LANCELOT_SERVER" )); |
| 364 | if (serverName.isNull()) |
| 365 | serverName = "lancelot.test.qt-project.org" ; |
| 366 | |
| 367 | socket.connectToHost(hostName: serverName, port: ServerPort); |
| 368 | if (!socket.waitForConnected(msecs: Timeout)) { |
| 369 | sysSleep(ms: 3000); // Wait a bit and try again, the server might just be restarting |
| 370 | if (!socket.waitForConnected(msecs: Timeout)) { |
| 371 | errMsg += QLS("TCP connectToHost failed. Host:" ) + QLS(serverName) + QLS(" port:" ) + QString::number(ServerPort); |
| 372 | return false; |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | PlatformInfo pi = clientInfo.isEmpty() ? PlatformInfo::localHostInfo() : clientInfo; |
| 377 | pi.insert(akey: PI_TestCase, avalue: testCase); |
| 378 | QByteArray block; |
| 379 | QDataStream ds(&block, QIODevice::ReadWrite); |
| 380 | ds << pi; |
| 381 | if (!sendBlock(cmd: AcceptPlatformInfo, block)) { |
| 382 | errMsg += QLS("Failed to send data to server." ); |
| 383 | return false; |
| 384 | } |
| 385 | |
| 386 | Command cmd = UnknownError; |
| 387 | if (!receiveBlock(cmd: &cmd, block: &block)) { |
| 388 | errMsg.prepend(QLS("Failed to get response from server. " )); |
| 389 | return false; |
| 390 | } |
| 391 | |
| 392 | if (cmd == Abort) { |
| 393 | errMsg += QLS("Server rejected connection. Reason: " ) + QString::fromLatin1(str: block); |
| 394 | return false; |
| 395 | } |
| 396 | |
| 397 | if (dryrun) |
| 398 | *dryrun = (cmd == DoDryRun); |
| 399 | |
| 400 | if (cmd != Ack && cmd != DoDryRun) { |
| 401 | errMsg += QLS("Unexpected response from server." ); |
| 402 | return false; |
| 403 | } |
| 404 | |
| 405 | return true; |
| 406 | } |
| 407 | |
| 408 | |
| 409 | bool BaselineProtocol::acceptConnection(PlatformInfo *pi) |
| 410 | { |
| 411 | errMsg.clear(); |
| 412 | |
| 413 | QByteArray block; |
| 414 | Command cmd = AcceptPlatformInfo; |
| 415 | if (!receiveBlock(cmd: &cmd, block: &block) || cmd != AcceptPlatformInfo) |
| 416 | return false; |
| 417 | |
| 418 | if (pi) { |
| 419 | QDataStream ds(block); |
| 420 | ds >> *pi; |
| 421 | pi->insert(akey: PI_HostAddress, avalue: socket.peerAddress().toString()); |
| 422 | } |
| 423 | |
| 424 | return true; |
| 425 | } |
| 426 | |
| 427 | |
| 428 | bool BaselineProtocol::requestBaselineChecksums(const QString &testFunction, ImageItemList *itemList) |
| 429 | { |
| 430 | errMsg.clear(); |
| 431 | if (!itemList) |
| 432 | return false; |
| 433 | |
| 434 | for(ImageItemList::iterator it = itemList->begin(); it != itemList->end(); it++) |
| 435 | it->testFunction = testFunction; |
| 436 | |
| 437 | QByteArray block; |
| 438 | QDataStream ds(&block, QIODevice::WriteOnly); |
| 439 | ds << *itemList; |
| 440 | if (!sendBlock(cmd: RequestBaselineChecksums, block)) |
| 441 | return false; |
| 442 | |
| 443 | Command cmd; |
| 444 | QByteArray rcvBlock; |
| 445 | if (!receiveBlock(cmd: &cmd, block: &rcvBlock) || cmd != BaselineProtocol::Ack) |
| 446 | return false; |
| 447 | QDataStream rds(&rcvBlock, QIODevice::ReadOnly); |
| 448 | rds >> *itemList; |
| 449 | return true; |
| 450 | } |
| 451 | |
| 452 | |
| 453 | bool BaselineProtocol::submitMatch(const ImageItem &item, QByteArray *serverMsg) |
| 454 | { |
| 455 | Command cmd; |
| 456 | ImageItem smallItem = item; |
| 457 | smallItem.image = QImage(); // No need to waste bandwith sending image (identical to baseline) to server |
| 458 | return (sendItem(cmd: AcceptMatch, item: smallItem) && receiveBlock(cmd: &cmd, block: serverMsg) && cmd == Ack); |
| 459 | } |
| 460 | |
| 461 | |
| 462 | bool BaselineProtocol::submitNewBaseline(const ImageItem &item, QByteArray *serverMsg) |
| 463 | { |
| 464 | Command cmd; |
| 465 | return (sendItem(cmd: AcceptNewBaseline, item) && receiveBlock(cmd: &cmd, block: serverMsg) && cmd == Ack); |
| 466 | } |
| 467 | |
| 468 | |
| 469 | bool BaselineProtocol::submitMismatch(const ImageItem &item, QByteArray *serverMsg, bool *fuzzyMatch) |
| 470 | { |
| 471 | Command cmd; |
| 472 | if (sendItem(cmd: AcceptMismatch, item) && receiveBlock(cmd: &cmd, block: serverMsg) && (cmd == Ack || cmd == FuzzyMatch)) { |
| 473 | if (fuzzyMatch) |
| 474 | *fuzzyMatch = (cmd == FuzzyMatch); |
| 475 | return true; |
| 476 | } |
| 477 | return false; |
| 478 | } |
| 479 | |
| 480 | |
| 481 | bool BaselineProtocol::sendItem(Command cmd, const ImageItem &item) |
| 482 | { |
| 483 | errMsg.clear(); |
| 484 | QBuffer buf; |
| 485 | buf.open(openMode: QIODevice::WriteOnly); |
| 486 | QDataStream ds(&buf); |
| 487 | ds << item; |
| 488 | if (!sendBlock(cmd, block: buf.data())) { |
| 489 | errMsg.prepend(QLS("Failed to submit image to server. " )); |
| 490 | return false; |
| 491 | } |
| 492 | return true; |
| 493 | } |
| 494 | |
| 495 | |
| 496 | bool BaselineProtocol::sendBlock(Command cmd, const QByteArray &block) |
| 497 | { |
| 498 | QDataStream s(&socket); |
| 499 | // TBD: set qds version as a constant |
| 500 | s << quint16(ProtocolVersion) << quint16(cmd); |
| 501 | s.writeBytes(block.constData(), len: block.size()); |
| 502 | return true; |
| 503 | } |
| 504 | |
| 505 | |
| 506 | bool BaselineProtocol::receiveBlock(Command *cmd, QByteArray *block) |
| 507 | { |
| 508 | while (socket.bytesAvailable() < int(2*sizeof(quint16) + sizeof(quint32))) { |
| 509 | if (!socket.waitForReadyRead(msecs: Timeout)) |
| 510 | return false; |
| 511 | } |
| 512 | QDataStream ds(&socket); |
| 513 | quint16 rcvProtocolVersion, rcvCmd; |
| 514 | ds >> rcvProtocolVersion >> rcvCmd; |
| 515 | if (rcvProtocolVersion != ProtocolVersion) { |
| 516 | errMsg = QLS("Baseline protocol version mismatch, received:" ) + QString::number(rcvProtocolVersion) |
| 517 | + QLS(" expected:" ) + QString::number(ProtocolVersion); |
| 518 | return false; |
| 519 | } |
| 520 | if (cmd) |
| 521 | *cmd = Command(rcvCmd); |
| 522 | |
| 523 | QByteArray uMsg; |
| 524 | quint32 remaining; |
| 525 | ds >> remaining; |
| 526 | uMsg.resize(size: remaining); |
| 527 | int got = 0; |
| 528 | char* uMsgBuf = uMsg.data(); |
| 529 | do { |
| 530 | got = ds.readRawData(uMsgBuf, len: remaining); |
| 531 | remaining -= got; |
| 532 | uMsgBuf += got; |
| 533 | } while (remaining && got >= 0 && socket.waitForReadyRead(msecs: Timeout)); |
| 534 | |
| 535 | if (got < 0) |
| 536 | return false; |
| 537 | |
| 538 | if (block) |
| 539 | *block = uMsg; |
| 540 | |
| 541 | return true; |
| 542 | } |
| 543 | |
| 544 | |
| 545 | QString BaselineProtocol::errorMessage() |
| 546 | { |
| 547 | QString ret = errMsg; |
| 548 | if (socket.error() >= 0) |
| 549 | ret += QLS(" Socket state: " ) + socket.errorString(); |
| 550 | return ret; |
| 551 | } |
| 552 | |
| 553 | |