mirror of
https://github.com/espruino/Espruino.git
synced 2025-12-08 19:06:15 +00:00
1247 lines
43 KiB
C
1247 lines
43 KiB
C
|
|
/*
|
|
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
|
|
*
|
|
* Copyright (C) 2015 Gordon Williams <gw@pur3.co.uk>
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
* This file is designed to be parsed during the build process
|
|
*
|
|
* Contains ESP8266 board network specific functions.
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
// ESP8266 specific includes
|
|
#include <c_types.h>
|
|
#include <user_interface.h>
|
|
#include <mem.h>
|
|
#include "osapi_release.h"
|
|
#include <espconn.h>
|
|
#include <espmissingincludes.h>
|
|
|
|
#define _GCC_WRAP_STDINT_H
|
|
typedef long long int64_t;
|
|
|
|
#include "network_esp8266.h"
|
|
#include "socketerrors.h"
|
|
#include "esp8266_board_utils.h"
|
|
#include "pktbuf.h"
|
|
|
|
//#define espconn_abort espconn_disconnect
|
|
|
|
// Set NET_DBG to 0 to disable debug printf's, to 1 for important printf's
|
|
#ifdef RELEASE
|
|
#define DBG(format, ...) do { } while(0)
|
|
#else
|
|
// Normal debug
|
|
#if NET_DBG > 0
|
|
#define DBG(format, ...) os_printf(format, ## __VA_ARGS__)
|
|
// #include "jsinteractive.h"
|
|
// #define DBG(format, ...) jsiConsolePrintf(format, ## __VA_ARGS__)
|
|
static char DBG_LIB[] = "net_esp8266"; // library name
|
|
#else
|
|
#define DBG(format, ...) do { } while(0)
|
|
#endif
|
|
#endif
|
|
|
|
static struct socketData *getSocketData(int s);
|
|
|
|
/**
|
|
* The next socketId to be used.
|
|
*/
|
|
static int g_nextSocketId = 0;
|
|
|
|
static int getServerSocketByLocalPort(unsigned short port);
|
|
static void setSocketInError(struct socketData *pSocketData, int code);
|
|
static void dumpEspConn(struct espconn *pEspConn);
|
|
static struct socketData *allocateNewSocket();
|
|
static int connectSocket(struct socketData *pSocketData);
|
|
static void doClose(struct socketData *pSocketData);
|
|
static void releaseSocket(struct socketData *pSocketData);
|
|
static void resetSocket(struct socketData *pSocketData);
|
|
static void esp8266_dumpSocketData(struct socketData *pSocketData);
|
|
|
|
static void esp8266_callback_connectCB_inbound(void *arg);
|
|
static void esp8266_callback_connectCB_outbound(void *arg);
|
|
static void esp8266_callback_disconnectCB(void *arg);
|
|
static void esp8266_callback_sentCB(void *arg);
|
|
static void esp8266_callback_writeFinishedCB(void *arg);
|
|
static void esp8266_callback_recvCB(void *arg, char *pData, unsigned short len);
|
|
static void esp8266_callback_reconnectCB(void *arg, sint8 err);
|
|
|
|
/** Socket data structure
|
|
*
|
|
* We maintain an array of socketData structures. The number of such structures is defined in the
|
|
* MAX_SOCKETS define. The private variable that contains the array is called "socketArray".
|
|
* Each one of these array instances represents a possible socket structure that we can use.
|
|
*
|
|
* Each socket maintains state and its creation purpose.
|
|
*
|
|
* The trickiest part is closing. If the socket lib closes a socket it forgets about the socket
|
|
* as soon as we return. We then have to issue a disconnect to espconn and await the disconnect
|
|
* call-back in the SOCKET_STATE_DISCONNECTING. Once that's done, we can deallocate everything.
|
|
* If we receive a disconnect from the remote end, we free the espconn struct and we transition
|
|
* to SOCKET_STATE_CLOSED until we can respond to a send/recv call from the
|
|
* socket library with -1 and it then calls close.
|
|
* In summary, the rules are:
|
|
* - SOCKET_STATE_DISCONNECTING: the socket lib has closed, we close when we get disconCB
|
|
* - SOCKET_STATE_CLOSED: we have closed espconn, awaiting socket lib to issue close
|
|
* - SOCKET_STATE_ABORTING: we await reconCB/disconCB, we also await socket lib to issue close
|
|
*/
|
|
|
|
/**
|
|
* The potential states for a socket.
|
|
* See the socket state diagram.
|
|
*/
|
|
enum SOCKET_STATE {
|
|
SOCKET_STATE_UNUSED, //!< Unused socket "slot"
|
|
SOCKET_STATE_UNACCEPTED, //!< New inbound connection that Espruino hasn't accepted yet
|
|
SOCKET_STATE_HOST_RESOLVING, //!< Resolving a hostname, happens before CONNECTING
|
|
SOCKET_STATE_CONNECTING, //!< In the process of connecting
|
|
SOCKET_STATE_IDLE, //!< Connected but nothing in tx buffers
|
|
SOCKET_STATE_TRANSMITTING, //!< Connected and espconn_send has been called, awaiting CB
|
|
SOCKET_STATE_DISCONNECTING, //!< Did disconnect, awaiting discon callback from espconn
|
|
SOCKET_STATE_ABORTING, //!< Did abort, awaiting discon callback from espconn
|
|
SOCKET_STATE_TO_ABORT, //!< Need to abort asap (couldn't do it in callback)
|
|
SOCKET_STATE_CLOSED, //!< Closed, espconn struct freed, awaiting close from socket lib
|
|
};
|
|
|
|
/**
|
|
* How was the socket created.
|
|
*/
|
|
enum SOCKET_CREATION_TYPE {
|
|
SOCKET_CREATED_NONE, //!< The socket has not yet been created.
|
|
SOCKET_CREATED_SERVER, //!< Listening socket ("server socket")
|
|
SOCKET_CREATED_OUTBOUND, //!< Outbound connection
|
|
SOCKET_CREATED_INBOUND //!< Inbound connection
|
|
};
|
|
|
|
/**
|
|
* The core socket structure.
|
|
* The structure is initialized by resetSocket.
|
|
*/
|
|
struct socketData {
|
|
int socketId; //!< The id of THIS socket.
|
|
enum SOCKET_STATE state; //!< What is the socket state?
|
|
enum SOCKET_CREATION_TYPE creationType; //!< How was the socket created?
|
|
|
|
struct espconn *pEspconn; //!< The ESPConn structure.
|
|
|
|
uint8 *currentTx; //!< Data currently being transmitted.
|
|
PktBuf *rxBufQ; //!< Queue of received buffers
|
|
|
|
short errorCode; //!< Error code, 0=no error
|
|
|
|
uint32_t host;
|
|
unsigned short port;
|
|
|
|
uint32_t multicastGrpIp;
|
|
uint32_t multicastIp;
|
|
};
|
|
|
|
|
|
/**
|
|
* An array of socket data structures.
|
|
*/
|
|
static struct socketData socketArray[MAX_SOCKETS];
|
|
|
|
/**
|
|
* Flag the sockets as initially NOT initialized.
|
|
*/
|
|
static bool g_socketsInitialized = false;
|
|
|
|
/**
|
|
* Dump all the socket structures.
|
|
* This is used exclusively for debugging. It walks through each of the
|
|
* socket structures and dumps their state to the debug log.
|
|
*/
|
|
void esp8266_dumpAllSocketData() {
|
|
for (int i=0; i<MAX_SOCKETS; i++) {
|
|
esp8266_dumpSocketData(&socketArray[i]);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Write the details of a socket to the debug log.
|
|
* The data associated with the socket is dumped to the debug log.
|
|
*/
|
|
void esp8266_dumpSocket(
|
|
int socketId //!< The ID of the socket data structure to be logged.
|
|
) {
|
|
struct socketData *pSocketData = getSocketData(socketId);
|
|
esp8266_dumpSocketData(pSocketData);
|
|
}
|
|
|
|
|
|
/**
|
|
* Write the details of a socketData to the debug log.
|
|
* The data associated with the socketData is dumped to the debug log.
|
|
*/
|
|
static void esp8266_dumpSocketData(
|
|
struct socketData *pSocketData //!< The socket data structure to be logged
|
|
) {
|
|
DBG("=== socket %d", pSocketData->socketId);
|
|
char *creationTypeMsg;
|
|
switch(pSocketData->creationType) {
|
|
case SOCKET_CREATED_NONE:
|
|
creationTypeMsg = "none";
|
|
break;
|
|
case SOCKET_CREATED_INBOUND:
|
|
creationTypeMsg = "inbound";
|
|
break;
|
|
case SOCKET_CREATED_OUTBOUND:
|
|
creationTypeMsg = "outbound";
|
|
break;
|
|
case SOCKET_CREATED_SERVER:
|
|
creationTypeMsg = "server";
|
|
break;
|
|
}
|
|
DBG(" type=%s, txBuf=%p", creationTypeMsg, pSocketData->currentTx);
|
|
char *stateMsg;
|
|
switch(pSocketData->state) {
|
|
case SOCKET_STATE_CLOSED:
|
|
stateMsg = "closing";
|
|
break;
|
|
case SOCKET_STATE_CONNECTING:
|
|
stateMsg = "connecting";
|
|
break;
|
|
case SOCKET_STATE_DISCONNECTING:
|
|
stateMsg = "disconnecting";
|
|
break;
|
|
case SOCKET_STATE_ABORTING:
|
|
stateMsg = "aborting";
|
|
break;
|
|
case SOCKET_STATE_IDLE:
|
|
stateMsg = "idle";
|
|
break;
|
|
case SOCKET_STATE_TRANSMITTING:
|
|
stateMsg = "transmitting";
|
|
break;
|
|
case SOCKET_STATE_HOST_RESOLVING:
|
|
stateMsg = "resolving";
|
|
break;
|
|
case SOCKET_STATE_UNACCEPTED:
|
|
stateMsg = "unaccepted";
|
|
break;
|
|
case SOCKET_STATE_UNUSED:
|
|
stateMsg = "unused";
|
|
break;
|
|
default:
|
|
stateMsg = "Unexpected state!!";
|
|
break;
|
|
}
|
|
DBG(", state=%s, espconn=%p, err=%d", stateMsg, pSocketData->pEspconn, pSocketData->errorCode);
|
|
DBG(", rx:");
|
|
for (PktBuf *b=pSocketData->rxBufQ; b; b=b->next) {
|
|
DBG(" %d@%p", b->filled, b);
|
|
}
|
|
DBG("\n");
|
|
}
|
|
|
|
|
|
/**
|
|
* Dump a struct espconn (for debugging purposes).
|
|
*/
|
|
#if 0
|
|
static void dumpEspConn(
|
|
struct espconn *pEspConn //!<
|
|
) {
|
|
char ipString[20];
|
|
LOG("Dump of espconn: 0x%x\n", (int)pEspConn);
|
|
if (pEspConn == NULL) {
|
|
return;
|
|
}
|
|
switch(pEspConn->type) {
|
|
case ESPCONN_TCP:
|
|
LOG(" - type = TCP\n");
|
|
LOG(" - local address = %d.%d.%d.%d [%d]\n",
|
|
pEspConn->proto.tcp->local_ip[0],
|
|
pEspConn->proto.tcp->local_ip[1],
|
|
pEspConn->proto.tcp->local_ip[2],
|
|
pEspConn->proto.tcp->local_ip[3],
|
|
pEspConn->proto.tcp->local_port);
|
|
LOG(" - remote address = %d.%d.%d.%d [%d]\n",
|
|
pEspConn->proto.tcp->remote_ip[0],
|
|
pEspConn->proto.tcp->remote_ip[1],
|
|
pEspConn->proto.tcp->remote_ip[2],
|
|
pEspConn->proto.tcp->remote_ip[3],
|
|
pEspConn->proto.tcp->remote_port);
|
|
break;
|
|
case ESPCONN_UDP:
|
|
LOG(" - type = UDP\n");
|
|
LOG(" - local_port = %d\n", pEspConn->proto.udp->local_port);
|
|
LOG(" - local_ip = %d.%d.%d.%d\n",
|
|
pEspConn->proto.udp->local_ip[0],
|
|
pEspConn->proto.udp->local_ip[1],
|
|
pEspConn->proto.udp->local_ip[2],
|
|
pEspConn->proto.udp->local_ip[3]);
|
|
LOG(" - remote_port = %d\n", pEspConn->proto.udp->remote_port);
|
|
LOG(" - remote_ip = %d.%d.%d.%d\n",
|
|
pEspConn->proto.udp->remote_ip[0],
|
|
pEspConn->proto.udp->remote_ip[1],
|
|
pEspConn->proto.udp->remote_ip[2],
|
|
pEspConn->proto.udp->remote_ip[3]);
|
|
break;
|
|
default:
|
|
LOG(" - type = Unknown!! 0x%x\n", pEspConn->type);
|
|
}
|
|
switch(pEspConn->state) {
|
|
case ESPCONN_NONE:
|
|
LOG(" - state=NONE");
|
|
break;
|
|
case ESPCONN_WAIT:
|
|
LOG(" - state=WAIT");
|
|
break;
|
|
case ESPCONN_LISTEN:
|
|
LOG(" - state=LISTEN");
|
|
break;
|
|
case ESPCONN_CONNECT:
|
|
LOG(" - state=CONNECT");
|
|
break;
|
|
case ESPCONN_WRITE:
|
|
LOG(" - state=WRITE");
|
|
break;
|
|
case ESPCONN_READ:
|
|
LOG(" - state=READ");
|
|
break;
|
|
case ESPCONN_CLOSE:
|
|
LOG(" - state=CLOSE");
|
|
break;
|
|
default:
|
|
LOG(" - state=unknown!!");
|
|
break;
|
|
}
|
|
LOG(", link_cnt=%d", pEspConn->link_cnt);
|
|
LOG(", reverse=0x%x\n", (unsigned int)pEspConn->reverse);
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* Get the next new global socket id.
|
|
* \return A new socketId that is assured to be unique.
|
|
*/
|
|
static int getNextGlobalSocketId() {
|
|
return ++g_nextSocketId;
|
|
}
|
|
|
|
|
|
/**
|
|
* Allocate a new socket
|
|
* Look for the first free socket in the array of sockets and return the first one
|
|
* that is available. The socketId property is set to a unique and new socketId value
|
|
* that will not previously have been seen.
|
|
* \return The socketData structure for the returned socket.
|
|
*/
|
|
static struct socketData *allocateNewSocket() {
|
|
// Walk through each of the sockets in the array of possible sockets and stop
|
|
// at the first one that is flagged as not in use. For that socket, set its
|
|
// socketId to the next global socketId value.
|
|
for (int i=0; i<MAX_SOCKETS; i++) {
|
|
if (socketArray[i].state == SOCKET_STATE_UNUSED) {
|
|
socketArray[i].socketId = getNextGlobalSocketId();
|
|
return &socketArray[i];
|
|
}
|
|
}
|
|
esp8266_dumpAllSocketData();
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieve the socketData for the given socket index.
|
|
* \return The socket data for the given socket or NULL if there is no matching socket.
|
|
*/
|
|
static struct socketData *getSocketData(int socketId) {
|
|
struct socketData *pSocketData = socketArray;
|
|
for (int socketArrayIndex=0; socketArrayIndex<MAX_SOCKETS; socketArrayIndex++) {
|
|
if (pSocketData->socketId == socketId) {
|
|
return pSocketData;
|
|
}
|
|
pSocketData++;
|
|
}
|
|
DBG("%s: socket %d not found\n", DBG_LIB, socketId);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find the server socket that is bound to the given local port.
|
|
* \return The socket id of the socket listening on the given port or -1 if there is no
|
|
* server socket that matches.
|
|
*/
|
|
static int getServerSocketByLocalPort(
|
|
unsigned short port //!< The port number on which a server socket is listening.
|
|
) {
|
|
// Loop through each of the sockets in the socket array looking for a socket
|
|
// that is inuse, a server and has a local_port of the passed in port number.
|
|
int socketArrayIndex;
|
|
struct socketData *pSocketData = socketArray;
|
|
for (socketArrayIndex=0; socketArrayIndex<MAX_SOCKETS; socketArrayIndex++) {
|
|
if (pSocketData->state != SOCKET_STATE_UNUSED &&
|
|
pSocketData->creationType == SOCKET_CREATED_SERVER &&
|
|
pSocketData->pEspconn->proto.tcp->local_port == port)
|
|
{
|
|
return pSocketData->socketId;
|
|
}
|
|
pSocketData++;
|
|
} // End of for each socket
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Release the socket and return it to the free pool.
|
|
* The connection (espconn) must be closed and deallocated before calling releaseSocket.
|
|
*/
|
|
static void releaseSocket(
|
|
struct socketData *pSocketData //!< The socket to release
|
|
) {
|
|
assert(pSocketData != NULL);
|
|
//DBG("%s: freeing socket %d\n", DBG_LIB, pSocketData->socketId);
|
|
assert(pSocketData->state != SOCKET_STATE_UNUSED);
|
|
assert(pSocketData->pEspconn == NULL);
|
|
|
|
// free any unconsumed receive buffers
|
|
while (pSocketData->rxBufQ != NULL)
|
|
pSocketData->rxBufQ = PktBuf_ShiftFree(pSocketData->rxBufQ);
|
|
|
|
if (pSocketData->currentTx != NULL) {
|
|
//DBG("%s: freeing tx buf %p\n", DBG_LIB, pSocketData->currentTx);
|
|
os_free(pSocketData->currentTx);
|
|
pSocketData->currentTx = NULL;
|
|
}
|
|
|
|
os_memset(pSocketData, 0, sizeof(struct socketData));
|
|
}
|
|
|
|
/**
|
|
* Release the espconn structure
|
|
*/
|
|
static void releaseEspconn(
|
|
struct socketData *pSocketData
|
|
) {
|
|
if (pSocketData->pEspconn == NULL) return;
|
|
// if the socket is an inbound connection then espconn will free the struct, else we do it now
|
|
if (pSocketData->creationType != SOCKET_CREATED_INBOUND) {
|
|
//DBG("%s: freeing espconn %p/%p for socket %d\n", DBG_LIB,
|
|
// pSocketData->pEspconn, pSocketData->pEspconn->proto.tcp, pSocketData->socketId);
|
|
os_free(pSocketData->pEspconn->proto.tcp);
|
|
pSocketData->pEspconn->proto.tcp = NULL;
|
|
os_free(pSocketData->pEspconn);
|
|
}
|
|
pSocketData->pEspconn = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialize the entire socket array
|
|
*/
|
|
void netInit_esp8266_board() {
|
|
if (g_socketsInitialized) return;
|
|
g_socketsInitialized = true;
|
|
os_memset(socketArray, 0, sizeof(socketArray));
|
|
}
|
|
|
|
|
|
/**
|
|
* Perform an actual closure of the socket by calling the ESP8266 disconnect API.
|
|
*/
|
|
static void doClose(
|
|
struct socketData *pSocketData //!< The socket to be closed.
|
|
) {
|
|
if (pSocketData == NULL) return; // just in case
|
|
|
|
// if we're already closing, then don't do anything
|
|
if (pSocketData->state == SOCKET_STATE_CLOSED ||
|
|
pSocketData->state == SOCKET_STATE_DISCONNECTING)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if we're in the name resolution phase, we don't have much to do
|
|
// if we're in aborting ditto
|
|
if (pSocketData->state == SOCKET_STATE_HOST_RESOLVING ||
|
|
pSocketData->state == SOCKET_STATE_ABORTING)
|
|
{
|
|
pSocketData->state = SOCKET_STATE_DISCONNECTING;
|
|
return;
|
|
}
|
|
|
|
// if we need to abort, then do that
|
|
if (pSocketData->state == SOCKET_STATE_TO_ABORT) {
|
|
espconn_abort(pSocketData->pEspconn);
|
|
pSocketData->state = SOCKET_STATE_DISCONNECTING;
|
|
return;
|
|
}
|
|
|
|
// Tell espconn to disconnect/delete the connection
|
|
if (pSocketData->creationType == SOCKET_CREATED_SERVER) {
|
|
//dumpEspConn(pSocketData->pEspconn);
|
|
int rc = espconn_delete(pSocketData->pEspconn);
|
|
if (rc != 0) {
|
|
setSocketInError(pSocketData, rc);
|
|
}
|
|
// we do not get a disconnected callback so we go straight to SOCKET_STATE_UNUSED
|
|
pSocketData->state = SOCKET_STATE_UNUSED;
|
|
pSocketData->creationType = SOCKET_CREATED_NONE;
|
|
|
|
} else {
|
|
int rc = espconn_disconnect(pSocketData->pEspconn);
|
|
if (rc == 0) {
|
|
pSocketData->state = SOCKET_STATE_DISCONNECTING;
|
|
} else {
|
|
setSocketInError(pSocketData, rc);
|
|
pSocketData->state = SOCKET_STATE_UNUSED; // don't expect a callback
|
|
pSocketData->creationType = SOCKET_CREATED_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the given socket as being in error supplying the espconn code.
|
|
* This translates the espconn code to an Espruino socket error code.
|
|
*/
|
|
static void setSocketInError(
|
|
struct socketData *pSocketData, //!< The socket that is being flagged as in error.
|
|
int code //!< The espconn error code
|
|
) {
|
|
assert(pSocketData != NULL);
|
|
assert(pSocketData->state != SOCKET_STATE_UNUSED);
|
|
|
|
if (pSocketData->errorCode != 0) return; // don't overwrite previous error
|
|
int err = code;
|
|
switch (code) {
|
|
case ESPCONN_MEM: err = SOCKET_ERR_MEM; break;
|
|
case ESPCONN_ABRT: err = SOCKET_ERR_RESET; break;
|
|
case ESPCONN_CLSD: err = SOCKET_ERR_CLOSED; break;
|
|
case ESPCONN_IF: err = SOCKET_ERR_UNKNOWN; break;
|
|
case ESPCONN_ISCONN: err = SOCKET_ERR_BUSY; break;
|
|
case ESPCONN_HANDSHAKE: err = SOCKET_ERR_SSL_HAND; break;
|
|
case ESPCONN_SSL_INVALID_DATA: err = SOCKET_ERR_SSL_INVALID; break;
|
|
}
|
|
pSocketData->errorCode = err;
|
|
DBG("%s: error %d->%d on socket %d: %s\n", DBG_LIB,
|
|
code, err, pSocketData->socketId, socketErrorString(err));
|
|
}
|
|
|
|
/**
|
|
* Callback function registered to the ESP8266 environment that is
|
|
* invoked when a new inbound connection has been formed.
|
|
*/
|
|
static void esp8266_callback_connectCB_inbound(
|
|
void *arg //!<
|
|
) {
|
|
struct espconn *pEspconn = (struct espconn *)arg;
|
|
assert(pEspconn != NULL);
|
|
|
|
struct socketData *pClientSocketData = allocateNewSocket();
|
|
if (pClientSocketData == NULL) {
|
|
DBG("%s: out of sockets, dropping inbound connection\n", DBG_LIB);
|
|
espconn_disconnect(pEspconn);
|
|
return;
|
|
}
|
|
DBG("%s: accepted socket %d inbound to port %d from %d.%d.%d.%d:%d\n", DBG_LIB,
|
|
pClientSocketData->socketId, pEspconn->proto.tcp->local_port,
|
|
IP2STR(pEspconn->proto.tcp->remote_ip), pEspconn->proto.tcp->remote_port);
|
|
//dumpEspConn(pEspconn);
|
|
|
|
// register callbacks on the new connection
|
|
if (pEspconn->type == ESPCONN_TCP) {
|
|
espconn_regist_disconcb(pEspconn, esp8266_callback_disconnectCB);
|
|
espconn_regist_reconcb(pEspconn, esp8266_callback_reconnectCB);
|
|
}
|
|
espconn_regist_sentcb(pEspconn, esp8266_callback_sentCB);
|
|
espconn_regist_recvcb(pEspconn, esp8266_callback_recvCB);
|
|
|
|
pClientSocketData->pEspconn = pEspconn;
|
|
pClientSocketData->pEspconn->reverse = pClientSocketData;
|
|
pClientSocketData->creationType = SOCKET_CREATED_INBOUND;
|
|
pClientSocketData->state = SOCKET_STATE_UNACCEPTED;
|
|
}
|
|
|
|
/**
|
|
* Callback function registered to the ESP8266 environment that is
|
|
* invoked when a new outbound connection has been formed.
|
|
*/
|
|
static void esp8266_callback_connectCB_outbound(
|
|
void *arg //!< A pointer to a `struct espconn`.
|
|
) {
|
|
struct espconn *pEspconn = (struct espconn *)arg;
|
|
assert(pEspconn != NULL);
|
|
struct socketData *pSocketData = (struct socketData *)pEspconn->reverse;
|
|
if (pSocketData == NULL) return; // stray callback (possibly after a disconnect)
|
|
DBG("%s: socket %d connected\n", DBG_LIB, pSocketData->socketId);
|
|
|
|
// if we're connecting, then move on, else ignore (could be that we're disconnecting)
|
|
if (pSocketData->state == SOCKET_STATE_CONNECTING) {
|
|
pSocketData->state = SOCKET_STATE_IDLE;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback function registered to the ESP8266 environment that is
|
|
* Invoked when a connection has been disconnected. This does get invoked if we
|
|
* initiated the disconnect (new since SDK 1.5?).
|
|
*/
|
|
static void esp8266_callback_disconnectCB(
|
|
void *arg //!< A pointer to a `struct espconn`.
|
|
) {
|
|
struct espconn *pEspconn = (struct espconn *)arg;
|
|
struct socketData *pSocketData = (struct socketData *)pEspconn->reverse;
|
|
if (pSocketData == NULL) return;
|
|
//if (pEspconn != pSocketData->pEspconn) DBG("%s: pEspconn changed in disconnectCB ***\n", DBG_LIB);
|
|
assert(pSocketData->state != SOCKET_STATE_UNUSED);
|
|
DBG("%s: socket %d disconnected\n", DBG_LIB, pSocketData->socketId);
|
|
|
|
// we can deallocate the espconn structure
|
|
releaseEspconn(pSocketData);
|
|
|
|
// if we were in SOCKET_STATE_DISCONNECTING the socket lib is already done with this socket,
|
|
// so we can free the whole thing. Otherwise, we transition to SOCKET_STATE_CLOSED because
|
|
// we will need to tell the socket lib about the disconnect.
|
|
if (pSocketData->state == SOCKET_STATE_DISCONNECTING) {
|
|
releaseSocket(pSocketData);
|
|
} else {
|
|
// we can deallocate the tx buffer
|
|
if (pSocketData->currentTx != NULL) {
|
|
//DBG("%s: freeing tx buf %p\n", DBG_LIB, pSocketData->currentTx);
|
|
os_free(pSocketData->currentTx);
|
|
pSocketData->currentTx = NULL;
|
|
}
|
|
|
|
pSocketData->state = SOCKET_STATE_CLOSED;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Error handler callback.
|
|
* Although this is called `reconnect` by Espressif, this is really a connection reset callback.
|
|
*/
|
|
static void esp8266_callback_reconnectCB(
|
|
void *arg, //!< A pointer to a `struct espconn`.
|
|
sint8 err //!< The error code.
|
|
) {
|
|
struct espconn *pEspconn = (struct espconn *)arg;
|
|
struct socketData *pSocketData = (struct socketData *)pEspconn->reverse;
|
|
if (pSocketData == NULL) return; // we already closed this.
|
|
//if (pEspconn != pSocketData->pEspconn) DBG("%s: pEspconn changed in reconnectCB ***\n", DBG_LIB);
|
|
DBG("%s: socket %d connection reset: Err %d - %s\n", DBG_LIB,
|
|
pSocketData->socketId, err, esp8266_errorToString(err));
|
|
// Do the same as for a disconnect
|
|
esp8266_callback_disconnectCB(arg);
|
|
// Set the socket state as in error (unless it got freed by esp8266_callback_disconnectCB)
|
|
if (pSocketData->state != SOCKET_STATE_UNUSED)
|
|
setSocketInError(pSocketData, err);
|
|
//DBG("%s: ret from reconnectCB\n", DBG_LIB);
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback function registered to the ESP8266 environment that is
|
|
* invoked when a send operation has been completed. This signals that we can reuse the tx buffer
|
|
* and that we can send the next chunk of data.
|
|
*/
|
|
static void esp8266_callback_sentCB(
|
|
void *arg //!< A pointer to a `struct espconn`.
|
|
) {
|
|
struct espconn *pEspconn = (struct espconn *)arg;
|
|
struct socketData *pSocketData = (struct socketData *)pEspconn->reverse;
|
|
if (pSocketData == NULL) return; // we already closed this.
|
|
//if (pEspconn != pSocketData->pEspconn) DBG("%s: pEspconn changed in sentCB ***\n", DBG_LIB);
|
|
assert(pSocketData->state != SOCKET_STATE_UNUSED);
|
|
|
|
//DBG("%s: socket %d send completed\n", DBG_LIB, pSocketData->socketId);
|
|
|
|
// We have transmitted the data ... which means that the data that was in the transmission
|
|
// buffer can be released.
|
|
if (pSocketData->currentTx != NULL) {
|
|
os_free(pSocketData->currentTx);
|
|
pSocketData->currentTx = NULL;
|
|
}
|
|
|
|
if (pSocketData->state == SOCKET_STATE_TRANSMITTING) {
|
|
pSocketData->state = SOCKET_STATE_IDLE;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* ESP8266 callback function that is invoked when new data has arrived over
|
|
* the TCP/IP connection.
|
|
*/
|
|
static void esp8266_callback_recvCB(
|
|
void *arg, //!< A pointer to a `struct espconn`.
|
|
char *pData, //!< A pointer to data received over the socket.
|
|
unsigned short len //!< The length of the data.
|
|
) {
|
|
struct espconn *pEspconn = (struct espconn *)arg;
|
|
struct socketData *pSocketData = (struct socketData *)pEspconn->reverse;
|
|
if (pSocketData == NULL) return; // we closed this socket
|
|
//if (pEspconn != pSocketData->pEspconn) DBG("%s: pEspconn changed in recvCB ***\n", DBG_LIB);
|
|
assert(pSocketData->state != SOCKET_STATE_UNUSED);
|
|
|
|
//DBG("%s: socket %d recv %d\n", DBG_LIB, pSocketData->socketId, len);
|
|
//DBG("%s: recv data: %p\n", DBG_LIB, pData);
|
|
|
|
// if this is a dead connection then just ignore the callback
|
|
if (pSocketData->state == SOCKET_STATE_ABORTING ||
|
|
pSocketData->state == SOCKET_STATE_TO_ABORT ||
|
|
pSocketData->state == SOCKET_STATE_CLOSED ||
|
|
pSocketData->state == SOCKET_STATE_DISCONNECTING) {
|
|
return;
|
|
}
|
|
|
|
bool isUDP = pEspconn->type == ESPCONN_UDP;
|
|
if (isUDP && len > 536/2 /*chunkSize*/) {
|
|
jsWarn("Recv %d too long (UDP)!\n", len);
|
|
return;
|
|
}
|
|
|
|
// Allocate a buffer and add to the receive queue
|
|
PktBuf *buf = PktBuf_New(len);
|
|
if (!buf) {
|
|
// handle out of memory condition
|
|
DBG("%s: Out of memory allocating %d for recv\n", DBG_LIB, len);
|
|
// at this point we're gonna deallocate all receive buffers as a panic measure
|
|
while (pSocketData->rxBufQ != NULL)
|
|
pSocketData->rxBufQ = PktBuf_ShiftFree(pSocketData->rxBufQ);
|
|
// save the error
|
|
setSocketInError(pSocketData, ESPCONN_MEM);
|
|
// now reset the connection
|
|
//espconn_abort(pEspconn); // can't do this: espconn crashes!
|
|
pSocketData->state = SOCKET_STATE_TO_ABORT; // some function called from socket lib will abort
|
|
//DBG("%s: ret from recvCB\n", DBG_LIB);
|
|
return;
|
|
}
|
|
|
|
if (isUDP) {
|
|
// UDP remote host/port
|
|
remot_info *premot = NULL;
|
|
if (espconn_get_connection_info(pEspconn, &premot, 0) == ESPCONN_OK) {
|
|
pSocketData->host = *(uint32_t *)&premot->remote_ip;
|
|
pSocketData->port = *(unsigned short *)&premot->remote_port;
|
|
}
|
|
}
|
|
|
|
DBG("%s: Recv %d to %x:%d\n", DBG_LIB, len, pSocketData->host, pSocketData->port);
|
|
|
|
// if this is the second buffer then stop the flood!
|
|
if (pSocketData->rxBufQ != NULL) espconn_recv_hold(pEspconn);
|
|
// got buffer, fill it
|
|
os_memcpy(buf->data, pData, len);
|
|
buf->filled = len;
|
|
pSocketData->rxBufQ = PktBuf_Push(pSocketData->rxBufQ, buf);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------
|
|
|
|
/**
|
|
* Define the implementation functions for the logical network functions.
|
|
*/
|
|
void netSetCallbacks_esp8266_board(
|
|
JsNetwork *net //!< The Network we are going to use.
|
|
) {
|
|
net->idle = net_ESP8266_BOARD_idle;
|
|
net->checkError = net_ESP8266_BOARD_checkError;
|
|
net->createsocket = net_ESP8266_BOARD_createSocket;
|
|
net->closesocket = net_ESP8266_BOARD_closeSocket;
|
|
net->accept = net_ESP8266_BOARD_accept;
|
|
net->gethostbyname = net_ESP8266_BOARD_gethostbyname;
|
|
net->recv = net_ESP8266_BOARD_recv;
|
|
net->send = net_ESP8266_BOARD_send;
|
|
// The TCP MSS is 536, we use half that 'cause otherwise we easily run out of JSvars memory
|
|
net->chunkSize = 536/2;
|
|
}
|
|
|
|
/**
|
|
* Determine if there is a new client connection on the server socket.
|
|
* This function is called to poll to see if the serverSckt has a new
|
|
* accepted connection (socket) and, if it does, return it else return -1 to indicate
|
|
* that there was no new accepted socket.
|
|
*/
|
|
int net_ESP8266_BOARD_accept(
|
|
JsNetwork *net, //!< The Network we are going to use to create the socket.
|
|
int serverSckt //!< The socket that we are checking to see if there is a new client connection.
|
|
) {
|
|
struct socketData *pSocketData = getSocketData(serverSckt);
|
|
assert(pSocketData->state != SOCKET_STATE_UNUSED);
|
|
assert(pSocketData->creationType == SOCKET_CREATED_SERVER);
|
|
|
|
// iterate through all sockets and see whether there is one in the UNACCEPTED state that is for
|
|
// the server socket's local port.
|
|
uint16_t serverPort = pSocketData->pEspconn->proto.tcp->local_port;
|
|
for (uint8_t i=0; i<MAX_SOCKETS; i++) {
|
|
if (socketArray[i].state == SOCKET_STATE_UNACCEPTED &&
|
|
socketArray[i].pEspconn != NULL &&
|
|
socketArray[i].pEspconn->proto.tcp->local_port == serverPort)
|
|
{
|
|
DBG("%s: Accepted socket %d\n", DBG_LIB, socketArray[i].socketId);
|
|
socketArray[i].state = SOCKET_STATE_IDLE;
|
|
return socketArray[i].socketId;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Receive data from the network device.
|
|
* Returns the number of bytes received which may be 0 and <0 if there was an error.
|
|
*/
|
|
int net_ESP8266_BOARD_recv(
|
|
JsNetwork *net, //!< The Network we are going to use to create the socket.
|
|
SocketType socketType, //!< The socket type bitmask
|
|
int sckt, //!< The socket from which we are to receive data.
|
|
void *buf, //!< The storage buffer into which we will receive data.
|
|
size_t len //!< The length of the buffer.
|
|
) {
|
|
//DBG("%s:recv\n", DBG_LIB);
|
|
struct socketData *pSocketData = getSocketData(sckt);
|
|
assert(pSocketData);
|
|
assert(pSocketData->state != SOCKET_STATE_UNUSED);
|
|
|
|
// handle socket that needs aborting
|
|
if (pSocketData->state == SOCKET_STATE_TO_ABORT) {
|
|
espconn_abort(pSocketData->pEspconn);
|
|
return pSocketData->errorCode;
|
|
}
|
|
|
|
// If there is no data in the receive buffer, then all we need do is return
|
|
// 0 bytes as the length of data moved or -1 if the socket is actually closed.
|
|
if (pSocketData->rxBufQ == NULL) {
|
|
switch (pSocketData->state) {
|
|
case SOCKET_STATE_CLOSED:
|
|
return pSocketData->errorCode != 0 ? pSocketData->errorCode : SOCKET_ERR_CLOSED;
|
|
case SOCKET_STATE_DISCONNECTING:
|
|
case SOCKET_STATE_ABORTING:
|
|
return pSocketData->errorCode;
|
|
case SOCKET_STATE_HOST_RESOLVING:
|
|
case SOCKET_STATE_CONNECTING:
|
|
return SOCKET_ERR_NO_CONN;
|
|
default:
|
|
return 0; // we just have no data
|
|
}
|
|
}
|
|
PktBuf *rxBuf = pSocketData->rxBufQ;
|
|
|
|
size_t delta = 0;
|
|
if (socketType & ST_UDP) {
|
|
delta = sizeof(uint32_t) + sizeof(unsigned short) + sizeof(uint16_t);
|
|
uint32_t *host = (uint32_t*)buf;
|
|
unsigned short *port = (unsigned short*)&host[1];
|
|
uint16_t *size = (uint16_t*)&port[1];
|
|
buf += delta;
|
|
len -= delta;
|
|
// UDP remote host/port
|
|
*host = pSocketData->host;
|
|
*port = pSocketData->port;
|
|
*size = rxBuf->filled;
|
|
}
|
|
|
|
// If the receive buffer is able to completely fit in the buffer
|
|
// passed into us then we can copy all the data and the receive buffer will be clear.
|
|
if (rxBuf->filled <= len) {
|
|
os_memcpy(buf, rxBuf->data, rxBuf->filled);
|
|
uint16_t retLen = rxBuf->filled;
|
|
pSocketData->rxBufQ = PktBuf_ShiftFree(rxBuf);
|
|
// if we now have exactly one buffer enqueued we need to re-enable the flood
|
|
if (pSocketData->rxBufQ != NULL && pSocketData->rxBufQ->next == NULL)
|
|
espconn_recv_unhold(pSocketData->pEspconn);
|
|
//DBG("%s: socket %d JS recv %d\n", DBG_LIB, sckt, retLen);
|
|
return retLen + delta;
|
|
}
|
|
|
|
// If we are here, then we have more data in the receive buffer than is available
|
|
// to be returned in this request for data. So we have to copy the amount of data
|
|
// that is allowed to be returned and then strip that from the beginning of the
|
|
// receive buffer.
|
|
|
|
// First we copy the data we are going to return.
|
|
os_memcpy(buf, rxBuf->data, len);
|
|
// Next we shift up the remaining data
|
|
uint16_t newLen = rxBuf->filled - len;
|
|
os_memmove(rxBuf->data, rxBuf->data + len, newLen);
|
|
rxBuf->filled = newLen;
|
|
//DBG("%s: socket %d JS recv %d\n", DBG_LIB, sckt, len);
|
|
return len + delta;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send data to the partner.
|
|
* The return is the number of bytes actually transmitted which may also be
|
|
* 0 to indicate no bytes sent or -1 to indicate an error. For the ESP8266 implementation we
|
|
* will return 0 if the socket is not connected or we are in the `SOCKET_STATE_TRANSMITTING`
|
|
* state.
|
|
*/
|
|
int net_ESP8266_BOARD_send(
|
|
JsNetwork *net, //!< The Network we are going to use to create the socket.
|
|
SocketType socketType, //!< The socket type bitmask
|
|
int sckt, //!< The socket over which we will send data.
|
|
const void *buf, //!< The buffer containing the data to be sent.
|
|
size_t len //!< The length of data in the buffer to send.
|
|
) {
|
|
//DBG("%s:send\n", DBG_LIB);
|
|
struct socketData *pSocketData = getSocketData(sckt);
|
|
assert(pSocketData->state != SOCKET_STATE_UNUSED);
|
|
|
|
DBG("%s:send state:%d err:%d\n", DBG_LIB, pSocketData->state, pSocketData->errorCode);
|
|
|
|
// If the socket is in error or it is closing return -1
|
|
switch (pSocketData->state) {
|
|
case SOCKET_STATE_CLOSED:
|
|
case SOCKET_STATE_DISCONNECTING:
|
|
return pSocketData->errorCode != 0 ? pSocketData->errorCode : SOCKET_ERR_CLOSED;
|
|
case SOCKET_STATE_ABORTING:
|
|
return pSocketData->errorCode;
|
|
case SOCKET_STATE_TO_ABORT:
|
|
espconn_abort(pSocketData->pEspconn);
|
|
return pSocketData->errorCode;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Unless we are in the idle state, we can't send more shtuff
|
|
if (pSocketData->state != SOCKET_STATE_IDLE) {
|
|
return 0;
|
|
}
|
|
|
|
// Log the content of the data we are sending.
|
|
//esp8266_board_writeString(buf, len);
|
|
//os_printf("\n");
|
|
size_t delta = 0;
|
|
if (socketType & ST_UDP) {
|
|
delta = sizeof(uint32_t) + sizeof(unsigned short) + sizeof(uint16_t);
|
|
uint32_t *host = (uint32_t*)buf;
|
|
unsigned short *port = (unsigned short*)&host[1];
|
|
uint16_t *size = (uint16_t*)&port[1];
|
|
|
|
// UDP remote IP/port need to be set everytime we call espconn_send
|
|
*(uint32_t *)&pSocketData->pEspconn->proto.tcp->remote_ip = *host;
|
|
pSocketData->pEspconn->proto.tcp->remote_port = *port;
|
|
|
|
buf += delta;
|
|
len = *size;
|
|
DBG("%s: Sendto %d to %x:%d\n", DBG_LIB, len, *host, *port);
|
|
}
|
|
|
|
// Copy the data to be sent into a transmit buffer we hand off to espconn
|
|
assert(pSocketData->currentTx == NULL);
|
|
pSocketData->currentTx = (uint8_t *)os_malloc(len);
|
|
if (pSocketData->currentTx == NULL) {
|
|
DBG("%s: Out of memory sending %d on socket %d\n", DBG_LIB, len, sckt);
|
|
setSocketInError(pSocketData, ESPCONN_MEM);
|
|
espconn_abort(pSocketData->pEspconn);
|
|
pSocketData->state = SOCKET_STATE_ABORTING;
|
|
return pSocketData->errorCode;
|
|
}
|
|
memcpy(pSocketData->currentTx, buf, len);
|
|
|
|
// Set transmitting now as the sentCB is called synchronously from inside
|
|
// espconn_send for UDP
|
|
pSocketData->state = SOCKET_STATE_TRANSMITTING;
|
|
|
|
// Send the data over the ESP8266 SDK.
|
|
int rc = espconn_send(pSocketData->pEspconn, pSocketData->currentTx, len);
|
|
if (rc < 0) {
|
|
setSocketInError(pSocketData, rc);
|
|
os_free(pSocketData->currentTx);
|
|
pSocketData->currentTx = NULL;
|
|
espconn_abort(pSocketData->pEspconn);
|
|
pSocketData->state = SOCKET_STATE_ABORTING;
|
|
return rc;
|
|
}
|
|
|
|
//DBG("%s: socket %d JS send %d\n", DBG_LIB, sckt, len);
|
|
return len + delta;
|
|
}
|
|
|
|
|
|
/**
|
|
* Perform idle processing.
|
|
* There is the possibility that we may wish to perform logic when we are idle. For the
|
|
* ESP8266 there is no specific idle network processing needed.
|
|
*/
|
|
void net_ESP8266_BOARD_idle(
|
|
JsNetwork *net //!< The Network we are part of.
|
|
) {
|
|
// Don't echo here because it is called continuously
|
|
//os_printf("> net_ESP8266_BOARD_idle\n");
|
|
}
|
|
|
|
|
|
/**
|
|
* Check for errors.
|
|
* Returns true if there are NO errors.
|
|
*/
|
|
bool net_ESP8266_BOARD_checkError(
|
|
JsNetwork *net //!< The Network we are checking.
|
|
) {
|
|
//os_printf("> net_ESP8266_BOARD_checkError\n");
|
|
return true;
|
|
}
|
|
|
|
/* Static variable hack to support async DNS resolutions. This is not great, but it works.
|
|
* There is only one call to net_ESP8266_BOARD_gethostbyname and it is immediately followed
|
|
* by a call to net_ESP8266_BOARD_createSocket, so we save the hostname from the first call
|
|
* in a global variable and then use it in the second to actually kick off the name resolution.
|
|
*/
|
|
static char *savedHostname = 0;
|
|
|
|
/**
|
|
* Get an IP address from a name. See the hack description above. This always returns -1
|
|
*/
|
|
void net_ESP8266_BOARD_gethostbyname(
|
|
JsNetwork *net, //!< The Network we are going to use to create the socket.
|
|
char *hostname, //!< The string representing the hostname we wish to lookup.
|
|
uint32_t *outIp //!< The address into which the resolved IP address will be stored.
|
|
) {
|
|
assert(hostname != NULL);
|
|
savedHostname = hostname;
|
|
*outIp = -1;
|
|
}
|
|
|
|
/**
|
|
* Callback handler for espconn_gethostbyname.
|
|
*/
|
|
static void dnsFoundCallback(const char *hostName, ip_addr_t *ipAddr, void *arg) {
|
|
assert(arg != NULL); // arg points to the espconn struct where the resolved IP address needs to go
|
|
struct espconn *pEspconn = arg;
|
|
struct socketData *pSocketData = pEspconn->reverse;
|
|
|
|
if (pSocketData->state == SOCKET_STATE_DISCONNECTING) {
|
|
// the sockte library closed the socket while we were resolving, we now need to deallocate
|
|
releaseEspconn(pSocketData);
|
|
releaseSocket(pSocketData);
|
|
return;
|
|
}
|
|
if (pSocketData->state != SOCKET_STATE_HOST_RESOLVING) return; // not sure what happened
|
|
|
|
// ipAddr will be NULL if the IP address can not be resolved.
|
|
if (ipAddr != NULL) {
|
|
*(uint32_t *)&pEspconn->proto.tcp->remote_ip = ipAddr->addr;
|
|
if (pSocketData != NULL) connectSocket(pSocketData);
|
|
} else {
|
|
releaseEspconn(pSocketData);
|
|
if (pSocketData != NULL) {
|
|
setSocketInError(pSocketData, SOCKET_ERR_NOT_FOUND);
|
|
pSocketData->state = SOCKET_STATE_CLOSED;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a new socket.
|
|
* if `ipAddress == 0`, creates a server otherwise creates a client (and automatically connects).
|
|
* Returns >=0 on success.
|
|
*/
|
|
int net_ESP8266_BOARD_createSocket(
|
|
JsNetwork *net, //!< The Network we are going to use to create the socket.
|
|
SocketType socketType, //!< The socket type bitmask
|
|
uint32_t ipAddress, //!< The address of the partner of the socket or 0 if we are to be a server.
|
|
unsigned short port,//!< The port number that the partner is listening upon.
|
|
JsVar *options
|
|
) {
|
|
// allocate a socket data structure
|
|
struct socketData *pSocketData = allocateNewSocket();
|
|
if (pSocketData == NULL) { // No free socket
|
|
DBG("%s: No free sockets for outbound connection\n", DBG_LIB);
|
|
return SOCKET_ERR_MAX_SOCK;
|
|
}
|
|
|
|
// allocate espconn data structure and initialize it
|
|
struct espconn *pEspconn = os_zalloc(sizeof(struct espconn));
|
|
|
|
esp_tcp *tcp = os_zalloc(sizeof(esp_tcp));
|
|
if (pEspconn == NULL || tcp == NULL) {
|
|
DBG("%s: Out of memory for outbound connection\n", DBG_LIB);
|
|
if (pEspconn != NULL) os_free(pEspconn);
|
|
if (tcp != NULL) os_free(tcp);
|
|
releaseSocket(pSocketData);
|
|
return SOCKET_ERR_MEM;
|
|
}
|
|
|
|
if (socketType & ST_UDP) {
|
|
pEspconn->type = ESPCONN_UDP;
|
|
|
|
// esp_tcp and esp_udp start identically (up to remote_ip)
|
|
// so we can leave the proto.tcp as an alias to proto.udp
|
|
} else {
|
|
pEspconn->type = ESPCONN_TCP;
|
|
|
|
espconn_set_opt(pEspconn, ESPCONN_NODELAY); // disable nagle, don't need the extra delay
|
|
}
|
|
|
|
pSocketData->pEspconn = pEspconn;
|
|
pEspconn->state = ESPCONN_NONE;
|
|
pEspconn->proto.tcp = tcp;
|
|
tcp->remote_port = port;
|
|
tcp->local_port = espconn_port(); // using 0 doesn't work
|
|
pEspconn->reverse = pSocketData;
|
|
|
|
// multicast support
|
|
// FIXME: perhaps extend the JsNetwork with addmembership/removemembership instead of using options
|
|
JsVar *mgrpVar = jsvObjectGetChild(options, "multicastGroup", 0);
|
|
if (mgrpVar) {
|
|
char ipStr[18];
|
|
|
|
jsvGetString(mgrpVar, ipStr, sizeof(ipStr));
|
|
jsvUnLock(mgrpVar);
|
|
uint32_t grpip = networkParseIPAddress(ipStr);
|
|
|
|
JsVar *ipVar = jsvObjectGetChild(options, "multicastIp", 0);
|
|
jsvGetString(ipVar, ipStr, sizeof(ipStr));
|
|
jsvUnLock(ipVar);
|
|
uint32_t ip = networkParseIPAddress(ipStr);
|
|
|
|
pSocketData->multicastGrpIp = grpip;
|
|
pSocketData->multicastIp = ip;
|
|
}
|
|
|
|
if (ipAddress == (uint32_t)-1) {
|
|
// We need DNS resolution, kick it off
|
|
int rc = espconn_gethostbyname(pEspconn, savedHostname,
|
|
(void*)&pEspconn->proto.tcp->remote_ip, dnsFoundCallback);
|
|
if (rc < 0) {
|
|
}
|
|
DBG("%s: resolving %s\n", DBG_LIB, savedHostname);
|
|
pSocketData->state = SOCKET_STATE_HOST_RESOLVING;
|
|
return pSocketData->socketId;
|
|
} else {
|
|
// No DNS resolution needed, go right ahead
|
|
*(uint32_t *)&pEspconn->proto.tcp->remote_ip = ipAddress;
|
|
return connectSocket(pSocketData);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Continue creating a socket, the name resolution having completed
|
|
*/
|
|
static int connectSocket(
|
|
struct socketData *pSocketData //!< Allocated socket data structure
|
|
) {
|
|
struct espconn *pEspconn = pSocketData->pEspconn;
|
|
bool isServer = *(uint32_t *)&pEspconn->proto.tcp->remote_ip == 0;
|
|
int rc;
|
|
|
|
int newSocket = pSocketData->socketId;
|
|
assert(pSocketData->rxBufQ == NULL);
|
|
assert(pSocketData->currentTx == NULL);
|
|
|
|
espconn_regist_sentcb(pEspconn, esp8266_callback_sentCB);
|
|
espconn_regist_recvcb(pEspconn, esp8266_callback_recvCB);
|
|
|
|
// If we are a client
|
|
if (!isServer) {
|
|
pSocketData->state = SOCKET_STATE_CONNECTING;
|
|
pSocketData->creationType = SOCKET_CREATED_OUTBOUND;
|
|
|
|
if (pEspconn->type == ESPCONN_TCP) {
|
|
espconn_regist_connectcb(pEspconn, esp8266_callback_connectCB_outbound);
|
|
espconn_regist_disconcb(pEspconn, esp8266_callback_disconnectCB);
|
|
espconn_regist_reconcb(pEspconn, esp8266_callback_reconnectCB);
|
|
|
|
// Make a call to espconn_connect.
|
|
#if 0
|
|
DBG("%s: connecting socket %d/%p/%p to %d.%d.%d.%d:%d from :%d\n",
|
|
DBG_LIB, pSocketData->socketId, pSocketData, pEspconn,
|
|
IP2STR(pEspconn->proto.tcp->remote_ip), pEspconn->proto.tcp->remote_port,
|
|
pEspconn->proto.tcp->local_port);
|
|
#endif
|
|
rc = espconn_connect(pEspconn);
|
|
} else {
|
|
rc = espconn_create(pEspconn);
|
|
}
|
|
if (rc != 0) {
|
|
DBG("%s: error %d connecting socket %d: %s\n", DBG_LIB,
|
|
rc, pSocketData->socketId, esp8266_errorToString(rc));
|
|
releaseEspconn(pSocketData);
|
|
releaseSocket(pSocketData);
|
|
return rc;
|
|
}
|
|
DBG("%s: connecting socket %d to %d.%d.%d.%d:%d\n", DBG_LIB, pSocketData->socketId,
|
|
IP2STR(pEspconn->proto.tcp->remote_ip), pEspconn->proto.tcp->remote_port);
|
|
}
|
|
|
|
// If the ipAddress IS 0 ... then we are a server.
|
|
else {
|
|
// We are going to set ourselves up as a server
|
|
pSocketData->state = SOCKET_STATE_IDLE;
|
|
pSocketData->creationType = SOCKET_CREATED_SERVER;
|
|
pEspconn->proto.tcp->local_port = pEspconn->proto.tcp->remote_port;
|
|
pEspconn->proto.tcp->remote_port = 0;
|
|
|
|
if (pEspconn->type == ESPCONN_TCP) {
|
|
espconn_regist_connectcb(pEspconn, esp8266_callback_connectCB_inbound);
|
|
|
|
// Make a call to espconn_accept (this should really be called espconn_listen, sigh)
|
|
rc = espconn_accept(pEspconn);
|
|
if (rc != 0) {
|
|
DBG("%s: error %d creating listening socket %d: %s\n", DBG_LIB,
|
|
rc, pSocketData->socketId, esp8266_errorToString(rc));
|
|
releaseEspconn(pSocketData);
|
|
releaseSocket(pSocketData);
|
|
return rc;
|
|
}
|
|
espconn_regist_time(pEspconn, 600, 0);
|
|
} else {
|
|
rc = espconn_create(pEspconn);
|
|
if (rc != 0) {
|
|
DBG("%s: error %d creating listening socket %d: %s\n", DBG_LIB,
|
|
rc, pSocketData->socketId, esp8266_errorToString(rc));
|
|
releaseEspconn(pSocketData);
|
|
releaseSocket(pSocketData);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (pSocketData->multicastGrpIp) {
|
|
// multicast support
|
|
espconn_igmp_join((ip_addr_t *)&pSocketData->multicastIp,
|
|
(ip_addr_t *)&pSocketData->multicastGrpIp);
|
|
}
|
|
|
|
DBG("%s: listening socket %d on port %d\n", DBG_LIB,
|
|
pSocketData->socketId, pEspconn->proto.tcp->local_port);
|
|
}
|
|
|
|
return newSocket;
|
|
}
|
|
|
|
/**
|
|
* Close a socket.
|
|
* This gets called in two situations: when the user requests the close of a socket and as
|
|
* an acknowledgment after we signal the socket library that a connection has closed by
|
|
* returning <0 to a send or recv call.
|
|
*/
|
|
void net_ESP8266_BOARD_closeSocket(
|
|
JsNetwork *net, //!< The Network we are going to use to create the socket.
|
|
int socketId //!< The socket to be closed.
|
|
) {
|
|
struct socketData *pSocketData = getSocketData(socketId);
|
|
assert(pSocketData != NULL); // We had better have found a socket to be closed.
|
|
assert(pSocketData->state != SOCKET_STATE_UNUSED); // Shouldn't be closing an unused socket.
|
|
|
|
if (pSocketData->state == SOCKET_STATE_CLOSED) {
|
|
// In these states we have already freed the espconn structures, so all that's left is to
|
|
// free the socket structure
|
|
//DBG("%s: socket %d close acknowledged\n", DBG_LIB, pSocketData->socketId);
|
|
releaseSocket(pSocketData);
|
|
} else {
|
|
// Looks like this is the user telling us to close a connection, let's do it.
|
|
DBG("%s: socket %d to be closed\n", DBG_LIB, pSocketData->socketId);
|
|
doClose(pSocketData);
|
|
}
|
|
}
|