source: psi/trunk/iris/jabber/xmpp_ibb.cpp

Last change on this file was 2, checked in by dmik, 19 years ago

Imported original Psi 0.10 sources from Affinix

File size: 13.3 KB
Line 
1/*
2 * ibb.cpp - Inband bytestream
3 * Copyright (C) 2001, 2002 Justin Karneges
4 *
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.
9 *
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.
14 *
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
18 *
19 */
20
21#include"xmpp_ibb.h"
22
23#include<qtimer.h>
24#include"xmpp_xmlcommon.h"
25#include"base64.h"
26
27#include<stdlib.h>
28
29#define IBB_PACKET_SIZE 4096
30#define IBB_PACKET_DELAY 0
31
32using namespace XMPP;
33
34static int num_conn = 0;
35static int id_conn = 0;
36
37//----------------------------------------------------------------------------
38// IBBConnection
39//----------------------------------------------------------------------------
40class IBBConnection::Private
41{
42public:
43 Private() {}
44
45 int state;
46 Jid peer;
47 QString sid;
48 IBBManager *m;
49 JT_IBB *j;
50 QDomElement comment;
51 QString iq_id;
52
53 int blockSize;
54 QByteArray recvbuf, sendbuf;
55 bool closePending, closing;
56
57 int id;
58};
59
60IBBConnection::IBBConnection(IBBManager *m)
61:ByteStream(m)
62{
63 d = new Private;
64 d->m = m;
65 d->j = 0;
66 reset();
67
68 ++num_conn;
69 d->id = id_conn++;
70 QString dstr; dstr.sprintf("IBBConnection[%d]: constructing, count=%d\n", d->id, num_conn);
71 d->m->client()->debug(dstr);
72}
73
74void IBBConnection::reset(bool clear)
75{
76 d->m->unlink(this);
77 d->state = Idle;
78 d->closePending = false;
79 d->closing = false;
80
81 delete d->j;
82 d->j = 0;
83
84 d->sendbuf.resize(0);
85 if(clear)
86 d->recvbuf.resize(0);
87}
88
89IBBConnection::~IBBConnection()
90{
91 reset(true);
92
93 --num_conn;
94 QString dstr; dstr.sprintf("IBBConnection[%d]: destructing, count=%d\n", d->id, num_conn);
95 d->m->client()->debug(dstr);
96
97 delete d;
98}
99
100void IBBConnection::connectToJid(const Jid &peer, const QDomElement &comment)
101{
102 close();
103 reset(true);
104
105 d->state = Requesting;
106 d->peer = peer;
107 d->comment = comment;
108
109 QString dstr; dstr.sprintf("IBBConnection[%d]: initiating request to %s\n", d->id, peer.full().latin1());
110 d->m->client()->debug(dstr);
111
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);
115 d->j->go(true);
116}
117
118void IBBConnection::accept()
119{
120 if(d->state != WaitingForAccept)
121 return;
122
123 QString dstr; dstr.sprintf("IBBConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1());
124 d->m->client()->debug(dstr);
125
126 d->m->doAccept(this, d->iq_id);
127 d->state = Active;
128 d->m->link(this);
129}
130
131void IBBConnection::close()
132{
133 if(d->state == Idle)
134 return;
135
136 if(d->state == WaitingForAccept) {
137 d->m->doReject(this, d->iq_id, 403, "Rejected");
138 reset();
139 return;
140 }
141
142 QString dstr; dstr.sprintf("IBBConnection[%d]: closing\n", d->id);
143 d->m->client()->debug(dstr);
144
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;
149 trySend();
150 return;
151 }
152
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);
156 j->go(true);
157 }
158
159 reset();
160}
161
162int IBBConnection::state() const
163{
164 return d->state;
165}
166
167Jid IBBConnection::peer() const
168{
169 return d->peer;
170}
171
172QString IBBConnection::streamid() const
173{
174 return d->sid;
175}
176
177QDomElement IBBConnection::comment() const
178{
179 return d->comment;
180}
181
182bool IBBConnection::isOpen() const
183{
184 if(d->state == Active)
185 return true;
186 else
187 return false;
188}
189
190void IBBConnection::write(const QByteArray &a)
191{
192 if(d->state != Active || d->closePending || d->closing)
193 return;
194
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());
199
200 trySend();
201}
202
203QByteArray IBBConnection::read(int)
204{
205 // TODO: obey argument
206 QByteArray a = d->recvbuf.copy();
207 d->recvbuf.resize(0);
208 return a;
209}
210
211int IBBConnection::bytesAvailable() const
212{
213 return d->recvbuf.size();
214}
215
216int IBBConnection::bytesToWrite() const
217{
218 return d->sendbuf.size();
219}
220
221void IBBConnection::waitForAccept(const Jid &peer, const QString &sid, const QDomElement &comment, const QString &iq_id)
222{
223 close();
224 reset(true);
225
226 d->state = WaitingForAccept;
227 d->peer = peer;
228 d->sid = sid;
229 d->comment = comment;
230 d->iq_id = iq_id;
231}
232
233void IBBConnection::takeIncomingData(const QByteArray &a, bool close)
234{
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());
239
240 readyRead();
241
242 if(close) {
243 reset();
244 connectionClosed();
245 }
246}
247
248void IBBConnection::ibb_finished()
249{
250 JT_IBB *j = d->j;
251 d->j = 0;
252
253 if(j->success()) {
254 if(j->mode() == JT_IBB::ModeRequest) {
255 d->sid = j->streamid();
256
257 QString dstr; dstr.sprintf("IBBConnection[%d]: %s [%s] accepted.\n", d->id, d->peer.full().latin1(), d->sid.latin1());
258 d->m->client()->debug(dstr);
259
260 d->state = Active;
261 d->m->link(this);
262 connected();
263 }
264 else {
265 bytesWritten(d->blockSize);
266
267 if(d->closing) {
268 reset();
269 delayedCloseFinished();
270 }
271
272 if(!d->sendbuf.isEmpty() || d->closePending)
273 QTimer::singleShot(IBB_PACKET_DELAY, this, SLOT(trySend()));
274 }
275 }
276 else {
277 if(j->mode() == JT_IBB::ModeRequest) {
278 QString dstr; dstr.sprintf("IBBConnection[%d]: %s refused.\n", d->id, d->peer.full().latin1());
279 d->m->client()->debug(dstr);
280
281 reset(true);
282 error(ErrRequest);
283 }
284 else {
285 reset(true);
286 error(ErrData);
287 }
288 }
289}
290
291void IBBConnection::trySend()
292{
293 // if we already have an active task, then don't do anything
294 if(d->j)
295 return;
296
297 QByteArray a;
298 if(!d->sendbuf.isEmpty()) {
299 // take a chunk
300 if(d->sendbuf.size() < IBB_PACKET_SIZE)
301 a.resize(d->sendbuf.size());
302 else
303 a.resize(IBB_PACKET_SIZE);
304 memcpy(a.data(), d->sendbuf.data(), a.size());
305 d->sendbuf.resize(d->sendbuf.size() - a.size());
306 }
307
308 bool doClose = false;
309 if(d->sendbuf.isEmpty() && d->closePending)
310 doClose = true;
311
312 // null operation?
313 if(a.isEmpty() && !doClose)
314 return;
315
316 printf("IBBConnection[%d]: sending [%d] bytes ", d->id, a.size());
317 if(doClose)
318 printf("and closing.\n");
319 else
320 printf("(%d bytes left)\n", d->sendbuf.size());
321
322 if(doClose) {
323 d->closePending = false;
324 d->closing = true;
325 }
326
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);
331 d->j->go(true);
332}
333
334
335//----------------------------------------------------------------------------
336// IBBManager
337//----------------------------------------------------------------------------
338class IBBManager::Private
339{
340public:
341 Private() {}
342
343 Client *client;
344 IBBConnectionList activeConns;
345 IBBConnectionList incomingConns;
346 JT_IBB *ibb;
347};
348
349IBBManager::IBBManager(Client *parent)
350:QObject(parent)
351{
352 d = new Private;
353 d->client = parent;
354
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)));
358}
359
360IBBManager::~IBBManager()
361{
362 d->incomingConns.setAutoDelete(true);
363 d->incomingConns.clear();
364 delete d->ibb;
365 delete d;
366}
367
368Client *IBBManager::client() const
369{
370 return d->client;
371}
372
373IBBConnection *IBBManager::takeIncoming()
374{
375 if(d->incomingConns.isEmpty())
376 return 0;
377
378 IBBConnection *c = d->incomingConns.getFirst();
379 d->incomingConns.removeRef(c);
380 return c;
381}
382
383void IBBManager::ibb_incomingRequest(const Jid &from, const QString &id, const QDomElement &comment)
384{
385 QString sid = genUniqueKey();
386
387 // create a "waiting" connection
388 IBBConnection *c = new IBBConnection(this);
389 c->waitForAccept(from, sid, comment, id);
390 d->incomingConns.append(c);
391 incomingReady();
392}
393
394void IBBManager::ibb_incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close)
395{
396 IBBConnection *c = findConnection(streamid, from);
397 if(!c) {
398 d->ibb->respondError(from, id, 404, "No such stream");
399 }
400 else {
401 d->ibb->respondAck(from, id);
402 c->takeIncomingData(data, close);
403 }
404}
405
406QString IBBManager::genKey() const
407{
408 QString key = "ibb_";
409
410 for(int i = 0; i < 4; ++i) {
411 int word = rand() & 0xffff;
412 for(int n = 0; n < 4; ++n) {
413 QString s;
414 s.sprintf("%x", (word >> (n * 4)) & 0xf);
415 key.append(s);
416 }
417 }
418
419 return key;
420}
421
422QString IBBManager::genUniqueKey() const
423{
424 // get unused key
425 QString key;
426 while(1) {
427 key = genKey();
428
429 if(!findConnection(key))
430 break;
431 }
432
433 return key;
434}
435
436void IBBManager::link(IBBConnection *c)
437{
438 d->activeConns.append(c);
439}
440
441void IBBManager::unlink(IBBConnection *c)
442{
443 d->activeConns.removeRef(c);
444}
445
446IBBConnection *IBBManager::findConnection(const QString &sid, const Jid &peer) const
447{
448 IBBConnectionListIt it(d->activeConns);
449 for(IBBConnection *c; (c = it.current()); ++it) {
450 if(c->streamid() == sid && (peer.isEmpty() || c->peer().compare(peer)) )
451 return c;
452 }
453 return 0;
454}
455
456void IBBManager::doAccept(IBBConnection *c, const QString &id)
457{
458 d->ibb->respondSuccess(c->peer(), id, c->streamid());
459}
460
461void IBBManager::doReject(IBBConnection *c, const QString &id, int code, const QString &str)
462{
463 d->ibb->respondError(c->peer(), id, code, str);
464}
465
466
467//----------------------------------------------------------------------------
468// JT_IBB
469//----------------------------------------------------------------------------
470class JT_IBB::Private
471{
472public:
473 Private() {}
474
475 QDomElement iq;
476 int mode;
477 bool serve;
478 Jid to;
479 QString streamid;
480};
481
482JT_IBB::JT_IBB(Task *parent, bool serve)
483:Task(parent)
484{
485 d = new Private;
486 d->serve = serve;
487}
488
489JT_IBB::~JT_IBB()
490{
491 delete d;
492}
493
494void JT_IBB::request(const Jid &to, const QDomElement &comment)
495{
496 d->mode = ModeRequest;
497 QDomElement iq;
498 d->to = to;
499 iq = createIQ(doc(), "set", to.full(), id());
500 QDomElement query = doc()->createElement("query");
501 query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
502 iq.appendChild(query);
503 query.appendChild(comment);
504 d->iq = iq;
505}
506
507void JT_IBB::sendData(const Jid &to, const QString &streamid, const QByteArray &a, bool close)
508{
509 d->mode = ModeSendData;
510 QDomElement iq;
511 d->to = to;
512 iq = createIQ(doc(), "set", to.full(), id());
513 QDomElement query = doc()->createElement("query");
514 query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
515 iq.appendChild(query);
516 query.appendChild(textTag(doc(), "streamid", streamid));
517 if(!a.isEmpty())
518 query.appendChild(textTag(doc(), "data", Base64::arrayToString(a)));
519 if(close) {
520 QDomElement c = doc()->createElement("close");
521 query.appendChild(c);
522 }
523 d->iq = iq;
524}
525
526void JT_IBB::respondSuccess(const Jid &to, const QString &id, const QString &streamid)
527{
528 QDomElement iq = createIQ(doc(), "result", to.full(), id);
529 QDomElement query = doc()->createElement("query");
530 query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
531 iq.appendChild(query);
532 query.appendChild(textTag(doc(), "streamid", streamid));
533 send(iq);
534}
535
536void JT_IBB::respondError(const Jid &to, const QString &id, int code, const QString &str)
537{
538 QDomElement iq = createIQ(doc(), "error", to.full(), id);
539 QDomElement err = textTag(doc(), "error", str);
540 err.setAttribute("code", QString::number(code));
541 iq.appendChild(err);
542 send(iq);
543}
544
545void JT_IBB::respondAck(const Jid &to, const QString &id)
546{
547 QDomElement iq = createIQ(doc(), "result", to.full(), id);
548 send(iq);
549}
550
551void JT_IBB::onGo()
552{
553 send(d->iq);
554}
555
556bool JT_IBB::take(const QDomElement &e)
557{
558 if(d->serve) {
559 // must be an iq-set tag
560 if(e.tagName() != "iq" || e.attribute("type") != "set")
561 return false;
562
563 if(queryNS(e) != "http://jabber.org/protocol/ibb")
564 return false;
565
566 Jid from(e.attribute("from"));
567 QString id = e.attribute("id");
568 QDomElement q = queryTag(e);
569
570 bool found;
571 QDomElement s = findSubTag(q, "streamid", &found);
572 if(!found) {
573 QDomElement comment = findSubTag(q, "comment", &found);
574 incomingRequest(from, id, comment);
575 }
576 else {
577 QString sid = tagContent(s);
578 QByteArray a;
579 bool close = false;
580 s = findSubTag(q, "data", &found);
581 if(found)
582 a = Base64::stringToArray(tagContent(s));
583 s = findSubTag(q, "close", &found);
584 if(found)
585 close = true;
586
587 incomingData(from, sid, id, a, close);
588 }
589
590 return true;
591 }
592 else {
593 Jid from(e.attribute("from"));
594 if(e.attribute("id") != id() || !d->to.compare(from))
595 return false;
596
597 if(e.attribute("type") == "result") {
598 QDomElement q = queryTag(e);
599
600 // request
601 if(d->mode == ModeRequest) {
602 bool found;
603 QDomElement s = findSubTag(q, "streamid", &found);
604 if(found)
605 d->streamid = tagContent(s);
606 else
607 d->streamid = "";
608 setSuccess();
609 }
610 // sendData
611 else {
612 // thank you for the ack, kind sir
613 setSuccess();
614 }
615 }
616 else {
617 setError(e);
618 }
619
620 return true;
621 }
622}
623
624QString JT_IBB::streamid() const
625{
626 return d->streamid;
627}
628
629Jid JT_IBB::jid() const
630{
631 return d->to;
632}
633
634int JT_IBB::mode() const
635{
636 return d->mode;
637}
638
Note: See TracBrowser for help on using the repository browser.