diff --git a/doc/mdns.txt b/doc/mdns.txt index 12ac04b1..1ff64fed 100644 --- a/doc/mdns.txt +++ b/doc/mdns.txt @@ -92,9 +92,9 @@ If this call returns successfully, the following queries will be answered: LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return); } - Since a hostname struct is used for TXT storage each single item can be max - 63 bytes long, and the total max length (including length bytes for each - item) is 255 bytes. + Each item is encoded as a length byte followed by the data, so each single + item can be max 255 bytes long, and the total max length (including length + bytes for each item) is defined by MDNS_TXT_RDATA_SIZE (default 255). If your device runs a webserver on port 80, an example call might be: diff --git a/src/apps/mdns/mdns.c b/src/apps/mdns/mdns.c index 84f267a6..054467c5 100644 --- a/src/apps/mdns/mdns.c +++ b/src/apps/mdns/mdns.c @@ -1272,7 +1272,7 @@ mdns_parse_pkt_known_answers(struct netif *netif, struct mdns_packet *pkt, } else if (match & REPLY_SERVICE_TXT) { mdns_prepare_txtdata(service); if (service->txtdata.length == ans.rd_length && - pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) { + pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.rdata, ans.rd_length) == 0) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: TXT\n")); reply->serv_replies[i] &= ~REPLY_SERVICE_TXT; } @@ -2050,7 +2050,7 @@ mdns_handle_response(struct mdns_packet *pkt, struct netif *netif) } else if (ans.info.type == DNS_RRTYPE_TXT) { mdns_prepare_txtdata(service); if (service->txtdata.length == ans.rd_length && - pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) { + pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.rdata, ans.rd_length) == 0) { LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own TXT record -> no conflict\n")); conflict = 0; } @@ -2613,10 +2613,29 @@ mdns_resp_rename_service(struct netif *netif, u8_t slot, const char *name) return ERR_OK; } +/* Adds an RFC 1035 character-string to TXT RDATA. */ +static err_t +mdns_txt_add_charstr(struct mdns_txtdata *txtdata, const char *value, u8_t len) +{ + if (1 + len + txtdata->length > MDNS_TXT_RDATA_SIZE) { + LWIP_DEBUGF(MDNS_DEBUG, ("mdns_txt_add_charstr: adding string would exceed buffer (1+%d+%d > %d). Consider increasing MDNS_TXT_RDATA_SIZE.\n", + len, txtdata->length, MDNS_TXT_RDATA_SIZE)); + return ERR_MEM; + } + txtdata->rdata[txtdata->length] = len; + txtdata->length++; + if (len) { + MEMCPY(&txtdata->rdata[txtdata->length], value, len); + txtdata->length += len; + } + return ERR_OK; +} + /** * @ingroup mdns * Call this function from inside the service_get_txt_fn_t callback to add text data. - * Buffer for TXT data is 256 bytes, and each field is prefixed with a length byte. + * Buffer for TXT data is MDNS_TXT_RDATA_SIZE (default 256) bytes, and each + * field is prefixed with a length byte. * @param service The service provided to the get_txt callback * @param txt String to add to the TXT field. * @param txt_len Length of string @@ -2629,7 +2648,7 @@ mdns_resp_add_service_txtitem(struct mdns_service *service, const char *txt, u8_ LWIP_ASSERT("mdns_resp_add_service_txtitem: service != NULL", service); /* Use a mdns_domain struct to store txt chunks since it is the same encoding */ - return mdns_domain_add_label(&service->txtdata, txt, txt_len); + return mdns_txt_add_charstr(&service->txtdata, txt, txt_len); } #if LWIP_MDNS_SEARCH diff --git a/src/apps/mdns/mdns_out.c b/src/apps/mdns/mdns_out.c index 5c6d26b7..7dfbcba2 100644 --- a/src/apps/mdns/mdns_out.c +++ b/src/apps/mdns/mdns_out.c @@ -62,7 +62,7 @@ static void mdns_clear_outmsg(struct mdns_outmsg *outmsg); void mdns_prepare_txtdata(struct mdns_service *service) { - memset(&service->txtdata, 0, sizeof(struct mdns_domain)); + memset(&service->txtdata, 0, sizeof(struct mdns_txtdata)); if (service->txt_fn) { service->txt_fn(service, service->txt_userdata); } @@ -508,7 +508,7 @@ mdns_add_txt_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg, } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with TXT record\n")); return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_TXT, DNS_RRCLASS_IN, - msg->cache_flush, ttl, (u8_t *) &service->txtdata.name, + msg->cache_flush, ttl, service->txtdata.rdata, service->txtdata.length, NULL); } diff --git a/src/include/lwip/apps/mdns_opts.h b/src/include/lwip/apps/mdns_opts.h index 1eee3e38..7a0ef4eb 100644 --- a/src/include/lwip/apps/mdns_opts.h +++ b/src/include/lwip/apps/mdns_opts.h @@ -88,6 +88,12 @@ #define MDNS_OUTPUT_PACKET_SIZE ((MDNS_MAX_SERVICES == 1) ? 512 : 1450) #endif +/** The maximum size of TXT RDATA allocated for each service. + */ +#ifndef MDNS_TXT_RDATA_SIZE +# define MDNS_TXT_RDATA_SIZE 256 +#endif + /** MDNS_RESP_USENETIF_EXTCALLBACK==1: register an ext_callback on the netif * to automatically restart probing/announcing on status or address change. */ diff --git a/src/include/lwip/apps/mdns_priv.h b/src/include/lwip/apps/mdns_priv.h index 5209ba00..8a08e681 100644 --- a/src/include/lwip/apps/mdns_priv.h +++ b/src/include/lwip/apps/mdns_priv.h @@ -90,10 +90,16 @@ struct mdns_request { }; #endif +/** TXT record data */ +struct mdns_txtdata { + u8_t rdata[MDNS_TXT_RDATA_SIZE]; + u16_t length; +}; + /** Description of a service */ struct mdns_service { /** TXT record to answer with */ - struct mdns_domain txtdata; + struct mdns_txtdata txtdata; /** Name of service, like 'myweb' */ char name[MDNS_LABEL_MAXLEN + 1]; /** Type of service, like '_http' */ diff --git a/test/unit/mdns/test_mdns.c b/test/unit/mdns/test_mdns.c index 01434b5d..bffb1830 100644 --- a/test/unit/mdns/test_mdns.c +++ b/test/unit/mdns/test_mdns.c @@ -35,6 +35,7 @@ #include "lwip/pbuf.h" #include "lwip/apps/mdns.h" #include "lwip/apps/mdns_domain.h" +#include "lwip/apps/mdns_out.h" #include "lwip/apps/mdns_priv.h" START_TEST(readname_basic) @@ -876,6 +877,155 @@ START_TEST(compress_long_match) } END_TEST +#define TXT_STRING_1 "path=/" +#define TXT_LENGTH_1 6 +#define TXT_LENSTR_1 "\006" + +#define TXT_STRING_2 "" +#define TXT_LENGTH_2 0 +#define TXT_LENSTR_2 "\000" + +#define TXT_STRING_3 "This sentence is sixty-three bytes long, including punctuation." +#define TXT_LENGTH_3 63 +#define TXT_LENSTR_3 "\077" + +#define TXT_STRING_4 "This tests whether mdns_resp_add_service_txtitem can properly handle strings longer than 63 characters." +#define TXT_LENGTH_4 103 +#define TXT_LENSTR_4 "\147" + +START_TEST(txt_short_item) +{ + const char *expected_txtdata = TXT_LENSTR_1 TXT_STRING_1; + const size_t expected_txtdata_length = 1 + TXT_LENGTH_1; + + struct mdns_service service; + err_t res; + memset(&service, 0, sizeof(struct mdns_service)); + + mdns_prepare_txtdata(&service); + res = mdns_resp_add_service_txtitem(&service, TXT_STRING_1, TXT_LENGTH_1); + fail_unless(res == ERR_OK); + + fail_unless(service.txtdata.length == expected_txtdata_length); + fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length)); +} +END_TEST + +START_TEST(txt_empty_item) +{ + const char *expected_txtdata = TXT_LENSTR_2 TXT_STRING_2; + const size_t expected_txtdata_length = 1 + TXT_LENGTH_2; + + struct mdns_service service; + err_t res; + memset(&service, 0, sizeof(struct mdns_service)); + + mdns_prepare_txtdata(&service); + res = mdns_resp_add_service_txtitem(&service, TXT_STRING_2, TXT_LENGTH_2); + fail_unless(res == ERR_OK); + + fail_unless(service.txtdata.length == expected_txtdata_length); + fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length)); +} +END_TEST + +START_TEST(txt_long_item) +{ + const char *expected_txtdata = TXT_LENSTR_4 TXT_STRING_4; + const size_t expected_txtdata_length = 1 + TXT_LENGTH_4; + + struct mdns_service service; + err_t res; + memset(&service, 0, sizeof(struct mdns_service)); + + mdns_prepare_txtdata(&service); + res = mdns_resp_add_service_txtitem(&service, TXT_STRING_4, TXT_LENGTH_4); + fail_unless(res == ERR_OK); + + fail_unless(service.txtdata.length == expected_txtdata_length); + fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length)); +} +END_TEST + +START_TEST(txt_multiple_items) +{ + const char *expected_txtdata = ( + TXT_LENSTR_1 + TXT_STRING_1 + TXT_LENSTR_2 + TXT_STRING_2 + TXT_LENSTR_3 + TXT_STRING_3 + TXT_LENSTR_4 + TXT_STRING_4 + ); + const size_t expected_txtdata_length = ( + 1 + TXT_LENGTH_1 + + 1 + TXT_LENGTH_2 + + 1 + TXT_LENGTH_3 + + 1 + TXT_LENGTH_4 + ); + + struct mdns_service service; + err_t res; + memset(&service, 0, sizeof(struct mdns_service)); + + mdns_prepare_txtdata(&service); + + res = mdns_resp_add_service_txtitem(&service, TXT_STRING_1, TXT_LENGTH_1); + fail_unless(res == ERR_OK); /* TXT_STRING_1 */ + + res = mdns_resp_add_service_txtitem(&service, TXT_STRING_2, TXT_LENGTH_2); + fail_unless(res == ERR_OK); /* TXT_STRING_1 */ + + res = mdns_resp_add_service_txtitem(&service, TXT_STRING_3, TXT_LENGTH_3); + fail_unless(res == ERR_OK); /* TXT_STRING_3 */ + + res = mdns_resp_add_service_txtitem(&service, TXT_STRING_4, TXT_LENGTH_4); + fail_unless(res == ERR_OK); /* TXT_STRING_4 */ + + fail_unless(service.txtdata.length == expected_txtdata_length); + fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length)); +} +END_TEST + +START_TEST(txt_buffer_full) +{ + const char *expected_txtdata = ( + TXT_LENSTR_3 TXT_STRING_3 + TXT_LENSTR_3 TXT_STRING_3 + TXT_LENSTR_3 TXT_STRING_3 + TXT_LENSTR_3 TXT_STRING_3 + ); + const size_t expected_txtdata_length = 256; + + struct mdns_service service; + err_t res; + int i; + memset(&service, 0, sizeof(struct mdns_service)); + + mdns_prepare_txtdata(&service); + + /* add a 64-byte string 4 times = 256 bytes */ + for (i = 0; i < 4; i++) { + res = mdns_resp_add_service_txtitem(&service, TXT_STRING_3, TXT_LENGTH_3); + ck_assert_msg(res == ERR_OK, + "adding text item failed with error %d (i=%d, txtdata.length=%d)", + res, i, service.txtdata.length); + } + + /* Try to add a few more strings while the buffer is full. This should fail. */ + res = mdns_resp_add_service_txtitem(&service, "", 0); + fail_unless(res != ERR_OK); /* empty string */ + + res = mdns_resp_add_service_txtitem(&service, "path=/", 6); + fail_unless(res != ERR_OK); /* short string */ + + fail_unless(service.txtdata.length == expected_txtdata_length); + fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length)); +} +END_TEST + Suite* mdns_suite(void) { testfunc tests[] = { @@ -911,6 +1061,12 @@ Suite* mdns_suite(void) TESTFUNC(compress_2nd_label_short), TESTFUNC(compress_jump_to_jump), TESTFUNC(compress_long_match), + + TESTFUNC(txt_short_item), + TESTFUNC(txt_empty_item), + TESTFUNC(txt_long_item), + TESTFUNC(txt_multiple_items), + TESTFUNC(txt_buffer_full), }; return create_suite("MDNS", tests, sizeof(tests)/sizeof(testfunc), NULL, NULL); }