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