diff --git a/components/modules/Kconfig b/components/modules/Kconfig index 3bd7954e..2f3d62a3 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -34,7 +34,7 @@ config LUA_BUILTIN_DEBUG config LUA_BUILTIN_DEBUG_EXTENDED depends on LUA_BUILTIN_DEBUG - bool "Extended debug support + bool "Extended debug support" default "n" help Includes the full debug module, rather than just getregistry and traceback. @@ -46,17 +46,13 @@ depends on LUA_BUILTIN_DEBUG endmenu -config LUA_MODULE_NODE - bool "Node module" - default "y" - help - Includes the node module (recommended). -config LUA_MODULE_FILE - bool "File module" - default "y" + +config LUA_MODULE_BTHCI + bool "BlueTooth HCI interface module" + default "n" help - Includes the file module (recommended). + Includes the simple BlueTooth HCI module. config LUA_MODULE_ENCODER bool "Encoder module" @@ -65,16 +61,28 @@ config LUA_MODULE_ENCODER Includes the encoder module. This provides hex and base64 encoding and decoding functionality. +config LUA_MODULE_FILE + bool "File module" + default "y" + help + Includes the file module (recommended). + +config LUA_MODULE_NET + bool "Net module" + default "y" + help + Includes the net module (recommended). + +config LUA_MODULE_NODE + bool "Node module" + default "y" + help + Includes the node module (recommended). + config LUA_MODULE_WIFI bool "WiFi module" default "y" help Includes the WiFi module (recommended). -config LUA_MODULE_BTHCI - bool "BlueTooth HCI interface module" - default "n" - help - Includes the simple BlueTooth HCI module. - endmenu diff --git a/components/modules/net.c b/components/modules/net.c new file mode 100644 index 00000000..66898214 --- /dev/null +++ b/components/modules/net.c @@ -0,0 +1,1170 @@ +// Module for network + +#include "module.h" +#include "lauxlib.h" +#include "platform.h" +#include "lmem.h" +#include "ip_fmt.h" +#include "task/task.h" + +#include +#include +#include + +#include "lwip/err.h" +#include "lwip/ip_addr.h" +#include "lwip/dns.h" +#include "lwip/igmp.h" +#include "lwip/tcp.h" +#include "lwip/udp.h" + +// Some LWIP macros cause complaints with ptr NULL checks, so shut them off :( +#pragma GCC diagnostic ignored "-Waddress" + +typedef enum net_type { + TYPE_TCP_SERVER = 0, + TYPE_TCP_CLIENT, + TYPE_UDP_SOCKET +} net_type; + +typedef const char net_table_name[14]; + +static const net_table_name NET_TABLES[] = { + "net.tcpserver", + "net.tcpsocket", + "net.udpsocket" +}; +#define NET_TABLE_TCP_SERVER NET_TABLES[0] +#define NET_TABLE_TCP_CLIENT NET_TABLES[1] +#define NET_TABLE_UDP_SOCKET NET_TABLES[2] + +#define TYPE_TCP TYPE_TCP_CLIENT +#define TYPE_UDP TYPE_UDP_SOCKET + +typedef struct lnet_userdata { + enum net_type type; + int self_ref; + union { + struct tcp_pcb *tcp_pcb; + struct udp_pcb *udp_pcb; + void *pcb; + }; + union { + struct { + int cb_accept_ref; + } server; + struct { + int wait_dns; + int cb_dns_ref; + int cb_receive_ref; + int cb_sent_ref; + // Only for TCP: + int hold; + int cb_connect_ref; + int cb_disconnect_ref; + int cb_reconnect_ref; + } client; + }; +} lnet_userdata; + + + +// --- Event handling + +typedef struct { + ip_addr_t src_ip; + uint16_t src_port; + uint16_t payload_len; + char payload[0]; +} lnet_recvdata; + + +typedef struct { + enum { + DNSFOUND, + DNSSTATIC, + CONNECTED, + ACCEPT, + RECVDATA, + SENTDATA, + ERR + } event; + union { + lnet_userdata *ud; + int cb_ref; + }; + union { + struct tcp_pcb *accept_newpcb; + lnet_recvdata recvdata; + ip_addr_t resolved_ip; + int err; + }; +} lnet_event; + +static task_handle_t net_event; + + +// --- LWIP errors + +int lwip_lua_checkerr (lua_State *L, err_t err) { + switch (err) { + case ERR_OK: return 0; + case ERR_MEM: return luaL_error(L, "out of memory"); + case ERR_BUF: return luaL_error(L, "buffer error"); + case ERR_TIMEOUT: return luaL_error(L, "timeout"); + case ERR_RTE: return luaL_error(L, "routing problem"); + case ERR_INPROGRESS: return luaL_error(L, "in progress"); + case ERR_VAL: return luaL_error(L, "illegal value"); + case ERR_WOULDBLOCK: return luaL_error(L, "would block"); + case ERR_ABRT: return luaL_error(L, "connection aborted"); + case ERR_RST: return luaL_error(L, "connection reset"); + case ERR_CLSD: return luaL_error(L, "connection closed"); + case ERR_CONN: return luaL_error(L, "not connected"); + case ERR_ARG: return luaL_error(L, "illegal argument"); + case ERR_USE: return luaL_error(L, "address in use"); + case ERR_IF: return luaL_error(L, "netif error"); + case ERR_ISCONN: return luaL_error(L, "already connected"); + default: return luaL_error(L, "unknown error"); + } +} + +// --- Create + +lnet_userdata *net_create( lua_State *L, enum net_type type ) { + const char *mt = NET_TABLES[type]; + lnet_userdata *ud = (lnet_userdata *)lua_newuserdata(L, sizeof(lnet_userdata)); + if (!ud) return NULL; + luaL_getmetatable(L, mt); + lua_setmetatable(L, -2); + + ud->type = type; + ud->self_ref = LUA_NOREF; + ud->pcb = NULL; + + switch (type) { + case TYPE_TCP_CLIENT: + ud->client.cb_connect_ref = LUA_NOREF; + ud->client.cb_reconnect_ref = LUA_NOREF; + ud->client.cb_disconnect_ref = LUA_NOREF; + ud->client.hold = 0; + case TYPE_UDP_SOCKET: + ud->client.wait_dns = 0; + ud->client.cb_dns_ref = LUA_NOREF; + ud->client.cb_receive_ref = LUA_NOREF; + ud->client.cb_sent_ref = LUA_NOREF; + break; + case TYPE_TCP_SERVER: + ud->server.cb_accept_ref = LUA_NOREF; + break; + } + return ud; +} + +// --- LWIP callbacks and task_post helpers + +static bool post_net_err (lnet_userdata *ud, err_t err) { + lnet_event *ev = (lnet_event *)malloc (sizeof (lnet_event)); + if (!ev) + return false; + ev->event = ERR; + ev->ud = ud; + ev->err = err; + if (!task_post_medium (net_event, (task_param_t)ev)) { + free (ev); + return false; + } + return true; +} + +static void net_err_cb(void *arg, err_t err) { + lnet_userdata *ud = (lnet_userdata*)arg; + if (!ud || ud->type != TYPE_TCP_CLIENT || ud->self_ref == LUA_NOREF) return; + ud->pcb = NULL; // Will be freed at LWIP level + + post_net_err (ud, err); +} + + +static bool post_net_connected (lnet_userdata *ud) { + lnet_event *ev = (lnet_event *)malloc (sizeof (lnet_event)); + if (!ev) + return false; + ev->event = CONNECTED; + ev->ud = ud; + if (!task_post_medium (net_event, (task_param_t)ev)) { + free (ev); + return false; + } + return true; +} + +static err_t net_connected_cb(void *arg, struct tcp_pcb *tpcb, err_t err) { + lnet_userdata *ud = (lnet_userdata*)arg; + if (!ud || ud->pcb != tpcb) return ERR_ABRT; + if (err != ERR_OK) { + net_err_cb(arg, err); + return ERR_ABRT; + } + post_net_connected (ud); + return ERR_OK; +} + + +static bool post_net_dns (lnet_userdata *ud, const char *name, const ip_addr_t *ipaddr) +{ + lnet_event *ev = (lnet_event *)malloc (sizeof (lnet_event)); + if (!ev || !ipaddr) + return false; + ev->event = DNSFOUND; + ev->ud = ud; + ev->resolved_ip = *ipaddr; + if (!task_post_medium (net_event, (task_param_t)ev)) { + free (ev); + return false; + } + return true; +} + +static void net_dns_cb(const char *name, const ip_addr_t *ipaddr, void *arg) { + ip_addr_t addr; + if (ipaddr != NULL) addr = *ipaddr; + else addr = ip_addr_any; + lnet_userdata *ud = (lnet_userdata*)arg; + if (!ud) return; + + post_net_dns (ud, name, &addr); +} + + +static bool post_net_recv (lnet_userdata *ud, struct pbuf *p, const ip_addr_t *ip, u16_t port) +{ + lnet_event *ev = (lnet_event *)malloc (sizeof (lnet_event) + p->len); + if (!ev) + return false; + + ev->event = RECVDATA; + ev->ud = ud; + + if (ip) + ev->recvdata.src_ip = *ip; + ev->recvdata.src_port = port; + ev->recvdata.payload_len = p->len; + pbuf_copy_partial (p, &ev->recvdata.payload, p->len, 0); + + if (!task_post_high (net_event, (task_param_t)ev)) + { + free (ev); + return false; + } + return true; +} + +static void net_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) { + lnet_userdata *ud = (lnet_userdata*)arg; + if (!ud || !ud->pcb || ud->type != TYPE_UDP_SOCKET || ud->self_ref == LUA_NOREF) { + if (p) pbuf_free(p); + return; + } + post_net_recv (ud, p, addr, port); +} + +static err_t net_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { + lnet_userdata *ud = (lnet_userdata*)arg; + if (!ud || !ud->pcb || ud->type != TYPE_TCP_CLIENT || ud->self_ref == LUA_NOREF) + return ERR_ABRT; + if (!p) { + net_err_cb(arg, err); + return tcp_close(tpcb); + } + + if (post_net_recv (ud, p, 0, 0)) + tcp_recved(tpcb, p->len); + + return ERR_OK; +} + + +static bool post_net_sent (lnet_userdata *ud) { + lnet_event *ev = (lnet_event *)malloc (sizeof (lnet_event)); + if (!ev) + return false; + ev->event = SENTDATA; + ev->ud = ud; + if (!task_post_medium (net_event, (task_param_t)ev)) { + free (ev); + return false; + } + return true; +} + +static err_t net_sent_cb(void *arg, struct tcp_pcb *tpcb, u16_t len) { + lnet_userdata *ud = (lnet_userdata*)arg; + if (!ud || !ud->pcb || ud->type != TYPE_TCP_CLIENT || ud->self_ref == LUA_NOREF) return ERR_ABRT; + if (ud->client.cb_sent_ref == LUA_NOREF) return ERR_OK; + + post_net_sent (ud); + // TODO: if we can't post this, we effectively stall this socket, how to fix? + return ERR_OK; +} + + +static bool post_net_accept (lnet_userdata *ud, struct tcp_pcb *newpcb) { + lnet_event *ev = (lnet_event *)malloc (sizeof (lnet_event)); + if (!ev) + return false; + ev->event = SENTDATA; + ev->ud = ud; + ev->accept_newpcb = newpcb; + if (!task_post_medium (net_event, (task_param_t)ev)) { + free (ev); + return false; + } + return true; +} + + +static err_t net_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) { + lnet_userdata *ud = (lnet_userdata*)arg; + if (!ud || ud->type != TYPE_TCP_SERVER || !ud->pcb) return ERR_ABRT; + if (ud->self_ref == LUA_NOREF || ud->server.cb_accept_ref == LUA_NOREF) return ERR_ABRT; + + return post_net_accept (ud, newpcb) ? ERR_OK : ERR_ABRT; +} + +// --- Lua API - create + +extern int tls_socket_create( lua_State *L ); + +// Lua: net.createUDPSocket() +int net_createUDPSocket( lua_State *L ) { + net_create(L, TYPE_UDP_SOCKET); + return 1; +} + +// Lua: net.createServer(type, timeout) +int net_createServer( lua_State *L ) { + int type, timeout; + + type = luaL_optlong(L, 1, TYPE_TCP); + timeout = luaL_optlong(L, 2, 30); + + if (type == TYPE_UDP) return net_createUDPSocket( L ); + if (type != TYPE_TCP) return luaL_error(L, "invalid type"); + + net_create(L, TYPE_TCP_SERVER); + // TODO: timeout + (void)timeout; + return 1; +} + +// Lua: net.createConnection(type, secure) +int net_createConnection( lua_State *L ) { + int type, secure; + + type = luaL_optlong(L, 1, TYPE_TCP); + secure = luaL_optlong(L, 2, 0); + + if (type == TYPE_UDP) return net_createUDPSocket( L ); + if (type != TYPE_TCP) return luaL_error(L, "invalid type"); + if (secure) { +#ifdef CLIENT_SSL_ENABLE + return tls_socket_create( L ); +#else + return luaL_error(L, "secure connections not enabled"); +#endif + } + net_create(L, TYPE_TCP_CLIENT); + return 1; +} + +// --- Get & check userdata + +lnet_userdata *net_get_udata_s( lua_State *L, int stack ) { + if (!lua_isuserdata(L, stack)) return NULL; + lnet_userdata *ud = (lnet_userdata *)lua_touserdata(L, stack); + switch (ud->type) { + case TYPE_TCP_CLIENT: + case TYPE_TCP_SERVER: + case TYPE_UDP_SOCKET: + break; + default: return NULL; + } + const char *mt = NET_TABLES[ud->type]; + ud = luaL_checkudata(L, stack, mt); + return ud; +} +#define net_get_udata(L) net_get_udata_s(L, 1) + +// --- Lua API + +// Lua: server:listen(port, addr, function(c)), socket:listen(port, addr) +int net_listen( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud || ud->type == TYPE_TCP_CLIENT) + return luaL_error(L, "invalid user data"); + if (ud->pcb) + return luaL_error(L, "already listening"); + int stack = 2; + uint16_t port = 0; + const char *domain = "0.0.0.0"; + if (lua_isnumber(L, stack)) + port = lua_tointeger(L, stack++); + if (lua_isstring(L, stack)) { + size_t dl = 0; + domain = luaL_checklstring(L, stack++, &dl); + } + ip_addr_t addr; + if (!ipaddr_aton(domain, &addr)) + return luaL_error(L, "invalid IP address"); + if (ud->type == TYPE_TCP_SERVER) { + if (lua_isfunction(L, stack) || lua_islightfunction(L, stack)) { + lua_pushvalue(L, stack++); + luaL_unref(L, LUA_REGISTRYINDEX, ud->server.cb_accept_ref); + ud->server.cb_accept_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + return luaL_error(L, "need callback"); + } + } + err_t err = ERR_OK; + switch (ud->type) { + case TYPE_TCP_SERVER: + ud->tcp_pcb = tcp_new(); + if (!ud->tcp_pcb) + return luaL_error(L, "cannot allocate PCB"); + err = tcp_bind(ud->tcp_pcb, &addr, port); + if (err == ERR_OK) { + tcp_arg(ud->tcp_pcb, ud); + struct tcp_pcb *pcb = tcp_listen(ud->tcp_pcb); + if (!pcb) { + err = ERR_MEM; + } else { + ud->tcp_pcb = pcb; + tcp_accept(ud->tcp_pcb, net_accept_cb); + } + } + break; + case TYPE_UDP_SOCKET: + ud->udp_pcb = udp_new(); + if (!ud->udp_pcb) + return luaL_error(L, "cannot allocate PCB"); + udp_recv(ud->udp_pcb, net_udp_recv_cb, ud); + err = udp_bind(ud->udp_pcb, &addr, port); + break; + default: break; + } + if (err != ERR_OK) { + switch (ud->type) { + case TYPE_TCP_SERVER: + tcp_close(ud->tcp_pcb); + ud->tcp_pcb = NULL; + break; + case TYPE_UDP_SOCKET: + udp_remove(ud->udp_pcb); + ud->udp_pcb = NULL; + break; + default: break; + } + return lwip_lua_checkerr(L, err); + } + if (ud->self_ref == LUA_NOREF) { + lua_pushvalue(L, 1); + ud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + return 0; +} + +// Lua: client:connect(port, addr) +int net_connect( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud || ud->type != TYPE_TCP_CLIENT) + return luaL_error(L, "invalid user data"); + if (ud->pcb) + return luaL_error(L, "already connected"); + uint16_t port = luaL_checkinteger(L, 2); + if (port == 0) + return luaL_error(L, "specify port"); + const char *domain = "127.0.0.1"; + if (lua_isstring(L, 3)) { + size_t dl = 0; + domain = luaL_checklstring(L, 3, &dl); + } + ud->tcp_pcb = tcp_new(); + if (!ud->tcp_pcb) + return luaL_error(L, "cannot allocate PCB"); + tcp_arg(ud->tcp_pcb, ud); + tcp_err(ud->tcp_pcb, net_err_cb); + tcp_recv(ud->tcp_pcb, net_tcp_recv_cb); + tcp_sent(ud->tcp_pcb, net_sent_cb); + ud->tcp_pcb->remote_port = port; + ip_addr_t addr; + ud->client.wait_dns ++; + int unref = 0; + if (ud->self_ref == LUA_NOREF) { + unref = 1; + lua_pushvalue(L, 1); + ud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + err_t err = dns_gethostbyname(domain, &addr, net_dns_cb, ud); + if (err == ERR_OK) { + net_dns_cb(domain, &addr, ud); + } else if (err != ERR_INPROGRESS) { + ud->client.wait_dns --; + if (unref) { + luaL_unref(L, LUA_REGISTRYINDEX, ud->self_ref); + ud->self_ref = LUA_NOREF; + } + tcp_abort(ud->tcp_pcb); + ud->tcp_pcb = NULL; + return lwip_lua_checkerr(L, err); + } + return 0; +} + +// Lua: client/socket:on(name, callback) +int net_on( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud || ud->type == TYPE_TCP_SERVER) + return luaL_error(L, "invalid user data"); + int *refptr = NULL; + const char *name = luaL_checkstring(L, 2); + if (!name) return luaL_error(L, "need callback name"); + switch (ud->type) { + case TYPE_TCP_CLIENT: + if (strcmp("connection",name)==0) + { refptr = &ud->client.cb_connect_ref; break; } + if (strcmp("disconnection",name)==0) + { refptr = &ud->client.cb_disconnect_ref; break; } + if (strcmp("reconnection",name)==0) + { refptr = &ud->client.cb_reconnect_ref; break; } + case TYPE_UDP_SOCKET: + if (strcmp("dns",name)==0) + { refptr = &ud->client.cb_dns_ref; break; } + if (strcmp("receive",name)==0) + { refptr = &ud->client.cb_receive_ref; break; } + if (strcmp("sent",name)==0) + { refptr = &ud->client.cb_sent_ref; break; } + break; + default: return luaL_error(L, "invalid user data"); + } + if (refptr == NULL) + return luaL_error(L, "invalid callback name"); + if (lua_isfunction(L, 3) || lua_islightfunction(L, 3)) { + lua_pushvalue(L, 3); + luaL_unref(L, LUA_REGISTRYINDEX, *refptr); + *refptr = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (lua_isnil(L, 3)) { + luaL_unref(L, LUA_REGISTRYINDEX, *refptr); + *refptr = LUA_NOREF; + } else { + return luaL_error(L, "invalid callback function"); + } + return 0; +} + +// Lua: client:send(data, function(c)), socket:send(port, ip, data, function(s)) +int net_send( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud || ud->type == TYPE_TCP_SERVER) + return luaL_error(L, "invalid user data"); + ip_addr_t addr; + uint16_t port = 0; + const char *data; + size_t datalen = 0; + int stack = 2; + if (ud->type == TYPE_UDP_SOCKET) { + size_t dl = 0; + port = luaL_checkinteger(L, stack++); + if (port == 0) return luaL_error(L, "need port"); + const char *domain = luaL_checklstring(L, stack++, &dl); + if (!domain) return luaL_error(L, "need IP address"); + if (!ipaddr_aton(domain, &addr)) return luaL_error(L, "invalid IP address"); + } + data = luaL_checklstring(L, stack++, &datalen); + if (!data || datalen == 0) return luaL_error(L, "no data to send"); + if (lua_isfunction(L, stack) || lua_islightfunction(L, stack)) { + lua_pushvalue(L, stack++); + luaL_unref(L, LUA_REGISTRYINDEX, ud->client.cb_sent_ref); + ud->client.cb_sent_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + if (ud->type == TYPE_UDP_SOCKET && !ud->pcb) { + ud->udp_pcb = udp_new(); + if (!ud->udp_pcb) + return luaL_error(L, "cannot allocate PCB"); + udp_recv(ud->udp_pcb, net_udp_recv_cb, ud); + ip_addr_t laddr = IPADDR_ANY_TYPE_INIT; + err_t err = udp_bind(ud->udp_pcb, &laddr, 0); + if (err != ERR_OK) { + udp_remove(ud->udp_pcb); + ud->udp_pcb = NULL; + return lwip_lua_checkerr(L, err); + } + if (ud->self_ref == LUA_NOREF) { + lua_pushvalue(L, 1); + ud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + } + if (!ud->pcb || ud->self_ref == LUA_NOREF) + return luaL_error(L, "not connected"); + err_t err; + if (ud->type == TYPE_UDP_SOCKET) { + struct pbuf *pb = pbuf_alloc(PBUF_TRANSPORT, datalen, PBUF_RAM); + if (!pb) + return luaL_error(L, "cannot allocate message buffer"); + pbuf_take(pb, data, datalen); + err = udp_sendto(ud->udp_pcb, pb, &addr, port); + pbuf_free(pb); + if (ud->client.cb_sent_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->client.cb_sent_ref); + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref); + lua_call(L, 1, 0); + } + } else if (ud->type == TYPE_TCP_CLIENT) { + err = tcp_write(ud->tcp_pcb, data, datalen, TCP_WRITE_FLAG_COPY); + } + else { + err = ERR_VAL; + } + return lwip_lua_checkerr(L, err); +} + +// Lua: client:hold() +int net_hold( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud || ud->type != TYPE_TCP_CLIENT) + return luaL_error(L, "invalid user data"); + ud->client.hold = 1; + return 0; +} + +// Lua: client:unhold() +int net_unhold( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud || ud->type != TYPE_TCP_CLIENT) + return luaL_error(L, "invalid user data"); + ud->client.hold = 0; + return 0; +} + +// Lua: client/socket:dns(domain, callback(socket, addr)) +int net_dns( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud || ud->type == TYPE_TCP_SERVER) + return luaL_error(L, "invalid user data"); + size_t dl = 0; + const char *domain = luaL_checklstring(L, 2, &dl); + if (!domain) + return luaL_error(L, "no domain specified"); + if (lua_isfunction(L, 3) || lua_islightfunction(L, 3)) { + luaL_unref(L, LUA_REGISTRYINDEX, ud->client.cb_dns_ref); + lua_pushvalue(L, 3); + ud->client.cb_dns_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + if (ud->client.cb_dns_ref == LUA_NOREF) + return luaL_error(L, "no callback specified"); + ud->client.wait_dns ++; + int unref = 0; + if (ud->self_ref == LUA_NOREF) { + unref = 1; + lua_pushvalue(L, 1); + ud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + ip_addr_t addr; + err_t err = dns_gethostbyname(domain, &addr, net_dns_cb, ud); + if (err == ERR_OK) { + net_dns_cb(domain, &addr, ud); + } else if (err != ERR_INPROGRESS) { + ud->client.wait_dns --; + if (unref) { + luaL_unref(L, LUA_REGISTRYINDEX, ud->self_ref); + ud->self_ref = LUA_NOREF; + } + return lwip_lua_checkerr(L, err); + } + return 0; +} + +// Lua: client:getpeer() +int net_getpeer( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud || ud->type != TYPE_TCP_CLIENT) + return luaL_error(L, "invalid user data"); + if (!ud->pcb) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + uint16_t port = ud->tcp_pcb->remote_port; + ip_addr_t addr = ud->tcp_pcb->remote_ip; + if (port == 0) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + char addr_str[IP_STR_SZ]; + ipstr (addr_str, &addr); + lua_pushinteger(L, port); + lua_pushstring(L, addr_str); + return 2; +} + +// Lua: client/server/socket:getaddr() +int net_getaddr( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud) return luaL_error(L, "invalid user data"); + if (!ud->pcb) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + uint16_t port = 0; + ip_addr_t addr; + switch (ud->type) { + case TYPE_TCP_CLIENT: + case TYPE_TCP_SERVER: + addr = ud->tcp_pcb->local_ip; + port = ud->tcp_pcb->local_port; + break; + case TYPE_UDP_SOCKET: + addr = ud->udp_pcb->local_ip; + port = ud->udp_pcb->local_port; + break; + } + if (port == 0) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + char addr_str[IP_STR_SZ]; + ipstr (addr_str, &addr); + lua_pushinteger(L, port); + lua_pushstring(L, addr_str); + return 2; +} + +// Lua: client/server/socket:close() +int net_close( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud) return luaL_error(L, "invalid user data"); + if (ud->pcb) { + switch (ud->type) { + case TYPE_TCP_CLIENT: + if (ERR_OK != tcp_close(ud->tcp_pcb)) { + tcp_arg(ud->tcp_pcb, NULL); + tcp_abort(ud->tcp_pcb); + } + ud->tcp_pcb = NULL; + break; + case TYPE_TCP_SERVER: + tcp_close(ud->tcp_pcb); + ud->tcp_pcb = NULL; + break; + case TYPE_UDP_SOCKET: + udp_remove(ud->udp_pcb); + ud->udp_pcb = NULL; + break; + } + } else { + return luaL_error(L, "not connected"); + } + if (ud->type == TYPE_TCP_SERVER || + (ud->pcb == NULL && ud->client.wait_dns == 0)) { + lua_gc(L, LUA_GCSTOP, 0); + luaL_unref(L, LUA_REGISTRYINDEX, ud->self_ref); + ud->self_ref = LUA_NOREF; + lua_gc(L, LUA_GCRESTART, 0); + } + return 0; +} + +int net_delete( lua_State *L ) { + lnet_userdata *ud = net_get_udata(L); + if (!ud) return luaL_error(L, "no user data"); + if (ud->pcb) { + switch (ud->type) { + case TYPE_TCP_CLIENT: + tcp_arg(ud->tcp_pcb, NULL); + tcp_abort(ud->tcp_pcb); + ud->tcp_pcb = NULL; + break; + case TYPE_TCP_SERVER: + tcp_close(ud->tcp_pcb); + ud->tcp_pcb = NULL; + break; + case TYPE_UDP_SOCKET: + udp_remove(ud->udp_pcb); + ud->udp_pcb = NULL; + break; + } + } + switch (ud->type) { + case TYPE_TCP_CLIENT: + luaL_unref(L, LUA_REGISTRYINDEX, ud->client.cb_connect_ref); + ud->client.cb_connect_ref = LUA_NOREF; + luaL_unref(L, LUA_REGISTRYINDEX, ud->client.cb_disconnect_ref); + ud->client.cb_disconnect_ref = LUA_NOREF; + luaL_unref(L, LUA_REGISTRYINDEX, ud->client.cb_reconnect_ref); + ud->client.cb_reconnect_ref = LUA_NOREF; + case TYPE_UDP_SOCKET: + luaL_unref(L, LUA_REGISTRYINDEX, ud->client.cb_dns_ref); + ud->client.cb_dns_ref = LUA_NOREF; + luaL_unref(L, LUA_REGISTRYINDEX, ud->client.cb_receive_ref); + ud->client.cb_receive_ref = LUA_NOREF; + luaL_unref(L, LUA_REGISTRYINDEX, ud->client.cb_sent_ref); + ud->client.cb_sent_ref = LUA_NOREF; + break; + case TYPE_TCP_SERVER: + luaL_unref(L, LUA_REGISTRYINDEX, ud->server.cb_accept_ref); + ud->server.cb_accept_ref = LUA_NOREF; + break; + } + lua_gc(L, LUA_GCSTOP, 0); + luaL_unref(L, LUA_REGISTRYINDEX, ud->self_ref); + ud->self_ref = LUA_NOREF; + lua_gc(L, LUA_GCRESTART, 0); + return 0; +} + +// --- Multicast + +static int net_multicastJoinLeave( lua_State *L, int join) { + size_t il; + ip4_addr_t multicast_addr; + ip4_addr_t if_addr; + const char *multicast_ip; + const char *if_ip; + + NODE_DBG("net_multicastJoin is called.\n"); + if(! lua_isstring(L,1) ) return luaL_error( L, "wrong arg type" ); + if_ip = luaL_checklstring( L, 1, &il ); + if (if_ip != NULL) + if ( if_ip[0] == '\0' || strcasecmp(if_ip,"any") == 0) + { + if_ip = "0.0.0.0"; + il = 7; + } + if (if_ip == NULL || il > 15 || il < 7) return luaL_error( L, "invalid if ip" ); + ip4addr_aton (if_ip, &if_addr); + + if(! lua_isstring(L,2) ) return luaL_error( L, "wrong arg type" ); + multicast_ip = luaL_checklstring( L, 2, &il ); + if (multicast_ip == NULL || il > 15 || il < 7) return luaL_error( L, "invalid multicast ip" ); + ip4addr_aton (multicast_ip, &multicast_addr); + if (join) + { + igmp_joingroup(&if_addr, &multicast_addr); + } + else + { + igmp_leavegroup(&if_addr, &multicast_addr); + } + return 0; +} + +// Lua: net.multicastJoin(ifip, multicastip) +// if ifip "" or "any" all interfaces are affected +static int net_multicastJoin( lua_State* L ) { + return net_multicastJoinLeave(L,1); +} + +// Lua: net.multicastLeave(ifip, multicastip) +// if ifip "" or "any" all interfaces are affected +static int net_multicastLeave( lua_State* L ) { + return net_multicastJoinLeave(L,0); +} + +// --- DNS + +static void net_dns_static_cb(const char *name, const ip_addr_t *ipaddr, void *callback_arg) { + lnet_event *ev = (lnet_event *)callback_arg; + + ev->resolved_ip = ipaddr ? *ipaddr : ip_addr_any; + + if (!task_post_medium (net_event, (task_param_t)ev)) + free (ev); +} + +// Lua: net.dns.resolve( domain, function(ip) ) +static int net_dns_static( lua_State* L ) { + size_t dl; + const char* domain = luaL_checklstring(L, 1, &dl); + if (!domain && dl > 128) { + return luaL_error(L, "wrong domain"); + } + + // Note: this will be free'd using regular free(), so can't luaM_malloc() + lnet_event *ev = (lnet_event *)malloc (sizeof (lnet_event)); + if (!ev) + return luaL_error (L, "out of memory"); + + ev->event = DNSSTATIC; + + luaL_checkanyfunction(L, 2); + lua_pushvalue(L, 2); // copy argument (func) to the top of stack + ev->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + err_t err = dns_gethostbyname( + domain, &ev->resolved_ip, net_dns_static_cb, ev); + if (err == ERR_OK) { + net_dns_static_cb(domain, &ev->resolved_ip, ev); + return 0; + } else if (err == ERR_INPROGRESS) { + return 0; + } else { + int e = lwip_lua_checkerr(L, err); + free(ev); + return e; + } + return 0; +} + +// Lua: s = net.dns.setdnsserver(ip_addr, [index]) +static int net_setdnsserver( lua_State* L ) { + size_t l; + ip_addr_t ipaddr; + + const char *server = luaL_checklstring( L, 1, &l ); + if (l>16 || server == NULL || + !ipaddr_aton (server, &ipaddr) || ip_addr_isany (&ipaddr)) + return luaL_error( L, "invalid dns server ip" ); + + int numdns = luaL_optint(L, 2, 0); + if (numdns >= DNS_MAX_SERVERS) + return luaL_error( L, "server index out of range [0-%d]", DNS_MAX_SERVERS - 1); + + dns_setserver(numdns,&ipaddr); + + return 0; +} + +// Lua: s = net.dns.getdnsserver([index]) +static int net_getdnsserver( lua_State* L ) { + int numdns = luaL_optint(L, 1, 0); + if (numdns >= DNS_MAX_SERVERS) + return luaL_error( L, "server index out of range [0-%d]", DNS_MAX_SERVERS - 1); + + // ip_addr_t ipaddr; + // dns_getserver(numdns,&ipaddr); + // Bug fix by @md5crypt https://github.com/nodemcu/nodemcu-firmware/pull/500 + ip_addr_t ipaddr = dns_getserver(numdns); + + if ( ip_addr_isany(&ipaddr) ) { + lua_pushnil( L ); + } else { + char temp[IP_STR_SZ]; + ipstr (temp, &ipaddr); + lua_pushstring( L, temp ); + } + + return 1; +} + +// --- Lua event dispatch + +static void ldnsfound_cb (lua_State *L, lnet_userdata *ud, ip_addr_t *addr) { + if (ud->self_ref != LUA_NOREF && ud->client.cb_dns_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->client.cb_dns_ref); + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref); + if (!ip_addr_isany (addr)) { + char iptmp[IP_STR_SZ]; + ipstr (iptmp, addr); + lua_pushstring(L, iptmp); + } else { + lua_pushnil(L); + } + lua_call(L, 2, 0); + } + ud->client.wait_dns --; + if (ud->pcb && ud->type == TYPE_TCP_CLIENT && ud->tcp_pcb->state == CLOSED) { + tcp_connect(ud->tcp_pcb, addr, ud->tcp_pcb->remote_port, net_connected_cb); + } else if (!ud->pcb && ud->client.wait_dns == 0) { + lua_gc(L, LUA_GCSTOP, 0); + luaL_unref(L, LUA_REGISTRYINDEX, ud->self_ref); + ud->self_ref = LUA_NOREF; + lua_gc(L, LUA_GCRESTART, 0); + } +} + +static void ldnsstatic_cb (lua_State *L, int cb_ref, ip_addr_t *addr) { + if (cb_ref == LUA_NOREF) + return; + + lua_rawgeti(L, LUA_REGISTRYINDEX, cb_ref); + luaL_unref (L, LUA_REGISTRYINDEX, cb_ref); + + if (!ip_addr_isany (addr)) { + char iptmp[IP_STR_SZ]; + ipstr (iptmp, addr); + lua_pushstring(L, iptmp); + } else { + lua_pushnil(L); + } + lua_call(L, 1, 0); +} + +static void lconnected_cb (lua_State *L, lnet_userdata *ud) { + if (ud->self_ref != LUA_NOREF && ud->client.cb_connect_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->client.cb_connect_ref); + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref); + lua_call(L, 1, 0); + } +} + +static void laccept_cb (lua_State *L, lnet_userdata *ud, struct tcp_pcb *newpcb) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->server.cb_accept_ref); + + lnet_userdata *nud = net_create(L, TYPE_TCP_CLIENT); + lua_pushvalue(L, 2); + nud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); + nud->tcp_pcb = newpcb; + tcp_arg(nud->tcp_pcb, nud); + tcp_err(nud->tcp_pcb, net_err_cb); + tcp_recv(nud->tcp_pcb, net_tcp_recv_cb); + tcp_sent(nud->tcp_pcb, net_sent_cb); + + tcp_accepted(ud->tcp_pcb); + + lua_call(L, 1, 0); +} + +static void lrecv_cb (lua_State *L, lnet_userdata *ud, const lnet_recvdata *rd) { + if (ud->client.cb_receive_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->client.cb_receive_ref); + int num_args = 2; + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref); + lua_pushlstring(L, rd->payload, rd->payload_len); + if (ud->type == TYPE_UDP_SOCKET) { + num_args += 2; + char iptmp[IP_STR_SZ]; + ipstr (iptmp, &rd->src_ip); + lua_pushinteger(L, rd->src_port); + lua_pushstring(L, iptmp); + } + lua_call(L, num_args, 0); + } +} + +static void lsent_cb (lua_State *L, lnet_userdata *ud) { + if (ud->client.cb_sent_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->client.cb_sent_ref); + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref); + lua_call(L, 1, 0); + } +} + +static void lerr_cb (lua_State *L, lnet_userdata *ud, err_t err) +{ + int ref; + if (err != ERR_OK && ud->client.cb_reconnect_ref != LUA_NOREF) + ref = ud->client.cb_reconnect_ref; + else ref = ud->client.cb_disconnect_ref; + if (ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref); + lua_pushinteger(L, err); + lua_call(L, 2, 0); + } + if (ud->client.wait_dns == 0) { + lua_gc(L, LUA_GCSTOP, 0); + luaL_unref(L, LUA_REGISTRYINDEX, ud->self_ref); + ud->self_ref = LUA_NOREF; + lua_gc(L, LUA_GCRESTART, 0); + } +} + +static void handle_net_event (task_param_t param, task_prio_t prio) +{ + lnet_event *ev = (lnet_event *)param; + (void)prio; + + lua_State *L = lua_getstate(); + switch (ev->event) + { + case DNSFOUND: ldnsfound_cb (L, ev->ud, &ev->resolved_ip); break; + case DNSSTATIC: ldnsstatic_cb (L, ev->cb_ref, &ev->resolved_ip); break; + case CONNECTED: lconnected_cb (L, ev->ud); break; + case ACCEPT: laccept_cb (L, ev->ud, ev->accept_newpcb); break; + case RECVDATA: lrecv_cb (L, ev->ud, &ev->recvdata); break; + case SENTDATA: lsent_cb (L, ev->ud); break; + case ERR: lerr_cb (L, ev->ud, ev->err); break; + } + + free (ev); +} + +// --- Tables + +extern const LUA_REG_TYPE tls_cert_map[]; + +// Module function map +static const LUA_REG_TYPE net_tcpserver_map[] = { + { LSTRKEY( "listen" ), LFUNCVAL( net_listen ) }, + { LSTRKEY( "getaddr" ), LFUNCVAL( net_getaddr ) }, + { LSTRKEY( "close" ), LFUNCVAL( net_close ) }, + { LSTRKEY( "__gc" ), LFUNCVAL( net_delete ) }, + { LSTRKEY( "__index" ), LROVAL( net_tcpserver_map ) }, + { LNILKEY, LNILVAL } +}; + +static const LUA_REG_TYPE net_tcpsocket_map[] = { + { LSTRKEY( "connect" ), LFUNCVAL( net_connect ) }, + { LSTRKEY( "close" ), LFUNCVAL( net_close ) }, + { LSTRKEY( "on" ), LFUNCVAL( net_on ) }, + { LSTRKEY( "send" ), LFUNCVAL( net_send ) }, + { LSTRKEY( "hold" ), LFUNCVAL( net_hold ) }, + { LSTRKEY( "unhold" ), LFUNCVAL( net_unhold ) }, + { LSTRKEY( "dns" ), LFUNCVAL( net_dns ) }, + { LSTRKEY( "getpeer" ), LFUNCVAL( net_getpeer ) }, + { LSTRKEY( "getaddr" ), LFUNCVAL( net_getaddr ) }, + { LSTRKEY( "__gc" ), LFUNCVAL( net_delete ) }, + { LSTRKEY( "__index" ), LROVAL( net_tcpsocket_map ) }, + { LNILKEY, LNILVAL } +}; + +static const LUA_REG_TYPE net_udpsocket_map[] = { + { LSTRKEY( "listen" ), LFUNCVAL( net_listen ) }, + { LSTRKEY( "close" ), LFUNCVAL( net_close ) }, + { LSTRKEY( "on" ), LFUNCVAL( net_on ) }, + { LSTRKEY( "send" ), LFUNCVAL( net_send ) }, + { LSTRKEY( "dns" ), LFUNCVAL( net_dns ) }, + { LSTRKEY( "getaddr" ), LFUNCVAL( net_getaddr ) }, + { LSTRKEY( "__gc" ), LFUNCVAL( net_delete ) }, + { LSTRKEY( "__index" ), LROVAL( net_udpsocket_map ) }, + { LNILKEY, LNILVAL } +}; + +static const LUA_REG_TYPE net_dns_map[] = { + { LSTRKEY( "setdnsserver" ), LFUNCVAL( net_setdnsserver ) }, + { LSTRKEY( "getdnsserver" ), LFUNCVAL( net_getdnsserver ) }, + { LSTRKEY( "resolve" ), LFUNCVAL( net_dns_static ) }, + { LNILKEY, LNILVAL } +}; + +static const LUA_REG_TYPE net_map[] = { + { LSTRKEY( "createServer" ), LFUNCVAL( net_createServer ) }, + { LSTRKEY( "createConnection" ), LFUNCVAL( net_createConnection ) }, + { LSTRKEY( "createUDPSocket" ), LFUNCVAL( net_createUDPSocket ) }, + { LSTRKEY( "multicastJoin"), LFUNCVAL( net_multicastJoin ) }, + { LSTRKEY( "multicastLeave"), LFUNCVAL( net_multicastLeave ) }, + { LSTRKEY( "dns" ), LROVAL( net_dns_map ) }, +#ifdef CLIENT_SSL_ENABLE + { LSTRKEY( "cert" ), LROVAL( tls_cert_map ) }, +#endif + { LSTRKEY( "TCP" ), LNUMVAL( TYPE_TCP ) }, + { LSTRKEY( "UDP" ), LNUMVAL( TYPE_UDP ) }, + { LSTRKEY( "__metatable" ), LROVAL( net_map ) }, + { LNILKEY, LNILVAL } +}; + +int luaopen_net( lua_State *L ) { + igmp_init(); + + luaL_rometatable(L, NET_TABLE_TCP_SERVER, (void *)net_tcpserver_map); + luaL_rometatable(L, NET_TABLE_TCP_CLIENT, (void *)net_tcpsocket_map); + luaL_rometatable(L, NET_TABLE_UDP_SOCKET, (void *)net_udpsocket_map); + + net_event = task_get_id (handle_net_event); + + return 0; +} + +NODEMCU_MODULE(NET, "net", net_map, luaopen_net);