|
|
|
/*
|
|
|
|
outgoingtransfer.cpp - msn p2p protocol
|
|
|
|
|
|
|
|
Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org>
|
|
|
|
Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com>
|
|
|
|
|
|
|
|
*************************************************************************
|
|
|
|
* *
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
|
|
* (at your option) any later version. *
|
|
|
|
* *
|
|
|
|
*************************************************************************
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "outgoingtransfer.h"
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
// Kde includes
|
|
|
|
#include <kbufferedsocket.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kmdcodec.h>
|
|
|
|
using namespace KNetwork;
|
|
|
|
|
|
|
|
// Qt includes
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <tqtimer.h>
|
|
|
|
|
|
|
|
// Kopete includes
|
|
|
|
#include <kopetetransfermanager.h>
|
|
|
|
|
|
|
|
#include <netinet/in.h> // For htonl
|
|
|
|
using P2P::TransferContext;
|
|
|
|
using P2P::Dispatcher;
|
|
|
|
using P2P::OutgoingTransfer;
|
|
|
|
using P2P::Message;
|
|
|
|
|
|
|
|
OutgoingTransfer::OutgoingTransfer(const TQString& to, P2P::Dispatcher *dispatcher, Q_UINT32 sessionId)
|
|
|
|
: TransferContext(to,dispatcher,sessionId)
|
|
|
|
{
|
|
|
|
m_direction = Outgoing;
|
|
|
|
m_handshake = 0x01;
|
|
|
|
}
|
|
|
|
|
|
|
|
OutgoingTransfer::~OutgoingTransfer()
|
|
|
|
{
|
|
|
|
kdDebug(14140) << k_funcinfo << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::sendImage(const TQByteArray& image)
|
|
|
|
{
|
|
|
|
|
|
|
|
// TODO TQByteArray base64 = KCodecs::base64Encode(image);
|
|
|
|
//
|
|
|
|
// TQCString body = "MIME-Version: 1.0\r\n"
|
|
|
|
// "Content-Type: image/gif\r\n"
|
|
|
|
// "\r\n"
|
|
|
|
// "base64:" +
|
|
|
|
//
|
|
|
|
// Message outbound;
|
|
|
|
// outbound.header.sessionId = m_sessionId;
|
|
|
|
// outbound.header.identifier = m_baseIdentifier;
|
|
|
|
// outbound.header.dataOffset = 0;
|
|
|
|
// outbound.header.totalDataSize = 4;
|
|
|
|
// outbound.header.dataSize = 4;
|
|
|
|
// outbound.header.flag = 0;
|
|
|
|
// outbound.header.ackSessionIdentifier = rand()%0x8FFFFFF0 + 4;
|
|
|
|
// outbound.header.ackUniqueIdentifier = 0;
|
|
|
|
// outbound.header.ackDataSize = 0l;
|
|
|
|
// TQByteArray bytes(4);
|
|
|
|
// bytes.fill('\0');
|
|
|
|
// outbound.body = bytes;
|
|
|
|
// outbound.applicationIdentifier = 0;
|
|
|
|
// outbound.attachApplicationId = false;
|
|
|
|
// outbound.destination = m_recipient;
|
|
|
|
//
|
|
|
|
// sendMessage(outbound, body);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::slotSendData()
|
|
|
|
{
|
|
|
|
Q_INT32 bytesRead = 0;
|
|
|
|
TQByteArray buffer(1202);
|
|
|
|
if(!m_file)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Read a chunk from the source file.
|
|
|
|
bytesRead = m_file->readBlock(buffer.data(), buffer.size());
|
|
|
|
|
|
|
|
if (bytesRead < 0) {
|
|
|
|
m_file->close();
|
|
|
|
// ### error handling
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
|
|
|
if(bytesRead < 1202){
|
|
|
|
buffer.resize(bytesRead);
|
|
|
|
}
|
|
|
|
|
|
|
|
kdDebug(14140) << k_funcinfo << TQString("Sending, %1 bytes").arg(bytesRead) << endl;
|
|
|
|
|
|
|
|
if((m_offset + bytesRead) < m_file->size())
|
|
|
|
{
|
|
|
|
sendData(buffer);
|
|
|
|
m_offset += bytesRead;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_isComplete = true;
|
|
|
|
// Send the last chunk of the file.
|
|
|
|
sendData(buffer);
|
|
|
|
m_offset += buffer.size();
|
|
|
|
// Close the file.
|
|
|
|
m_file->close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(m_transfer){
|
|
|
|
m_transfer->slotProcessed(m_offset);
|
|
|
|
if(m_isComplete){
|
|
|
|
// The transfer is complete.
|
|
|
|
m_transfer->slotComplete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::acknowledged()
|
|
|
|
{
|
|
|
|
kdDebug(14140) << k_funcinfo << endl;
|
|
|
|
|
|
|
|
switch(m_state)
|
|
|
|
{
|
|
|
|
case Invitation:
|
|
|
|
{
|
|
|
|
if(m_type == UserDisplayIcon)
|
|
|
|
{
|
|
|
|
m_state = Negotiation;
|
|
|
|
// Send data preparation message.
|
|
|
|
sendDataPreparation();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case Negotiation:
|
|
|
|
{
|
|
|
|
if(m_type == UserDisplayIcon)
|
|
|
|
{
|
|
|
|
// <<< Data preparation acknowledge message.
|
|
|
|
m_state = DataTransfer;
|
|
|
|
m_identifier++;
|
|
|
|
// Start sending data.
|
|
|
|
slotSendData();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case DataTransfer:
|
|
|
|
// NOTE <<< Data acknowledged message.
|
|
|
|
// <<< Bye message should follow.
|
|
|
|
if(m_type == File)
|
|
|
|
{
|
|
|
|
if(m_handshake == 0x01)
|
|
|
|
{
|
|
|
|
// Data handshake acknowledge message.
|
|
|
|
// Start sending data.
|
|
|
|
slotSendData();
|
|
|
|
}
|
|
|
|
else if(m_handshake == 0x02)
|
|
|
|
{
|
|
|
|
// Data acknowledge message.
|
|
|
|
// Send the recipient a BYE message.
|
|
|
|
m_state = Finished;
|
|
|
|
sendMessage(BYE, "\r\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Finished:
|
|
|
|
if(m_type == File)
|
|
|
|
{
|
|
|
|
// BYE acknowledge message.
|
|
|
|
m_dispatcher->detach(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::processMessage(const Message& message)
|
|
|
|
{
|
|
|
|
TQString body =
|
|
|
|
TQCString(message.body.data(), message.header.dataSize);
|
|
|
|
kdDebug(14140) << k_funcinfo << "received, " << body << endl;
|
|
|
|
|
|
|
|
if(body.startsWith("BYE"))
|
|
|
|
{
|
|
|
|
m_state = Finished;
|
|
|
|
// Send the recipient an acknowledge message.
|
|
|
|
acknowledge(message);
|
|
|
|
if(!m_isComplete)
|
|
|
|
{
|
|
|
|
// The peer cancelled the transfer.
|
|
|
|
if(m_transfer)
|
|
|
|
{
|
|
|
|
// Inform the user of the file transfer cancelation.
|
|
|
|
m_transfer->slotError(KIO::ERR_ABORTED, i18n("File transfer canceled."));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Dispose of this transfer context.
|
|
|
|
m_dispatcher->detach(this);
|
|
|
|
}
|
|
|
|
else if(body.startsWith("MSNSLP/1.0 200 OK"))
|
|
|
|
{
|
|
|
|
// Retrieve the message content type.
|
|
|
|
TQRegExp regex("Content-Type: ([A-Za-z0-9$!*/\\-]*)");
|
|
|
|
regex.search(body);
|
|
|
|
TQString contentType = regex.cap(1);
|
|
|
|
|
|
|
|
if(contentType == "application/x-msnmsgr-sessionreqbody")
|
|
|
|
{
|
|
|
|
// Recipient has accepted the file transfer.
|
|
|
|
// Acknowledge the recipient.
|
|
|
|
acknowledge(message);
|
|
|
|
|
|
|
|
// Try to open the file for reading.
|
|
|
|
// If an error occurs, send an internal
|
|
|
|
// error message to the recipient.
|
|
|
|
if(!m_file->open(IO_ReadOnly)){
|
|
|
|
error();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve the receiving client's contact.
|
|
|
|
Kopete::Contact *contact = m_dispatcher->getContactByAccountId(m_recipient);
|
|
|
|
if(contact == 0l)
|
|
|
|
{
|
|
|
|
error();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_transfer =
|
|
|
|
Kopete::TransferManager::transferManager()->addTransfer(contact, m_file->name(), m_file->size(), m_recipient, Kopete::FileTransferInfo::Outgoing);
|
|
|
|
|
|
|
|
TQObject::connect(m_transfer , TQT_SIGNAL(transferCanceled()), this, TQT_SLOT(abort()));
|
|
|
|
|
|
|
|
m_state = Negotiation;
|
|
|
|
|
|
|
|
m_branch = P2P::Uid::createUid();
|
|
|
|
|
|
|
|
// Send the direct connection invitation message.
|
|
|
|
TQString content = "Bridges: TRUDPv1 TCPv1\r\n" +
|
|
|
|
TQString("NetID: %1\r\n").arg("-123657987") +
|
|
|
|
TQString("Conn-Type: %1\r\n").arg("Restrict-NAT") +
|
|
|
|
"UPnPNat: false\r\n"
|
|
|
|
"ICF: false\r\n" +
|
|
|
|
TQString("Hashed-Nonce: {%1}\r\n").arg(P2P::Uid::createUid()) +
|
|
|
|
"\r\n";
|
|
|
|
sendMessage(INVITE, content);
|
|
|
|
}
|
|
|
|
else if(contentType == "application/x-msnmsgr-transrespbody")
|
|
|
|
{
|
|
|
|
// Determine whether the recipient created
|
|
|
|
// a listening endpoint.
|
|
|
|
regex = TQRegExp("Listening: ([^\r\n]+)\r\n");
|
|
|
|
regex.search(body);
|
|
|
|
bool isListening = (regex.cap(1) == "true");
|
|
|
|
|
|
|
|
// Send the recipient an acknowledge message.
|
|
|
|
acknowledge(message);
|
|
|
|
|
|
|
|
m_state = DataTransfer;
|
|
|
|
|
|
|
|
#if 1
|
|
|
|
isListening = false; // TODO complete direct connection.
|
|
|
|
#endif
|
|
|
|
if(isListening)
|
|
|
|
{
|
|
|
|
// Retrieve the hashed nonce for this direct connection instance.
|
|
|
|
regex = TQRegExp("Hashed-Nonce: \\{([0-9A-F\\-]*)\\}\r\n");
|
|
|
|
regex.search(body);
|
|
|
|
m_nonce = regex.cap(1);
|
|
|
|
// Retrieve the listening endpoints of the receiving client.
|
|
|
|
regex = TQRegExp("IPv4Internal-Addrs: ([^\r\n]+)\r\n");
|
|
|
|
regex.search(body);
|
|
|
|
m_peerEndpoints = TQStringList::split(" ", regex.cap(1));
|
|
|
|
m_endpointIterator = m_peerEndpoints.begin();
|
|
|
|
// Retrieve the listening port of the receiving client.
|
|
|
|
regex = TQRegExp("IPv4Internal-Port: ([^\r\n]+)\r\n");
|
|
|
|
regex.search(body);
|
|
|
|
m_remotePort = regex.cap(1);
|
|
|
|
|
|
|
|
// Try to connect to the receiving client's
|
|
|
|
// listening endpoint.
|
|
|
|
connectToEndpoint(*m_endpointIterator);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_handshake = 0x02;
|
|
|
|
// Otherwise, send data through the already
|
|
|
|
// existing session.
|
|
|
|
slotSendData();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(body.startsWith("MSNSLP/1.0 603 Decline"))
|
|
|
|
{
|
|
|
|
// File transfer has been cancelled remotely.
|
|
|
|
// Send an acknowledge message
|
|
|
|
acknowledge(message);
|
|
|
|
if(m_transfer)
|
|
|
|
{
|
|
|
|
// Inform the user of the file transfer cancelation.
|
|
|
|
m_transfer->slotError(KIO::ERR_ABORTED, i18n("File transfer canceled."));
|
|
|
|
}
|
|
|
|
|
|
|
|
if(m_file && m_file->isOpen()){
|
|
|
|
// Close the file.
|
|
|
|
m_file->close();
|
|
|
|
}
|
|
|
|
m_dispatcher->detach(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::readyToSend()
|
|
|
|
{
|
|
|
|
if(m_isComplete){
|
|
|
|
// Ignore, do nothing.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
slotSendData();
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::connectToEndpoint(const TQString& hostName)
|
|
|
|
{
|
|
|
|
m_socket = new KBufferedSocket(hostName, m_remotePort);
|
|
|
|
m_socket->setBlocking(false);
|
|
|
|
m_socket->enableRead(true);
|
|
|
|
// Disable write signal for now. Only enable
|
|
|
|
// when we are ready to sent data.
|
|
|
|
// NOTE readyWrite consumes too much cpu usage.
|
|
|
|
m_socket->enableWrite(false);
|
|
|
|
TQObject::connect(m_socket, TQT_SIGNAL(readyRead()), this, TQT_SLOT(slotRead()));
|
|
|
|
TQObject::connect(m_socket, TQT_SIGNAL(connected(const KResolverEntry&)), this, TQT_SLOT(slotConnected()));
|
|
|
|
TQObject::connect(m_socket, TQT_SIGNAL(gotError(int)), this, TQT_SLOT(slotSocketError(int)));
|
|
|
|
TQObject::connect(m_socket, TQT_SIGNAL(closed()), this, TQT_SLOT(slotSocketClosed()));
|
|
|
|
// Try to connect to the endpoint.
|
|
|
|
m_socket->connect();
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::slotConnected()
|
|
|
|
{
|
|
|
|
kdDebug(14140) << k_funcinfo << endl;
|
|
|
|
// Check if connection is ok.
|
|
|
|
Q_UINT32 bytesWritten = m_socket->writeBlock(TQCString("foo").data(), 4);
|
|
|
|
if(bytesWritten != 4)
|
|
|
|
{
|
|
|
|
// Not all data was written, close the socket.
|
|
|
|
m_socket->closeNow();
|
|
|
|
// Schedule the data to be sent through the existing session.
|
|
|
|
TQTimer::singleShot(2000, this, TQT_SLOT(slotSendData()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send data handshake message.
|
|
|
|
P2P::Message handshake;
|
|
|
|
handshake.header.sessionId = 0;
|
|
|
|
handshake.header.identifier = ++m_identifier;
|
|
|
|
handshake.header.dataOffset = 0l;
|
|
|
|
handshake.header.totalDataSize = 0l;
|
|
|
|
handshake.header.dataSize = 0;
|
|
|
|
// Set the flag to indicate that this is
|
|
|
|
// a direct connection handshake message.
|
|
|
|
handshake.header.flag = 0x100;
|
|
|
|
TQString nonce = m_nonce.remove('-');
|
|
|
|
handshake.header.ackSessionIdentifier = nonce.mid(0, 8).toUInt(0, 16);
|
|
|
|
handshake.header.ackUniqueIdentifier =
|
|
|
|
nonce.mid(8, 4).toUInt(0, 16) | (nonce.mid(12, 4).toUInt(0, 16) << 16);
|
|
|
|
const Q_UINT32 lo = nonce.mid(16, 8).toUInt(0, 16);
|
|
|
|
const Q_UINT32 hi = nonce.mid(24, 8).toUInt(0, 16);
|
|
|
|
handshake.header.ackDataSize =
|
|
|
|
((Q_INT64)htonl(lo)) | (((Q_INT64)htonl(hi)) << 32);
|
|
|
|
|
|
|
|
TQByteArray stream;
|
|
|
|
// Write the message to the memory stream.
|
|
|
|
m_messageFormatter.writeMessage(handshake, stream, true);
|
|
|
|
// Send the byte stream over the wire.
|
|
|
|
m_socket->writeBlock(stream.data(), stream.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::slotRead()
|
|
|
|
{
|
|
|
|
Q_INT32 bytesAvailable = m_socket->bytesAvailable();
|
|
|
|
kdDebug(14140) << k_funcinfo << bytesAvailable << ", bytes available." << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::slotSocketError(int)
|
|
|
|
{
|
|
|
|
kdDebug(14140) << k_funcinfo << m_socket->errorString() << endl;
|
|
|
|
// If an error has occurred, try to connect
|
|
|
|
// to another available peer endpoint.
|
|
|
|
// If there are no more available endpoints,
|
|
|
|
// send the data through the session.
|
|
|
|
m_socket->closeNow();
|
|
|
|
|
|
|
|
// Move to the next available endpoint.
|
|
|
|
m_endpointIterator++;
|
|
|
|
if(m_endpointIterator != m_peerEndpoints.end()){
|
|
|
|
// Try to connect to the endpoint.
|
|
|
|
connectToEndpoint(*m_endpointIterator);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Otherwise, send the data through the session.
|
|
|
|
m_identifier -= 1;
|
|
|
|
TQTimer::singleShot(2000, this, TQT_SLOT(slotSendData()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OutgoingTransfer::slotSocketClosed()
|
|
|
|
{
|
|
|
|
kdDebug(14140) << k_funcinfo << endl;
|
|
|
|
m_socket->deleteLater();
|
|
|
|
m_socket = 0l;
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "outgoingtransfer.moc"
|