2
* ibb.cpp - Inband bytestream
3
* Copyright (C) 2001, 2002 Justin Karneges
5
* This library is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU Lesser General Public
7
* License as published by the Free Software Foundation; either
8
* version 2.1 of the License, or (at your option) any later version.
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* Lesser General Public License for more details.
15
* You should have received a copy of the GNU Lesser General Public
16
* License along with this library; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
#include "xmpp_xmlcommon.h"
29
#define IBB_PACKET_SIZE 4096
30
#define IBB_PACKET_DELAY 0
34
static int num_conn = 0;
35
static int id_conn = 0;
37
//----------------------------------------------------------------------------
39
//----------------------------------------------------------------------------
40
class IBBConnection::Private
54
QByteArray recvbuf, sendbuf;
55
bool closePending, closing;
60
IBBConnection::IBBConnection(IBBManager *m)
70
QString dstr; dstr.sprintf("IBBConnection[%d]: constructing, count=%d\n", d->id, num_conn);
71
d->m->client()->debug(dstr);
74
void IBBConnection::reset(bool clear)
78
d->closePending = false;
89
IBBConnection::~IBBConnection()
94
QString dstr; dstr.sprintf("IBBConnection[%d]: destructing, count=%d\n", d->id, num_conn);
95
d->m->client()->debug(dstr);
100
void IBBConnection::connectToJid(const Jid &peer, const QDomElement &comment)
105
d->state = Requesting;
107
d->comment = comment;
109
QString dstr; dstr.sprintf("IBBConnection[%d]: initiating request to %s\n", d->id, peer.full().toLatin1().data());
110
d->m->client()->debug(dstr);
112
d->j = new JT_IBB(d->m->client()->rootTask());
113
connect(d->j, SIGNAL(finished()), SLOT(ibb_finished()));
114
d->j->request(d->peer, comment);
118
void IBBConnection::accept()
120
if(d->state != WaitingForAccept)
123
QString dstr; dstr.sprintf("IBBConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().toLatin1().data(), d->sid.toLatin1().data());
124
d->m->client()->debug(dstr);
126
d->m->doAccept(this, d->iq_id);
131
void IBBConnection::close()
136
if(d->state == WaitingForAccept) {
137
d->m->doReject(this, d->iq_id, 403, "Rejected");
142
QString dstr; dstr.sprintf("IBBConnection[%d]: closing\n", d->id);
143
d->m->client()->debug(dstr);
145
if(d->state == Active) {
146
// if there is data pending to be written, then pend the closing
147
if(bytesToWrite() > 0) {
148
d->closePending = true;
153
// send a close packet
154
JT_IBB *j = new JT_IBB(d->m->client()->rootTask());
155
j->sendData(d->peer, d->sid, QByteArray(), true);
162
int IBBConnection::state() const
167
Jid IBBConnection::peer() const
172
QString IBBConnection::streamid() const
177
QDomElement IBBConnection::comment() const
182
bool IBBConnection::isOpen() const
184
if(d->state == Active)
190
void IBBConnection::write(const QByteArray &a)
192
if(d->state != Active || d->closePending || d->closing)
195
// append to the end of our send buffer
196
int oldsize = d->sendbuf.size();
197
d->sendbuf.resize(oldsize + a.size());
198
memcpy(d->sendbuf.data() + oldsize, a.data(), a.size());
203
QByteArray IBBConnection::read(int)
205
// TODO: obey argument
206
QByteArray a = d->recvbuf;
207
d->recvbuf.resize(0);
211
int IBBConnection::bytesAvailable() const
213
return d->recvbuf.size();
216
int IBBConnection::bytesToWrite() const
218
return d->sendbuf.size();
221
void IBBConnection::waitForAccept(const Jid &peer, const QString &sid, const QDomElement &comment, const QString &iq_id)
226
d->state = WaitingForAccept;
229
d->comment = comment;
233
void IBBConnection::takeIncomingData(const QByteArray &a, bool close)
235
// append to the end of our recv buffer
236
int oldsize = d->recvbuf.size();
237
d->recvbuf.resize(oldsize + a.size());
238
memcpy(d->recvbuf.data() + oldsize, a.data(), a.size());
248
void IBBConnection::ibb_finished()
254
if(j->mode() == JT_IBB::ModeRequest) {
255
d->sid = j->streamid();
257
QString dstr; dstr.sprintf("IBBConnection[%d]: %s [%s] accepted.\n", d->id, d->peer.full().toLatin1().data(), d->sid.toLatin1().data());
258
d->m->client()->debug(dstr);
265
bytesWritten(d->blockSize);
269
delayedCloseFinished();
272
if(!d->sendbuf.isEmpty() || d->closePending)
273
QTimer::singleShot(IBB_PACKET_DELAY, this, SLOT(trySend()));
277
if(j->mode() == JT_IBB::ModeRequest) {
278
QString dstr; dstr.sprintf("IBBConnection[%d]: %s refused.\n", d->id, d->peer.full().toLatin1().data());
279
d->m->client()->debug(dstr);
291
void IBBConnection::trySend()
293
// if we already have an active task, then don't do anything
298
if(!d->sendbuf.isEmpty()) {
300
if(d->sendbuf.size() < IBB_PACKET_SIZE)
301
a.resize(d->sendbuf.size());
303
a.resize(IBB_PACKET_SIZE);
304
memcpy(a.data(), d->sendbuf.data(), a.size());
305
d->sendbuf.resize(d->sendbuf.size() - a.size());
308
bool doClose = false;
309
if(d->sendbuf.isEmpty() && d->closePending)
313
if(a.isEmpty() && !doClose)
316
printf("IBBConnection[%d]: sending [%d] bytes ", d->id, a.size());
318
printf("and closing.\n");
320
printf("(%d bytes left)\n", d->sendbuf.size());
323
d->closePending = false;
327
d->blockSize = a.size();
328
d->j = new JT_IBB(d->m->client()->rootTask());
329
connect(d->j, SIGNAL(finished()), SLOT(ibb_finished()));
330
d->j->sendData(d->peer, d->sid, a, doClose);
335
//----------------------------------------------------------------------------
337
//----------------------------------------------------------------------------
338
class IBBManager::Private
344
IBBConnectionList activeConns;
345
IBBConnectionList incomingConns;
349
IBBManager::IBBManager(Client *parent)
355
d->ibb = new JT_IBB(d->client->rootTask(), true);
356
connect(d->ibb, SIGNAL(incomingRequest(const Jid &, const QString &, const QDomElement &)), SLOT(ibb_incomingRequest(const Jid &, const QString &, const QDomElement &)));
357
connect(d->ibb, SIGNAL(incomingData(const Jid &, const QString &, const QString &, const QByteArray &, bool)), SLOT(ibb_incomingData(const Jid &, const QString &, const QString &, const QByteArray &, bool)));
360
IBBManager::~IBBManager()
362
while (!d->incomingConns.isEmpty()) {
363
delete d->incomingConns.takeFirst();
369
Client *IBBManager::client() const
374
IBBConnection *IBBManager::takeIncoming()
376
if(d->incomingConns.isEmpty())
379
IBBConnection *c = d->incomingConns.takeFirst();
383
void IBBManager::ibb_incomingRequest(const Jid &from, const QString &id, const QDomElement &comment)
385
QString sid = genUniqueKey();
387
// create a "waiting" connection
388
IBBConnection *c = new IBBConnection(this);
389
c->waitForAccept(from, sid, comment, id);
390
d->incomingConns.append(c);
394
void IBBManager::ibb_incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close)
396
IBBConnection *c = findConnection(streamid, from);
398
d->ibb->respondError(from, id, 404, "No such stream");
401
d->ibb->respondAck(from, id);
402
c->takeIncomingData(data, close);
406
QString IBBManager::genKey() const
408
QString key = "ibb_";
410
for(int i = 0; i < 4; ++i) {
411
int word = rand() & 0xffff;
412
for(int n = 0; n < 4; ++n) {
414
s.sprintf("%x", (word >> (n * 4)) & 0xf);
422
QString IBBManager::genUniqueKey() const
429
if(!findConnection(key))
436
void IBBManager::link(IBBConnection *c)
438
d->activeConns.append(c);
441
void IBBManager::unlink(IBBConnection *c)
443
d->activeConns.removeAll(c);
446
IBBConnection *IBBManager::findConnection(const QString &sid, const Jid &peer) const
448
foreach(IBBConnection* c, d->activeConns) {
449
if(c->streamid() == sid && (peer.isEmpty() || c->peer().compare(peer)) )
455
void IBBManager::doAccept(IBBConnection *c, const QString &id)
457
d->ibb->respondSuccess(c->peer(), id, c->streamid());
460
void IBBManager::doReject(IBBConnection *c, const QString &id, int code, const QString &str)
462
d->ibb->respondError(c->peer(), id, code, str);
466
//----------------------------------------------------------------------------
468
//----------------------------------------------------------------------------
469
class JT_IBB::Private
481
JT_IBB::JT_IBB(Task *parent, bool serve)
493
void JT_IBB::request(const Jid &to, const QDomElement &comment)
495
d->mode = ModeRequest;
498
iq = createIQ(doc(), "set", to.full(), id());
499
QDomElement query = doc()->createElement("query");
500
query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
501
iq.appendChild(query);
502
query.appendChild(comment);
506
void JT_IBB::sendData(const Jid &to, const QString &streamid, const QByteArray &a, bool close)
508
d->mode = ModeSendData;
511
iq = createIQ(doc(), "set", to.full(), id());
512
QDomElement query = doc()->createElement("query");
513
query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
514
iq.appendChild(query);
515
query.appendChild(textTag(doc(), "streamid", streamid));
517
query.appendChild(textTag(doc(), "data", QCA::Base64().arrayToString(a)));
519
QDomElement c = doc()->createElement("close");
520
query.appendChild(c);
525
void JT_IBB::respondSuccess(const Jid &to, const QString &id, const QString &streamid)
527
QDomElement iq = createIQ(doc(), "result", to.full(), id);
528
QDomElement query = doc()->createElement("query");
529
query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
530
iq.appendChild(query);
531
query.appendChild(textTag(doc(), "streamid", streamid));
535
void JT_IBB::respondError(const Jid &to, const QString &id, int code, const QString &str)
537
QDomElement iq = createIQ(doc(), "error", to.full(), id);
538
QDomElement err = textTag(doc(), "error", str);
539
err.setAttribute("code", QString::number(code));
544
void JT_IBB::respondAck(const Jid &to, const QString &id)
546
QDomElement iq = createIQ(doc(), "result", to.full(), id);
555
bool JT_IBB::take(const QDomElement &e)
558
// must be an iq-set tag
559
if(e.tagName() != "iq" || e.attribute("type") != "set")
562
if(queryNS(e) != "http://jabber.org/protocol/ibb")
565
Jid from(e.attribute("from"));
566
QString id = e.attribute("id");
567
QDomElement q = queryTag(e);
570
QDomElement s = findSubTag(q, "streamid", &found);
572
QDomElement comment = findSubTag(q, "comment", &found);
573
incomingRequest(from, id, comment);
576
QString sid = tagContent(s);
579
s = findSubTag(q, "data", &found);
581
a = QCA::Base64().stringToArray(tagContent(s)).toByteArray();
582
s = findSubTag(q, "close", &found);
586
incomingData(from, sid, id, a, close);
592
Jid from(e.attribute("from"));
593
if(e.attribute("id") != id() || !d->to.compare(from))
596
if(e.attribute("type") == "result") {
597
QDomElement q = queryTag(e);
600
if(d->mode == ModeRequest) {
602
QDomElement s = findSubTag(q, "streamid", &found);
604
d->streamid = tagContent(s);
611
// thank you for the ack, kind sir
623
QString JT_IBB::streamid() const
628
Jid JT_IBB::jid() const
633
int JT_IBB::mode() const