From 88b77ba8384f7b5493b830ef7b8c36c8753ce884 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Thu, 9 Dec 2010 09:49:23 +0100 Subject: [PATCH] Symbian CODA: Add ping for serial, handle reading long messages. Some renaming and cleanup. --- src/shared/symbianutils/tcftrkdevice.cpp | 95 +++++++++++++------ src/shared/symbianutils/tcftrkdevice.h | 5 +- .../codaclient/codaclientapplication.cpp | 74 ++++++++++++--- .../tools/codaclient/codaclientapplication.h | 4 +- 4 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/shared/symbianutils/tcftrkdevice.cpp b/src/shared/symbianutils/tcftrkdevice.cpp index 17d0fd8ee37..5876c5f08de 100644 --- a/src/shared/symbianutils/tcftrkdevice.cpp +++ b/src/shared/symbianutils/tcftrkdevice.cpp @@ -41,7 +41,7 @@ enum { debug = 0 }; -static const char wlanMessageTerminatorC[] = "\003\001"; +static const char tcpMessageTerminatorC[] = "\003\001"; // Serial Ping: 0xfc,0x1f static const char serialPingC[] = "\xfc\x1f"; @@ -50,8 +50,15 @@ static const char serialPongC[] = "\xfc\xf1"; static const char locatorAnswerC[] = "E\0Locator\0Hello\0[\"Locator\"]"; +/* Serial messages > (1K - 2) have to chunked in order to pass the USB + * router as '0xfe char(chunkCount - 1) data' ... '0x0 char(chunkCount - 2) data' + * ... '0x0 0x0 last-data' */ static const unsigned serialChunkLength = 0x400; // 1K max USB router -static const int maxSerialMessageLength = 0x10000; // give chunking scheme +static const int maxSerialMessageLength = 0x10000; // given chunking scheme + +static const unsigned char serialChunkingStart = 0xfe; +static const unsigned char serialChunkingContinuation = 0x0; +enum { SerialChunkHeaderSize = 2 }; // Create USB router frame static inline void encodeSerialFrame(const QByteArray &data, QByteArray *target) @@ -65,7 +72,8 @@ static inline void encodeSerialFrame(const QByteArray &data, QByteArray *target) // Split in chunks of 1K according to CODA protocol chunking static inline QByteArray encodeUsbSerialMessage(const QByteArray &dataIn) { - static const int chunkSize = serialChunkLength - 2; // 2 Header bytes + // Reserve 2 header bytes + static const int chunkSize = serialChunkLength - SerialChunkHeaderSize; const int size = dataIn.size(); QByteArray frame; // Do we need to split? @@ -86,7 +94,7 @@ static inline QByteArray encodeUsbSerialMessage(const QByteArray &dataIn) for (unsigned c = chunkCount - 1; pos < size ; c--) { QByteArray chunk; // chunk with long message start/continuation code chunk.reserve(serialChunkLength); - chunk.append(pos ? char(0) : char(0xfe)); + chunk.append(pos ? serialChunkingContinuation : serialChunkingStart); chunk.append(char(static_cast<unsigned char>(c))); // Avoid any signedness issues. const int chunkEnd = qMin(pos + chunkSize, size); chunk.append(dataIn.mid(pos, chunkEnd - pos)); @@ -318,22 +326,24 @@ struct TcfTrkDevicePrivate { TcfTrkDevicePrivate(); - const QByteArray m_messageTerminator; + const QByteArray m_tcpMessageTerminator; IODevicePtr m_device; unsigned m_verbose; QByteArray m_readBuffer; + QByteArray m_serialBuffer; // for chunked messages int m_token; QQueue<TcfTrkSendQueueEntry> m_sendQueue; TokenWrittenMessageMap m_writtenMessages; QVector<QByteArray> m_registerNames; QVector<QByteArray> m_fakeGetMRegisterValues; bool m_serialFrame; + bool m_serialPingOnly; }; TcfTrkDevicePrivate::TcfTrkDevicePrivate() : - m_messageTerminator(wlanMessageTerminatorC), - m_verbose(0), m_token(0), m_serialFrame(false) + m_tcpMessageTerminator(tcpMessageTerminatorC), + m_verbose(0), m_token(0), m_serialFrame(false), m_serialPingOnly(false) { } @@ -457,7 +467,7 @@ void TcfTrkDevice::slotDeviceReadyRead() if (d->m_serialFrame) { deviceReadyReadSerial(); } else { - deviceReadyReadWLAN(); + deviceReadyReadTcp(); } } @@ -496,31 +506,59 @@ void TcfTrkDevice::deviceReadyReadSerial() const int messageEnd = messagePos.first + messagePos.second; if (messageEnd > d->m_readBuffer.size()) break; - const QByteArray message = d->m_readBuffer.mid(messagePos.first, messagePos.second); - // Is thing a ping/pong response - if (debug > 1) - qDebug("Serial message: at %d (%d bytes) of %d: %s", - messagePos.first, messagePos.second, d->m_readBuffer.size(), - qPrintable(trk::stringFromArray(message))); - if (message.startsWith(serialPongC)) { - const QString version = QString::fromLatin1(message.mid(sizeof(serialPongC) - 1)); - emitLogMessage(QString::fromLatin1("Serial connection from '%1'").arg(version)); - emit serialPong(version); - // Answer with locator. - writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC))); - } else { - processMessage(message); - } + processSerialMessage(d->m_readBuffer.mid(messagePos.first, messagePos.second)); d->m_readBuffer.remove(0, messageEnd); } while (d->m_readBuffer.isEmpty()); checkSendQueue(); // Send off further messages } -void TcfTrkDevice::deviceReadyReadWLAN() +void TcfTrkDevice::processSerialMessage(const QByteArray &message) +{ + if (debug > 1) + qDebug("Serial message: %s",qPrintable(trk::stringFromArray(message))); + if (message.isEmpty()) + return; + // Is thing a ping/pong response + const int size = message.size(); + if (message.startsWith(serialPongC)) { + const QString version = QString::fromLatin1(message.mid(sizeof(serialPongC) - 1)); + emitLogMessage(QString::fromLatin1("Serial connection from '%1'").arg(version)); + emit serialPong(version); + // Answer with locator. + if (!d->m_serialPingOnly) + writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC))); + return; + } + // Check for long message (see top, '0xfe #number, data' or '0x0 #number, data') + // TODO: This is currently untested. + const unsigned char *dataU = reinterpret_cast<const unsigned char *>(message.constData()); + const bool isLongMessageStart = size > SerialChunkHeaderSize + && *dataU == serialChunkingStart; + const bool isLongMessageContinuation = size > SerialChunkHeaderSize + && *dataU == serialChunkingContinuation; + if (isLongMessageStart || isLongMessageContinuation) { + const unsigned chunkNumber = *++dataU; + if (isLongMessageStart) { // Start new buffer + d->m_serialBuffer.clear(); + d->m_serialBuffer.reserve( (chunkNumber + 1) * serialChunkLength); + } + d->m_serialBuffer.append(message.mid(SerialChunkHeaderSize, size - SerialChunkHeaderSize)); + // Last chunk? - Process + if (!chunkNumber) { + processMessage(d->m_serialBuffer); + d->m_serialBuffer.clear(); + d->m_serialBuffer.squeeze(); + } + } else { + processMessage(message); // Normal, unchunked message + } +} + +void TcfTrkDevice::deviceReadyReadTcp() { // Take complete message off front of readbuffer. do { - const int messageEndPos = d->m_readBuffer.indexOf(d->m_messageTerminator); + const int messageEndPos = d->m_readBuffer.indexOf(d->m_tcpMessageTerminator); if (messageEndPos == -1) break; if (messageEndPos == 0) { @@ -529,7 +567,7 @@ void TcfTrkDevice::deviceReadyReadWLAN() } else { processMessage(d->m_readBuffer.left(messageEndPos)); } - d->m_readBuffer.remove(0, messageEndPos + d->m_messageTerminator.size()); + d->m_readBuffer.remove(0, messageEndPos + d->m_tcpMessageTerminator.size()); } while (!d->m_readBuffer.isEmpty()); checkSendQueue(); // Send off further messages } @@ -767,11 +805,12 @@ bool TcfTrkDevice::checkOpen() return true; } -void TcfTrkDevice::sendSerialPing() +void TcfTrkDevice::sendSerialPing(bool pingOnly) { if (!checkOpen()) return; + d->m_serialPingOnly = pingOnly; setSerialFrame(true); writeMessage(QByteArray(serialPingC, qstrlen(serialPingC)), false); if (d->m_verbose) @@ -847,7 +886,7 @@ void TcfTrkDevice::writeMessage(QByteArray data, bool ensureTerminating0) if (d->m_serialFrame) { data = encodeUsbSerialMessage(data); } else { - data += d->m_messageTerminator; + data += d->m_tcpMessageTerminator; } if (debug > 1) diff --git a/src/shared/symbianutils/tcftrkdevice.h b/src/shared/symbianutils/tcftrkdevice.h index 4ab5fbbda01..3cf2045a7ea 100644 --- a/src/shared/symbianutils/tcftrkdevice.h +++ b/src/shared/symbianutils/tcftrkdevice.h @@ -186,7 +186,7 @@ public: void setDevice(const IODevicePtr &dp); // Serial Only: Initiate communication. Will emit serialPong() signal with version. - void sendSerialPing(); + void sendSerialPing(bool pingOnly = false); // Send with parameters from string (which may contain '\0'). void sendTcfTrkMessage(MessageType mt, Services service, @@ -367,7 +367,7 @@ private slots: private: void deviceReadyReadSerial(); - void deviceReadyReadWLAN(); + void deviceReadyReadTcp(); bool checkOpen(); void checkSendQueue(); @@ -375,6 +375,7 @@ private: void emitLogMessage(const QString &); inline int parseMessage(const QByteArray &); void processMessage(const QByteArray &message); + inline void processSerialMessage(const QByteArray &message); int parseTcfCommandReply(char type, const QVector<QByteArray> &tokens); int parseTcfEvent(const QVector<QByteArray> &tokens); // Send with parameters from string (which may contain '\0'). diff --git a/tests/tools/codaclient/codaclientapplication.cpp b/tests/tools/codaclient/codaclientapplication.cpp index aa12dd010a9..00918440227 100644 --- a/tests/tools/codaclient/codaclientapplication.cpp +++ b/tests/tools/codaclient/codaclientapplication.cpp @@ -44,11 +44,13 @@ static const char usageC[] = "\n%1 v0.1 built "__DATE__"\n\n" "Test client for Symbian CODA\n\n" "Usage:\n" -"%1 launch [-d] address[:port] binary uid [--] [arguments]\n" -"%1 install[-s] address[:port] remote-sis-file [targetdrive]\n" -"%1 put [c size] address[:port] local-file remote-file\n" -"%1 stat address[:port] remote-file\n" -"\nOptions:\n" +"%1 ping connection Note: For serial connections ONLY.\n" +"%1 launch [-d] connection binary uid [--] [arguments]\n" +"%1 install[-s] connection remote-sis-file [targetdrive]\n" +"%1 put [c size] connection local-file remote-file\n" +"%1 stat connection remote-file\n\n" +"'connection': address[:port] or serial-port\n\n" +"Options:\n" "-d Launch: Launch under Debug control (wait for termination)\n" "-c [size] Put: Chunk size in KB (default %2KB)\n" "-s Install: Silent installation\n\n" @@ -59,11 +61,20 @@ static const char usageC[] = "%1 put 192.168.0.42 test.sis c:\\data\\test.sis\n" "%1 stat 192.168.0.42 c:\\data\\test.sis\n" "%1 install 192.168.0.42 c:\\data\\test.sis c:\n" -"%1 launch 192.168.0.42 c:\\sys\\bin\\test.exe 0x34f2b\n"; +"%1 launch 192.168.0.42 c:\\sys\\bin\\test.exe 0x34f2b\n" +"%1 ping /dev/ttyUSB1\n"; static const unsigned short defaultPort = 65029; static const quint64 defaultChunkSize = 10240; +static inline bool isSerialPort(const QString &address) +{ + return address.startsWith(QLatin1String("/dev")) + || address.startsWith(QLatin1String("com"), Qt::CaseInsensitive) + || address.startsWith(QLatin1String("tty"), Qt::CaseInsensitive) + || address.startsWith(QLatin1Char('\\')); +} + static inline QString fixSlashes(QString s) { s.replace(QLatin1Char('/'), QLatin1Char('\\')); @@ -102,6 +113,8 @@ static inline CodaClientApplication::Mode modeArg(const QString &a) { if (a == QLatin1String("launch")) return CodaClientApplication::Launch; + if (a == QLatin1String("ping")) + return CodaClientApplication::Ping; if (a == QLatin1String("install")) return CodaClientApplication::Install; if (a == QLatin1String("put")) @@ -229,6 +242,16 @@ CodaClientApplication::ParseArgsResult CodaClientApplication::parseArguments(QSt return ParseInitError; } break; + case Ping: + if (m_address.isEmpty()) { + *errorMessage = QString::fromLatin1("Not enough parameters for ping."); + return ParseInitError; + } + if (!isSerialPort(m_address)) { + *errorMessage = QString::fromLatin1("'ping' not supported for TCP/IP."); + return ParseInitError; + } + break; case Install: if (m_address.isEmpty() || m_installSisFile.isEmpty()) { *errorMessage = QString::fromLatin1("Not enough parameters for install."); @@ -257,6 +280,8 @@ bool CodaClientApplication::start() { m_startTime.start(); switch (m_mode) { + case Ping: + break; case Launch: { const QString args = m_launchArgs.join(QString(QLatin1Char(' '))); std::printf("Launching 0x%x '%s '%s' (debug: %d)\n", @@ -288,9 +313,9 @@ bool CodaClientApplication::start() this, SLOT(slotTrkLogMessage(QString))); connect(m_trkDevice.data(), SIGNAL(tcfEvent(tcftrk::TcfTrkEvent)), this, SLOT(slotTcftrkEvent(tcftrk::TcfTrkEvent))); - if (m_address.startsWith(QLatin1String("/dev")) - || m_address.startsWith(QLatin1String("com"), Qt::CaseInsensitive) - || m_address.startsWith(QLatin1Char('\\'))) { + connect(m_trkDevice.data(), SIGNAL(serialPong(QString)), + this, SLOT(slotSerialPong(QString))); + if (isSerialPort(m_address)) { #ifdef HAS_SERIALPORT // Serial #ifdef Q_OS_WIN @@ -317,7 +342,7 @@ bool CodaClientApplication::start() return false; } // Initiate communication - m_trkDevice->sendSerialPing(); + m_trkDevice->sendSerialPing(m_mode == Ping); serialPort->flush(); #else std::fprintf(stderr, "Not implemented\n"); @@ -341,6 +366,7 @@ void CodaClientApplication::slotError(const QString &e) void CodaClientApplication::slotTrkLogMessage(const QString &m) { + printTimeStamp(); std::printf("%s\n", qPrintable(m)); } @@ -348,6 +374,7 @@ void CodaClientApplication::handleCreateProcess(const tcftrk::TcfTrkCommandResul { const bool ok = result.type == tcftrk::TcfTrkCommandResult::SuccessReply; if (ok) { + printTimeStamp(); std::printf("Launch succeeded: %s\n", qPrintable(result.toString())); if (!m_launchDebug) doExit(0); @@ -398,6 +425,7 @@ void CodaClientApplication::putSendNextChunk() closeRemoteFile(); } else { m_putLastChunkSize = data.size(); + printTimeStamp(); std::printf("Writing %llu bytes to remote file '%s' at %llu\n", m_putLastChunkSize, m_remoteFileHandle.constData(), pos); @@ -431,6 +459,7 @@ void CodaClientApplication::handleFileSystemFStat(const tcftrk::TcfTrkCommandRes // Close remote file even if copy fails if (m_statFstatOk) { const tcftrk::TcfTrkStatResponse statr = tcftrk::TcfTrkDevice::parseStat(result); + printTimeStamp(); std::printf("File: %s\nSize: %llu bytes\nAccessed: %s\nModified: %s\n", qPrintable(m_statRemoteFile), statr.size, qPrintable(statr.accessTime.toString(Qt::LocalDate)), @@ -444,6 +473,7 @@ void CodaClientApplication::handleFileSystemFStat(const tcftrk::TcfTrkCommandRes void CodaClientApplication::handleFileSystemClose(const tcftrk::TcfTrkCommandResult &result) { if (result.type == tcftrk::TcfTrkCommandResult::SuccessReply) { + printTimeStamp(); std::printf("File closed.\n"); const bool ok = m_mode == Put ? m_putWriteOk : m_statFstatOk; doExit(ok ? 0 : -1); @@ -456,6 +486,7 @@ void CodaClientApplication::handleFileSystemClose(const tcftrk::TcfTrkCommandRes void CodaClientApplication::handleSymbianInstall(const tcftrk::TcfTrkCommandResult &result) { if (result.type == tcftrk::TcfTrkCommandResult::SuccessReply) { + printTimeStamp(); std::printf("Installation succeeded\n."); doExit(0); } else { @@ -466,10 +497,13 @@ void CodaClientApplication::handleSymbianInstall(const tcftrk::TcfTrkCommandResu void CodaClientApplication::slotTcftrkEvent (const tcftrk::TcfTrkEvent &ev) { + printTimeStamp(); std::printf("Event: %s\n", qPrintable(ev.toString())); switch (ev.type()) { case tcftrk::TcfTrkEvent::LocatorHello: // Commands accepted now switch (m_mode) { + case Ping: + break; case Launch: m_trkDevice->sendProcessStartCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleCreateProcess), m_launchBinary, m_launchUID, m_launchArgs, QString(), m_launchDebug); @@ -509,6 +543,7 @@ void CodaClientApplication::slotTcftrkEvent (const tcftrk::TcfTrkEvent &ev) const tcftrk::TcfTrkRunControlModuleLoadContextSuspendedEvent &me = static_cast<const tcftrk::TcfTrkRunControlModuleLoadContextSuspendedEvent &>(ev); if (me.info().requireResume) { + printTimeStamp(); std::printf("Continuing...\n"); m_trkDevice->sendRunControlResumeCommand(tcftrk::TcfTrkCallback(), me.id()); } @@ -522,10 +557,16 @@ void CodaClientApplication::slotTcftrkEvent (const tcftrk::TcfTrkEvent &ev) } } -void CodaClientApplication::doExit(int ex) +void CodaClientApplication::slotSerialPong(const QString &v) { - std::printf("Operation took %4.2f second(s)\n", m_startTime.elapsed()/1000.); + printTimeStamp(); + std::printf("Pong from '%s'\n", qPrintable(v)); + if (m_mode == Ping) + doExit(0); +} +void CodaClientApplication::doExit(int ex) +{ if (!m_trkDevice.isNull()) { const QSharedPointer<QIODevice> dev = m_trkDevice->device(); if (!dev.isNull()) { @@ -538,6 +579,15 @@ void CodaClientApplication::doExit(int ex) } } } + printTimeStamp(); std::printf("Exiting (%d)\n", ex); exit(ex); } + +void CodaClientApplication::printTimeStamp() +{ + const int elapsedMS = m_startTime.elapsed(); + const int secs = elapsedMS / 1000; + const int msecs = elapsedMS % 1000; + std::printf("%4d.%03ds: ", secs, msecs); +} diff --git a/tests/tools/codaclient/codaclientapplication.h b/tests/tools/codaclient/codaclientapplication.h index e5aa33d7045..5837efdbeb1 100644 --- a/tests/tools/codaclient/codaclientapplication.h +++ b/tests/tools/codaclient/codaclientapplication.h @@ -47,7 +47,7 @@ class CodaClientApplication : public QCoreApplication { Q_OBJECT public: - enum Mode { Invalid, Launch, Put, Stat, Install }; + enum Mode { Invalid, Ping, Launch, Put, Stat, Install }; explicit CodaClientApplication(int &argc, char **argv); ~CodaClientApplication(); @@ -63,8 +63,10 @@ private slots: void slotError(const QString &); void slotTrkLogMessage(const QString &); void slotTcftrkEvent(const tcftrk::TcfTrkEvent &); + void slotSerialPong(const QString &); private: + void printTimeStamp(); bool parseArgument(const QString &a, int argNumber, QString *errorMessage); void handleCreateProcess(const tcftrk::TcfTrkCommandResult &result); void handleFileSystemOpen(const tcftrk::TcfTrkCommandResult &result); -- GitLab