/***************************************************************************//**
  @addtogroup lip_mdns
  @{
  @file lip_mdns.c
  @brief Файл содержит реализацию модуля @ref lip_mdns

  @author Borisov Alexey <borisov@lcard.ru>

  @}
 ******************************************************************************/
#include "lip_config.h"
#include "lip/misc/lip_rand_cfg_defs.h"


#ifdef LIP_USE_MDNS

#ifndef LIP_USE_UDP
    #error "mDNS requires udp: define LIP_USE_UPD in lip_conf.h"
#endif

#if !LIP_RAND_ENABLE
    #error "mDNS requires pseudo-random generator: define LIP_CFG_RAND_ENABLE in lip_confif.h"
#endif

#ifndef LIP_USE_IGMP
    #error "mDNS requires igmp: define LIP_USE_IGMP in lip_conf.h"
#endif


#include "lip/app/dns/mdns/lip_mdns_private.h"
#include "lip/app/dns/lip_dns_defs.h"
#include "lip/transport/udp/lip_udp_private.h"
#include "lip/net/ipv4/lip_igmp.h"
#include "lip/misc/lip_rand.h"
#include "lip/lip_private.h"
#include "lcspec.h"
#include "ltimer.h"
#include <string.h>

#ifdef LIP_USE_DNSSD
    #include "lip/app/dns/dns_sd/lip_dns_sd_private.h"
#endif

/****************** Установка неопределенных опциональных параметров **********/
/** @addtogroup lip_conf_mdns
 *  @{ */
#ifndef LIP_MDNS_PROBES_CNT
    /** Опционально может быть переопределено в файле конфигурации.
        Количество проверок наличия уникальной записи в сети. */
    #define LIP_MDNS_PROBES_CNT  3
#endif

#ifndef LIP_MDNS_PROBE_WAIT
    /** Опционально может быть переопределено в файле конфигурации.
        Максимальное время (в мс), которое ожидается перед первой
        проверкой уникальной записи */
    #define LIP_MDNS_PROBE_WAIT  250
#endif

#ifndef LIP_MDNS_PROBE_INTERVAL
    /** Опционально может быть переопределено в файле конфигурации.
        Интервал времени (в мс) между проверками уникальной записи. */
    #define LIP_MDNS_PROBE_INTERVAL  250
#endif

#ifndef LIP_MDNS_PROBE_DEFFERED_TIME
    /** Опционально может быть переопределено в файле конфигурации.
        Интервал времени (в мс) на который откладывается проверка,
        если во время проверки это же имя проверяет другой узел и он выигрывает
        процедуру сравнения записей */
    #define LIP_MDNS_PROBE_DEFFERED_TIME 1000
#endif

#ifndef LIP_MDNS_ANNOUNCES_CNT
    /** Опционально может быть переопределено в файле конфигурации.
        Количество посылок анонсов для записей. */
    #define LIP_MDNS_ANNOUNCES_CNT  2
#endif

#ifndef LIP_MDNS_ANNOUNCE_INTERVAL
    /** Опционально может быть переопределено в файле конфигурации.
        Интервал (в мс) между посылками анонсов. */
    #define LIP_MDNS_ANNOUNCE_INTERVAL 1000
#endif



#ifndef LIP_MDNS_MCAST_RESPONSE_INTERVAL
    /** Опционально может быть переопределено в файле конфигурации.
        Минимальный интервал (в мс) между двумя последовательными
        передачами в ответе записи с использованием multicast. */
    #define LIP_MDNS_MCAST_RESPONSE_INTERVAL  1000
#endif

#ifndef LIP_MDNS_MCAST_RESPONSE_PORBE_INTERVAL
    /** Опционально может быть переопределено в файле конфигурации.
        Минимальный интервал (в мс) между двумя последовательными
        передачами в ответе записи с использованием multicast в ответ на пробу. */
    #define LIP_MDNS_MCAST_RESPONSE_PORBE_INTERVAL  250
#endif

#ifndef LIP_MDNS_SHARED_RESP_DELAY_MIN
    /** Опционально может быть переопределено в файле конфигурации.
        Минимальная задержка (мс) между запросом и ответом на него,
        если на запрос могут ответить и другие узлы сети */
    #define LIP_MDNS_SHARED_RESP_DELAY_MIN   20
#endif

#ifndef LIP_MDNS_SHARED_RESP_DELAY_MAX
    /** Опционально может быть переопределено в файле конфигурации.
        Максимальная задержка (мс) между запросом и ответом на него,
        если на запрос могут ответить и другие узлы сети */
    #define LIP_MDNS_SHARED_RESP_DELAY_MAX   120
#endif


#ifndef LIP_MDNS_TC_RESP_DELAY_MIN
    /** Опционально может быть переопределено в файле конфигурации.
        Минимальная задержка (мс) между запросом и ответом на него,
        если в запросе установлен бит TC. */
    #define LIP_MDNS_TC_RESP_DELAY_MIN   400
#endif

#ifndef LIP_MDNS_TC_RESP_DELAY_MAX
    /** Опционально может быть переопределено в файле конфигурации.
        Максимальная задержка (мс) между запросом и ответом на него,
        если в запросе установлен бит TC. */
    #define LIP_MDNS_TC_RESP_DELAY_MAX   500
#endif

#ifndef LIP_MDNS_AGGREGATION_TIME
    /** Опционально может быть переопределено в файле конфигурации.
        Время (мс), на которое может быть отложен ответ, чтобы быть
        посланным вместе с другим ответом в одном пакете. */
    #define LIP_MDNS_AGGREGATION_TIME   500
#endif

#ifndef LIP_MDNS_AGGREGATION_PROBE_TIME
    /** Опционально может быть переопределено в файле конфигурации.
        Время (мс), на которое может быть отложен ответ на пробу,
        чтобы быть посланным вместе с другим ответом в одном пакете. */
    #define LIP_MDNS_AGGREGATION_PROBE_TIME   250
#endif

#ifndef LIP_MDNS_MAX_CONFLICT_CNT
    /** Опционально может быть переопределено в файле конфигурации.
        Максимальное количество конфликтов, после которого начинается ограничение
        по времени в #LIP_MDNS_RATE_LIMIT_INTERVAL между пробами. */
    #define LIP_MDNS_MAX_CONFLICT_CNT    15
#endif

#ifndef LIP_MDNS_RATE_LIMIT_INTERVAL
    /** Опционально может быть переопределено в файле конфигурации.
        Интервал (в мс) между проверками после наступления #LIP_MDNS_MAX_CONFLICT_CNT
        конфликтов за #LIP_MDNS_CONFLICT_TOUT. */        
    #define LIP_MDNS_RATE_LIMIT_INTERVAL  5000
#endif

#ifndef LIP_MDNS_CONFLICT_TOUT
    /** Опционально может быть переопределено в файле конфигурации.
        Интервал в мс, за который должны произойти конфликты, чтобы наступило ограничение
        на посылки проверок. Реально используется как интервал, через который, если
        нет конфликтов, обнуляется информация о их количестве. */
    #define LIP_MDNS_CONFLICT_TOUT  10000
#endif


#ifndef LIP_MDNS_LEGACY_RR_TTL_MAX
    /** Опционально может быть переопределено в файле конфигурации.
        Максимальное время жизни записей, при ответе на legacy-запрос. */
    #define LIP_MDNS_LEGACY_RR_TTL_MAX 10
#endif


#ifndef LIP_MDNS_IP_TTL
    /** Опционально может быть переопределено в файле конфигурации.
        Значение поля TTL в IP-пакете при передачи mDNS сообщений.
        По-умолчанию используется 255 для совместимости со старыми реализациями mDNS. */
    #define LIP_MDNS_IP_TTL   255
#endif

#ifndef LIP_MDNS_OS_NAME
    /** Опционально может быть переопределено в файле конфигурации.
        Название ОС, использующееся в записи HINFO. Так как lip
        предназначен для использования без ОС, то по-умолчанию --- просто название
        самого стека. */
    #define LIP_MDNS_OS_NAME "lip"
#endif

/** @} */


#define MDNS_ADDR_NODES             1
#define MDNS_ADDR_RRS               (2*MDNS_ADDR_NODES)

/* одно имя под имя хоста */
#define MDNS_INTARNAL_UNIQUE_NODES  (1 + MDNS_ADDR_NODES + LIP_MDNS_ADDITIONAL_UNIQUE_NODES_CNT)
/* одно имя под обратный адрес */
#define MDNS_INTERNAL_SHARED_NODES  (LIP_MDNS_ADDITIONAL_SHARED_NODES_CNT)
/* A + HINFO + PTR для обратного адреса */
#define MDNS_INTERNAL_RRS           (MDNS_ADDR_RRS + 1 + LIP_MDNS_ADDITIONAL_TOTAL_RR_CNT)

#ifdef LIP_USE_DNSSD
    #define MDNS_UNIQUE_NODES_MAX_CNT   (MDNS_INTARNAL_UNIQUE_NODES + LIP_DNSSD_SERVICES_CNT)
    #define MDNS_SHARED_NODES_MAX_CNT   (MDNS_INTERNAL_SHARED_NODES + (LIP_DNSSD_SERVICES_CNT + 1))
    #define MDNS_RRS_EXPLICIT_MAX_CNT   (MDNS_INTERNAL_RRS   + (4*LIP_DNSSD_SERVICES_CNT))
#else
    #define MDNS_UNIQUE_NODES_MAX_CNT   MDNS_INTARNAL_UNIQUE_NODES
    #define MDNS_SHARED_NODES_MAX_CNT   MDNS_INTERNAL_SHARED_NODES
    #define MDNS_RRS_EXPLICIT_MAX_CNT   MDNS_INTERNAL_RRS
#endif

#define MDNS_NODES_MAX_CNT          (MDNS_UNIQUE_NODES_MAX_CNT + MDNS_SHARED_NODES_MAX_CNT)
#define MDNS_RRS_TOTAL_MAX_CNT      (MDNS_UNIQUE_NODES_MAX_CNT + MDNS_RRS_EXPLICIT_MAX_CNT)







/* MDNS переопределяет бит поля класс в question для указания,
    что требуется unicast, а не multicast ответ */
#define LIP_MDNS_QCLASS_FLAG_QU         0x8000

/* MDNS переопределяет бит поля класс в записях (RR) для указания,
    что это уникальная запись, и старые записи из кеша должны быть удалены */
#define LIP_MDNS_RRCLASS_FLAG_FLUSH     0x8000


/* возвращаемое значение при сравнение, если пакет был закончен раньше времени.
 * значение положительное, что означает, что локальная запись победила */
#define MDNS_RR_CMP_ERR_VAL  (1)

/* проверка, не является ли запись псевдо-записью (генерируемой на лету) */
#define MDNS_RR_IS_PSEUDO(prr_info)  ((prr_info)->rr.type == LIP_DNS_RRTYPE_NSEC)
/* признак, что запись действительна */
#define MDNS_RR_ESTABLISHED(prr_info) !(((prr_info)->flags & MDNS_RR_FLAGS_REMOVE) \
                                        || ((prr_info)->rr.ttl == 0))
/* признак, что еще не завершился этап анонса записи */
#define MDNS_RR_ANNOUNCE_IN_PROGRESS(prr_info) (!MDNS_RR_IS_PSEUDO(prr_info) && \
                                    ((prr_info)->announce_cnt < (LIP_MDNS_ANNOUNCES_CNT-1)))


#define MDNS_PUT_FLAGS_NSEC_GEN(flags)  ((flags) & (LIP_MDNS_ADDFLAGS_UNIQUE | LIP_MDNS_ADDFLAGS_REVERSE_ADDR_MAP))
#define MDNS_NODE_FLAGS_NSEC_GEN(flags) ((flags) & (MDNS_NODE_FLAGS_UNIQUE | MDNS_NODE_FLAGS_REVERSE_ADDR_MAP))



/* Флаги состояния модуля, которые могут присутствовать в #f_state_flags */
typedef enum {
    /* Признак, что адрес IPv4 был установлен и добавлена запись типа A */
    MDNS_STATE_FLAGS_IPV4_SET         = 0x0001,
    MDNS_STATE_FLAGS_IPV6_SET         = 0x0002,
    /* Признак, что есть записи, которые проходят проверку на конфликты */
    MDNS_STATE_FLAGS_PROBE            = 0x0004,
    /* Признак, что есть записи, для которых не завершено анонсирование */
    MDNS_STATE_FLAGS_ANNOUNCE         = 0x0008,
    /* Признак, что есть ответы, которые нужно послать */
    _MDNS_STATE_FLAGS_RESPONSE        = 0x0010,
    /* Признак, что нужно проверить пакеты, помеченные на удаление */
    MDNS_STATE_FLAGS_SEND_GOODBYE     = 0x0020,
    /* Признак, что есть unicast-ответ, который нужно послать */
    MDNS_STATE_FLAGS_UNICAST_RESPONSE = 0x0040,
    /* Признак, что ожидаем продолжения пакета с TC-флагом */
    MDNS_STATE_FLAGS_WAIT_TC          = 0x0080,
    /* Признак, что есть записи, помеченные флагом REMOVE, которые надо удалить */
    MDNS_STATE_FLAGS_REMOVE_RRS       = 0x0100,
} t_state_flags;

/* Флаги, используемые в функции f_put_rr() */
typedef enum {
    /* Флаг указывает, что содержимое записи SRV не нужно упаковывать (используется
       при совместимости с legacy-реализациями mDNS */
    LIP_DNS_PUTRR_FLAGS_UNSUPPORT_SRV_COMPR   = 0x0001,
    /* Флаг указывает, что в поле класса записи при передаче необходимо
        установить старший бит для очистки кеша приемников - cache flush */
    LIP_DNS_PUTRR_FLAGS_CACHE_FLUSH           = 0x0002,
    /* Флаг указывает, что добавляется lagacy-запись */
    LIP_DNS_PUTRR_FLAGS_MDNS_LEGACY           = 0x0004,
    /* Признак, что нужно пометить с помощью MDNS_RR_FLAGS_ADDITION_NEED
       дополнительную информацию, связанную с помещаемой записью */
    LIP_DNS_PUTRR_FLAGS_MARK_ADDITIONAL       = 0x008,

    /* Флаг, что запись надо сравнить, а не сохранить в пакете */
    LIP_DNS_PUTRR_FLAGS_CMP_ONLY              = 0x0010,
} t_lip_dns_putrr_flags;


/* внутренние флаги текущего состояния зарегистрированного доменного имени */
typedef enum {
    /* Признак, что данное имя узла уникально и его надо проверить */
    MDNS_NODE_FLAGS_UNIQUE             = 0x01,
    /* Признак, что имя данного узла проходит проверку */
    MDNS_NODE_FLAGS_PROBE              = 0x02,
    /* Признак, что имя данного узла еще не проверено, но проверка отложена
       из-за конфликта при предыдущей */
    MDNS_NODE_FLAGS_PROBE_DEFFERED     = 0x04,
    /* Признак, что этот узел соответствует обратной адресной записи */
    MDNS_NODE_FLAGS_REVERSE_ADDR_MAP   = 0x08,



    /* Признак, что на этот узел пришел запрос.
       Действителен только внутри f_process_packet() */
    MDNS_NODE_FLAGS_QUESTIONED         = 0x10,
    /* Признак, что по данному узлу произошел конфликт.
     *  Действителен только внутри f_process_packet() */
    MDNS_NODE_FLAGS_CONFLICT            = 0x20,
    /* признак, что для этого узла был записан вопрос при передаче пробы */
    MDNS_NODE_FLAGS_QUESTION_PUT        = 0x40
} t_mdns_node_flags;

/* Внутренние флаги текущего состояния зарегистрированной записи */
typedef enum {
    /* Признак, что идет надо передать запись по mcast */
    MDNS_RR_FLAGS_ANNOUNCE              = 0x0001,
    /* Признак, что данная передача по mcast запланирована в ответ на пробу */
    MDNS_RR_FLAGS_ANNOUNCE_FOR_PROBE    = 0x0002,

    /* Признак, что данная запись передавалась недавно по mcast и
       время передачи действительно */
    MDNS_RR_FLAGS_MCAST_TIME_VALID      = 0x0004,

    /* Признак, что запись должна быть удалена */
    MDNS_RR_FLAGS_REMOVE                = 0x0010,
    /* Признак, что данная запись входит в unicast-ответ, запланированный
       на передачу */
    MDNS_RR_FLAGS_UNICAST_RESP          = 0x0020,



    /* Признак, что данная запись была уже добавлена в сформированный ответ.
       действительна только внутри f_send_response() */
    MDNS_RR_FLAGS_PUT_RESP              = 0x0040,
    /* Признак, что эту запись нужно положить в additional-секцию.
       действительна только внутри f_send_response() */
    MDNS_RR_FLAGS_ADDITION_NEED         = 0x0080,
    /* Признак, что для этой записи запросили ответ с установленным флагом QU.
       Действителен только внутри f_process_packet(). */
    MDNS_RR_FLAGS_UNICAST_REQ           = 0x0100,
    /* Признак, что для этой записи запросили multicast-ответ.
       Действителен только внутри f_process_packet(). */
    MDNS_RR_FLAGS_MULTICAST_REQ         = 0x0200,

    /* Признак, что нашли одинаковую запись при probe tiebreaking */
    MDNS_RR_FLAGS_TIEBREAK_FND_EQU      = 0x0400,
    /* Признак, что эта запись запланирована на ответ по mcast на пакет с флагом TC
       (и ни на какой другой) */
    MDNS_RR_FLAGS_WAIT_TC_MCAST         = 0x0800,
    /* Признак, что эта запись запланирована на ответ по ucast на пакет с флагом TC
       (и ни на какой другой) */
    MDNS_RR_FLAGS_WAIT_TC_UCAST         = 0x1000,
    /* Признак, что эта запись соответствует записи сервиса в протоколе DNS-SD */
    MDNS_RR_FLAGS_DNS_SD                = 0x2000

} t_rr_flags;



/* Тип посылаемого ответа для f_send_response() */
typedef enum {
    MDNS_SEND_RESP_MCAST, /* передача multicast ответа или анонса */
    MDNS_SEND_RESP_UNICAST, /* передача unicast ответа */
    MDNS_SEND_RESP_LEGACY /* передача legacy-ответа */
} t_send_resp_type;

/* Параметры legacy-запроса, используемые при передаче ответа
   в f_send_response()*/
typedef struct {
    const t_lip_dns_hdr *hdr; /* указатель на начало пакета запроса */
    const uint8_t *qend; /* указатель на байт идущий сразу за последним байтом
                             секции вопросов в запросе */
    const uint8_t *addr; /* адрес, с которого пришел запрос */
    t_lip_ip_type ip_type; /* тип адреса, с которого пришел запрос */
    uint16_t qcnt; /* количество вопросов в запросе */
    uint16_t id; /* id-запросе */
    uint16_t port; /* номер udp-порта, с которого пришел запрос */
} t_mdns_lagacy_info;

/* Структура, описывающая текущее состояние записи (RR) */
typedef struct {
    uint16_t flags;  /* флаги, описывающие состояние */
    t_lip_dns_rr rr; /* сама изначальная запись */
    const void *rdata; /* указатель на данные записи (реально здесь, а не в rr) */
    uint16_t announce_cnt; /* количество сделанных передач при проверке/анонсе */
    uint16_t rr_later_cnt; /* количество записей lexicographical later при probe tiebreaking */

    t_lclock_ticks mcast_snd_time; /* время последней отправки записи по mcast
                               (если действителен флаг MDNS_RR_FLAGS_MCAST_TIME_VALID */
    t_lclock_ticks time_announce_init; /* начальное время, на которое запланировано
                                  послать ответ (при флаге MDNS_RR_FLAGS_ANNOUNCE) */
    t_lclock_ticks time_announce; /* текущее время, на которое запланировано послать
                             запись по mcast. может быть отложено относительно
                             time_announce_init максимум на LIP_MDNS_AGGREGATION_TIME */
} t_mdns_rr_info;


/* Структура, описывающая состояние узла mDNS, включая состояние
   всех записей (RR) */
typedef struct {
    t_lip_dns_name domain; /* dns-имя узла */
    t_lip_mdns_name_conflict_cb conflict_cb; /* функция, вызываемая при конфликте для узла */
    uint16_t conflict_cnt; /* количество конфликтов для уникального узла */
    uint16_t flags;    /* флаги */
    uint8_t probes_cnt; /* количество повторных передач по данному узлу */
    uint8_t rr_cnt; /* количество записей, принадлежащий данному узлу */
    uint16_t rr_idx; /* номер первой записи, соответствующей данному узлу */
} t_mdns_node;

/* порт UDP по которому работает протокол mDNS */
#define LIP_UDP_PORT_MDNS        5353
/* multicast адрес IPv4, используемый mDNS */
static const uint8_t f_mdns_ipv4_addr[LIP_IPV4_ADDR_SIZE] = {224,0,0,251};

static const char f_mdns_domain_name[] = "local";

static const t_lip_dns_name f_mdns_domain = {f_mdns_domain_name, 0};

#ifdef LIP_MDNS_CPU_NAME
    static const char f_cpu_name[] = LIP_MDNS_CPU_NAME;
    static const char f_os_name[] = LIP_MDNS_OS_NAME;
    static const t_lip_dns_rdata_hinfo f_hinfo = {f_cpu_name, f_os_name};
#endif

static t_mdns_node f_nodes[MDNS_NODES_MAX_CNT];
static t_mdns_rr_info f_rrs[MDNS_RRS_TOTAL_MAX_CNT];

uint16_t f_nodes_cnt;
uint16_t f_rr_cnt;
/* количество использованных зарезервированных записей под адреса */
uint16_t f_addr_nodes_cnt;
uint16_t f_addrl_rr_cnt;

/* флаги состояния всего модуля */
static uint16_t f_state_flags;
/* таймер для следующей проверки */
static t_ltimer f_probe_tmr;
/* таймер для посылки следующего анонса */
static t_ltimer f_announce_tmr;

/* таймер для передачи unicast-ответа по IPv4 */
static t_ltimer f_unicast_tmr;
/* адрес, на который нужно послать unicast-ответ */
static uint8_t f_unicast_addr[LIP_IPV4_ADDR_SIZE];
/* адрес, с которого пришел последний запрос с флгом TC */
static uint8_t f_tc_addr[LIP_IPV4_ADDR_SIZE];

/* строка с переведенным текущим IPv4 адресом в строковое представление*/
static char f_rev_addr_str[LIP_IPV4_ADDR_SIZE*4];
/* имя домена, для разрешения имен по адресам */
static const char f_rev_addr_domain_str[] = "in-addr.arpa";
static const t_lip_dns_name f_rev_addr_domain = {f_rev_addr_domain_str, NULL};
static const t_lip_dns_name f_rev_ipv4_addr = {f_rev_addr_str, &f_rev_addr_domain};

/* количество конфликтов имен, которое произошло
    за последние #LIP_MDNS_CONFLICT_TOUT */
static uint8_t f_conflict_cnt;
/* время последнего конфликта */
static t_lclock_ticks f_conflict_time;

/* Объявление статических функций */
static void f_start_announce(t_mdns_rr_info* rr, t_lclock_ticks time);
static LINLINE void f_start_probe(void);
static void f_init_rr_state(t_mdns_node *node, t_mdns_rr_info *rr, int flags);
static t_lip_errs f_add_rr_to_node(t_mdns_node *node, const t_lip_dns_rr *rr,
                            const void *rdata, int flags);


#define MDNS_HOST_NAME_IS_SET() ((f_nodes_cnt != 0) && (f_nodes[0].domain.name != NULL))


static void f_send_response(t_send_resp_type type, const t_mdns_lagacy_info *legacy_info);


/***************************************************************************//**
  @internal
  Добавляем записи к существующему узлу, если есть место в памяти f_node_state_mem.
  Вся информация за добавленными записями соответственно смещается.
  Поле rr_cnt узла соответственно увеличивается на rr_cnt
  
  @param[in,out] add_node - указатель на узел, к которому добавляются записи
                             (должен быть внутри f_node_state_mem)
  @param[in] rr_cnt       - количество добавляемых записей (>0)
  @param[in] flags        - флаги
  @return                 - указатель на первую добавленную запись или NULL, если
                             недостаточно места
 ******************************************************************************/  
static t_mdns_rr_info *f_alloc_new_rrs(t_mdns_node *add_node, unsigned add_rr_cnt, int flags) {
    t_mdns_rr_info *res_rr = NULL;
    unsigned max_rrs = (flags & LIP_MDNS_ADDFLAGS_INTERNAL_ADDR) ? MDNS_RRS_TOTAL_MAX_CNT :
                                        MDNS_RRS_TOTAL_MAX_CNT - MDNS_ADDR_RRS + f_addrl_rr_cnt;
    lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
               "lip mdns: alloc rrs - internal %d, rr_cnt %d, add rrs %d, max_rrs %d\n",
                !!(flags & LIP_MDNS_ADDFLAGS_INTERNAL_ADDR), f_rr_cnt, add_rr_cnt, max_rrs);
    if ((f_rr_cnt + add_rr_cnt) <= max_rrs) {
        int put_idx = add_node->rr_idx + add_node->rr_cnt;

        res_rr = &f_rrs[put_idx];

        if (put_idx != f_rr_cnt) {
            /* еслиз запись добавляется не в конец, то сдвигаем память записей,
               идущих после добавляемой, а также индексы в идущих после узлах */
            memmove(&f_rrs[put_idx + add_rr_cnt], &f_rrs[put_idx], (f_rr_cnt - put_idx) * sizeof(f_rrs[0]));

            for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
                if ((&f_nodes[node_idx] != add_node) && (f_nodes[node_idx].rr_idx >= add_node->rr_idx)) {
                    f_nodes[node_idx].rr_idx += add_rr_cnt;
                }
            }
        }

        /* изменяем кол-во записей для узла */
        add_node->rr_cnt += add_rr_cnt;
        f_rr_cnt += add_rr_cnt;

        if (flags & LIP_MDNS_ADDFLAGS_INTERNAL_ADDR)
            f_addrl_rr_cnt += add_rr_cnt;
    }

    return res_rr;
}

static LINLINE void f_nsec_fill_rr(t_lip_dns_rr *rr) {
    rr->rdlength = 0;
    rr->rclass = LIP_DNS_CLASS_IN;
    rr->type = LIP_DNS_RRTYPE_NSEC;
    rr->ttl = LIP_MDNS_DEFAULT_TTL;
}


/* в спецификации утверждается, что TTL для NSEC должен быть такой, как если
 * бы он был бы, если бы эта запись была. Так как явно определить это сложно,
 * то определяем TTL как минимальный среди записей того же узла */
static void f_nsec_set_ttl(t_mdns_node *node) {
    uint32_t min_ttl = 0xFFFFFFFFUL;
    int min_fnd = 0;
    t_mdns_rr_info *nsec_rr = NULL;
    for (int i = node->rr_idx; i < (node->rr_idx + node->rr_cnt); ++i) {
        if (f_rrs[i].rr.type == LIP_DNS_RRTYPE_NSEC) {
            nsec_rr = &f_rrs[i];
        } else if (MDNS_RR_ESTABLISHED(&f_rrs[i])) {
            if (f_rrs[i].rr.ttl < min_ttl) {
                min_ttl = f_rrs[i].rr.ttl;
                min_fnd = 1;
            }
        }
    }

    if ((nsec_rr != NULL) && min_fnd) {
        nsec_rr->rr.ttl = min_ttl;
    }
}


static int f_fill_new_node(t_mdns_node *node, const t_lip_dns_name *domain, int flags, unsigned rr_cnt, int *rr_idx) {
    int pseudo_rr_cnt = MDNS_PUT_FLAGS_NSEC_GEN(flags) ? 1 : 0;
    unsigned add_rr_cnt = pseudo_rr_cnt + rr_cnt;
    unsigned max_rrs = (flags & LIP_MDNS_ADDFLAGS_INTERNAL_ADDR) ? MDNS_RRS_TOTAL_MAX_CNT :
                         MDNS_RRS_TOTAL_MAX_CNT - MDNS_ADDR_RRS + f_addrl_rr_cnt;

    int ok = (f_rr_cnt + add_rr_cnt) <= max_rrs;
    if (ok) {
        memset(node, 0, sizeof(t_mdns_node));
        node->domain = *domain;
        node->rr_cnt = rr_cnt + pseudo_rr_cnt;
        node->conflict_cb = NULL;
        node->rr_idx = f_rr_cnt;


        if (MDNS_PUT_FLAGS_NSEC_GEN(flags)) {
            t_mdns_rr_info *nsec_rr = &f_rrs[node->rr_idx];
            f_nsec_fill_rr(&nsec_rr->rr);
            nsec_rr->rdata = NULL;
            f_init_rr_state(node, nsec_rr, flags);
        }

        if (flags & LIP_MDNS_ADDFLAGS_UNIQUE) {
            node->flags = MDNS_NODE_FLAGS_PROBE | MDNS_NODE_FLAGS_UNIQUE;
            f_start_probe();
        }

        if (flags & LIP_MDNS_ADDFLAGS_REVERSE_ADDR_MAP) {
            node->flags |= MDNS_NODE_FLAGS_REVERSE_ADDR_MAP;
        }

        if (rr_idx != NULL)
            *rr_idx = node->rr_idx + pseudo_rr_cnt;

        f_rr_cnt += add_rr_cnt;
        if (flags & LIP_MDNS_ADDFLAGS_INTERNAL_ADDR) {
            /* увеличиваем кол-во использованных внутренних записей.
               тут не учитываем псевдозаписи, а только явно добавляемые */
            f_addrl_rr_cnt += rr_cnt;
        }
    }
    return ok;
}

/***************************************************************************//**
 @internal
    Функция резервирует память в f_node_state_mem под новый узел в конце списка.
    Для уникального узла функция сразу планирует посылку проверки,
    а для разделяемого - планируются анонсы
    @param[in] domain   Доменное имя нового узла
    @param[in] flags    Флаги (из #t_lip_mdns_addflags)
    @param[in] rr_cnt   Количество записей в новом узле
    @return             Указатель на выделенный узел, если памяти достаточно или
                          0, если памяти не хватило
 ******************************************************************************/
static t_mdns_node *f_alloc_new_node (const t_lip_dns_name *domain, int flags,
                                      unsigned rr_cnt, int *rr_idx) {
    t_mdns_node *node = NULL;
    unsigned max_nodes = (flags & LIP_MDNS_ADDFLAGS_INTERNAL_ADDR) ? MDNS_NODES_MAX_CNT :
                                 MDNS_NODES_MAX_CNT - MDNS_ADDR_NODES + f_addr_nodes_cnt;

    lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
               "lip mdns: alloc nodes - internal %d, node_cnt %d, max_nodes %d, rr_cnt %d\n",
                !!(flags & LIP_MDNS_ADDFLAGS_INTERNAL_ADDR), f_nodes_cnt, max_nodes, f_rr_cnt);
    /* проверяем наличие свободного места в памяти */
    if (f_nodes_cnt < max_nodes) {
        node = &f_nodes[f_nodes_cnt];
        if (f_fill_new_node(node, domain, flags, rr_cnt, rr_idx)) {
            ++f_nodes_cnt;

            if (flags & LIP_MDNS_ADDFLAGS_INTERNAL_ADDR) {
                ++f_addr_nodes_cnt;
            }
        }
    }

    return node;
}



static void f_recalc_nodes_rr_idx(void) {
    uint16_t cur_idx = 0;
    for (int node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
        f_nodes[node_idx].rr_idx = cur_idx;
        cur_idx += f_nodes[node_idx].rr_cnt;
    }
}

/***************************************************************************//**
  @internal
   Удаление всех записей из памяти, помеченных флагом  #MDNS_RR_FLAGS_REMOVE
*******************************************************************************/
static void f_remove_rrs(void) {
    int removed_nodes = 0;
    for (int node_idx = 0; node_idx < f_nodes_cnt; node_idx++) {
        int valid_cnt = 0;
        int drop_cnt = 0;
        for (int rr_idx = f_nodes[node_idx].rr_idx; rr_idx <
             (f_nodes[node_idx].rr_idx + f_nodes[node_idx].rr_cnt); rr_idx++) {
            if (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_REMOVE) {
                drop_cnt++;
            } else {
                if (!MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) {
                    valid_cnt++;
                }
            }
        }

        /* не считая первой записи, соответствующей имени хоста, удаляем узел, если
           в нем остались только псевдо-записи, помечая их тоже на удаление
           (чтобы потом проще удалить записи из массива) */
        if (valid_cnt == 0) {
            if (drop_cnt != f_nodes[node_idx].rr_cnt) {
                for (int rr_idx = f_nodes[node_idx].rr_idx; rr_idx <
                     (f_nodes[node_idx].rr_idx + f_nodes[node_idx].rr_cnt); ++rr_idx) {
                    f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_REMOVE;
                }
            }
            if (node_idx != 0) {
                ++removed_nodes;
            }
            f_nodes[node_idx].rr_cnt = 0;
        } else {
            f_nodes[node_idx].rr_cnt -= drop_cnt;
        }

        /* сдвигаем элементы массива, если были удалены узлы */
        if ((f_nodes[node_idx].rr_cnt != 0) && (removed_nodes != 0))
            f_nodes[node_idx - removed_nodes] = f_nodes[node_idx];
    }

    f_nodes_cnt -= removed_nodes;

    for (int rr_idx = f_rr_cnt - 1, drop_cnt = 0; rr_idx >= 0; --rr_idx) {
        if (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_REMOVE) {
            ++drop_cnt;
        }
        /* когда попадается действительная запись или дошли до первой, то сдвигаем
           записи, чтобы удалить все помеченные идущие подряд, которые были найдены
           до этого */
        if ((drop_cnt != 0) && (!(f_rrs[rr_idx].flags & MDNS_RR_FLAGS_REMOVE) || (rr_idx == 0))) {
            int move_cnt = f_rr_cnt - (rr_idx + 1 + drop_cnt);
            if (move_cnt > 0) {
                memmove(&f_rrs[rr_idx + 1], &f_rrs[rr_idx + 1 + drop_cnt],
                        move_cnt * sizeof(f_rrs[0]));
            }
            f_rr_cnt -= drop_cnt;
            drop_cnt = 0;
        }
    }

    /* если для узла с именем хоста удалили все записи, то делаем его не
       действтиельным. Если вообще все записи удалены, то можно удалить
       и этот узел тоже */
    if (f_nodes[0].rr_cnt == 0) {
        if (f_nodes_cnt == 1) {
            f_nodes_cnt = 0;
        } else {
            f_nodes[0].domain.name = NULL;
        }

    }


    f_recalc_nodes_rr_idx();

    f_state_flags &= ~MDNS_STATE_FLAGS_REMOVE_RRS;
}


static void f_init_rr_state(t_mdns_node *node, t_mdns_rr_info *rr, int flags) {
    rr->announce_cnt = 0;
    rr->flags = 0;
    if (!MDNS_RR_IS_PSEUDO(rr)) {
        if (flags & LIP_MDNS_ADDFLAGS_UNIQUE) {
            if (node->flags & MDNS_NODE_FLAGS_UNIQUE) {
                /* если имя узла уже проверено - то сразу анонсируем,
                  иначе можно ничего не делать, стандартный процесс
                  проверки выполнится и для нового узла */
                if (!(node->flags & (MDNS_NODE_FLAGS_PROBE | MDNS_NODE_FLAGS_PROBE_DEFFERED))) {
                    f_start_announce(rr, lclock_get_ticks());
                }
            } else {
                t_lip_dns_rr nsec_rr;
                f_nsec_fill_rr(&nsec_rr);
                /* если узел не был уникальным, делаем его таким
                  и начинаем проверку */
                node->probes_cnt = 0;
                node->flags |= MDNS_NODE_FLAGS_PROBE | MDNS_NODE_FLAGS_UNIQUE;
                f_add_rr_to_node(node, &nsec_rr, NULL, flags);
                f_nsec_set_ttl(node);

                f_start_probe();
            }
        } else {
            /* разделяемую запись просто анонсируем (если только
              сейчас не идет проверка уникального узла) */
            if (!(node->flags & (MDNS_NODE_FLAGS_PROBE | MDNS_NODE_FLAGS_PROBE_DEFFERED))) {
                f_start_announce(rr, lclock_get_ticks());
            }
        }

        if (flags & LIP_MDNS_ADDFLAGS_DNS_SD_PTR)
            rr->flags |= MDNS_RR_FLAGS_DNS_SD;

        /* обновляем TTL для NSEC в случае уникального узла. но чтобы лишний раз
         * не проходится по всем записям, делаем это только если TTL ниже первой */
        if (MDNS_NODE_FLAGS_NSEC_GEN(node->flags) && (rr->rr.ttl < f_rrs[node->rr_idx].rr.ttl)) {
            f_nsec_set_ttl(node);
        }
    }
}

/***************************************************************************//**
  @internal Добавление записи к существующему узлу
  Дополнительно решается вопрос начала проверки или анонса записи в зависимости
  от уникальности узла и добавляемой записи
  @param[in] node    Указатель на структуру состояния узла, к которому
                         добавляется запись
  @param[in] rr      Указатель на структуру с параметрами добавляемой rr (без
                         переменной части)
  @param[in] rdata   Указатель на данные записи (переменная часть)
  @param[in] flags   Флаги из LIP_MDNS_FLAGS_XX
  @return            Код ошибки
  *****************************************************************************/
static t_lip_errs f_add_rr_to_node(t_mdns_node *node, const t_lip_dns_rr *rr,
                                    const void *rdata, int flags) {
    int err = LIP_ERR_SUCCESS;
    t_mdns_rr_info *new_rr;

    /* если установлен флаг, то надо удалить все старые
      записи такого же типа для данного доменного узла */
    if (flags & LIP_MDNS_ADDFLAGS_REMOVE_PREVIOUS) {
        /* если узел не уникальный, то нужно послать последний
           пакет для оповещения*/
        if (!(node->flags & MDNS_NODE_FLAGS_UNIQUE) &&
            !(flags & LIP_MDNS_ADDFLAGS_UNIQUE)) {
            for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                if (f_rrs[rr_idx].rr.type == rr->type) {
                    f_rrs[rr_idx].rr.ttl = 0;
                    f_state_flags |= MDNS_STATE_FLAGS_SEND_GOODBYE;
                }
            }
        } else {
            /* если имя уникальное, то можно сразу удалять, так
              как повторное объявление новой записи удалит из
              кэша старые данные*/
            for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                if (f_rrs[rr_idx].rr.type == rr->type) {
                    f_rrs[rr_idx].flags = MDNS_RR_FLAGS_REMOVE;
                    f_state_flags |= MDNS_STATE_FLAGS_REMOVE_RRS;
                }
            }
        }
    }

    if (f_state_flags & MDNS_STATE_FLAGS_REMOVE_RRS) {
        /* удаляем эти записи (чтобы было доп. место для добавления) */
        f_remove_rrs();
    }

    new_rr = f_alloc_new_rrs(node, 1, flags);
    /* если не удалось высвободить память, но есть записи, по которым нужно
       послать GOODBYE, то пробуем послать, чтобы их удалить */
    if ((new_rr == NULL) && (f_state_flags & MDNS_STATE_FLAGS_SEND_GOODBYE)) {
        f_send_response(MDNS_SEND_RESP_MCAST, NULL);
        new_rr = f_alloc_new_rrs(node, 1, flags);
    }

    if (new_rr != NULL) {
        new_rr->flags = 0;
        new_rr->rdata = rdata;
        new_rr->announce_cnt = 0;
        new_rr->rr = *rr;
        f_init_rr_state(node, new_rr, flags);
    } else {
        err = LIP_ERR_MDNS_INSUFFICIENT_MEMORY;
        lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_ERROR_HIGH, err,
                   "lip mdns: insufficient momory to add rr\n");
    }

    return err;
}

/***************************************************************************//**
  @internal Получение позиции в пакете DNS, следующей за указанным DNS-именем.
  @param[in] cur_pos   Указатель на первый байт DNS-имени в пакете.
  @param[in] end_pos   Указатель на байт, следующий за последним в пакете (для
                          определения переполнения).
  @return              Указатель на байт, следующий за указанной меткой, или
                       0, если вышли за пределы пакета
*******************************************************************************/
static uint8_t *f_get_pos_after_name(const uint8_t *cur_pos, const uint8_t *end_pos) {
    int out = 0;
    while (!out && (cur_pos != NULL)) {
        if (cur_pos < end_pos) {
            uint8_t len = *cur_pos++;
            switch (len & LIP_DNS_LABEL_TYPE_MSK) {
                case LIP_DNS_LABEL_TYPE_COMPRESSED:
                    /* если признак упакованного имени, то этот байт со следующим
                       определяют смещения и на этом метка заканчивается */
                    if (cur_pos < end_pos) {
                        ++cur_pos;
                    } else {
                        cur_pos = NULL;
                    }
                    out = 1;
                    break;
                case LIP_DNS_LABEL_TYPE_NORMAL:
                    if (len==0) {
                        out = 1;
                    } else {
                        cur_pos += len;
                    }
                    break;
                case LIP_DNS_LABEL_TYPE_EXTENDED:
                    /** todo extended type */
                default:
                    cur_pos = NULL;
                    break;
            }
        } else {
            cur_pos = NULL;
        }
    }
    return (uint8_t*)cur_pos;
}


static LINLINE uint8_t f_get_label_size(const char *name, const char **new_pos) {
    const char *name_pos;
    for (name_pos = name; (*name_pos != '\0') && (*name_pos != '.'); ++name_pos) {
    }

    if (new_pos != NULL)
        *new_pos = name_pos;

    return name_pos - name;
}


static LINLINE int f_cmp_dns_char(char b1, char b2) {
    int res = (int)b1 - b2;
    /* если b1 и b2 являются разным регистром одинаковых
     * английских букв, то считаем их равными. Остальные
     * символы сравниваются по коду (rfc6762 пункт 16).
     * Так как символы ASCII не встречаются нигде в качестве
     * такого байта в UTF-8, то проверку можно делать на основе
     * одного байта */
    if ((res == ('a' - 'A')) && ((b1 >= 'a') && (b1 <= 'z'))) {
        res = 0;
    } else if ((res == -('a' - 'A')) && ((b2 >= 'a') && (b2 <= 'z'))) {
        res = 0;
    }
    return res;
}

/***************************************************************************//**
 @internal
   Сравнение имен dns - одно, представлено во внутреннем представлении модуля
   (t_lip_dns_name), другое - в закодированном в соответствии со спецификацией
   DNS (@rfc{1034}), используя возможно name compression.
   Применяется перевод к одному регистру английских букв
    @param[in] node       Доменное имя во внутреннем представлении
    @param[in] pkt_pos    Указатель на доменное имя из DNS-пакета
    @param[in] hdr        Укзатель на начало заголовка DNS-пакета
                          (используется для восстановления name compression)
    @param[in] end_pos    Указатель на байт сразу за последним действительным
                          байтом буфера в котором формируется пакет
                          (для проверки не выхода за пределы)
    @return               Если имена равны, разница между первым неравным
                          символом (dns_node - pkt_pos)
 ******************************************************************************/
static int f_cmp_dns (const t_lip_dns_name *node, const uint8_t *pkt_pos,
                      const t_lip_dns_hdr *hdr, const uint8_t *end_pos) {

    const char *name_pos = node->name;
    uint8_t label_size = 0;
    int res=0;
    do {
        label_size = f_get_label_size(name_pos, NULL);

        /* В случае label_size == 0 и parent != NULL нужно перейти к родительскому
         * узлу для дальнейшего сравнения, поэтому код сравнения пропускаем и
         * переходим сразу к коду перехода к следующей метке */
        if ((label_size != 0) || (node->parent == NULL)) {
            /* если применено name compression, то переходим по указанному
              смещению в пакете */
            if ((*pkt_pos & LIP_DNS_LABEL_TYPE_MSK) == LIP_DNS_LABEL_TYPE_COMPRESSED) {
                if ((pkt_pos + 1) < end_pos) {
                    uint16_t offset = *pkt_pos++ & ~LIP_DNS_LABEL_TYPE_MSK;
                    offset <<= 8;
                    offset |= *pkt_pos++ & 0xFF;
                    pkt_pos = (uint8_t*)hdr + offset;
                } else {
                    res = MDNS_RR_CMP_ERR_VAL;
                }
            }

            /* сравниваем размеры меток */
            if (!res && (pkt_pos < end_pos)) {
                res = (int)label_size - *pkt_pos++;
            }

            if (!res) {
                /* сравниваем метки */
                if ((pkt_pos + label_size) <= end_pos) {
                    int i;
                    for (i=0; (i < label_size) && !res; ++i) {
                        res = f_cmp_dns_char(name_pos[i], *pkt_pos++);
                    }
                } else {
                    res = MDNS_RR_CMP_ERR_VAL;
                }
            } /* if (!res) */
        }

        if (!res) {
            name_pos += label_size;
            /* переходим к следующей метке */
            if (*name_pos == '.') {
                 ++name_pos;
            } else {
                if (node->parent != NULL) {
                    node = node->parent;
                    name_pos = node->name;
                    label_size = 1;
                } else  {
                    label_size = 0;
                    if (pkt_pos < end_pos) {
                        res = (int)0 - *pkt_pos++;
                    } else {
                        res = MDNS_RR_CMP_ERR_VAL;
                    }
                }
            }
        } /* if (!res && pkt_pos) */
    } while ((res == 0) && (label_size != 0));

    return res;
}

/***************************************************************************//**
  @internal
  Проверка эквивалентности двух имен узлов, представленных во внутреннем
    представлении
  @param node1   Первое имя сравнения.
  @param node2   Второе имя для сравнения.
  @return        1, если узлы равны.
                 0,  если не равны.
  *****************************************************************************/
static int f_nodes_is_equ(const t_lip_dns_name *pnode1, const t_lip_dns_name *pnode2) {
    uint8_t equ = 1, out = 0;

    /* если узлы указывают на одно имя и на один родительский домен,
       то их можно не сравнивать - точно равны */
    if ((pnode1->name != pnode2->name)
            || (pnode1->parent != pnode2->parent)) {
        t_lip_dns_name node1 = *pnode1;
        t_lip_dns_name node2 = *pnode2;

        while (!out && equ) {
            /* последняя точка в имени не имеет значения */
            if ((*node1.name == '.') && (*(node1.name + 1) == '\0')) {
                ++node1.name;
            }
            if ((*node2.name == '.') && (*(node2.name + 1)) == '\0') {
                ++node2.name;
            }

            /* проверяем вариант когда закончилась одна из меток */
            if (*node1.name == '\0') {
                /* если есть родительский домен - переходим к нему
                   (при этом во втором узле тоже должен быть переход или '.' */
                if (node1.parent != NULL) {
                    node1 = *node1.parent;
                    if (*node2.name == '\0') {
                        if (node2.parent != NULL) {
                            node2 = *node2.parent;
                        } else {
                            equ = 0;
                        }
                    } else if (*node2.name == '.') {
                       ++node2.name;
                    } else {
                        equ = 0;
                    }
                } else if ((*node2.name == '\0') && (node2.parent == NULL)) {
                    out = 1;
                } else  {
                    equ = 0;
                }
            } else if (*node2.name=='\0') {
                if (*node1.name == '.')  {
                    ++node1.name;
                    if (node2.parent!=NULL) {
                        node2 = *node2.parent;
                    } else {
                        equ = 0;
                    }
                } else {
                    equ = 0;
                }
            } else {
                equ = f_cmp_dns_char(*node1.name++, *node2.name++) == 0;
            }
        } while (equ && !out);
    }
    return equ;
}



/***************************************************************************//**
 @internal
    Функция переводит DNS имя в нужное представление для передачи и сохраняет
       его в нужном месте в пакете. При этом выполняется попытка сжать имя
       ссылкой на часть имени из уже сформированной части сообщения
       (name compression).
    @param[in] node       Доменное имя во внутреннем представлении
    @param[in] put_pos    Позиция, начиная с которой нужно сохранить доменное имя
    @param[in] hdr        Указатель на начало заголовка DNS-пакета
                          (используется для восстановления name compression)
    @param[in] end_pos    Указатель на байт сразу за последним действительным
                          байтом буфера в котором формируется пакет
                          (для проверки не выхода за пределы)
    @return               Указатель на байт сразу за последним добавленным или
                          0, если для добавления имени не хватило места
 ******************************************************************************/
static uint8_t* f_dns_put_name (const t_lip_dns_name *name,  uint8_t *put_pos,
                                const t_lip_dns_hdr *hdr, const uint8_t *end_pos) {

    t_lip_dns_name cur_node = *name;
    uint8_t label_size = 0;
    /* сжатие имен пытаемся применить только если не сохраняем метку
       в начале пакета */
    uint8_t compr = (hdr != NULL) && (&hdr->data[0] != put_pos);
    uint8_t out = 0;

    do {
        const char *name_pos;

        label_size = f_get_label_size(cur_node.name, &name_pos);

        /* если метка не нулевого размера - то пытаемся найти такую же в части
          сформированного пакета, чтобы просто поместить на нее ссылку */
        if ((label_size != 0) && compr) {
            const uint8_t *pos;
            for (pos = &hdr->data[0]; (pos < (put_pos-label_size-1)) && !out; ++pos) {
                if ((*pos == label_size) &&
                        (f_cmp_dns(&cur_node, pos, hdr, put_pos)==0)) {
                    out = 1;
                    if ((put_pos + 1) < end_pos) {
                        uint16_t offs = pos - (uint8_t*)hdr;
                        *put_pos++ = ((offs>>8) & ~LIP_DNS_LABEL_TYPE_MSK)
                                | LIP_DNS_LABEL_TYPE_COMPRESSED;
                        *put_pos++ = offs & 0xFF;
                    } else {
                        put_pos = NULL;
                    }
                }
            }
        }

        if (!out && (put_pos!=NULL)) {
            if (put_pos < (end_pos-label_size)) {
                /* сохраняем размер метки */
                *put_pos++ = label_size;
                 if (label_size!=0) {
                     memcpy(put_pos, cur_node.name, label_size);
                     put_pos += label_size;
                 }

                 /*  переходим к следующей метке */
                 if (*name_pos == '.') {
                     cur_node.name = name_pos+1;
                 } else {
                     if (cur_node.parent != NULL) {
                         cur_node = *cur_node.parent;
                         label_size = 1;
                     } else {
                         label_size = 0;
                         if (put_pos && (put_pos < end_pos)) {
                             *put_pos++ = 0;
                         } else {
                             put_pos = NULL;
                         }
                     }
                 }
            } /* if (put_pos < end_pos) */
        } /* if (!out && put_pos) */
    }  while ((label_size!=0) && !out && (put_pos!=NULL));
    return put_pos;
}


/***************************************************************************//**
 @internal
    Добавление вопрос а в запросе на получения записи в секцию question
    @param[in] qname      Доменное имя, соответствующее запросу
    @param[in] qtype      Тип запрашиваемой записи (#t_lip_dns_rr_types)
    @param[in] qclass     Класс записи (обычно #LIP_DNS_CLASS_IN)
                          + флаг #LIP_MDNS_QCLASS_FLAG_QU
    @param[in] put_pos    Позиция, начиная с которой следует поместить вопрос
    @param[in] hdr        Начало заголовка запроса
    @param[in] end_pos    Указатель на байт сразу за последним действительным
                          байтом буфера в котором формируется пакет
                          (для проверки не выхода за пределы)
    @return               Указатель на байт сразу за последним добавленным или
                          0, если для добавления имени не хватило места
 ******************************************************************************/
static LINLINE uint8_t *f_put_question(const t_lip_dns_name *qname, uint16_t qtype,
                                       uint16_t qclass,  uint8_t *put_pos,
                                       const t_lip_dns_hdr *hdr, const uint8_t *end_pos) {
    put_pos = f_dns_put_name(qname, put_pos, hdr, end_pos);
    if (put_pos != NULL) {
        if ((end_pos - put_pos) > 4) {
            *put_pos++ = (qtype >> 8)&0xFF;
            *put_pos++ = (qtype&0xFF);
            *put_pos++ = (qclass >> 8)&0xFF;
            *put_pos++ = (qclass&0xFF);
        } else {
            put_pos = NULL;
        }
    }
    return put_pos;
}



static LINLINE void f_fill_mcast_ipv4_udp(t_lip_udp_info *info) {
    /* заполняем udp информацию для отсылаемого пакета */
    memset(info, 0, sizeof(t_lip_udp_info));
    info->dst_port = info->src_port = LIP_UDP_PORT_MDNS;
    info->dst_addr = f_mdns_ipv4_addr;
    info->ip_type = LIP_IPTYPE_IPv4;
    info->ip_ttl = LIP_MDNS_IP_TTL;
}


/***************************************************************************//**
  @internal
  Функция помечает для записей заданного типа (до 2-х типов) имеющих заданное имя,
  что их необходимо добавить в дополнительную секцию пакета
  @param[in] domain    Доменное имя записи
  @param[in] type1     Первый тип, для которого нужно пометить записи
  @param[in] type2     Второй тип, для которого нужно пометить записи (может быть
                       0, если используется только один
  *****************************************************************************/
static void f_mark_addition_rr(const t_lip_dns_name *domain, uint16_t type1, uint16_t type2) {
    int fnd = 0;
    for (int node_idx=0; !fnd && (node_idx < f_nodes_cnt); node_idx++) {
        t_mdns_node *node = &f_nodes[node_idx];
        if (f_nodes_is_equ(&node->domain, domain)) {
            fnd = 1;
            for (unsigned rr_idx=node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                if ((f_rrs[rr_idx].rr.type == type1) || (f_rrs[rr_idx].rr.type == type2)) {
                    f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_ADDITION_NEED;
                    /* если SRV добавляем в доп. запись, то также нужно добавить и адреса */
                    if (f_rrs[rr_idx].rr.type == LIP_DNS_RRTYPE_SRV) {
                        const t_lip_dns_rdata_srv *srvdat = (const t_lip_dns_rdata_srv *)f_rrs[rr_idx].rdata;
                        f_mark_addition_rr(srvdat->target, LIP_DNS_RRTYPE_A, LIP_DNS_RRTYPE_AAAA);
                    }
                }
            }
        }
    }
}


static int f_put_cmp_str(const char *str, uint8_t *cur_pos,
                         const uint8_t *end_pos, int flags, uint8_t **new_pos) {
    uint8_t len = strnlen(str, LIP_DNS_STR_LEN_MAX);
    int res = 0;
    if (flags & LIP_DNS_PUTRR_FLAGS_CMP_ONLY) {
        uint8_t rem_len = *cur_pos++;
        /* вначале сравниваем длину строки */
        res = (int)len-(int)rem_len;
        if (res==0) {
            if ((cur_pos + rem_len) <= end_pos) {
                res = memcmp(str, cur_pos, len);
                cur_pos += len;
            } else {
                res = MDNS_RR_CMP_ERR_VAL;
                cur_pos = NULL;
            }
        }
    } else {
        if ((cur_pos + len + 1) <= end_pos) {
            *cur_pos++ = len;
            memcpy(cur_pos, str, len);
            cur_pos += len;
        } else {
            res = MDNS_RR_CMP_ERR_VAL;
            cur_pos = NULL;
        }
    }

    if (new_pos != NULL)
        *new_pos = cur_pos;
    return res;
}

static LINLINE int f_put_cmp_byte(uint8_t *cur_pos, uint8_t byte, int flags, uint8_t **new_pos) {
    int res = 0;
    if (flags & LIP_DNS_PUTRR_FLAGS_CMP_ONLY) {
        res = byte - *cur_pos;
    } else {
        *cur_pos = byte;
    }
    *new_pos = cur_pos + 1;
    return res;
}

static LINLINE int f_put_cmp_arr(uint8_t *cur_pos, const uint8_t *bytes, unsigned size, int flags, uint8_t **new_pos) {
    int res = 0;
    if (flags & LIP_DNS_PUTRR_FLAGS_CMP_ONLY) {
        res = memcmp(bytes, cur_pos, size);
    } else {
        memcpy(cur_pos, bytes, size);
    }
    if (new_pos!=NULL)
        *new_pos = cur_pos+size;
    return res;
}


static LINLINE int f_put_cmp_dns_name(const t_lip_dns_name *name, uint8_t *cur_pos,
                                      const t_lip_dns_hdr *hdr, const uint8_t *end_pos,
                                      int flags, uint8_t **new_pos) {
    int res = 0;
    if (flags & LIP_DNS_PUTRR_FLAGS_CMP_ONLY) {
        res = f_cmp_dns(name, cur_pos, hdr, end_pos);
        cur_pos = f_get_pos_after_name(cur_pos, end_pos);
    } else {
        cur_pos = f_dns_put_name(name, cur_pos, hdr, end_pos);
    }

    if (new_pos != NULL)
        *new_pos = cur_pos;
    return res;
}




static int f_put_cmp_rr_data(t_mdns_node *node, t_mdns_rr_info *rr, uint8_t *cur_pos,
                          const t_lip_dns_hdr *hdr, const uint8_t *end_pos, int flags,
                          uint8_t **new_pos) {
    int res = 0;

    switch (rr->rr.type) {
        case LIP_DNS_RRTYPE_HINFO: {
                t_lip_dns_rdata_hinfo* data = (t_lip_dns_rdata_hinfo*)rr->rdata;
                res = f_put_cmp_str(data->cpu, cur_pos, end_pos, flags, &cur_pos);
                if (res==0)
                    res = f_put_cmp_str(data->os, cur_pos, end_pos, flags, &cur_pos);
            }
            break;
        case LIP_DNS_RRTYPE_SRV: {
                t_lip_dns_rdata_srv* data = (t_lip_dns_rdata_srv*)rr->rdata;
                if ((cur_pos + 6) <= end_pos) {
                    res = f_put_cmp_byte(cur_pos, (data->priority >> 8) & 0xFF, flags, &cur_pos);
                    if (res==0)
                        res = f_put_cmp_byte(cur_pos, data->priority & 0xFF, flags, &cur_pos);
                    if (res==0)
                        res = f_put_cmp_byte(cur_pos, (data->weight >> 8) & 0xFF, flags, &cur_pos);
                    if (res==0)
                        res = f_put_cmp_byte(cur_pos, data->weight & 0xFF, flags, &cur_pos);
                    if (res==0)
                        res = f_put_cmp_byte(cur_pos, (data->port >> 8) & 0xFF, flags, &cur_pos);
                    if (res==0)
                        res = f_put_cmp_byte(cur_pos, data->port & 0xFF, flags, &cur_pos);



                    /* если установлен флаг LIP_DNS_PUTRR_FLAGS_UNSUPPORT_SRV_COMPR,
                       то name-compression не выполняется (для ответов на
                       запросы старого формата */
                    if (res == 0) {
                        res = f_put_cmp_dns_name(data->target, cur_pos,
                                                 (flags & LIP_DNS_PUTRR_FLAGS_UNSUPPORT_SRV_COMPR) &&
                                                 !(flags & LIP_DNS_PUTRR_FLAGS_CMP_ONLY) ?
                                                    NULL : hdr, end_pos, flags, &cur_pos);
                    }

                    if ((cur_pos != NULL) && (flags & LIP_DNS_PUTRR_FLAGS_MARK_ADDITIONAL)) {
                        /* помечаем в качестве additional все адресные записи
                          для узла, на который указывает SRV */
                        f_mark_addition_rr(data->target, LIP_DNS_RRTYPE_A, LIP_DNS_RRTYPE_AAAA);
                    }
                } else {
                    cur_pos = NULL;
                }
            }
            break;
        case LIP_DNS_RRTYPE_PTR:
        case LIP_DNS_RRTYPE_NS:
        case LIP_DNS_RRTYPE_CNAME:
            res = f_put_cmp_dns_name((t_lip_dns_name*)rr->rdata, cur_pos,
                                          hdr, end_pos, flags, &cur_pos);
            if ((cur_pos != NULL) && (flags & LIP_DNS_PUTRR_FLAGS_MARK_ADDITIONAL)) {
                if (rr->rr.type == LIP_DNS_RRTYPE_NS) {
                    f_mark_addition_rr((t_lip_dns_name*)rr->rdata, LIP_DNS_RRTYPE_A, LIP_DNS_RRTYPE_AAAA);
                } else if (rr->rr.type == LIP_DNS_RRTYPE_PTR) {
                    /* Для DNS-SD добавляем дополнительные записи TXT, SRV, A, AAAA
                       (rfc6763 пункт 12.1) */
                    if (rr->flags & MDNS_RR_FLAGS_DNS_SD) {
                        f_mark_addition_rr((t_lip_dns_name*)rr->rdata, LIP_DNS_RRTYPE_SRV, LIP_DNS_RRTYPE_TXT);
                    }
                }
            }
            break;
        case LIP_DNS_RRTYPE_TXT: {
                t_lip_dns_rdata_txt *data = (t_lip_dns_rdata_txt *)rr->rdata;
                uint8_t *start_pos = cur_pos;
                /* в данном формате значения сохраняются в виде строк
                   "key=value", где первая строка - ключ, второе значение */
                if (data->format == LIP_DNS_TXT_FORMAT_KEYS) {
                    for (unsigned i = 0; (i < data->str_cnt) && (cur_pos!=NULL) && (res==0); i+=2) {
                        int len1 = data->str[i] != NULL ? strlen(data->str[i]) : 0;
                        int len2 = data->str[i+1] != NULL ? strlen(data->str[i+1]) : 0;
                        int len = len1 + len2 + ((data->str[i+1] == NULL) ? 0 : 1);

                        if ((cur_pos + len) <= end_pos) {
                            res = f_put_cmp_byte(cur_pos, len, flags, &cur_pos);
                            if ((res == 0) && (len1 != 0))
                                res = f_put_cmp_arr(cur_pos, (uint8_t*)data->str[i], len1, flags, &cur_pos);
                            if ((res == 0) && (data->str[i+1] != NULL))
                                res = f_put_cmp_byte(cur_pos, '=', flags, &cur_pos);
                            if ((res == 0) && (len2 != 0))
                                res = f_put_cmp_arr(cur_pos, (uint8_t*)data->str[i+1], len2, flags, &cur_pos);
                        } else {
                            cur_pos = NULL;
                        }
                    }
                } else {
                    for (unsigned i = 0; (i < data->str_cnt) && (cur_pos!=NULL) && (res==0); i++) {
                        res = f_put_cmp_str(data->str[i], cur_pos, end_pos, flags, &cur_pos);
                    }
                }

                /* пустую текстовую запись посылать нельзя в соответствии
                  с rfc1035 => если нет записей, то добавляем строку
                  нулевой длины (из одного нуля) */
                if (start_pos == cur_pos) {
                    res = f_put_cmp_byte(cur_pos, 0, flags, &cur_pos);
                }
            }
            break;
        case LIP_DNS_RRTYPE_NSEC: {
                uint8_t bitmask[32];
                uint8_t cur_block=0;
                uint8_t next_block=0;
                int max_type;

                res = f_put_cmp_dns_name(&node->domain, cur_pos, hdr, end_pos,
                                         flags, &cur_pos);

                if ((res==0) && (cur_pos!=NULL)) {
                    /* цикл по блокам (каждый блок соответствует старшему
                       байту типа записи). Начинаем всегда с блока 0 (в подавляющем
                       большинстве случаев он и единственный), но проверяем
                       также сразу и есть ли старшие блоки и если есть -
                       запоминаем номер ближайшего для избежания лишних циклов */
                    do {
                        memset(bitmask, 0, sizeof(bitmask));
                        next_block = 0;
                        max_type = -1;

                        /* добавляем битовую маски по существующим записям,
                           соответствующим блокам */
                        for (unsigned rr_idx=node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); rr_idx++) {
                            /* псевдозаписи не включаются в NSEC, а для mDNS
                             * не включается также и сама NSEC */
                            if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx]) && !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) {
                                uint8_t block = f_rrs[rr_idx].rr.type >> 8;
                                if (block == cur_block) {
                                    uint8_t subtype = f_rrs[rr_idx].rr.type  & 0xFF;
                                    if ((int)subtype > max_type)
                                        max_type = subtype;
                                    bitmask[subtype >> 3] |= 1 << (7-(subtype & 0x7));
                                } else if ((block > cur_block) && ((next_block == 0) || (block < next_block))) {
                                    next_block = cur_block;
                                }
                            }
                        }

                        if (max_type >= 0) {
                            uint8_t len = (max_type >> 3) + 1;
                            if ((cur_pos + 2 + len) <= end_pos) {
                                res = f_put_cmp_byte(cur_pos, cur_block, flags, &cur_pos);
                                if (res == 0)
                                    res = f_put_cmp_byte(cur_pos, len, flags, &cur_pos);
                                if (res == 0)
                                    res = f_put_cmp_arr(cur_pos, bitmask, len, flags, &cur_pos);
                            } else {
                                cur_pos = NULL;
                            }
                        }
                    } while ((next_block != 0) && (cur_pos!=NULL) && (res==0));
                }
            }
            break;
        default:
            /* для всех остальных записей просто копируем напрямую
               данные из rdata в пакет */
              if ((cur_pos + rr->rr.rdlength) <= end_pos) {
                  res = f_put_cmp_arr(cur_pos, rr->rdata, rr->rr.rdlength, flags, &cur_pos);

                  if ((cur_pos != NULL) && (flags & LIP_DNS_PUTRR_FLAGS_MARK_ADDITIONAL)) {
                      /* для адресных записей, помечаем на добавление в additional
                        так же записи адреса другого типа */
                      if (rr->rr.type == LIP_DNS_RRTYPE_A) {
                          f_mark_addition_rr(&node->domain, LIP_DNS_RRTYPE_AAAA, 0);
                      } else if (rr->rr.type == LIP_DNS_RRTYPE_AAAA) {
                          f_mark_addition_rr(&node->domain, LIP_DNS_RRTYPE_A, 0);
                      }
                  }
            } else {
                cur_pos = NULL;
            }
            break;
    }


    if (new_pos != NULL)
        *new_pos = cur_pos;
    if (cur_pos==NULL)
        res = MDNS_RR_CMP_ERR_VAL;
    return res;
}



/***************************************************************************//**
  @internal
  Добавление записи (RR) в пакет в указанное место
  @param[in] node      Используемый узел mDNS
  @param[in] rr        Поля записи (без узла и переменной части)
  @param[in] rdata     Указатель на данные записи (часть переменной длины)
                       зависит от записи. обычно трактуется массив длиной rr->rdata
  @param[in] put_pos   Указатель на место, с которого должны передаваться данные
  @param[in] hdr       Указатель на заголовок пакета (для поиска в нем имен для
                       name compression)
  @param[in] end_pos   Указатель на байт за последним доступным байтом в буфере
                       для формирования пакета (для проверки выхода за диапазон)
  @param[in] flags     Дополнительные флаги  из #t_lip_dns_putrr_flags
  @return указатель на байт, идущий сразу за добавленной записью при успехе,
          0 при нехватке памяти
  *****************************************************************************/
static uint8_t *f_put_rr(t_mdns_node *node, t_mdns_rr_info *rr, uint8_t *put_pos,
                         const t_lip_dns_hdr *hdr, const uint8_t *end_pos, int flags) {

    put_pos = f_dns_put_name(&node->domain, put_pos, hdr, end_pos);
    /* проверяем, что имя передано успешно и осталось достаточно места
     * для фиксированной части данных */
    if ((put_pos != NULL) && ((put_pos + sizeof(t_lip_dns_rr)) <= end_pos)) {
        uint16_t rr_class = rr->rr.rclass;
        uint32_t ttl = rr->rr.ttl;
        uint8_t *rr_len_pos;
        if (flags & LIP_DNS_PUTRR_FLAGS_CACHE_FLUSH) {
            rr_class |= LIP_MDNS_RRCLASS_FLAG_FLUSH;
        }
        /* для legacy-ответов, TTL должен всегда не превышать 10с */
        if ((flags & LIP_DNS_PUTRR_FLAGS_MDNS_LEGACY)
                && (ttl > LIP_MDNS_LEGACY_RR_TTL_MAX)) {
            ttl =  LIP_MDNS_LEGACY_RR_TTL_MAX;
        }

        *put_pos++ = (rr->rr.type >> 8)&0xFF;
        *put_pos++ = rr->rr.type &0xFF;
        *put_pos++ = (rr_class >> 8)&0xFF;
        *put_pos++ = rr_class &0xFF;
        *put_pos++ = (ttl >> 24)&0xFF;
        *put_pos++ = (ttl >> 16)&0xFF;
        *put_pos++ = (ttl >> 8)&0xFF;
        *put_pos++ = ttl &0xFF;

        rr_len_pos = put_pos;
        put_pos += 2;

        f_put_cmp_rr_data(node, rr, put_pos, hdr, end_pos, flags, &put_pos);
        if (put_pos != NULL) {
            uint16_t rdlen = put_pos - rr_len_pos - 2;
            *rr_len_pos++ = (rdlen >> 8) & 0xFF;
            *rr_len_pos++ = rdlen & 0xFF;
        }
    } else {
        put_pos = NULL;
    }
    return put_pos;
}


/***************************************************************************//**
 @internal
 Функция сравнивает зарегистрированную запись хоста с записью из пакета данных.
 Подразумевается, что доменные имена уже сравнены
 @param[in] rr_state   Состояние зарегистрированной записи, с которой
                       сравнивается запись из пакета.
 @param[in] pkt_pos    Указатель на место в пакете, с которого начинается
                       запись в пакете (сразу после доменного имени записи)
 @param[in] hdr        Указатель на начало пакета (для разрешения упаковки имен)
 @param[in] end_pos    Указатель на байт за последним доступным байтом в буфере
                       для формирования пакета (для проверки выхода за диапазон)
 @param[in] flags      Флаги (резерв)
 @return               0, если записи равны
                       >0, если сохраненная запись больше ("lexicographically later"
                           в соответствии с пунктом 8.2, page 28)
                       <0, если сохраненная запись меньше
                       Если неверный формат у записи из пакета - сохраненная
                       считается больше
 ******************************************************************************/
static int f_cmp_rr(t_mdns_node *node, t_mdns_rr_info *rr_state, const uint8_t *pkt_pos, //t_lip_dns_rr* rr,
                    const t_lip_dns_hdr *hdr, const uint8_t *end_pos, int flags) {
    int res = MDNS_RR_CMP_ERR_VAL;
    /* проверяем, что есть достаточно места в пакете для постоянной части */
    if ((pkt_pos + LIP_DNS_RR_CONST_SIZE) <= end_pos) {
        /* получаем параметры записи */
        uint16_t rtype, rclass, rlen;
        rtype = *pkt_pos++;
        rtype <<= 8;
        rtype += *pkt_pos++;
        rclass = *pkt_pos++;
        rclass <<= 8;
        rclass += *pkt_pos++;
        /* пропускаем TTL - он не сравнивается */
        pkt_pos+=4;
        rlen = *pkt_pos++;
        rlen <<= 8;
        rlen += *pkt_pos++;

        if ((pkt_pos + rlen) <= end_pos) {
            /* старший бит поля класса используются для флага "cache flush"
               и не сравнивается при сравнении записей */
            rclass &= ~LIP_MDNS_RRCLASS_FLAG_FLUSH;
            /* вначале сравниваем класс записей */
            res = (int)rr_state->rr.rclass - (int)rclass;
            /* если одинаковы - сравниваем тип */
            if (res == 0) {
                res = (int)rr_state->rr.type - (int)rtype;
            }
            /* Если типы одинаковые - сравниваем данные.
               Представление данных в сохранной записи зависит от типа записи */
            if (res == 0)   {
                end_pos = pkt_pos + rlen;
                res = f_put_cmp_rr_data(node, rr_state, (uint8_t*)pkt_pos, hdr, end_pos,
                                        flags | LIP_DNS_PUTRR_FLAGS_CMP_ONLY,
                                        (uint8_t**)&pkt_pos);
                if (res==0) {
                    /* если записи равны, сравниваем длины */
                    res = pkt_pos - end_pos;
                }
            }
        } /* if ((pkt_pos + rlen) <= end_pos) */
    } /* if ((pkt_pos + LIP_DNS_RR_CONST_SIZE) <= end_pos) */
    return res;
}

/***************************************************************************//**
  @internal
  Устанавливает флаг, что нужно анонсировать записи
  *****************************************************************************/
static void f_start_announce(t_mdns_rr_info *rr, t_lclock_ticks time) {
    if (MDNS_RR_ESTABLISHED(rr) && !MDNS_RR_IS_PSEUDO(rr)) {
        rr->flags |= MDNS_RR_FLAGS_ANNOUNCE;
        rr->time_announce = rr->time_announce_init = time;

        /* если есть запланированная посылка ответа, то задерживаем текущую до
           заданного значения */
        if (f_state_flags & MDNS_STATE_FLAGS_ANNOUNCE) {
            t_lclock_ticks interval =  ltimer_expiration_from(&f_announce_tmr, time);
            if (interval > 0) {
                if (interval < LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_AGGREGATION_TIME)) {
                    rr->time_announce+=interval;
                } else {
                    /* иначе, если сообщение должно отправится раньше, то изменяем
                        соответсвующим образом интервал таймера, чтобы сработал
                        на данную запись */
                    if (interval < f_announce_tmr.interval) {
                        f_announce_tmr.interval-=interval;
                    } else {
                        f_announce_tmr.interval=0;
                    }
                }
            }
        } else {
            ltimer_set(&f_announce_tmr, 0);
            f_state_flags |= MDNS_STATE_FLAGS_ANNOUNCE;
        }
    }
}

static LINLINE int f_mdns_rr_mark_announce(t_mdns_rr_info *rr, t_lclock_ticks time, int is_probe) {
    int drop = 0;
    /* если запись уже помечена на передачу, то повторно что-то изменять для нее
       ну нужно. единственный вариант, это если до этого был планировали обычный ответ
       (который может быть задержан надолго), а сейчас нужно ответить на пробу */
    if (rr->flags & MDNS_RR_FLAGS_ANNOUNCE) {
        if (!is_probe || (rr->flags & MDNS_RR_FLAGS_ANNOUNCE_FOR_PROBE)) {
            drop = 1;
        }
    }

    if (!drop) {
        /* интервал между mcast-ответами по одной записи не должен быть меньше
           LIP_MDNS_MCAST_RESPONSE_INTERVAL. При ответе на пробу он уменьшается
           до LIP_MDNS_MCAST_RESPONSE_PORBE_INTERVAL (пункт 6.0 p16). */
        t_lclock_ticks prot_interval = is_probe ? LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_MCAST_RESPONSE_PORBE_INTERVAL)
                                         : LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_MCAST_RESPONSE_INTERVAL);
        if (rr->flags & MDNS_RR_FLAGS_MCAST_TIME_VALID) {
            t_lclock_ticks interval = (time - rr->mcast_snd_time);
            if (!LCLOCK_IS_NEG(interval) && (interval < prot_interval))
                time+=(prot_interval - interval);
        }

        rr->flags |= MDNS_RR_FLAGS_ANNOUNCE;
        if (is_probe)
            rr->flags |= MDNS_RR_FLAGS_ANNOUNCE_FOR_PROBE;

        rr->time_announce = rr->time_announce_init = time;
        f_state_flags |= MDNS_STATE_FLAGS_ANNOUNCE;
    }

    return drop;
}



/***************************************************************************//**
  @internal
  Проверка, есть ли еще записи, которые нужно передать через mcast.
  Если есть, то устанавливает таймер f_announce_tmr на ближайший интервал.
  Если нет, то снимает флаг общий флаг #MDNS_STATE_FLAGS_ANNOUNCE, указывающий
   что нужны посылки по mcast
  @return 0 - если нет записей в процессе анонсирования, 1 - есть
  *****************************************************************************/
static int f_check_announce(void) {
    t_lclock_ticks cur_time = lclock_get_ticks();
    t_lclock_ticks min_interval = (t_lclock_ticks)(-1);
    t_lclock_ticks max_interval = (t_lclock_ticks)(-1);
    t_lclock_ticks max_planned_interval;
    int need_announce = 0;
    int need_goodbye = 0;


    for (unsigned node_idx = 0; node_idx < f_nodes_cnt; node_idx++) {
        t_mdns_node *node = &f_nodes[node_idx];
        for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); rr_idx++) {
            if ((f_rrs[rr_idx].rr.ttl == 0) && !(f_rrs[rr_idx].flags & MDNS_RR_FLAGS_REMOVE) &&
                    !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) {
                need_goodbye = 1;
            } else if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx]) && (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_ANNOUNCE)) {
                t_lclock_ticks cur_interval = (f_rrs[rr_idx].time_announce - cur_time);
                t_lclock_ticks cur_interval_max = (f_rrs[rr_idx].time_announce_init - cur_time +
                                            (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_ANNOUNCE_FOR_PROBE) ?
                                                LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_AGGREGATION_PROBE_TIME) :
                                                LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_AGGREGATION_TIME));
                if (LCLOCK_IS_NEG(cur_interval))
                    cur_interval = 0;
                if (LCLOCK_IS_NEG(cur_interval_max))
                    cur_interval_max = 0;

                if (!need_announce || (cur_interval < min_interval)) {
                    min_interval = cur_interval;
                }
                if (!need_announce || (cur_interval_max < max_interval))
                    max_interval = cur_interval_max;

                need_announce = 1;
            }
        }
    }

    max_planned_interval = min_interval;
    if (min_interval != max_interval) {
        for (unsigned node_idx = 0; node_idx < f_nodes_cnt; node_idx++) {
            t_mdns_node *node = &f_nodes[node_idx];

            for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); rr_idx++) {
                if (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_ANNOUNCE) {
                    t_lclock_ticks cur_interval = (f_rrs[rr_idx].time_announce - cur_time);
                    if (LCLOCK_IS_NEG(cur_interval))
                        cur_interval = 0;
                    if ((cur_interval > max_planned_interval) && (cur_interval <= max_interval)) {
                        max_planned_interval = cur_interval;
                    }
                }
            }
        }
    }

    if (max_planned_interval != min_interval) {
        for (unsigned node_idx = 0; node_idx < f_nodes_cnt; node_idx++) {
            t_mdns_node *node = &f_nodes[node_idx];

            for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); rr_idx++) {
                if (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_ANNOUNCE) {
                    t_lclock_ticks cur_interval = (f_rrs[rr_idx].time_announce - cur_time);
                    if (LCLOCK_IS_NEG(cur_interval))
                        cur_interval = 0;
                    if (cur_interval <= max_planned_interval)
                        f_rrs[rr_idx].time_announce = cur_time + max_planned_interval;
                }
            }
        }
    }

    if (need_announce) {
        f_state_flags |= MDNS_STATE_FLAGS_ANNOUNCE;
        ltimer_set_at(&f_announce_tmr, max_planned_interval, cur_time);
        lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                   "lip mdns: announce planned %d\n", max_planned_interval);
    } else {
        f_state_flags &= ~MDNS_STATE_FLAGS_ANNOUNCE;
        lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_DEBUG_MEDIUM, 0, "lip mdns: no more announce!\n");
    }

    if (need_goodbye) {
        f_state_flags |= MDNS_STATE_FLAGS_SEND_GOODBYE;
    } else {
        f_state_flags &= ~MDNS_STATE_FLAGS_SEND_GOODBYE;
    }

    return need_announce;
}



static t_lip_dns_hdr *f_send_prepare_packet(const t_lip_udp_info *tx_info,
                                            t_send_resp_type type,
                                            const t_mdns_lagacy_info *legacy_info,
                                            uint8_t **pcur_pos, uint8_t **pend_pos) {

    int max_size;
    t_lip_dns_hdr *hdr = (t_lip_dns_hdr*)lip_udp_prepare(tx_info, &max_size);
    if (hdr!=NULL) {
        if (max_size <= (int)sizeof(t_lip_dns_hdr)) {
            hdr = NULL;
        } else {
            uint8_t *cur_pos = &hdr->data[0];
            uint8_t *end_pos = (uint8_t*)hdr + max_size;

            memset(hdr, 0, sizeof(t_lip_dns_hdr));
            /* устанавливаем признак ответа + в mDNS все ответы авторитативные */
            hdr->flags = LIP_DNS_FLAG_AA | LIP_DNS_FLAG_QR;
            /* для legacy ответов нужно установить id, таким же как и у запроса
                и копируем секцию ответов */
            if (type == MDNS_SEND_RESP_LEGACY) {
                int qsize = legacy_info->qend - &legacy_info->hdr->data[0];
                /* id в lagacy-info уже в в network byte order (чтобы 2 раза не
                    переставлять байты) */
                hdr->id = legacy_info->id;
                hdr->qdcount = HTON16(legacy_info->qcnt);
                if ((end_pos - cur_pos) > qsize) {
                    /* секцию ответов копируем как есть */
                    memcpy(cur_pos, &legacy_info->hdr->data[0],  qsize);
                    cur_pos += qsize;
                } else {
                    hdr = NULL;
                }
            }

            if (hdr!=NULL) {
                *pcur_pos = cur_pos;
                *pend_pos = end_pos;
            }
        }
    }
    return hdr;
}

static int f_send_packet(t_lip_dns_hdr *hdr, const uint8_t *cur_pos, t_send_resp_type type) {
    int out = 0;
    /* если хоть одну запись положили в пакет, то передаем данные */
    if ((cur_pos - &hdr->data[0]) > 0)  {
        if (type != MDNS_SEND_RESP_LEGACY) {
            /* Для legacy запросов флаг TC должен означать что не все данные
               поместились. При этом больше одного пакета в ответ посылать
               не нужно */
            hdr->flags |= LIP_DNS_FLAG_TC;
            out = 1;
        }
        hdr->flags = HTON16(hdr->flags);
        hdr->ancount = HTON16(hdr->ancount);
        lip_udp_flush(cur_pos - (uint8_t*)hdr);
    } else {
        out = 1;
    }
    return out;
}



static int f_process_snd_rr(t_mdns_node *node, unsigned rr_idx,
                            const t_lip_udp_info *tx_info,
                            t_lip_dns_hdr **phdr, uint8_t **pcur_pos, uint8_t **pend_pos,
                            t_lclock_ticks cur_time, uint16_t test_flag, t_send_resp_type type,
                            const t_mdns_lagacy_info *legacy_info) {
    int out = 0;
    t_lip_dns_hdr *hdr = *phdr;
    uint8_t *cur_pos = *pcur_pos;
    uint8_t *end_pos = *pend_pos;

    while (!out && !(f_rrs[rr_idx].flags & MDNS_RR_FLAGS_PUT_RESP)) {
        /* подготавливаем новый пакет, если не подготовлен */
        if (hdr==NULL) {
            hdr = f_send_prepare_packet(tx_info, type, legacy_info, &cur_pos, &end_pos);
        }

        if (hdr==NULL) {
            out = 1;
        } else {
            uint8_t* next_pos;
            int put_flags = (type == MDNS_SEND_RESP_LEGACY) ?
                        LIP_DNS_PUTRR_FLAGS_UNSUPPORT_SRV_COMPR | LIP_DNS_PUTRR_FLAGS_MDNS_LEGACY:
                        (node->flags & MDNS_NODE_FLAGS_UNIQUE) ? LIP_DNS_PUTRR_FLAGS_CACHE_FLUSH : 0;

            /* добавляем запись в пакет, для legacy-ответов нет поддержки
               cache-flush флага и сжатия имен в SRV-записях */
            next_pos = f_put_rr(node, &f_rrs[rr_idx],
                                cur_pos, hdr, end_pos,
                                put_flags | LIP_DNS_PUTRR_FLAGS_MARK_ADDITIONAL);

            if (next_pos != NULL) {
                f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_PUT_RESP;
                /* если добавили успешно - обновляем поля */
                cur_pos = next_pos;
                hdr->ancount++;
                /* если это не последний анонс - устанавливаем таймер
                  для следующего */
                if ((type == MDNS_SEND_RESP_MCAST) && MDNS_RR_ANNOUNCE_IN_PROGRESS(&f_rrs[rr_idx])) {
                    /* если не было сделано нужное кол-во анонсов, то планируем
                       еще один */
                    f_rrs[rr_idx].announce_cnt++;
                    f_rrs[rr_idx].time_announce_init = f_rrs[rr_idx].time_announce =
                            cur_time + LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_ANNOUNCE_INTERVAL);
                } else {
                    f_rrs[rr_idx].flags &= ~test_flag;
                }


                if (type == MDNS_SEND_RESP_MCAST) {
                    if (f_rrs[rr_idx].rr.ttl == 0) {
                        f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_REMOVE;
                        f_state_flags |= MDNS_STATE_FLAGS_REMOVE_RRS;
                    } else {
                        /* запоминаем время последней отсылки записи по mcast */
                        f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_MCAST_TIME_VALID;
                        f_rrs[rr_idx].mcast_snd_time = cur_time;
                    }
                }
            } else {
                out = f_send_packet(hdr, cur_pos, type);
                hdr = NULL;
            }
        }
    }

    *phdr = hdr;
    *pcur_pos = cur_pos;
    *pend_pos = end_pos;

    return out;
}

/***************************************************************************//**
  @internal
  Передача ответа ответов (на запрос или анонсов) для помеченных для этого записей
  @param[in] type  Тип ответа:
                     - #MDNS_SEND_RESP_MCAST - передача mcast-ответа или анонса -
                       пересылаем записи, помеченные флагом #MDNS_RR_FLAGS_ANNOUNCE
                       для которых пришло запланированное время. В случае анонса
                       планирование следующего при необходимости
                     - #MDNS_SEND_RESP_UNICAST - передача unicast ответа на адрес
                       #f_unicast_addr состоящего из записей, помеченных флагом
                       #MDNS_RR_FLAGS_UNICAST_RESP
                     - #MDNS_SEND_RESP_LEGACY - передача legacy ответа на указанный
                       адрес и порт из записей, помеченных на передачу в текущем f_process_packet()
                       На legacy-запрос отвечаем как на обычный DNS - сразу, unicast
                       на исходный адрес и порт + повторяя question-секцию + id (в отличии
                       от всех остальных ответов) + нельзя name-compression на SRV
                       + ttl не больше 10 сек.
  @param[in] legacy_info  Указатель на структуру с параметрами запроса для
                       legacy-ответа. Имеет значение только для типа
                       #MDNS_SEND_RESP_LEGACY

  *****************************************************************************/
static void f_send_response(t_send_resp_type type,
                            const t_mdns_lagacy_info *legacy_info) {
    t_lip_udp_info tx_info;
    t_lip_dns_hdr *hdr = NULL;
    int out = 0;
    int test_flag;
    t_lclock_ticks cur_time = lclock_get_ticks();
    uint8_t *cur_pos = NULL, *end_pos = NULL;

    /* заполняем udp информацию для отсылаемого пакета */
    f_fill_mcast_ipv4_udp(&tx_info);

    /* часть параметров зависит от типа ответа */
    if (type == MDNS_SEND_RESP_MCAST) {
        test_flag = MDNS_RR_FLAGS_ANNOUNCE | MDNS_RR_FLAGS_ANNOUNCE_FOR_PROBE;
    } else if (type == MDNS_SEND_RESP_UNICAST) {
        test_flag = MDNS_RR_FLAGS_UNICAST_RESP;
        tx_info.dst_addr = f_unicast_addr;
    } else {
        test_flag = MDNS_RR_FLAGS_MULTICAST_REQ | MDNS_RR_FLAGS_UNICAST_REQ;
        tx_info.dst_port = legacy_info->port;
        tx_info.dst_addr = legacy_info->addr;
        tx_info.ip_type = legacy_info->ip_type;
    }



    /* проходимся по всем узлам, в поисках записей, которые нужно анонсировать */
    for (unsigned node_idx=0; (node_idx < f_nodes_cnt) && !out; node_idx++) {
        t_mdns_node *node = &f_nodes[node_idx];
        int put_rr = 0;

        for (unsigned rr_idx=node->rr_idx; (rr_idx < (node->rr_idx + node->rr_cnt)) && !out; rr_idx++) {
            /* проверяем, что нужная запись. Для multicast-ответов помимо
              флага должен истечь таймер на отправку записи. Кроме того для
              multicast тут проверяем посылку goodbye-пакетов (с ttl = 0) */
            if (!(f_rrs[rr_idx].flags & MDNS_RR_FLAGS_PUT_RESP) &&
                    (((type == MDNS_SEND_RESP_MCAST) && (f_rrs[rr_idx].rr.ttl==0) && !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) ||
                    ((f_rrs[rr_idx].flags & test_flag) && ((type!=MDNS_SEND_RESP_MCAST) ||
                           (!LCLOCK_IS_NEG(cur_time - f_rrs[rr_idx].time_announce)))))) {

                out = f_process_snd_rr(node, rr_idx, &tx_info, &hdr, &cur_pos,
                                       &end_pos, cur_time, test_flag, type, legacy_info);

                if (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_PUT_RESP) {
                    put_rr = 1;

                    /* если уникальная запись, то должны посылать все
                       записи из набора, если их несколько */
                    if (!MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx]) && (node->flags & MDNS_NODE_FLAGS_UNIQUE)) {
                        for (unsigned rr_idx2=node->rr_idx; (rr_idx2 < (node->rr_idx + node->rr_cnt)); rr_idx2++) {
                            if (!(f_rrs[rr_idx2].flags & MDNS_RR_FLAGS_PUT_RESP) &&
                                    (f_rrs[rr_idx].rr.type == f_rrs[rr_idx2].rr.type)) {
                                if (!out) {
                                    out = f_process_snd_rr(node, rr_idx2, &tx_info, &hdr, &cur_pos,
                                                           &end_pos, cur_time, test_flag, type, legacy_info);
                                }


                                if ((type == MDNS_SEND_RESP_MCAST) && !(f_rrs[rr_idx2].flags & MDNS_RR_FLAGS_PUT_RESP)) {
                                    /* если не удалось за раз послать все, то
                                       планируем послать как можно скорее остальные
                                       записи из RRSet */
                                    f_rrs[rr_idx2].flags |= MDNS_RR_FLAGS_ANNOUNCE;
                                    f_rrs[rr_idx2].time_announce = cur_time;
                                    /* ставим время начала так, чтобы нельзя было
                                       откладывать посылку этого сообщения на
                                       другое время, т.к. RRSet должен быть передан
                                       как можно скорее */
                                    f_rrs[rr_idx2].time_announce_init = cur_time -
                                            LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_AGGREGATION_TIME);
                                }
                            }
                        }
                    }
                }
            }
        }

        /* если была передана хотя бы одна запись для уникального узла,
         * то помечаем, что стоит положить NSEC в Additional (p17) */
        if (put_rr && MDNS_NODE_FLAGS_NSEC_GEN(node->flags)) {
            f_mark_addition_rr(&node->domain, LIP_DNS_RRTYPE_NSEC, 0);
        }
    } /* for (node...) */


    if (!out && (hdr != NULL) && (cur_pos != &hdr->data[0])) {
        /* проверяем, есть ли записи в секцию Additional */
        for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
            t_mdns_node *node = &f_nodes[node_idx];

            for (int rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                if (!(f_rrs[rr_idx].flags & MDNS_RR_FLAGS_PUT_RESP) &&
                        (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_ADDITION_NEED)) {
                    int put_flags = (type == MDNS_SEND_RESP_LEGACY) ?
                                LIP_DNS_PUTRR_FLAGS_UNSUPPORT_SRV_COMPR | LIP_DNS_PUTRR_FLAGS_MDNS_LEGACY:
                                (node->flags & MDNS_NODE_FLAGS_UNIQUE) ? LIP_DNS_PUTRR_FLAGS_CACHE_FLUSH : 0;
                    /* добавляем в addition секцию те что помечены флагом, но
                      не были добавлены в пакет */
                    uint8_t *next_pos = f_put_rr(node, &f_rrs[rr_idx], cur_pos,
                                                 hdr, end_pos, put_flags);
                    if (next_pos != NULL) {
                        cur_pos = next_pos;
                        ++hdr->arcount;
                    }
                }
            }
        }

        lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                   "lip mdns: send response - acnt = %d, arcnt = %d\n", hdr->ancount, hdr->arcount);
        hdr->flags = HTON16(hdr->flags);
        hdr->ancount = HTON16(hdr->ancount);
        hdr->arcount = HTON16(hdr->arcount);        
        lip_udp_flush(cur_pos - (uint8_t*)hdr);
    }


    if (f_state_flags & MDNS_STATE_FLAGS_REMOVE_RRS)
        f_remove_rrs();


    if (type == MDNS_SEND_RESP_MCAST) {        
        /* проверяем на следующую mcast-передачу */
        f_check_announce();
    } else if (type == MDNS_SEND_RESP_UNICAST) {
        /* снимаем флаг, что нужно послать unicast-ответ */
        f_state_flags &= ~MDNS_STATE_FLAGS_UNICAST_RESPONSE;
    }

    /* убираем все временные флаги */
    for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
        t_mdns_node *node = &f_nodes[node_idx];
        for (int rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
            f_rrs[rr_idx].flags &= ~(MDNS_RR_FLAGS_PUT_RESP | MDNS_RR_FLAGS_ADDITION_NEED);
        }
    }
}

/***************************************************************************//**
  @internal
  Если процедура проверки не запущена, то выбирается случайное время для
  первой проверки из диапазона [0, #LIP_MDNS_PROBE_WAIT]
  *****************************************************************************/
static LINLINE void f_start_probe(void) {
    if (!(f_state_flags & MDNS_STATE_FLAGS_PROBE)
            && (f_state_flags & (MDNS_STATE_FLAGS_IPV4_SET | MDNS_STATE_FLAGS_IPV6_SET))) {

        ltimer_set(&f_probe_tmr, lip_rand_range(LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_PROBE_WAIT)));
        f_state_flags |= MDNS_STATE_FLAGS_PROBE;
    }
}
/***************************************************************************//**
  @internal
  Проверка, есть ли еще записи с флагом #MDNS_NODE_FLAGS_PROBE, для которых
  нужно послать еще один проверочный пакет.
  Снимает флаг #MDNS_NODE_FLAGS_PROBE для записей, для которые прошли проверку
  @return 0 - если посылать проверочный пакет (probe) не нужно, 1 - нужно
  *****************************************************************************/
static int f_check_probe(void) {
    int need_probe = 0;
    int need_probe_deffered = 0;
    t_lclock_ticks cur_time = lclock_get_ticks();
    for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
        t_mdns_node *node = &f_nodes[node_idx];
        if (node->flags & MDNS_NODE_FLAGS_PROBE) {
            /* если нужное кол-во проверок провели без конфликтов =>
               переходим к анонсу записей */
            if (node->probes_cnt == LIP_MDNS_PROBES_CNT) {
                node->flags &= ~MDNS_NODE_FLAGS_PROBE;
                /* для записей, которые прошли проверку, запускаем анонсировние */
                for (int rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                    f_rrs[rr_idx].announce_cnt = 0;
                    f_start_announce(&f_rrs[rr_idx], cur_time);
                }
            } else {
                /* иначе - нам еще нужно послать проверки */
                need_probe = 1;
            }
        } else if (node->flags & MDNS_NODE_FLAGS_PROBE_DEFFERED) {
            need_probe_deffered = 1;
        }
    }

    /* если прошли обычные проверки, но остались отложенные, то планируем их сейчас */
    if (!need_probe && need_probe_deffered) {
        for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
            t_mdns_node *node = &f_nodes[node_idx];

            if (node->flags & MDNS_NODE_FLAGS_PROBE_DEFFERED) {
                node->flags &= ~MDNS_NODE_FLAGS_PROBE_DEFFERED;
                node->flags |= MDNS_NODE_FLAGS_PROBE;
                node->probes_cnt = 0;
            }
        }

        ltimer_set(&f_probe_tmr, LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_PROBE_DEFFERED_TIME));
    }

    if (!need_probe && !need_probe_deffered) {
        /* если все имена узлов проверены - завершаем проверку */
        f_state_flags &= ~MDNS_STATE_FLAGS_PROBE;
    }

    return need_probe;
}




/***************************************************************************//**
  @internal
  Посылаем проверочный запрос используя имена уникальных узлов, для которых еще
  не было послано нужное кол-во проверок
  *****************************************************************************/
static void f_send_probe(void) {
    if (f_check_probe()) {
        int max_size;
        t_lip_udp_info tx_info;
        t_lip_dns_hdr *hdr;
        f_fill_mcast_ipv4_udp(&tx_info);
        
        /* подготавливаем udp-пакет */
        hdr = (t_lip_dns_hdr*)lip_udp_prepare(&tx_info, &max_size);
        if (hdr && (max_size > (int)sizeof(t_lip_dns_hdr))) {            
            uint8_t *cur_pos, *end_pos;

            cur_pos = &hdr->data[0];
            end_pos = (uint8_t*)hdr + max_size;
            memset(hdr, 0, sizeof(t_lip_dns_hdr));

            
            for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
                t_mdns_node *node = &f_nodes[node_idx];

                node->flags &= ~MDNS_NODE_FLAGS_QUESTION_PUT;

                if (node->flags & MDNS_NODE_FLAGS_PROBE) {
                    cur_pos = f_put_question(&node->domain, LIP_DNS_RRTYPE_ANY,
                                   LIP_DNS_CLASS_IN | LIP_MDNS_QCLASS_FLAG_QU,
                                   cur_pos, hdr, end_pos);
                    if (cur_pos != NULL) {
                        hdr->qdcount++;
                        node->flags |= MDNS_NODE_FLAGS_QUESTION_PUT;
                    }
                }
            }

            if (cur_pos != NULL) {
                for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
                    t_mdns_node *node = &f_nodes[node_idx];

                    /* в секции authority должны быть всегда все проверяемые записи */
                    if (node->flags & MDNS_NODE_FLAGS_QUESTION_PUT) {
                        for (int rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                            if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx]) &&
                                    !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) {
                                cur_pos = f_put_rr(node, &f_rrs[rr_idx],
                                                   cur_pos, hdr, end_pos, 0);
                                hdr->nscount++;
                            }
                        }
                    }
                }
            }

            /** @todo для случая, когда не удалось поместить все запросы
                в один пакет рассмотреть вариант передачи в несколько */
            if (cur_pos != NULL) {
                hdr->qdcount = HTON16(hdr->qdcount);
                hdr->nscount = HTON16(hdr->nscount);
                if (lip_udp_flush(cur_pos - (uint8_t*)hdr) == LIP_ERR_SUCCESS) {
                    /* при удачной передаче увеличиваем кол-во выполненных проверок */
                    for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
                        t_mdns_node *node = &f_nodes[node_idx];
                        if (node->flags & MDNS_NODE_FLAGS_QUESTION_PUT) {
                            ++node->probes_cnt;
                            node->flags &= ~MDNS_NODE_FLAGS_QUESTION_PUT;
                        }
                    }

                    /* устанавливаем таймаут для следующей передачи */
                    ltimer_set(&f_probe_tmr, LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_PROBE_INTERVAL));
                }
            }
        }
    }
}


/***************************************************************************//**
 @internal
 Заполняет поле #f_rev_addr_str, срокой, соответствующей части доменного имени
 для обратного отображения адреса (указанного в @a ip_addr) на доменное имя
 @param[in] ip_addr - адрес, который следует отобразить в #f_rev_addr_str
 ******************************************************************************/
static void f_fill_rev_ipv4_str(const uint8_t *ip_addr) {
    int8_t i;
    char *put_pos = f_rev_addr_str;
    if (ip_addr != NULL) {
        for (i = LIP_IPV4_ADDR_SIZE - 1; i >= 0; --i) {
            *put_pos++ = '0' + ip_addr[i] / 100;
            *put_pos++ = '0' + (ip_addr[i] % 100) / 10;
            *put_pos++ = '0' + (ip_addr[i] % 10);
            if (i != 0)
                *put_pos++ = '.';
        }
        *put_pos++ = 0;
    }
}


static void f_prepare_unicast_resp(const t_lip_udp_info *info, t_lclock_ticks tout) {
    if (info->ip_type == LIP_IPTYPE_IPv4) {
        /* если уже стоит запланированный unicast-ответ
          - шлем его, т.к. мы запоминаем только один */
        if (f_state_flags & MDNS_STATE_FLAGS_UNICAST_RESPONSE) {
            f_send_response(MDNS_SEND_RESP_UNICAST, NULL);
        }
        /* планируем новый unicast-ответ */
        f_state_flags |= MDNS_STATE_FLAGS_UNICAST_RESPONSE;
        ltimer_set(&f_unicast_tmr, tout);
        lip_ipv4_addr_cpy(f_unicast_addr, info->src_addr);
    }
}

/***************************************************************************//**
 @internal
 Удаляем предыдущее имя хоста
 ******************************************************************************/
static void f_reset_hostname(void) {
    /* удаляем все записи, связанные с прошлым именем */
    lip_mdns_rr_remove(&f_nodes[0].domain, LIP_DNS_RRTYPE_ANY, NULL, LIP_MDNS_REMFLAGS_FLUSH);
    lip_mdns_pull();
    /* обнуляем флаги состояния (мы перешли в исходное состояние) */
    f_state_flags = 0;
    /* сбрасываем имя */
    f_nodes[0].domain.name = 0;
    f_nodes[0].flags = MDNS_NODE_FLAGS_UNIQUE;

    /** @todo нужно ли сбрасывать все флаги по всем узлам? */
}

/**************************************************************************//**
 @internal
 Запуск проверки имени при возникновении конфликта.
 Рассчитывает время для запуска новой пробы, отслеживает кол-во конфликтов
 и сбрасывает состояние узла
 @param[in] node   Узел, для которого произошел конфликт имен
 ******************************************************************************/
static void f_restart_conflict_probe(t_mdns_node* node) {
    int another_probe = 0;

    f_conflict_time = lclock_get_ticks();
    f_conflict_cnt++;

    /* проверяем, есть ли другой узел для которого выполняется проверка.
       если нет, то выполняем обычный алгоритм, иначе проверку конфликтной
       записи будем делать уже после завершения остальных */
    for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
        t_mdns_node *cur_node = &f_nodes[node_idx];
        if ((cur_node != node) && (cur_node->flags & MDNS_NODE_FLAGS_PROBE)) {
            another_probe = 1;
            break;
        }
    }

    if (!another_probe) {
        /* если не превысили максимально возможное кол-во
          конфликтов - просто посылаем повторную проверку */
        if (f_conflict_cnt < LIP_MDNS_MAX_CONFLICT_CNT) {
            ++f_conflict_cnt;
            ltimer_set(&f_probe_tmr,
                lip_rand_range(LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_PROBE_WAIT)));
        } else {
            /* при превышении ограничиваем время посылки проверок */
            ltimer_set_ms(&f_probe_tmr, LIP_MDNS_RATE_LIMIT_INTERVAL);
        }

        f_state_flags |= MDNS_STATE_FLAGS_PROBE;
        node->flags |= MDNS_NODE_FLAGS_PROBE;
    } else {
        if (f_conflict_cnt < LIP_MDNS_MAX_CONFLICT_CNT) {
            ++f_conflict_cnt;
            node->flags |= MDNS_NODE_FLAGS_PROBE;
        } else {
            node->flags |= MDNS_NODE_FLAGS_PROBE_DEFFERED;
            node->flags &= ~MDNS_NODE_FLAGS_PROBE;
        }
    }

    node->probes_cnt = 0;

}

/***************************************************************************//**
 @internal
 Функция помечает все записи, принадлежащие данному узлу и удовлетворяющие
 заданному типу и классу с помощью флагов #_MDNS_RR_FLAGS_RESP,
 #MDNS_RR_FLAGS_UNICAST_REQ и #MDNS_RR_FLAGS_MULTICAST_REQ и возвращает кол-во
 найденных записей. Так же помечает сам узел флагом #_MDNS_NODE_FLAGS_QUESTIONED
 @param[in] node     Узел, для которого ищутся записи
 @param[in] qclass   Класс записи (с возможным флагом #LIP_MDNS_QCLASS_FLAG_QU)
 @param[in] qtype    Тип записи для поиска
 @param[out] first_ind Указатель на переменную, в которую будет сохранен индекс
                       первой найденной записи (если записи не найдены - остается
                       без изменений) или 0
 @return             Количество найденных записей
 ******************************************************************************/
static LINLINE int f_find_answers(t_mdns_node *node, uint16_t qclass,
                                  uint16_t qtype, int *first_ind) {
    int fnd_rr = 0;

    node->flags |= MDNS_NODE_FLAGS_QUESTIONED;

    /* ищем удовлетворяющие запрос записи */
    for (unsigned rr_idx=node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); rr_idx++) {
        if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx])) {
            /* смотрим - подходит ли данная запись по типу
              и классу. псевдозаписи не входят в ANY, но добавляем, если ищем явно */
            if (((f_rrs[rr_idx].rr.type == qtype) ||
                    ((qtype == LIP_DNS_RRTYPE_ANY) && !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx]))) &&
                    ((f_rrs[rr_idx].rr.rclass == (qclass & ~LIP_MDNS_QCLASS_FLAG_QU)) ||
                     ((qclass & ~LIP_MDNS_QCLASS_FLAG_QU) == LIP_DNS_CLASS_ANY))
                    ) {

                if ((fnd_rr == 0) && (first_ind != NULL))
                    *first_ind = (int)rr_idx;

                ++fnd_rr;
                if (qclass & LIP_MDNS_QCLASS_FLAG_QU) {
                    f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_UNICAST_REQ;
                } else {
                    f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_MULTICAST_REQ;
                }
            }
        }
    }
    return fnd_rr;
}

/***************************************************************************//**
 @internal
 Функция ищет запись типа #LIP_DNS_RRTYPE_CNAME в заданном узле. Если не находит
 то устанавливает флаг для указания, что надо послать NSEC, если необходимо.
 Если находит, то ищет записи с заданным типом и классом в узле, на который
 указывает запись CNAME. Если там найденных записей нет, то возвращает указатель
 на данный тип узла для повторного поиска.
 Используется для разрешения цепочек CNAME
 @param[in] node    Узел, для которого ищется запись CNAME
 @param[in] qclass  Запрашиваемый класс записи для узла, на который указывает CNAME
 @param[in] qtype   Запрашиваемый тип записи для узла, на который указывает CNAME
 @param[in] resp_type Тип ответа (unicast, multicast, legacy) - для пометки NSEC
 @param[in,out] answ_cnt Содержимое переменной увеличивается на количество найденных
                      ответов
 @return            Указатель на узел, в котором следует продолжить поиски, или 0
 ********************************************************************************/
static t_mdns_node *f_check_cname(t_mdns_node *node, uint16_t qclass, uint16_t qtype,
                                  t_send_resp_type resp_type, uint16_t *answ_cnt) {
    t_mdns_node *next_node = NULL;
    int cname_ind = 0;
    /* в начале проверяем, есть ли запись с типом CNAME */
    int fnd_rr = f_find_answers(node, qclass, LIP_DNS_RRTYPE_CNAME, &cname_ind);
    /* если нашли, ищем ответы среди узла, на который указывает CNAME */
    if (fnd_rr != 0) {
        int fnd = 0;
        /* Так как по правилам у узла может быть только запись CNAME (одна) и
           вообще не должно быть других записей (rfc1034, p15) то вполне корректно
           искать только первую запись с типом cname */
        const t_lip_dns_name *cname = (const t_lip_dns_name *)f_rrs[cname_ind].rdata;
        *answ_cnt = *answ_cnt + fnd_rr;

        for (unsigned node_idx = 0; (node_idx < f_nodes_cnt) && !fnd; ++node_idx) {
            node = &f_nodes[node_idx];

            if (f_nodes_is_equ(&node->domain, cname)) {
                fnd = 1;
                fnd_rr = f_find_answers(node, qclass, qtype, NULL);
                if (fnd_rr != 0) {
                    *answ_cnt = *answ_cnt + fnd_rr;
                } else {
                    /* если не нашли ответы, то должны заново искать CNAME
                      возвращаем указатель на узел, для которого потом снова
                      вызываем f_check_cname */
                    next_node = node;
                }
            }
        }
    } else {
        /* если не нашли уникальную запись - то нужно будет ответить
          с помощью NSEC */
        if (MDNS_NODE_FLAGS_NSEC_GEN(node->flags)) {
            fnd_rr = f_find_answers(node, LIP_DNS_CLASS_ANY, LIP_DNS_RRTYPE_NSEC, NULL);
            *answ_cnt = *answ_cnt + fnd_rr;
        }
    }
    return next_node;
}

/** @internal Заполнение параметров запроса из места в пакете, начинающегося с имени узла запроса.
 *  */
static const uint8_t *f_fill_pkt_query(const uint8_t *cur_pos, const uint8_t *end_pos, t_lip_dns_query *query) {
    cur_pos = f_get_pos_after_name(cur_pos, end_pos - LIP_DNS_QUERY_CONST_SIZE);
    /* если в пакете есть место - получаем тип и класс запроса */
    if (cur_pos != NULL) {
        if (query != NULL) {
            query->qtype  = ((uint16_t)cur_pos[0] << 8) | cur_pos[1];
            query->qclass = ((uint16_t)cur_pos[2] << 8) | cur_pos[3];
        }
        cur_pos += LIP_DNS_QUERY_CONST_SIZE;
    }
    return cur_pos;
}

static const uint8_t *f_fill_pkt_rr(const uint8_t *cur_pos, const uint8_t *end_pos,
                              const uint8_t **rr_pos, t_lip_dns_rr *rr) {
    cur_pos = f_get_pos_after_name(cur_pos, end_pos - LIP_DNS_RR_CONST_SIZE);
    if (cur_pos != NULL) {
        uint16_t rlen = ((uint16_t)cur_pos[8] << 8) | cur_pos[9];
        if (rr!=NULL) {
            rr->type   = ((uint16_t)cur_pos[0] << 8) | cur_pos[1];
            rr->rclass = (((uint16_t)cur_pos[2] << 8) | cur_pos[3])
                    & ~LIP_MDNS_RRCLASS_FLAG_FLUSH;
            rr->ttl = ((uint32_t)cur_pos[4] << 24) |
                       ((uint32_t)cur_pos[5] << 16) |
                       ((uint32_t)cur_pos[6] << 8) |
                        cur_pos[7];
            rr->rdlength = rlen;
        }

        if (rr_pos != NULL)
            *rr_pos = cur_pos;
        cur_pos += LIP_DNS_RR_CONST_SIZE;
        cur_pos += rlen;
    }
    return cur_pos;
}

/* определение, кто выиграл, при одновременной пробе текущего хоста с другим.
 * = 0 - записи одинаковые
 * > 0 - локальные записи lexicographically later
 * < 0 - записи из пакета lexicographically later */
static int f_probe_tiebreaking(t_mdns_node *node, const uint8_t *cur_pos,
                               const t_lip_dns_hdr *hdr, const uint8_t *end_pos, uint16_t rr_cnt) {
    int out = 0;
    unsigned local_rr_cnt = 0;

    for (unsigned rr_idx=node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
        if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx]) && !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) {
            ++local_rr_cnt;
        }
    }

    lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_WARNING_MEDIUM, 0,
               "lip mdns: start probe tiebreaking. packet rr cnt = %d, loc rr cnt = %d\n", rr_cnt, local_rr_cnt);

    /* каждую запись из пакета, относящуюся к данному узлу, сравниваем с
     * с каждой локальной. Для каждой локальной записи сохраняем счетчик,
     * указывающий, для сколько записей из пакета данная локальная запись
     * lexicographically later. Также устанавливается флаг MDNS_RR_FLAGS_TIEBREAK_FND_EQU,
     * если в пакете есть одинаковая запись */
    for (unsigned pkt_rr_idx = 0; pkt_rr_idx < rr_cnt; ++pkt_rr_idx) {
        const uint8_t *rr_pos;
        const uint8_t *aname = cur_pos;

        cur_pos = f_fill_pkt_rr(cur_pos, end_pos, &rr_pos, NULL);
        if (cur_pos != NULL) {
            if (f_cmp_dns(&node->domain, aname, hdr, end_pos) == 0) {
                int fnd_equ = 0;

                for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                    if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx]) && !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) {
                        int res = f_cmp_rr(node, &f_rrs[rr_idx], rr_pos, hdr, end_pos, 0);
                        lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_WARNING_LOW, 0,
                                   "lip mdns: rr (%d,%d) cmp result = %d\n", rr_idx-node->rr_idx, pkt_rr_idx, res);
                        if ((res == 0) && !fnd_equ) {
                            f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_TIEBREAK_FND_EQU;
                            fnd_equ = 1;
                        } else if (res > 0) {
                            /* количество записей, больше которых данная запись */
                            ++f_rrs[rr_idx].rr_later_cnt;
                        }
                    }
                }
            }
        }
    }

    /* сравнение результатов должно идти начиная с минимальной записи. минимальной
     * будет запись с минимальным кол-вом rr_later_cnt. пока наборы совпадают,
     * то должно быть по одной записи с 0, 1 и т.е. с флагом EQU. Первое отклонение
     * от этого образца определяет, какой набор больше */
    for (unsigned cmp_num = 0; (cmp_num < rr_cnt) && (cmp_num < local_rr_cnt) && !out; ++cmp_num) {
        int cmp_cnt = 0;
        int cmp_equ = 0;

        for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
            if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx]) && !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) {
                if (f_rrs[rr_idx].rr_later_cnt == cmp_num) {
                    ++cmp_cnt;
                    if (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_TIEBREAK_FND_EQU) {
                        ++cmp_equ;
                    }
                }
            }
        }

        lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_WARNING_LOW, 0,
                   "lip mdns: cmp check %d: cmp cnt cmp cnt = %d, equ cnt = %d\n",
                   cmp_num, cmp_cnt, cmp_equ);

        if (cmp_cnt == 0) {
            out = 1;
        } else if ((cmp_cnt > 1) || (cmp_equ==0)) {
            out = -1;
        }
    }
    /* если пока есть записи в одном из источников, они все одинаковы, то
     * смотрим у кого остались записи */
    if (out == 0) {
        out = local_rr_cnt - rr_cnt;
    }

    /* очищаем временные флаги */
   for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
        if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx]) && !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) {
            f_rrs[rr_idx].rr_later_cnt = 0;
            f_rrs[rr_idx].flags &= ~MDNS_RR_FLAGS_TIEBREAK_FND_EQU;
        }
    }

    return out;
}



/***************************************************************************//**
     @internal
     Обработка принятых пакетов по UDP-порту mDNS.
     @param[in] pkt  - Указатель на первый байт после udp-заголовка в принятом
                       пакете
     @param[in] size - Размер данных в пакете
     @param[in] info - Структура с информацией udp и ip уровня для пакета
     @return           Код ошибки
 ******************************************************************************/
static t_lip_errs f_process_packet(uint8_t *pkt, size_t size, const t_lip_udp_info *info) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    const t_lip_dns_hdr *hdr = (const t_lip_dns_hdr *)pkt;
    const uint8_t *cur_pos = &hdr->data[0];
    const uint8_t *end_pos = pkt + size;
    t_lclock_ticks cur_time = lclock_get_ticks();
    uint16_t hdr_flags = 0;
    if (size < sizeof(hdr)) {
        err = LIP_ERR_RX_PACKET_FORMAT;
    } else {
        /* пакеты с полями RCODE!=0 или OPCODE!=0 должны быть проигнорированны */
         hdr_flags = NTOH16(hdr->flags);
         if (((LIP_DNS_OPCODE_MSK & hdr_flags) != 0) ||
                 ((LIP_DNS_RCODE_MSK & hdr_flags) != 0)) {
             err = LIP_ERR_RX_PACKET_FORMAT;
         }
    }

    if (err == LIP_ERR_SUCCESS) {
        t_lip_ip_addr addr;
        lip_ip_addr_fill(&addr, info->src_addr, info->ip_type);
        uint8_t onlink = lip_ip_addr_check(&addr, LIP_IP_ADDR_TYPE_LOCAL) == LIP_ERR_SUCCESS;
        uint8_t direct_query = !(info->flags & LIP_IP_FLAG_MULTICAST);

        uint16_t qdcount = HTON16(hdr->qdcount);
        uint16_t ancount = HTON16(hdr->ancount);
        uint16_t nscount = NTOH16(hdr->nscount);

        /* если это ответ - то нужно проверить, что не конфликтует с существующими
          именами */
        if (hdr_flags & LIP_DNS_FLAG_QR) {
            /* ответы, посланные из другой сети не рассматриваем
               также игнорируем все ответы с портом источника не 5353 (p 15)
            */
            int valid = onlink && (info->src_port == LIP_UDP_PORT_MDNS);

            if (valid) {
                uint16_t q;                
                int conflicts = 0;

                /* пропускаем секцию вопросов, если она есть */

                for (q = 0; (q < qdcount) && (cur_pos != NULL); ++q) {
                    cur_pos = f_fill_pkt_query(cur_pos, end_pos, NULL);
                }

                /* проверяем ответы на совпадения с нашими */
                for (q = 0; (q < ancount) && (cur_pos != NULL); ++q) {
                    const uint8_t *aname = cur_pos;
                    const uint8_t *rr_pos;
                    t_lip_dns_rr rr;
                    cur_pos = f_fill_pkt_rr(cur_pos, end_pos, &rr_pos, &rr);
                    if (cur_pos != NULL) {
                        for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
                            t_mdns_node *node = &f_nodes[node_idx];

                            if (!(node->flags & MDNS_NODE_FLAGS_CONFLICT) &&
                                    (f_cmp_dns(&node->domain, aname, hdr, end_pos) == 0)) {
                                int fnd_equ = 0;

                                for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                                    if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx]) &&
                                            (rr.type == f_rrs[rr_idx].rr.type) &&
                                            (rr.rclass == f_rrs[rr_idx].rr.rclass)) {
                                        if (f_cmp_rr(node, &f_rrs[rr_idx], rr_pos,
                                                     hdr, end_pos, 0) == 0) {
                                            fnd_equ = 1;
                                            /* Если записи равны проверяем TTL.
                                               Если объявляемый TTL меньше TTL/2 =>
                                                ставим эту запись на анонс (пункт 6.6)*/
                                            if (!(node->flags & (MDNS_NODE_FLAGS_PROBE |
                                                                 LIP_MDNS_PROBE_DEFFERED_TIME))) {
                                                if (rr.ttl < (f_rrs[rr_idx].rr.ttl >> 1)) {
                                                    f_start_announce(&f_rrs[rr_idx], cur_time);
                                                } else if (rr.ttl >= f_rrs[rr_idx].rr.ttl) {
                                                    /* если планировали послать ответ с данной
                                                       записью, но видим, что он уже посылается,
                                                       то считаем, что мы его уже послали
                                                       пункт 7.4 p24 */
                                                    if ((f_rrs[rr_idx].flags & MDNS_RR_FLAGS_ANNOUNCE)
                                                            && !MDNS_RR_ANNOUNCE_IN_PROGRESS(&f_rrs[rr_idx])) {
                                                        f_rrs[rr_idx].flags &= ~MDNS_RR_FLAGS_ANNOUNCE;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }

                                if (!fnd_equ) {
                                    /* если нет одинаковой записи, а узел должен
                                       быть уникальным => конфликт
                                       При этом если еще не посылали пробу, то игнорируем
                                       конфликт 8.1 p27 */
                                    if ((node->flags & MDNS_NODE_FLAGS_UNIQUE) && (node->probes_cnt != 0)) {
                                        node->flags |= MDNS_NODE_FLAGS_CONFLICT;
                                        conflicts = 1;
                                    }
                                }
                            }
                        }
                    }
                } /* for (q=0; (q < hdr->ancount) && (cur_pos); q++) */

                /* если были обнаружены конфликты - разрешаем их */
                if (conflicts != 0) {
                    for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
                        t_mdns_node *node = &f_nodes[node_idx];

                        if (node->flags & MDNS_NODE_FLAGS_CONFLICT) {
                            node->flags &= ~MDNS_NODE_FLAGS_CONFLICT;
                            /* если пришел ответ по узлу, по которому идет проверка,
                              то значит конфликт (в соответствии с page 26) */
                            if (node->flags & MDNS_NODE_FLAGS_PROBE) {
                                t_lip_dns_name new_name = {NULL, NULL};

                                ++node->conflict_cnt;
                                lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_WARNING_MEDIUM, 0,
                                       "lip mdsn: name conflict for node %s (%d), conflicts for node %d\n",
                                       node->domain.name, node_idx, node->conflict_cnt);

                                /* если есть callback - вызываем его для получения
                                  нового доменного имени */
                                if (node->conflict_cb != NULL)
                                    node->conflict_cb(&node->domain, &new_name, node->conflict_cnt);

                                if (new_name.name != NULL) {
                                    node->domain = new_name;
                                    f_restart_conflict_probe(node);

#ifdef LIP_USE_DNSSD
                                    if (node_idx == 0)
                                        lip_dnssd_hostname_ch_cb();
#endif
                                } else {
                                    /* если нового имени не предоставлено, нужно удалить запись */
                                    if (node_idx == 0) {
                                        f_reset_hostname();
                                    } else {
                                        lip_mdns_rr_remove(&node->domain, LIP_DNS_RRTYPE_ANY, NULL, 0);
                                    }
                                }
                            } else {
                                /* переходим снова в состояние probe для проверки на
                                   конфликт */
                                node->flags |= MDNS_NODE_FLAGS_PROBE;
                                node->probes_cnt = 0;
                                f_start_probe();
                            }
                        }
                    }
                } /* if (conflicts!=0) */
            } /* if (onlink) */
        } else {
            /* -------------- обработка запросов ----------------------*/
            const uint8_t *end_quest = NULL;
            int out = 0;
            /* признак, что это проверка */
            uint8_t is_probe = 0;
            /* признак, что данный хост содержит все ответы из запроса */
            uint8_t is_author = 1;
            /* признак, что нашли хотя бы один ответ */
            uint16_t has_answ  = 0;
            /* признак, что это legacy-запрос */
            uint8_t legacy = (info->src_port == LIP_UDP_PORT_MDNS) ? 0 : 1;

            uint16_t q = 0;
            uint8_t unicast_prep = 0, mcast_prep = 0;
            

            /* проходимся по всем вопросам в запросе */
            for (q = 0; (q < qdcount) && !out; ++q) {
                const uint8_t *qname = cur_pos;
                t_lip_dns_query query;
                cur_pos = f_fill_pkt_query(cur_pos, end_pos, &query);
                if (cur_pos != NULL) {
                    int fnd = 0;
                    /* ищем узел с запрашиваемым именем */
                    for (unsigned node_idx = 0; !fnd && (node_idx < f_nodes_cnt); ++node_idx) {
                        t_mdns_node *node = &f_nodes[node_idx];

                        if (f_cmp_dns(&node->domain, qname, hdr, end_pos) == 0) {
                            int fnd_rr;
                            fnd = 1;
                            /* если есть хоть одна запрашиваемая разделяемая
                              запись - то значит могут и другие ответить */
                            if (!(node->flags & (MDNS_NODE_FLAGS_UNIQUE | MDNS_NODE_FLAGS_REVERSE_ADDR_MAP)))
                                is_author = 0;

                            fnd_rr = f_find_answers(node, query.qclass, query.qtype, 0);

                            if (fnd_rr == 0) {
                                t_send_resp_type type = legacy ? MDNS_SEND_RESP_LEGACY :
                                        (onlink && ((query.qclass & LIP_MDNS_QCLASS_FLAG_QU) || direct_query)) ?
                                        MDNS_SEND_RESP_UNICAST : MDNS_SEND_RESP_MCAST;
                                t_mdns_node *next_node = node;
                                while (next_node != NULL) {
                                    next_node = f_check_cname(next_node,
                                                   query.qclass, query.qtype, type, &has_answ);
                                }
                            } else {
                                has_answ += fnd_rr;
                            }
                        }
                    }
                    /* если на хотя бы один вопрос не нашли ответа =>
                      могут ответить и другие */
                    if (!fnd)
                        is_author = 0;
                }
            } /* for (q=0; (q < hdr->qdcount) && !out; q++) */

            end_quest = cur_pos;


            /* проверяем секцию ответов, в которой содержатся уже известные
               запрашивающиму ответы (known-answer list). Если TTL в такой
               записи не меньше половины TTL записи, то отвечать не нужно
               (пункт 7.1 стр. 22) */
            if (ancount!=0) {
                uint16_t ans = 0;
                int tc_next = 0;
                /* если этот флаг - продолжение предыдущего пакета с флагом TC */
                if ((qdcount == 0) && (f_state_flags & MDNS_STATE_FLAGS_WAIT_TC)
                        && (lip_ipv4_addr_equ(f_tc_addr, info->src_addr))) {
                    tc_next = 1;
                }

                /* проходим по всем ответам в запросе */
                for (ans = 0; (ans < ancount) && (cur_pos != NULL); ++ans) {
                    /* ищем узел с доменным именем, соответствующим ответу */
                    uint8_t fnd_node = 0;
                    t_lip_dns_rr rr;
                    const uint8_t *aname = cur_pos;
                    const uint8_t *rr_pos;
                    cur_pos = f_fill_pkt_rr(cur_pos, end_pos, &rr_pos, &rr);

                    for (unsigned node_idx = 0; !fnd_node && (node_idx < f_nodes_cnt); ++node_idx) {
                        t_mdns_node *node = &f_nodes[node_idx];

                        if (f_cmp_dns(&node->domain, aname, hdr, end_pos) == 0) {
                            fnd_node = 1;

                            for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                                if (f_cmp_rr(node, &f_rrs[rr_idx], rr_pos, hdr, end_pos, 0) == 0) {
                                    /* если записи совпадают и TTL достаточный -
                                      снимаем флаг для ответа */
                                    if (rr.ttl >= (f_rrs[rr_idx].rr.ttl/2)) {
                                        /* если это второй пакет, то смотрим по
                                           TC-флагам, если эта запись планируется
                                           только на этот ответ, то отменяем
                                           план на ее аннонс */
                                        if (tc_next) {
                                            if (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_WAIT_TC_MCAST) {
                                                f_rrs[rr_idx].flags &= ~MDNS_RR_FLAGS_ANNOUNCE;
                                            }
                                            if (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_WAIT_TC_UCAST) {
                                                f_rrs[rr_idx].flags &= ~MDNS_RR_FLAGS_UNICAST_RESP;
                                            }
                                        } else {
                                            /* если это секция ответов в том же пакете, что и вопросы,
                                               то просто убираем флаги на рассмотрение на отправку */
                                            if (f_rrs[rr_idx].flags & (MDNS_RR_FLAGS_UNICAST_REQ |
                                                                       MDNS_RR_FLAGS_MULTICAST_REQ)) {
                                                f_rrs[rr_idx].flags &= ~(MDNS_RR_FLAGS_UNICAST_REQ |
                                                                MDNS_RR_FLAGS_MULTICAST_REQ);
                                                --has_answ;
                                            }
                                        }
                                    }
                                }
                            }
                        } /* if (f_cmp_dns(node->domain, aname, hdr, pkt+size)==0) */
                    } /* for (node) */
                } /* for (ans = 0; ans < hdr->ancount; ans++) */

                if (tc_next) {
                    /* по последующему пакету с AnswerList откладываем планирование
                       на эти ответы на 400-500 мс от текущего времени */
                    t_lclock_ticks tout = lip_rand_range(LIP_MDNS_TC_RESP_DELAY_MAX-LIP_MDNS_TC_RESP_DELAY_MIN)
                                                         + LIP_MDNS_TC_RESP_DELAY_MIN;
                    int fnd_ucast = 0;
                    int fnd_mcast = 0;
                    for (unsigned rr_idx = 0; rr_idx < f_rr_cnt; ++rr_idx) {
                        if (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_WAIT_TC_MCAST) {
                            f_rrs[rr_idx].time_announce = cur_time + tout;
                            fnd_mcast = 1;
                        }
                        if (!fnd_ucast && (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_WAIT_TC_UCAST)) {
                            ltimer_set(&f_unicast_tmr, tout);
                            fnd_ucast = 1;
                        }
                    }

                    /* Если все планируемые ответы на пакет с TC исключены,
                       то снимаем флаг, чтобы уже не проверять в будущем */
                    if (!fnd_mcast && !fnd_ucast)
                        f_state_flags &= MDNS_STATE_FLAGS_WAIT_TC;

                    if (fnd_mcast)
                        f_check_announce();
                }




            } /* if (hdr->ancount) */

            if (nscount != 0) {
                uint16_t auth = 0;

                /* согласно спецификации, пробу можно отличить по признаку,
                  что authority содержит ответы на вопросы из query-секции.
                  Здесь находим первую запись, содержащую ответ, на случай, если
                  у authority появятся еще функции в будущем... */
                for (auth = 0; (auth < nscount) && (cur_pos != NULL); ++auth) {
                    const uint8_t* aname = cur_pos;

                    cur_pos = f_get_pos_after_name(cur_pos, end_pos);
                    for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
                        t_mdns_node *node = &f_nodes[node_idx];

                        if ((node->flags & MDNS_NODE_FLAGS_QUESTIONED)
                                && !f_cmp_dns(&node->domain, aname, hdr, pkt + size)) {
                            is_probe = 1;
                            if (node->flags & MDNS_NODE_FLAGS_PROBE) {
                                int cmp_res = f_probe_tiebreaking(node, aname, hdr,
                                                                  end_pos, nscount - auth);
                                node->flags &= ~MDNS_NODE_FLAGS_QUESTIONED;
                                /* если проиграли в сравнении, то откладываем проверку
                                 * на потом */
                                if (cmp_res < 0) {
                                    node->flags &= ~MDNS_NODE_FLAGS_PROBE;
                                    node->flags |= MDNS_NODE_FLAGS_PROBE_DEFFERED;
                                }
                            }
                        }
                    }
                }
            }


            if (legacy && has_answ) {
                /* ответ на legacy-запрос посылаем сразу */
                t_mdns_lagacy_info legacy_info;

                legacy_info.addr = info->src_addr;
                legacy_info.ip_type = info->ip_type;
                legacy_info.hdr = hdr;
                legacy_info.port = info->src_port;
                legacy_info.qcnt = qdcount;
                legacy_info.qend = end_quest;
                legacy_info.id = hdr->id;
                f_send_response(MDNS_SEND_RESP_LEGACY, &legacy_info);
            } else if (has_answ) {
                t_lclock_ticks tout;


                /* Если это запрос на проверку - нужно отсылать немедленно.
                   Так же если у нас все ответы, то можем точже отвечать сразу */
                if (is_probe || (is_author && !(hdr_flags & LIP_DNS_FLAG_TC))) {
                    tout = 0;
                } else if (hdr_flags & LIP_DNS_FLAG_TC) {
                    /* если пришел новый пакет TC, то снимаем все флаги от старого */
                    /** @note Сохраняем только один адрес пакета с TC. Если при этом
                       придет второй запрос, то от предыдущего перестаем отслеживать
                       список известных ответов. Это может привести только к посылке
                       лишних пакетов, но не нарушит работу протокола (хотя нарушает
                       RFC) */
                    f_state_flags |= MDNS_STATE_FLAGS_WAIT_TC;
                    for (unsigned rr_idx = 0; rr_idx < f_rr_cnt; ++rr_idx) {
                        f_rrs[rr_idx].flags &= ~(MDNS_RR_FLAGS_WAIT_TC_MCAST | MDNS_RR_FLAGS_WAIT_TC_UCAST);
                    }
                    lip_ipv4_addr_cpy(f_tc_addr, info->src_addr);

                    /* при установленном флаге TC таймаут выбирается из большего интервала */
                    tout = LTIMER_MS_TO_CLOCK_TICKS(lip_rand_range(LIP_MDNS_TC_RESP_DELAY_MAX-LIP_MDNS_TC_RESP_DELAY_MIN)
                            + LIP_MDNS_TC_RESP_DELAY_MIN);
                } else {
                    tout = LTIMER_MS_TO_CLOCK_TICKS(lip_rand_range(LIP_MDNS_SHARED_RESP_DELAY_MAX-LIP_MDNS_SHARED_RESP_DELAY_MIN)
                            + LIP_MDNS_SHARED_RESP_DELAY_MIN);
                }

                /* устанавливаем таймер и флаг для каждой записи, для которой
                  посылаем ответ */
                for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
                    t_mdns_node *node = &f_nodes[node_idx];
                    if (!(node->flags & MDNS_NODE_FLAGS_PROBE)) {
                        if (node->flags & MDNS_NODE_FLAGS_QUESTIONED) {
                            for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                                if (f_rrs[rr_idx].flags & (MDNS_RR_FLAGS_UNICAST_REQ | MDNS_RR_FLAGS_MULTICAST_REQ)) {
                                    /* проверяем, следует на этот узел послать unicast или multicast.
                                       1. если адрес не в локальной сети => всегда mcast (иначе будет отброшен)
                                       2. можем послать qu только если запросили битом в классе запроса, либо
                                          если это запрос на наш адрес (а не mcast)
                                       3. если это не ответ на пробу и прямой запрос, но при этом
                                          не посылали сообщение в ближайшие TTL/4
                                          по mcast (p12), то вместо этого пошлем mcast */
                                    uint8_t mcast_expired = !(f_rrs[rr_idx].flags & MDNS_RR_FLAGS_MCAST_TIME_VALID)
                                            || ((cur_time - f_rrs[rr_idx].mcast_snd_time) / LCLOCK_TICKS_PER_SECOND)
                                                    >= (f_rrs[rr_idx].rr.ttl >> 2);

                                    if (onlink && ((f_rrs[rr_idx].flags & MDNS_RR_FLAGS_UNICAST_REQ) || direct_query)) {

                                        if (!mcast_expired || direct_query || is_probe) {
                                            if (!unicast_prep) {
                                                /* если это первая найденная запись для unicast
                                                    ответа, выполняем подготовительные действия */
                                                unicast_prep = 1;
                                                f_prepare_unicast_resp(info, tout);
                                            }

                                            if (hdr_flags & LIP_DNS_FLAG_TC) {
                                                f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_WAIT_TC_UCAST;
                                            }

                                            f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_UNICAST_RESP;
                                        }
                                    }


                                    /* проверяем, нужно ли запланировать multicast (при стечении обстоятельств multicast и unicast
                                     * могут посылаться одновременно */
                                    if (!onlink || (f_rrs[rr_idx].flags & MDNS_RR_FLAGS_MULTICAST_REQ)
                                            || mcast_expired) {                                        

                                        if (hdr_flags & LIP_DNS_FLAG_TC) {
                                            if (!(f_rrs[rr_idx].flags & MDNS_RR_FLAGS_ANNOUNCE))
                                                f_rrs[rr_idx].flags |= MDNS_RR_FLAGS_WAIT_TC_MCAST;
                                        } else {
                                            f_rrs[rr_idx].flags &= ~MDNS_RR_FLAGS_WAIT_TC_MCAST;
                                        }

                                        /* иначе шлем mcast */
                                       if (f_mdns_rr_mark_announce(&f_rrs[rr_idx],
                                                    cur_time + tout, is_probe) == 0) {
                                           mcast_prep = 1;
                                       }

                                    }
                                } /* if (node->rrs[i].flags & _MDNS_RR_FLAGS_RESP) */
                            } /* for (i=0; i < node->rr_cnt; i++) */
                        } /* if (node->flags & _MDNS_NODE_FLAGS_QUESTIONED) */
                    } /* if (!(node->flags & _MDNS_NODE_FLAGS_PROBE) */
                } /* for (node...) */

                if (mcast_prep) {
                    /* корректируем таймер на посылку анонсов */
                    f_check_announce();
                }
            } /* if (has_answ) */
        } /* if (hdr->flags & LIP_DNS_FLAG_QR) */

        /* снимаем все временные флаги удалены */
        for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
            t_mdns_node *node = &f_nodes[node_idx];
            for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                f_rrs[rr_idx].flags &= ~(MDNS_RR_FLAGS_MULTICAST_REQ | MDNS_RR_FLAGS_UNICAST_REQ);
            }
            node->flags &= ~(MDNS_NODE_FLAGS_QUESTIONED);
        }
    }
    return err;
}

/***************************************************************************//**
 * @internal
 * Инициализация модуля mDNS.
 * Вызывается ядром стека из lip_init().
 * Устанавливаем локальные переменные в значения по-умолчанию
 * Выполняется регистрация в multicast-группе, начинаем прослушивать UDP-порт
 ******************************************************************************/
void lip_mdns_init(void) {
    f_nodes_cnt = f_addr_nodes_cnt = 0;
    f_rr_cnt = f_addrl_rr_cnt = 0;
    
    f_state_flags = 0;
    f_conflict_cnt = 0;
    
    f_nodes[0].domain.name = NULL;
    f_nodes[0].domain.parent = NULL;

    /* регистрируемся в multicast-группе с адресом mDNS */
    lip_igmp_join((t_lip_igmp_sock)f_process_packet, f_mdns_ipv4_addr);
    /* начинаем прослушивать udp-порт mDNS */
    lip_udp_port_listen(LIP_UDP_PORT_MDNS, f_process_packet);


    lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_DEBUG_MEDIUM, err,
               "lip mdns: memory size = %d\n", sizeof(f_nodes) + sizeof(f_rrs));
}

/***************************************************************************//**
 * @internal
 * Закрытие модуля mDNS. Вызывается ядром из lip_close().
 ******************************************************************************/
void lip_mdns_close(void) {
    /* удаляем все записи */
    lip_mdns_rr_remove(NULL, LIP_DNS_RRTYPE_ANY, NULL, 0);
    if (f_state_flags & MDNS_STATE_FLAGS_SEND_GOODBYE) {
        f_send_response(MDNS_SEND_RESP_MCAST, NULL);
    }    
}

/***************************************************************************//**
  @internal
  Функция вызывается ядром стека, когда произошло подключение к новой сети.
  Сбрасываем состояние модуля, но сохраняем все записи DNS, которые были
  добавлены до этого
  *****************************************************************************/
void lip_mdns_reconnect(void) {
    int has_uniq = 0;
    f_state_flags = 0;
    f_conflict_cnt = 0;

    for (unsigned node_idx = 0; node_idx < f_nodes_cnt; ++node_idx) {
        t_mdns_node *node = &f_nodes[node_idx];
        node->probes_cnt = 0;
        /* для уникальных записей мы должны снова проверить их на конфликты -
           переходим в состояние проверки */
        if (node->flags & MDNS_NODE_FLAGS_UNIQUE) {
            node->flags |= MDNS_NODE_FLAGS_PROBE;
            has_uniq = 1;
        }

        /* сбрасываем состояния, связанные с записями */
        for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
            /** @note по идее адресные записи должны уже измениться на
                      функцию изменения адреса и не нужно их тут менять
                      дополнительно */

            /* если должны были удалить записи, то удаляем сейчас */
            if (f_rrs[rr_idx].rr.ttl == 0) {
                f_rrs[rr_idx].flags = MDNS_RR_FLAGS_REMOVE;
                f_state_flags |= MDNS_STATE_FLAGS_REMOVE_RRS;
            } else {
                f_rrs[rr_idx].flags = 0;
                f_rrs[rr_idx].announce_cnt = 0;
                if (!(node->flags & MDNS_NODE_FLAGS_UNIQUE)) {
                    f_start_announce(&f_rrs[rr_idx], lclock_get_ticks());
                }
            }
        }
    }

    if (f_state_flags & MDNS_STATE_FLAGS_REMOVE_RRS) {
        f_remove_rrs();
    }

    /* обновляем состояние состояние записей с IP-адресами на основе текущих
       адресов */
    lip_mdns_ip_addr_changed(LIP_IPTYPE_IPv4);
    lip_mdns_ip_addr_changed(LIP_IPTYPE_IPv6);
    /* если были уникальные записи - заново их проверяем */
    if (has_uniq)
        f_start_probe();

}


static t_lip_errs f_set_ipv4_rr(const uint8_t *addr) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    t_lip_dns_rr rr;
    t_mdns_node *node = &f_nodes[0];

    /* удаляем реверсную запись, если была */
    lip_mdns_rr_remove(&f_rev_ipv4_addr, LIP_DNS_RRTYPE_ANY, NULL, LIP_MDNS_REMFLAGS_FLUSH);

    if (lip_ipv4_addr_is_valid(addr)) {
        rr.rclass = LIP_DNS_CLASS_IN;
        rr.ttl = LIP_MDNS_HOSTNAME_TTL;
        rr.type = LIP_DNS_RRTYPE_A;
        rr.rdlength = LIP_IPV4_ADDR_SIZE;

        err = f_add_rr_to_node(node, &rr, addr, LIP_MDNS_ADDFLAGS_UNIQUE |
                               LIP_MDNS_ADDFLAGS_REMOVE_PREVIOUS | LIP_MDNS_ADDFLAGS_INTERNAL_ADDR);

        if (err == LIP_ERR_SUCCESS) {
            /* если не было действительного адреса, то начинаем проверку в момент
             * установления */
            if (!(f_state_flags & MDNS_STATE_FLAGS_IPV4_SET)) {
                f_state_flags |= MDNS_STATE_FLAGS_IPV4_SET;
                f_start_probe();
            }
            /* добавляем запись для обратного отображения - адреса на имя */
            f_fill_rev_ipv4_str(lip_ipv4_cur_addr());
            rr.type = LIP_DNS_RRTYPE_PTR;
            rr.rdlength = sizeof(t_lip_dns_name);
            /* в соответствии с 8.1 page 27 проверка PTR адресных записей не проводится,
             * как и разрешение конфликтов вне mDNS, однако на них нужно генерировать
             * NSEC */
            err = lip_mdns_rr_add(&f_rev_ipv4_addr, &rr, &node->domain,
                                  LIP_MDNS_ADDFLAGS_REVERSE_ADDR_MAP | LIP_MDNS_ADDFLAGS_REMOVE_PREVIOUS |
                                  LIP_MDNS_ADDFLAGS_INTERNAL_ADDR, 0);
        }
    } else {
        lip_mdns_rr_remove(&node->domain, LIP_DNS_RRTYPE_A, NULL, LIP_MDNS_REMFLAGS_FLUSH);
        f_state_flags &= ~MDNS_STATE_FLAGS_IPV4_SET;
    }
    return err;
}


/***************************************************************************//**
   @internal
   Функция вызывается ядром стека при смене активного ip-адреса.
   При этом старая запись с ip-адресом либо удаляется, либо заменяется но
   на запись с новым адресом, если он действителен.

   @param[in] type  - тип адреса (IPv4, IPv6)
  *****************************************************************************/
void lip_mdns_ip_addr_changed(t_lip_ip_type type) {
    /* если доменное имя еще не устанавливали, то находимся в начальном
       состоянии и по изменению ничего делать не надо */    
    if (MDNS_HOST_NAME_IS_SET()) {
        if (type == LIP_IPTYPE_IPv4) {
            f_set_ipv4_rr(lip_ipv4_cur_addr());
        }
    }
}



/***************************************************************************//**
   @internal
   Выполнение фоновых задач модуля.
   Здесь происходит отправка пакетов по таймаутам (проверки, анонсы, ответы)
   ****************************************************************************/
void lip_mdns_pull(void) {    
    /* проверяем, было ли уже установлено имя устройства. в противном случае
       mDNS не используется вообще.... */
    if (MDNS_HOST_NAME_IS_SET()) {
        
        if (f_state_flags & (MDNS_STATE_FLAGS_IPV4_SET | MDNS_STATE_FLAGS_IPV6_SET)) {           
            /* если нужно - посылаем проверку уникальных записей */
            if (f_state_flags & MDNS_STATE_FLAGS_PROBE) {
                if (ltimer_expired(&f_probe_tmr))
                    f_send_probe();
            } else if ((f_state_flags & MDNS_STATE_FLAGS_UNICAST_RESPONSE)
                                && (ltimer_expired(&f_unicast_tmr))) {
               f_send_response(MDNS_SEND_RESP_UNICAST, NULL);
            }
            /* анонсы посылаем только если уже прошли проверку уникальные
               записи */
            else if (((f_state_flags & MDNS_STATE_FLAGS_ANNOUNCE)
                        && (ltimer_expired(&f_announce_tmr)))
                     || (f_state_flags & MDNS_STATE_FLAGS_SEND_GOODBYE)) {
                f_send_response(MDNS_SEND_RESP_MCAST, NULL);
            }
        }
    }

    if (f_conflict_cnt != 0) {
        if ((lclock_get_ticks() - f_conflict_time) >
                LTIMER_MS_TO_CLOCK_TICKS(LIP_MDNS_CONFLICT_TOUT)) {
            /* если за последний LIP_MDNS_CONFLICT_TOUT мс не было конфликтов,
              сбрасываем их количество */
            f_conflict_cnt = 0;
        }
    }
}


/***************************************************************************//**
  Установить имя хоста для устройства, под которым оно будет отображаться в
    сети. Модуль создает записи, устанавливающие соответствие между именем
    хоста и его адресом (или адресами, если есть IPv6 (не реализовано)).
    Обычно вызывается сразу после lip_init().
  @param[in] name   Указатель на строку с именем. Строка не должна изменяться
                     пока ее использует модуль (модуль не копирует имя!)
  @param[in] cb     Указатель на функцию, которая будет вызвана, если
                      обнаружится, что данное имя уже используется в
                      локальной сети
  @return           Код ошибки
*******************************************************************************/
t_lip_errs lip_mdns_set_hostname(const char *name, t_lip_mdns_name_conflict_cb cb) {
    t_lip_errs err = LIP_ERR_SUCCESS;    
    t_mdns_node *node = &f_nodes[0];
    t_lip_dns_name domain = {name, &f_mdns_domain};

    /* проверяем, было ли уже установлено имя устройства */
    if (f_nodes_cnt != 0) {
        f_reset_hostname();
        f_fill_new_node(node, &domain, LIP_MDNS_ADDFLAGS_UNIQUE, 0, NULL);
    } else {
        node = f_alloc_new_node(&domain, LIP_MDNS_ADDFLAGS_UNIQUE, 0, NULL);
        if (node == NULL) {
            err = LIP_ERR_MDNS_INSUFFICIENT_MEMORY;
        }
    }

    if (err == LIP_ERR_SUCCESS) {
        node->conflict_cb = cb;
    }
#ifdef LIP_MDNS_CPU_NAME
    if (err == LIP_ERR_SUCCESS) {
        t_lip_dns_rr rr;
        rr.rclass = LIP_DNS_CLASS_IN;
        rr.ttl = LIP_MDNS_HOSTNAME_TTL;
        rr.type = LIP_DNS_RRTYPE_HINFO;
        rr.rdlength = 0;
        err = f_add_rr_to_node(node, &rr, &f_hinfo, LIP_MDNS_ADDFLAGS_UNIQUE);
    }
#endif

#ifdef LIP_USE_DNSSD
     if (err == LIP_ERR_SUCCESS)
        lip_dnssd_hostname_ch_cb();
#endif

    if ((err == LIP_ERR_SUCCESS) && lip_ipv4_cur_addr_is_valid()) {
        f_set_ipv4_rr(lip_ipv4_cur_addr());
    }

    return err;
}

/***************************************************************************//**
  Добавление записи новой записи к базе записей, связанных с данным хостом.
    Если запись уникальна, то проводится проверка имени и при неудаче вызывается
    указанная функция. Если такая запись уже существует, то новая добавляется
    в дополнение к предыдущей.  Добавить новые записи возможно только в случае,
    если уже было установлено имя хоста с помощью lip_mdns_set_hostname().
  @param[in] domain   Имя узла, соответствующее данной записи
  @param[in] rr       Указатель на структуру с полями записи
  @param[in] rdata    Указатель на данные переменной длины.
                      Эти данные не должны изменяться во время жизни записи
  @param[in] flags    Флаги из #t_lip_mdns_addflags
  @param[in] cb       Сallback, вызываемый на возникновение конфликта по данному
                         имени. Имеет смысл только для уникальных записей.
                         При этом на одно имя может быть только одна функция,
                         если узел уже существует с другой функцией, то останется
                         установленной только новая функция.
  @return           Код ошибки
*******************************************************************************/
t_lip_errs lip_mdns_rr_add(const t_lip_dns_name *domain, const t_lip_dns_rr *rr,
                           const void *rdata, unsigned flags,
                           t_lip_mdns_name_conflict_cb cb) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    int fnd = 0;

    if (!MDNS_HOST_NAME_IS_SET()) {
        err = LIP_ERR_MDNS_HOSTNAME_IS_NOT_SET;
        lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_ERROR_HIGH, err,
                   "lip mdns: try to add record while hostname is not set\n");
    } else {
        /* ищем, есть ли уже в базе узел с заданным именем */
        for (unsigned node_idx = 0; !fnd && (node_idx < f_nodes_cnt); ++node_idx) {
            t_mdns_node *node = &f_nodes[node_idx];
            if (f_nodes_is_equ(domain, &node->domain)) {
                fnd = 1;
                err = f_add_rr_to_node(node, rr, rdata, flags);
                if ((err == LIP_ERR_SUCCESS) && (flags & LIP_MDNS_ADDFLAGS_UNIQUE) && (cb != NULL)) {
                   node->conflict_cb = cb;
                }
            } /* if (f_nodes_is_equ(domain, node->domain)) */
        } /* for (node = , .. , ..) */

        /* если записи с таким именем нет, то добавляем */
        if (!fnd) {
            int rr_idx;
            t_mdns_node *node = f_alloc_new_node(domain, flags, 1, &rr_idx);
            if (node != NULL) {
                node->conflict_cb = cb;
                f_rrs[rr_idx].rr = *rr;
                f_rrs[rr_idx].rdata = rdata;
                f_init_rr_state(node, &f_rrs[rr_idx], flags);
            } else {
                err = LIP_ERR_MDNS_INSUFFICIENT_MEMORY;
                lip_printf(LIP_LOG_MODULE_MDNS, LIP_LOG_LVL_ERROR_HIGH, err,
                           "lip mdns: insufficient momory to add node\n");
            }
        }
    }    
    return err;
}


/***************************************************************************//**
    Функция предназначена для удаления записей, связанных с узлом DNS. Удаляются
    записи указанного типа (можно указать #LIP_DNS_RRTYPE_ANY для удаления всех
    записей). Если несколько записей с одним типом принадлежат узлу, а надо
    удалить только одну запись, то можно указать ненулевое значение rdata, тогда
    будет удалена именно запись, ссылающаяся на указанные данные (должен быть
    одинаковый адрес, а не эквивалентность самих данных).

    Если запись уже была объявлена, то в действительности  функция только
    помечает ее на удаление, а удаляются записи при следующем продвижении стека,
    чтобы сократить количество посылаемых пакетов в случае удаления нескольких записей.
    Однако это означает, что данные, на которые указывает запись, должны
    оставаться неизменными до следующего продвижения стека.
    Однако это поведение можно изменить при необходимости с помощью флага
    #LIP_MDNS_REMFLAGS_FLUSH, в результате чего все действия по удалению записи
    будут выполнятся внутри функции. Это не позволяет использовать
    один пакет для оповещения удаления нескольких записей, но позволяет
    быть уверенным, что данные не используются после удаления. При удалении
    нескольких записей подряд имеет смысл указывать #LIP_MDNS_REMFLAGS_FLUSH
    именно при последнем вызове, чтобы использовать один пакет на все эти записи.

    @param[in] domain   Имя узла, соответствующее данной записи
    @param[in] rtype    Тип удаляемой записи (может быть #LIP_DNS_RRTYPE_ANY)
    @param[in] rdata    NULL, если нужно удалить все записи данного типа, или
                        указатель на данные, если нужно удалить только запись
                        с указанными данными из нескольких с записей одним типом
    @param[in] flags    Флаги из #t_lip_mdns_remflags
    @return             Код ошибки
*******************************************************************************/
t_lip_errs lip_mdns_rr_remove(const t_lip_dns_name *domain, uint16_t rtype,
                              const void *rdata, unsigned flags) {
    t_lip_errs err = LIP_ERR_SUCCESS;    
    int stop_fnd = 0;

    /* ищем заданный узел */
    for (unsigned node_idx = 0; !stop_fnd && (node_idx < f_nodes_cnt); ++node_idx) {
        t_mdns_node *node = &f_nodes[node_idx];

        if ((domain == NULL) || f_nodes_is_equ(domain, &node->domain)) {
            if (domain != NULL)
                stop_fnd = 1;

            for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                /* проверяем совпадение типа и данных, если указаны */
                if (((rtype == LIP_DNS_RRTYPE_ANY)
                        || (rtype == f_rrs[rr_idx].rr.type))
                        && ((rdata == NULL) || (rdata == f_rrs[rr_idx].rdata))) {

                    /* если проверку еще не прошли, то можем запись сразу удалять */
                    if (node->flags & MDNS_NODE_FLAGS_PROBE) {
                        f_rrs[rr_idx].flags = MDNS_RR_FLAGS_REMOVE;
                        f_state_flags |= MDNS_STATE_FLAGS_REMOVE_RRS;
                    } else {
                        /* если запись уже была объявлена, то помечаем, что нужно
                           послать GOODBYE, после чего и удалим */
                        f_rrs[rr_idx].rr.ttl = 0;
                        /* общий флаг -> чтобы проверить, что реально их удалили */
                        f_state_flags |= MDNS_STATE_FLAGS_SEND_GOODBYE;
                    }
                }
            } /* for (i=0; i < node->rr_cnt; i++) */
        } /* if (f_nodes_is_equ(domain, node->domain)) */
    } /* for (node ... ) */

    if (flags & LIP_MDNS_REMFLAGS_FLUSH) {
        /* если надо немедленно удалить запись, то пробуем послать оповещение сразу */
        if (f_state_flags & MDNS_STATE_FLAGS_SEND_GOODBYE) {
            f_send_response(MDNS_SEND_RESP_MCAST, NULL);
        }
        /* если не удается послать завершающий пакет, то удаляем эти записи без
           посылки */
        if (f_state_flags & MDNS_STATE_FLAGS_SEND_GOODBYE) {
            for (unsigned rr_idx = 0; rr_idx < f_rr_cnt; ++rr_idx) {
                if (f_rrs[rr_idx].rr.ttl == 0) {
                    f_rrs[rr_idx].flags = MDNS_RR_FLAGS_REMOVE;
                    f_state_flags |= MDNS_STATE_FLAGS_REMOVE_RRS;
                }
            }
            f_state_flags &= ~MDNS_STATE_FLAGS_SEND_GOODBYE;
        }
    }

    if (f_state_flags & MDNS_STATE_FLAGS_REMOVE_RRS)
        f_remove_rrs();
    return err;
}

/***************************************************************************//**
    При вызове данной функции, все записи с заданными параметрами помечаются
    для повторного объявления.

    Данная функция используются в первую очередь для случая, если данные,
    на которые ссылается запись были изменены (при этом не было изменения
    самого расположения данных, т.е. указатель остался тот же).
    
    Если же изменилось расположение данных в памяти, то запись необходимо
    заново добавлять, удаляя предыдущую.

    @param[in] domain   Имя узла, соответствующее записи
    @param[in] rtype    Тип записи, которую нужно пометить для объявления
                        (может быть #LIP_DNS_RRTYPE_ANY)
    @param[in] rdata    NULL, если нужно пометить все записи нужного типа, иди
                        указатель на данные, если нужно пометить только
                        одну запись, ссылающуюся на указанные данные
    @param[in] new_rdlength Если rdata не равно NULL, то данный размер данных
                        устанавливается для помеченной таким образом записи
    @return             Код ошибки
*******************************************************************************/
t_lip_errs lip_mdns_rr_reannonce(const t_lip_dns_name *domain, uint16_t rtype,
                                 const void *rdata, int new_rdlength) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    int fnd = 0;

    /* ищем заданный узел */
    for (unsigned node_idx = 0; !fnd && (node_idx < f_nodes_cnt); node_idx++) {
        t_mdns_node *node = &f_nodes[node_idx];

        if ((domain == NULL) || f_nodes_is_equ(domain, &node->domain)) {
            fnd = 1;
            for (unsigned rr_idx = node->rr_idx; rr_idx < (node->rr_idx + node->rr_cnt); ++rr_idx) {
                if (MDNS_RR_ESTABLISHED(&f_rrs[rr_idx]) && !MDNS_RR_IS_PSEUDO(&f_rrs[rr_idx])) {
                    /* проверяем совпадение типа и данных, если указаны */
                    if (((rtype == LIP_DNS_RRTYPE_ANY)
                            || (rtype == f_rrs[rr_idx].rr.type))
                            && ((rdata == NULL) || (rdata == f_rrs[rr_idx].rdata))
                            ) {
                        if (rdata != NULL)
                            f_rrs[rr_idx].rr.rdlength = new_rdlength;

                        /* если изменились данные при проверке, то начинаем проверку заново */
                        if (node->flags & MDNS_NODE_FLAGS_PROBE) {
                            node->probes_cnt = 0;
                        } else if (!(node->flags & MDNS_NODE_FLAGS_PROBE_DEFFERED)) {
                            f_rrs[rr_idx].announce_cnt = 0;
                            f_start_announce(&f_rrs[rr_idx], lclock_get_ticks());
                        }
                    }
                }
            }
        }
    }
    return err;
}
/***************************************************************************//**
  Получение доменного имени хоста в локальной сети. Поле @c name возвращенной
  структуры указывает на имя хоста, установленное с помощью lip_mdns_set_hostname(),
  а поле @c parent указывает на используемый домен mDNS (по-умолчанию "local").
  @return Доменное имя хоста в локальной сети.
  *****************************************************************************/
const t_lip_dns_name* lip_mdns_get_host_domain(void) {    
    return &f_nodes[0].domain;
}

/***************************************************************************//**
  Получить доменное имя родительского узла для mDNS. Поле @c name возвращенной
  структуры указывает на имя домена (по-умолчанию "local"), а поле @c parant равно NULL.
  Может использоваться для формирования доменных имен добавляемых записей
  в качестве родительского домена.
  @return Доменное имя родительского узла для mDNS.
  *****************************************************************************/
const t_lip_dns_name* lip_mdns_get_parent_domain(void) {
    return &f_mdns_domain;
}


#endif


