/*
 * Copyright (C) 2007,2008  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 "qdnssd.h"

#include <QtCore>
#include <stdio.h>

// for ntohs
#ifdef Q_OS_WIN
# include <windows.h>
#else
# include <netinet/in.h>
#endif

#include "dns_sd.h"

namespace {

// safeobj stuff, from qca

void releaseAndDeleteLater(QObject *owner, QObject *obj)
{
    obj->disconnect(owner);
    obj->setParent(0);
    obj->deleteLater();
}

class SafeTimer : public QObject
{
    Q_OBJECT
public:
    SafeTimer(QObject *parent = 0) :
        QObject(parent)
    {
        t = new QTimer(this);
        connect(t, SIGNAL(timeout()), SIGNAL(timeout()));
    }

    ~SafeTimer()
    {
        releaseAndDeleteLater(this, t);
    }

    int interval() const                { return t->interval(); }
    bool isActive() const               { return t->isActive(); }
    bool isSingleShot() const           { return t->isSingleShot(); }
    void setInterval(int msec)          { t->setInterval(msec); }
    void setSingleShot(bool singleShot) { t->setSingleShot(singleShot); }
    int timerId() const                 { return t->timerId(); }

public slots:
    void start(int msec)                { t->start(msec); }
    void start()                        { t->start(); }
    void stop()                         { t->stop(); }

signals:
    void timeout();

private:
    QTimer *t;
};

class SafeSocketNotifier : public QObject
{
    Q_OBJECT
public:
    SafeSocketNotifier(int socket, QSocketNotifier::Type type,
        QObject *parent = 0) :
        QObject(parent)
    {
        sn = new QSocketNotifier(socket, type, this);
        connect(sn, SIGNAL(activated(int)), SIGNAL(activated(int)));
    }

    ~SafeSocketNotifier()
    {
        sn->setEnabled(false);
        releaseAndDeleteLater(this, sn);
    }

    bool isEnabled() const             { return sn->isEnabled(); }
    int socket() const                 { return sn->socket(); }
    QSocketNotifier::Type type() const { return sn->type(); }

public slots:
    void setEnabled(bool enable)       { sn->setEnabled(enable); }

signals:
    void activated(int socket);

private:
    QSocketNotifier *sn;
};

// DNSServiceRef must be allocated by the user and initialized by the
//   API.  Additionally, it is unclear from the API whether or not
//   DNSServiceRef can be copied (it is an opaque data structure).
//   What we'll do is allocate DNSServiceRef on the heap, allowing us
//   to maintain a pointer which /can/ be copied.  Also, we'll keep
//   a flag to indicate whether the allocated DNSServiceRef has been
//   initialized yet.
class ServiceRef
{
private:
    DNSServiceRef *_p;
    bool _initialized;

public:
    ServiceRef() :
        _initialized(false)
    {
        _p = (DNSServiceRef *)malloc(sizeof(DNSServiceRef));
    }

    ~ServiceRef()
    {
        if(_initialized)
            DNSServiceRefDeallocate(*_p);
        free(_p);
    }

    DNSServiceRef *data()
    {
        return _p;
    }

    void setInitialized()
    {
        _initialized = true;
    }
};

class RecordRef
{
private:
    DNSRecordRef *_p;

public:
    RecordRef()
    {
        _p = (DNSRecordRef *)malloc(sizeof(DNSRecordRef));
    }

    ~RecordRef()
    {
        free(_p);
    }

    DNSRecordRef *data()
    {
        return _p;
    }
};

class IdManager
{
private:
    QSet<int> set;
    int at;

    inline void bump_at()
    {
        if(at == 0x7fffffff)
            at = 0;
        else
            ++at;
    }

public:
    IdManager() :
        at(0)
    {
    }

    int reserveId()
    {
        while(1)
        {
            if(!set.contains(at))
            {
                int id = at;
                set.insert(id);
                bump_at();
                return id;
            }

            bump_at();
        }
    }

    void releaseId(int id)
    {
        set.remove(id);
    }
};

}

//----------------------------------------------------------------------------
// QDnsSd
//----------------------------------------------------------------------------
class QDnsSd::Private : public QObject
{
    Q_OBJECT
public:
    QDnsSd *q;
    IdManager idManager;

    class SubRecord
    {
    public:
        Private *_self;
        int _id;
        RecordRef *_sdref;

        SubRecord(Private *self) :
            _self(self),
            _id(-1),
            _sdref(0)
        {
        }

        ~SubRecord()
        {
            delete _sdref;
            _self->idManager.releaseId(_id);
        }
    };

    class Request
    {
    public:
        enum Type
        {
            Query,
            Browse,
            Resolve,
            Reg
        };

        Private *_self;
        int _type;
        int _id;
        ServiceRef *_sdref;
        int _sockfd;
        SafeSocketNotifier *_sn_read;
        SafeTimer *_errorTrigger;

        bool _doSignal;
        LowLevelError _lowLevelError;
        QList<QDnsSd::Record> _queryRecords;
        QList<QDnsSd::BrowseEntry> _browseEntries;
        QByteArray _resolveFullName;
        QByteArray _resolveHost;
        int _resolvePort;
        QByteArray _resolveTxtRecord;
        QByteArray _regDomain;
        bool _regConflict;

        QList<SubRecord*> _subRecords;

        Request(Private *self) :
            _self(self),
            _id(-1),
            _sdref(0),
            _sockfd(-1),
            _sn_read(0),
            _errorTrigger(0),
            _doSignal(false)
        {
        }

        ~Request()
        {
            qDeleteAll(_subRecords);

            delete _errorTrigger;
            delete _sn_read;
            delete _sdref;
            _self->idManager.releaseId(_id);
        }

        int subRecordIndexById(int rec_id) const
        {
            for(int n = 0; n < _subRecords.count(); ++n)
            {
                if(_subRecords[n]->_id == rec_id)
                    return n;
            }
            return -1;
        }
    };

    QHash<int,Request*> _requestsById;
    QHash<SafeSocketNotifier*,Request*> _requestsBySocket;
    QHash<SafeTimer*,Request*> _requestsByTimer;
    QHash<int,Request*> _requestsByRecId;

    Private(QDnsSd *_q) :
        QObject(_q),
        q(_q)
    {
    }

    ~Private()
    {
        qDeleteAll(_requestsById);
    }

    void setDelayedError(Request *req, const LowLevelError &lowLevelError)
    {
        delete req->_sdref;
        req->_sdref = 0;

        req->_lowLevelError = lowLevelError;

        req->_errorTrigger = new SafeTimer(this);
        connect(req->_errorTrigger, SIGNAL(timeout()), SLOT(doError()));
        req->_errorTrigger->setSingleShot(true);

        _requestsByTimer.insert(req->_errorTrigger, req);

        req->_errorTrigger->start();
    }

    void removeRequest(Request *req)
    {
        foreach(const SubRecord *srec, req->_subRecords)
            _requestsByRecId.remove(srec->_id);
        if(req->_errorTrigger)
            _requestsByTimer.remove(req->_errorTrigger);
        if(req->_sn_read)
            _requestsBySocket.remove(req->_sn_read);
        _requestsById.remove(req->_id);
        delete req;
    }

    int regIdForRecId(int rec_id) const
    {
        Request *req = _requestsByRecId.value(rec_id);
        if(req)
            return req->_id;
        return -1;
    }

    int query(const QByteArray &name, int qType)
    {
        int id = idManager.reserveId();

        Request *req = new Request(this);
        req->_type = Request::Query;
        req->_id = id;
        req->_sdref = new ServiceRef;

        DNSServiceErrorType err = DNSServiceQueryRecord(
            req->_sdref->data(), kDNSServiceFlagsLongLivedQuery,
            0, name.constData(), qType, kDNSServiceClass_IN,
            cb_queryRecordReply, req);
        if(err != kDNSServiceErr_NoError)
        {
            setDelayedError(req, LowLevelError(
                "DNSServiceQueryRecord", err));
            return id;
        }

        req->_sdref->setInitialized();

        int sockfd = DNSServiceRefSockFD(*(req->_sdref->data()));
        if(sockfd == -1)
        {
            setDelayedError(req, LowLevelError(
                "DNSServiceRefSockFD", -1));
            return id;
        }

        req->_sockfd = sockfd;
        req->_sn_read = new SafeSocketNotifier(sockfd, QSocketNotifier::Read, this);
        connect(req->_sn_read, SIGNAL(activated(int)), SLOT(sn_activated()));
        _requestsById.insert(id, req);
        _requestsBySocket.insert(req->_sn_read, req);

        return id;
    }

    int browse(const QByteArray &serviceType, const QByteArray &domain)
    {
        int id = idManager.reserveId();

        Request *req = new Request(this);
        req->_type = Request::Browse;
        req->_id = id;
        req->_sdref = new ServiceRef;

        DNSServiceErrorType err = DNSServiceBrowse(
            req->_sdref->data(), 0, 0, serviceType.constData(),
            !domain.isEmpty() ? domain.constData() : NULL,
            cb_browseReply, req);
        if(err != kDNSServiceErr_NoError)
        {
            setDelayedError(req, LowLevelError(
                "DNSServiceBrowse", err));
            return id;
        }

        req->_sdref->setInitialized();

        int sockfd = DNSServiceRefSockFD(*(req->_sdref->data()));
        if(sockfd == -1)
        {
            setDelayedError(req, LowLevelError(
                "DNSServiceRefSockFD", -1));
            return id;
        }

        req->_sockfd = sockfd;
        req->_sn_read = new SafeSocketNotifier(sockfd, QSocketNotifier::Read, this);
        connect(req->_sn_read, SIGNAL(activated(int)), SLOT(sn_activated()));
        _requestsById.insert(id, req);
        _requestsBySocket.insert(req->_sn_read, req);

        return id;
    }

    int resolve(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain)
    {
        int id = idManager.reserveId();

        Request *req = new Request(this);
        req->_type = Request::Resolve;
        req->_id = id;
        req->_sdref = new ServiceRef;

        DNSServiceErrorType err = DNSServiceResolve(
            req->_sdref->data(), 0, 0, serviceName.constData(),
            serviceType.constData(), domain.constData(),
            (DNSServiceResolveReply)cb_resolveReply, req);
        if(err != kDNSServiceErr_NoError)
        {
            setDelayedError(req, LowLevelError(
                "DNSServiceResolve", err));
            return id;
        }

        req->_sdref->setInitialized();

        int sockfd = DNSServiceRefSockFD(*(req->_sdref->data()));
        if(sockfd == -1)
        {
            setDelayedError(req, LowLevelError(
                "DNSServiceRefSockFD", -1));
            return id;
        }

        req->_sockfd = sockfd;
        req->_sn_read = new SafeSocketNotifier(sockfd, QSocketNotifier::Read, this);
        connect(req->_sn_read, SIGNAL(activated(int)), SLOT(sn_activated()));
        _requestsById.insert(id, req);
        _requestsBySocket.insert(req->_sn_read, req);

        return id;
    }

    int reg(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain, int port, const QByteArray &txtRecord)
    {
        int id = idManager.reserveId();

        Request *req = new Request(this);
        req->_type = Request::Reg;
        req->_id = id;

        if(port < 1 || port > 0xffff)
        {
            setDelayedError(req, LowLevelError());
            return id;
        }

        uint16_t sport = port;
        sport = htons(sport);

        req->_sdref = new ServiceRef;

        DNSServiceErrorType err = DNSServiceRegister(
            req->_sdref->data(), kDNSServiceFlagsNoAutoRename, 0,
            serviceName.constData(), serviceType.constData(),
            domain.constData(), NULL, sport, txtRecord.size(),
            txtRecord.data(), cb_regReply, req);
        if(err != kDNSServiceErr_NoError)
        {
            setDelayedError(req, LowLevelError(
                "DNSServiceRegister", err));
            return id;
        }

        req->_sdref->setInitialized();

        int sockfd = DNSServiceRefSockFD(*(req->_sdref->data()));
        if(sockfd == -1)
        {
            setDelayedError(req, LowLevelError(
                "DNSServiceRefSockFD", -1));
            return id;
        }

        req->_sockfd = sockfd;
        req->_sn_read = new SafeSocketNotifier(sockfd, QSocketNotifier::Read, this);
        connect(req->_sn_read, SIGNAL(activated(int)), SLOT(sn_activated()));
        _requestsById.insert(id, req);
        _requestsBySocket.insert(req->_sn_read, req);

        return id;
    }

    int recordAdd(int reg_id, const Record &rec, LowLevelError *lowLevelError)
    {
        Request *req = _requestsById.value(reg_id);
        if(!req)
        {
            if(lowLevelError)
                *lowLevelError = LowLevelError();
            return -1;
        }

        RecordRef *recordRef = new RecordRef;

        DNSServiceErrorType err = DNSServiceAddRecord(
            *(req->_sdref->data()), recordRef->data(), 0,
            rec.rrtype, rec.rdata.size(), rec.rdata.data(),
            rec.ttl);
        if(err != kDNSServiceErr_NoError)
        {
            if(lowLevelError)
                *lowLevelError = LowLevelError("DNSServiceAddRecord", err);
            delete recordRef;
            return -1;
        }

        int id = idManager.reserveId();
        SubRecord *srec = new SubRecord(this);
        srec->_id = id;
        srec->_sdref = recordRef;
        req->_subRecords += srec;
        _requestsByRecId.insert(id, req);

        return id;
    }

    bool recordUpdate(int reg_id, int rec_id, const Record &rec, LowLevelError *lowLevelError)
    {
        Request *req = _requestsById.value(reg_id);
        if(!req)
        {
            if(lowLevelError)
                *lowLevelError = LowLevelError();
            return false;
        }

        SubRecord *srec = 0;
        if(rec_id != -1)
        {
            int at = req->subRecordIndexById(rec_id);
            if(at == -1)
            {
                if(lowLevelError)
                    *lowLevelError = LowLevelError();
                return false;
            }
            srec = req->_subRecords[at];
        }

        DNSServiceErrorType err = DNSServiceUpdateRecord(
            *(req->_sdref->data()),
            srec ? *(srec->_sdref->data()) : NULL, 0,
            rec.rdata.size(), rec.rdata.data(), rec.ttl);
        if(err != kDNSServiceErr_NoError)
        {
            if(lowLevelError)
                *lowLevelError = LowLevelError("DNSServiceUpdateRecord", err);
            return false;
        }

        return true;
    }

    void recordRemove(int rec_id)
    {
        Request *req = _requestsByRecId.value(rec_id);
        if(!req)
            return;

        // this can't fail
        int at = req->subRecordIndexById(rec_id);

        SubRecord *srec = req->_subRecords[at];
        DNSServiceRemoveRecord(*(req->_sdref->data()), *(srec->_sdref->data()), 0);
        _requestsByRecId.remove(srec->_id);
        req->_subRecords.removeAt(at);
        delete srec;
    }

    void stop(int id)
    {
        Request *req = _requestsById.value(id);
        if(req)
            removeRequest(req);
    }

private slots:
    void sn_activated()
    {
        SafeSocketNotifier *sn_read = static_cast<SafeSocketNotifier *>(sender());
        Request *req = _requestsBySocket.value(sn_read);
        if(!req)
            return;

        int id = req->_id;

        DNSServiceErrorType err = DNSServiceProcessResult(*(req->_sdref->data()));

        // do error if the above function returns an error, or if we
        //   collected an error during a callback
        if(err != kDNSServiceErr_NoError || !req->_lowLevelError.func.isEmpty())
        {
            LowLevelError lowLevelError;
            if(err != kDNSServiceErr_NoError)
                lowLevelError = LowLevelError("DNSServiceProcessResult", err);
            else
                lowLevelError = req->_lowLevelError;

            // reg conflict indicated via callback
            bool regConflict = false;
            if(req->_type == Request::Reg && !req->_lowLevelError.func.isEmpty())
                regConflict = req->_regConflict;

            removeRequest(req);

            if(req->_type == Request::Query)
            {
                QDnsSd::QueryResult r;
                r.success = false;
                r.lowLevelError = lowLevelError;
                emit q->queryResult(id, r);
            }
            else if(req->_type == Request::Browse)
            {
                QDnsSd::BrowseResult r;
                r.success = false;
                r.lowLevelError = lowLevelError;
                emit q->browseResult(id, r);
            }
            else if(req->_type == Request::Resolve)
            {
                QDnsSd::ResolveResult r;
                r.success = false;
                r.lowLevelError = lowLevelError;
                emit q->resolveResult(id, r);
            }
            else // Reg
            {
                QDnsSd::RegResult r;
                r.success = false;
                if(regConflict)
                    r.errorCode = QDnsSd::RegResult::ErrorConflict;
                else
                    r.errorCode = QDnsSd::RegResult::ErrorGeneric;
                r.lowLevelError = lowLevelError;
                emit q->regResult(id, r);
            }

            return;
        }

        // handle success

        if(req->_type == Request::Query)
        {
            if(req->_doSignal)
            {
                QDnsSd::QueryResult r;
                r.success = true;
                r.records = req->_queryRecords;
                req->_queryRecords.clear();
                req->_doSignal = false;
                emit q->queryResult(id, r);
            }
        }
        else if(req->_type == Request::Browse)
        {
            if(req->_doSignal)
            {
                QDnsSd::BrowseResult r;
                r.success = true;
                r.entries = req->_browseEntries;
                req->_browseEntries.clear();
                req->_doSignal = false;
                emit q->browseResult(id, r);
            }
        }
        else if(req->_type == Request::Resolve)
        {
            if(req->_doSignal)
            {
                QDnsSd::ResolveResult r;
                r.success = true;
                r.fullName = req->_resolveFullName;
                r.hostTarget = req->_resolveHost;
                r.port = req->_resolvePort;
                r.txtRecord = req->_resolveTxtRecord;
                req->_doSignal = false;

                // there is only one response
                removeRequest(req);

                emit q->resolveResult(id, r);
            }
        }
        else // Reg
        {
            if(req->_doSignal)
            {
                QDnsSd::RegResult r;
                r.success = true;
                r.domain = req->_regDomain;
                req->_doSignal = false;

                emit q->regResult(id, r);
            }
        }
    }

    void doError()
    {
        SafeTimer *t = static_cast<SafeTimer *>(sender());
        Request *req = _requestsByTimer.value(t);
        if(!req)
            return;

        int id = req->_id;
        int type = req->_type;
        removeRequest(req);

        if(type == Request::Query)
        {
            QDnsSd::QueryResult r;
            r.success = false;
            r.lowLevelError = req->_lowLevelError;
            emit q->queryResult(id, r);
        }
        else if(type == Request::Browse)
        {
            QDnsSd::BrowseResult r;
            r.success = false;
            r.lowLevelError = req->_lowLevelError;
            emit q->browseResult(id, r);
        }
        else if(type == Request::Resolve)
        {
            QDnsSd::ResolveResult r;
            r.success = false;
            r.lowLevelError = req->_lowLevelError;
            emit q->resolveResult(id, r);
        }
        else // Reg
        {
            QDnsSd::RegResult r;
            r.success = false;
            r.errorCode = QDnsSd::RegResult::ErrorGeneric;
            r.lowLevelError = req->_lowLevelError;
            emit q->regResult(id, r);
        }
    }

private:
    static void cb_queryRecordReply(DNSServiceRef ref,
        DNSServiceFlags flags, uint32_t interfaceIndex,
        DNSServiceErrorType errorCode, const char *fullname,
        uint16_t rrtype, uint16_t rrclass, uint16_t rdlen,
        const void *rdata, uint32_t ttl, void *context)
    {
        Q_UNUSED(ref);
        Q_UNUSED(interfaceIndex);
        Q_UNUSED(rrclass);

        Request *req = static_cast<Request *>(context);
        req->_self->handle_queryRecordReply(req, flags, errorCode,
            fullname, rrtype, rdlen, (const char *)rdata, ttl);
    }

    static void cb_browseReply(DNSServiceRef ref,
        DNSServiceFlags flags, uint32_t interfaceIndex,
        DNSServiceErrorType errorCode, const char *serviceName,
        const char *regtype, const char *replyDomain, void *context)
    {
        Q_UNUSED(ref);
        Q_UNUSED(interfaceIndex);

        Request *req = static_cast<Request *>(context);
        req->_self->handle_browseReply(req, flags, errorCode,
            serviceName, regtype, replyDomain);
    }

    static void cb_resolveReply(DNSServiceRef ref,
        DNSServiceFlags flags, uint32_t interfaceIndex,
        DNSServiceErrorType errorCode, const char *fullname,
        const char *hosttarget, uint16_t port, uint16_t txtLen,
        const unsigned char *txtRecord, void *context)
    {
        Q_UNUSED(ref);
        Q_UNUSED(flags);
        Q_UNUSED(interfaceIndex);

        Request *req = static_cast<Request *>(context);
        req->_self->handle_resolveReply(req, errorCode, fullname,
            hosttarget, port, txtLen, txtRecord);
    }

    static void cb_regReply(DNSServiceRef ref,
        DNSServiceFlags flags, DNSServiceErrorType errorCode,
        const char *name, const char *regtype, const char *domain,
        void *context)
    {
        Q_UNUSED(ref);
        Q_UNUSED(flags);

        Request *req = static_cast<Request *>(context);
        req->_self->handle_regReply(req, errorCode, name, regtype,
            domain);
    }

    void handle_queryRecordReply(Request *req, DNSServiceFlags flags,
        DNSServiceErrorType errorCode, const char *fullname,
        uint16_t rrtype, uint16_t rdlen, const char *rdata,
        uint16_t ttl)
    {
        if(errorCode != kDNSServiceErr_NoError)
        {
            req->_doSignal = true;
            req->_lowLevelError = LowLevelError("DNSServiceQueryRecordReply", errorCode);
            return;
        }

        QDnsSd::Record rec;
        rec.added = (flags & kDNSServiceFlagsAdd) ? true: false;
        rec.name = QByteArray(fullname);
        rec.rrtype = rrtype;
        rec.rdata = QByteArray(rdata, rdlen);
        rec.ttl = ttl;
        req->_queryRecords += rec;

        if(!(flags & kDNSServiceFlagsMoreComing))
            req->_doSignal = true;
    }

    void handle_browseReply(Request *req, DNSServiceFlags flags,
        DNSServiceErrorType errorCode, const char *serviceName,
        const char *regtype, const char *replyDomain)
    {
        if(errorCode != kDNSServiceErr_NoError)
        {
            req->_doSignal = true;
            req->_lowLevelError = LowLevelError("DNSServiceBrowseReply", errorCode);
            return;
        }

        QDnsSd::BrowseEntry e;
        e.added = (flags & kDNSServiceFlagsAdd) ? true: false;
        e.serviceName = QByteArray(serviceName);
        e.serviceType = QByteArray(regtype);
        e.replyDomain = QByteArray(replyDomain);
        req->_browseEntries += e;

        if(!(flags & kDNSServiceFlagsMoreComing))
            req->_doSignal = true;
    }

    void handle_resolveReply(Request *req, DNSServiceErrorType errorCode,
        const char *fullname, const char *hosttarget, uint16_t port,
        uint16_t txtLen, const unsigned char *txtRecord)
    {
        if(errorCode != kDNSServiceErr_NoError)
        {
            req->_doSignal = true;
            req->_lowLevelError = LowLevelError("DNSServiceResolveReply", errorCode);
            return;
        }

        req->_resolveFullName = QByteArray(fullname);
        req->_resolveHost = QByteArray(hosttarget);
        req->_resolvePort = ntohs(port);
        req->_resolveTxtRecord = QByteArray((const char *)txtRecord, txtLen);

        req->_doSignal = true;
    }

    void handle_regReply(Request *req, DNSServiceErrorType errorCode,
        const char *name, const char *regtype, const char *domain)
    {
        Q_UNUSED(name);
        Q_UNUSED(regtype);

        if(errorCode != kDNSServiceErr_NoError)
        {
            req->_doSignal = true;
            req->_lowLevelError = LowLevelError("DNSServiceRegisterReply", errorCode);

            if(errorCode == kDNSServiceErr_NameConflict)
                req->_regConflict = true;
            else
                req->_regConflict = false;
            return;
        }

        req->_regDomain = QByteArray(domain);
        req->_doSignal = true;
    }
};

QDnsSd::QDnsSd(QObject *parent) :
    QObject(parent)
{
    d = new Private(this);
}

QDnsSd::~QDnsSd()
{
    delete d;
}

int QDnsSd::query(const QByteArray &name, int qType)
{
    return d->query(name, qType);
}

int QDnsSd::browse(const QByteArray &serviceType, const QByteArray &domain)
{
    return d->browse(serviceType, domain);
}

int QDnsSd::resolve(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain)
{
    return d->resolve(serviceName, serviceType, domain);
}

int QDnsSd::reg(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain, int port, const QByteArray &txtRecord)
{
    return d->reg(serviceName, serviceType, domain, port, txtRecord);
}

int QDnsSd::recordAdd(int reg_id, const Record &rec, LowLevelError *lowLevelError)
{
    return d->recordAdd(reg_id, rec, lowLevelError);
}

bool QDnsSd::recordUpdate(int rec_id, const Record &rec, LowLevelError *lowLevelError)
{
    int reg_id = d->regIdForRecId(rec_id);
    if(reg_id == -1)
        return false;

    return d->recordUpdate(reg_id, rec_id, rec, lowLevelError);
}

bool QDnsSd::recordUpdateTxt(int reg_id, const QByteArray &txtRecord, quint32 ttl, LowLevelError *lowLevelError)
{
    Record rec;
    rec.rrtype = kDNSServiceType_TXT;
    rec.rdata = txtRecord;
    rec.ttl = ttl;
    return d->recordUpdate(reg_id, -1, rec, lowLevelError);
}

void QDnsSd::recordRemove(int rec_id)
{
    d->recordRemove(rec_id);
}

void QDnsSd::stop(int id)
{
    d->stop(id);
}

QByteArray QDnsSd::createTxtRecord(const QList<QByteArray> &strings)
{
    // split into var/val and validate
    QList<QByteArray> vars;
    QList<QByteArray> vals; // null = no value, empty = empty value
    foreach(const QByteArray &i, strings)
    {
        QByteArray var;
        QByteArray val;
        int n = i.indexOf('=');
        if(n != -1)
        {
            var = i.mid(0, n);
            val = i.mid(n + 1);
        }
        else
            var = i;

        for(int n = 0; n < var.size(); ++n)
        {
            unsigned char c = var[n];
            if(c < 0x20 || c > 0x7e)
                return QByteArray();
        }

        vars += var;
        vals += val;
    }

    TXTRecordRef ref;
    QByteArray buf(256, 0);
    TXTRecordCreate(&ref, buf.size(), buf.data());
    for(int n = 0; n < vars.count(); ++n)
    {
        int valueSize = vals[n].size();
        char *value;
        if(!vals[n].isNull())
            value = vals[n].data();
        else
            value = 0;

        DNSServiceErrorType err = TXTRecordSetValue(&ref,
            vars[n].data(), valueSize, value);
        if(err != kDNSServiceErr_NoError)
        {
            TXTRecordDeallocate(&ref);
            return QByteArray();
        }
    }
    QByteArray out((const char *)TXTRecordGetBytesPtr(&ref), TXTRecordGetLength(&ref));
    TXTRecordDeallocate(&ref);
    return out;
}

QList<QByteArray> QDnsSd::parseTxtRecord(const QByteArray &txtRecord)
{
    QList<QByteArray> out;
    int count = TXTRecordGetCount(txtRecord.size(), txtRecord.data());
    for(int n = 0; n < count; ++n)
    {
        QByteArray keyBuf(256, 0);
        uint8_t valueLen;
        const void *value;
        DNSServiceErrorType err = TXTRecordGetItemAtIndex(
            txtRecord.size(), txtRecord.data(), n, keyBuf.size(),
            keyBuf.data(), &valueLen, &value);
        if(err != kDNSServiceErr_NoError)
            return QList<QByteArray>();

        keyBuf.resize(qstrlen(keyBuf.data()));

        QByteArray entry = keyBuf;
        if(value)
        {
            entry += '=';
            entry += QByteArray((const char *)value, valueLen);
        }
        out += entry;
    }
    return out;
}

#include "qdnssd.moc"
