/*
 * 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, see <https://www.gnu.org/licenses/>.
 *
 */

#include "qdnssd.h"

#include "dns_sd.h"

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

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); }
};

} // namespace

//----------------------------------------------------------------------------
// 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)
    {
        for (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
    for (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"
