#ifdef ENABLE_DNSSD
#ifdef ENABLE_BONJOUR
    #include "dns_sd.h"


    typedef uint32_t t_svc_iface_idx;
    typedef struct {
        uint16_t  txtLen;
        const unsigned char *txtStr;
    } t_svc_txt_record;
#elif defined ENABLE_AVAHI
    #include <netinet/in.h>
    #include <avahi-client/client.h>
    #include <avahi-client/lookup.h>

    #include <avahi-common/simple-watch.h>
    #include <avahi-common/malloc.h>
    #include <avahi-common/error.h>

    typedef AvahiIfIndex t_svc_iface_idx;
    typedef AvahiStringList* t_svc_txt_record;
#else
    #error Invalid DNSSD backend
#endif
#include "ltimer.h"
#include "e502api_tcp_private.h"



#include <stdlib.h>

#define E502_SVC_REC_SIGN     0xE502CCA5
#define E502_SVC_BROWSE_SIGN  0xE502BBA5

#define SERVICE_CONTEXT_MAX_CNT  1024


#define E502_TCP_SERVICE_NAME       "_lcard_acqdev._tcp"


#define E502_CHECK_SVC_REC(svc) ((svc != NULL) ? (svc)->sign == E502_SVC_REC_SIGN ? X502_ERR_OK \
    : X502_ERR_INVALID_SVC_RECORD_HANDLE : X502_ERR_INVALID_SVC_RECORD_HANDLE)

#define E502_CHECK_BROWSE_CONTEXT(svc) ((svc != NULL) ? (svc)->sign == E502_SVC_BROWSE_SIGN ? X502_ERR_OK \
    : X502_ERR_INVALID_SVC_BROWSE_HANDLE : X502_ERR_INVALID_SVC_BROWSE_HANDLE)

typedef struct st_e502_eth_svc_record {
    uint32_t sign;
    char devname[X502_DEVNAME_SIZE];
    char serial[X502_SERIAL_SIZE];
    char service_name[X502_INSTANCE_NAME_SIZE];
    char *domain;
    char *hosttarget;
    uint16_t port;
    t_svc_iface_idx iface_idx;
    uint32_t ipv4_addr;
    int op_in_progr;
    int32_t op_err;
} t_svc_record;



typedef struct {
#if defined ENABLE_BONJOUR
    DNSServiceRef sdref;
    DNSServiceRef sdref_addr;
#elif defined ENABLE_AVAHI
    AvahiServiceResolver *resolver;
#endif
    t_e502_eth_svc_event event;
    int init_done;
    int fail;
    t_e502_eth_svc_record_hnd rec;
} t_service_context;

typedef struct st_e502_eth_svc_browse_context {
    uint32_t sign;
#if defined ENABLE_BONJOUR
    DNSServiceRef main_ref;
    DNSServiceRef browse_ref;
    int socket;
#elif defined ENABLE_AVAHI
    AvahiSimplePoll *poll;
    AvahiClient *client;
    AvahiServiceBrowser *sb;
#endif
    int32_t err;
    unsigned svc_cnt;
    t_service_context services[SERVICE_CONTEXT_MAX_CNT];
} t_e502_service_browse_context;



#if defined ENABLE_BONJOUR
    void DNSSD_API f_cb_resolve (DNSServiceRef sdRef, DNSServiceFlags flags,
                                 uint32_t interfaceIndex,
                                 DNSServiceErrorType errorCode,
                                 const char *fullname, const char *hosttarget,
                                 uint16_t  port,        /* In network byte order */
                                 uint16_t  txtLen,
                                 const unsigned char *txtRecord,
                                 void  *context);

#elif defined ENABLE_AVAHI

    static void f_resolve_callback(AvahiServiceResolver *r,  AvahiIfIndex interface,
                                    AvahiProtocol protocol, AvahiResolverEvent event,
                                    const char *name, const char *type,
                                    const char *domain,  const char *host_name,
                                    const AvahiAddress *address, uint16_t port,
                                    AvahiStringList *txt, AvahiLookupResultFlags flags,
                                    void* userdata);
#endif



static LINLINE void f_set_timeval_left(t_ltimer *tmr, struct timeval *tval) {
    t_lclock_ticks left = ltimer_expiration(tmr);
    tval->tv_sec = left / LCLOCK_TICKS_PER_SECOND;
    tval->tv_usec = (left % LCLOCK_TICKS_PER_SECOND) * 1000000/LCLOCK_TICKS_PER_SECOND;
}



static t_e502_eth_svc_record_hnd f_service_record_create(void) {
    t_e502_eth_svc_record_hnd svc = calloc(1, sizeof(t_svc_record));
    if (svc != NULL) {
        svc->sign = E502_SVC_REC_SIGN;
    }
    return svc;
}

static t_e502_eth_svc_record_hnd f_service_record_create_copy(t_e502_eth_svc_record_hnd rec) {
    t_e502_eth_svc_record_hnd ret = NULL;
    if (E502_CHECK_SVC_REC(rec) == X502_ERR_OK) {
         ret = f_service_record_create();
         memcpy(ret, rec, sizeof(t_svc_record));
         if (rec->domain != NULL) {
             size_t len = strlen(rec->domain) + 1;
             ret->domain = malloc(len);
             if (ret->domain != NULL) {
                 strcpy(ret->domain, rec->domain);
             } else {
                 E502_EthSvcRecordFree(ret);
                 ret = NULL;
             }             
         }
         ret->hosttarget = NULL;
    }
    return ret;
}

X502_EXPORT(int32_t) E502_EthSvcRecordFree(t_e502_eth_svc_record_hnd rec) {
    int32_t err = E502_CHECK_SVC_REC(rec);
    if (err == X502_ERR_OK) {
        if (rec->domain != NULL)
            free(rec->domain);
        if (rec->hosttarget)
            free(rec->hosttarget);
        memset(rec, 0, sizeof(t_svc_record));
        free(rec);
    }
    return err;
}


static void f_svc_context_free(t_service_context *svc_context) {
#if defined ENABLE_BONJOUR
    if (svc_context->sdref != NULL)
        DNSServiceRefDeallocate(svc_context->sdref);
    if (svc_context->sdref_addr)
        DNSServiceRefDeallocate(svc_context->sdref_addr);
#elif defined ENABLE_AVAHI
    if (svc_context->resolver)
        avahi_service_resolver_free(svc_context->resolver);
#endif
    if (svc_context->rec != NULL)
        E502_EthSvcRecordFree(svc_context->rec);
    svc_context->event = E502_ETH_SVC_EVENT_NONE;
    svc_context->init_done = 0;
}

static int32_t f_browse_context_free(t_e502_eth_svc_browse_hnd context) {
    int32_t err = E502_CHECK_BROWSE_CONTEXT(context);
    if (err == X502_ERR_OK) {
        unsigned i;
        for (i=0; i < context->svc_cnt; i++) {
            f_svc_context_free(&context->services[i]);
        }
#if defined ENABLE_BONJOUR
        if (context->browse_ref != NULL)
            DNSServiceRefDeallocate(context->browse_ref);
        if (context->main_ref != NULL)
            DNSServiceRefDeallocate(context->main_ref);
#elif defined ENABLE_AVAHI
        if (context->sb != NULL)
            avahi_service_browser_free(context->sb);
        if (context->client != NULL)
            avahi_client_free(context->client);
        if (context->poll != NULL) {
            avahi_simple_poll_quit(context->poll);
            avahi_simple_poll_free(context->poll);
        }
#endif
        memset(context, 0, sizeof(t_e502_service_browse_context));
        free(context);
    }
    return err;
}


#if defined ENABLE_BONJOUR
    static const char* f_get_txt_rec(t_svc_txt_record txt_record, const char *name, uint8_t *len) {
        return TXTRecordGetValuePtr(txt_record.txtLen, txt_record.txtStr, name, len);
    }
#elif defined ENABLE_AVAHI
    static const char* f_get_txt_rec(t_svc_txt_record txt_record, const char *name, uint8_t *len) {
        const char *ret = NULL;
        int out = 0;
        int name_len = strlen(name);
        while (!out) {
            const char *label = (const char*)txt_record->text;
            int rec_len = strlen(label);
            const char *label_end = strchr(label, '=');
            int label_len = (label_end != NULL) ? label_end - label : rec_len;
            if ((name_len == label_len) && !memcmp(name, label, label_len)) {
                int val_len = label_end == NULL ? 0 : rec_len - label_len;
                if (val_len != 0) {
                    ret = label_end+1;
                } else {
                    ret = NULL;
                }
                *len = val_len;
                out = 1;
            }

            if (!out) {
                if (txt_record->next != NULL) {
                    txt_record = txt_record->next;
                } else {
                    out = 1;
                }
            }
        }
        return ret;
    }
#endif

/* Анализ текстовой информации о найденном сервисе и проверка, подходит ли он */
static int32_t f_svc_check_resolved(t_service_context *svc_context,
                                     t_svc_txt_record txt_record) {
    uint8_t len;
    int32_t err = X502_ERR_OK;
    const char *devname_ptr = f_get_txt_rec(txt_record, "devname", &len);
    const char *serial_ptr;
    if ((devname_ptr != NULL) &&
        (!strncmp(devname_ptr, E502_DEVICE_NAME, len) || !strncmp(devname_ptr, E16_DEVICE_NAME, len))) {
        memcpy(svc_context->rec->devname, devname_ptr, len);
        serial_ptr = f_get_txt_rec(txt_record, "serial", &len);
        if (serial_ptr != NULL) {
            if (len >= X502_SERIAL_SIZE)
                len = X502_SERIAL_SIZE-1;
            memcpy(svc_context->rec->serial, serial_ptr, len);
            svc_context->rec->serial[len] = 0;
        }        
    } else {
        err = X502_ERR_INVALID_DEVICE;
    }
    return err;
}


static void f_add_new_svc(t_e502_eth_svc_browse_hnd browse_context, t_svc_iface_idx iface_idx,
                         const char *svc_name, const char *domain, const char *regtype) {
    if (browse_context->svc_cnt < SERVICE_CONTEXT_MAX_CNT) {
        t_service_context *svc_context = &browse_context->services[browse_context->svc_cnt];
        size_t domain_len = strlen(domain) + 1;

        memset(svc_context, 0, sizeof(t_service_context));
        svc_context->rec = f_service_record_create();
        if (svc_context->rec != NULL) {
            strncpy(svc_context->rec->service_name, svc_name, X502_INSTANCE_NAME_SIZE-1);
            svc_context->rec->service_name[X502_INSTANCE_NAME_SIZE-1] = '\0';
            svc_context->rec->domain = malloc(domain_len);
            if (svc_context->rec->domain != NULL) {
                strcpy(svc_context->rec->domain, domain);

                svc_context->rec->iface_idx = iface_idx;
#ifdef ENABLE_BONJOUR
                svc_context->sdref = browse_context->main_ref;
                if (DNSServiceResolve(&svc_context->sdref, kDNSServiceFlagsShareConnection,
                                      iface_idx, svc_name, regtype, domain,
                                      f_cb_resolve, browse_context) == kDNSServiceErr_NoError) {
                    browse_context->svc_cnt++;
                } else {
                    svc_context->sdref = NULL;
                    E502_EthSvcRecordFree(svc_context->rec);
                }
#elif defined ENABLE_AVAHI
                svc_context->resolver = avahi_service_resolver_new(browse_context->client,
                                                                   iface_idx, AVAHI_PROTO_INET,
                                                                   svc_name, regtype, domain,
                                                                   AVAHI_PROTO_UNSPEC, 0, f_resolve_callback, browse_context);
                if (svc_context->resolver != NULL) {
                    browse_context->svc_cnt++;
                } else {
                    E502_EthSvcRecordFree(svc_context->rec);
                }
#endif
            } else {
                E502_EthSvcRecordFree(svc_context->rec);
            }
        }
    }
}

static void f_remove_service_idx(t_e502_eth_svc_browse_hnd browse_context, unsigned i) {
    t_service_context *svc_context = &browse_context->services[i];
    if (svc_context->init_done && !svc_context->fail) {
        svc_context->event = E502_ETH_SVC_EVENT_REMOVE;
    } else {
        f_svc_context_free(svc_context);
        if (i != (browse_context->svc_cnt-1)) {
            memmove(&browse_context->services[i], &browse_context->services[i+1],
                    (browse_context->svc_cnt-i-1)*sizeof(browse_context->services[0]));
        }
        browse_context->svc_cnt--;
    }
}


static void f_svc_fail(t_e502_eth_svc_browse_hnd browse_context, unsigned i) {
    t_service_context *svc_context = &browse_context->services[i];
    if (svc_context->init_done) {
        svc_context->event = E502_ETH_SVC_EVENT_REMOVE;
    }
    svc_context->fail = 1;
    svc_context->init_done = 0;
}



static void f_remove_service(t_e502_eth_svc_browse_hnd browse_context, const char *svc_name,
                             const char *domain) {
    unsigned i;
    for (i = 0; i < browse_context->svc_cnt; i++) {
        t_service_context *svc_context = &browse_context->services[i];
        if (!strcmp(svc_name, svc_context->rec->service_name) &&
                !strcmp(domain, svc_context->rec->domain)) {
            f_remove_service_idx(browse_context, i);
            break;
        }
    }
}

#ifdef ENABLE_BONJOUR
static uint32_t f_get_addr(const struct sockaddr *address) {
    uint32_t ipv4_addr = 0;
    if (address && address->sa_family == AF_INET) {
        const unsigned char *b = (const unsigned char *) &((struct sockaddr_in *)address)->sin_addr;
        ipv4_addr =  ((uint32_t)b[0] << 24) |
                          ((uint32_t)b[1] << 16) |
                          ((uint32_t)b[2] << 8)  |
                           b[3];
    }
    return ipv4_addr;
}



static void DNSSD_API f_get_addr_info_reply(
    DNSServiceRef                    sdRef,
    DNSServiceFlags                  flags,
    uint32_t                         interfaceIndex,
    DNSServiceErrorType              errorCode,
    const char                       *hostname,
    const struct sockaddr            *address,
    uint32_t                         ttl,
    void                             *context
    ) {
    t_e502_eth_svc_record_hnd rec = (t_e502_eth_svc_record_hnd)context;
    if (errorCode != kDNSServiceErr_NoError) {
        rec->op_err = X502_ERR_DNSSD_COMMUNICATION;
        rec->op_in_progr = 0;
    } else {
        if (address && address->sa_family == AF_INET) {            
            rec->ipv4_addr = f_get_addr(address);
            rec->op_err = X502_ERR_OK;
            rec->op_in_progr = 0;
        }
    }
}

void DNSSD_API f_cb_gethosttarget(DNSServiceRef sdRef, DNSServiceFlags flags,
                                uint32_t interfaceIndex,
                                DNSServiceErrorType errorCode,
                                const char *fullname, const char *hosttarget,
                                uint16_t  port,        /* In network byte order */
                                uint16_t  txtLen,
                                const unsigned char *txtRecord,
                                void  *context) {

    t_e502_eth_svc_record_hnd rec = (t_e502_eth_svc_record_hnd)context;
    if (errorCode != kDNSServiceErr_NoError) {
        rec->op_err = X502_ERR_DNSSD_COMMUNICATION;
    } else {
        size_t hostlen = strlen(hosttarget)+1;
        rec->port = ntohs(port);
        if (rec->hosttarget != NULL)
            free(rec->hosttarget);
        rec->hosttarget = malloc(hostlen);
        if (rec->hosttarget != NULL) {
            strcpy(rec->hosttarget, hosttarget);
            rec->op_err = X502_ERR_OK;
        } else {
            rec->op_err = X502_ERR_MEMORY_ALLOC;
        }
    }
    rec->op_in_progr = 0;
}


static void DNSSD_API f_cb_browse_addr(
    DNSServiceRef                    sdRef,
    DNSServiceFlags                  flags,
    uint32_t                         interfaceIndex,
    DNSServiceErrorType              errorCode,
    const char                       *hostname,
    const struct sockaddr            *address,
    uint32_t                         ttl,
    void                             *context
    ) {
    t_e502_eth_svc_browse_hnd browse_context = (t_e502_eth_svc_browse_hnd)context;

    unsigned i;

    for (i=0; i < browse_context->svc_cnt; i++) {
        t_service_context *svc_context = &browse_context->services[i];
        if (svc_context->sdref_addr == sdRef) {
            if (errorCode == kDNSServiceErr_NoError) {
                if (address && address->sa_family == AF_INET) {
                    uint32_t new_addr = f_get_addr(address);
                    if (svc_context->init_done == 0) {
                        svc_context->rec->ipv4_addr = new_addr;
                        svc_context->init_done = 1;
                        svc_context->event = E502_ETH_SVC_EVENT_ADD;
                    } else if (svc_context->event == E502_ETH_SVC_EVENT_NONE) {
                        if (new_addr != svc_context->rec->ipv4_addr) {
                            svc_context->rec->ipv4_addr = new_addr;
                            svc_context->event = E502_ETH_SVC_EVENT_CHANGED;
                        }
                    }
                }
            }
        }
    }
}


void DNSSD_API f_cb_resolve (DNSServiceRef sdRef, DNSServiceFlags flags,
                             uint32_t interfaceIndex,
                             DNSServiceErrorType errorCode,
                             const char *fullname, const char *hosttarget,
                             uint16_t  port,        /* In network byte order */
                             uint16_t  txtLen,
                             const unsigned char *txtRecord,
                             void  *context) {
    t_e502_eth_svc_browse_hnd browse_context = (t_e502_eth_svc_browse_hnd)context;

    unsigned i;

    for (i=0; i < browse_context->svc_cnt; i++) {
        t_service_context *svc_context = &browse_context->services[i];
        if (svc_context->sdref == sdRef) {
            t_svc_txt_record txt_rec;
            txt_rec.txtLen = txtLen;
            txt_rec.txtStr = txtRecord;            
            if (f_svc_check_resolved(svc_context, txt_rec) != X502_ERR_OK) {
                f_remove_service_idx(browse_context, i);
            } else {
                if (svc_context->init_done) {
                    if (svc_context->event == E502_ETH_SVC_EVENT_NONE)
                        svc_context->event = E502_ETH_SVC_EVENT_CHANGED;
                } else {
                    svc_context->sdref_addr = browse_context->main_ref;
                    if (DNSServiceGetAddrInfo(&svc_context->sdref_addr, kDNSServiceFlagsShareConnection,
                                              interfaceIndex,
                                              kDNSServiceProtocol_IPv4,
                                              hosttarget, f_cb_browse_addr,
                                              browse_context)
                            != kDNSServiceErr_NoError) {
                        svc_context->sdref_addr = NULL;
                        f_remove_service_idx(browse_context, i);
                    }
                }
            }
        }
    }
}


static void DNSSD_API f_browse_replay (DNSServiceRef sdRef, DNSServiceFlags flags,
                                       uint32_t  interfaceIndex, DNSServiceErrorType errorCode,
                                       const char *serviceName, const char *regtype,
                                       const char *replyDomain, void *context) {
    t_e502_eth_svc_browse_hnd browse_context = (t_e502_eth_svc_browse_hnd)context;
    if (errorCode != kDNSServiceErr_NoError) {
        browse_context->err = X502_ERR_DNSSD_COMMUNICATION;
    } else {
        if (flags & kDNSServiceFlagsAdd) {
            f_add_new_svc(browse_context, interfaceIndex, serviceName, replyDomain, regtype);
        } else {            
            f_remove_service(browse_context, serviceName, replyDomain);
        }
    }
}
#elif defined ENABLE_AVAHI
    static void f_resolve_addr_callback(AvahiServiceResolver *r,  AvahiIfIndex interface,
                                AvahiProtocol protocol, AvahiResolverEvent event,
                                const char *name, const char *type,
                                const char *domain,  const char *host_name,
                                const AvahiAddress *address, uint16_t port,
                                AvahiStringList *txt, AvahiLookupResultFlags flags,
                                void* userdata) {
        t_e502_eth_svc_record_hnd rec = (t_e502_eth_svc_record_hnd) userdata;        
        switch (event) {
            case AVAHI_RESOLVER_FAILURE:
                rec->op_err = X502_ERR_DNSSD_COMMUNICATION;
                rec->op_in_progr = 0;
                break;
            case AVAHI_RESOLVER_FOUND: {
                    size_t hostlen = strlen(host_name) + 1;
                    rec->port = port;
                    if (rec->hosttarget != NULL)
                        free(rec->hosttarget);
                    rec->hosttarget = malloc(hostlen);
                    if (rec->hosttarget != NULL) {
                        strcpy(rec->hosttarget, host_name);
                        rec->op_err = X502_ERR_OK;
                        rec->ipv4_addr =  ntohl(address->data.ipv4.address);
                    } else {
                        rec->op_err = X502_ERR_MEMORY_ALLOC;
                    }
                    rec->op_in_progr = 0;
                }
                break;
        }
    }

    static void f_resolve_callback(AvahiServiceResolver *r,  AvahiIfIndex interface,
                                    AvahiProtocol protocol, AvahiResolverEvent event,
                                    const char *name, const char *type,
                                    const char *domain,  const char *host_name,
                                    const AvahiAddress *address, uint16_t port,
                                    AvahiStringList *txt, AvahiLookupResultFlags flags,
                                    void* userdata) {
        t_e502_eth_svc_browse_hnd browse_context = (t_e502_eth_svc_browse_hnd)userdata;
        unsigned i;        
        for (i=0; i < browse_context->svc_cnt; i++) {
            t_service_context *svc_context = &browse_context->services[i];            
            if (svc_context->resolver == r) {
                /* Called whenever a service has been resolved successfully or timed out */
                switch (event) {
                    case AVAHI_RESOLVER_FAILURE:
                        f_svc_fail(browse_context, i);
                        break;
                    case AVAHI_RESOLVER_FOUND: {
                            int32_t err = f_svc_check_resolved(svc_context, txt);
                            if (err != X502_ERR_OK) {
                                f_remove_service_idx(browse_context, i);
                            } else {
                                if (svc_context->init_done == 0)  {
                                    svc_context->init_done = 1;
                                    svc_context->fail = 0;
                                    svc_context->event = E502_ETH_SVC_EVENT_ADD;
                                } else if (svc_context->event == E502_ETH_SVC_EVENT_NONE) {
                                    svc_context->event = E502_ETH_SVC_EVENT_CHANGED;
                                }
                            }
                        }
                        break;
                }
            }
        }
    }

    static void f_browse_callback(AvahiServiceBrowser *b,  AvahiIfIndex interface,
                                  AvahiProtocol protocol, AvahiBrowserEvent event,
                                  const char *name, const char *type,
                                  const char *domain, AvahiLookupResultFlags flags,
                                  void* userdata) {
        t_e502_eth_svc_browse_hnd context = (t_e502_eth_svc_browse_hnd)userdata;

        switch (event) {
            case AVAHI_BROWSER_FAILURE:
                context->err = X502_ERR_DNSSD_COMMUNICATION;
                avahi_simple_poll_quit(context->poll);                
                return;

            case AVAHI_BROWSER_NEW:
                f_add_new_svc(context, interface, name, domain, type);                
                break;

            case AVAHI_BROWSER_REMOVE:
                f_remove_service(context, name, domain);                
                break;

            case AVAHI_BROWSER_ALL_FOR_NOW:
            case AVAHI_BROWSER_CACHE_EXHAUSTED:

                break;
        }
    }

    static void f_client_callback(AvahiClient *c, AvahiClientState state, void * userdata) {

        /* Called whenever the client or server state changes */

        if (state == AVAHI_CLIENT_FAILURE) {
            t_e502_eth_svc_browse_hnd context = (t_e502_eth_svc_browse_hnd)userdata;
            context->err = X502_ERR_DNSSD_NOT_RUNNING;
            avahi_simple_poll_quit(context->poll);
        }
    }



#endif


static t_e502_eth_svc_event f_get_event(t_e502_eth_svc_browse_hnd browse_context,
                                 t_e502_eth_svc_record_hnd *svc_hnd, uint32_t *flags) {
    t_e502_eth_svc_event ret = E502_ETH_SVC_EVENT_NONE;
    unsigned i;

    for (i=0; (i < browse_context->svc_cnt) && (ret == E502_ETH_SVC_EVENT_NONE); i++) {
        t_service_context *svc = &browse_context->services[i];
        if (svc->event != E502_ETH_SVC_EVENT_NONE) {
            ret = svc->event;

            if ((svc->event == E502_ETH_SVC_EVENT_REMOVE) && !svc->fail) {
                /* так при удалении запись нам больше не нужна, то можно ее
                 * передать пользователю, без создания копии */
                *svc_hnd = svc->rec;
                svc->rec = NULL;
                f_svc_context_free(svc);
                if (i != (browse_context->svc_cnt-1)) {
                    memmove(&browse_context->services[i], &browse_context->services[i+1],
                            (browse_context->svc_cnt-i-1)*sizeof(browse_context->services[0]));
                }
                browse_context->svc_cnt--;
            } else {
                /* если событие ADD/CHANGE, то сбрасываем флаг события */
                svc->event = E502_ETH_SVC_EVENT_NONE;
                /* далаем копию записи, чтобы пользователь мог ее сохранить и
                    после завершения */
                *svc_hnd = f_service_record_create_copy(svc->rec);
            }
        }
    }
    return ret;
}




X502_EXPORT(int32_t) E502_EthSvcRecordGetInstanceName(t_e502_eth_svc_record_hnd rec, char *name) {
    int32_t err = name == NULL ? X502_ERR_INVALID_POINTER : E502_CHECK_SVC_REC(rec);
    if (err == X502_ERR_OK) {
        strcpy(name, rec->service_name);
    }
    return err;
}

X502_EXPORT(int32_t) E502_EthSvcRecordGetDevSerial(t_e502_eth_svc_record_hnd rec, char *serial) {
    int32_t err = serial == NULL ? X502_ERR_INVALID_POINTER : E502_CHECK_SVC_REC(rec);
    if (err == X502_ERR_OK) {
        strcpy(serial, rec->serial);
    }
    return err;
}

X502_EXPORT(int32_t) E502_EthSvcRecordGetDevName(t_e502_eth_svc_record_hnd rec, char *devname) {
    int32_t err = devname == NULL ? X502_ERR_INVALID_POINTER : E502_CHECK_SVC_REC(rec);
    if (err == X502_ERR_OK) {
        strcpy(devname, rec->devname);
    }
    return err;
}

#ifdef ENABLE_BONJOUR
static int32_t f_wait_result(DNSServiceRef ref, t_e502_eth_svc_record_hnd rec, t_ltimer *ptmr) {
    int32_t err = X502_ERR_OK;
    int sock = DNSServiceRefSockFD(ref);
    int nfds = sock + 1;
    int out = 0;
    do {
        fd_set readfds;
        struct timeval tv;
        int result;

        FD_ZERO(&readfds);
        FD_SET(sock, &readfds);
        f_set_timeval_left(ptmr, &tv);
        result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
        if (result > 0) {
            DNSServiceErrorType dnssd_err = DNSServiceProcessResult(ref);
            if (dnssd_err == kDNSServiceErr_NoError) {
                if (!rec->op_in_progr) {
                    err = rec->op_err;
                    out = 1;
                }
            } else {
                err = X502_ERR_DNSSD_COMMUNICATION;
            }
        } else if (ltimer_expired(ptmr) && rec->op_in_progr) {
            err = X502_ERR_SVC_RESOLVE_TIMEOUT;
            rec->op_in_progr = 0;
        }
    } while ((err == X502_ERR_OK)  && !out);
    DNSServiceRefDeallocate(ref);
    return err;
}
#endif

X502_EXPORT(int32_t) E502_EthSvcRecordResolveIPv4Addr(t_e502_eth_svc_record_hnd rec,
                                                      uint32_t *addr, uint32_t tout) {
    int32_t err = addr == NULL ? X502_ERR_INVALID_POINTER : E502_CHECK_SVC_REC(rec);
    if (err == X502_ERR_OK) {
#if defined ENABLE_BONJOUR
        DNSServiceRef ref;
        t_ltimer tmr;
        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));


        rec->op_in_progr = 1;
        if (DNSServiceResolve(&ref, 0, rec->iface_idx, rec->service_name,
                              E502_TCP_SERVICE_NAME, rec->domain,
                              f_cb_gethosttarget, rec) == kDNSServiceErr_NoError) {
            err = f_wait_result(ref, rec, &tmr);
        } else {
            err = X502_ERR_DNSSD_COMMUNICATION;
        }

        if (err == X502_ERR_OK) {
            rec->op_in_progr = 1;
            if (DNSServiceGetAddrInfo(&ref, 0, rec->iface_idx, kDNSServiceProtocol_IPv4,
                                        rec->hosttarget, f_get_addr_info_reply, rec)
                    == kDNSServiceErr_NoError) {;
                err = f_wait_result(ref, rec, &tmr);
            } else {
                err = X502_ERR_DNSSD_COMMUNICATION;
            }
        }
#elif defined ENABLE_AVAHI
        AvahiSimplePoll *poll = NULL;
        AvahiClient *client = NULL;
        AvahiServiceResolver *resolver = NULL;
        poll = avahi_simple_poll_new();
        if (poll == NULL) {
            err = X502_ERR_MEMORY_ALLOC;
        } else {
            client = avahi_client_new(avahi_simple_poll_get(poll), 0,
                                      NULL, NULL, NULL);
            if (client == NULL) {
                err = X502_ERR_DNSSD_COMMUNICATION;
            }
        }

        if (err == X502_ERR_OK) {
            t_ltimer tmr;
            int out = 0;
            ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));
            resolver = avahi_service_resolver_new(client, rec->iface_idx, AVAHI_PROTO_INET,
                                                  rec->service_name, E502_TCP_SERVICE_NAME,
                                                  rec->domain, AVAHI_PROTO_UNSPEC, 0,
                                                  f_resolve_addr_callback, rec);
            do {
                rec->op_in_progr = 1;
                avahi_simple_poll_iterate(poll, LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(&tmr)));
                if (rec->op_in_progr == 0) {
                    err = rec->op_err;
                    out = 1;
                } else if (ltimer_expired(&tmr)) {
                    err = X502_ERR_SVC_RESOLVE_TIMEOUT;
                }
            }  while ((err == X502_ERR_OK) && !out);
        }

        if (resolver != NULL)
            avahi_service_resolver_free(resolver);
        if (client != NULL)
            avahi_client_free(client);
        if (poll != NULL) {
            avahi_simple_poll_quit(poll);
            avahi_simple_poll_free(poll);
        }
#endif
        if (err == X502_ERR_OK) {
            *addr = rec->ipv4_addr;
        }
    }
    return err;
}

X502_EXPORT(int32_t) E502_EthSvcRecordIsSameInstance(t_e502_eth_svc_record_hnd svc1,
                                                     t_e502_eth_svc_record_hnd svc2) {
    int32_t err = E502_CHECK_SVC_REC(svc1);
    if (err == X502_ERR_OK)
        err = E502_CHECK_SVC_REC(svc2);
    if (err == X502_ERR_OK) {
        if (strcmp(svc1->service_name, svc2->service_name) || strcmp(svc1->domain, svc2->domain)) {
            err = X502_ERR_INSTANCE_MISMATCH;
        }
    }
    return err;
}


X502_EXPORT(int32_t) E502_EthSvcBrowseStart(t_e502_eth_svc_browse_hnd *pcontext, uint32_t flags) {
    int32_t err = X502_ERR_OK;
    t_e502_eth_svc_browse_hnd context = calloc(1, sizeof(t_e502_service_browse_context));

    if (context != NULL) {
        context->sign = E502_SVC_BROWSE_SIGN;
    } else {
        err = X502_ERR_MEMORY_ALLOC;
    }

#ifdef ENABLE_BONJOUR
    if (err == X502_ERR_OK) {
        DNSServiceErrorType dnssd_err = DNSServiceCreateConnection(&context->main_ref);
        if (dnssd_err == kDNSServiceErr_NoError) {
            context->browse_ref = context->main_ref;
            dnssd_err = DNSServiceBrowse(&context->browse_ref, kDNSServiceFlagsShareConnection, 0,
                               E502_TCP_SERVICE_NAME, NULL, f_browse_replay, context);

            if (dnssd_err == kDNSServiceErr_NoError) {
                context->socket = DNSServiceRefSockFD(context->main_ref);
            }
        }

        if (dnssd_err != kDNSServiceErr_NoError) {
            if (dnssd_err == kDNSServiceErr_ServiceNotRunning) {
                err = X502_ERR_DNSSD_NOT_RUNNING;
            } else {
                err = X502_ERR_DNSSD_COMMUNICATION;
            }
        }
    }
#elif defined ENABLE_AVAHI
    if (err == X502_ERR_OK) {
        context->poll = avahi_simple_poll_new();
        if (context->poll == NULL) {
            err = X502_ERR_MEMORY_ALLOC;
        } else {
            context->client = avahi_client_new(avahi_simple_poll_get(context->poll), 0,
                                               f_client_callback, context, NULL);
            if (context->client == NULL) {
                err = X502_ERR_DNSSD_COMMUNICATION;
            } else if (context->err != X502_ERR_OK) {
                err = context->err;
            }

            if (err == X502_ERR_OK) {
                context->sb = avahi_service_browser_new(context->client, AVAHI_IF_UNSPEC,
                                                        AVAHI_PROTO_INET,
                                                        E502_TCP_SERVICE_NAME, NULL, 0,
                                                        f_browse_callback, context);
                if (context->sb == NULL)
                    err = X502_ERR_DNSSD_COMMUNICATION;
            }
        }
    }
#endif

    if (err != X502_ERR_OK) {
        if (context != NULL)
            f_browse_context_free(context);
    } else {
        *pcontext = context;
    }

    return err;
}




X502_EXPORT(int32_t) E502_EthSvcBrowseGetEvent(t_e502_eth_svc_browse_hnd context,
                                                 t_e502_eth_svc_record_hnd *svc, uint32_t *pevent,
                                                 uint32_t *flags, uint32_t tout) {
    int32_t err = (svc == NULL) || (pevent == NULL) ? X502_ERR_INVALID_POINTER :
                                                     E502_CHECK_BROWSE_CONTEXT(context);

    if (err == X502_ERR_OK) {
        t_ltimer tmr;
        t_e502_eth_svc_event evt = f_get_event(context, svc, flags);
        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));
        context->err = X502_ERR_OK;

        while ((evt == E502_ETH_SVC_EVENT_NONE) && !ltimer_expired(&tmr) && (err == X502_ERR_OK)) {
#ifdef ENABLE_BONJOUR
            fd_set readfds;
            struct timeval tv;
            int result;

            FD_ZERO(&readfds);
            FD_SET(context->socket, &readfds);
            f_set_timeval_left(&tmr, &tv);

            result = select(context->socket + 1, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
            if (result > 0) {
                DNSServiceErrorType dnssd_err = DNSServiceProcessResult(context->main_ref);
                if (dnssd_err == kDNSServiceErr_NoError) {
                    evt = f_get_event(context, svc, flags);
                } else {
                    err = X502_ERR_DNSSD_COMMUNICATION;
                }
            }
#elif defined ENABLE_AVAHI
             avahi_simple_poll_iterate(context->poll, LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(&tmr)));
             if (context->err != X502_ERR_OK) {
                 err = context->err;
             } else {
                evt = f_get_event(context, svc, flags);
             }
#endif
        }        
        *pevent = evt;
    }
    return err;
}

X502_EXPORT(int32_t) E502_EthSvcBrowseStop(t_e502_eth_svc_browse_hnd context) {
    return f_browse_context_free(context);
}


X502_EXPORT(int32_t) E502_MakeDevRecordByEthSvc(t_x502_devrec *devrec, t_e502_eth_svc_record_hnd svc,
                                                uint32_t flags, uint32_t tout) {

    int32_t err = e502_make_tcp_rec(devrec, flags, tout, svc->devname);
    if (err==X502_ERR_OK) {
        t_tcp_devinfo_data *devinfo_data = (t_tcp_devinfo_data *)devrec->internal->iface_data;
        devinfo_data->svc_rec = f_service_record_create_copy(svc);

        strcpy(devrec->location, svc->service_name);
        devrec->location_type = X502_LOCATION_TYPE_INSTANCE_NAME;
        strcpy(devrec->serial, svc->serial);
    }
    return err;
}

int32_t e502_svc_fill_devinfo(t_tcp_devinfo_data *data) {
    uint32_t addr;
    int32_t err = E502_EthSvcRecordResolveIPv4Addr(data->svc_rec, &addr, data->open_tout);
    if (err == X502_ERR_OK) {
        data->ip_addr = addr;
        data->cmd_port = data->svc_rec->port;
    }
    return err;
}

#else
#include "e502api.h"
X502_EXPORT(int32_t) E502_EthSvcRecordFree(t_e502_eth_svc_record_hnd rec) {
    return X502_ERR_NOT_IMPLEMENTED;
}

X502_EXPORT(int32_t) E502_EthSvcRecordGetInstanceName(t_e502_eth_svc_record_hnd rec, char *name) {
    return X502_ERR_NOT_IMPLEMENTED;
}

X502_EXPORT(int32_t) E502_EthSvcRecordGetDevSerial(t_e502_eth_svc_record_hnd rec, char *serial) {
    return X502_ERR_NOT_IMPLEMENTED;
}

X502_EXPORT(int32_t) E502_EthSvcRecordResolveIPv4Addr(t_e502_eth_svc_record_hnd rec,
                                                      uint32_t *addr, uint32_t tout) {
    return X502_ERR_NOT_IMPLEMENTED;
}

X502_EXPORT(int32_t) E502_EthSvcRecordIsSameInstance(t_e502_eth_svc_record_hnd svc1,
                                                     t_e502_eth_svc_record_hnd svc2) {
    return X502_ERR_NOT_IMPLEMENTED;
}


X502_EXPORT(int32_t) E502_EthSvcBrowseStart(t_e502_eth_svc_browse_hnd *pcontext, uint32_t flags) {
    return X502_ERR_NOT_IMPLEMENTED;
}

X502_EXPORT(int32_t) E502_EthSvcBrowseGetEvent(t_e502_eth_svc_browse_hnd context,
                                                 t_e502_eth_svc_record_hnd *svc, uint32_t *pevent,
                                               uint32_t *flags, uint32_t tout) {
    return X502_ERR_NOT_IMPLEMENTED;
}

X502_EXPORT(int32_t) E502_EthSvcBrowseStop(t_e502_eth_svc_browse_hnd context) {
    return X502_ERR_NOT_IMPLEMENTED;
}

X502_EXPORT(int32_t) E502_MakeDevRecordByEthSvc(t_x502_devrec *devrec, t_e502_eth_svc_record_hnd svc,
                                                uint32_t flags, uint32_t tout) {
    return X502_ERR_NOT_IMPLEMENTED;
}
#endif

X502_EXPORT(int32_t) E502_SearchEthForDevicesIPv4Addr(t_x502_devrec* rec_list, uint32_t flags, uint32_t size, 
                                        uint32_t *devcount, uint32_t event_tout, uint32_t tcp_tout){
    
    uint32_t count = 0;
    int32_t err = X502_ERR_OK;

    t_e502_eth_svc_browse_hnd browse_hnd;
    err = E502_EthSvcBrowseStart(&browse_hnd, 0);

    if (!err){
        int run = 1;
        while (run) {
            t_e502_eth_svc_record_hnd svc_hnd;
            uint32_t event;
            // обязательно вернет значение после tout мс
            err = E502_EthSvcBrowseGetEvent(browse_hnd, &svc_hnd, &event, &flags, event_tout);
            if (!err){
                if (E502_ETH_SVC_EVENT_NONE == event) {
                    run = 0;
                } else {
                    if ((NULL != rec_list) && (count < size)) {
                        int32_t res = E502_MakeDevRecordByEthSvc(&rec_list[count], svc_hnd, flags, tcp_tout);
                        if (X502_ERR_OK != res){
                            break;
                        }
                        uint32_t ip_addr;
                        E502_EthSvcRecordResolveIPv4Addr(svc_hnd, &ip_addr ,tcp_tout);

                        sprintf(rec_list[count].location, "%d.%d.%d.%d",
                            (ip_addr>>24) & 0xFF,
                            (ip_addr>>16) & 0xFF,
                            (ip_addr>>8) & 0xFF,
                            (ip_addr>>0) & 0xFF);
                        rec_list[count].location_type = X502_LOCATION_TYPE_ADDR;
                    }
                    count++;
                }
            } else {
                run = 0;
            }
        }
    }
    err = E502_EthSvcBrowseStop(browse_hnd);

    if (NULL != devcount){
        *devcount = count;
    }
    
    return err != X502_ERR_OK ? err  : count > (uint32_t)size ? (int32_t)size : (int32_t)count ;
}
