Sfoglia il codice sorgente

Net functionality rewrite on top of LWIP (#1379)

* Rewrite net.dns.resolve on LWIP

* Move IGMP (net multicast) to LWIP

* Cleanup net module

* Move secure connection operations to tls module

* Net module on LWIP

* Server timeout parameter

* TCP hold

* Fixes in documentation

* Documentation fixes

* Note TLS module depends on net

* Add TLS module to user_modules.h

* Callback on connect event

* Fix depends net module on tls module

* Fix unhold exponential time issue
Yury Popov 7 anni fa
parent
commit
3adba91b15
7 ha cambiato i file con 1724 aggiunte e 1383 eliminazioni
  1. 1 0
      app/include/user_modules.h
  2. 673 1256
      app/modules/net.c
  3. 649 0
      app/modules/tls.c
  4. 110 125
      docs/en/modules/net.md
  5. 288 0
      docs/en/modules/tls.md
  6. 1 0
      mkdocs.yml
  7. 2 2
      tools/make_server_cert.py

+ 1 - 0
app/include/user_modules.h

@@ -60,6 +60,7 @@
 //#define LUA_USE_MODULES_STRUCT
 //#define LUA_USE_MODULES_SWITEC
 //#define LUA_USE_MODULES_TM1829
+#define LUA_USE_MODULES_TLS
 #define LUA_USE_MODULES_TMR
 //#define LUA_USE_MODULES_TSL2561
 //#define LUA_USE_MODULES_U8G

File diff suppressed because it is too large
+ 673 - 1256
app/modules/net.c


+ 649 - 0
app/modules/tls.c

@@ -0,0 +1,649 @@
+// Module for TLS
+
+#include "module.h"
+
+#if defined(CLIENT_SSL_ENABLE) && defined(LUA_USE_MODULES_NET)
+
+#include "lauxlib.h"
+#include "platform.h"
+#include "lmem.h"
+
+#include "c_string.h"
+#include "c_stdlib.h"
+
+#include "c_types.h"
+#include "mem.h"
+#include "lwip/ip_addr.h"
+#include "espconn.h"
+#include "lwip/err.h"
+#include "lwip/dns.h"
+
+#ifdef HAVE_SSL_SERVER_CRT
+#include HAVE_SSL_SERVER_CRT
+#else
+__attribute__((section(".servercert.flash"))) unsigned char tls_server_cert_area[INTERNAL_FLASH_SECTOR_SIZE];
+#endif
+
+__attribute__((section(".clientcert.flash"))) unsigned char tls_client_cert_area[INTERNAL_FLASH_SECTOR_SIZE];
+
+extern int tls_socket_create( lua_State *L );
+extern const LUA_REG_TYPE tls_cert_map[];
+
+typedef struct {
+  struct espconn *pesp_conn;
+  int self_ref;
+  int cb_connect_ref;
+  int cb_reconnect_ref;
+  int cb_disconnect_ref;
+  int cb_sent_ref;
+  int cb_receive_ref;
+  int cb_dns_ref;
+} tls_socket_ud;
+
+int tls_socket_create( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud*) lua_newuserdata(L, sizeof(tls_socket_ud));
+
+  ud->pesp_conn = NULL;
+  ud->self_ref =
+  ud->cb_connect_ref =
+  ud->cb_reconnect_ref =
+  ud->cb_disconnect_ref =
+  ud->cb_sent_ref =
+  ud->cb_receive_ref =
+  ud->cb_dns_ref = LUA_NOREF;
+
+  luaL_getmetatable(L, "tls.socket");
+  lua_setmetatable(L, -2);
+
+  return 1;
+}
+
+static void tls_socket_onconnect( struct espconn *pesp_conn ) {
+  tls_socket_ud *ud = (tls_socket_ud *)pesp_conn->reverse;
+  if (!ud || ud->self_ref == LUA_NOREF) return;
+  if (ud->cb_connect_ref != LUA_NOREF) {
+    lua_State *L = lua_getstate();
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->cb_connect_ref);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref);
+    lua_call(L, 1, 0);
+  }
+}
+
+static void tls_socket_cleanup(tls_socket_ud *ud) {
+  if (ud->pesp_conn) {
+    espconn_secure_disconnect(ud->pesp_conn);
+    if (ud->pesp_conn->proto.tcp) {
+      c_free(ud->pesp_conn->proto.tcp);
+      ud->pesp_conn->proto.tcp = NULL;
+    }
+    c_free(ud->pesp_conn);
+    ud->pesp_conn = NULL;
+  }
+  lua_State *L = lua_getstate();
+  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 tls_socket_ondisconnect( struct espconn *pesp_conn ) {
+  tls_socket_ud *ud = (tls_socket_ud *)pesp_conn->reverse;
+  if (!ud || ud->self_ref == LUA_NOREF) return;
+  tls_socket_cleanup(ud);
+  if (ud->cb_disconnect_ref != LUA_NOREF) {
+    lua_State *L = lua_getstate();
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->cb_disconnect_ref);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref);
+    tls_socket_cleanup(ud);
+    lua_call(L, 1, 0);
+  } else tls_socket_cleanup(ud);
+}
+
+static void tls_socket_onreconnect( struct espconn *pesp_conn, s8 err ) {
+  tls_socket_ud *ud = (tls_socket_ud *)pesp_conn->reverse;
+  if (!ud || ud->self_ref == LUA_NOREF) return;
+  if (ud->cb_reconnect_ref != LUA_NOREF) {
+    const char* reason = NULL;
+    switch (err) {
+      case(ESPCONN_MEM): reason = "Out of memory"; break;
+      case(ESPCONN_TIMEOUT): reason = "Timeout"; break;
+      case(ESPCONN_RTE): reason = "Routing problem"; break;
+      case(ESPCONN_ABRT): reason = "Connection aborted"; break;
+      case(ESPCONN_RST): reason = "Connection reset"; break;
+      case(ESPCONN_CLSD): reason = "Connection closed"; break;
+      case(ESPCONN_HANDSHAKE): reason = "SSL handshake failed"; break;
+      case(ESPCONN_SSL_INVALID_DATA): reason = "SSL application invalid"; break;
+    }
+    lua_State *L = lua_getstate();
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->cb_reconnect_ref);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref);
+    if (reason != NULL) {
+      lua_pushstring(L, reason);
+    } else {
+      lua_pushnil(L);
+    }
+    tls_socket_cleanup(ud);
+    lua_call(L, 2, 0);
+  } else tls_socket_cleanup(ud);
+}
+
+static void tls_socket_onrecv( struct espconn *pesp_conn, char *buf, u16 length ) {
+  tls_socket_ud *ud = (tls_socket_ud *)pesp_conn->reverse;
+  if (!ud || ud->self_ref == LUA_NOREF) return;
+  if (ud->cb_receive_ref != LUA_NOREF) {
+    lua_State *L = lua_getstate();
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->cb_receive_ref);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref);
+    lua_pushlstring(L, buf, length);
+    lua_call(L, 2, 0);
+  }
+}
+
+static void tls_socket_onsent( struct espconn *pesp_conn ) {
+  tls_socket_ud *ud = (tls_socket_ud *)pesp_conn->reverse;
+  if (!ud || ud->self_ref == LUA_NOREF) return;
+  if (ud->cb_sent_ref != LUA_NOREF) {
+    lua_State *L = lua_getstate();
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->cb_sent_ref);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref);
+    lua_call(L, 1, 0);
+  }
+}
+
+static void tls_socket_dns_cb( const char* domain, const ip_addr_t *ip_addr, tls_socket_ud *ud ) {
+  if (ud->self_ref == LUA_NOREF) return;
+  ip_addr_t addr;
+  if (ip_addr) addr = *ip_addr;
+  else addr.addr = 0xFFFFFFFF;
+  lua_State *L = lua_getstate();
+  if (ud->cb_dns_ref != LUA_NOREF) {
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->cb_dns_ref);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->self_ref);
+    if (addr.addr == 0xFFFFFFFF) {
+      lua_pushnil(L);
+    } else {
+      char tmp[20];
+      c_sprintf(tmp, IPSTR, IP2STR(&addr.addr));
+      lua_pushstring(L, tmp);
+    }
+    lua_call(L, 2, 0);
+  }
+  if (addr.addr == 0xFFFFFFFF) {
+    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);
+  } else {
+    os_memcpy(ud->pesp_conn->proto.tcp->remote_ip, &addr.addr, 4);
+    espconn_secure_connect(ud->pesp_conn);
+  }
+}
+
+static int tls_socket_connect( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud *)luaL_checkudata(L, 1, "tls.socket");
+  luaL_argcheck(L, ud, 1, "TLS socket expected");
+  if(ud==NULL){
+  	NODE_DBG("userdata is nil.\n");
+  	return 0;
+  }
+
+  if (ud->pesp_conn) {
+    return luaL_error(L, "already connected");
+  }
+
+  u16 port = luaL_checkinteger( L, 2 );
+  size_t il;
+  const char *domain = "127.0.0.1";
+  if( lua_isstring(L, 3) )
+    domain = luaL_checklstring( L, 3, &il );
+  if (port == 0)
+    return luaL_error(L, "invalid port");
+  if (domain == NULL)
+    return luaL_error(L, "invalid domain");
+
+  ud->pesp_conn = (struct espconn*)c_zalloc(sizeof(struct espconn));
+  if(!ud->pesp_conn)
+    return luaL_error(L, "not enough memory");
+  ud->pesp_conn->proto.udp = NULL;
+  ud->pesp_conn->proto.tcp = (esp_tcp *)c_zalloc(sizeof(esp_tcp));
+  if(!ud->pesp_conn->proto.tcp){
+    c_free(ud->pesp_conn);
+    ud->pesp_conn = NULL;
+    return luaL_error(L, "not enough memory");
+  }
+  ud->pesp_conn->type = ESPCONN_TCP;
+  ud->pesp_conn->state = ESPCONN_NONE;
+  ud->pesp_conn->reverse = ud;
+  ud->pesp_conn->proto.tcp->remote_port = port;
+  espconn_regist_connectcb(ud->pesp_conn, (espconn_connect_callback)tls_socket_onconnect);
+  espconn_regist_disconcb(ud->pesp_conn, (espconn_connect_callback)tls_socket_ondisconnect);
+  espconn_regist_reconcb(ud->pesp_conn, (espconn_reconnect_callback)tls_socket_onreconnect);
+  espconn_regist_recvcb(ud->pesp_conn, (espconn_recv_callback)tls_socket_onrecv);
+  espconn_regist_sentcb(ud->pesp_conn, (espconn_sent_callback)tls_socket_onsent);
+
+  if (ud->self_ref == LUA_NOREF) {
+    lua_pushvalue(L, 1);  // copy to the top of stack
+    ud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+  }
+
+  ip_addr_t addr;
+  err_t err = dns_gethostbyname(domain, &addr, (dns_found_callback)tls_socket_dns_cb, ud);
+  if (err == ERR_OK) {
+    tls_socket_dns_cb(domain, &addr, ud);
+  } else if (err != ERR_INPROGRESS) {
+    tls_socket_dns_cb(domain, NULL, ud);
+  }
+
+  return 0;
+}
+
+static int tls_socket_on( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud *)luaL_checkudata(L, 1, "tls.socket");
+  luaL_argcheck(L, ud, 1, "TLS socket expected");
+  if(ud==NULL){
+  	NODE_DBG("userdata is nil.\n");
+  	return 0;
+  }
+
+  size_t sl;
+  const char *method = luaL_checklstring( L, 2, &sl );
+  if (method == NULL)
+    return luaL_error( L, "wrong arg type" );
+
+  luaL_checkanyfunction(L, 3);
+  lua_pushvalue(L, 3);  // copy argument (func) to the top of stack
+
+  if (strcmp(method, "connection") == 0) {
+    luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_connect_ref);
+    ud->cb_connect_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+  } else if (strcmp(method, "disconnection") == 0) {
+    luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_disconnect_ref);
+    ud->cb_disconnect_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+  } else if (strcmp(method, "reconnection") == 0) {
+    luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_reconnect_ref);
+    ud->cb_reconnect_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+  } else if (strcmp(method, "receive") == 0) {
+    luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_receive_ref);
+    ud->cb_receive_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+  } else if (strcmp(method, "sent") == 0) {
+    luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_sent_ref);
+    ud->cb_sent_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+  } else if (strcmp(method, "dns") == 0) {
+    luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_dns_ref);
+    ud->cb_dns_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+  } else {
+    return luaL_error(L, "invalid method");
+  }
+  return 0;
+}
+
+static int tls_socket_send( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud *)luaL_checkudata(L, 1, "tls.socket");
+  luaL_argcheck(L, ud, 1, "TLS socket expected");
+  if(ud==NULL){
+  	NODE_DBG("userdata is nil.\n");
+  	return 0;
+  }
+
+  if(ud->pesp_conn == NULL) {
+    NODE_DBG("not connected");
+    return 0;
+  }
+
+  size_t sl;
+  const char* buf = luaL_checklstring(L, 2, &sl);
+  if (!buf) {
+    return luaL_error(L, "wrong arg type");
+  }
+
+  espconn_secure_send(ud->pesp_conn, (void*)buf, sl);
+
+  return 0;
+}
+static int tls_socket_hold( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud *)luaL_checkudata(L, 1, "tls.socket");
+  luaL_argcheck(L, ud, 1, "TLS socket expected");
+  if(ud==NULL){
+  	NODE_DBG("userdata is nil.\n");
+  	return 0;
+  }
+
+  if(ud->pesp_conn == NULL) {
+    NODE_DBG("not connected");
+    return 0;
+  }
+
+  espconn_recv_hold(ud->pesp_conn);
+
+  return 0;
+}
+static int tls_socket_unhold( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud *)luaL_checkudata(L, 1, "tls.socket");
+  luaL_argcheck(L, ud, 1, "TLS socket expected");
+  if(ud==NULL){
+  	NODE_DBG("userdata is nil.\n");
+  	return 0;
+  }
+
+  if(ud->pesp_conn == NULL) {
+    NODE_DBG("not connected");
+    return 0;
+  }
+
+  espconn_recv_unhold(ud->pesp_conn);
+
+  return 0;
+}
+static int tls_socket_dns( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud *)luaL_checkudata(L, 1, "tls.socket");
+  luaL_argcheck(L, ud, 1, "TLS socket expected");
+  if(ud==NULL){
+  	NODE_DBG("userdata is nil.\n");
+  	return 0;
+  }
+
+  return 0;
+}
+static int tls_socket_getpeer( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud *)luaL_checkudata(L, 1, "tls.socket");
+  luaL_argcheck(L, ud, 1, "TLS socket expected");
+  if(ud==NULL){
+  	NODE_DBG("userdata is nil.\n");
+  	return 0;
+  }
+
+  if(ud->pesp_conn && ud->pesp_conn->proto.tcp->remote_port != 0){
+    char temp[20] = {0};
+    c_sprintf(temp, IPSTR, IP2STR( &(ud->pesp_conn->proto.tcp->remote_ip) ) );
+    lua_pushstring( L, temp );
+    lua_pushinteger( L, ud->pesp_conn->proto.tcp->remote_port );
+  } else {
+    lua_pushnil( L );
+    lua_pushnil( L );
+  }
+  return 2;
+}
+static int tls_socket_close( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud *)luaL_checkudata(L, 1, "tls.socket");
+  luaL_argcheck(L, ud, 1, "TLS socket expected");
+  if(ud==NULL){
+  	NODE_DBG("userdata is nil.\n");
+  	return 0;
+  }
+
+  if (ud->pesp_conn) {
+    espconn_secure_disconnect(ud->pesp_conn);
+  }
+
+  return 0;
+}
+static int tls_socket_delete( lua_State *L ) {
+  tls_socket_ud *ud = (tls_socket_ud *)luaL_checkudata(L, 1, "tls.socket");
+  luaL_argcheck(L, ud, 1, "TLS socket expected");
+  if(ud==NULL){
+  	NODE_DBG("userdata is nil.\n");
+  	return 0;
+  }
+  if (ud->pesp_conn) {
+    espconn_secure_disconnect(ud->pesp_conn);
+    if (ud->pesp_conn->proto.tcp) {
+      c_free(ud->pesp_conn->proto.tcp);
+      ud->pesp_conn->proto.tcp = NULL;
+    }
+    c_free(ud->pesp_conn);
+    ud->pesp_conn = NULL;
+  }
+
+  luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_connect_ref);
+  ud->cb_connect_ref = LUA_NOREF;
+  luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_disconnect_ref);
+  ud->cb_disconnect_ref = LUA_NOREF;
+  luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_reconnect_ref);
+  ud->cb_reconnect_ref = LUA_NOREF;
+  luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_dns_ref);
+  ud->cb_dns_ref = LUA_NOREF;
+  luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_receive_ref);
+  ud->cb_receive_ref = LUA_NOREF;
+  luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_sent_ref);
+  ud->cb_sent_ref = LUA_NOREF;
+
+  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;
+}
+
+// Returns NULL on success, error message otherwise
+static const char *append_pem_blob(const char *pem, const char *type, uint8_t **buffer_p, uint8_t *buffer_limit, const char *name) {
+  char unb64[256];
+  memset(unb64, 0xff, sizeof(unb64));
+  int i;
+  for (i = 0; i < 64; i++) {
+    unb64["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
+  }
+
+  if (!pem) {
+    return "No PEM blob";
+  }
+
+  // Scan for -----BEGIN CERT
+  pem = strstr(pem, "-----BEGIN ");
+  if (!pem) {
+    return "No PEM header";
+  }
+
+  if (strncmp(pem + 11, type, strlen(type))) {
+    return "Wrong PEM type";
+  }
+
+  pem = strchr(pem, '\n');
+  if (!pem) {
+    return "Incorrect PEM format";
+  }
+  //
+  // Base64 encoded data starts here
+  // Get all the base64 data into a single buffer....
+  // We will use the back end of the buffer....
+  //
+
+  uint8_t *buffer = *buffer_p;
+
+  uint8_t *dest = buffer + 32 + 2;  // Leave space for name and length
+  int bitcount = 0;
+  int accumulator = 0;
+  for (; *pem && dest < buffer_limit; pem++) {
+    int val = unb64[*(uint8_t*) pem];
+    if (val & 0xC0) {
+      // not a base64 character
+      if (isspace(*(uint8_t*) pem)) {
+	continue;
+      }
+      if (*pem == '=') {
+	// just ignore -- at the end
+	bitcount = 0;
+	continue;
+      }
+      if (*pem == '-') {
+	break;
+      }
+      return "Invalid character in PEM";
+    } else {
+      bitcount += 6;
+      accumulator = (accumulator << 6) + val;
+      if (bitcount >= 8) {
+	bitcount -= 8;
+	*dest++ = accumulator >> bitcount;
+      }
+    }
+  }
+  if (dest >= buffer_limit || strncmp(pem, "-----END ", 9) || strncmp(pem + 9, type, strlen(type)) || bitcount) {
+    return "Invalid PEM format data";
+  }
+  size_t len = dest - (buffer + 32 + 2);
+
+  memset(buffer, 0, 32);
+  strcpy(buffer, name);
+  buffer[32] = len & 0xff;
+  buffer[33] = (len >> 8) & 0xff;
+  *buffer_p = dest;
+  return NULL;
+}
+
+static const char *fill_page_with_pem(lua_State *L, const unsigned char *flash_memory, int flash_offset, const char **types, const char **names) 
+{
+  uint8_t  *buffer = luaM_malloc(L, INTERNAL_FLASH_SECTOR_SIZE);
+  uint8_t  *buffer_base = buffer;
+  uint8_t  *buffer_limit = buffer + INTERNAL_FLASH_SECTOR_SIZE;
+
+  int argno;
+
+  for (argno = 1; argno <= lua_gettop(L) && types[argno - 1]; argno++) {
+    const char *pem = lua_tostring(L, argno);
+
+    const char *error = append_pem_blob(pem, types[argno - 1], &buffer, buffer_limit, names[argno - 1]);
+    if (error) {
+      luaM_free(L, buffer_base);
+      return error;
+    }
+  }
+
+  memset(buffer, 0xff, buffer_limit - buffer);
+
+  // Lets see if it matches what is already there....
+  if (c_memcmp(buffer_base, flash_memory, INTERNAL_FLASH_SECTOR_SIZE) != 0) {
+    // Starts being dangerous
+    if (platform_flash_erase_sector(flash_offset / INTERNAL_FLASH_SECTOR_SIZE) != PLATFORM_OK) {
+      luaM_free(L, buffer_base);
+      return "Failed to erase sector";
+    }
+    if (platform_s_flash_write(buffer_base, flash_offset, INTERNAL_FLASH_SECTOR_SIZE) != INTERNAL_FLASH_SECTOR_SIZE) {
+      luaM_free(L, buffer_base);
+      return "Failed to write sector";
+    }
+    // ends being dangerous
+  }
+
+  luaM_free(L, buffer_base);
+
+  return NULL;
+}
+
+// Lua: tls.cert.auth(true / false | PEM data [, PEM data] )
+static int tls_cert_auth(lua_State *L)
+{
+  int enable;
+
+  uint32_t flash_offset = platform_flash_mapped2phys((uint32_t) &tls_client_cert_area[0]);
+  if ((flash_offset & 0xfff) || flash_offset > 0xff000 || INTERNAL_FLASH_SECTOR_SIZE != 0x1000) {
+    // THis should never happen
+    return luaL_error( L, "bad offset" );
+  }
+
+  if (lua_type(L, 1) == LUA_TSTRING) {
+    const char *types[3] = { "CERTIFICATE", "RSA PRIVATE KEY", NULL };
+    const char *names[2] = { "certificate", "private_key" };
+    const char *error = fill_page_with_pem(L, &tls_client_cert_area[0], flash_offset, types, names);
+    if (error) {
+      return luaL_error(L, error);
+    }
+
+    enable = 1;
+  } else {
+    enable = lua_toboolean(L, 1);
+  }
+
+  bool rc;
+
+  if (enable) {
+    // See if there is a cert there
+    if (tls_client_cert_area[0] == 0x00 || tls_client_cert_area[0] == 0xff) {
+      return luaL_error( L, "no certificates found" );
+    }
+    rc = espconn_secure_cert_req_enable(1, flash_offset / INTERNAL_FLASH_SECTOR_SIZE);
+  } else {
+    rc = espconn_secure_cert_req_disable(1);
+  }
+
+  lua_pushboolean(L, rc);
+  return 1;
+}
+
+// Lua: tls.cert.verify(true / false | PEM data [, PEM data] )
+static int tls_cert_verify(lua_State *L)
+{
+  int enable;
+
+  uint32_t flash_offset = platform_flash_mapped2phys((uint32_t) &tls_server_cert_area[0]);
+  if ((flash_offset & 0xfff) || flash_offset > 0xff000 || INTERNAL_FLASH_SECTOR_SIZE != 0x1000) {
+    // THis should never happen
+    return luaL_error( L, "bad offset" );
+  }
+
+  if (lua_type(L, 1) == LUA_TSTRING) {
+    const char *types[2] = { "CERTIFICATE", NULL };
+    const char *names[1] = { "certificate" };
+
+    const char *error = fill_page_with_pem(L, &tls_server_cert_area[0], flash_offset, types, names);
+    if (error) {
+      return luaL_error(L, error);
+    }
+
+    enable = 1;
+  } else {
+    enable = lua_toboolean(L, 1);
+  }
+
+  bool rc;
+
+  if (enable) {
+    // See if there is a cert there
+    if (tls_server_cert_area[0] == 0x00 || tls_server_cert_area[0] == 0xff) {
+      return luaL_error( L, "no certificates found" );
+    }
+    rc = espconn_secure_ca_enable(1, flash_offset / INTERNAL_FLASH_SECTOR_SIZE);
+  } else {
+    rc = espconn_secure_ca_disable(1);
+  }
+
+  lua_pushboolean(L, rc);
+  return 1;
+}
+
+static const LUA_REG_TYPE tls_socket_map[] = {
+  { LSTRKEY( "connect" ), LFUNCVAL( tls_socket_connect ) },
+  { LSTRKEY( "close" ),   LFUNCVAL( tls_socket_close ) },
+  { LSTRKEY( "on" ),      LFUNCVAL( tls_socket_on ) },
+  { LSTRKEY( "send" ),    LFUNCVAL( tls_socket_send ) },
+  { LSTRKEY( "hold" ),    LFUNCVAL( tls_socket_hold ) },
+  { LSTRKEY( "unhold" ),  LFUNCVAL( tls_socket_unhold ) },
+  { LSTRKEY( "dns" ),     LFUNCVAL( tls_socket_dns ) },
+  { LSTRKEY( "getpeer" ), LFUNCVAL( tls_socket_getpeer ) },
+  { LSTRKEY( "__gc" ),    LFUNCVAL( tls_socket_delete ) },
+  { LSTRKEY( "__index" ), LROVAL( tls_socket_map ) },
+  { LNILKEY, LNILVAL }
+};
+
+const LUA_REG_TYPE tls_cert_map[] = {
+  { LSTRKEY( "verify" ),           LFUNCVAL( tls_cert_verify ) },
+  { LSTRKEY( "auth" ),             LFUNCVAL( tls_cert_auth ) },
+  { LSTRKEY( "__index" ),          LROVAL( tls_cert_map ) },
+  { LNILKEY, LNILVAL }
+};
+
+static const LUA_REG_TYPE tls_map[] = {
+  { LSTRKEY( "createConnection" ), LFUNCVAL( tls_socket_create ) },
+  { LSTRKEY( "cert" ),             LROVAL( tls_cert_map ) },
+  { LSTRKEY( "__metatable" ),      LROVAL( tls_map ) },
+  { LNILKEY, LNILVAL }
+};
+
+int luaopen_tls( lua_State *L ) {
+  luaL_rometatable(L, "tls.socket", (void *)tls_socket_map);  // create metatable for net.server
+  espconn_secure_set_size(ESPCONN_CLIENT, 4096);
+  return 0;
+}
+
+NODEMCU_MODULE(TLS, "tls", tls_map, luaopen_tls);
+#endif

+ 110 - 125
docs/en/modules/net.md

@@ -1,13 +1,9 @@
 # net Module
 | Since  | Origin / Contributor  | Maintainer  | Source  |
 | :----- | :-------------------- | :---------- | :------ |
-| 2014-12-22 | [Zeroday](https://github.com/funshine) | [Zeroday](https://github.com/funshine) | [net.c](../../../app/modules/net.c)|
+| 2014-12-22 | [Zeroday](https://github.com/funshine) | [PhoeniX](https://github.com/djphoenix) | [net.c](../../../app/modules/net.c)|
 
-**SSL/TLS support**
-
-NodeMCU includes [mbedTLS library](https://tls.mbed.org/). With default config it supports **TLS** 1.0 / 1.1 / 1.2 and most common cipher suites, including DH/ECDH key exchange methods.
-
-Full list of features you may get on [mbedTLS page](https://tls.mbed.org/core-features).
+** TLS operations was moved to the [TLS](tls.md) module **
 
 ## Constants
 Constants to be used in other functions: `net.TCP`, `net.UDP`
@@ -20,20 +16,23 @@ Creates a client.
 `net.createConnection(type, secure)`
 
 #### Parameters
-- `type` `net.TCP` or `net.UDP`
-- `secure` 1 for encrypted, 0 for plain
+- `type` `net.TCP` or `net.UDP`. UDP connections chained to [net.createUDPSocket()](#netcreateudpsocket)
+- `secure` 1 for encrypted, 0 for plain. Secure connections chained to [tls.createConnection()](tls.md#tlscreateconnection)
 
 #### Returns
-net.socket sub module
+
+- for `net.TCP` - net.socket sub module
+- for `net.UDP` - net.udpsocket sub module
+- for `net.TCP` with `secure` - tls.socket sub module
 
 #### Example
 
 ```lua
-net.createConnection(net.UDP, 0)
+net.createConnection(net.TCP, 0)
 ```
 
 #### See also
-[`net.createServer()`](#netcreateserver)
+[`net.createServer()`](#netcreateserver), [`net.createUDPSocket()`](#netcreateudpsocket), [`tls.createConnection()`](tls.md#tlscreateconnection)
 
 ## net.createServer()
 
@@ -43,11 +42,13 @@ Creates a server.
 `net.createServer(type, timeout)`
 
 #### Parameters
-- `type` `net.TCP` or `net.UDP`
+- `type` `net.TCP` or `net.UDP`. UDP connections chained to [net.createUDPSocket()](#netcreateudpsocket)
 - `timeout` for a TCP server timeout is 1~28'800 seconds (for an inactive client to be disconnected)
 
 #### Returns
-net.server sub module
+
+- for `net.TCP` - net.server sub module
+- for `net.UDP` - net.udpsocket sub module
 
 #### Example
 
@@ -55,6 +56,22 @@ net.server sub module
 net.createServer(net.TCP, 30) -- 30s timeout
 ```
 
+#### See also
+[`net.createConnection()`](#netcreateconnection), [`net.createUDPSocket()`](#netcreateudpsocket)
+
+## net.createUDPSocket()
+
+Creates an UDP socket.
+
+#### Syntax
+`net.createUDPSocket()`
+
+#### Parameters
+none
+
+#### Returns
+net.udpsocket sub module
+
 #### See also
 [`net.createConnection()`](#netcreateconnection)
 
@@ -117,10 +134,10 @@ sv:close()
 Listen on port from IP address.
 
 #### Syntax
-`net.server.listen(port,[ip],function(net.socket))`
+`net.server.listen([port],[ip],function(net.socket))`
 
 #### Parameters
-- `port` port number
+- `port` port number, can be omitted (random port will be chosen)
 - `ip` IP address string, can be omitted
 - `function(net.socket)` callback function, pass to caller function as param if a connection is created successfully
 
@@ -149,19 +166,22 @@ end
 #### See also
 [`net.createServer()`](#netcreateserver)
 
-## net.server:on()
+## net.server:getaddr()
 
-UDP server only: Register callback functions for specific events.
+Returns server local address/port.
 
-#### See also
-[`net.socket:on()`](#netsocketon)
+#### Syntax
+`net.server.getaddr()`
 
-## net.server:send()
+#### Parameters
+none
 
-UDP server only: Sends data to remote peer.
+#### Returns
+`port`, `ip` (or `nil, nil` if not listening)
 
 #### See also
-[`net.socket:send()`](#netsocketsend)
+[`net.server:listen()`](#netserverlisten)
+
 
 # net.socket Module
 ## net.socket:close()
@@ -223,7 +243,7 @@ sk = nil
 
 ## net.socket:getpeer()
 
-Retrieve port and ip of peer.
+Retrieve port and ip of remote peer.
 
 #### Syntax
 `getpeer()`
@@ -232,8 +252,20 @@ Retrieve port and ip of peer.
 none
 
 #### Returns
-- `ip` of peer
-- `port` of peer
+`port`, `ip` (or `nil, nil` if not connected)
+
+## net.socket:getaddr()
+
+Retrieve local port and ip of socket.
+
+#### Syntax
+`getaddr()`
+
+#### Parameters
+none
+
+#### Returns
+`port`, `ip` (or `nil, nil` if not connected)
 
 ## net.socket:hold()
 
@@ -260,7 +292,16 @@ Register callback functions for specific events.
 
 #### Parameters
 - `event` string, which can be "connection", "reconnection", "disconnection", "receive" or "sent"
-- `function(net.socket[, string])` callback function. The first parameter is the socket. If event is "receive", the second parameter is the received data as string.
+- `function(net.socket[, string])` callback function. Can be `nil` to remove callback.
+
+The first parameter of callback is the socket.
+
+- If event is "receive", the second parameter is the received data as string.
+- If event is "disconnection" or "reconnection", the second parameter is error code.
+
+If reconnection event is specified, disconnection receives only "normal close" events.
+
+Otherwise, all connection errors (with normal close) passed to disconnection event.
 
 #### Returns
 `nil`
@@ -269,11 +310,11 @@ Register callback functions for specific events.
 ```lua
 srv = net.createConnection(net.TCP, 0)
 srv:on("receive", function(sck, c) print(c) end)
-srv:connect(80,"192.168.0.66")
-srv:on("connection", function(sck, c)
+srv:on("connection", function(sck)
   -- Wait for connection before sending.
   sck:send("GET / HTTP/1.1\r\nHost: 192.168.0.66\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
 end)
+srv:connect(80,"192.168.0.66")
 ```
 
 #### See also
@@ -367,6 +408,44 @@ none
 #### See also
 [`net.socket:hold()`](#netsockethold)
 
+# net.udpsocket Module
+
+## net.udpsocket:close()
+
+Closes UDP socket.
+
+The syntax and functional identical to [`net.socket:close()`](#netsocketclose).
+
+## net.udpsocket:listen()
+
+Listen on port from IP address.
+
+The syntax and functional similar to [`net.server:listen()`](#netserverlisten), but callback parameter is not provided.
+
+## net.udpsocket:on()
+
+Register callback functions for specific events.
+
+The syntax and functional similar to [`net.socket:on()`](#netsocketon), only "received", "sent" and "dns" events are valid.
+
+**`received` callback have `port` and `ip` after `data` argument.**
+
+## net.udpsocket:send()
+
+Sends data to specific remote peer.
+
+## net.udpsocket:dns()
+
+Provides DNS resolution for a hostname.
+
+The syntax and functional identical to [`net.socket:dns()`](#netsocketdns).
+
+## net.udpsocket:getaddr()
+
+Retrieve local port and ip of socket.
+
+The syntax and functional identical to [`net.socket:getaddr()`](#netsocketgetaddr).
+
 # net.dns Module
 
 ## net.dns.getdnsserver()
@@ -401,11 +480,11 @@ print(net.dns.getdnsserver(1)) -- 192.168.1.252
 Resolve a hostname to an IP address. Doesn't require a socket like [`net.socket.dns()`](#netsocketdns).
 
 #### Syntax
-`net.dns.resolve(host, function(ip))`
+`net.dns.resolve(host, function(sk, ip))`
 
 #### Parameters
 - `host` hostname to resolve
-- `function(sk, ip)` callback called when the name was resolved. Don't use `sk`, it's a socket used internally to resolve the hostname.
+- `function(sk, ip)` callback called when the name was resolved. `sk` is always `nil`
 
 #### Returns
 `nil`
@@ -438,98 +517,4 @@ Sets the IP of the DNS server used to resolve hostnames. Default: resolver1.open
 
 # net.cert Module
 
-This controls certificate verification when SSL is in use. 
-
-## net.cert.verify()
-
-Controls the vertificate verification process when the Nodemcu makes a secure connection.
-
-#### Syntax
-`net.cert.verify(enable)`
-
-`net.cert.verify(pemdata)`
-
-#### Parameters
-- `enable` A boolean which indicates whether verification should be enabled or not. The default at boot is `false`.
-- `pemdata` A string containing the CA certificate to use for verification.
-
-#### Returns
-`true` if it worked. 
-
-Can throw a number of errors if invalid data is supplied.
-
-#### Example
-Make a secure https connection and verify that the certificate chain is valid.
-```
-net.cert.verify(true)
-http.get("https://example.com/info", nil, function (code, resp) print(code, resp) end)
-```
-
-Load a certificate into the flash chip and make a request. This is the [startssl](https://startssl.com) root certificate. They provide free
-certificates.
-
-```
-net.cert.verify([[
------BEGIN CERTIFICATE-----
-MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
-MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
-Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
-dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
-MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
-U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
-cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
-A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
-pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
-OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
-Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
-Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
-HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
-Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
-+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
-Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
-Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
-26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
-AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
-FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
-ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
-LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
-BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
-Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
-dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
-cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
-YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
-dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
-bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
-YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
-TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
-9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
-jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
-FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
-ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
-ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
-EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
-L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
-yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
-O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
-um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
-NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
------END CERTIFICATE-----
-]])
-
-http.get("https://pskreporter.info/gen404", nil, function (code, resp) print(code, resp) end)
-```
-
-
-#### Notes
-The certificate needed for verification is stored in the flash chip. The `net.cert.verify` call with `true`
-enables verification against the value stored in the flash. 
-
-The certificate can be loaded into the flash chip in two ways -- one at firmware build time, and the other at initial boot
-of the firmware. In order to load the certificate at build time, just place a file containing the CA certificate (in PEM format) 
-at `server-ca.crt` in the root of the nodemcu-firmware build tree. The build scripts will incorporate this into the resulting
-firmware image.
-
-The alternative approach is easier for development, and that is to supply the PEM data as a string value to `net.cert.verify`. This
-will store the certificate into the flash chip and turn on verification for that certificate. Subsequent boots of the nodemcu can then
-use `net.cert.verify(true)` and use the stored certificate.
-
+This part gone to the [TLS](tls.md) module, link kept for backward compatibility.

+ 288 - 0
docs/en/modules/tls.md

@@ -0,0 +1,288 @@
+# TLS Module
+| Since  | Origin / Contributor  | Maintainer  | Source  |
+| :----- | :-------------------- | :---------- | :------ |
+| 2016-12-15 | [PhoeniX](https://github.com/djphoenix) | [PhoeniX](https://github.com/djphoenix) | [tls.c](../../../app/modules/tls.c)|
+
+**SSL/TLS support**
+
+!!! important
+    TLS module requires [net](net.md) module.
+
+NodeMCU includes [mbedTLS library](https://tls.mbed.org/). With default config it supports **TLS** 1.0 / 1.1 / 1.2 and most common cipher suites, including DH/ECDH key exchange methods.
+
+Full list of features you may get on [mbedTLS page](https://tls.mbed.org/core-features).
+
+This controls certificate verification when SSL is in use.
+
+## tls.createConnection()
+
+Creates TLS connection.
+
+#### Syntax
+`tls.createConnection()`
+
+#### Parameters
+none
+
+#### Returns
+tls.socket sub module
+
+#### Example
+
+```lua
+tls.createConnection()
+```
+
+# tls.socket Module
+
+## tls.socket:close()
+
+Closes socket.
+
+#### Syntax
+`close()`
+
+#### Parameters
+none
+
+#### Returns
+`nil`
+
+#### See also
+[`tls.createConnection()`](#tlscreateconnection)
+
+## tls.socket:connect()
+
+Connect to a remote server.
+
+#### Syntax
+`connect(port, ip|domain)`
+
+#### Parameters
+- `port` port number
+- `ip` IP address or domain name string
+
+#### Returns
+`nil`
+
+#### See also
+[`tls.socket:on()`](#tlssocketon)
+
+## tls.socket:dns()
+
+Provides DNS resolution for a hostname.
+
+#### Syntax
+`dns(domain, function(tls.socket, ip))`
+
+#### Parameters
+- `domain` domain name
+- `function(tls.socket, ip)` callback function. The first parameter is the socket, the second parameter is the IP address as a string.
+
+#### Returns
+`nil`
+
+#### Example
+```lua
+sk = tls.createConnection()
+sk:dns("google.com", function(conn, ip) print(ip) end)
+sk = nil
+```
+
+## tls.socket:getpeer()
+
+Retrieve port and ip of peer.
+
+#### Syntax
+`getpeer()`
+
+#### Parameters
+none
+
+#### Returns
+- `ip` of peer
+- `port` of peer
+
+## tls.socket:hold()
+
+Throttle data reception by placing a request to block the TCP receive function. This request is not effective immediately, Espressif recommends to call it while reserving 5*1460 bytes of memory.
+
+#### Syntax
+`hold()`
+
+#### Parameters
+none
+
+#### Returns
+`nil`
+
+#### See also
+[`tls.socket:unhold()`](#tlssocketunhold)
+
+## tls.socket:on()
+
+Register callback functions for specific events.
+
+#### Syntax
+`on(event, function())`
+
+#### Parameters
+- `event` string, which can be "connection", "reconnection", "disconnection", "receive" or "sent"
+- `function(tls.socket[, string])` callback function. The first parameter is the socket.
+If event is "receive", the second parameter is the received data as string.
+If event is "reconnection", the second parameter is the reason of connection error (string).
+
+#### Returns
+`nil`
+
+#### Example
+```lua
+srv = tls.createConnection()
+srv:on("receive", function(sck, c) print(c) end)
+srv:on("connection", function(sck, c)
+  -- Wait for connection before sending.
+  sck:send("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
+end)
+srv:connect(443,"google.com")
+```
+
+#### See also
+- [`tls.createConnection()`](#tlscreateconnection)
+- [`tls.socket:hold()`](#tlssockethold)
+
+## tls.socket:send()
+
+Sends data to remote peer.
+
+#### Syntax
+`send(string)`
+
+#### Parameters
+- `string` data in string which will be sent to server
+
+#### Returns
+`nil`
+
+#### Note
+
+Multiple consecutive `send()` calls aren't guaranteed to work (and often don't) as network requests are treated as separate tasks by the SDK. Instead, subscribe to the "sent" event on the socket and send additional data (or close) in that callback. See [#730](https://github.com/nodemcu/nodemcu-firmware/issues/730#issuecomment-154241161) for details.
+
+#### See also
+[`tls.socket:on()`](#tlssocketon)
+
+## tls.socket:unhold()
+
+Unblock TCP receiving data by revocation of a preceding `hold()`.
+
+#### Syntax
+`unhold()`
+
+#### Parameters
+none
+
+#### Returns
+`nil`
+
+#### See also
+[`tls.socket:hold()`](#tlssockethold)
+
+
+
+
+
+
+
+
+# tls.cert Module
+
+## tls.cert.verify()
+
+Controls the vertificate verification process when the Nodemcu makes a secure connection.
+
+#### Syntax
+`tls.cert.verify(enable)`
+
+`tls.cert.verify(pemdata)`
+
+#### Parameters
+- `enable` A boolean which indicates whether verification should be enabled or not. The default at boot is `false`.
+- `pemdata` A string containing the CA certificate to use for verification.
+
+#### Returns
+`true` if it worked. 
+
+Can throw a number of errors if invalid data is supplied.
+
+#### Example
+Make a secure https connection and verify that the certificate chain is valid.
+```
+tls.cert.verify(true)
+http.get("https://example.com/info", nil, function (code, resp) print(code, resp) end)
+```
+
+Load a certificate into the flash chip and make a request. This is the [startssl](https://startssl.com) root certificate. They provide free
+certificates.
+
+```
+tls.cert.verify([[
+-----BEGIN CERTIFICATE-----
+MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
+MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
+U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
+cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
+pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
+OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
+Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
+Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
+HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
+Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
+Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
+26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
+AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
+FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
+ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
+LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
+BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
+Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
+dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
+cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
+YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
+dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
+bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
+YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
+TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
+9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
+jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
+FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
+ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
+ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
+EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
+L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
+yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
+O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
+um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
+NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
+-----END CERTIFICATE-----
+]])
+
+http.get("https://pskreporter.info/gen404", nil, function (code, resp) print(code, resp) end)
+```
+
+#### Notes
+The certificate needed for verification is stored in the flash chip. The `tls.cert.verify` call with `true`
+enables verification against the value stored in the flash. 
+
+The certificate can be loaded into the flash chip in two ways -- one at firmware build time, and the other at initial boot
+of the firmware. In order to load the certificate at build time, just place a file containing the CA certificate (in PEM format) 
+at `server-ca.crt` in the root of the nodemcu-firmware build tree. The build scripts will incorporate this into the resulting
+firmware image.
+
+The alternative approach is easier for development, and that is to supply the PEM data as a string value to `tls.cert.verify`. This
+will store the certificate into the flash chip and turn on verification for that certificate. Subsequent boots of the nodemcu can then
+use `tls.cert.verify(true)` and use the stored certificate.

+ 1 - 0
mkdocs.yml

@@ -76,6 +76,7 @@ pages:
         - 'spi': 'en/modules/spi.md'
         - 'struct': 'en/modules/struct.md'
         - 'switec': 'en/modules/switec.md'
+        - 'tls': 'en/modules/tls.md'
         - 'tm1829': 'en/modules/tm1829.md'
         - 'tmr': 'en/modules/tmr.md'
         - 'tsl2561': 'en/modules/tsl2561.md'

+ 2 - 2
tools/make_server_cert.py

@@ -31,8 +31,8 @@ def main():
 		    help='specify the section for the data (default is .servercert.flash)')
 
     parser.add_argument('--name', 
-                   default='net_server_cert_area',
-		    help='specify the variable name for the data (default is net_server_cert_area)')
+                   default='tls_server_cert_area',
+		    help='specify the variable name for the data (default is tls_server_cert_area)')
 
     parser.add_argument('file', nargs='+',
 		    help='One or more PEM files')

Some files were not shown because too many files changed in this diff