1 /*
2  * Copyright (C) 2007  Justin Karneges
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301  USA
18  *
19  */
20 
21 #include "irisnetplugin.h"
22 
23 #include <QtCore>
24 #include <QtNetwork>
25 #include "qdnssd.h"
26 
27 // for ntohl
28 #ifdef Q_OS_WIN
29 # include <windows.h>
30 #else
31 # include <netinet/in.h>
32 #endif
33 
nameToDottedString(const QByteArray & in)34 static QByteArray nameToDottedString(const QByteArray &in)
35 {
36 	QByteArray out;
37 	int at = 0;
38 	while(at < in.size())
39 	{
40 		int len = in[at++];
41 		if(len > 0)
42 			out += in.mid(at, len);
43 		out += '.';
44 		at += len;
45 	}
46 	return out;
47 }
48 
textsToAttribs(const QList<QByteArray> & texts)49 static QMap<QString,QByteArray> textsToAttribs(const QList<QByteArray> &texts)
50 {
51 	QMap<QString,QByteArray> out;
52 	foreach(const QByteArray &a, texts)
53 	{
54 		QString key;
55 		QByteArray value;
56 		int x = a.indexOf('=');
57 		if(x != -1)
58 		{
59 			key = QString::fromLatin1(a.mid(0, x));
60 			value = a.mid(x + 1);
61 		}
62 		else
63 		{
64 			key = QString::fromLatin1(a);
65 		}
66 
67 		out.insert(key, value);
68 	}
69 	return out;
70 }
71 
attribsToTxtRecord(const QMap<QString,QByteArray> & attribs)72 static QByteArray attribsToTxtRecord(const QMap<QString,QByteArray> &attribs)
73 {
74 	QList<QByteArray> texts;
75 	QMapIterator<QString,QByteArray> it(attribs);
76 	while(it.hasNext())
77 	{
78 		it.next();
79 		QByteArray line = it.key().toLatin1() + '=' + it.value();
80 		texts += line;
81 	}
82 	return QDnsSd::createTxtRecord(texts);
83 }
84 
85 // returns a list of 3 items, or an empty list on error
nameToInstanceParts(const QByteArray & name)86 static QList<QByteArray> nameToInstanceParts(const QByteArray &name)
87 {
88 	// FIXME: improve this parsing...  (what about escaping??)
89 	int at = name.indexOf('.');
90 	QByteArray sname = name.mid(0, at);
91 	++at;
92 	int next = name.indexOf('.', at);
93 	++next;
94 	next = name.indexOf('.', next);
95 	QByteArray stype = name.mid(at, next - at);
96 	at = next + 1;
97 	QByteArray sdomain = name.mid(at);
98 
99 	QList<QByteArray> out;
100 	out += sname;
101 	out += stype;
102 	out += sdomain;
103 	return out;
104 }
105 
importQDnsSdRecord(const QDnsSd::Record & in)106 static XMPP::NameRecord importQDnsSdRecord(const QDnsSd::Record &in)
107 {
108 	XMPP::NameRecord out;
109 	switch(in.rrtype)
110 	{
111 		case 1: // A
112 		{
113 			quint32 *p = (quint32 *)in.rdata.data();
114 			out.setAddress(QHostAddress(ntohl(*p)));
115 		}
116 		break;
117 
118 		case 28: // AAAA
119 		{
120 			out.setAddress(QHostAddress((quint8 *)in.rdata.data()));
121 		}
122 		break;
123 
124 		case 12: // PTR
125 		{
126 			out.setPtr(nameToDottedString(in.rdata));
127 		}
128 		break;
129 
130 		case 10: // NULL
131 		{
132 			out.setNull(in.rdata);
133 		}
134 		break;
135 
136 		case 16: // TXT
137 		{
138 			QList<QByteArray> txtEntries = QDnsSd::parseTxtRecord(in.rdata);
139 			if(txtEntries.isEmpty())
140 				return out;
141 			out.setTxt(txtEntries);
142 		}
143 		break;
144 
145 		default: // unsupported
146 		{
147 			return out;
148 		}
149 	}
150 
151 	out.setOwner(in.name);
152 	out.setTtl(in.ttl);
153 	return out;
154 }
155 
156 namespace {
157 
158 class QDnsSdDelegate
159 {
160 public:
~QDnsSdDelegate()161 	virtual ~QDnsSdDelegate()
162 	{
163 	}
164 
dns_queryResult(int id,const QDnsSd::QueryResult & result)165 	virtual void dns_queryResult(int id, const QDnsSd::QueryResult &result)
166 	{
167 		Q_UNUSED(id);
168 		Q_UNUSED(result);
169 	}
170 
dns_browseResult(int id,const QDnsSd::BrowseResult & result)171 	virtual void dns_browseResult(int id, const QDnsSd::BrowseResult &result)
172 	{
173 		Q_UNUSED(id);
174 		Q_UNUSED(result);
175 	}
176 
dns_resolveResult(int id,const QDnsSd::ResolveResult & result)177 	virtual void dns_resolveResult(int id, const QDnsSd::ResolveResult &result)
178 	{
179 		Q_UNUSED(id);
180 		Q_UNUSED(result);
181 	}
182 
dns_regResult(int id,const QDnsSd::RegResult & result)183 	virtual void dns_regResult(int id, const QDnsSd::RegResult &result)
184 	{
185 		Q_UNUSED(id);
186 		Q_UNUSED(result);
187 	}
188 };
189 
190 class IdManager
191 {
192 private:
193 	QSet<int> set;
194 	int at;
195 
bump_at()196 	inline void bump_at()
197 	{
198 		if(at == 0x7fffffff)
199 			at = 0;
200 		else
201 			++at;
202 	}
203 
204 public:
IdManager()205 	IdManager() :
206 		at(0)
207 	{
208 	}
209 
reserveId()210 	int reserveId()
211 	{
212 		while(1)
213 		{
214 			if(!set.contains(at))
215 			{
216 				int id = at;
217 				set.insert(id);
218 				bump_at();
219 				return id;
220 			}
221 
222 			bump_at();
223 		}
224 	}
225 
releaseId(int id)226 	void releaseId(int id)
227 	{
228 		set.remove(id);
229 	}
230 };
231 
232 }
233 
234 //----------------------------------------------------------------------------
235 // AppleProvider
236 //----------------------------------------------------------------------------
237 class AppleProvider : public XMPP::IrisNetProvider
238 {
239 	Q_OBJECT
240 	Q_INTERFACES(XMPP::IrisNetProvider);
241 public:
242 	QDnsSd dns;
243 	QHash<int,QDnsSdDelegate*> delegateById;
244 
AppleProvider()245 	AppleProvider() :
246 		dns(this)
247 	{
248 		connect(&dns, SIGNAL(queryResult(int,QDnsSd::QueryResult)), SLOT(dns_queryResult(int,QDnsSd::QueryResult)));
249 		connect(&dns, SIGNAL(browseResult(int,QDnsSd::BrowseResult)), SLOT(dns_browseResult(int,QDnsSd::BrowseResult)));
250 		connect(&dns, SIGNAL(resolveResult(int,QDnsSd::ResolveResult)), SLOT(dns_resolveResult(int,QDnsSd::ResolveResult)));
251 		connect(&dns, SIGNAL(regResult(int,QDnsSd::RegResult)), SLOT(dns_regResult(int,QDnsSd::RegResult)));
252 	}
253 
254 	virtual XMPP::NameProvider *createNameProviderInternet();
255 	virtual XMPP::NameProvider *createNameProviderLocal();
256 	virtual XMPP::ServiceProvider *createServiceProvider();
257 
query(QDnsSdDelegate * p,const QByteArray & name,int qType)258 	int query(QDnsSdDelegate *p, const QByteArray &name, int qType)
259 	{
260 		int id = dns.query(name, qType);
261 		delegateById[id] = p;
262 		return id;
263 	}
264 
browse(QDnsSdDelegate * p,const QByteArray & serviceType,const QByteArray & domain)265 	int browse(QDnsSdDelegate *p, const QByteArray &serviceType, const QByteArray &domain)
266 	{
267 		int id = dns.browse(serviceType, domain);
268 		delegateById[id] = p;
269 		return id;
270 	}
271 
resolve(QDnsSdDelegate * p,const QByteArray & serviceName,const QByteArray & serviceType,const QByteArray & domain)272 	int resolve(QDnsSdDelegate *p, const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain)
273 	{
274 		int id = dns.resolve(serviceName, serviceType, domain);
275 		delegateById[id] = p;
276 		return id;
277 	}
278 
reg(QDnsSdDelegate * p,const QByteArray & serviceName,const QByteArray & serviceType,const QByteArray & domain,int port,const QByteArray & txtRecord)279 	int reg(QDnsSdDelegate *p, const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain, int port, const QByteArray &txtRecord)
280 	{
281 		int id = dns.reg(serviceName, serviceType, domain, port, txtRecord);
282 		delegateById[id] = p;
283 		return id;
284 	}
285 
stop(int id)286 	void stop(int id)
287 	{
288 		delegateById.remove(id);
289 		dns.stop(id);
290 	}
291 
stop_all(QDnsSdDelegate * p)292 	void stop_all(QDnsSdDelegate *p)
293 	{
294 		QList<int> ids;
295 		QHashIterator<int,QDnsSdDelegate*> it(delegateById);
296 		while(it.hasNext())
297 		{
298 			it.next();
299 			if(it.value() == p)
300 				ids += it.key();
301 		}
302 		foreach(int id, ids)
303 			stop(id);
304 	}
305 
306 private slots:
dns_queryResult(int id,const QDnsSd::QueryResult & result)307 	void dns_queryResult(int id, const QDnsSd::QueryResult &result)
308 	{
309 		delegateById[id]->dns_queryResult(id, result);
310 	}
311 
dns_browseResult(int id,const QDnsSd::BrowseResult & result)312 	void dns_browseResult(int id, const QDnsSd::BrowseResult &result)
313 	{
314 		delegateById[id]->dns_browseResult(id, result);
315 	}
316 
dns_resolveResult(int id,const QDnsSd::ResolveResult & result)317 	void dns_resolveResult(int id, const QDnsSd::ResolveResult &result)
318 	{
319 		delegateById[id]->dns_resolveResult(id, result);
320 	}
321 
dns_regResult(int id,const QDnsSd::RegResult & result)322 	void dns_regResult(int id, const QDnsSd::RegResult &result)
323 	{
324 		delegateById[id]->dns_regResult(id, result);
325 	}
326 };
327 
328 //----------------------------------------------------------------------------
329 // AppleBrowseSession
330 //----------------------------------------------------------------------------
331 // only use this class for a single browse.  if you want to browse again,
332 //   create a new object.
333 class AppleBrowse : public QObject, public QDnsSdDelegate
334 {
335 	Q_OBJECT
336 public:
337 	AppleProvider *global;
338 	int browse_id;
339 	QList<XMPP::ServiceInstance> instances;
340 	QHash<int,QByteArray> pendingByQueryId; // waiting for TXT
341 
AppleBrowse(AppleProvider * _global,QObject * parent=0)342 	AppleBrowse(AppleProvider *_global, QObject *parent = 0) :
343 		QObject(parent),
344 		global(_global),
345 		browse_id(-1)
346 	{
347 		connect(this, SIGNAL(unavailable_p(XMPP::ServiceInstance)), SIGNAL(unavailable(XMPP::ServiceInstance)));
348 	}
349 
~AppleBrowse()350 	~AppleBrowse()
351 	{
352 		global->stop_all(this);
353 	}
354 
browse(const QString & type,const QString & domain)355 	void browse(const QString &type, const QString &domain)
356 	{
357 		browse_id = global->browse(this, type.toUtf8(), domain.toUtf8());
358 	}
359 
360 signals:
361 	void available(const XMPP::ServiceInstance &instance);
362 	void unavailable(const XMPP::ServiceInstance &instance);
363 	void error();
364 
365 	// emit delayed
366 	void unavailable_p(const XMPP::ServiceInstance &instance);
367 
368 protected:
dns_browseResult(int id,const QDnsSd::BrowseResult & result)369 	virtual void dns_browseResult(int id, const QDnsSd::BrowseResult &result)
370 	{
371 		Q_UNUSED(id);
372 
373 		if(!result.success)
374 		{
375 			emit error();
376 			return;
377 		}
378 
379 		foreach(const QDnsSd::BrowseEntry &e, result.entries)
380 		{
381 			XMPP::ServiceInstance si(e.serviceName, e.serviceType, e.replyDomain, QMap<QString,QByteArray>());
382 
383 			if(e.added)
384 			{
385 				int query_id = global->query(this, si.name(), 16); // 16 == TXT
386 				pendingByQueryId[query_id] = si.name();
387 			}
388 			else // removed
389 			{
390 				// emit these queued for SS.  no worry of SR since
391 				//   the browse operation is not cancellable.
392 				for(int n = 0; n < instances.count(); ++n)
393 				{
394 					const XMPP::ServiceInstance &i = instances[n];
395 					if(i.name() == si.name())
396 					{
397 						emit unavailable_p(i);
398 						instances.removeAt(n);
399 						--n; // adjust position
400 					}
401 				}
402 			}
403 		}
404 	}
405 
dns_queryResult(int id,const QDnsSd::QueryResult & result)406 	virtual void dns_queryResult(int id, const QDnsSd::QueryResult &result)
407 	{
408 		if(!result.success)
409 		{
410 			// if we get here, then it means we received a browse
411 			//   entry, but could not fetch its TXT record.  if
412 			//   that happens, cancel the query and drop the
413 			//   browse entry.
414 			global->stop(id);
415 			pendingByQueryId.remove(id);
416 			return;
417 		}
418 
419 		// qdnssd guarantees at least one answer
420 		Q_ASSERT(!result.records.isEmpty());
421 
422 		// only the first entry matters, and it must be an added TXT
423 		if(!result.records[0].added || result.records[0].rrtype != 16)
424 			return;
425 
426 		// we only care about one answer
427 		QByteArray name = pendingByQueryId[id];
428 		QList<QByteArray> parts = nameToInstanceParts(name);
429 		if(parts.isEmpty())
430 		{
431 			// TODO: error
432 			Q_ASSERT(0);
433 		}
434 
435 		global->stop(id);
436 		pendingByQueryId.remove(id);
437 
438 		XMPP::NameRecord rec = importQDnsSdRecord(result.records[0]);
439 
440 		// bad answer?
441 		if(rec.isNull())
442 			return;
443 
444 		QMap<QString,QByteArray> attribs = textsToAttribs(rec.texts());
445 		// FIXME: conversion/escaping?
446 		XMPP::ServiceInstance si(QString::fromUtf8(parts[0]), QString::fromUtf8(parts[1]), QString::fromUtf8(parts[2]), attribs);
447 
448 		// does qdnssd guarantee we won't receive dups?
449 		bool found = false;
450 		foreach(const XMPP::ServiceInstance &i, instances)
451 		{
452 			if(i.name() == si.name())
453 			{
454 				found = true;
455 				break;
456 			}
457 		}
458 		Q_ASSERT(!found);
459 
460 		instances += si;
461 		emit available(si);
462 	}
463 };
464 
465 //----------------------------------------------------------------------------
466 // AppleBrowseLookup
467 //----------------------------------------------------------------------------
468 // only use this class for a single lookup.  if you want to lookup again,
469 //   create a new object.
470 class AppleBrowseLookup : public QObject, public QDnsSdDelegate
471 {
472 	Q_OBJECT
473 public:
474 	AppleProvider *global;
475 	int resolve_id;
476 	XMPP::NameResolver nameResolverAaaa;
477 	XMPP::NameResolver nameResolverA;
478 	bool activeAaaa;
479 	bool activeA;
480 	QTimer waitTimer;
481 	QByteArray host;
482 	QHostAddress addr4;
483 	QHostAddress addr6;
484 	int port;
485 
AppleBrowseLookup(AppleProvider * _global,QObject * parent=0)486 	AppleBrowseLookup(AppleProvider *_global, QObject *parent = 0) :
487 		QObject(parent),
488 		global(_global),
489 		resolve_id(-1),
490 		nameResolverAaaa(this),
491 		nameResolverA(this),
492 		activeAaaa(false),
493 		activeA(false),
494 		waitTimer(this)
495 	{
496 		connect(&nameResolverAaaa, SIGNAL(resultsReady(QList<XMPP::NameRecord>)),
497 			SLOT(nameAaaa_resultsReady(QList<XMPP::NameRecord>)));
498 		connect(&nameResolverAaaa, SIGNAL(error(XMPP::NameResolver::Error)),
499 			SLOT(nameAaaa_error(XMPP::NameResolver::Error)));
500 
501 		connect(&nameResolverA, SIGNAL(resultsReady(QList<XMPP::NameRecord>)),
502 			SLOT(nameA_resultsReady(QList<XMPP::NameRecord>)));
503 		connect(&nameResolverA, SIGNAL(error(XMPP::NameResolver::Error)),
504 			SLOT(nameA_error(XMPP::NameResolver::Error)));
505 
506 		connect(&waitTimer, SIGNAL(timeout()), SLOT(waitTimer_timeout()));
507 		waitTimer.setSingleShot(true);
508 	}
509 
~AppleBrowseLookup()510 	~AppleBrowseLookup()
511 	{
512 		global->stop_all(this);
513 	}
514 
resolve(const QByteArray & instance,const QByteArray & type,const QByteArray & domain)515 	void resolve(const QByteArray &instance, const QByteArray &type, const QByteArray &domain)
516 	{
517 		resolve_id = global->resolve(this, instance, type, domain);
518 	}
519 
520 signals:
521 	// emits at least 1 and at most 2
522 	void finished(const QList<QHostAddress> &addrs, int port);
523 	void error();
524 
525 protected:
dns_resolveResult(int id,const QDnsSd::ResolveResult & result)526 	void dns_resolveResult(int id, const QDnsSd::ResolveResult &result)
527 	{
528 		// there is only one response, so deregister
529 		global->stop(id);
530 
531 		if(!result.success)
532 		{
533 			emit error();
534 			return;
535 		}
536 
537 		host = result.hostTarget;
538 		port = result.port;
539 
540 		activeAaaa = true;
541 		activeA = true;
542 		nameResolverAaaa.start(host, XMPP::NameRecord::Aaaa);
543 		nameResolverA.start(host, XMPP::NameRecord::A);
544 		waitTimer.start(500); // 500ms cut-off time, take what we have and run
545 	}
546 
547 private slots:
nameAaaa_resultsReady(const QList<XMPP::NameRecord> & results)548 	void nameAaaa_resultsReady(const QList<XMPP::NameRecord> &results)
549 	{
550 		// nameresolver guarantees at least one result, and we only
551 		//   care about the first
552 		addr6 = results[0].address();
553 		activeAaaa = false;
554 		tryDone();
555 	}
556 
nameAaaa_error(XMPP::NameResolver::Error e)557 	void nameAaaa_error(XMPP::NameResolver::Error e)
558 	{
559 		Q_UNUSED(e);
560 		activeAaaa = false;
561 		tryDone();
562 	}
563 
nameA_resultsReady(const QList<XMPP::NameRecord> & results)564 	void nameA_resultsReady(const QList<XMPP::NameRecord> &results)
565 	{
566 		// nameresolver guarantees at least one result, and we only
567 		//   care about the first
568 		addr4 = results[0].address();
569 		activeA = false;
570 		tryDone();
571 	}
572 
nameA_error(XMPP::NameResolver::Error e)573 	void nameA_error(XMPP::NameResolver::Error e)
574 	{
575 		Q_UNUSED(e);
576 		activeA = false;
577 		tryDone();
578 	}
579 
waitTimer_timeout()580 	void waitTimer_timeout()
581 	{
582 		tryDone();
583 	}
584 
585 private:
tryDone()586 	void tryDone()
587 	{
588 		// we're done if both resolves are inactive and we have no
589 		//   results, or if the wait timer ends and we have at least
590 		//   one result
591 
592 		if(!activeAaaa && !activeA && addr6.isNull() && addr4.isNull())
593 		{
594 			nameResolverAaaa.stop();
595 			nameResolverA.stop();
596 			waitTimer.stop();
597 
598 			emit error();
599 			return;
600 		}
601 
602 		if(!waitTimer.isActive() && (!addr6.isNull() || !addr4.isNull()))
603 		{
604 			nameResolverAaaa.stop();
605 			nameResolverA.stop();
606 
607 			QList<QHostAddress> out;
608 			if(!addr4.isNull())
609 				out += addr4;
610 			if(!addr6.isNull())
611 				out += addr6;
612 			emit finished(out, port);
613 		}
614 	}
615 };
616 
617 //----------------------------------------------------------------------------
618 // AppleNameProvider
619 //----------------------------------------------------------------------------
620 class AppleNameProvider : public XMPP::NameProvider, public QDnsSdDelegate
621 {
622 	Q_OBJECT
623 public:
624 	AppleProvider *global;
625 
AppleNameProvider(AppleProvider * parent)626 	AppleNameProvider(AppleProvider *parent) :
627 		NameProvider(parent),
628 		global(parent)
629 	{
630 	}
631 
~AppleNameProvider()632 	~AppleNameProvider()
633 	{
634 		global->stop_all(this);
635 	}
636 
supportsLongLived() const637 	virtual bool supportsLongLived() const
638 	{
639 		return true;
640 	}
641 
supportsRecordType(int type) const642 	virtual bool supportsRecordType(int type) const
643 	{
644 		// all record types supported
645 		Q_UNUSED(type);
646 		return true;
647 	}
648 
resolve_start(const QByteArray & name,int qType,bool longLived)649 	virtual int resolve_start(const QByteArray &name, int qType, bool longLived)
650 	{
651 		Q_UNUSED(longLived); // query is always long lived
652 		return global->query(this, name, qType);
653 	}
654 
resolve_stop(int id)655 	virtual void resolve_stop(int id)
656 	{
657 		global->stop(id);
658 	}
659 
660 protected:
dns_queryResult(int id,const QDnsSd::QueryResult & result)661 	virtual void dns_queryResult(int id, const QDnsSd::QueryResult &result)
662 	{
663 		if(!result.success)
664 		{
665 			emit resolve_error(id, XMPP::NameResolver::ErrorGeneric);
666 			return;
667 		}
668 
669 		QList<XMPP::NameRecord> results;
670 		foreach(const QDnsSd::Record &rec, result.records)
671 		{
672 			XMPP::NameRecord nr = importQDnsSdRecord(rec);
673 
674 			// unsupported type
675 			if(nr.isNull())
676 				continue;
677 
678 			// if removed, ensure ttl is 0
679 			if(!rec.added)
680 				nr.setTtl(0);
681 
682 			results += nr;
683 		}
684 
685 		emit resolve_resultsReady(id, results);
686 	}
687 };
688 
689 //----------------------------------------------------------------------------
690 // AppleServiceProvider
691 //----------------------------------------------------------------------------
692 class AppleServiceProvider : public XMPP::ServiceProvider, public QDnsSdDelegate
693 {
694 	Q_OBJECT
695 public:
696 	class Browse
697 	{
698 	public:
699 		AppleServiceProvider *parent;
700 		int id;
701 		AppleBrowse *browse;
702 
Browse(AppleServiceProvider * _parent)703 		Browse(AppleServiceProvider *_parent) :
704 			parent(_parent),
705 			id(-1),
706 			browse(0)
707 		{
708 		}
709 
~Browse()710 		~Browse()
711 		{
712 			delete browse;
713 			parent->idManager.releaseId(id);
714 		}
715 	};
716 
717 	class Resolve
718 	{
719 	public:
720 		AppleServiceProvider *parent;
721 		int id;
722 		AppleBrowseLookup *resolve;
723 
Resolve(AppleServiceProvider * _parent)724 		Resolve(AppleServiceProvider *_parent) :
725 			parent(_parent),
726 			id(-1),
727 			resolve(0)
728 		{
729 		}
730 
~Resolve()731 		~Resolve()
732 		{
733 			delete resolve;
734 			parent->idManager.releaseId(id);
735 		}
736 	};
737 
738 	AppleProvider *global;
739 	QList<Browse*> browseList;
740 	QList<Resolve*> resolveList;
741 	IdManager idManager;
742 
AppleServiceProvider(AppleProvider * parent)743 	AppleServiceProvider(AppleProvider *parent) :
744 		ServiceProvider(parent),
745 		global(parent)
746 	{
747 	}
748 
~AppleServiceProvider()749 	~AppleServiceProvider()
750 	{
751 		qDeleteAll(resolveList);
752 		qDeleteAll(browseList);
753 		global->stop_all(this);
754 	}
755 
indexOfBrowseByBrowse(AppleBrowse * browse) const756 	int indexOfBrowseByBrowse(AppleBrowse *browse) const
757 	{
758 		for(int n = 0; n < browseList.count(); ++n)
759 		{
760 			if(browseList[n]->browse == browse)
761 				return n;
762 		}
763 		return -1;
764 	}
765 
indexOfBrowseById(int id) const766 	int indexOfBrowseById(int id) const
767 	{
768 		for(int n = 0; n < browseList.count(); ++n)
769 		{
770 			if(browseList[n]->id == id)
771 				return n;
772 		}
773 		return -1;
774 	}
775 
indexOfResolveByResolve(AppleBrowseLookup * resolve) const776 	int indexOfResolveByResolve(AppleBrowseLookup *resolve) const
777 	{
778 		for(int n = 0; n < resolveList.count(); ++n)
779 		{
780 			if(resolveList[n]->resolve == resolve)
781 				return n;
782 		}
783 		return -1;
784 	}
785 
indexOfResolveById(int id) const786 	int indexOfResolveById(int id) const
787 	{
788 		for(int n = 0; n < resolveList.count(); ++n)
789 		{
790 			if(resolveList[n]->id == id)
791 				return n;
792 		}
793 		return -1;
794 	}
795 
browse_start(const QString & type,const QString & domain)796 	virtual int browse_start(const QString &type, const QString &domain)
797 	{
798 		Browse *b = new Browse(this);
799 		b->id = idManager.reserveId();
800 		b->browse = new AppleBrowse(global, this);
801 		connect(b->browse, SIGNAL(available(XMPP::ServiceInstance)), SLOT(browse_available(XMPP::ServiceInstance)));
802 		connect(b->browse, SIGNAL(unavailable(XMPP::ServiceInstance)), SLOT(browse_unavailable(XMPP::ServiceInstance)));
803 		connect(b->browse, SIGNAL(error()), SLOT(browse_error()));
804 		browseList += b;
805 		b->browse->browse(type, domain);
806 		return b->id;
807 	}
808 
browse_stop(int id)809 	virtual void browse_stop(int id)
810 	{
811 		int at = indexOfBrowseById(id);
812 		if(at == -1)
813 			return;
814 
815 		Browse *b = browseList[at];
816 		browseList.removeAt(at);
817 		delete b;
818 	}
819 
resolve_start(const QByteArray & name)820 	virtual int resolve_start(const QByteArray &name)
821 	{
822 		QList<QByteArray> parts = nameToInstanceParts(name);
823 		if(parts.isEmpty())
824 		{
825 			// TODO: signal error rather than die
826 			Q_ASSERT(0);
827 		}
828 
829 		Resolve *r = new Resolve(this);
830 		r->id = idManager.reserveId();
831 		r->resolve = new AppleBrowseLookup(global, this);
832 		connect(r->resolve, SIGNAL(finished(QList<QHostAddress>)), SLOT(resolve_finished(QList<QHostAddress>)));
833 		connect(r->resolve, SIGNAL(error()), SLOT(resolve_error()));
834 		resolveList += r;
835 		r->resolve->resolve(parts[0], parts[1], parts[2]);
836 		return r->id;
837 	}
838 
resolve_stop(int id)839 	virtual void resolve_stop(int id)
840 	{
841 		int at = indexOfResolveById(id);
842 		if(at == -1)
843 			return;
844 
845 		Resolve *r = resolveList[at];
846 		resolveList.removeAt(at);
847 		delete r;
848 	}
849 
publish_start(const QString & instance,const QString & type,int port,const QMap<QString,QByteArray> & attributes)850 	virtual int publish_start(const QString &instance, const QString &type, int port, const QMap<QString,QByteArray> &attributes)
851 	{
852 		QByteArray txtRecord = attribsToTxtRecord(attributes);
853 		if(txtRecord.isEmpty())
854 		{
855 			// TODO: signal error rather than die
856 			Q_ASSERT(0);
857 		}
858 
859 		QString domain = "local";
860 
861 		// FIXME: conversion/escaping is probably wrong?
862 		return global->reg(this, instance.toUtf8(), type.toUtf8(), domain.toUtf8(), port, txtRecord);
863 	}
864 
publish_update(int id,const QMap<QString,QByteArray> & attributes)865 	virtual void publish_update(int id, const QMap<QString,QByteArray> &attributes)
866 	{
867 		// TODO: verify 'id' is valid.  if not valid, then assert/return (don't do anything or signal error)
868 
869 		QByteArray txtRecord = attribsToTxtRecord(attributes);
870 		if(txtRecord.isEmpty())
871 		{
872 			// TODO: signal error rather than die
873 			Q_ASSERT(0);
874 		}
875 
876 		if(global->dns.recordUpdateTxt(id, txtRecord, 4500))
877 		{
878 			// FIXME: SR
879 			QMetaObject::invokeMethod(this, "publish_published", Qt::QueuedConnection, Q_ARG(int, id));
880 		}
881 		else
882 		{
883 			// TODO: unpublish
884 
885 			// FIXME: register meta type, SR
886 			QMetaObject::invokeMethod(this, "publish_error", Qt::QueuedConnection,
887 				Q_ARG(int, id),
888 				Q_ARG(XMPP::ServiceLocalPublisher::Error, XMPP::ServiceLocalPublisher::ErrorGeneric));
889 		}
890 	}
891 
publish_stop(int id)892 	virtual void publish_stop(int id)
893 	{
894 		global->stop(id);
895 	}
896 
publish_extra_start(int pub_id,const XMPP::NameRecord & name)897 	virtual int publish_extra_start(int pub_id, const XMPP::NameRecord &name)
898 	{
899 		// TODO
900 		Q_UNUSED(pub_id);
901 		Q_UNUSED(name);
902 		return 0;
903 	}
904 
publish_extra_update(int id,const XMPP::NameRecord & name)905 	virtual void publish_extra_update(int id, const XMPP::NameRecord &name)
906 	{
907 		// TODO
908 		Q_UNUSED(id);
909 		Q_UNUSED(name);
910 	}
911 
publish_extra_stop(int id)912 	virtual void publish_extra_stop(int id)
913 	{
914 		// TODO
915 		Q_UNUSED(id);
916 	}
917 
918 	// called by AppleProvider
919 
dns_regResult(int id,const QDnsSd::RegResult & result)920 	void dns_regResult(int id, const QDnsSd::RegResult &result)
921 	{
922 		// TODO
923 		Q_UNUSED(id);
924 		Q_UNUSED(result);
925 	}
926 
927 private slots:
browse_available(const XMPP::ServiceInstance & instance)928 	void browse_available(const XMPP::ServiceInstance &instance)
929 	{
930 		int at = indexOfBrowseByBrowse((AppleBrowse *)sender());
931 		Q_ASSERT(at != -1);
932 
933 		emit browse_instanceAvailable(browseList[at]->id, instance);
934 	}
935 
browse_unavailable(const XMPP::ServiceInstance & instance)936 	void browse_unavailable(const XMPP::ServiceInstance &instance)
937 	{
938 		int at = indexOfBrowseByBrowse((AppleBrowse *)sender());
939 		Q_ASSERT(at != -1);
940 
941 		emit browse_instanceUnavailable(browseList[at]->id, instance);
942 	}
943 
browse_error()944 	void browse_error()
945 	{
946 		int at = indexOfBrowseByBrowse((AppleBrowse *)sender());
947 		Q_ASSERT(at != -1);
948 
949 		Browse *b = browseList[at];
950 		browseList.removeAt(at);
951 		int id = b->id;
952 		delete b;
953 
954 		// FIXME: this looks weird, we should probably rename our
955 		//   local function
956 		emit ServiceProvider::browse_error(id, XMPP::ServiceBrowser::ErrorGeneric);
957 	}
958 
resolve_finished(const QList<QHostAddress> & addrs,int port)959 	void resolve_finished(const QList<QHostAddress> &addrs, int port)
960 	{
961 		int at = indexOfResolveByResolve((AppleBrowseLookup *)sender());
962 		Q_ASSERT(at != -1);
963 
964 		Resolve *r = resolveList[at];
965 		resolveList.removeAt(at);
966 		int id = r->id;
967 		delete r;
968 
969 		QList<ResolveResult> results;
970 		foreach(const QHostAddress &addr, addrs)
971 		{
972 			ResolveResult r;
973 			r.address = addr;
974 			r.port = port;
975 			results += r;
976 		}
977 
978 		emit resolve_resultsReady(id, results);
979 	}
980 
resolve_error()981 	void resolve_error()
982 	{
983 		int at = indexOfResolveByResolve((AppleBrowseLookup *)sender());
984 		Q_ASSERT(at != -1);
985 
986 		Resolve *r = resolveList[at];
987 		resolveList.removeAt(at);
988 		int id = r->id;
989 		delete r;
990 
991 		// FIXME: this looks weird, we should probably rename our
992 		//   local function
993 		emit ServiceProvider::resolve_error(id, XMPP::ServiceResolver::ErrorGeneric);
994 	}
995 };
996 
997 // AppleProvider
createNameProviderInternet()998 XMPP::NameProvider *AppleProvider::createNameProviderInternet()
999 {
1000 	return new AppleNameProvider(this);
1001 }
1002 
createNameProviderLocal()1003 XMPP::NameProvider *AppleProvider::createNameProviderLocal()
1004 {
1005 	return new AppleNameProvider(this);
1006 }
1007 
createServiceProvider()1008 XMPP::ServiceProvider *AppleProvider::createServiceProvider()
1009 {
1010 	return new AppleServiceProvider(this);
1011 }
1012 
1013 #ifdef APPLEDNS_STATIC
irisnet_createAppleProvider()1014 XMPP::IrisNetProvider *irisnet_createAppleProvider()
1015 {
1016         return new AppleProvider;
1017 }
1018 #else
1019 Q_EXPORT_PLUGIN2(appledns, AppleProvider)
1020 #endif
1021 
1022 #include "appledns.moc"
1023