Added files

This commit is contained in:
2016-03-10 23:49:47 +01:00
commit adea64ef9c
1492 changed files with 13182 additions and 0 deletions
+331
View File
@@ -0,0 +1,331 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
/*
This is a 'captive portal' DNS server: it basically replies with a fixed IP (in this case:
the one of the SoftAP interface of this ESP module) for any and all DNS queries. This can
be used to send mobile phones, tablets etc which connect to the ESP in AP mode directly to
the internal webserver.
*/
#include <esp8266.h>
#ifdef FREERTOS
#include "espressif/esp_common.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "lwip/sockets.h"
#include "lwip/err.h"
static int sockFd;
#endif
#define DNS_LEN 512
typedef struct __attribute__ ((packed)) {
uint16_t id;
uint8_t flags;
uint8_t rcode;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
} DnsHeader;
typedef struct __attribute__ ((packed)) {
uint8_t len;
uint8_t data;
} DnsLabel;
typedef struct __attribute__ ((packed)) {
//before: label
uint16_t type;
uint16_t class;
} DnsQuestionFooter;
typedef struct __attribute__ ((packed)) {
//before: label
uint16_t type;
uint16_t class;
uint32_t ttl;
uint16_t rdlength;
//after: rdata
} DnsResourceFooter;
typedef struct __attribute__ ((packed)) {
uint16_t prio;
uint16_t weight;
} DnsUriHdr;
#define FLAG_QR (1<<7)
#define FLAG_AA (1<<2)
#define FLAG_TC (1<<1)
#define FLAG_RD (1<<0)
#define QTYPE_A 1
#define QTYPE_NS 2
#define QTYPE_CNAME 5
#define QTYPE_SOA 6
#define QTYPE_WKS 11
#define QTYPE_PTR 12
#define QTYPE_HINFO 13
#define QTYPE_MINFO 14
#define QTYPE_MX 15
#define QTYPE_TXT 16
#define QTYPE_URI 256
#define QCLASS_IN 1
#define QCLASS_ANY 255
#define QCLASS_URI 256
//Function to put unaligned 16-bit network values
static void ICACHE_FLASH_ATTR setn16(void *pp, int16_t n) {
char *p=pp;
*p++=(n>>8);
*p++=(n&0xff);
}
//Function to put unaligned 32-bit network values
static void ICACHE_FLASH_ATTR setn32(void *pp, int32_t n) {
char *p=pp;
*p++=(n>>24)&0xff;
*p++=(n>>16)&0xff;
*p++=(n>>8)&0xff;
*p++=(n&0xff);
}
static uint16_t ICACHE_FLASH_ATTR my_ntohs(uint16_t *in) {
char *p=(char*)in;
return ((p[0]<<8)&0xff00)|(p[1]&0xff);
}
//Parses a label into a C-string containing a dotted
//Returns pointer to start of next fields in packet
static char* ICACHE_FLASH_ATTR labelToStr(char *packet, char *labelPtr, int packetSz, char *res, int resMaxLen) {
int i, j, k;
char *endPtr=NULL;
i=0;
do {
if ((*labelPtr&0xC0)==0) {
j=*labelPtr++; //skip past length
//Add separator period if there already is data in res
if (i<resMaxLen && i!=0) res[i++]='.';
//Copy label to res
for (k=0; k<j; k++) {
if ((labelPtr-packet)>packetSz) return NULL;
if (i<resMaxLen) res[i++]=*labelPtr++;
}
} else if ((*labelPtr&0xC0)==0xC0) {
//Compressed label pointer
endPtr=labelPtr+2;
int offset=my_ntohs(((uint16_t *)labelPtr))&0x3FFF;
//Check if offset points to somewhere outside of the packet
if (offset>packetSz) return NULL;
labelPtr=&packet[offset];
}
//check for out-of-bound-ness
if ((labelPtr-packet)>packetSz) return NULL;
} while (*labelPtr!=0);
res[i]=0; //zero-terminate
if (endPtr==NULL) endPtr=labelPtr+1;
return endPtr;
}
//Converts a dotted hostname to the weird label form dns uses.
static char ICACHE_FLASH_ATTR *strToLabel(char *str, char *label, int maxLen) {
char *len=label; //ptr to len byte
char *p=label+1; //ptr to next label byte to be written
while (1) {
if (*str=='.' || *str==0) {
*len=((p-len)-1); //write len of label bit
len=p; //pos of len for next part
p++; //data ptr is one past len
if (*str==0) break; //done
str++;
} else {
*p++=*str++; //copy byte
// if ((p-label)>maxLen) return NULL; //check out of bounds
}
}
*len=0;
return p; //ptr to first free byte in resp
}
//Receive a DNS packet and maybe send a response back
#ifndef FREERTOS
static void ICACHE_FLASH_ATTR captdnsRecv(void* arg, char *pusrdata, unsigned short length) {
struct espconn *conn=(struct espconn *)arg;
#else
static void ICACHE_FLASH_ATTR captdnsRecv(struct sockaddr_in *premote_addr, char *pusrdata, unsigned short length) {
#endif
char buff[DNS_LEN];
char reply[DNS_LEN];
int i;
char *rend=&reply[length];
char *p=pusrdata;
DnsHeader *hdr=(DnsHeader*)p;
DnsHeader *rhdr=(DnsHeader*)&reply[0];
p+=sizeof(DnsHeader);
// httpd_printf("DNS packet: id 0x%X flags 0x%X rcode 0x%X qcnt %d ancnt %d nscount %d arcount %d len %d\n",
// my_ntohs(&hdr->id), hdr->flags, hdr->rcode, my_ntohs(&hdr->qdcount), my_ntohs(&hdr->ancount), my_ntohs(&hdr->nscount), my_ntohs(&hdr->arcount), length);
//Some sanity checks:
if (length>DNS_LEN) return; //Packet is longer than DNS implementation allows
if (length<sizeof(DnsHeader)) return; //Packet is too short
if (hdr->ancount || hdr->nscount || hdr->arcount) return; //this is a reply, don't know what to do with it
if (hdr->flags&FLAG_TC) return; //truncated, can't use this
//Reply is basically the request plus the needed data
memcpy(reply, pusrdata, length);
rhdr->flags|=FLAG_QR;
for (i=0; i<my_ntohs(&hdr->qdcount); i++) {
//Grab the labels in the q string
p=labelToStr(pusrdata, p, length, buff, sizeof(buff));
if (p==NULL) return;
DnsQuestionFooter *qf=(DnsQuestionFooter*)p;
p+=sizeof(DnsQuestionFooter);
httpd_printf("DNS: Q (type 0x%X class 0x%X) for %s\n", my_ntohs(&qf->type), my_ntohs(&qf->class), buff);
if (my_ntohs(&qf->type)==QTYPE_A) {
//They want to know the IPv4 address of something.
//Build the response.
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
if (rend==NULL) return;
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
rend+=sizeof(DnsResourceFooter);
setn16(&rf->type, QTYPE_A);
setn16(&rf->class, QCLASS_IN);
setn32(&rf->ttl, 0);
setn16(&rf->rdlength, 4); //IPv4 addr is 4 bytes;
//Grab the current IP of the softap interface
struct ip_info info;
wifi_get_ip_info(SOFTAP_IF, &info);
*rend++=ip4_addr1(&info.ip);
*rend++=ip4_addr2(&info.ip);
*rend++=ip4_addr3(&info.ip);
*rend++=ip4_addr4(&info.ip);
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
// httpd_printf("Added A rec to resp. Resp len is %d\n", (rend-reply));
} else if (my_ntohs(&qf->type)==QTYPE_NS) {
//Give ns server. Basically can be whatever we want because it'll get resolved to our IP later anyway.
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
rend+=sizeof(DnsResourceFooter);
setn16(&rf->type, QTYPE_NS);
setn16(&rf->class, QCLASS_IN);
setn16(&rf->ttl, 0);
setn16(&rf->rdlength, 4);
*rend++=2;
*rend++='n';
*rend++='s';
*rend++=0;
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
// httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
} else if (my_ntohs(&qf->type)==QTYPE_URI) {
//Give uri to us
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
rend+=sizeof(DnsResourceFooter);
DnsUriHdr *uh=(DnsUriHdr *)rend;
rend+=sizeof(DnsUriHdr);
setn16(&rf->type, QTYPE_URI);
setn16(&rf->class, QCLASS_URI);
setn16(&rf->ttl, 0);
setn16(&rf->rdlength, 4+16);
setn16(&uh->prio, 10);
setn16(&uh->weight, 1);
memcpy(rend, "http://esp.nonet", 16);
rend+=16;
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
// httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
}
}
//Send the response
#ifndef FREERTOS
remot_info *remInfo=NULL;
//Send data to port/ip it came from, not to the ip/port we listen on.
if (espconn_get_connection_info(conn, &remInfo, 0)==ESPCONN_OK) {
conn->proto.udp->remote_port=remInfo->remote_port;
memcpy(conn->proto.udp->remote_ip, remInfo->remote_ip, sizeof(remInfo->remote_ip));
}
espconn_sendto(conn, (uint8*)reply, rend-reply);
#else
sendto(sockFd,(uint8*)reply, rend-reply, 0, (struct sockaddr *)premote_addr, sizeof(struct sockaddr_in));
#endif
}
#ifdef FREERTOS
static void captdnsTask(void *pvParameters) {
struct sockaddr_in server_addr;
int32 ret;
struct sockaddr_in from;
socklen_t fromlen;
struct ip_info ipconfig;
char udp_msg[DNS_LEN];
memset(&ipconfig, 0, sizeof(ipconfig));
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(53);
server_addr.sin_len = sizeof(server_addr);
do {
sockFd=socket(AF_INET, SOCK_DGRAM, 0);
if (sockFd==-1) {
httpd_printf("captdns_task failed to create sock!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while (sockFd==-1);
do {
ret=bind(sockFd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret!=0) {
httpd_printf("captdns_task failed to bind sock!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while (ret!=0);
httpd_printf("CaptDNS inited.\n");
while(1) {
memset(&from, 0, sizeof(from));
fromlen=sizeof(struct sockaddr_in);
ret=recvfrom(sockFd, (u8 *)udp_msg, DNS_LEN, 0,(struct sockaddr *)&from,(socklen_t *)&fromlen);
if (ret>0) captdnsRecv(&from,udp_msg,ret);
}
close(sockFd);
vTaskDelete(NULL);
}
void captdnsInit(void) {
xTaskCreate(captdnsTask, (const signed char *)"captdns_task", 1200, NULL, 3, NULL);
}
#else
void ICACHE_FLASH_ATTR captdnsInit(void) {
static struct espconn conn;
static esp_udp udpconn;
conn.type=ESPCONN_UDP;
conn.proto.udp=&udpconn;
conn.proto.udp->local_port = 53;
espconn_regist_recvcb(&conn, captdnsRecv);
espconn_create(&conn);
}
#endif
+323
View File
@@ -0,0 +1,323 @@
/*
Some flash handling cgi routines. Used for reading the existing flash and updating the ESPFS image.
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "cgiflash.h"
#include "espfs.h"
#include "cgiflash.h"
#include "espfs.h"
//#include <osapi.h>
#include "cgiflash.h"
#include "espfs.h"
#ifndef UPGRADE_FLAG_FINISH
#define UPGRADE_FLAG_FINISH 0x02
#endif
// Check that the header of the firmware blob looks like actual firmware...
static int ICACHE_FLASH_ATTR checkBinHeader(void *buf) {
uint8_t *cd = (uint8_t *)buf;
if (cd[0] != 0xEA) return 0;
if (cd[1] != 4 || cd[2] > 3 || cd[3] > 0x40) return 0;
if (((uint16_t *)buf)[3] != 0x4010) return 0;
if (((uint32_t *)buf)[2] != 0) return 0;
return 1;
}
static int ICACHE_FLASH_ATTR checkEspfsHeader(void *buf) {
if (memcmp(buf, "ESfs", 4)!=0) return 0;
return 1;
}
// Cgi to query which firmware needs to be uploaded next
int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) {
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
uint8 id = system_upgrade_userbin_check();
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdHeader(connData, "Content-Length", "9");
httpdEndHeaders(connData);
char *next = id == 1 ? "user1.bin" : "user2.bin";
httpdSend(connData, next, -1);
httpd_printf("Next firmware: %s (got %d)\n", next, id);
return HTTPD_CGI_DONE;
}
//Cgi that reads the SPI flash. Assumes 512KByte flash.
//ToDo: Figure out real flash size somehow?
int ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) {
int *pos=(int *)&connData->cgiData;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
if (*pos==0) {
httpd_printf("Start flash download.\n");
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "application/bin");
httpdEndHeaders(connData);
*pos=0x40200000;
return HTTPD_CGI_MORE;
}
//Send 1K of flash per call. We will get called again if we haven't sent 512K yet.
httpdSend(connData, (char*)(*pos), 1024);
*pos+=1024;
if (*pos>=0x40200000+(512*1024)) return HTTPD_CGI_DONE; else return HTTPD_CGI_MORE;
}
//Cgi that allows the firmware to be replaced via http POST This takes
//a direct POST from e.g. Curl or a Javascript AJAX call with either the
//firmware given by cgiGetFirmwareNext or an OTA upgrade image.
//Because we don't have the buffer to allocate an entire sector but will
//have to buffer some data because the post buffer may be misaligned, we
//write SPI data in pages. The page size is a software thing, not
//a hardware one.
#define PAGELEN 64
#define FLST_START 0
#define FLST_WRITE 1
#define FLST_SKIP 2
#define FLST_DONE 3
#define FLST_ERROR 4
#define FILETYPE_ESPFS 0
#define FILETYPE_FLASH 1
#define FILETYPE_OTA 2
typedef struct {
int state;
int filetype;
int flashPos;
char pageData[PAGELEN];
int pagePos;
int address;
int len;
int skip;
char *err;
} UploadState;
typedef struct __attribute__((packed)) {
char magic[4];
char tag[28];
int32_t len1;
int32_t len2;
} OtaHeader;
int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) {
CgiUploadFlashDef *def=(CgiUploadFlashDef*)connData->cgiArg;
UploadState *state=(UploadState *)connData->cgiData;
int len;
char buff[128];
if (connData->conn==NULL) {
//Connection aborted. Clean up.
if (state!=NULL) free(state);
return HTTPD_CGI_DONE;
}
if (state==NULL) {
//First call. Allocate and initialize state variable.
httpd_printf("Firmware upload cgi start.\n");
state=malloc(sizeof(UploadState));
if (state==NULL) {
httpd_printf("Can't allocate firmware upload struct!\n");
return HTTPD_CGI_DONE;
}
memset(state, 0, sizeof(UploadState));
state->state=FLST_START;
connData->cgiData=state;
state->err="Premature end";
}
char *data=connData->post->buff;
int dataLen=connData->post->buffLen;
while (dataLen!=0) {
if (state->state==FLST_START) {
//First call. Assume the header of whatever we're uploading already is in the POST buffer.
if (def->type==CGIFLASH_TYPE_FW && memcmp(data, "EHUG", 4)==0) {
//Type is combined flash1/flash2 file
OtaHeader *h=(OtaHeader*)data;
strncpy(buff, h->tag, 27);
buff[27]=0;
if (strcmp(buff, def->tagName)!=0) {
httpd_printf("OTA tag mismatch! Current=`%s` uploaded=`%s`.\n",
def->tagName, buff);
len=httpdFindArg(connData->getArgs, "force", buff, sizeof(buff));
if (len!=-1 && atoi(buff)) {
httpd_printf("Forcing firmware flash.\n");
} else {
state->err="Firmware not intended for this device!\n";
state->state=FLST_ERROR;
}
}
if (state->state!=FLST_ERROR && connData->post->len > def->fwSize*2+sizeof(OtaHeader)) {
state->err="Firmware image too large";
state->state=FLST_ERROR;
}
if (state->state!=FLST_ERROR) {
//Flash header seems okay.
dataLen-=sizeof(OtaHeader); //skip header when parsing data
data+=sizeof(OtaHeader);
if (system_upgrade_userbin_check()==1) {
httpd_printf("Flashing user1.bin from ota image\n");
state->len=h->len1;
state->skip=h->len2;
state->state=FLST_WRITE;
state->address=def->fw1Pos;
} else {
httpd_printf("Flashing user2.bin from ota image\n");
state->len=h->len2;
state->skip=h->len1;
state->state=FLST_SKIP;
state->address=def->fw2Pos;
}
}
} else if (def->type==CGIFLASH_TYPE_FW && checkBinHeader(connData->post->buff)) {
if (connData->post->len > def->fwSize) {
state->err="Firmware image too large";
state->state=FLST_ERROR;
} else {
state->len=connData->post->len;
state->address=def->fw1Pos;
state->state=FLST_WRITE;
}
} else if (def->type==CGIFLASH_TYPE_ESPFS && checkEspfsHeader(connData->post->buff)) {
if (connData->post->len > def->fwSize) {
state->err="Firmware image too large";
state->state=FLST_ERROR;
} else {
state->len=connData->post->len;
state->address=def->fw1Pos;
state->state=FLST_WRITE;
}
} else {
state->err="Invalid flash image type!";
state->state=FLST_ERROR;
httpd_printf("Did not recognize flash image type!\n");
}
} else if (state->state==FLST_SKIP) {
//Skip bytes without doing anything with them
if (state->skip>dataLen) {
//Skip entire buffer
state->skip-=dataLen;
dataLen=0;
} else {
//Only skip part of buffer
dataLen-=state->skip;
data+=state->skip;
state->skip=0;
if (state->len) state->state=FLST_WRITE; else state->state=FLST_DONE;
}
} else if (state->state==FLST_WRITE) {
//Copy bytes to page buffer, and if page buffer is full, flash the data.
//First, calculate the amount of bytes we need to finish the page buffer.
int lenLeft=PAGELEN-state->pagePos;
if (state->len<lenLeft) lenLeft=state->len; //last buffer can be a cut-off one
//See if we need to write the page.
if (dataLen<lenLeft) {
//Page isn't done yet. Copy data to buffer and exit.
memcpy(&state->pageData[state->pagePos], data, dataLen);
state->pagePos+=dataLen;
state->len-=dataLen;
dataLen=0;
} else {
//Finish page; take data we need from post buffer
memcpy(&state->pageData[state->pagePos], data, lenLeft);
data+=lenLeft;
dataLen-=lenLeft;
state->pagePos+=lenLeft;
state->len-=lenLeft;
//Erase sector, if needed
if ((state->address&(SPI_FLASH_SEC_SIZE-1))==0) {
spi_flash_erase_sector(state->address/SPI_FLASH_SEC_SIZE);
}
//Write page
//httpd_printf("Writing %d bytes of data to SPI pos 0x%x...\n", state->pagePos, state->address);
spi_flash_write(state->address, (uint32 *)state->pageData, state->pagePos);
state->address+=PAGELEN;
state->pagePos=0;
if (state->len==0) {
//Done.
if (state->skip) state->state=FLST_SKIP; else state->state=FLST_DONE;
}
}
} else if (state->state==FLST_DONE) {
httpd_printf("Huh? %d bogus bytes received after data received.\n", dataLen);
//Ignore those bytes.
dataLen=0;
} else if (state->state==FLST_ERROR) {
//Just eat up any bytes we receive.
dataLen=0;
}
}
if (connData->post->len==connData->post->received) {
//We're done! Format a response.
httpd_printf("Upload done. Sending response.\n");
httpdStartResponse(connData, state->state==FLST_ERROR?400:200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
if (state->state!=FLST_DONE) {
httpdSend(connData, "Firmware image error:", -1);
httpdSend(connData, state->err, -1);
httpdSend(connData, "\n", -1);
}
free(state);
return HTTPD_CGI_DONE;
}
return HTTPD_CGI_MORE;
}
static os_timer_t resetTimer;
static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
system_upgrade_flag_set(UPGRADE_FLAG_FINISH);
system_upgrade_reboot();
}
// Handle request to reboot into the new firmware
int ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) {
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
// TODO: sanity-check that the 'next' partition actually contains something that looks like
// valid firmware
//Do reboot in a timer callback so we still have time to send the response.
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, 200, 0);
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
httpdSend(connData, "Rebooting...", -1);
return HTTPD_CGI_DONE;
}
+354
View File
@@ -0,0 +1,354 @@
/*
Websocket support for esphttpd. Inspired by https://github.com/dangrie158/ESP-8266-WebSocket
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "httpd.h"
#include "sha1.h"
#include "base64.h"
#include "cgiwebsocket.h"
#define WS_KEY_IDENTIFIER "Sec-WebSocket-Key: "
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
/* from IEEE RFC6455 sec 5.2
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
*/
#define FLAG_FIN (1 << 7)
#define OPCODE_CONTINUE 0x0
#define OPCODE_TEXT 0x1
#define OPCODE_BINARY 0x2
#define OPCODE_CLOSE 0x8
#define OPCODE_PING 0x9
#define OPCODE_PONG 0xA
#define FLAGS_MASK ((uint8_t)0xF0)
#define OPCODE_MASK ((uint8_t)0x0F)
#define IS_MASKED ((uint8_t)(1<<7))
#define PAYLOAD_MASK ((uint8_t)0x7F)
typedef struct WebsockFrame WebsockFrame;
#define ST_FLAGS 0
#define ST_LEN0 1
#define ST_LEN1 2
#define ST_LEN2 3
//...
#define ST_LEN8 9
#define ST_MASK1 10
#define ST_MASK4 13
#define ST_PAYLOAD 14
struct WebsockFrame {
uint8_t flags;
uint8_t len8;
uint64_t len;
uint8_t mask[4];
};
struct WebsockPriv {
struct WebsockFrame fr;
uint8_t maskCtr;
uint8 frameCont;
uint8 closedHere;
int wsStatus;
Websock *next; //in linked list
};
static Websock *llStart=NULL;
static int ICACHE_FLASH_ATTR sendFrameHead(Websock *ws, int opcode, int len) {
char buf[14];
int i=0;
buf[i++]=opcode;
if (len>65535) {
buf[i++]=127;
buf[i++]=0; buf[i++]=0; buf[i++]=0; buf[i++]=0;
buf[i++]=len>>24;
buf[i++]=len>>16;
buf[i++]=len>>8;
buf[i++]=len;
} else if (len>125) {
buf[i++]=126;
buf[i++]=len>>8;
buf[i++]=len;
} else {
buf[i++]=len;
}
httpd_printf("WS: Sent frame head for payload of %d bytes.\n", len);
return httpdSend(ws->conn, buf, i);
}
int ICACHE_FLASH_ATTR cgiWebsocketSend(Websock *ws, char *data, int len, int flags) {
int r=0;
int fl=0;
if (flags&WEBSOCK_FLAG_BIN) fl=OPCODE_BINARY; else fl=OPCODE_TEXT;
if (!(flags&WEBSOCK_FLAG_CONT)) fl|=FLAG_FIN;
sendFrameHead(ws, fl, len);
if (len!=0) r=httpdSend(ws->conn, data, len);
httpdFlushSendBuffer(ws->conn);
return r;
}
//Broadcast data to all websockets at a specific url. Returns the amount of connections sent to.
int ICACHE_FLASH_ATTR cgiWebsockBroadcast(char *resource, char *data, int len, int flags) {
//This is majorly broken (and actually, always, it just tended to work because the circumstances
//were juuuust right). Because the socket is used outside of the httpd send/receive context, it
//will not have an associated send buffer. This means httpdSend will write to a dangling pointer!
//Disabled for now. If you really need this, open an issue on github or otherwise poke me and I'll
//see what I can do.
/*
Websock *lw=llStart;
int ret=0;
while (lw!=NULL) {
if (strcmp(lw->conn->url, resource)==0) {
cgiWebsocketSend(lw, data, len, flags);
ret++;
}
lw=lw->priv->next;
}
return ret;
*/
return 0;
}
void ICACHE_FLASH_ATTR cgiWebsocketClose(Websock *ws, int reason) {
char rs[2]={reason>>8, reason&0xff};
sendFrameHead(ws, FLAG_FIN|OPCODE_CLOSE, 2);
httpdSend(ws->conn, rs, 2);
ws->priv->closedHere=1;
httpdFlushSendBuffer(ws->conn);
}
static void ICACHE_FLASH_ATTR websockFree(Websock *ws) {
httpd_printf("Ws: Free\n");
if (ws->closeCb) ws->closeCb(ws);
//Clean up linked list
if (llStart==ws) {
llStart=ws->priv->next;
} else if (llStart) {
Websock *lws=llStart;
//Find ws that links to this one.
while (lws!=NULL && lws->priv->next!=ws) lws=lws->priv->next;
if (lws!=NULL) lws->priv->next=ws->priv->next;
}
if (ws->priv) free(ws->priv);
}
int ICACHE_FLASH_ATTR cgiWebSocketRecv(HttpdConnData *connData, char *data, int len) {
int i, j, sl;
int r=HTTPD_CGI_MORE;
int wasHeaderByte;
Websock *ws=(Websock*)connData->cgiData;
for (i=0; i<len; i++) {
// httpd_printf("Ws: State %d byte 0x%02X\n", ws->priv->wsStatus, data[i]);
wasHeaderByte=1;
if (ws->priv->wsStatus==ST_FLAGS) {
ws->priv->maskCtr=0;
ws->priv->frameCont=0;
ws->priv->fr.flags=(uint8_t)data[i];
ws->priv->wsStatus=ST_LEN0;
} else if (ws->priv->wsStatus==ST_LEN0) {
ws->priv->fr.len8=(uint8_t)data[i];
if ((ws->priv->fr.len8&127)>=126) {
ws->priv->fr.len=0;
ws->priv->wsStatus=ST_LEN1;
} else {
ws->priv->fr.len=ws->priv->fr.len8&127;
ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD;
}
} else if (ws->priv->wsStatus<=ST_LEN8) {
ws->priv->fr.len=(ws->priv->fr.len<<8)|data[i];
if (((ws->priv->fr.len8&127)==126 && ws->priv->wsStatus==ST_LEN2) || ws->priv->wsStatus==ST_LEN8) {
ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD;
} else {
ws->priv->wsStatus++;
}
} else if (ws->priv->wsStatus<=ST_MASK4) {
ws->priv->fr.mask[ws->priv->wsStatus-ST_MASK1]=data[i];
ws->priv->wsStatus++;
} else {
//Was a payload byte.
wasHeaderByte=0;
}
if (ws->priv->wsStatus==ST_PAYLOAD && wasHeaderByte) {
//We finished parsing the header, but i still is on the last header byte. Move one forward so
//the payload code works as usual.
i++;
}
//Also finish parsing frame if we haven't received any payload bytes yet, but the length of the frame
//is zero.
if (ws->priv->wsStatus==ST_PAYLOAD) {
//Okay, header is in; this is a data byte. We're going to process all the data bytes we have
//received here at the same time; no more byte iterations till the end of this frame.
//First, unmask the data
sl=len-i;
httpd_printf("Ws: Frame payload. wasHeaderByte %d fr.len %d sl %d cmd 0x%x\n", wasHeaderByte, (int)ws->priv->fr.len, (int)sl, ws->priv->fr.flags);
if (sl > ws->priv->fr.len) sl=ws->priv->fr.len;
for (j=0; j<sl; j++) data[i+j]^=(ws->priv->fr.mask[(ws->priv->maskCtr++)&3]);
// httpd_printf("Unmasked: ");
// for (j=0; j<sl; j++) httpd_printf("%02X ", data[i+j]&0xff);
// httpd_printf("\n");
//Inspect the header to see what we need to do.
if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_PING) {
if (ws->priv->fr.len>125) {
if (!ws->priv->frameCont) cgiWebsocketClose(ws, 1002);
r=HTTPD_CGI_DONE;
break;
} else {
if (!ws->priv->frameCont) sendFrameHead(ws, OPCODE_PONG|FLAG_FIN, ws->priv->fr.len);
if (sl>0) httpdSend(ws->conn, data+i, sl);
}
} else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_TEXT ||
(ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY ||
(ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CONTINUE) {
if (sl>ws->priv->fr.len) sl=ws->priv->fr.len;
if (!(ws->priv->fr.len8&IS_MASKED)) {
//We're a server; client should send us masked packets.
cgiWebsocketClose(ws, 1002);
r=HTTPD_CGI_DONE;
break;
} else {
int flags=0;
if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY) flags|=WEBSOCK_FLAG_BIN;
if ((ws->priv->fr.flags&FLAG_FIN)==0) flags|=WEBSOCK_FLAG_CONT;
if (ws->recvCb) ws->recvCb(ws, data+i, sl, flags);
}
} else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CLOSE) {
httpd_printf("WS: Got close frame\n");
if (!ws->priv->closedHere) {
httpd_printf("WS: Sending response close frame\n");
cgiWebsocketClose(ws, ((data[i]<<8)&0xff00)+(data[i+1]&0xff));
}
r=HTTPD_CGI_DONE;
break;
} else {
if (!ws->priv->frameCont) httpd_printf("WS: Unknown opcode 0x%X\n", ws->priv->fr.flags&OPCODE_MASK);
}
i+=sl-1;
ws->priv->fr.len-=sl;
if (ws->priv->fr.len==0) {
ws->priv->wsStatus=ST_FLAGS; //go receive next frame
} else {
ws->priv->frameCont=1; //next payload is continuation of this frame.
}
}
}
if (r==HTTPD_CGI_DONE) {
//We're going to tell the main webserver we're done. The webserver expects us to clean up by ourselves
//we're chosing to be done. Do so.
websockFree(ws);
free(connData->cgiData);
connData->cgiData=NULL;
}
return r;
}
//Websocket 'cgi' implementation
int ICACHE_FLASH_ATTR cgiWebsocket(HttpdConnData *connData) {
char buff[256];
int i;
sha1nfo s;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
httpd_printf("WS: Cleanup\n");
if (connData->cgiData) {
Websock *ws=(Websock*)connData->cgiData;
websockFree(ws);
free(connData->cgiData);
connData->cgiData=NULL;
}
return HTTPD_CGI_DONE;
}
if (connData->cgiData==NULL) {
// httpd_printf("WS: First call\n");
//First call here. Check if client headers are OK, send server header.
i=httpdGetHeader(connData, "Upgrade", buff, sizeof(buff)-1);
httpd_printf("WS: Upgrade: %s\n", buff);
if (i && strcasecmp(buff, "websocket")==0) {
i=httpdGetHeader(connData, "Sec-WebSocket-Key", buff, sizeof(buff)-1);
if (i) {
// httpd_printf("WS: Key: %s\n", buff);
//Seems like a WebSocket connection.
// Alloc structs
connData->cgiData=malloc(sizeof(Websock));
memset(connData->cgiData, 0, sizeof(Websock));
Websock *ws=(Websock*)connData->cgiData;
ws->priv=malloc(sizeof(WebsockPriv));
memset(ws->priv, 0, sizeof(WebsockPriv));
ws->conn=connData;
//Reply with the right headers.
strcat(buff, WS_GUID);
sha1_init(&s);
sha1_write(&s, buff, strlen(buff));
httpdDisableTransferEncoding(connData);
httpdStartResponse(connData, 101);
httpdHeader(connData, "Upgrade", "websocket");
httpdHeader(connData, "Connection", "upgrade");
base64_encode(20, sha1_result(&s), sizeof(buff), buff);
httpdHeader(connData, "Sec-WebSocket-Accept", buff);
httpdEndHeaders(connData);
//Set data receive handler
connData->recvHdl=cgiWebSocketRecv;
//Inform CGI function we have a connection
WsConnectedCb connCb=connData->cgiArg;
connCb(ws);
//Insert ws into linked list
if (llStart==NULL) {
llStart=ws;
} else {
Websock *lw=llStart;
while (lw->priv->next) lw=lw->priv->next;
lw->priv->next=ws;
}
return HTTPD_CGI_MORE;
}
}
//No valid websocket connection
httpdStartResponse(connData, 500);
httpdEndHeaders(connData);
return HTTPD_CGI_DONE;
}
//Sending is done. Call the sent callback if we have one.
Websock *ws=(Websock*)connData->cgiData;
if (ws && ws->sentCb) ws->sentCb(ws);
return HTTPD_CGI_MORE;
}
+314
View File
@@ -0,0 +1,314 @@
/*
Cgi/template routines for the /wifi url.
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "cgiwifi.h"
//Enable this to disallow any changes in AP settings
//#define DEMO_MODE
//WiFi access point data
typedef struct {
char ssid[32];
char bssid[8];
int channel;
char rssi;
char enc;
} ApData;
//Scan result
typedef struct {
char scanInProgress; //if 1, don't access the underlying stuff from the webpage.
ApData **apData;
int noAps;
} ScanResultData;
//Static scan status storage.
static ScanResultData cgiWifiAps;
#define CONNTRY_IDLE 0
#define CONNTRY_WORKING 1
#define CONNTRY_SUCCESS 2
#define CONNTRY_FAIL 3
//Connection result var
static int connTryStatus=CONNTRY_IDLE;
static os_timer_t resetTimer;
//Callback the code calls when a wlan ap scan is done. Basically stores the result in
//the cgiWifiAps struct.
void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) {
int n;
struct bss_info *bss_link = (struct bss_info *)arg;
httpd_printf("wifiScanDoneCb %d\n", status);
if (status!=OK) {
cgiWifiAps.scanInProgress=0;
return;
}
//Clear prev ap data if needed.
if (cgiWifiAps.apData!=NULL) {
for (n=0; n<cgiWifiAps.noAps; n++) free(cgiWifiAps.apData[n]);
free(cgiWifiAps.apData);
}
//Count amount of access points found.
n=0;
while (bss_link != NULL) {
bss_link = bss_link->next.stqe_next;
n++;
}
//Allocate memory for access point data
cgiWifiAps.apData=(ApData **)malloc(sizeof(ApData *)*n);
cgiWifiAps.noAps=n;
httpd_printf("Scan done: found %d APs\n", n);
//Copy access point data to the static struct
n=0;
bss_link = (struct bss_info *)arg;
while (bss_link != NULL) {
if (n>=cgiWifiAps.noAps) {
//This means the bss_link changed under our nose. Shouldn't happen!
//Break because otherwise we will write in unallocated memory.
httpd_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps);
break;
}
//Save the ap data.
cgiWifiAps.apData[n]=(ApData *)malloc(sizeof(ApData));
cgiWifiAps.apData[n]->rssi=bss_link->rssi;
cgiWifiAps.apData[n]->channel=bss_link->channel;
cgiWifiAps.apData[n]->enc=bss_link->authmode;
strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32);
strncpy(cgiWifiAps.apData[n]->bssid, (char*)bss_link->bssid, 6);
bss_link = bss_link->next.stqe_next;
n++;
}
//We're done.
cgiWifiAps.scanInProgress=0;
}
//Routine to start a WiFi access point scan.
static void ICACHE_FLASH_ATTR wifiStartScan() {
// int x;
if (cgiWifiAps.scanInProgress) return;
cgiWifiAps.scanInProgress=1;
wifi_station_scan(NULL, wifiScanDoneCb);
}
//This CGI is called from the bit of AJAX-code in wifi.tpl. It will initiate a
//scan for access points and if available will return the result of an earlier scan.
//The result is embedded in a bit of JSON parsed by the javascript in wifi.tpl.
int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) {
int pos=(int)connData->cgiData;
int len;
char buff[1024];
if (!cgiWifiAps.scanInProgress && pos!=0) {
//Fill in json code for an access point
if (pos-1<cgiWifiAps.noAps) {
len=sprintf(buff, "{\"essid\": \"%s\", \"bssid\": \"" MACSTR "\", \"rssi\": \"%d\", \"enc\": \"%d\", \"channel\": \"%d\"}%s\n",
cgiWifiAps.apData[pos-1]->ssid, MAC2STR(cgiWifiAps.apData[pos-1]->bssid), cgiWifiAps.apData[pos-1]->rssi,
cgiWifiAps.apData[pos-1]->enc, cgiWifiAps.apData[pos-1]->channel, (pos-1==cgiWifiAps.noAps-1)?"":",");
httpdSend(connData, buff, len);
}
pos++;
if ((pos-1)>=cgiWifiAps.noAps) {
len=sprintf(buff, "]\n}\n}\n");
httpdSend(connData, buff, len);
//Also start a new scan.
wifiStartScan();
return HTTPD_CGI_DONE;
} else {
connData->cgiData=(void*)pos;
return HTTPD_CGI_MORE;
}
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/json");
httpdEndHeaders(connData);
if (cgiWifiAps.scanInProgress==1) {
//We're still scanning. Tell Javascript code that.
len=sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n");
httpdSend(connData, buff, len);
return HTTPD_CGI_DONE;
} else {
//We have a scan result. Pass it on.
len=sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"0\", \n\"APs\": [\n");
httpdSend(connData, buff, len);
if (cgiWifiAps.apData==NULL) cgiWifiAps.noAps=0;
connData->cgiData=(void *)1;
return HTTPD_CGI_MORE;
}
}
//Temp store for new ap info.
static struct station_config stconf;
//This routine is ran some time after a connection attempt to an access point. If
//the connect succeeds, this gets the module in STA-only mode.
static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
int x=wifi_station_get_connect_status();
if (x==STATION_GOT_IP) {
//Go to STA mode. This needs a reset, so do that.
httpd_printf("Got IP. Going into STA mode..\n");
wifi_set_opmode(1);
system_restart();
} else {
connTryStatus=CONNTRY_FAIL;
httpd_printf("Connect fail. Not going into STA-only mode.\n");
//Maybe also pass this through on the webpage?
}
}
//Actually connect to a station. This routine is timed because I had problems
//with immediate connections earlier. It probably was something else that caused it,
//but I can't be arsed to put the code back :P
static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) {
int x;
httpd_printf("Try to connect to AP....\n");
wifi_station_disconnect();
wifi_station_set_config(&stconf);
wifi_station_connect();
x=wifi_get_opmode();
connTryStatus=CONNTRY_WORKING;
if (x!=1) {
//Schedule disconnect/connect
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, 15000, 0); //time out after 15 secs of trying to connect
}
}
//This cgi uses the routines above to connect to a specific access point with the
//given ESSID using the given password.
int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) {
char essid[128];
char passwd[128];
static os_timer_t reassTimer;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdFindArg(connData->post->buff, "essid", essid, sizeof(essid));
httpdFindArg(connData->post->buff, "passwd", passwd, sizeof(passwd));
strncpy((char*)stconf.ssid, essid, 32);
strncpy((char*)stconf.password, passwd, 64);
httpd_printf("Try to connect to AP %s pw %s\n", essid, passwd);
//Schedule disconnect/connect
os_timer_disarm(&reassTimer);
os_timer_setfn(&reassTimer, reassTimerCb, NULL);
//Set to 0 if you want to disable the actual reconnecting bit
#ifdef DEMO_MODE
httpdRedirect(connData, "/wifi");
#else
os_timer_arm(&reassTimer, 500, 0);
httpdRedirect(connData, "connecting.html");
#endif
return HTTPD_CGI_DONE;
}
//This cgi uses the routines above to connect to a specific access point with the
//given ESSID using the given password.
int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) {
int len;
char buff[1024];
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff));
if (len!=0) {
httpd_printf("cgiWifiSetMode: %s\n", buff);
#ifndef DEMO_MODE
wifi_set_opmode(atoi(buff));
system_restart();
#endif
}
httpdRedirect(connData, "/wifi");
return HTTPD_CGI_DONE;
}
int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) {
char buff[1024];
int len;
struct ip_info info;
int st=wifi_station_get_connect_status();
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/json");
httpdEndHeaders(connData);
if (connTryStatus==CONNTRY_IDLE) {
len=sprintf(buff, "{\n \"status\": \"idle\"\n }\n");
} else if (connTryStatus==CONNTRY_WORKING || connTryStatus==CONNTRY_SUCCESS) {
if (st==STATION_GOT_IP) {
wifi_get_ip_info(0, &info);
len=sprintf(buff, "{\n \"status\": \"success\",\n \"ip\": \"%d.%d.%d.%d\" }\n",
(info.ip.addr>>0)&0xff, (info.ip.addr>>8)&0xff,
(info.ip.addr>>16)&0xff, (info.ip.addr>>24)&0xff);
//Reset into AP-only mode sooner.
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, 1000, 0);
} else {
len=sprintf(buff, "{\n \"status\": \"working\"\n }\n");
}
} else {
len=sprintf(buff, "{\n \"status\": \"fail\"\n }\n");
}
httpdSend(connData, buff, len);
return HTTPD_CGI_DONE;
}
//Template code for the WLAN page.
int ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg) {
char buff[1024];
int x;
static struct station_config stconf;
if (token==NULL) return HTTPD_CGI_DONE;
wifi_station_get_config(&stconf);
strcpy(buff, "Unknown");
if (strcmp(token, "WiFiMode")==0) {
x=wifi_get_opmode();
if (x==1) strcpy(buff, "Client");
if (x==2) strcpy(buff, "SoftAP");
if (x==3) strcpy(buff, "STA+AP");
} else if (strcmp(token, "currSsid")==0) {
strcpy(buff, (char*)stconf.ssid);
} else if (strcmp(token, "WiFiPasswd")==0) {
strcpy(buff, (char*)stconf.password);
} else if (strcmp(token, "WiFiapwarn")==0) {
x=wifi_get_opmode();
if (x==2) {
strcpy(buff, "<b>Can't scan in this mode.</b> Click <a href=\"setmode.cgi?mode=3\">here</a> to go to STA+AP mode.");
} else {
strcpy(buff, "Click <a href=\"setmode.cgi?mode=2\">here</a> to go to standalone AP mode.");
}
}
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}