 /**************************************************************************//** 
  @defgroup lip_mdns mDNS
  @ingroup  lip_lvl_app
  @brief Модуль стека @c lip, реализующий протокол multicast-DNS
   в соответствии с @rfc{6762}.\n
   Зависит от: @ref lip_ip, @ref lip_udp, @ref lip_igmp, @ref lip_rand

   Протокол @ref lip_mdns (multicast DNS) позволяет выполнять операции, подобные
    обычному DNS, в локальной сети при отсутствии DNS-сервера.
    Для этих целей выделяется отдельная ветвь пространства имен DNS ---
    <c> .local. </c>,  имена в которой действительны только в пределах
    локальной сети и не требуют специальной регистрации.

   В частности, @ref lip_mdns позволяет сопоставить имена устройств и их адреса в пределах
     локальной сети. При этом выбор имен, как и разрешение конфликтов имен,
     выполняется самими устройствами сети, реализующими этот протокол, и не требует общего
     сервера.

   Протокол @ref lip_mdns является частью технологии, получившей название
       @a "Zero Configuration Networking", позволяющей автоматически
       настраивать устройства в локальной сети и создавать IP-сеть
       без конфигурации или специальных серверов.

   Вместе с @ref lip_dns_sd протокол @ref lip_mdns
     позволяет организовать автоматический поиск устройств, предоставляющих
     определенные сервисы, в пределах локальной сети.

   Данный модуль предоставляет возможность для регистрации новых DNS-записей
     (т.е. реализует mDNS-server или mDNS-responder).
     Модуль не предоставляет возможности для запроса и поиска записей других
     хостов (mDNS-resolver).

   Для включения поддержки @ref lip_mdns нужно определить макрос #LIP_USE_MDNS
     в [файле конфигурации](@ref lip_conf) (при этом должны быть включены также все зависимости).
     После инициализации стека с помощью lip_init() необходимо установить
     имя хоста с помощью lip_mdns_set_hostname() (без имени домена --- домен
     <c> .local. </c>  добавляется самим модулем).
     До установки имени модуль не будет выполнять никаких операций.
     После установки имени модуль проверит его уникальность в локальной сети и, в
     случае возникновения конфликтов, вызовет предоставленную callback-функцию
     типа #t_lip_mdns_name_conflict_cb, которая должна предоставить новое имя.
     Если имя не будет предоставлено, то модуль фактически перейдет в режим ожидания 
     установки нового имени.

   Модуль сам создает записи для установки соответствия адреса устройства и
     указанного имени и отслеживает смену IP-адресов.

   После установки имени, можно добавить новые записи DNS с помощью
     lip_mdns_rr_add(). Каждая запись имеет доменное имя, которому она принадлежит,
     постоянную часть (тип, класс, время жизни), описываемую типом #t_lip_dns_rr,
     и переменную, содержимое которой зависит от типа. В зависимости от типа в
     качестве переменной части передается:
       - для типа #LIP_DNS_RRTYPE_HINFO --- указатель на структуру #t_lip_dns_rdata_hinfo;
       - для типа #LIP_DNS_RRTYPE_SRV   --- указатель на структуру #t_lip_dns_rdata_srv;
       - для типа #LIP_DNS_RRTYPE_TXT   --- указатель на структуру #t_lip_dns_rdata_txt;
       - для типов #LIP_DNS_RRTYPE_PTR, #LIP_DNS_RRTYPE_NS и #LIP_DNS_RRTYPE_CNAME ---
            указатель на структуру #t_lip_dns_name;
       - для остальных типов --- непрозрачный массив байт указанной длины.

   Важно, что данные записей (а также строки с именами узлов) не копируются
     модулем, а используются переданные указатели. Т.е. эти данные должны
     постоянно храниться вне модуля приложением в течении всего времени жизни
     записи и оставаться неизменными. В случае изменения данных, на которую
     ссылается созданная запись, новые данные должен быть объявлены повторно
     с помощью lip_mdns_rr_reannonce() (или запись может быть удалена до
     изменения и добавлена снова после изменения).

   Каждая запись принадлежит некому доменному узлу (имеет доменное имя),
     которое определяется типом #t_lip_dns_name, который позволяет ссылаться
     на уже существующее доменное имя, как на родительское.
     Все доменные имена для @ref lip_mdns должны находится в специальном
     пространстве (<c>.local.</c>). Чтобы сослаться на него, как на родительское,
     можно получить указатель на данное имя с помощью функции
     lip_mdns_get_parent_domain(). В остальном доменные имена добавляемых записей
     могут быть любыми --- как связанными с именем хоста, так и нет.
     Текущему установленному имени хоста соответствует доменный узел с именем
     <c> \<имя хоста>.local. </c> Получить структуру, описывающую это доменное имя можно
     с помощью функции lip_mdns_get_host_domain().

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

   Следует учитывать, что реально в @ref lip_mdns используются не уникальные
     записи, а уникальные доменные имена, и проверки выполняются для любого
     доменного имени, содержащего хотя бы одну уникальную запись. При этом
     callback-функция реально соответствует доменному имени, а не записи. Поэтому
     при добавлении нескольких уникальных записей для одного имени необходимо
     либо указывать указатель на одну и ту же callback-функцию, либо указать
     функцию только для одной записи, а для остальных записей указывать
     в качестве указателя на функцию NULL. В противном случае будет вызвана 
     при конфликте только функция, соответствующая последнему переданному 
     ненулевому указателю.
     Для того, чтобы указать, что запись является уникальной, необходимо
     установить флаг #LIP_MDNS_ADDFLAGS_UNIQUE при вызове lip_mdns_rr_add().

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

  Удалить записи можно с помощью функции lip_mdns_rr_remove(). При этом можно:
     - удалить все записи, принадлежащие заданному доменному имени, указав в
       качестве типа @c rtype #LIP_DNS_RRTYPE_ANY;
     - удалить все записи определенного типа, принадлежащие заданному доменному
       имени, указав нужный тип в @c rtype и NULL в качестве @c rdata;
     - удалить конкретную запись заданного типа, принадлежащую заданному доменному
       имени, указав нужный тип в @c rtype и указатель на данные нужной записи
       в @c rdata.

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

  @note Так как текущая реализация считает символ '.' в имени разделителем между частями
  доменного имени, то она не поддерживает имена записей, которые внутри доменного
  имени содержат символ '.'.
 ******************************************************************************/

/***************************************************************************//**
  @addtogroup lip_mdns
  @{
  @file lip_mdns.h
  @brief Файл содержит определения функций и макросов для модуля @ref lip_mdns,
    которые могут быть использованы в приложении.

  @author Borisov Alexey <borisov@lcard.ru>
  @}
 ******************************************************************************/
 
#ifndef LIP_MDNS_H_
#define LIP_MDNS_H_

#include "../lip_dns_defs.h"

/** @addtogroup lip_conf_mdns
 *  @{ */
#ifndef LIP_MDNS_HOSTNAME_TTL
    /** Опционально может быть переопределено в файле конфигурации.
        Время жизни записей, связанных с именем хоста (A,AAAA, HINFO, SRV).
        */
    #define LIP_MDNS_HOSTNAME_TTL  120
#endif

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

/** @} */

/** @addtogroup lip_mdns
 *  @{ */

/** Флаги, которые используются при добавлении записей в функции lip_mdns_rr_add() */
typedef enum {
    /** Признак, что данная запись является уникальной и необходима
        проверка конфликта */
    LIP_MDNS_ADDFLAGS_UNIQUE              = 0x01,
    /** Признак, что при добавлении данной записи, если были записи такого же типа,
    то их нужно удалить, а не добавлять дополнительную */
    LIP_MDNS_ADDFLAGS_REMOVE_PREVIOUS     = 0x02,
} t_lip_mdns_addflags;

/** Флаги, которые используются при удалении записей в функции lip_mdns_rr_remove() */
typedef enum {
    /** По-умолчанию, если запись удаляется, то она может сохраняться еще
        в модуле, чтобы послать позже извещение о ее удалении. Отложенное извещение
        позволяет использовать один пакет для передачи извещений на несколько
        записей. Данный флаг указывает, что необходимо послать пакет немедленно
        и данные удаляемой записи уже не должны использоваться. Может быть нужен
        например, если данные записи после вызова lip_mdns_rr_remove() будут
        немедленно изменены и на них ссылаться уже нельзя */
    LIP_MDNS_REMFLAGS_FLUSH               = 0x01
} t_lip_mdns_remflags;







/***************************************************************************//**
  @brief callback-функция на возникновение конфликта имен

  Callback-функция, вызываемая модулем при возникновении конфликта имени
    mDNS (если такое имя в локальной сети уже существует). Необходимо
    сгенерировать и вернуть новое имя для записи
  @param[in]  name        Указатель на имя mDNS, для которого произошел конфликт
  @param[out] new_name    Указатель на новое имя (если в поле name остается
                          равным NULL, то записи, соответствующие этому имени,
                          удаляются)
  @param[in] conflict_cnt Количество конфликтов имен, возникшее для данного узла
                          с момента его создания.
  *****************************************************************************/
typedef void (*t_lip_mdns_name_conflict_cb)(const t_lip_dns_name *name, t_lip_dns_name *new_name,
                                            unsigned conflict_cnt);


/** @brief Установить имя, под которым будет отображаться устройство в сети   */
t_lip_errs lip_mdns_set_hostname(const char *name, t_lip_mdns_name_conflict_cb cb);

/** @brief Получить доменное имя устройства в локальной сети */
const t_lip_dns_name *lip_mdns_get_host_domain(void);

/** @brief Получить доменное имя родительского узла для mDNS */
const t_lip_dns_name *lip_mdns_get_parent_domain(void);

/** @brief Добавление записи (RR) */
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);

/** @brief Удаление записи (RR) */
t_lip_errs lip_mdns_rr_remove(const t_lip_dns_name *domain, uint16_t rtype,
                              const void *rdata, unsigned flags);



/** @brief Пометить записи для повторного объявления */
t_lip_errs lip_mdns_rr_reannonce(const t_lip_dns_name *domain, uint16_t rtype,
                                 const void *rdata, int rdlength);


#endif /* LIP_MDNS_H_ */

/** @}*/
