You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp

639 lines
13 KiB

/*
* ibb.cpp - Inband bytestream
* Copyright (C) 2001, 2002 Justin Karneges
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "xmpp_ibb.h"
#include <tqtimer.h>
#include "xmpp_xmlcommon.h"
#include "base64.h"
#include <stdlib.h>
#define IBB_PACKET_SIZE 4096
#define IBB_PACKET_DELAY 0
using namespace XMPP;
static int num_conn = 0;
static int id_conn = 0;
//----------------------------------------------------------------------------
// IBBConnection
//----------------------------------------------------------------------------
class IBBConnection::Private
{
public:
Private() {}
int state;
Jid peer;
TQString sid;
IBBManager *m;
JT_IBB *j;
TQDomElement comment;
TQString iq_id;
int blockSize;
TQByteArray recvbuf, sendbuf;
bool closePending, closing;
int id;
};
IBBConnection::IBBConnection(IBBManager *m)
:ByteStream(m)
{
d = new Private;
d->m = m;
d->j = 0;
reset();
++num_conn;
d->id = id_conn++;
TQString dstr; dstr.sprintf("IBBConnection[%d]: constructing, count=%d\n", d->id, num_conn);
d->m->client()->debug(dstr);
}
void IBBConnection::reset(bool clear)
{
d->m->unlink(this);
d->state = Idle;
d->closePending = false;
d->closing = false;
delete d->j;
d->j = 0;
d->sendbuf.resize(0);
if(clear)
d->recvbuf.resize(0);
}
IBBConnection::~IBBConnection()
{
reset(true);
--num_conn;
TQString dstr; dstr.sprintf("IBBConnection[%d]: destructing, count=%d\n", d->id, num_conn);
d->m->client()->debug(dstr);
delete d;
}
void IBBConnection::connectToJid(const Jid &peer, const TQDomElement &comment)
{
close();
reset(true);
d->state = Requesting;
d->peer = peer;
d->comment = comment;
TQString dstr; dstr.sprintf("IBBConnection[%d]: initiating request to %s\n", d->id, peer.full().latin1());
d->m->client()->debug(dstr);
d->j = new JT_IBB(d->m->client()->rootTask());
connect(d->j, TQT_SIGNAL(finished()), TQT_SLOT(ibb_finished()));
d->j->request(d->peer, comment);
d->j->go(true);
}
void IBBConnection::accept()
{
if(d->state != WaitingForAccept)
return;
TQString dstr; dstr.sprintf("IBBConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1());
d->m->client()->debug(dstr);
d->m->doAccept(this, d->iq_id);
d->state = Active;
d->m->link(this);
}
void IBBConnection::close()
{
if(d->state == Idle)
return;
if(d->state == WaitingForAccept) {
d->m->doReject(this, d->iq_id, 403, "Rejected");
reset();
return;
}
TQString dstr; dstr.sprintf("IBBConnection[%d]: closing\n", d->id);
d->m->client()->debug(dstr);
if(d->state == Active) {
// if there is data pending to be written, then pend the closing
if(bytesToWrite() > 0) {
d->closePending = true;
trySend();
return;
}
// send a close packet
JT_IBB *j = new JT_IBB(d->m->client()->rootTask());
j->sendData(d->peer, d->sid, TQByteArray(), true);
j->go(true);
}
reset();
}
int IBBConnection::state() const
{
return d->state;
}
Jid IBBConnection::peer() const
{
return d->peer;
}
TQString IBBConnection::streamid() const
{
return d->sid;
}
TQDomElement IBBConnection::comment() const
{
return d->comment;
}
bool IBBConnection::isOpen() const
{
if(d->state == Active)
return true;
else
return false;
}
void IBBConnection::write(const TQByteArray &a)
{
if(d->state != Active || d->closePending || d->closing)
return;
// append to the end of our send buffer
int oldsize = d->sendbuf.size();
d->sendbuf.resize(oldsize + a.size());
memcpy(d->sendbuf.data() + oldsize, a.data(), a.size());
trySend();
}
TQByteArray IBBConnection::read(int)
{
// TODO: obey argument
TQByteArray a = d->recvbuf.copy();
d->recvbuf.resize(0);
return a;
}
int IBBConnection::bytesAvailable() const
{
return d->recvbuf.size();
}
int IBBConnection::bytesToWrite() const
{
return d->sendbuf.size();
}
void IBBConnection::waitForAccept(const Jid &peer, const TQString &sid, const TQDomElement &comment, const TQString &iq_id)
{
close();
reset(true);
d->state = WaitingForAccept;
d->peer = peer;
d->sid = sid;
d->comment = comment;
d->iq_id = iq_id;
}
void IBBConnection::takeIncomingData(const TQByteArray &a, bool close)
{
// append to the end of our recv buffer
int oldsize = d->recvbuf.size();
d->recvbuf.resize(oldsize + a.size());
memcpy(d->recvbuf.data() + oldsize, a.data(), a.size());
readyRead();
if(close) {
reset();
connectionClosed();
}
}
void IBBConnection::ibb_finished()
{
JT_IBB *j = d->j;
d->j = 0;
if(j->success()) {
if(j->mode() == JT_IBB::ModeRequest) {
d->sid = j->streamid();
TQString dstr; dstr.sprintf("IBBConnection[%d]: %s [%s] accepted.\n", d->id, d->peer.full().latin1(), d->sid.latin1());
d->m->client()->debug(dstr);
d->state = Active;
d->m->link(this);
connected();
}
else {
bytesWritten(d->blockSize);
if(d->closing) {
reset();
delayedCloseFinished();
}
if(!d->sendbuf.isEmpty() || d->closePending)
TQTimer::singleShot(IBB_PACKET_DELAY, this, TQT_SLOT(trySend()));
}
}
else {
if(j->mode() == JT_IBB::ModeRequest) {
TQString dstr; dstr.sprintf("IBBConnection[%d]: %s refused.\n", d->id, d->peer.full().latin1());
d->m->client()->debug(dstr);
reset(true);
error(ErrRequest);
}
else {
reset(true);
error(ErrData);
}
}
}
void IBBConnection::trySend()
{
// if we already have an active task, then don't do anything
if(d->j)
return;
TQByteArray a;
if(!d->sendbuf.isEmpty()) {
// take a chunk
if(d->sendbuf.size() < IBB_PACKET_SIZE)
a.resize(d->sendbuf.size());
else
a.resize(IBB_PACKET_SIZE);
memcpy(a.data(), d->sendbuf.data(), a.size());
d->sendbuf.resize(d->sendbuf.size() - a.size());
}
bool doClose = false;
if(d->sendbuf.isEmpty() && d->closePending)
doClose = true;
// null operation?
if(a.isEmpty() && !doClose)
return;
printf("IBBConnection[%d]: sending [%d] bytes ", d->id, a.size());
if(doClose)
printf("and closing.\n");
else
printf("(%d bytes left)\n", d->sendbuf.size());
if(doClose) {
d->closePending = false;
d->closing = true;
}
d->blockSize = a.size();
d->j = new JT_IBB(d->m->client()->rootTask());
connect(d->j, TQT_SIGNAL(finished()), TQT_SLOT(ibb_finished()));
d->j->sendData(d->peer, d->sid, a, doClose);
d->j->go(true);
}
//----------------------------------------------------------------------------
// IBBManager
//----------------------------------------------------------------------------
class IBBManager::Private
{
public:
Private() {}
Client *client;
IBBConnectionList activeConns;
IBBConnectionList incomingConns;
JT_IBB *ibb;
};
IBBManager::IBBManager(Client *parent)
:TQObject(parent)
{
d = new Private;
d->client = parent;
d->ibb = new JT_IBB(d->client->rootTask(), true);
connect(d->ibb, TQT_SIGNAL(incomingRequest(const Jid &, const TQString &, const TQDomElement &)), TQT_SLOT(ibb_incomingRequest(const Jid &, const TQString &, const TQDomElement &)));
connect(d->ibb, TQT_SIGNAL(incomingData(const Jid &, const TQString &, const TQString &, const TQByteArray &, bool)), TQT_SLOT(ibb_incomingData(const Jid &, const TQString &, const TQString &, const TQByteArray &, bool)));
}
IBBManager::~IBBManager()
{
d->incomingConns.setAutoDelete(true);
d->incomingConns.clear();
delete d->ibb;
delete d;
}
Client *IBBManager::client() const
{
return d->client;
}
IBBConnection *IBBManager::takeIncoming()
{
if(d->incomingConns.isEmpty())
return 0;
IBBConnection *c = d->incomingConns.getFirst();
d->incomingConns.removeRef(c);
return c;
}
void IBBManager::ibb_incomingRequest(const Jid &from, const TQString &id, const TQDomElement &comment)
{
TQString sid = genUniqueKey();
// create a "waiting" connection
IBBConnection *c = new IBBConnection(this);
c->waitForAccept(from, sid, comment, id);
d->incomingConns.append(c);
incomingReady();
}
void IBBManager::ibb_incomingData(const Jid &from, const TQString &streamid, const TQString &id, const TQByteArray &data, bool close)
{
IBBConnection *c = findConnection(streamid, from);
if(!c) {
d->ibb->respondError(from, id, 404, "No such stream");
}
else {
d->ibb->respondAck(from, id);
c->takeIncomingData(data, close);
}
}
TQString IBBManager::genKey() const
{
TQString key = "ibb_";
for(int i = 0; i < 4; ++i) {
int word = rand() & 0xffff;
for(int n = 0; n < 4; ++n) {
TQString s;
s.sprintf("%x", (word >> (n * 4)) & 0xf);
key.append(s);
}
}
return key;
}
TQString IBBManager::genUniqueKey() const
{
// get unused key
TQString key;
while(1) {
key = genKey();
if(!findConnection(key))
break;
}
return key;
}
void IBBManager::link(IBBConnection *c)
{
d->activeConns.append(c);
}
void IBBManager::unlink(IBBConnection *c)
{
d->activeConns.removeRef(c);
}
IBBConnection *IBBManager::findConnection(const TQString &sid, const Jid &peer) const
{
IBBConnectionListIt it(d->activeConns);
for(IBBConnection *c; (c = it.current()); ++it) {
if(c->streamid() == sid && (peer.isEmpty() || c->peer().compare(peer)) )
return c;
}
return 0;
}
void IBBManager::doAccept(IBBConnection *c, const TQString &id)
{
d->ibb->respondSuccess(c->peer(), id, c->streamid());
}
void IBBManager::doReject(IBBConnection *c, const TQString &id, int code, const TQString &str)
{
d->ibb->respondError(c->peer(), id, code, str);
}
//----------------------------------------------------------------------------
// JT_IBB
//----------------------------------------------------------------------------
class JT_IBB::Private
{
public:
Private() {}
TQDomElement iq;
int mode;
bool serve;
Jid to;
TQString streamid;
};
JT_IBB::JT_IBB(Task *parent, bool serve)
:Task(parent)
{
d = new Private;
d->serve = serve;
}
JT_IBB::~JT_IBB()
{
delete d;
}
void JT_IBB::request(const Jid &to, const TQDomElement &comment)
{
d->mode = ModeRequest;
TQDomElement iq;
d->to = to;
iq = createIQ(doc(), "set", to.full(), id());
TQDomElement query = doc()->createElement("query");
query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
iq.appendChild(query);
query.appendChild(comment);
d->iq = iq;
}
void JT_IBB::sendData(const Jid &to, const TQString &streamid, const TQByteArray &a, bool close)
{
d->mode = ModeSendData;
TQDomElement iq;
d->to = to;
iq = createIQ(doc(), "set", to.full(), id());
TQDomElement query = doc()->createElement("query");
query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
iq.appendChild(query);
query.appendChild(textTag(doc(), "streamid", streamid));
if(!a.isEmpty())
query.appendChild(textTag(doc(), "data", Base64::arrayToString(a)));
if(close) {
TQDomElement c = doc()->createElement("close");
query.appendChild(c);
}
d->iq = iq;
}
void JT_IBB::respondSuccess(const Jid &to, const TQString &id, const TQString &streamid)
{
TQDomElement iq = createIQ(doc(), "result", to.full(), id);
TQDomElement query = doc()->createElement("query");
query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
iq.appendChild(query);
query.appendChild(textTag(doc(), "streamid", streamid));
send(iq);
}
void JT_IBB::respondError(const Jid &to, const TQString &id, int code, const TQString &str)
{
TQDomElement iq = createIQ(doc(), "error", to.full(), id);
TQDomElement err = textTag(doc(), "error", str);
err.setAttribute("code", TQString::number(code));
iq.appendChild(err);
send(iq);
}
void JT_IBB::respondAck(const Jid &to, const TQString &id)
{
TQDomElement iq = createIQ(doc(), "result", to.full(), id);
send(iq);
}
void JT_IBB::onGo()
{
send(d->iq);
}
bool JT_IBB::take(const TQDomElement &e)
{
if(d->serve) {
// must be an iq-set tag
if(e.tagName() != "iq" || e.attribute("type") != "set")
return false;
if(queryNS(e) != "http://jabber.org/protocol/ibb")
return false;
Jid from(e.attribute("from"));
TQString id = e.attribute("id");
TQDomElement q = queryTag(e);
bool found;
TQDomElement s = findSubTag(q, "streamid", &found);
if(!found) {
TQDomElement comment = findSubTag(q, "comment", &found);
incomingRequest(from, id, comment);
}
else {
TQString sid = tagContent(s);
TQByteArray a;
bool close = false;
s = findSubTag(q, "data", &found);
if(found)
a = Base64::stringToArray(tagContent(s));
s = findSubTag(q, "close", &found);
if(found)
close = true;
incomingData(from, sid, id, a, close);
}
return true;
}
else {
Jid from(e.attribute("from"));
if(e.attribute("id") != id() || !d->to.compare(from))
return false;
if(e.attribute("type") == "result") {
TQDomElement q = queryTag(e);
// request
if(d->mode == ModeRequest) {
bool found;
TQDomElement s = findSubTag(q, "streamid", &found);
if(found)
d->streamid = tagContent(s);
else
d->streamid = "";
setSuccess();
}
// sendData
else {
// thank you for the ack, kind sir
setSuccess();
}
}
else {
setError(e);
}
return true;
}
}
TQString JT_IBB::streamid() const
{
return d->streamid;
}
Jid JT_IBB::jid() const
{
return d->to;
}
int JT_IBB::mode() const
{
return d->mode;
}