QueryHits.cpp

Go to the documentation of this file.
00001 /*
00002 
00003 Copyright (C) 2005-2007 by Peter Dimov.
00004 
00005 This file is part of Calitko (http://www.calitko.org).
00006 
00007 Calitko is free software; you can redistribute it and/or modify
00008 it under the terms of the GNU General Public License as published by
00009 the Free Software Foundation; either version 2 of the License, or
00010 (at your option) any later version.
00011 
00012 Calitko is distributed in the hope that it will be useful,
00013 but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Calitko; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 
00021 */
00022 
00023 #include "Qt.h"
00024 #include "QueryHits.h"
00025 
00026 using namespace Gnutella::Packets;
00027 
00028 bool findCString (const QByteArray &rawPayload, int startPos, int* len);
00029 
00030 enum QueryHitsDataFlags
00031 {
00032     GgepFlag            = 0x20,
00033     UploadSpeedFlag     = 0x10,
00034     HaveUploadedFlag    = 0x08,
00035     BusyFlag            = 0x04,
00036     PushFlag            = 0x01
00037 };
00038 
00042 QueryHits::QueryHits (const QByteArray &rawHeader, const QByteArray &rawPayload)
00043     : Packet (rawHeader, rawPayload)
00044 {
00045     parse();
00046 }
00047 
00048 QueryHits::QueryHits () : Packet (QueryHitsDescriptor)
00049 {
00050     p.numberOfHits  = 0;
00051     p.port          = 0;
00052     p.ipAddress     = QHostAddress ();
00053     p.speed         = 0;
00054     p.serventId     = QUuid();
00055 }
00056 
00057 QueryHits::~QueryHits()
00058 {
00059 }
00060 
00061 /* \todo Refac. Note -- I would like to use size_t instead of int */
00062 /* it seems more natural */
00063 bool findCString (const QByteArray &rawPayload, int startPos, int* len)
00064 {
00065     int endPos = rawPayload.indexOf ('\0', startPos);
00066     if (endPos == -1) /* No null terminator found? */
00067         return false;
00068     *len = endPos - startPos;
00069     return true;
00070 }
00071 
00072 bool QueryHits::prepareReadPayload (const QByteArray &rawPayload)
00073 {
00074     int payloadLength = rawPayload.length();
00075     if (payloadLength < 11) /* \todo What is 11 .. use a suitable const */
00076         return false;
00077 
00078     int currentPos = 11; // \todo literal constant? use a named one
00079     int numberOfHits = static_cast <uchar> (rawPayload.at(0));
00080     for (int i = 0; i < numberOfHits; i++) {
00081         p.resultSet.append (Result());
00082 
00083         Result& result = p.resultSet [i];
00084 
00085         currentPos += 8;
00086 
00087         int temp = 0;
00088         if (!findCString (rawPayload, currentPos, &temp))
00089                 return false;
00090         result.fileNameSize = temp;
00091         currentPos += result.fileNameSize + 1; /* skip past null */
00092 
00093         int dataLen = 0;
00094         if (!findCString (rawPayload, currentPos, &dataLen))
00095             return false;
00096         QByteArray rawResultData = QByteArray::fromRawData(
00097             rawPayload.data() + currentPos, dataLen);
00098         result.resultData.prepareRead (rawResultData);
00099         currentPos += dataLen + 1; /* skip past null */
00100     }
00101     payloadLength -= 16; // The serventId at the very end of the payload.
00102     if (currentPos > payloadLength)
00103         return false;
00104 
00105     int queryHitsDataSize = payloadLength - currentPos;
00106     QByteArray rawQueryHitsData = QByteArray::fromRawData (rawPayload.data() + currentPos, queryHitsDataSize);
00107     return prepareReadQueryHitsData (rawQueryHitsData);
00108 }
00109 
00110 void QueryHits::readPayload (QDataStream &stream)
00111 {
00112     QByteArray fileName;
00113     uchar temp;
00114 
00115     stream >> p.numberOfHits;
00116     stream >> p.port;
00117     Gnutella::Packets::operator>> (stream, p.ipAddress);
00118     stream >> p.speed;
00119     if (p.speed & (1<<15)) p.speed = 0;             // here eventually read the fields
00120 
00121     for (uchar i = 0; i < p.numberOfHits; i++) {
00122         Result &result = p.resultSet[i];
00123         stream >> result.fileIndex;
00124         stream >> result.fileSize;
00125 
00126         fileName.resize (result.fileNameSize);
00127         stream.readRawData (fileName.data(), result.fileNameSize);
00128         result.fullFileName = QString::fromLatin1 (fileName);
00129         fileName = QByteArray();
00130 
00131         stream >> temp; // '\0'
00132         result.resultData.read (stream);
00133         stream >> temp; // '\0'
00134     }
00135     readQueryHitsData (stream);
00136     stream >> p.serventId;
00137 }
00138 
00139 int QueryHits::prepareWritePayload() const
00140 {
00141     int payloadLength = 11;
00142 
00143     ResultSet::const_iterator it = p.resultSet.begin();
00144     for (uchar i = 0; i < p.numberOfHits && it != p.resultSet.end(); i++, it++) {
00145         const Result &result = *it;
00146 
00147         payloadLength += 8;
00148         payloadLength += result.fullFileName.length();
00149         payloadLength ++; // '\0'
00150         payloadLength += result.resultData.prepareWrite();
00151         payloadLength ++; // '\0'
00152     }
00153     payloadLength += prepareWriteQueryHitsData ();
00154     payloadLength += 16;
00155 
00156     return payloadLength;
00157 }
00158 
00159 void QueryHits::writePayload (QDataStream &stream) const
00160 {
00161     stream << p.numberOfHits;
00162     stream << p.port;
00163     Gnutella::Packets::operator<< (stream, p.ipAddress);
00164     stream << p.speed;
00165 
00166     ResultSet::const_iterator it = p.resultSet.begin();
00167     for (uchar i = 0; i < p.numberOfHits && it != p.resultSet.end(); i++, it++) {
00168         const Result &result = *it;
00169         stream << result.fileIndex;
00170         stream << result.fileSize;
00171         QByteArray fileName = result.fullFileName.toLatin1();
00172         stream.writeRawData (fileName.data(), fileName.length());
00173         stream << uchar (0);
00174         result.resultData.write (stream);
00175         stream << uchar (0);
00176     }
00177     writeQueryHitsData (stream);
00178     stream << p.serventId;
00179 }
00180 
00181 bool QueryHits::prepareReadQueryHitsData (const QByteArray &rawQueryHitsData)
00182 {
00183     int rawDataLength = rawQueryHitsData.length();
00184     // \todo That's just to read raw data until the implementation is tested.
00185     p.queryHitsData.xmlLength = rawDataLength;
00186     return true;
00187     int currentPos = 4;
00188 
00189     if (currentPos + 1 > rawDataLength)
00190         return false;
00191 
00192     int publicDataLength = rawQueryHitsData.at (currentPos++);
00193     if (currentPos + publicDataLength > rawDataLength)
00194         return false;
00195 
00196     p.queryHitsData.openAreaFlags1 = rawQueryHitsData.at (currentPos++);
00197     p.queryHitsData.openAreaFlags2 = rawQueryHitsData.at (currentPos++);
00198 
00199     if (publicDataLength >= 4) {
00200         p.queryHitsData.xmlLength = static_cast <uchar> (rawQueryHitsData.at (currentPos++));
00201         p.queryHitsData.xmlLength |= static_cast <uchar> (rawQueryHitsData.at (currentPos++)) << 8;
00202         rawDataLength -= p.queryHitsData.xmlLength; // The XML data is at the very end.
00203 
00204         // Jump right after the public area, where the GGEP block is expected to be.
00205         currentPos += publicDataLength - 4;
00206     } else
00207         p.queryHitsData.xmlLength = 0;
00208 
00209     currentPos++; // Private Vendor Flags.
00210 
00211     if ((p.queryHitsData.openAreaFlags1 & GgepFlag)
00212         && (p.queryHitsData.openAreaFlags2 & GgepFlag)) {
00213 
00214         if (currentPos + 1 > rawDataLength)
00215             return false;
00216 
00217         QByteArray ggepData = QByteArray::fromRawData (rawQueryHitsData.data() + currentPos, rawDataLength - currentPos);
00218         if (!p.queryHitsData.ggepBlock.prepareRead (ggepData))
00219             return false;
00220     }
00221     return true;
00222 }
00223 
00224 void QueryHits::readQueryHitsData (QDataStream &stream)
00225 {
00226     // \todo Still needs to be tested.
00227     p.queryHitsData.xmlData.resize (p.queryHitsData.xmlLength);
00228     stream.readRawData (p.queryHitsData.xmlData.data(), p.queryHitsData.xmlLength);
00229     return;
00230     stream >> p.queryHitsData.vendorCode;
00231     stream >> p.queryHitsData.openAreaSize;
00232 
00233     stream >> p.queryHitsData.openAreaFlags1;
00234     stream >> p.queryHitsData.openAreaFlags2;
00235 
00236     if (p.queryHitsData.xmlLength)
00237         stream >> p.queryHitsData.xmlLength;
00238 
00239     stream >> p.queryHitsData.privateVendorFlag;
00240 
00241     if ((p.queryHitsData.openAreaFlags1 & GgepFlag)
00242         && (p.queryHitsData.openAreaFlags2 & GgepFlag)) {
00243 
00244         p.queryHitsData.ggepBlock.read (stream);
00245     }
00246 
00247     if (p.queryHitsData.xmlLength > 0) {
00248         p.queryHitsData.xmlData.resize (p.queryHitsData.xmlLength - 1);
00249         stream.readRawData (p.queryHitsData.xmlData.data(), p.queryHitsData.xmlLength - 1);
00250         uchar tmp;
00251         stream >> tmp; // This is the engin zero.
00252     }
00253 }
00254 
00255 int QueryHits::prepareWriteQueryHitsData() const
00256 {
00257     int totalLength = 0;
00258 
00259     totalLength += VendorCode::VendorCodeLength; // Vendor code.
00260     totalLength += 1; // Open area size byte.
00261 
00262     p.queryHitsData.openAreaSize = 2;
00263     if (p.queryHitsData.xmlData.length()) {
00264         p.queryHitsData.openAreaSize += 2; // XML data length's 2 bytes.
00265         p.queryHitsData.xmlLength = p.queryHitsData.xmlData.length();
00266         p.queryHitsData.xmlLength += 1; // Ending zero.
00267     } else
00268         p.queryHitsData.xmlLength = 0;
00269 
00270     totalLength += p.queryHitsData.openAreaSize;
00271     totalLength += 1; // Private vendor flag;
00272 
00273     int ggepLength = p.queryHitsData.ggepBlock.prepareWrite();
00274     if (ggepLength > 0) {
00275         p.queryHitsData.openAreaFlags2 |= GgepFlag;
00276         // \todo When you add a constructor for local hits do not forget to
00277         // initialize the flags accordingly.
00278     }
00279 
00280     totalLength += ggepLength;
00281     totalLength += p.queryHitsData.xmlLength;
00282 
00283     return totalLength;
00284 }
00285 
00286 void QueryHits::writeQueryHitsData (QDataStream &stream) const
00287 {
00288     stream << p.queryHitsData.vendorCode;
00289 
00290     stream << p.queryHitsData.openAreaSize;
00291     stream << p.queryHitsData.openAreaFlags1;
00292     stream << p.queryHitsData.openAreaFlags2;
00293 
00294     if (p.queryHitsData.xmlLength)
00295         stream << p.queryHitsData.xmlLength;
00296 
00297     stream << p.queryHitsData.privateVendorFlag;
00298     stream << p.queryHitsData.ggepBlock;
00299 
00300     if (p.queryHitsData.xmlLength) {
00301         stream.writeRawData (p.queryHitsData.xmlData.data(), p.queryHitsData.xmlData.length());
00302         stream << uchar (0);
00303     }
00304 }
00305 
00306 QueryHits::QueryHitsData::QueryHitsData()
00307 : vendorCode ("LIME")
00308 {
00309     openAreaSize        = 2; // Just the two flags
00310     openAreaFlags1      = 0x3C; // 00111100 => use GGEP, use Avg Upload Speed, use Have uploaded, use Busy, no Push
00311     openAreaFlags2      = 0x19; // 00011001 => no GGEP, yes Advg Upload Speed, yes have uploaded, no Busy, use Push
00312     xmlLength           = 0;
00313     privateVendorFlag   = 0;
00314 }
00315 
00316 const QueryHits::Result & QueryHits::result (int &index) const
00317 {
00318 //  qDebug() << index << " " << p.resultSet.size() << numberOfHits();
00319     if (index < 0 || index >= numberOfHits())
00320     Q_ASSERT (index >= 0 && index < p.resultSet.size());
00321     return p.resultSet.at (index);
00322 }
00323 
00324 QString QueryHits::Result::fileExtension() const
00325 {
00326     QString extension = fullFileName;
00327     int startIndex = extension.length() - 9;
00328     startIndex = (startIndex > 0) ? startIndex : 0;
00329     int index = extension.indexOf ('.', startIndex);
00330     if (index > 0)
00331         return extension.remove (0, index+1);
00332     return QString();
00333 }
00334 
00335 QString QueryHits::Result::fileName() const
00336 {
00337     QString name = fullFileName;
00338     int startIndex = name.length() - 9;
00339     startIndex = (startIndex > 0) ? startIndex : 0;
00340     int index = name.indexOf ('.', startIndex);
00341     return name.remove (index, name.length());
00342 }
00343 
00344 quint32 QueryHits::Result::sizeOfFile() const
00345 {
00346     return fileSize;
00347 }
00348 
00349 void QueryHits::setPort (quint16 port)
00350 {
00351     p.port = port;
00352     invalidatePayload();
00353 }
00354 
00355 void QueryHits::setIpAddress (const QHostAddress &ipAddress)
00356 {
00357     p.ipAddress = ipAddress;
00358     invalidatePayload();
00359 }
00360 
00361 void QueryHits::setSpeed (quint32 speed)
00362 {
00363     p.speed = speed;
00364     invalidatePayload();
00365 }
00366 
00367 void QueryHits::setServentId (const QUuid &serventId)
00368 {
00369     p.serventId = serventId;
00370     invalidatePayload();
00371 }
00372 
00373 //void QueryHits::setQueryHitsData (QByteArray &data)
00374 //{
00375 //  p.queryHitsData.data = data;
00376 //  invalidatePayload();
00377 //}
00378 
00379 void QueryHits::appendResult (Result &result)
00380 {
00381     p.resultSet.append (result);
00382     p.numberOfHits = p.resultSet.size();
00383     invalidatePayload();
00384 }
00385