diff --git a/README.md b/README.md index f6bb40a..49c6878 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Current status is alpha quality, actively developed. AP STATION mode (ie wifi cl ## Open Source Components * [FreeRTOS](http://www.freertos.org/) V10.2.0 -* [lwIP](http://lwip.wikia.com/wiki/LwIP_Wiki) v2.0.3, with [some modifications](https://github.com/ourairquality/lwip/). +* [lwIP](http://lwip.wikia.com/wiki/LwIP_Wiki) v2.2.0, with [some modifications](https://github.com/ourairquality/lwip/). * [newlib](https://github.com/ourairquality/newlib) v3.0.0, with patches for xtensa support and locking stubs for thread-safe operation on FreeRTOS. For details of how third party libraries are integrated, [see the wiki page](https://github.com/SuperHouse/esp-open-rtos/wiki/Third-Party-Libraries). diff --git a/examples/access_point/access_point.c b/examples/access_point/access_point.c index 20e9d88..a340020 100644 --- a/examples/access_point/access_point.c +++ b/examples/access_point/access_point.c @@ -48,7 +48,7 @@ void user_init(void) */ static void telnetTask(void *pvParameters) { - ip_addr_t first_client_ip; + ip4_addr_t first_client_ip; IP4_ADDR(&first_client_ip, 172, 16, 0, 2); dhcpserver_start(&first_client_ip, 4); diff --git a/examples/esphttpd/user_main.c b/examples/esphttpd/user_main.c index a532fb6..a2c17aa 100644 --- a/examples/esphttpd/user_main.c +++ b/examples/esphttpd/user_main.c @@ -180,7 +180,7 @@ void wifiInit() { }; sdk_wifi_softap_set_config(&ap_config); - ip_addr_t first_client_ip; + ip4_addr_t first_client_ip; IP4_ADDR(&first_client_ip, 172, 16, 0, 2); dhcpserver_start(&first_client_ip, 4); dhcpserver_set_dns(&ap_ip.ip); diff --git a/examples/mdns-search/.gitignore b/examples/mdns-search/.gitignore new file mode 100644 index 0000000..e067149 --- /dev/null +++ b/examples/mdns-search/.gitignore @@ -0,0 +1 @@ +!local.mk diff --git a/examples/mdns-search/FreeRTOSConfig.h b/examples/mdns-search/FreeRTOSConfig.h new file mode 100644 index 0000000..baf25a4 --- /dev/null +++ b/examples/mdns-search/FreeRTOSConfig.h @@ -0,0 +1,7 @@ +#define configUSE_TRACE_FACILITY 1 +#define configGENERATE_RUN_TIME_STATS 1 +#define portGET_RUN_TIME_COUNTER_VALUE() (RTC.COUNTER) +#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() {} + +/* Use the defaults for everything else */ +#include_next diff --git a/examples/mdns-search/Makefile b/examples/mdns-search/Makefile new file mode 100644 index 0000000..7aeb77a --- /dev/null +++ b/examples/mdns-search/Makefile @@ -0,0 +1,11 @@ +# Makefile for mDNS search example +PROGRAM=mdns-search +EXTRA_COMPONENTS=extras/dhcpserver extras/wificfg + +# For the mDNS responder included with lwip: +EXTRA_CFLAGS += -DLWIP_MDNS_RESPONDER=1 -DLWIP_NUM_NETIF_CLIENT_DATA=1 -DLWIP_NETIF_EXT_STATUS_CALLBACK=1 -DLWIP_MDNS_SEARCH=1 + +# Avoid writing the wifi state to flash when using wificfg. +EXTRA_CFLAGS += -DWIFI_PARAM_SAVE=0 + +include ../../common.mk diff --git a/examples/mdns-search/local.mk b/examples/mdns-search/local.mk new file mode 100644 index 0000000..c6c3129 --- /dev/null +++ b/examples/mdns-search/local.mk @@ -0,0 +1 @@ +FLASH_SIZE ?= 32 diff --git a/examples/mdns-search/mdsn-search.c b/examples/mdns-search/mdsn-search.c new file mode 100644 index 0000000..d5f92fc --- /dev/null +++ b/examples/mdns-search/mdsn-search.c @@ -0,0 +1,160 @@ +/* + * Example mDNS service search. + * + * Copyright (C) 2019 OurAirQuality.org + * + * Licensed under the Apache License, Version 2.0, January 2004 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/ + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS WITH THE SOFTWARE. + * + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "lwip/sockets.h" +#include "lwip/apps/mdns.h" +#include "lwip/prot/dns.h" +#include "lwip/ip6.h" +#include "lwip/ip6_addr.h" + +#include "wificfg/wificfg.h" + +#if !LWIP_MDNS_SEARCH +#error "The LWIP_MDNS_SEARCH feature must be set." +#endif + +#define SEARCH_SERVICE "_http" +#define SEARCH_PROTO DNSSD_PROTO_TCP + +static void print_domain(const u8_t *src) +{ + u8_t i; + + while (*src) { + u8_t label_len = *src; + src++; + for (i = 0; i < label_len; i++) { + putchar(src[i]); + } + src += label_len; + printf("."); + } +} + +void result_fn(struct mdns_answer *answer, const char *varpart, + int varlen, int flags, void *arg) +{ + size_t i; + + printf("Domain "); + print_domain(answer->info.domain->name); + printf(" "); + + switch (answer->info.type) { + case DNS_RRTYPE_PTR: + printf("PTR "); + print_domain((u8_t *)varpart); + printf("\n"); + break; + + case DNS_RRTYPE_SRV: + /* Priority, weight, port fields. */ + printf("SRV "); + print_domain((u8_t *)varpart + 6); + printf(" port %u\n", ((u16_t)varpart[4] << 8) | varpart[5]); + break; + + case DNS_RRTYPE_TXT: + printf("TXT \"%s\"\n", varpart); + break; + + case DNS_RRTYPE_A: + if (varlen == sizeof(ip4_addr_t)) { + ip4_addr_t addr; + char buf[16]; + char *p; + SMEMCPY(&addr, varpart, sizeof(ip4_addr_t)); + p = ip4addr_ntoa_r(&addr, buf, sizeof(buf)); + if (p) printf("A %s\n", p); + } + break; + +#if LWIP_IPV6 + case DNS_RRTYPE_AAAA: + if (varlen == sizeof(ip6_addr_p_t)) { + ip6_addr_p_t addrp; + ip6_addr_t addr; + char buf[64]; + char *p; + SMEMCPY(addrp.addr, varpart, sizeof(ip6_addr_p_t)); + ip6_addr_copy_from_packed(addr, addrp); + p = ip6addr_ntoa_r(&addr, buf, sizeof(buf)); + if (p) printf("AAAA %s\n", p); + } + break; +#endif + + default: + printf("Unexpected type %u class %u, ans %p, part %p, len %d, flags %x, arg %p\n", answer->info.type, answer->info.klass, answer, varpart, varlen, flags, arg); + for (i = 0; i < varlen; i++) + printf(" %02x", varpart[i]); + printf("\n"); + } +} + +static void mdns_search_task(void *pvParameters) +{ + err_t err; + u8_t request_id; + struct netif *station_netif = sdk_system_get_netif(STATION_IF); + + if (station_netif == NULL) { + vTaskDelete(NULL); + } + + for (;;) { + printf("Starting mDNS search for %s : ", SEARCH_SERVICE); + LOCK_TCPIP_CORE(); + err = mdns_search_service(NULL, SEARCH_SERVICE, + SEARCH_PROTO, + station_netif, + result_fn, NULL, + &request_id); + UNLOCK_TCPIP_CORE(); + printf("request_id %d, error %d\n", request_id, err); + + vTaskDelay(10000 / portTICK_PERIOD_MS); + printf("Stopping mDNS search\n"); + + LOCK_TCPIP_CORE(); + mdns_search_stop(request_id); + UNLOCK_TCPIP_CORE(); + } +} + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("SDK version:%s\n", sdk_system_get_sdk_version()); + + wificfg_init(80, NULL); + + xTaskCreate(&mdns_search_task, "mDNS search", 384, NULL, 1, NULL); +} diff --git a/examples/tcp_non_blocking/tcp_non_blocking.c b/examples/tcp_non_blocking/tcp_non_blocking.c index 4e35aa2..e8ddd93 100644 --- a/examples/tcp_non_blocking/tcp_non_blocking.c +++ b/examples/tcp_non_blocking/tcp_non_blocking.c @@ -116,7 +116,7 @@ static void nonBlockingTCP(void *pvParameters) struct netbuf *netbuf = NULL; // To store incoming Data struct netconn *nc_in = NULL; // To accept incoming netconn // - char buf[50]; + char buf[64]; char* buffer; uint16_t len_buf; @@ -138,10 +138,9 @@ static void nonBlockingTCP(void *pvParameters) ip_addr_t client_addr; //Address port uint16_t client_port; //Client port netconn_peer(nc_in, &client_addr, &client_port); - snprintf(buf, sizeof(buf), "Your address is %d.%d.%d.%d:%u.\r\n", - ip4_addr1(&client_addr), ip4_addr2(&client_addr), - ip4_addr3(&client_addr), ip4_addr4(&client_addr), - client_port); + char ip_addr_buf[48]; + ipaddr_ntoa_r(&client_addr, ip_addr_buf, sizeof(ip_addr_buf)); + snprintf(buf, sizeof(buf), "Your address is %s:%u.\r\n", ip_addr_buf, client_port); netconn_write(nc_in, buf, strlen(buf), NETCONN_COPY); } else if(events.nc->state != NETCONN_LISTEN) // If netconn is the client and receive data @@ -191,7 +190,7 @@ void user_init(void) }; sdk_wifi_softap_set_config(&ap_config); - ip_addr_t first_client_ip; + ip4_addr_t first_client_ip; IP4_ADDR(&first_client_ip, 172, 16, 0, 2); dhcpserver_start(&first_client_ip, 4); printf("DHCP started\n"); diff --git a/extras/wificfg/wificfg.c b/extras/wificfg/wificfg.c index 1e4c5d2..2d507a0 100644 --- a/extras/wificfg/wificfg.c +++ b/extras/wificfg/wificfg.c @@ -1692,6 +1692,14 @@ static bool host_is_name(const char *host) } +#if LWIP_MDNS_RESPONDER +static void mdns_srv_txt(struct mdns_service *service, void *txt_userdata) +{ + err_t res = mdns_resp_add_service_txtitem(service, "path=/", 6); + LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return); +} +#endif + /* * The http server uses a single thread to service all requests, one request at * a time, to keep peak resource usage to a minimum. Keeping connections open @@ -1745,16 +1753,16 @@ static void server_task(void *pvParameters) LOCK_TCPIP_CORE(); if (wifi_sta_mdns && station_netif) { LOCK_TCPIP_CORE(); - mdns_resp_add_netif(station_netif, hostname, 120); + mdns_resp_add_netif(station_netif, hostname); mdns_resp_add_service(station_netif, hostname, "_http", - DNSSD_PROTO_TCP, 80, 3600, NULL, NULL); + DNSSD_PROTO_TCP, 80, mdns_srv_txt, NULL); UNLOCK_TCPIP_CORE(); } if (wifi_ap_mdns && softap_netif) { LOCK_TCPIP_CORE(); - mdns_resp_add_netif(softap_netif, hostname, 120); + mdns_resp_add_netif(softap_netif, hostname); mdns_resp_add_service(softap_netif, hostname, "_http", - DNSSD_PROTO_TCP, 80, 3600, NULL, NULL); + DNSSD_PROTO_TCP, 80, mdns_srv_txt, NULL); UNLOCK_TCPIP_CORE(); } UNLOCK_TCPIP_CORE(); diff --git a/lwip/esp_interface.c b/lwip/esp_interface.c index 77ad177..2517220 100644 --- a/lwip/esp_interface.c +++ b/lwip/esp_interface.c @@ -81,6 +81,10 @@ low_level_init(struct netif *netif) /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6; +#if LWIP_ACD + netif->acd_list = NULL; +#endif /* LWIP_ACD */ + /* Do whatever else is needed to initialize interface. */ } @@ -384,7 +388,7 @@ ethernetif_init(struct netif *netif) netif->name[0] = IFNAME0; netif->name[1] = IFNAME1; /* We directly use etharp_output() here to save a function call. - * You can instead declare your own function an call etharp_output() + * You can instead declare your own function and call etharp_output() * from it if you have to do some checks before sending (e.g. if link * is available...) */ #if LWIP_IPV4 diff --git a/lwip/include/lwipopts.h b/lwip/include/lwipopts.h index 57c6f05..084acaa 100644 --- a/lwip/include/lwipopts.h +++ b/lwip/include/lwipopts.h @@ -258,6 +258,8 @@ void sys_unlock_tcpip_core(void); * Since the received pbufs are enqueued, be sure to configure * PBUF_POOL_SIZE > IP_REASS_MAX_PBUFS so that the stack is still able to receive * packets even if the maximum amount of fragments is enqueued for reassembly! + * When IPv4 *and* IPv6 are enabled, this even changes to + * (PBUF_POOL_SIZE > 2 * IP_REASS_MAX_PBUFS)! */ #ifndef IP_REASS_MAX_PBUFS #define IP_REASS_MAX_PBUFS 2 @@ -290,17 +292,82 @@ void sys_unlock_tcpip_core(void); /** * DHCP_DOES_ARP_CHECK==1: Do an ARP check on the offered address. */ -#ifndef DHCP_DOES_ARP_CHECK -#define DHCP_DOES_ARP_CHECK ((LWIP_DHCP) && (LWIP_ARP)) +#ifndef LWIP_DHCP_DOES_ACD_CHECK +#define LWIP_DHCP_DOES_ACD_CHECK LWIP_DHCP #endif #define LWIP_DHCP_BOOTP_FILE 0 +/** + * LWIP_DHCP_GETS_NTP==1: Request NTP servers with discover/select. For each + * response packet, an callback is called, which has to be provided by the port: + * void dhcp_set_ntp_servers(u8_t num_ntp_servers, ip_addr_t* ntp_server_addrs); +*/ +#ifndef LWIP_DHCP_GET_NTP_SRV +#define LWIP_DHCP_GET_NTP_SRV 0 +#endif + +/** + * The maximum of NTP servers requested + */ +#ifndef LWIP_DHCP_MAX_NTP_SERVERS +#define LWIP_DHCP_MAX_NTP_SERVERS 1 +#endif + +/** + * LWIP_DHCP_MAX_DNS_SERVERS > 0: Request DNS servers with discover/select. + * DNS servers received in the response are passed to DNS via @ref dns_setserver() + * (up to the maximum limit defined here). + */ +#ifndef LWIP_DHCP_MAX_DNS_SERVERS +#define LWIP_DHCP_MAX_DNS_SERVERS DNS_MAX_SERVERS +#endif + /* ------------------------------------ ---------- AUTOIP options ---------- ------------------------------------ */ + +/** + * LWIP_AUTOIP==1: Enable AUTOIP module. + */ +#ifndef LWIP_AUTOIP +#define LWIP_AUTOIP 0 +#endif + +/** + * LWIP_DHCP_AUTOIP_COOP==1: Allow DHCP and AUTOIP to be both enabled on + * the same interface at the same time. + */ +#ifndef LWIP_DHCP_AUTOIP_COOP +#define LWIP_DHCP_AUTOIP_COOP 0 +#endif + +/** + * LWIP_DHCP_AUTOIP_COOP_TRIES: Set to the number of DHCP DISCOVER probes + * that should be sent before falling back on AUTOIP (the DHCP client keeps + * running in this case). This can be set as low as 1 to get an AutoIP address + * very quickly, but you should be prepared to handle a changing IP address + * when DHCP overrides AutoIP. + */ +#ifndef LWIP_DHCP_AUTOIP_COOP_TRIES +#define LWIP_DHCP_AUTOIP_COOP_TRIES 9 +#endif + +/* + ------------------------------------ + ----------- ACD options ------------ + ------------------------------------ +*/ + +/** + * LWIP_ACD==1: Enable ACD module. ACD module is needed when using AUTOIP. + */ +#ifndef LWIP_ACD +#define LWIP_ACD (LWIP_AUTOIP || LWIP_DHCP_DOES_ACD_CHECK) +#endif + /* ---------------------------------- ----- SNMP MIB2 support ----- @@ -514,6 +581,12 @@ void sys_unlock_tcpip_core(void); */ #define PBUF_LINK_ENCAPSULATION_HLEN 36 +/** + * LWIP_PBUF_CUSTOM_DATA: Store private data on pbufs (e.g. timestamps) + * This extends struct pbuf so user can store custom data on every pbuf. + */ +#define LWIP_PBUF_CUSTOM_DATA void *esf_buf; + /* ------------------------------------------------ ---------- Network Interfaces options ---------- @@ -642,6 +715,26 @@ void sys_unlock_tcpip_core(void); #define LWIP_SOCKET_OFFSET 3 #endif +/** + * LWIP_SOCKET_EXTERNAL_HEADERS==1: Use external headers instead of sockets.h + * and inet.h. In this case, user must provide its own headers by setting the + * values for LWIP_SOCKET_EXTERNAL_HEADER_SOCKETS_H and + * LWIP_SOCKET_EXTERNAL_HEADER_INET_H to appropriate include file names and the + * whole content of the default sockets.h and inet.h is skipped. + */ +#ifndef LWIP_SOCKET_EXTERNAL_HEADERS +#define LWIP_SOCKET_EXTERNAL_HEADERS 0 +#endif + +/** + * LWIP_TCP_KEEPALIVE==1: Enable TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT + * options processing. Note that TCP_KEEPIDLE and TCP_KEEPINTVL have to be set + * in seconds. (does not require sockets.c, and will affect tcp.c) + */ +#ifndef LWIP_TCP_KEEPALIVE +#define LWIP_TCP_KEEPALIVE 1 +#endif + /** * LWIP_SO_SNDTIMEO==1: Enable send timeout for sockets/netconns and * SO_SNDTIMEO processing. @@ -658,15 +751,6 @@ void sys_unlock_tcpip_core(void); #define LWIP_SO_RCVTIMEO 1 #endif -/** - * LWIP_TCP_KEEPALIVE==1: Enable TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT - * options processing. Note that TCP_KEEPIDLE and TCP_KEEPINTVL have to be set - * in seconds. (does not require sockets.c, and will affect tcp.c) - */ -#ifndef LWIP_TCP_KEEPALIVE -#define LWIP_TCP_KEEPALIVE 1 -#endif - /** * LWIP_SO_RCVBUF==1: Enable SO_RCVBUF processing. */ @@ -674,6 +758,27 @@ void sys_unlock_tcpip_core(void); #define LWIP_SO_RCVBUF 0 #endif +/** + * LWIP_SO_LINGER==1: Enable SO_LINGER processing. + */ +#ifndef LWIP_SO_LINGER +#define LWIP_SO_LINGER 0 +#endif + +/** + * If LWIP_SO_RCVBUF is used, this is the default value for recv_bufsize. + */ +#ifndef RECV_BUFSIZE_DEFAULT +#define RECV_BUFSIZE_DEFAULT INT_MAX +#endif + +/** + * By default, TCP socket/netconn close waits 20 seconds max to send the FIN + */ +#ifndef LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT +#define LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT 20000 +#endif + /** * SO_REUSE==1: Enable SO_REUSEADDR option. */ @@ -681,6 +786,45 @@ void sys_unlock_tcpip_core(void); #define SO_REUSE 1 #endif +/** + * SO_REUSE_RXTOALL==1: Pass a copy of incoming broadcast/multicast packets + * to all local matches if SO_REUSEADDR is turned on. + * WARNING: Adds a memcpy for every packet if passing to more than one pcb! + */ +#ifndef SO_REUSE_RXTOALL +#define SO_REUSE_RXTOALL 0 +#endif + +/** + * LWIP_FIONREAD_LINUXMODE==0 (default): ioctl/FIONREAD returns the amount of + * pending data in the network buffer. This is the way windows does it. It's + * the default for lwIP since it is smaller. + * LWIP_FIONREAD_LINUXMODE==1: ioctl/FIONREAD returns the size of the next + * pending datagram in bytes. This is the way linux does it. This code is only + * here for compatibility. + */ +#ifndef LWIP_FIONREAD_LINUXMODE +#define LWIP_FIONREAD_LINUXMODE 0 +#endif + +/** + * LWIP_SOCKET_SELECT==1 (default): enable select() for sockets (uses a netconn + * callback to keep track of events). + * This saves RAM (counters per socket) and code (netconn event callback), which + * should improve performance a bit). + */ +#ifndef LWIP_SOCKET_SELECT +#define LWIP_SOCKET_SELECT 1 +#endif + +/** + * LWIP_SOCKET_POLL==1 (default): enable poll() for sockets (including + * struct pollfd, nfds_t, and constants) + */ +#ifndef LWIP_SOCKET_POLL +#define LWIP_SOCKET_POLL 1 +#endif + /* ---------------------------------------- ---------- Statistics options ---------- @@ -725,6 +869,21 @@ void sys_unlock_tcpip_core(void); #define LWIP_IPV6 0 #endif +/** + * LWIP_ND6_QUEUEING==1: queue outgoing IPv6 packets while MAC address + * is being resolved. + */ +#ifndef LWIP_ND6_QUEUEING +#define LWIP_ND6_QUEUEING LWIP_IPV6 +#endif + +/** + * MEMP_NUM_ND6_QUEUE: Max number of IPv6 packets to queue during MAC resolution. + */ +#ifndef MEMP_NUM_ND6_QUEUE +#define MEMP_NUM_ND6_QUEUE 5 +#endif + /* --------------------------------------- ---------- Hook options --------------- @@ -737,14 +896,6 @@ void sys_unlock_tcpip_core(void); --------------------------------------- */ -/** - * LWIP_MDNS_RESPONDER_QUEUE_ANNOUNCEMENTS==1: Unsolicited announcements are - * queued and run from a timer callback. - */ -#ifndef LWIP_MDNS_RESPONDER_QUEUE_ANNOUNCEMENTS -#define LWIP_MDNS_RESPONDER_QUEUE_ANNOUNCEMENTS 1 -#endif - /* --------------------------------------- ---------- Debugging options ---------- @@ -918,6 +1069,11 @@ void sys_unlock_tcpip_core(void); */ #define AUTOIP_DEBUG LWIP_DBG_OFF +/** + * ACD_DEBUG: Enable debugging in acd.c. + */ +#define ACD_DEBUG LWIP_DBG_OFF + /** * DNS_DEBUG: Enable debugging for DNS. */ @@ -928,6 +1084,11 @@ void sys_unlock_tcpip_core(void); */ #define IP6_DEBUG LWIP_DBG_OFF +/** + * DHCP6_DEBUG: Enable debugging in dhcp6.c. + */ +#define DHCP6_DEBUG LWIP_DBG_OFF + /** * MDNS_DEBUG: Enable debugging for multicast DNS. */ diff --git a/lwip/lwip b/lwip/lwip index d74e9ad..666a818 160000 --- a/lwip/lwip +++ b/lwip/lwip @@ -1 +1 @@ -Subproject commit d74e9ad2f7c9db996fb398cd41bf59ef463ae6fe +Subproject commit 666a818a5d595db0d2bf42fb6e2992a2c80ef544