PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/zeroconf/qxtdiscoverableservice.cpp

https://bitbucket.org/ghl800/libqxt
C++ | 379 lines | 179 code | 27 blank | 173 comment | 35 complexity | c4564471ab2a677406dec1d849bfa370 MD5 | raw file
  1. #include "qxtdiscoverableservice.h"
  2. #include "qxtdiscoverableservice_p.h"
  3. #include <QSocketNotifier>
  4. #include <QList>
  5. #include <QPair>
  6. #include <QtDebug>
  7. #include <QtEndian>
  8. /*!
  9. \class QxtDiscoverableService
  10. \inmodule QxtZeroconf
  11. \brief The QxtDiscoverableService class registers a service that can be discovered with Zeroconf, or resolve such a service
  12. QxtDiscoverableService represents a service on the local network that can be discovered using Zeroconf.
  13. It can function to provide such a service so that other systems on the network can find it, or it can
  14. resolve the connection parameters for a service provided by another system on the network. Note that
  15. resolving a service requires that you already know the service name in addition to the service type for
  16. the remote service; to find all services on the network that offer a given service type, use
  17. QxtServiceBrowser.
  18. When registering a service, you may specify a set of service subtypes to indicate what kind of optional
  19. capabilities your service offers. Other applications browsing for discoverable services (for instance,
  20. using QxtServiceBrowser) can limit their search to only those services that provide a given subtype.
  21. Use the properties inherited from QxtDiscoverableServiceName to configure the other service parameters.
  22. For more information about Zeroconf, see the documentation available on www.zeroconf.org.
  23. \sa QxtDiscoverableServiceName
  24. \sa QxtServiceBrowser
  25. */
  26. void QxtDiscoverableServicePrivate::registerServiceCallback(DNSServiceRef service, DNSServiceFlags flags,
  27. DNSServiceErrorType errCode, const char* name, const char* regtype, const char* domain, void* context)
  28. {
  29. Q_UNUSED(service);
  30. Q_UNUSED(flags);
  31. Q_UNUSED(regtype);
  32. QxtDiscoverableServicePrivate* self = reinterpret_cast<QxtDiscoverableServicePrivate*>(context);
  33. QxtDiscoverableService* pub = &(self->qxt_p());
  34. if(errCode == kDNSServiceErr_NoError) {
  35. pub->setServiceName(name);
  36. pub->setDomain(domain);
  37. self->state = QxtDiscoverableService::Registered;
  38. emit self->qxt_p().registered();
  39. } else {
  40. self->state = QxtDiscoverableService::Unknown;
  41. emit self->qxt_p().registrationError((QxtDiscoverableService::ErrorCode)errCode);
  42. }
  43. }
  44. void QxtDiscoverableServicePrivate::resolveServiceCallback(DNSServiceRef service, DNSServiceFlags flags, quint32 iface,
  45. DNSServiceErrorType errCode, const char* fullname, const char* host, quint16 port, quint16 txtLen,
  46. const unsigned char* txt, void* context)
  47. {
  48. Q_UNUSED(service);
  49. Q_UNUSED(flags);
  50. QxtDiscoverableServicePrivate* self = reinterpret_cast<QxtDiscoverableServicePrivate*>(context);
  51. QxtDiscoverableService* pub = &self->qxt_p();
  52. if(errCode == kDNSServiceErr_NoError) {
  53. QxtDiscoverableServiceName name(fullname);
  54. pub->setServiceName(name.serviceName());
  55. pub->setDomain(name.domain());
  56. pub->setHost(host);
  57. pub->setPort(qFromBigEndian(port));
  58. pub->setTxtRecord(txtLen > 0 ? QString::fromUtf8((const char*)txt, txtLen) : QString());
  59. self->iface = iface;
  60. emit pub->resolved(fullname);
  61. } else {
  62. self->state = QxtDiscoverableService::Unknown;
  63. emit pub->resolveError((QxtDiscoverableService::ErrorCode)errCode);
  64. }
  65. }
  66. void QxtDiscoverableServicePrivate::socketData()
  67. {
  68. DNSServiceProcessResult(service);
  69. }
  70. /*!
  71. * Constructs a QxtDiscoverableService object using the specified service type.
  72. *
  73. * The service type may be a plain type name or it may be provided in the standard format
  74. * specified by the Zeroconf specification.
  75. *
  76. * The service name will be automatically generated based on the system's hostname.
  77. */
  78. QxtDiscoverableService::QxtDiscoverableService(const QString& serviceType, QObject* parent)
  79. : QObject(parent), QxtDiscoverableServiceName(QString(), serviceType, QString())
  80. {
  81. QXT_INIT_PRIVATE(QxtDiscoverableService);
  82. qxt_zeroconf_parse_subtypes(&qxt_d(), serviceType.toUtf8());
  83. }
  84. /*!
  85. * Constructs a QxtDiscoverableService object using the specified service type.
  86. *
  87. * The service type may be a plain type name or it may be provided in the standard format
  88. * specified by the Zeroconf specification.
  89. *
  90. * If the specified service name is already in use, it will be updated with a number to
  91. * make it unique.
  92. */
  93. QxtDiscoverableService::QxtDiscoverableService(const QString& serviceType, const QString& serviceName, QObject* parent)
  94. : QObject(parent), QxtDiscoverableServiceName(serviceName, serviceType, QString())
  95. {
  96. QXT_INIT_PRIVATE(QxtDiscoverableService);
  97. qxt_zeroconf_parse_subtypes(&qxt_d(), serviceType.toUtf8());
  98. }
  99. /*!
  100. * Destroys the QxtDiscoverableService.
  101. *
  102. * TIf registered, the service will be unregistered.
  103. */
  104. QxtDiscoverableService::~QxtDiscoverableService()
  105. {
  106. if(state() == Registered || state() == Resolved)
  107. releaseService();
  108. }
  109. /*!
  110. * Returns the current state of the service.
  111. */
  112. QxtDiscoverableService::State QxtDiscoverableService::state() const {
  113. return qxt_d().state;
  114. }
  115. /*!
  116. * Returns a list of all subtypes known for the service.
  117. *
  118. * When discovering a service, only subtypes that were included in the service
  119. * discovery request will be included in this list.
  120. *
  121. * \sa setServiceSubTypes
  122. * \sa addServiceSubType
  123. * \sa removeServiceSubType
  124. * \sa hasServiceSubType
  125. */
  126. QStringList QxtDiscoverableService::serviceSubTypes() const
  127. {
  128. return qxt_d().serviceSubTypes;
  129. }
  130. /*!
  131. * Sets the list of subtypes for this service.
  132. *
  133. * \sa serviceSubTypes
  134. * \sa addServiceSubType
  135. * \sa removeServiceSubType
  136. * \sa hasServiceSubType
  137. */
  138. void QxtDiscoverableService::setServiceSubTypes(const QStringList& subtypes)
  139. {
  140. if(state() != Unknown)
  141. qWarning() << "QxtDiscoverableService: Setting service subtypes while not in Unknown state has no effect";
  142. qxt_d().serviceSubTypes = subtypes;
  143. }
  144. /*!
  145. * Adds a subtype to the service.
  146. *
  147. * \sa serviceSubTypes
  148. * \sa setServiceSubTypes
  149. * \sa removeServiceSubType
  150. * \sa hasServiceSubType
  151. */
  152. void QxtDiscoverableService::addServiceSubType(const QString& subtype)
  153. {
  154. if(state() != Unknown)
  155. qWarning() << "QxtDiscoverableService: Setting service subtypes while not in Unknown state has no effect";
  156. qxt_d().serviceSubTypes << subtype;
  157. }
  158. /*!
  159. * Removes a subtype from the service.
  160. *
  161. * \sa serviceSubTypes
  162. * \sa setServiceSubTypes
  163. * \sa addServiceSubType
  164. * \sa hasServiceSubType
  165. */
  166. void QxtDiscoverableService::removeServiceSubType(const QString& subtype)
  167. {
  168. if(state() != Unknown)
  169. qWarning() << "QxtDiscoverableService: Setting service subtypes while not in Unknown state has no effect";
  170. qxt_d().serviceSubTypes.removeAll(subtype);
  171. }
  172. /*!
  173. * Tests to see if the specified service is available and known for the service.
  174. *
  175. * When discovering a service, only subtypes that were included in the service
  176. * discovery request will be available.
  177. *
  178. * \sa serviceSubTypes
  179. * \sa setServiceSubTypes
  180. * \sa addServiceSubType
  181. * \sa removeServiceSubType
  182. */
  183. bool QxtDiscoverableService::hasServiceSubType(const QString& subtype)
  184. {
  185. return qxt_d().serviceSubTypes.contains(subtype);
  186. }
  187. /*!
  188. * Returns the port number used for connecting to the service.
  189. *
  190. * \sa setPort
  191. */
  192. quint16 QxtDiscoverableService::port() const
  193. {
  194. return qFromBigEndian(qxt_d().port);
  195. }
  196. /*!
  197. * Sets the port number used for connecting to the service.
  198. *
  199. * When registering a service with a port number of 0 (the default), the service will not be found when browsing,
  200. * but the service name will be marked as reserved.
  201. *
  202. * Setting the port is only meaningful when registering a service.
  203. *
  204. * \sa port
  205. */
  206. void QxtDiscoverableService::setPort(quint16 port)
  207. {
  208. qxt_d().port = qToBigEndian(port);
  209. }
  210. /*!
  211. * Attempts to register the service on the local network.
  212. *
  213. * If noAutoRename is set to true, registration will fail if another service of the same service type
  214. * is already registered with the same service name. Otherwise, the service name will be updated with
  215. * a number to make it unique.
  216. *
  217. * When present, the given TXT record string will be registered along with the service.
  218. *
  219. * \sa registered
  220. * \sa registrationError
  221. */
  222. void QxtDiscoverableService::registerService(bool noAutoRename)
  223. {
  224. if(state() != Unknown) {
  225. qWarning() << "QxtDiscoverableService: Cannot register service while not in Unknown state";
  226. emit registrationError(0);
  227. return;
  228. }
  229. QString txtRecord = QxtDiscoverableServiceName::txtRecord();
  230. QByteArray txt;
  231. QStringList subtypes = qxt_d().serviceSubTypes;
  232. subtypes.prepend(fullServiceType());
  233. if (txtRecord.length()) {
  234. txt.append((char)txtRecord.length());
  235. txt.append(txtRecord.toUtf8());
  236. }
  237. else {
  238. txt.fill(0, 1);
  239. }
  240. DNSServiceErrorType err;
  241. err = DNSServiceRegister(&(qxt_d().service),
  242. noAutoRename ? kDNSServiceFlagsNoAutoRename : 0,
  243. qxt_d().iface,
  244. serviceName().isEmpty() ? 0 : serviceName().toUtf8().constData(),
  245. subtypes.join(",_").toUtf8().constData(),
  246. domain().isEmpty() ? 0 : domain().toUtf8().constData(),
  247. host().isEmpty() ? 0 : host().toUtf8().constData(),
  248. qxt_d().port,
  249. txt.length(),
  250. txt.constData(),
  251. QxtDiscoverableServicePrivate::registerServiceCallback,
  252. &qxt_d());
  253. if(err != kDNSServiceErr_NoError) {
  254. qxt_d().state = Unknown;
  255. emit registrationError(err);
  256. } else {
  257. qxt_d().state = Registering;
  258. qxt_d().notifier = new QSocketNotifier(DNSServiceRefSockFD(qxt_d().service), QSocketNotifier::Read, this);
  259. QObject::connect(qxt_d().notifier, SIGNAL(activated(int)), &qxt_d(), SLOT(socketData()));
  260. }
  261. }
  262. /*!
  263. * Attempts to resolve the service in order to determine the host and port necessary to establish a connection.
  264. *
  265. * If forceMulticast is set to true, QxtDiscoverableService will use a multicast request to resolve the service,
  266. * even if the host name appears to be a unicast address (that is, outside the local network).
  267. *
  268. * \sa resolved
  269. * \sa resolveError
  270. */
  271. void QxtDiscoverableService::resolve(bool forceMulticast)
  272. {
  273. if(state() != Unknown && state() != Found) {
  274. qWarning() << "QxtDiscoverableService: Cannot resolve service while not in Unknown or Found state";
  275. emit resolveError(0);
  276. return;
  277. }
  278. DNSServiceErrorType err;
  279. err = DNSServiceResolve(&(qxt_d().service),
  280. (forceMulticast ? kDNSServiceFlagsForceMulticast : 0),
  281. qxt_d().iface,
  282. serviceName().toUtf8().constData(),
  283. fullServiceType().constData(),
  284. domain().toUtf8().constData(),
  285. QxtDiscoverableServicePrivate::resolveServiceCallback,
  286. &qxt_d());
  287. if(err != kDNSServiceErr_NoError) {
  288. qxt_d().state = Unknown;
  289. emit resolveError(err);
  290. } else {
  291. qxt_d().state = Resolving;
  292. qxt_d().notifier = new QSocketNotifier(DNSServiceRefSockFD(qxt_d().service), QSocketNotifier::Read, this);
  293. QObject::connect(qxt_d().notifier, SIGNAL(activated(int)), &qxt_d(), SLOT(socketData()));
  294. }
  295. }
  296. /*!
  297. * Releases the service.
  298. *
  299. * If the service is registered, it will be unregistered. Any outstanding resolve attempt will be aborted.
  300. */
  301. void QxtDiscoverableService::releaseService()
  302. {
  303. if(state() != Registered && state() != Resolved) {
  304. qWarning() << "QxtDiscoverableService: Attempting to unregister an unresolved, unregistered service";
  305. } else {
  306. DNSServiceRefDeallocate(qxt_d().service);
  307. qxt_d().notifier->deleteLater();
  308. }
  309. }
  310. /*!
  311. * \fn registered()
  312. *
  313. * This signal is emitted after a call to registerService() when the service has been successfully registered
  314. * on the network. The service name may have been updated to ensure uniqueness.
  315. *
  316. * \sa registerService
  317. * \sa registrationError
  318. */
  319. /*!
  320. * \fn registrationError(int code)
  321. *
  322. * This signal is emitted after a call to registerService() when the service cannot be registered. This could
  323. * be because the service name is already in use and noAutoRename was requested, or because the mDNSResponder
  324. * or Avahi daemon on the local machine could not be contacted.
  325. *
  326. * \sa registerService
  327. * \sa registered
  328. */
  329. /*!
  330. * \fn resolved(const QByteArray& domainName)
  331. *
  332. * This signal is emitted after a call to resolve() when a service matching the requested parameters is found.
  333. * domainName contains the complete domain name of the resolved service. The host name, port, and socket type
  334. * will be updated to match the connection parameters announced by the service.
  335. *
  336. * \sa resolve
  337. * \sa resolveError
  338. */
  339. /*!
  340. * \fn resolveError(int code)
  341. *
  342. * This signal is emitted after a call to resolve() if an error occurs while attempting to resolve the service.
  343. *
  344. * \sa resolve
  345. * \sa resolveError
  346. */