add files. it mostly compiles on posix now

master
Ondřej Hruška 1 year ago
commit 26867f4767
  1. 7
      .gitignore
  2. 21
      Makefile
  3. 8
      README.md
  4. 4
      espfsbuilder/.gitignore
  5. 12
      espfsbuilder/Makefile
  6. 124
      espfsbuilder/logging.h
  7. 560
      espfsbuilder/main.c
  8. 319
      lib/espfs/espfs.c
  9. 41
      lib/espfs/espfs.h
  10. 25
      lib/espfs/espfsformat.h
  11. 17
      lib/heatshrink/heatshrink_common.h
  12. 40
      lib/heatshrink/heatshrink_config.h
  13. 374
      lib/heatshrink/heatshrink_decoder.c
  14. 97
      lib/heatshrink/heatshrink_decoder.h
  15. 607
      lib/heatshrink/heatshrink_encoder.c
  16. 106
      lib/heatshrink/heatshrink_encoder.h
  17. 19
      lib/include/auth.h
  18. 33
      lib/include/cgiwebsocket.h
  19. 39
      lib/include/httpd-platform.h
  20. 10
      lib/include/httpd-utils.h
  21. 204
      lib/include/httpd.h
  22. 13
      lib/include/httpdespfs.h
  23. 193
      lib/include/logging.h
  24. 415
      lib/src/cgiwebsocket.c
  25. 235
      lib/src/httpd-loop.c
  26. 1090
      lib/src/httpd.c
  27. 402
      lib/src/httpdespfs.c
  28. 56
      lib/src/port/httpd-freertos.c
  29. 87
      lib/src/port/httpd-posix.c
  30. 112
      lib/src/utils/base64.c
  31. 6
      lib/src/utils/base64.h
  32. 174
      lib/src/utils/sha1.c
  33. 34
      lib/src/utils/sha1.h
  34. 9
      lib/todo/esphttpclient/LICENSE
  35. 78
      lib/todo/esphttpclient/README.md
  36. 589
      lib/todo/esphttpclient/httpclient.c
  37. 96
      lib/todo/httpclient.h
  38. 59
      lib/todo/uptime.c
  39. 22
      lib/todo/uptime.h
  40. 3
      lib/todo/webpages-espfs.h
  41. 12
      lib/todo/webpages.espfs.ld
  42. 58
      main.c

7
.gitignore vendored

@ -0,0 +1,7 @@
*.o
.idea/
*.bin
*.elf
*.map
*.d
*~

@ -0,0 +1,21 @@
PORT_SOURCES = lib/src/port/httpd-posix.c
LIB_SOURCES = ${PORT_SOURCES} \
lib/src/utils/base64.c \
lib/src/utils/sha1.c \
lib/espfs/espfs.c \
lib/src/httpdespfs.c \
lib/src/httpd.c \
lib/src/httpd-loop.c \
lib/src/cgiwebsocket.c \
lib/heatshrink/heatshrink_decoder.c
LIB_INCLUDES = -Ilib/include -Ilib/heatshrink -Ilib/espfs
DEMO_SOURCES = main.c
all: demo
demo: ${LIB_SOURCES} ${DEMO_SOURCES}
cc -g $^ -o $@ ${LIB_INCLUDES}

@ -0,0 +1,8 @@
# sprite-httpd
This project is a continuation of Sprite-TM's ESP8266 / ESP32 http server (https://github.com/Spritetm/esphttpd) with ESP-Term improvements
(https://github.com/MightyPork/libesphttpd), ported to plain FreeRTOS with Berkeley sockets. The posix port allows it to run on Linux as well
- ideal for debugging.
The server implements some of the more basic parts of HTTP/1.1 with Websockets and includes a templating system and a compressed ROM filesystem
called `espfs`.

@ -0,0 +1,4 @@
mkespfsimage
*.o
.idea/
*.bin

@ -0,0 +1,12 @@
TARGET=mkespfsimage
SOURCES = main.c ../lib/heatshrink/heatshrink_encoder.c ../lib/heatshrink/heatshrink_decoder.c ../lib/espfs/espfs.c
CFLAGS = -I. -I../lib/heatshrink/ -I../lib/espfs/
all: $(TARGET)
$(TARGET): ${SOURCES}
cc -O3 -lz $^ -o $@ ${CFLAGS}
clean:
rm $(TARGET)

@ -0,0 +1,124 @@
#pragma once
#include <stdio.h>
#ifndef VERBOSE_LOGGING
#define VERBOSE_LOGGING 1
#endif
#ifndef LOG_EOL
#define LOG_EOL "\n"
#endif
/**
* Print a startup banner message (printf syntax)
* Uses bright green color
*/
#define banner(fmt, ...) \
do { \
fprintf(stderr, LOG_EOL "\x1b[32;1m[i] " fmt "\x1b[0m" LOG_EOL, ##__VA_ARGS__); \
} while(0)
/**
* Same as 'info()', but enabled even if verbose logging is disabled.
* This can be used to print version etc under the banner.
*/
#define banner_info(fmt, ...) \
do { \
fprintf(stderr, "\x1b[32m[i] " fmt "\x1b[0m"LOG_EOL, ##__VA_ARGS__); \
} while(0)
/**
* Empty line in the headers
*/
#define banner_gap() \
do { \
fprintf(stderr, LOG_EOL); \
} while(0)
#if VERBOSE_LOGGING
/**
* Print a debug log message (printf format)
*/
#define dbg(fmt, ...) \
do { \
fprintf(stderr, "[ ] " fmt LOG_EOL, ##__VA_ARGS__); \
} while(0)
/**
* Print a info log message (printf format)
* Uses bright green color
*/
#define info(fmt, ...) \
do { \
fprintf(stderr, "[i] " fmt "\x1b[0m"LOG_EOL, ##__VA_ARGS__); \
} while(0)
#else
#define dbg(fmt, ...)
#define info(fmt, ...)
#endif
/**
* Print a error log message (printf format)
* Uses bright red color
*/
#define error(fmt, ...) \
do { \
fprintf(stderr, "[E] " fmt "\x1b[0m"LOG_EOL, ##__VA_ARGS__); \
} while(0)
/**
* Print a warning log message (printf format)
* Uses bright yellow color
*/
#define warn(fmt, ...) \
do { \
fprintf(stderr, "[W] " fmt "\x1b[0m"LOG_EOL, ##__VA_ARGS__); \
} while(0)
// --------------- logging categories --------------------
#ifndef DEBUG_ESPFS
#define DEBUG_ESPFS 1
#endif
#ifndef DEBUG_HEATSHRINK
#define DEBUG_HEATSHRINK 1
#endif
#ifndef DEBUG_MALLOC
#define DEBUG_MALLOC 0
#endif
// filesystem
#if DEBUG_ESPFS
#define espfs_warn(...) warn(__VA_ARGS__)
#define espfs_dbg(...) dbg(__VA_ARGS__)
#define espfs_error(...) error(__VA_ARGS__)
#define espfs_info(...) info(__VA_ARGS__)
#else
#define espfs_dbg(...)
#define espfs_warn(...)
#define espfs_error(...)
#define espfs_info(...)
#endif
// captive portal
#if DEBUG_HEATSHRINK
#define heatshrink_warn(...) warn(__VA_ARGS__)
#define heatshrink_dbg(...) dbg(__VA_ARGS__)
#define heatshrink_error(...) error(__VA_ARGS__)
#define heatshrink_info(...) info(__VA_ARGS__)
#else
#define heatshrink_dbg(...)
#define heatshrink_warn(...)
#define heatshrink_error(...)
#define heatshrink_info(...)
#endif
// all malloc usage
#if DEBUG_MALLOC
#define mem_dbg(...) dbg(__VA_ARGS__)
#else
#define mem_dbg(...)
#endif

@ -0,0 +1,560 @@
#include <stdint.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#include <getopt.h>
#include <stdbool.h>
#include "espfsformat.h"
#include "heatshrink_encoder.h"
#include "espfs.h"
#define DEFAULT_GZIP_EXTS "html,css,js,svg,png,jpg,gif"
// static variables
static int s_outFd = 1;
struct InputFileLinkedListEntry;
struct InputFileLinkedListEntry {
char *name;
struct InputFileLinkedListEntry *next;
};
static struct InputFileLinkedListEntry *s_inputFiles = NULL;
static struct InputFileLinkedListEntry *s_lastInputFile = NULL;
static char **s_gzipExtensions = NULL;
static bool s_gzipAll = false;
// impls to satisfy defs in the config header
void *httpdPlatMalloc(size_t len)
{
return malloc(len);
}
void httpdPlatFree(void *ptr)
{
free(ptr);
}
size_t compressHeatshrink(uint8_t *in, size_t insize, uint8_t *out, size_t outcap, int level)
{
uint8_t *inp = in;
uint8_t *outp = out;
size_t len;
int ws[] = {5, 6, 8, 11, 13};
int ls[] = {3, 3, 4, 4, 4};
HSE_poll_res pres;
HSE_sink_res sres;
size_t r;
if (level == -1) { level = 8; }
level = (level - 1) / 2; //level is now 0, 1, 2, 3, 4
heatshrink_encoder *enc = heatshrink_encoder_alloc(ws[level], ls[level]);
if (enc == NULL) {
perror("allocating mem for heatshrink");
exit(1);
}
//Save encoder parms as first byte
*outp = (ws[level] << 4) | ls[level];
outp++;
outcap--;
r = 1;
do {
if (insize > 0) {
sres = heatshrink_encoder_sink(enc, inp, insize, &len);
if (sres != HSER_SINK_OK) { break; }
inp += len;
insize -= len;
if (insize == 0) { heatshrink_encoder_finish(enc); }
}
do {
pres = heatshrink_encoder_poll(enc, outp, outcap, &len);
if (pres != HSER_POLL_MORE && pres != HSER_POLL_EMPTY) { break; }
outp += len;
outcap -= len;
r += len;
} while (pres == HSER_POLL_MORE);
} while (insize != 0);
if (insize != 0) {
fprintf(stderr, "Heatshrink: Bug? insize is still %d. sres=%d pres=%d\n", (int)insize, sres, pres);
exit(1);
}
heatshrink_encoder_free(enc);
return r;
}
size_t compressGzip(uint8_t *in, size_t insize, uint8_t *out, size_t outsize, int level)
{
z_stream stream;
int zresult;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.next_in = in;
stream.avail_in = insize;
stream.next_out = out;
stream.avail_out = outsize;
// 31 -> 15 window bits + 16 for gzip
zresult = deflateInit2(&stream, level, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY);
if (zresult != Z_OK) {
fprintf(stderr, "DeflateInit2 failed with code %d\n", zresult);
exit(1);
}
zresult = deflate(&stream, Z_FINISH);
if (zresult != Z_STREAM_END) {
fprintf(stderr, "Deflate failed with code %d\n", zresult);
exit(1);
}
zresult = deflateEnd(&stream);
if (zresult != Z_OK) {
fprintf(stderr, "DeflateEnd failed with code %d\n", zresult);
exit(1);
}
return stream.total_out;
}
bool shouldCompressGzip(const char *name)
{
if (!s_gzipExtensions) { return false; }
if (s_gzipAll) { return true; }
const char *ext = name + strlen(name);
while (*ext != '.') {
ext--;
if (ext < name) {
// no dot in file name -> no extension -> nothing to match against
return false;
}
}
ext++;
int i = 0;
while (s_gzipExtensions[i] != NULL) {
if (strcasecmp(ext, s_gzipExtensions[i]) == 0) {
return true;
}
i++;
}
return false;
}
int parseGzipExtensions(char *input)
{
char *token;
char *extList = input;
int count = 2; // one for first element, second for terminator
// count elements
while (*extList != 0) {
if (*extList == ',') { count++; }
extList++;
}
// split string
extList = input;
s_gzipExtensions = malloc(count * sizeof(char *));
count = 0;
token = strtok(extList, ",");
while (token) {
s_gzipExtensions[count++] = token;
token = strtok(NULL, ",");
}
// terminate list
s_gzipExtensions[count] = NULL;
return 1;
}
/**
* Process a file.
*
* @param fd - filedes
* @param name - filename to embed in the archive
* @param compression_mode - compression mode
* @param level - compression level for heatshrink, 1-9
* @param[out] compName - the used compression is output here (for debug print)
* @return
*/
int handleFile(int fd, const char *name, int compression_mode, int level, const char **compName)
{
uint8_t *fdat = NULL, *cdat = NULL, *cdatbuf = NULL;
uint32_t size, csize;
EspFsHeader h;
uint16_t realNameLen;
uint8_t flags = 0;
size = lseek(fd, 0, SEEK_END);
fdat = malloc(size);
lseek(fd, 0, SEEK_SET);
read(fd, fdat, size);
if (shouldCompressGzip(name)) {
csize = size * 3;
if (csize < 100) { // gzip has some headers that do not fit when trying to compress small files
csize = 100;
} // enlarge buffer if this is the case
cdat = cdatbuf = malloc(csize);
csize = compressGzip(fdat, size, cdat, csize, level);
compression_mode = COMPRESS_NONE; // don't use heatshrink if gzip was already used - it would only make it bigger
flags = FLAG_GZIP;
} else if (compression_mode == COMPRESS_NONE) {
csize = size;
cdat = fdat;
} else if (compression_mode == COMPRESS_HEATSHRINK) {
cdat = cdatbuf = malloc(size * 2);
csize = compressHeatshrink(fdat, size, cdat, size * 2, level);
} else {
fprintf(stderr, "Unknown compression - %d\n", compression_mode);
exit(1);
}
if (csize > size) {
fprintf(stderr, "! Compression enbiggened %s, embed as plain\n", name);
//Compressing enbiggened this file. Revert to uncompressed store.
compression_mode = COMPRESS_NONE;
csize = size;
cdat = fdat;
flags = 0;
}
//Fill header data
h.magic = htole32(ESPFS_MAGIC); // ('E' << 0) + ('S' << 8) + ('f' << 16) + ('s' << 24);
h.flags = flags;
h.compression = (int8_t) compression_mode;
h.nameLen = realNameLen = strlen(name) + 1; // zero terminator
uint32_t padbytes = 0;
if (h.nameLen & 3) {
//Round to next 32bit boundary
padbytes = 4 - (h.nameLen & 3);
h.nameLen += padbytes; // include the bytes in "name" to make parsing easier - these will be zeroed out, so the c-string remains the same.
}
h.nameLen = htole16(h.nameLen);
h.fileLenComp = htole32(csize);
h.fileLenDecomp = htole32(size);
write(s_outFd, &h, sizeof(EspFsHeader));
write(s_outFd, name, realNameLen);
if (padbytes) {
write(s_outFd, "\0\0\0", padbytes); // these zeros are included in h.nameLen
}
write(s_outFd, cdat, csize);
//Pad out to 32bit boundary - the parser does this automatically when walking over the archive.
if (csize & 3) {
padbytes = 4 - (csize & 3);
write(s_outFd, "\0\0\0", padbytes);
csize += padbytes;
}
free(fdat);
if (cdatbuf) {
// free the buffer allocated for compression output
free(cdatbuf);
}
// debug outputs ...
if (compName != NULL) {
if (h.compression == COMPRESS_HEATSHRINK) {
*compName = "heatshrink";
} else if (h.compression == COMPRESS_NONE) {
if (h.flags & FLAG_GZIP) {
*compName = "gzip";
} else {
*compName = "none";
}
} else {
*compName = "unknown";
}
}
// get compression % (lower is better)
return size ? (int) ((csize * 100) / size) : 100;
}
//Write final dummy header with FLAG_LASTFILE set.
void finishArchive()
{
EspFsHeader h;
h.magic = htole32(ESPFS_MAGIC); // ('E' << 0) + ('S' << 8) + ('f' << 16) + ('s' << 24);
h.flags = FLAG_LASTFILE;
h.compression = COMPRESS_NONE;
h.nameLen = 0;
h.fileLenComp = 0;
h.fileLenDecomp = 0;
write(s_outFd, &h, sizeof(EspFsHeader));
}
static size_t espfs_parse_filesize = -1;
static int espfs_parse_fd = -1;
void parseEspfsFileAndShowItsContents(const char *filename)
{
int rv;
fprintf(stderr, "Parsing: %s\n", filename);
FILE *f = fopen(filename, "r");
if (!f) {
perror(filename);
exit(1);
}
int fd = fileno(f);
espfs_parse_filesize = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
espfs_parse_fd = fd;
rv = espFsInit();
if (rv != 0) {
fprintf(stderr, "Fail to init FS\n");
exit(1);
}
EspFsWalk walk;
espFsWalkInit(&walk);
EspFsHeader header;
uint32_t offset;
char namebuf[1024];
while (espFsWalkNext(&walk, &header, namebuf, 1024, &offset)) {
fprintf(stderr, "at %04x: \"%s\", flags: %02x, comp: %s, compLen: %d, plainLen: %d\n", offset, namebuf, header.flags,
header.compression == 1 ? "HS" : "None", header.fileLenComp, header.fileLenDecomp);
}
fclose(f);
}
int httpdPlatEspfsRead(void *dest, uint32_t offset, size_t len)
{
fprintf(stderr, "FS read @ %d, len %d\n", offset, len);
if (offset + len > espfs_parse_filesize) {
fprintf(stderr, "Read out fo range!\n");
return -1;
}
lseek(espfs_parse_fd, offset, SEEK_SET);
read(espfs_parse_fd, dest, len);
return 0;
}
void queueInputFile(char *name)
{
fprintf(stderr, "INFILE: %s\n", name);
struct InputFileLinkedListEntry *tmp = malloc(sizeof(struct InputFileLinkedListEntry));
tmp->name = strdup(name);
tmp->next = NULL;
if (s_lastInputFile == NULL) {
s_inputFiles = tmp;
s_lastInputFile = tmp;
} else {
s_lastInputFile->next = tmp;
s_lastInputFile = tmp;
}
}
int main(int argc, char **argv)
{
int f;
char inputFileName[1024];
char *realName;
struct stat statBuf;
int serr;
int rate;
int err = 0;
int compType; //default compression type - heatshrink
int compLvl = -1;
bool use_gzip = false;
compType = COMPRESS_HEATSHRINK;
int c;
char *outfile = NULL;
char *parseFile = NULL;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"parse", required_argument, 0, 'p'},
{"compress", required_argument, 0, 'c'},
{"gzip", no_argument, 0, 'z'},
{"gzip-all", no_argument, 0, 'G'},
{"level", required_argument, 0, 'l'},
{"gzip-exts", required_argument, 0, 'g'},
{"input", required_argument, 0, 'i'},
{"output", required_argument, 0, 'o'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "c:l:g:zGhp:i:o:0123456789",
long_options, &option_index);
if (c == -1) {
break;
}
switch (c) {
case 'h':
goto show_help;
case 'z':
use_gzip = true;
break;
case '0' ... '9':
compLvl = c - '0';
break;
case 'p':
parseFile = strdup(optarg);
break;
case 'c':
compType = atoi(optarg);
break;
case 'G':
use_gzip = true;
s_gzipAll = true;
break;
case 'g':
use_gzip = true;
if (!parseGzipExtensions(optarg)) {
fprintf(stderr, "Bad gzip extension list: %s\n", optarg);
err = 1;
goto show_help;
}
break;
case 'l':
compLvl = atoi(optarg);
if (compLvl < 1 || compLvl > 9) {
fprintf(stderr, "Bad compression level: %d\n", compLvl);
err = 1;
goto show_help;
}
break;
case 'i':
queueInputFile(optarg);
break;
case 'o':
outfile = strdup(optarg);
break;
case '?':
goto show_help;
default:
fprintf(stderr, "Unknown option: %c\n", c);
err = 1;
goto show_help;
}
}
if (parseFile) {
parseEspfsFileAndShowItsContents(parseFile);
exit(0);
}
if (s_gzipExtensions == NULL && use_gzip) {
parseGzipExtensions(strdup(DEFAULT_GZIP_EXTS));
}
if (optind < argc) {
while (optind < argc) {
queueInputFile(argv[optind++]);
}
}
if (!s_inputFiles) {
fprintf(stderr, "Reading input file names from stdin\n");
while (fgets(inputFileName, sizeof(inputFileName), stdin)) {
//Kill off '\n' at the end
inputFileName[strlen(inputFileName) - 1] = 0;
queueInputFile(inputFileName);
}
}
FILE *outfp = NULL;
if (outfile) {
fprintf(stderr, "Writing to %s\n", outfile);
outfp = fopen(outfile, "w+");
if (!outfp) {
perror(outfile);
return 1;
}
s_outFd = fileno(outfp);
ftruncate(s_outFd, 0);
} else {
fprintf(stderr, "Writing to stdout\n\n");
}
struct InputFileLinkedListEntry *entry = s_inputFiles;
while (entry) {
char *name = entry->name;
//Only include files
serr = stat(name, &statBuf);
if ((serr == 0) && S_ISREG(statBuf.st_mode)) {
//Strip off './' or '/' madness.
realName = name;
if (name[0] == '.' && name[1] == '/') { realName += 2; }
if (realName[0] == '/') { realName++; }
f = open(name, O_RDONLY);
if (f > 0) {
const char *compName = "unknown";
rate = handleFile(f, realName, compType, compLvl, &compName);
fprintf(stderr, "%s (%d%%, %s)\n", realName, rate, compName);
close(f);
} else {
perror(name);
}
} else if (serr != 0) {
perror(name);
}
entry = entry->next;
}
finishArchive();
fsync(s_outFd);
if (outfp) {
fclose(outfp);
}
return 0;
show_help:
fprintf(stderr, "%s - Program to create espfs images\n", argv[0]);
fprintf(stderr, "Options:\n");
fprintf(stderr, "[-p|--parse FILE]\n Parse an espfs file and show a list of its contents. No other options apply in this mode.\n");
fprintf(stderr, "[-c|--compress COMPRESSOR]\n 0 - None, 1 - Heatshrink (default)\n");
fprintf(stderr, "[-l|--level LEVEL] or [-0 through -9]\n compression level 1-9, higher is better but uses more RAM\n");
fprintf(stderr, "[-z|--gzip]\n use gzip for files with extensions matching "DEFAULT_GZIP_EXTS"\n");
fprintf(stderr, "[-Z|--gzip-all]\n use gzip for all files\n");
fprintf(stderr, "[-g|--gzip-exts GZIPPED_EXTENSIONS]\n use gzip for files with custom extensions, comma-separated\n");
fprintf(stderr, "[-i|--input FILE]\n Input file, can be multiple. Files can also be passed at the end without -i, or as lines on stdin if not specified by args\n");
fprintf(stderr, "[-o|--output FILE]\n Output file name; if not specified, outputs to stdout\n");
fprintf(stderr, "[-h|--help\n Show help.\n\n");
exit(err);
}

@ -0,0 +1,319 @@
/*
This is a simple read-only implementation of a file system. It uses a block of data coming from the
mkespfsimg tool, and can use that block to do abstracted operations on the files that are in there.
It's written for use with httpd, but doesn't need to be used as such.
*/
/*
* ----------------------------------------------------------------------------
* "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 <stdio.h>
#include <stdint.h>
#include <string.h>
#include "espfsformat.h"
#include "espfs.h"
#include "heatshrink_decoder.h"
#include "logging.h"
// forward declaration for use in the stand-alone espfs tool
int httpdPlatEspfsRead(void *dest, uint32_t offset, size_t len);
struct EspFsFile {
uint32_t header;
char decompressor;
uint32_t posDecomp;
uint32_t posStart;
uint32_t posComp;
heatshrink_decoder *decompData;
};
EspFsInitResult espFsInit()
{
// check if there is valid header at address
EspFsHeader testHeader;
int rv;
rv = httpdPlatEspfsRead(&testHeader, 0, sizeof(EspFsHeader));
if (rv != 0 || testHeader.magic != ESPFS_MAGIC) {
espfs_error("[EspFS] Invalid magic on first file header");
return ESPFS_INIT_RESULT_NO_IMAGE;
}
return ESPFS_INIT_RESULT_OK;
}
// Returns flags of opened file.
int espFsFlags(EspFsFile *fh)
{
if (fh == NULL) {
espfs_error("[EspFS] File handle not ready");
return -1;
}
int8_t flags;
httpdPlatEspfsRead(&flags, fh->header + offsetof(EspFsHeader, flags), 1);
return (int) flags;
}
void espFsWalkInit(EspFsWalk *walk)
{
if (!walk) { return; }
walk->hpos = 0;
}
bool espFsWalkNext(EspFsWalk *walk, EspFsHeader *header, char *namebuf, size_t namebuf_cap, uint32_t *filepos)
{
int rv;
if (!header || !namebuf) {
espfs_error("[EspFS] espFsWalkNext NULL header or namebuf arg");
return false;
}
uint32_t p = walk->hpos;
//Grab the next file header.
rv = httpdPlatEspfsRead(header, p, sizeof(EspFsHeader));
if (rv != 0) {
return false;
}
if (header->magic != ESPFS_MAGIC) {
espfs_error("[EspFS] Magic mismatch. EspFS image broken.");
return false;
}
if (header->flags & FLAG_LASTFILE) {
espfs_dbg("[EspFS] End of image.");
return false;
}
if (header->nameLen > namebuf_cap) {
espfs_dbg("[EspFS] Name too long for buffer");
return false;
}
//Grab the name of the file.
p += sizeof(EspFsHeader);
httpdPlatEspfsRead(namebuf, p, header->nameLen);
namebuf[header->nameLen] = 0; // ensure it's terminated
if (filepos) {
*filepos = walk->hpos;
}
walk->hpos += sizeof(EspFsHeader) + header->nameLen + header->fileLenComp;
// Align
while(walk->hpos & 3) {
walk->hpos++;
}
return true;
}
//Open a file and return a pointer to the file desc struct.
EspFsFile *espFsOpenFromHeader(EspFsHeader *h, uint32_t hpos)
{
int rv;
if (h->magic != ESPFS_MAGIC) {
espfs_error("[EspFS] Magic mismatch. EspFS image broken.");
return NULL;
}
if (h->flags & FLAG_LASTFILE) {
espfs_dbg("[EspFS] End of image.");
return NULL;
}
hpos += sizeof(EspFsHeader);
hpos += h->nameLen; // Skip to content
EspFsFile *r = (EspFsFile *) httpdPlatMalloc(sizeof(EspFsFile)); //Alloc file desc mem
if (r == NULL) { return NULL; }
r->header = hpos;
r->decompressor = h->compression;
r->posComp = hpos;
r->posStart = hpos;
r->posDecomp = 0;
espfs_dbg("[EspFS] Found file @ hpos %d", hpos);
if (h->compression == COMPRESS_NONE) {
r->decompData = NULL;
} else if (h->compression == COMPRESS_HEATSHRINK) {
//File is compressed with Heatshrink.
char parm;
//Decoder params are stored in 1st byte.
rv = httpdPlatEspfsRead(&parm, r->posComp, 1);
if (rv != 0) {
return NULL;
}
r->posComp++;
espfs_dbg("[EspFS] Heatshrink compressed file; decode parms = %x", parm);
heatshrink_decoder *dec = heatshrink_decoder_alloc(16, (parm >> 4) & 0xf, parm & 0xf);
r->decompData = dec;
return r;
} else {
espfs_error("[EspFS] Invalid compression: %d", h->compression);
httpdPlatFree(r);
return NULL;
}
}
//Open a file and return a pointer to the file desc struct.
EspFsFile *espFsOpenAt(uint32_t hpos)
{
EspFsHeader h;
int rv = httpdPlatEspfsRead(&h, hpos, sizeof(EspFsHeader));
if (rv != 0) {
return NULL;
}
return espFsOpenFromHeader(&h, hpos);
}
//Open a file and return a pointer to the file desc struct.
EspFsFile *espFsOpen(const char *fileName)
{
int rv;
uint32_t p = 0;
uint32_t hpos;
char namebuf[256];
EspFsHeader h;
EspFsFile *r;
//Strip initial slashes
while (fileName[0] == '/') { fileName++; }
espfs_dbg("[EspFS] Open file: %s", fileName);
//Go find that file!
while (1) {
hpos = p;
//Grab the next file header.
rv = httpdPlatEspfsRead(&h, p, sizeof(EspFsHeader));
if (rv != 0) {
return NULL;
}
if (h.magic != ESPFS_MAGIC) {
espfs_error("[EspFS] Magic mismatch. EspFS image broken.");
return NULL;
}
if (h.flags & FLAG_LASTFILE) {
espfs_dbg("[EspFS] End of image.");
return NULL;
}
//Grab the name of the file.
p += sizeof(EspFsHeader);
rv = httpdPlatEspfsRead(&namebuf, p, h.nameLen);
if (rv != 0) {
return NULL;
}
namebuf[h.nameLen] = 0; // ensure it's terminated
if (strcmp(namebuf, fileName) == 0) {
//Yay, this is the file we need!
return espFsOpenFromHeader(&h, hpos);
}
//We don't need this file. Skip name and file
p += h.nameLen + h.fileLenComp;
while(p & 3) {
p++;
}
}
}
//Read len bytes from the given file into buff. Returns the actual amount of bytes read.
int espFsRead(EspFsFile *fh, char *buff, size_t len)
{
int rv;
int flen;
int fdlen;
if (fh == NULL) { return 0; }
rv = httpdPlatEspfsRead(&flen, fh->header + offsetof(EspFsHeader, fileLenComp), 4);
if (rv != 0) {
return 0;
}
//Cache file length.
//Do stuff depending on the way the file is compressed.
if (fh->decompressor == COMPRESS_NONE) {
int toRead = flen - (int) (fh->posComp - fh->posStart);
if (toRead < 0) { toRead = 0; }
if (len > toRead) { len = toRead; }
rv = httpdPlatEspfsRead(buff, fh->posComp, len);
if (rv != 0) {
return 0;
}
fh->posDecomp += len;
fh->posComp += len;
return (int) len;
} else if (fh->decompressor == COMPRESS_HEATSHRINK) {
rv = httpdPlatEspfsRead(&fdlen, fh->header + offsetof(EspFsHeader, fileLenDecomp), 4);
if (rv != 0) {
return 0;
}
size_t decoded = 0;
size_t elen, rlen;
char ebuff[16];
heatshrink_decoder *dec = fh->decompData;
if (fh->posDecomp == fdlen) {
return 0;
}
// We must ensure that whole file is decompressed and written to output buffer.
// This means even when there is no input data (elen==0) try to poll decoder until
// posDecomp equals decompressed file length
while (decoded < len) {
//Feed data into the decompressor
//ToDo: Check ret val of heatshrink fns for errors
elen = flen - (fh->posComp - fh->posStart);
if (elen > 0) {
rv = httpdPlatEspfsRead(ebuff, fh->posComp, 16);
if (rv != 0) {
return 0;
}
heatshrink_decoder_sink(dec, (uint8_t *) ebuff, (elen > 16) ? 16 : elen, &rlen);
fh->posComp += rlen;
}
//Grab decompressed data and put into buff
heatshrink_decoder_poll(dec, (uint8_t *) buff, len - decoded, &rlen);
fh->posDecomp += rlen;
buff += rlen;
decoded += rlen;
if (elen == 0) {
if (fh->posDecomp == fdlen) {
heatshrink_decoder_finish(dec);
}
return (int) decoded;
}
}
return (int) len;
}
return 0;
}
//Close the file.
void espFsClose(EspFsFile *fh)
{
if (fh == NULL) { return; }
if (fh->decompressor == COMPRESS_HEATSHRINK) {
heatshrink_decoder *dec = (heatshrink_decoder *) fh->decompData;
heatshrink_decoder_free(dec);
}
httpdPlatFree(fh);
}

@ -0,0 +1,41 @@
#pragma once
#include <stdbool.h>
#include "espfsformat.h"
typedef enum {
ESPFS_INIT_RESULT_OK,
ESPFS_INIT_RESULT_NO_IMAGE,
ESPFS_INIT_RESULT_BAD_ALIGN,
} EspFsInitResult;
typedef struct EspFsFile EspFsFile;
struct EspFsWalk {
uint32_t hpos;
};
typedef struct EspFsWalk EspFsWalk;
/** Init filesystem walk */
void espFsWalkInit(EspFsWalk *walk);
/**
* Advance in the filesystem walk
*
* header - the next file's header is read here
* namebuf - the name is read here
* filepos - the file header's pos is copied here, if not NULL
*/
bool espFsWalkNext(EspFsWalk *walk, EspFsHeader *header, char *namebuf, size_t namebuf_cap, uint32_t *filepos);
/** Open a file with header starting at the given position */
EspFsFile *espFsOpenAt(uint32_t hpos);
/**
* Open a file using an already read header and it's offset.
* This is the same function as espFsOpenAt, but avoids reading the header again if already read.
*/
EspFsFile *espFsOpenFromHeader(EspFsHeader *h, uint32_t hpos);
EspFsInitResult espFsInit();
EspFsFile *espFsOpen(const char *fileName);
int espFsFlags(EspFsFile *fh);
int espFsRead(EspFsFile *fh, char *buff, size_t len);
void espFsClose(EspFsFile *fh);

@ -0,0 +1,25 @@
#pragma once
/*
The idea 'borrows' from cpio: it's basically a concatenation of {header, filename, file} data.
Header, filename and file data is 32-bit aligned. The last file is indicated by data-less header
with the FLAG_LASTFILE flag set.
*/
#include <stdint.h>
#define FLAG_LASTFILE (1<<0)
#define FLAG_GZIP (1<<1)
#define COMPRESS_NONE 0
#define COMPRESS_HEATSHRINK 1
#define ESPFS_MAGIC 0x73665345 /* ASCII ESfs - when read as little endian */
/* 16 bytes long for alignment */
typedef struct {
uint32_t magic;
uint8_t flags;
uint8_t compression;
uint16_t nameLen;
uint32_t fileLenComp;
uint32_t fileLenDecomp;
} __attribute__((packed)) EspFsHeader;

@ -0,0 +1,17 @@
#pragma once
#define HEATSHRINK_AUTHOR "Scott Vokes <vokes.s@gmail.com>"
#define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink"
/* Version 0.4.1 */
#define HEATSHRINK_VERSION_MAJOR 0
#define HEATSHRINK_VERSION_MINOR 4
#define HEATSHRINK_VERSION_PATCH 1
#define HEATSHRINK_MIN_WINDOW_BITS 4
#define HEATSHRINK_MAX_WINDOW_BITS 15
#define HEATSHRINK_MIN_LOOKAHEAD_BITS 3
#define HEATSHRINK_LITERAL_MARKER 0x01
#define HEATSHRINK_BACKREF_MARKER 0x00

@ -0,0 +1,40 @@
#pragma once
/* Should functionality assuming dynamic allocation be used? */
#ifndef HEATSHRINK_DYNAMIC_ALLOC
#define HEATSHRINK_DYNAMIC_ALLOC 1
#endif
#if HEATSHRINK_DYNAMIC_ALLOC
// forward declare - needed when building the heatshrink compressor
void *httpdPlatMalloc(size_t len);
void httpdPlatFree(void *ptr);
/* Optional replacement of malloc/free */
#define HEATSHRINK_MALLOC(SZ) httpdPlatMalloc(SZ)
#define HEATSHRINK_FREE(P, SZ) httpdPlatFree(P)
#else
/* Required parameters for static configuration */
#ifndef HEATSHRINK_STATIC_INPUT_BUFFER_SIZE
#define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32
#endif
#ifndef HEATSHRINK_STATIC_WINDOW_BITS
#define HEATSHRINK_STATIC_WINDOW_BITS 8
#endif
#ifndef HEATSHRINK_STATIC_LOOKAHEAD_BITS
#define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4
#endif
#endif
/* Turn on logging for debugging. */
#ifndef HEATSHRINK_DEBUGGING_LOGS
#define HEATSHRINK_DEBUGGING_LOGS 0
#endif
/* Use indexing for faster compression. (This requires additional space.) */
#ifndef HEATSHRINK_USE_INDEX
#define HEATSHRINK_USE_INDEX 1
#endif

@ -0,0 +1,374 @@
#include <stdlib.h>
#include <string.h>
#include "heatshrink_decoder.h"
/* States for the polling state machine. */
typedef enum {
HSDS_TAG_BIT, /* tag bit */
HSDS_YIELD_LITERAL, /* ready to yield literal byte */
HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */
HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */
HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */
HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */
HSDS_YIELD_BACKREF, /* ready to yield back-reference */
} HSD_state;
#if HEATSHRINK_DEBUGGING_LOGS
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#define ASSERT(X) assert(X)
static const char *state_names[] = {
"tag_bit",
"yield_literal",
"backref_index_msb",
"backref_index_lsb",
"backref_count_msb",
"backref_count_lsb",
"yield_backref",
};
#else
#define LOG(...) /* no-op */
#define ASSERT(X) /* no-op */
#endif
typedef struct {
uint8_t *buf; /* output buffer */
size_t buf_size; /* buffer size */
size_t *output_size; /* bytes pushed to buffer, so far */
} output_info;
#define NO_BITS ((uint16_t)-1)
/* Forward references. */
static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count);
static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte);
#if HEATSHRINK_DYNAMIC_ALLOC
heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size,
uint8_t window_sz2,
uint8_t lookahead_sz2) {
if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) ||
(window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) ||
(input_buffer_size == 0) ||
(lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) ||
(lookahead_sz2 >= window_sz2)) {
return NULL;
}
size_t buffers_sz = (1 << window_sz2) + input_buffer_size;
size_t sz = sizeof(heatshrink_decoder) + buffers_sz;
heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz);
if (hsd == NULL) { return NULL; }
hsd->input_buffer_size = input_buffer_size;
hsd->window_sz2 = window_sz2;
hsd->lookahead_sz2 = lookahead_sz2;
heatshrink_decoder_reset(hsd);
LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n",
sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size);
return hsd;
}
void heatshrink_decoder_free(heatshrink_decoder *hsd) {
size_t buffers_sz = (1 << hsd->window_sz2) + hsd->input_buffer_size;
size_t sz = sizeof(heatshrink_decoder) + buffers_sz;
HEATSHRINK_FREE(hsd, sz);
(void)sz; /* may not be used by free */
}
#endif
void heatshrink_decoder_reset(heatshrink_decoder *hsd) {
size_t buf_sz = 1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd);
size_t input_sz = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd);
memset(hsd->buffers, 0, buf_sz + input_sz);
hsd->state = HSDS_TAG_BIT;
hsd->input_size = 0;
hsd->input_index = 0;
hsd->bit_index = 0x00;
hsd->current_byte = 0x00;
hsd->output_count = 0;
hsd->output_index = 0;
hsd->head_index = 0;
}
/* Copy SIZE bytes into the decoder's input buffer, if it will fit. */
HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd,
uint8_t *in_buf, size_t size, size_t *input_size) {
if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) {
return HSDR_SINK_ERROR_NULL;
}
size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size;
if (rem == 0) {
*input_size = 0;
return HSDR_SINK_FULL;
}
size = rem < size ? rem : size;
LOG("-- sinking %zd bytes\n", size);
/* copy into input buffer (at head of buffers) */
memcpy(&hsd->buffers[hsd->input_size], in_buf, size);
hsd->input_size += (uint16_t)size;
*input_size = size;
return HSDR_SINK_OK;
}
/*****************
* Decompression *
*****************/
#define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD))
#define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD))
// States
static HSD_state st_tag_bit(heatshrink_decoder *hsd);
static HSD_state st_yield_literal(heatshrink_decoder *hsd,
output_info *oi);
static HSD_state st_backref_index_msb(heatshrink_decoder *hsd);
static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd);
static HSD_state st_backref_count_msb(heatshrink_decoder *hsd);
static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd);
static HSD_state st_yield_backref(heatshrink_decoder *hsd,
output_info *oi);
HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd,
uint8_t *out_buf, size_t out_buf_size, size_t *output_size) {
if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) {
return HSDR_POLL_ERROR_NULL;
}
*output_size = 0;
output_info oi;
oi.buf = out_buf;
oi.buf_size = out_buf_size;
oi.output_size = output_size;
while (1) {
LOG("-- poll, state is %d (%s), input_size %d\n",
hsd->state, state_names[hsd->state], hsd->input_size);
uint8_t in_state = hsd->state;
HSD_state next_state;
switch (in_state) {
case HSDS_TAG_BIT:
next_state = st_tag_bit(hsd);
break;
case HSDS_YIELD_LITERAL:
next_state = st_yield_literal(hsd, &oi);
break;
case HSDS_BACKREF_INDEX_MSB:
next_state = st_backref_index_msb(hsd);
break;
case HSDS_BACKREF_INDEX_LSB:
next_state = st_backref_index_lsb(hsd);
break;
case HSDS_BACKREF_COUNT_MSB:
next_state = st_backref_count_msb(hsd);
break;
case HSDS_BACKREF_COUNT_LSB:
next_state = st_backref_count_lsb(hsd);
break;
case HSDS_YIELD_BACKREF:
next_state = st_yield_backref(hsd, &oi);
break;
default:
return HSDR_POLL_ERROR_UNKNOWN;
}
hsd->state = (uint8_t)next_state;
/* If the current state cannot advance, check if input or output
* buffer are exhausted. */
if (hsd->state == in_state) {
if (*output_size == out_buf_size) { return HSDR_POLL_MORE; }
return HSDR_POLL_EMPTY;
}
}
}
static HSD_state st_tag_bit(heatshrink_decoder *hsd) {
const uint32_t bits = get_bits(hsd, 1); // get tag bit
if (bits == NO_BITS) {
return HSDS_TAG_BIT;
} else if (bits > 0) {
return HSDS_YIELD_LITERAL;
/* This suppresses a warning for unreachable code in non-dynamic
* builds where the window bits is always <= 8. */
#if HEATSHRINK_DYNAMIC_ALLOC || HEATSHRINK_STATIC_WINDOW_BITS > 8
} else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) {
return HSDS_BACKREF_INDEX_MSB;
#endif
} else {
hsd->output_index = 0;
return HSDS_BACKREF_INDEX_LSB;
}
}
static HSD_state st_yield_literal(heatshrink_decoder *hsd,
output_info *oi) {
/* Emit a repeated section from the window buffer, and add it (again)
* to the window buffer. (Note that the repetition can include
* itself.)*/
if (*oi->output_size < oi->buf_size) {
uint16_t byte = get_bits(hsd, 8);
if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */
uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)];
uint16_t mask = (uint16_t)(1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1;
uint8_t c = byte & 0xFF;
LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.');
buf[hsd->head_index++ & mask] = c;
push_byte(hsd, oi, c);
return HSDS_TAG_BIT;
} else {
return HSDS_YIELD_LITERAL;
}
}
static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) {
uint8_t bit_ct = BACKREF_INDEX_BITS(hsd);
ASSERT(bit_ct > 8);
uint16_t bits = get_bits(hsd, bit_ct - 8);
LOG("-- backref index (msb), got 0x%04x (+1)\n", bits);
if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; }
hsd->output_index = (uint16_t)(bits << 8);
return HSDS_BACKREF_INDEX_LSB;
}
static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) {
uint8_t bit_ct = BACKREF_INDEX_BITS(hsd);
uint16_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8);
LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits);
if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; }
hsd->output_index |= bits;
hsd->output_index++;
uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd);
hsd->output_count = 0;
return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB;
}
static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) {
uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd);
ASSERT(br_bit_ct > 8);
uint16_t bits = get_bits(hsd, br_bit_ct - 8);
LOG("-- backref count (msb), got 0x%04x (+1)\n", bits);
if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; }
hsd->output_count = (uint16_t)(bits << 8);
return HSDS_BACKREF_COUNT_LSB;
}
static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) {
uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd);
uint16_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8);
LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits);
if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; }
hsd->output_count |= bits;
hsd->output_count++;
return HSDS_YIELD_BACKREF;
}
static HSD_state st_yield_backref(heatshrink_decoder *hsd,
output_info *oi) {
size_t count = oi->buf_size - *oi->output_size;
if (count > 0) {
size_t i = 0;
if (hsd->output_count < count) count = hsd->output_count;
uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)];
uint16_t mask = (uint16_t)((1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1);
uint16_t neg_offset = hsd->output_index;
LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset);
ASSERT(neg_offset <= mask + 1);
ASSERT(count <= (size_t)(1 << BACKREF_COUNT_BITS(hsd)));
for (i=0; i<count; i++) {
uint8_t c = buf[(hsd->head_index - neg_offset) & mask];
push_byte(hsd, oi, c);
buf[hsd->head_index & mask] = c;
hsd->head_index++;
LOG(" -- ++ 0x%02x\n", c);
}
hsd->output_count -= (uint16_t)count;
if (hsd->output_count == 0) { return HSDS_TAG_BIT; }
}
return HSDS_YIELD_BACKREF;
}
/* Get the next COUNT bits from the input buffer, saving incremental progress.
* Returns NO_BITS on end of input, or if more than 15 bits are requested. */
static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count) {
uint16_t accumulator = 0;
int i = 0;
if (count > 15) { return NO_BITS; }
LOG("-- popping %u bit(s)\n", count);
/* If we aren't able to get COUNT bits, suspend immediately, because we
* don't track how many bits of COUNT we've accumulated before suspend. */
if (hsd->input_size == 0) {
if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; }
}
for (i = 0; i < count; i++) {
if (hsd->bit_index == 0x00) {
if (hsd->input_size == 0) {
LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n",
accumulator, accumulator);
return NO_BITS;
}
hsd->current_byte = hsd->buffers[hsd->input_index++];
LOG(" -- pulled byte 0x%02x\n", hsd->current_byte);
if (hsd->input_index == hsd->input_size) {
hsd->input_index = 0; /* input is exhausted */
hsd->input_size = 0;
}
hsd->bit_index = 0x80;
}
accumulator <<= 1;
if (hsd->current_byte & hsd->bit_index) {
accumulator |= 0x01;
if (0) {
LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n",
accumulator, hsd->bit_index);
}
} else {
if (0) {
LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n",
accumulator, hsd->bit_index);
}
}
hsd->bit_index >>= 1;
}
if (count > 1) { LOG(" -- accumulated %08x\n", accumulator); }
return accumulator;
}
HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) {
if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; }
switch (hsd->state) {
case HSDS_TAG_BIT:
return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE;
/* If we want to finish with no input, but are in these states, it's
* because the 0-bit padding to the last byte looks like a backref
* marker bit followed by all 0s for index and count bits. */
case HSDS_BACKREF_INDEX_LSB:
case HSDS_BACKREF_INDEX_MSB:
case HSDS_BACKREF_COUNT_LSB:
case HSDS_BACKREF_COUNT_MSB:
return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE;
/* If the output stream is padded with 0xFFs (possibly due to being in
* flash memory), also explicitly check the input size rather than
* uselessly returning MORE but yielding 0 bytes when polling. */
case HSDS_YIELD_LITERAL:
return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE;
default:
return HSDR_FINISH_MORE;
}
}
static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) {
LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.');
oi->buf[(*oi->output_size)++] = byte;
(void)hsd;
}

@ -0,0 +1,97 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "heatshrink_common.h"
#include "heatshrink_config.h"
typedef enum {
HSDR_SINK_OK, /* data sunk, ready to poll */
HSDR_SINK_FULL, /* out of space in internal buffer */
HSDR_SINK_ERROR_NULL=-1, /* NULL argument */
} HSD_sink_res;
typedef enum {
HSDR_POLL_EMPTY, /* input exhausted */
HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */
HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */
HSDR_POLL_ERROR_UNKNOWN=-2,
} HSD_poll_res;
typedef enum {
HSDR_FINISH_DONE, /* output is done */
HSDR_FINISH_MORE, /* more output remains */
HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */
} HSD_finish_res;
#if HEATSHRINK_DYNAMIC_ALLOC
#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \
((BUF)->input_buffer_size)
#define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \
((BUF)->window_sz2)
#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \
((BUF)->lookahead_sz2)
#else
#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \
HEATSHRINK_STATIC_INPUT_BUFFER_SIZE
#define HEATSHRINK_DECODER_WINDOW_BITS(_) \
(HEATSHRINK_STATIC_WINDOW_BITS)
#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \
(HEATSHRINK_STATIC_LOOKAHEAD_BITS)
#endif
typedef struct {
uint16_t input_size; /* bytes in input buffer */
uint16_t input_index; /* offset to next unprocessed input byte */
uint16_t output_count; /* how many bytes to output */
uint16_t output_index; /* index for bytes to output */
uint16_t head_index; /* head of window buffer */
uint8_t state; /* current state machine node */
uint8_t current_byte; /* current byte of input */
uint8_t bit_index; /* current bit index */
#if HEATSHRINK_DYNAMIC_ALLOC
/* Fields that are only used if dynamically allocated. */
uint8_t window_sz2; /* window buffer bits */
uint8_t lookahead_sz2; /* lookahead bits */
uint16_t input_buffer_size; /* input buffer size */
/* Input buffer, then expansion window buffer */
uint8_t buffers[];
#else
/* Input buffer, then expansion window buffer */
uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_))
+ HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)];
#endif
} heatshrink_decoder;
#if HEATSHRINK_DYNAMIC_ALLOC
/* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes,
* an expansion buffer size of 2^WINDOW_SZ2, and a lookahead
* size of 2^lookahead_sz2. (The window buffer and lookahead sizes
* must match the settings used when the data was compressed.)
* Returns NULL on error. */
heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size,
uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2);
/* Free a decoder. */
void heatshrink_decoder_free(heatshrink_decoder *hsd);
#endif
/* Reset a decoder. */
void heatshrink_decoder_reset(heatshrink_decoder *hsd);
/* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to
* indicate how many bytes were actually sunk (in case a buffer was filled). */
HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd,
uint8_t *in_buf, size_t size, size_t *input_size);
/* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into
* OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */
HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd,
uint8_t *out_buf, size_t out_buf_size, size_t *output_size);
/* Notify the decoder that the input stream is finished.
* If the return value is HSDR_FINISH_MORE, there is still more output, so
* call heatshrink_decoder_poll and repeat. */
HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd);

@ -0,0 +1,607 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "heatshrink_encoder.h"
typedef enum {
HSES_NOT_FULL, /* input buffer not full enough */
HSES_FILLED, /* buffer is full */
HSES_SEARCH, /* searching for patterns */
HSES_YIELD_TAG_BIT, /* yield tag bit */
HSES_YIELD_LITERAL, /* emit literal byte */
HSES_YIELD_BR_INDEX, /* yielding backref index */
HSES_YIELD_BR_LENGTH, /* yielding backref length */
HSES_SAVE_BACKLOG, /* copying buffer to backlog */
HSES_FLUSH_BITS, /* flush bit buffer */
HSES_DONE, /* done */
} HSE_state;
#if HEATSHRINK_DEBUGGING_LOGS
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#define ASSERT(X) assert(X)
static const char *state_names[] = {
"not_full",
"filled",
"search",
"yield_tag_bit",
"yield_literal",
"yield_br_index",
"yield_br_length",
"save_backlog",
"flush_bits",
"done",
};
#else
#define LOG(...) /* no-op */
#define ASSERT(X) /* no-op */
#endif
// Encoder flags
enum {
FLAG_IS_FINISHING = 0x01,
};
typedef struct {
uint8_t *buf; /* output buffer */
size_t buf_size; /* buffer size */
size_t *output_size; /* bytes pushed to buffer, so far */
} output_info;
#define MATCH_NOT_FOUND ((uint16_t)-1)
static uint16_t get_input_offset(heatshrink_encoder *hse);
static uint16_t get_input_buffer_size(heatshrink_encoder *hse);
static uint16_t get_lookahead_size(heatshrink_encoder *hse);
static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag);
static int can_take_byte(output_info *oi);
static int is_finishing(heatshrink_encoder *hse);
static void save_backlog(heatshrink_encoder *hse);
/* Push COUNT (max 8) bits to the output buffer, which has room. */
static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits,
output_info *oi);
static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi);
static void push_literal_byte(heatshrink_encoder *hse, output_info *oi);
#if HEATSHRINK_DYNAMIC_ALLOC
heatshrink_encoder *heatshrink_encoder_alloc(uint8_t window_sz2,
uint8_t lookahead_sz2) {
if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) ||
(window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) ||
(lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) ||
(lookahead_sz2 >= window_sz2)) {
return NULL;
}
/* Note: 2 * the window size is used because the buffer needs to fit
* (1 << window_sz2) bytes for the current input, and an additional
* (1 << window_sz2) bytes for the previous buffer of input, which
* will be scanned for useful backreferences. */
size_t buf_sz = (2U << window_sz2);
heatshrink_encoder *hse = HEATSHRINK_MALLOC(sizeof(*hse) + buf_sz);
if (hse == NULL) { return NULL; }
hse->window_sz2 = window_sz2;
hse->lookahead_sz2 = lookahead_sz2;
heatshrink_encoder_reset(hse);
#if HEATSHRINK_USE_INDEX
size_t index_sz = buf_sz*sizeof(uint16_t);
hse->search_index = HEATSHRINK_MALLOC(index_sz + sizeof(struct hs_index));
if (hse->search_index == NULL) {
HEATSHRINK_FREE(hse, sizeof(*hse) + buf_sz);
return NULL;
}
hse->search_index->size = (uint16_t)index_sz;
#endif
LOG("-- allocated encoder with buffer size of %zu (%u byte input size)\n",
buf_sz, get_input_buffer_size(hse));
return hse;
}
void heatshrink_encoder_free(heatshrink_encoder *hse) {
size_t buf_sz = (2U << HEATSHRINK_ENCODER_WINDOW_BITS(hse));
#if HEATSHRINK_USE_INDEX
size_t index_sz = sizeof(struct hs_index) + hse->search_index->size;
HEATSHRINK_FREE(hse->search_index, index_sz);
(void)index_sz;
#endif
HEATSHRINK_FREE(hse, sizeof(heatshrink_encoder) + buf_sz);
(void)buf_sz;
}
#endif
void heatshrink_encoder_reset(heatshrink_encoder *hse) {
size_t buf_sz = (2U << HEATSHRINK_ENCODER_WINDOW_BITS(hse));
memset(hse->buffer, 0, buf_sz);
hse->input_size = 0;
hse->state = HSES_NOT_FULL;
hse->match_scan_index = 0;
hse->flags = 0;
hse->bit_index = 0x80;
hse->current_byte = 0x00;
hse->match_length = 0;
hse->outgoing_bits = 0x0000;
hse->outgoing_bits_count = 0;
#ifdef LOOP_DETECT
hse->loop_detect = (uint32_t)-1;
#endif
}
HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse,
uint8_t *in_buf, size_t size, size_t *input_size) {
if ((hse == NULL) || (in_buf == NULL) || (input_size == NULL)) {
return HSER_SINK_ERROR_NULL;
}
/* Sinking more content after saying the content is done, tsk tsk */
if (is_finishing(hse)) { return HSER_SINK_ERROR_MISUSE; }
/* Sinking more content before processing is done */
if (hse->state != HSES_NOT_FULL) { return HSER_SINK_ERROR_MISUSE; }
uint16_t write_offset = get_input_offset(hse) + hse->input_size;
uint16_t ibs = get_input_buffer_size(hse);
uint16_t rem = ibs - hse->input_size;
uint16_t cp_sz = rem < size ? rem : (uint16_t)size;
memcpy(&hse->buffer[write_offset], in_buf, cp_sz);
*input_size = cp_sz;
hse->input_size += cp_sz;
LOG("-- sunk %u bytes (of %zu) into encoder at %d, input buffer now has %u\n",
cp_sz, size, write_offset, hse->input_size);
if (cp_sz == rem) {
LOG("-- internal buffer is now full\n");
hse->state = HSES_FILLED;
}
return HSER_SINK_OK;
}
/***************
* Compression *
***************/
static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start,
uint16_t end, const uint16_t maxlen, uint16_t *match_length);
static void do_indexing(heatshrink_encoder *hse);
static HSE_state st_step_search(heatshrink_encoder *hse);
static HSE_state st_yield_tag_bit(heatshrink_encoder *hse,
output_info *oi);
static HSE_state st_yield_literal(heatshrink_encoder *hse,
output_info *oi);
static HSE_state st_yield_br_index(heatshrink_encoder *hse,
output_info *oi);
static HSE_state st_yield_br_length(heatshrink_encoder *hse,
output_info *oi);
static HSE_state st_save_backlog(heatshrink_encoder *hse);
static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse,
output_info *oi);
HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse,
uint8_t *out_buf, size_t out_buf_size, size_t *output_size) {
if ((hse == NULL) || (out_buf == NULL) || (output_size == NULL)) {
return HSER_POLL_ERROR_NULL;
}
if (out_buf_size == 0) {
LOG("-- MISUSE: output buffer size is 0\n");
return HSER_POLL_ERROR_MISUSE;
}
*output_size = 0;
output_info oi;
oi.buf = out_buf;
oi.buf_size = out_buf_size;
oi.output_size = output_size;
while (1) {
LOG("-- polling, state %u (%s), flags 0x%02x\n",
hse->state, state_names[hse->state], hse->flags);
const uint8_t in_state = hse->state;
HSE_state next_state;
switch (in_state) {
case HSES_NOT_FULL:
return HSER_POLL_EMPTY;
case HSES_FILLED:
do_indexing(hse);
next_state = HSES_SEARCH;
break;
case HSES_SEARCH:
next_state = st_step_search(hse);
break;
case HSES_YIELD_TAG_BIT:
next_state = st_yield_tag_bit(hse, &oi);
break;
case HSES_YIELD_LITERAL:
next_state = st_yield_literal(hse, &oi);
break;
case HSES_YIELD_BR_INDEX:
next_state = st_yield_br_index(hse, &oi);
break;
case HSES_YIELD_BR_LENGTH:
next_state = st_yield_br_length(hse, &oi);
break;
case HSES_SAVE_BACKLOG:
next_state = st_save_backlog(hse);
break;
case HSES_FLUSH_BITS:
hse->state = (uint8_t)st_flush_bit_buffer(hse, &oi);
return HSER_POLL_EMPTY;
case HSES_DONE:
return HSER_POLL_EMPTY;
default:
LOG("-- bad state %s\n", state_names[hse->state]);
return HSER_POLL_ERROR_MISUSE;
}
hse->state = (uint8_t)next_state;
if (hse->state == in_state) {
/* Check if output buffer is exhausted. */
if (*output_size == out_buf_size) return HSER_POLL_MORE;
}
}
}
HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse) {
if (hse == NULL) { return HSER_FINISH_ERROR_NULL; }
LOG("-- setting is_finishing flag\n");
hse->flags |= FLAG_IS_FINISHING;
if (hse->state == HSES_NOT_FULL) { hse->state = HSES_FILLED; }
return hse->state == HSES_DONE ? HSER_FINISH_DONE : HSER_FINISH_MORE;
}
static HSE_state st_step_search(heatshrink_encoder *hse) {
uint16_t window_length = get_input_buffer_size(hse);
uint16_t lookahead_sz = get_lookahead_size(hse);
uint16_t msi = hse->match_scan_index;
LOG("## step_search, scan @ +%d (%d/%d), input size %d\n",
msi, hse->input_size + msi, 2*window_length, hse->input_size);
bool fin = is_finishing(hse);
if (msi > hse->input_size - (fin ? 1 : lookahead_sz)) {
/* Current search buffer is exhausted, copy it into the
* backlog and await more input. */
LOG("-- end of search @ %d\n", msi);
return fin ? HSES_FLUSH_BITS : HSES_SAVE_BACKLOG;
}
uint16_t input_offset = get_input_offset(hse);
uint16_t end = input_offset + msi;
uint16_t start = end - window_length;
uint16_t max_possible = lookahead_sz;
if (hse->input_size - msi < lookahead_sz) {
max_possible = hse->input_size - msi;
}
uint16_t match_length = 0;
uint16_t match_pos = find_longest_match(hse,
start, end, max_possible, &match_length);
if (match_pos == MATCH_NOT_FOUND) {
LOG("ss Match not found\n");
hse->match_scan_index++;
hse->match_length = 0;
return HSES_YIELD_TAG_BIT;
} else {
LOG("ss Found match of %d bytes at %d\n", match_length, match_pos);
hse->match_pos = match_pos;
hse->match_length = match_length;
ASSERT(match_pos <= 1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse) /*window_length*/);
return HSES_YIELD_TAG_BIT;
}
}
static HSE_state st_yield_tag_bit(heatshrink_encoder *hse,
output_info *oi) {
if (can_take_byte(oi)) {
if (hse->match_length == 0) {
add_tag_bit(hse, oi, HEATSHRINK_LITERAL_MARKER);
return HSES_YIELD_LITERAL;
} else {
add_tag_bit(hse, oi, HEATSHRINK_BACKREF_MARKER);
hse->outgoing_bits = hse->match_pos - 1;
hse->outgoing_bits_count = HEATSHRINK_ENCODER_WINDOW_BITS(hse);
return HSES_YIELD_BR_INDEX;
}
} else {
return HSES_YIELD_TAG_BIT; /* output is full, continue */
}
}
static HSE_state st_yield_literal(heatshrink_encoder *hse,
output_info *oi) {
if (can_take_byte(oi)) {
push_literal_byte(hse, oi);
return HSES_SEARCH;
} else {
return HSES_YIELD_LITERAL;
}
}
static HSE_state st_yield_br_index(heatshrink_encoder *hse,
output_info *oi) {
if (can_take_byte(oi)) {
LOG("-- yielding backref index %u\n", hse->match_pos);
if (push_outgoing_bits(hse, oi) > 0) {
return HSES_YIELD_BR_INDEX; /* continue */
} else {
hse->outgoing_bits = hse->match_length - 1;
hse->outgoing_bits_count = HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse);
return HSES_YIELD_BR_LENGTH; /* done */
}
} else {
return HSES_YIELD_BR_INDEX; /* continue */
}
}
static HSE_state st_yield_br_length(heatshrink_encoder *hse,
output_info *oi) {
if (can_take_byte(oi)) {
LOG("-- yielding backref length %u\n", hse->match_length);
if (push_outgoing_bits(hse, oi) > 0) {
return HSES_YIELD_BR_LENGTH;
} else {
hse->match_scan_index += hse->match_length;
hse->match_length = 0;
return HSES_SEARCH;
}
} else {
return HSES_YIELD_BR_LENGTH;
}
}
static HSE_state st_save_backlog(heatshrink_encoder *hse) {
LOG("-- saving backlog\n");
save_backlog(hse);
return HSES_NOT_FULL;
}
static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse,
output_info *oi) {
if (hse->bit_index == 0x80) {
LOG("-- done!\n");
return HSES_DONE;
} else if (can_take_byte(oi)) {
LOG("-- flushing remaining byte (bit_index == 0x%02x)\n", hse->bit_index);
oi->buf[(*oi->output_size)++] = hse->current_byte;
LOG("-- done!\n");
return HSES_DONE;
} else {
return HSES_FLUSH_BITS;
}
}
static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag) {
LOG("-- adding tag bit: %d\n", tag);
push_bits(hse, 1, tag, oi);
}
static uint16_t get_input_offset(heatshrink_encoder *hse) {
return get_input_buffer_size(hse);
}
static uint16_t get_input_buffer_size(heatshrink_encoder *hse) {
(void)hse;
return (uint16_t)(1U << HEATSHRINK_ENCODER_WINDOW_BITS(hse));
}
static uint16_t get_lookahead_size(heatshrink_encoder *hse) {
(void)hse;
return (uint16_t)(1U << HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse));
}
static void do_indexing(heatshrink_encoder *hse) {
#if HEATSHRINK_USE_INDEX
/* Build an index array I that contains flattened linked lists
* for the previous instances of every byte in the buffer.
*
* For example, if buf[200] == 'x', then index[200] will either
* be an offset i such that buf[i] == 'x', or a negative offset
* to indicate end-of-list. This significantly speeds up matching,
* while only using sizeof(uint16_t)*sizeof(buffer) bytes of RAM.
*
* Future optimization options:
* 1. Since any negative value represents end-of-list, the other
* 15 bits could be used to improve the index dynamically.
*
* 2. Likewise, the last lookahead_sz bytes of the index will
* not be usable, so temporary data could be stored there to
* dynamically improve the index.
* */
struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse);
int16_t last[256];
memset(last, 0xFF, sizeof(last));
uint8_t * const data = hse->buffer;
int16_t * const index = hsi->index;
const uint16_t input_offset = get_input_offset(hse);
const uint16_t end = input_offset + hse->input_size;
for (int16_t i = 0; i < end; i++) {
uint8_t v = data[i];
int16_t lv = last[v];
index[i] = lv;
last[v] = i;
}
#else
(void)hse;
#endif
}
static int is_finishing(heatshrink_encoder *hse) {
return hse->flags & FLAG_IS_FINISHING;
}
static int can_take_byte(output_info *oi) {
return *oi->output_size < oi->buf_size;
}
/* Return the longest match for the bytes at buf[end:end+maxlen] between
* buf[start] and buf[end-1]. If no match is found, return -1. */
static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start,
uint16_t end, const uint16_t maxlen, uint16_t *match_length) {
LOG("-- scanning for match of buf[%u:%u] between buf[%u:%u] (max %u bytes)\n",
end, end + maxlen, start, end + maxlen - 1, maxlen);
uint8_t *buf = hse->buffer;
uint16_t match_maxlen = 0;
uint16_t match_index = MATCH_NOT_FOUND;
uint16_t len = 0;
uint8_t * const needlepoint = &buf[end];
#if HEATSHRINK_USE_INDEX
struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse);
int16_t pos = hsi->index[end];
while (pos - (int16_t)start >= 0) {
uint8_t * const pospoint = &buf[pos];
/* Only check matches that will potentially beat the current maxlen.
* This is redundant with the index if match_maxlen is 0, but the
* added branch overhead to check if it == 0 seems to be worse. */
if (pospoint[match_maxlen] != needlepoint[match_maxlen]) {
pos = hsi->index[pos];
continue;
}
for (len = 1; len < maxlen; len++) {
if (pospoint[len] != needlepoint[len]) break;
}
if (len > match_maxlen) {
match_maxlen = len;
match_index = (uint16_t)pos;
if (len == maxlen) { break; } /* won't find better */
}
pos = hsi->index[pos];
}
#else
for (int16_t pos=end - 1; pos - (int16_t)start >= 0; pos--) {
uint8_t * const pospoint = &buf[pos];
if ((pospoint[match_maxlen] == needlepoint[match_maxlen])
&& (*pospoint == *needlepoint)) {
for (len=1; len<maxlen; len++) {
if (0) {
LOG(" --> cmp buf[%d] == 0x%02x against %02x (start %u)\n",
pos + len, pospoint[len], needlepoint[len], start);
}
if (pospoint[len] != needlepoint[len]) { break; }
}
if (len > match_maxlen) {
match_maxlen = len;
match_index = pos;
if (len == maxlen) { break; } /* don't keep searching */
}
}
}
#endif
const size_t break_even_point =
(1 + HEATSHRINK_ENCODER_WINDOW_BITS(hse) +
HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse));
/* Instead of comparing break_even_point against 8*match_maxlen,
* compare match_maxlen against break_even_point/8 to avoid
* overflow. Since MIN_WINDOW_BITS and MIN_LOOKAHEAD_BITS are 4 and
* 3, respectively, break_even_point/8 will always be at least 1. */
if (match_maxlen > (break_even_point / 8)) {
LOG("-- best match: %u bytes at -%u\n",
match_maxlen, end - match_index);
*match_length = match_maxlen;
return end - match_index;
}
LOG("-- none found\n");
return MATCH_NOT_FOUND;
}
static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi) {
uint8_t count = 0;
uint8_t bits = 0;
if (hse->outgoing_bits_count > 8) {
count = 8;
bits = (uint8_t)(hse->outgoing_bits >> (hse->outgoing_bits_count - 8));
} else {
count = hse->outgoing_bits_count;
bits = (uint8_t)hse->outgoing_bits;
}
if (count > 0) {
LOG("-- pushing %d outgoing bits: 0x%02x\n", count, bits);
push_bits(hse, count, bits, oi);
hse->outgoing_bits_count -= count;
}
return count;
}
/* Push COUNT (max 8) bits to the output buffer, which has room.
* Bytes are set from the lowest bits, up. */
static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits,
output_info *oi) {
ASSERT(count <= 8);
LOG("++ push_bits: %d bits, input of 0x%02x\n", count, bits);
/* If adding a whole byte and at the start of a new output byte,
* just push it through whole and skip the bit IO loop. */
if (count == 8 && hse->bit_index == 0x80) {
oi->buf[(*oi->output_size)++] = bits;
} else {
for (int i=count - 1; i>=0; i--) {
bool bit = bits & (1 << i);
if (bit) { hse->current_byte |= hse->bit_index; }
if (0) {
LOG(" -- setting bit %d at bit index 0x%02x, byte => 0x%02x\n",
bit ? 1 : 0, hse->bit_index, hse->current_byte);
}
hse->bit_index >>= 1;
if (hse->bit_index == 0x00) {
hse->bit_index = 0x80;
LOG(" > pushing byte 0x%02x\n", hse->current_byte);
oi->buf[(*oi->output_size)++] = hse->current_byte;
hse->current_byte = 0x00;
}
}
}
}
static void push_literal_byte(heatshrink_encoder *hse, output_info *oi) {
uint16_t processed_offset = hse->match_scan_index - 1;
uint16_t input_offset = get_input_offset(hse) + processed_offset;
uint8_t c = hse->buffer[input_offset];
LOG("-- yielded literal byte 0x%02x ('%c') from +%d\n",
c, isprint(c) ? c : '.', input_offset);
push_bits(hse, 8, c, oi);
}
static void save_backlog(heatshrink_encoder *hse) {
size_t input_buf_sz = get_input_buffer_size(hse);
uint16_t msi = hse->match_scan_index;
/* Copy processed data to beginning of buffer, so it can be
* used for future matches. Don't bother checking whether the
* input is less than the maximum size, because if it isn't,
* we're done anyway. */
uint16_t rem = (uint16_t)input_buf_sz - msi; // unprocessed bytes
uint16_t shift_sz = (uint16_t)input_buf_sz + rem;
memmove(&hse->buffer[0],
&hse->buffer[input_buf_sz - rem],
shift_sz);
hse->match_scan_index = 0;
hse->input_size -= (uint16_t)input_buf_sz - rem;
}

@ -0,0 +1,106 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "heatshrink_common.h"
#include "heatshrink_config.h"
typedef enum {
HSER_SINK_OK, /* data sunk into input buffer */
HSER_SINK_ERROR_NULL=-1, /* NULL argument */
HSER_SINK_ERROR_MISUSE=-2, /* API misuse */
} HSE_sink_res;
typedef enum {
HSER_POLL_EMPTY, /* input exhausted */
HSER_POLL_MORE, /* poll again for more output */
HSER_POLL_ERROR_NULL=-1, /* NULL argument */
HSER_POLL_ERROR_MISUSE=-2, /* API misuse */
} HSE_poll_res;
typedef enum {
HSER_FINISH_DONE, /* encoding is complete */
HSER_FINISH_MORE, /* more output remaining; use poll */
HSER_FINISH_ERROR_NULL=-1, /* NULL argument */
} HSE_finish_res;
#if HEATSHRINK_DYNAMIC_ALLOC
#define HEATSHRINK_ENCODER_WINDOW_BITS(HSE) \
((HSE)->window_sz2)
#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(HSE) \
((HSE)->lookahead_sz2)
#define HEATSHRINK_ENCODER_INDEX(HSE) \
((HSE)->search_index)
struct hs_index {
uint16_t size;
int16_t index[];
};
#else
#define HEATSHRINK_ENCODER_WINDOW_BITS(_) \
(HEATSHRINK_STATIC_WINDOW_BITS)
#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(_) \
(HEATSHRINK_STATIC_LOOKAHEAD_BITS)
#define HEATSHRINK_ENCODER_INDEX(HSE) \
(&(HSE)->search_index)
struct hs_index {
uint16_t size;
int16_t index[2 << HEATSHRINK_STATIC_WINDOW_BITS];
};
#endif
typedef struct {
uint16_t input_size; /* bytes in input buffer */
uint16_t match_scan_index;
uint16_t match_length;
uint16_t match_pos;
uint16_t outgoing_bits; /* enqueued outgoing bits */
uint8_t outgoing_bits_count;
uint8_t flags;
uint8_t state; /* current state machine node */
uint8_t current_byte; /* current byte of output */
uint8_t bit_index; /* current bit index */
#if HEATSHRINK_DYNAMIC_ALLOC
uint8_t window_sz2; /* 2^n size of window */
uint8_t lookahead_sz2; /* 2^n size of lookahead */
#if HEATSHRINK_USE_INDEX
struct hs_index *search_index;
#endif
/* input buffer and / sliding window for expansion */
uint8_t buffer[];
#else
#if HEATSHRINK_USE_INDEX
struct hs_index search_index;
#endif
/* input buffer and / sliding window for expansion */
uint8_t buffer[2 << HEATSHRINK_ENCODER_WINDOW_BITS(_)];
#endif
} heatshrink_encoder;
#if HEATSHRINK_DYNAMIC_ALLOC
/* Allocate a new encoder struct and its buffers.
* Returns NULL on error. */
heatshrink_encoder *heatshrink_encoder_alloc(uint8_t window_sz2,
uint8_t lookahead_sz2);
/* Free an encoder. */
void heatshrink_encoder_free(heatshrink_encoder *hse);
#endif
/* Reset an encoder. */
void heatshrink_encoder_reset(heatshrink_encoder *hse);
/* Sink up to SIZE bytes from IN_BUF into the encoder.
* INPUT_SIZE is set to the number of bytes actually sunk (in case a
* buffer was filled.). */
HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse,
uint8_t *in_buf, size_t size, size_t *input_size);
/* Poll for output from the encoder, copying at most OUT_BUF_SIZE bytes into
* OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */
HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse,
uint8_t *out_buf, size_t out_buf_size, size_t *output_size);
/* Notify the encoder that the input stream is finished.
* If the return value is HSER_FINISH_MORE, there is still more output, so
* call heatshrink_encoder_poll and repeat. */
HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse);

@ -0,0 +1,19 @@
#pragma once
#include "httpd.h"
#ifndef HTTP_AUTH_REALM
#define HTTP_AUTH_REALM "Protected"
#endif
#define HTTPD_AUTH_SINGLE 0
#define HTTPD_AUTH_CALLBACK 1
#define AUTH_MAX_USER_LEN 32
#define AUTH_MAX_PASS_LEN 32
//Parameter given to authWhatever functions. This callback returns the usernames/passwords the device
//has.
typedef int (* AuthGetUserPw)(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen);
httpd_cgi_state authBasic(HttpdConnData *connData);

@ -0,0 +1,33 @@
#pragma once
#include "httpd.h"
#define WEBSOCK_FLAG_NONE 0
#define WEBSOCK_FLAG_MORE (1<<0) //Set if the data is not the final data in the message; more follows
#define WEBSOCK_FLAG_BIN (1<<1) //Set if the data is binary instead of text
#define WEBSOCK_FLAG_CONT (1<<2) // set if this is a continuation frame (after WEBSOCK_FLAG_MORE)
typedef struct Websock Websock;
typedef struct WebsockPriv WebsockPriv;
typedef void(*WsConnectedCb)(Websock *ws);
typedef void(*WsRecvCb)(Websock *ws, char *data, int len, int flags);
typedef void(*WsSentCb)(Websock *ws);
typedef void(*WsCloseCb)(Websock *ws);
struct Websock {
void *userData;
HttpdConnData *conn;
uint8_t status;
WsRecvCb recvCb;
WsSentCb sentCb;
WsCloseCb closeCb;
WebsockPriv *priv;
};
httpd_cgi_state cgiWebsocket(HttpdConnData *connData);
int cgiWebsocketSend(Websock *ws, const char *data, int len, int flags);
void cgiWebsocketClose(Websock *ws, int reason);
httpd_cgi_state cgiWebSocketRecv(HttpdConnData *connData, char *data, int len);
int cgiWebsockBroadcast(const char *resource, const char *data, int len, int flags);
void cgiWebsockMeasureBacklog(const char *resource, int *total, int *max);

@ -0,0 +1,39 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "httpd-utils.h"
// opaque conn type struct
struct HttpdConnType;
typedef struct HttpdConnType HttpdConnType;
typedef HttpdConnType* ConnTypePtr;
struct httpd_thread_handle;
typedef struct httpd_thread_handle httpd_thread_handle_t;
struct httpd_options;
#define httpd_printf(fmt, ...) printf(fmt, ##__VA_ARGS__)
// Prototypes for porting
int httpdConnSendData(ConnTypePtr conn, char *buff, int len);
void httpdConnDisconnect(ConnTypePtr conn);
void httpdPlatDisableTimeout(ConnTypePtr conn);
void httpdPlatInit();
httpd_thread_handle_t* httpdPlatStart(struct httpd_options *opts);
void httpdPlatJoin(httpd_thread_handle_t * handle);
void httpdPlatLock();
void httpdPlatUnlock();
void* httpdPlatMalloc(size_t len);
void httpdPlatFree(void *ptr);
char* httpdPlatStrdup(const char *s);
void httpdPlatDelayMs(uint32_t ms);
void httpdPlatTaskEnd();
int httpdPlatEspfsRead(void *dest, uint32_t offset, size_t len);
void platHttpServerTask(void *pvParameters);
void* platHttpServerTaskPosix(void *pvParameters);

@ -0,0 +1,10 @@
#pragma once
#include <string.h>
// Custom helpers
#define streq(a, b) (strcmp((const char*)(a), (const char*)(b)) == 0)
#define strneq(a, b, n) (strncmp((const char*)(a), (const char*)(b), (n)) == 0)
#define strstarts(a, b) strneq((a), (b), (int)strlen((b)))
#define last_char_n(str, n) ((str))[strlen((str)) - (n)]
#define last_char(str) last_char_n((str), 1)

@ -0,0 +1,204 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "httpd-platform.h"
#ifndef GIT_HASH
#define GIT_HASH "unknown"
#endif
// we must not use this macro outside the library, as the git hash is not defined there
#define HTTPDVER "0.4+MightyPork/libesphttpd#" GIT_HASH
// default servername
#ifndef HTTPD_SERVERNAME
#define HTTPD_SERVERNAME "esp8266-httpd " HTTPDVER
#endif
//Max length of request head. This is statically allocated for each connection.
#ifndef HTTPD_MAX_HEAD_LEN
#define HTTPD_MAX_HEAD_LEN 1024
#endif
//Max post buffer len. This is dynamically malloc'ed if needed.
#ifndef HTTPD_MAX_POST_LEN
#define HTTPD_MAX_POST_LEN 2048
#endif
//Max send buffer len. This is allocated on the stack.
#ifndef HTTPD_MAX_SENDBUFF_LEN
#define HTTPD_MAX_SENDBUFF_LEN 2048
#endif
//If some data can't be sent because the underlaying socket doesn't accept the data (like the nonos
//layer is prone to do), we put it in a backlog that is dynamically malloc'ed. This defines the max
//size of the backlog.
#ifndef HTTPD_MAX_BACKLOG_SIZE
#define HTTPD_MAX_BACKLOG_SIZE (4*1024)
#endif
//Max len of CORS token. This is allocated in each connection
#ifndef HTTPD_MAX_CORS_TOKEN_LEN
#define HTTPD_MAX_CORS_TOKEN_LEN 256
#endif
#ifndef HTTPD_MAX_CONNECTIONS
#define HTTPD_MAX_CONNECTIONS 4
#endif
/**
* CGI handler state / return value
*/
typedef enum {
HTTPD_CGI_MORE = 0,
HTTPD_CGI_DONE = 1,
HTTPD_CGI_NOTFOUND = 2,
HTTPD_CGI_AUTHENTICATED = 3,
} httpd_cgi_state;
/**
* HTTP method (verb) used for the request
*/
typedef enum {
HTTPD_METHOD_GET = 1,
HTTPD_METHOD_POST = 2,
HTTPD_METHOD_OPTIONS = 3,
HTTPD_METHOD_PUT = 4,
HTTPD_METHOD_DELETE = 5,
HTTPD_METHOD_PATCH = 6,
HTTPD_METHOD_HEAD = 7,
} httpd_method;
/**
* Transfer mode
*/
typedef enum {
HTTPD_TRANSFER_CLOSE = 0,
HTTPD_TRANSFER_CHUNKED = 1,
HTTPD_TRANSFER_NONE = 2,
} httpd_transfer_opt;
typedef struct HttpdPriv HttpdPriv;
typedef struct HttpdConnData HttpdConnData;
typedef struct HttpdPostData HttpdPostData;
// Private static connection pool
extern HttpdConnData *s_connData[HTTPD_MAX_CONNECTIONS];
typedef httpd_cgi_state (* cgiSendCallback)(HttpdConnData *connData);
typedef httpd_cgi_state (* cgiRecvHandler)(HttpdConnData *connData, char *data, int len);
struct httpd_options {
uint16_t port;
};
//A struct describing a http connection. This gets passed to cgi functions.
struct HttpdConnData {
ConnTypePtr conn; // The TCP connection. Exact type depends on the platform.
httpd_method requestType; // One of the HTTPD_METHOD_* values
char *url; // The URL requested, without hostname or GET arguments
char *getArgs; // The GET arguments for this request, if any.
const void *cgiArg; // Argument to the CGI function, as stated as the 3rd argument of
// the builtInUrls entry that referred to the CGI function.
const void *cgiArg2; // 4th argument of the builtInUrls entries, used to pass template file to the tpl handler.
void *cgiData; // Opaque data pointer for the CGI function
char *hostName; // Host name field of request
HttpdPriv *priv; // Opaque pointer to data for internal httpd housekeeping
cgiSendCallback cgi; // CGI function pointer
cgiRecvHandler recvHdl; // Handler for data received after headers, if any
HttpdPostData *post; // POST data structure
int remote_port; // Remote TCP port
uint8_t remote_ip[4]; // IP address of client
uint8_t slot; // Slot ID
};
//A struct describing the POST data sent inside the http connection. This is used by the CGI functions
struct HttpdPostData {
int len; // POST Content-Length
int buffSize; // The maximum length of the post buffer
int buffLen; // The amount of bytes in the current post buffer
int received; // The total amount of bytes received so far
char *buff; // Actual POST data buffer
char *multipartBoundary; //Text of the multipart boundary, if any
};
//A struct describing an url. This is the main struct that's used to send different URL requests to
//different routines.
typedef struct {
const char *url;
cgiSendCallback cgiCb;
const void *cgiArg;
const void *cgiArg2;
} HttpdBuiltInUrl;
// macros for defining HttpdBuiltInUrl's
/** Route with a CGI handler and two arguments */
#define ROUTE_CGI_ARG2(path, handler, arg1, arg2) {(path), (handler), (void *)(arg1), (void *)(arg2)}
/** Route with a CGI handler and one arguments */
#define ROUTE_CGI_ARG(path, handler, arg1) ROUTE_CGI_ARG2((path), (handler), (arg1), NULL)
/** Route with an argument-less CGI handler */
#define ROUTE_CGI(path, handler) ROUTE_CGI_ARG2((path), (handler), NULL, NULL)
/** Static file route (file loaded from espfs) */
#define ROUTE_FILE(path, filepath) ROUTE_CGI_ARG((path), cgiEspFsHook, (const char*)(filepath))
/** Static file as a template with a replacer function */
#define ROUTE_TPL(path, replacer) ROUTE_CGI_ARG((path), cgiEspFsTemplate, (TplCallback)(replacer))
/** Static file as a template with a replacer function, taking additional argument connData->cgiArg2 */
#define ROUTE_TPL_FILE(path, replacer, filepath) ROUTE_CGI_ARG2((path), cgiEspFsTemplate, (TplCallback)(replacer), (filepath))
/** Redirect to some URL */
#define ROUTE_REDIRECT(path, target) ROUTE_CGI_ARG((path), cgiRedirect, (const char*)(target))
/** Following routes are basic-auth protected */
#define ROUTE_AUTH(path, passwdFunc) ROUTE_CGI_ARG((path), authBasic, (AuthGetUserPw)(passwdFunc))
/** Websocket endpoint */
#define ROUTE_WS(path, callback) ROUTE_CGI_ARG((path), cgiWebsocket, (WsConnectedCb)(callback))
/** Catch-all filesystem route */
#define ROUTE_FILESYSTEM() ROUTE_CGI("*", cgiEspFsHook)
#define ROUTE_END() {NULL, NULL, NULL, NULL}
const char *httpdGetVersion(void);
httpd_cgi_state cgiRedirect(HttpdConnData *connData);
httpd_cgi_state cgiRedirectToHostname(HttpdConnData *connData);
httpd_cgi_state cgiRedirectApClientToHostname(HttpdConnData *connData);
void httpdRedirect(HttpdConnData *conn, const char *newUrl);
int httpdUrlDecode(const char *val, int valLen, char *ret, int retLen);
int httpdFindArg(const char *line, const char *arg, char *buff, int buffLen);
httpd_thread_handle_t *httpdInit(const HttpdBuiltInUrl *fixedUrls, struct httpd_options *options);
void httpdJoin(httpd_thread_handle_t *handle);
const char *httpdGetMimetype(const char *url);
const char *httpdMethodName(httpd_method m);
void httdSetTransferMode(HttpdConnData *conn, int mode);
void httpdStartResponse(HttpdConnData *conn, int code);
void httpdHeader(HttpdConnData *conn, const char *field, const char *val);
void httpdEndHeaders(HttpdConnData *conn);
int httpdGetHeader(HttpdConnData *conn, const char *header, char *ret, int retLen);
int httpdSend(HttpdConnData *conn, const char *data, int len);
int httpdSend_js(HttpdConnData *conn, const char *data, int len);
int httpdSend_html(HttpdConnData *conn, const char *data, int len);
bool httpdFlushSendBuffer(HttpdConnData *conn);
void httpdContinue(HttpdConnData *conn);
void httpdConnSendStart(HttpdConnData *conn);
void httpdConnSendFinish(HttpdConnData *conn);
void httpdAddCacheHeaders(HttpdConnData *connData, const char *mime);
int httpGetBacklogSize(const HttpdConnData *connData);
void httdResponseOptions(HttpdConnData *conn, int cors);
//Platform dependent code should call these.
void httpdSentCb(ConnTypePtr conn, const char *remIp, int remPort);
void httpdRecvCb(ConnTypePtr conn, const char *remIp, int remPort, char *data, unsigned short len);
void httpdDisconCb(ConnTypePtr conn, const char *remIp, int remPort);
int httpdConnectCb(ConnTypePtr conn, const char *remIp, int remPort);
void httpdSetName(const char *name);

@ -0,0 +1,13 @@
#pragma once
#include "httpd.h"
/**
* The template substitution callback.
* Returns CGI_MORE if more should be sent within the token, CGI_DONE otherwise.
*/
typedef httpd_cgi_state (* TplCallback)(HttpdConnData *connData, char *token, void **arg);
httpd_cgi_state cgiEspFsHook(HttpdConnData *connData);
httpd_cgi_state cgiEspFsTemplate(HttpdConnData *connData);
int tplSend(HttpdConnData *conn, const char *str, int len);

@ -0,0 +1,193 @@
#pragma once
#include "httpd-platform.h"
#include <stdio.h>
#ifndef VERBOSE_LOGGING
#define VERBOSE_LOGGING 1
#endif
#ifndef LOG_EOL
#define LOG_EOL "\n"
#endif
/**
* Print a startup banner message (printf syntax)
* Uses bright green color
*/
#define banner(fmt, ...) \
do { \
httpd_printf(LOG_EOL "\x1b[32;1m[i] " fmt "\x1b[0m" LOG_EOL, ##__VA_ARGS__); \
} while(0)
/**
* Same as 'info()', but enabled even if verbose logging is disabled.
* This can be used to print version etc under the banner.
*/
#define banner_info(fmt, ...) \
do { \
httpd_printf("\x1b[32m[i] " fmt "\x1b[0m"LOG_EOL, ##__VA_ARGS__); \
} while(0)
/**
* Empty line in the headers
*/
#define banner_gap() \
do { \
httpd_printf(LOG_EOL); \
} while(0)
#if VERBOSE_LOGGING
/**
* Print a debug log message (printf format)
*/
#define dbg(fmt, ...) \
do { \
httpd_printf("[ ] " fmt LOG_EOL, ##__VA_ARGS__); \
} while(0)
/**
* Print a info log message (printf format)
* Uses bright green color
*/
#define info(fmt, ...) \
do { \
httpd_printf("[i] " fmt "\x1b[0m"LOG_EOL, ##__VA_ARGS__); \
} while(0)
#else
#define dbg(fmt, ...)
#define info(fmt, ...)
#endif
/**
* Print a error log message (printf format)
* Uses bright red color
*/
#define error(fmt, ...) \
do { \
httpd_printf("[E] " fmt "\x1b[0m"LOG_EOL, ##__VA_ARGS__); \
} while(0)
/**
* Print a warning log message (printf format)
* Uses bright yellow color
*/
#define warn(fmt, ...) \
do { \
httpd_printf("[W] " fmt "\x1b[0m"LOG_EOL, ##__VA_ARGS__); \
} while(0)
// --------------- logging categories --------------------
#ifndef DEBUG_ROUTER
#define DEBUG_ROUTER 1
#endif
#ifndef DEBUG_ESPFS
#define DEBUG_ESPFS 1
#endif
#ifndef DEBUG_WS
#define DEBUG_WS 1
#endif
#ifndef DEBUG_HTTP
#define DEBUG_HTTP 1
#endif
#ifndef DEBUG_HTTPC
#define DEBUG_HTTPC 1
#endif
#ifndef DEBUG_HEATSHRINK
#define DEBUG_HEATSHRINK 1
#endif
#ifndef DEBUG_MALLOC
#define DEBUG_MALLOC 0
#endif
// router (resolving urls to serve)
#if DEBUG_ROUTER
#define router_warn(...) warn(__VA_ARGS__)
#define router_dbg(...) dbg(__VA_ARGS__)
#define router_error(...) error(__VA_ARGS__)
#define router_info(...) info(__VA_ARGS__)
#else
#define router_dbg(...)
#define router_warn(...)
#define router_error(...)
#define router_info(...)
#endif
// filesystem
#if DEBUG_ESPFS
#define espfs_warn(...) warn(__VA_ARGS__)
#define espfs_dbg(...) dbg(__VA_ARGS__)
#define espfs_error(...) error(__VA_ARGS__)
#define espfs_info(...) info(__VA_ARGS__)
#else
#define espfs_dbg(...)
#define espfs_warn(...)
#define espfs_error(...)
#define espfs_info(...)
#endif
// websocket
#if DEBUG_WS
#define ws_warn(...) warn(__VA_ARGS__)
#define ws_dbg(...) dbg(__VA_ARGS__)
#define ws_error(...) error(__VA_ARGS__)
#define ws_info(...) info(__VA_ARGS__)
#else
#define ws_dbg(...)
#define ws_warn(...)
#define ws_error(...)
#define ws_info(...)
#endif
// server
#if DEBUG_HTTP
#define http_warn(...) warn(__VA_ARGS__)
#define http_dbg(...) dbg(__VA_ARGS__)
#define http_error(...) error(__VA_ARGS__)
#define http_info(...) info(__VA_ARGS__)
#else
#define http_dbg(...)
#define http_warn(...)
#define http_error(...)
#define http_info(...)
#endif
// client
#if DEBUG_HTTPC
#define httpc_warn(...) warn(__VA_ARGS__)
#define httpc_dbg(...) dbg(__VA_ARGS__)
#define httpc_error(...) error(__VA_ARGS__)
#define httpc_info(...) info(__VA_ARGS__)
#else
#define httpc_dbg(...)
#define httpc_warn(...)
#define httpc_error(...)
#define httpc_info(...)
#endif
// captive portal
#if DEBUG_HEATSHRINK
#define heatshrink_warn(...) warn(__VA_ARGS__)
#define heatshrink_dbg(...) dbg(__VA_ARGS__)
#define heatshrink_error(...) error(__VA_ARGS__)
#define heatshrink_info(...) info(__VA_ARGS__)
#else
#define heatshrink_dbg(...)
#define heatshrink_warn(...)
#define heatshrink_error(...)
#define heatshrink_info(...)
#endif
// all malloc usage
#if DEBUG_MALLOC
#define mem_dbg(...) dbg(__VA_ARGS__)
#else
#define mem_dbg(...)
#endif

@ -0,0 +1,415 @@
/*
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 <string.h>
#include "httpd.h"
#include "httpd-platform.h"
#include "utils/sha1.h"
#include "utils/base64.h"
#include "cgiwebsocket.h"
#include "logging.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_t frameCont;
uint8_t closedHere;
int wsStatus;
Websock *next; //in linked list
};
static Websock *llStart = NULL;
static int sendFrameHead(Websock *ws, int opcode, int len)
{
uint8_t 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;
}
// ws_dbg("WS: Sent frame head for payload of %d bytes.", len);
return httpdSend(ws->conn, (char *) buf, i);
}
int cgiWebsocketSend(Websock *ws, const char *data, int len, int flags)
{
int r = 0;
int fl = 0;
// Continuation frame has opcode 0
if (!(flags & WEBSOCK_FLAG_CONT)) {
if (flags & WEBSOCK_FLAG_BIN) {
fl = OPCODE_BINARY;
} else {
fl = OPCODE_TEXT;
}
}
// add FIN to last frame
if (!(flags & WEBSOCK_FLAG_MORE)) { fl |= FLAG_FIN; }
sendFrameHead(ws, fl, len);
if (len != 0) { r = httpdSend(ws->conn, data, len); }
r &= httpdFlushSendBuffer(ws->conn);
return r;
}
//Broadcast data to all websockets at a specific url. Returns the amount of connections sent to.
int cgiWebsockBroadcast(const char *resource, const char *data, int len, int flags)
{
Websock *lw = llStart;
int ret = 0;
while (lw != NULL) {
if (strcmp(lw->conn->url, resource) == 0) {
httpdConnSendStart(lw->conn);
if (!cgiWebsocketSend(lw, data, len, flags)) {
// send failed, do not try further (assume memory is clogged due to max backlog size)
// HACK: If conn->conn is not NULL, {httpdConnSendFinish} would try to flush again
// (cgiWebsocketSend already tried to flush, and that's where the error came from, possibly)
// we remove it temporarily to bypass that
ConnTypePtr oldpriv = lw->conn->conn;
lw->conn->conn = NULL;
httpdConnSendFinish(lw->conn);
// -> and now we put it back for later
lw->conn->conn = oldpriv;
// Abort
return -1;
}
httpdConnSendFinish(lw->conn);
ret++;
}
lw = lw->priv->next;
}
return ret;
}
/** this is used for estimation how full the ram is */
void cgiWebsockMeasureBacklog(const char *resource, int *total, int *max)
{
Websock *lw = llStart;
int bMax = 0;
int bTotal = 0;
while (lw != NULL) {
if (strcmp(lw->conn->url, resource) == 0) {
//lw->conn
int bs = httpGetBacklogSize(lw->conn);
bTotal += bs;
if (bs > bMax) { bMax = bs; }
}
lw = lw->priv->next;
}
*total = bTotal;
*max = bMax;
}
void 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 websockFree(Websock *ws)
{
ws_dbg("Ws: Free");
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) { httpdPlatFree(ws->priv); }
}
httpd_cgi_state cgiWebSocketRecv(HttpdConnData *connData, char *data, int len)
{
int i, j, sl;
httpd_cgi_state 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;
// ws_dbg("Ws: Frame payload. wasHeaderByte %d fr.len %d sl %d cmd 0x%x", 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]); }
// if (DEBUG_WS) {
// ws_dbg("Unmasked: ");
// for (j = 0; j < sl; j++) httpd_printf("%02X ", data[i + j] & 0xff);
// ws_dbg("\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_MORE; }
if (ws->recvCb) { ws->recvCb(ws, data + i, sl, flags); }
}
} else if ((ws->priv->fr.flags & OPCODE_MASK) == OPCODE_CLOSE) {
// ws_dbg("WS: Got close frame");
if (!ws->priv->closedHere) {
// ws_dbg("WS: Sending response close frame, %x %x (i=%d, len=%d)", data[i], data[i+1], i, len);
int cause = 1000;
if (i <= len - 2) {
cause = ((data[i] << 8) & 0xff00) + (data[i + 1] & 0xff);
}
cgiWebsocketClose(ws, cause);
}
r = HTTPD_CGI_DONE;
break;
} else {
if (!ws->priv->frameCont) ws_error("WS: Unknown opcode 0x%X", 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);
httpdPlatFree(connData->cgiData);
connData->cgiData = NULL;
}
return r;
}
//Websocket 'cgi' implementation
httpd_cgi_state cgiWebsocket(HttpdConnData *connData)
{
char buff[256];
int i;
sha1nfo s;
if (connData->conn == NULL) {
//Connection aborted. Clean up.
ws_dbg("WS: Cleanup");
if (connData->cgiData) {
Websock *ws = (Websock *) connData->cgiData;
websockFree(ws);
httpdPlatFree(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);
ws_dbg("WS: Upgrade: %s", 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 = httpdPlatMalloc(sizeof(Websock));
if (connData->cgiData == NULL) {
ws_error("Can't allocate mem for websocket");
return HTTPD_CGI_DONE;
}
memset(connData->cgiData, 0, sizeof(Websock));
Websock *ws = (Websock *) connData->cgiData;
ws->priv = httpdPlatMalloc(sizeof(WebsockPriv));
if (ws->priv == NULL) {
ws_error("Can't allocate mem for websocket priv");
httpdPlatFree(connData->cgiData);
connData->cgiData = NULL;
return HTTPD_CGI_DONE;
}
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));
httdSetTransferMode(connData, HTTPD_TRANSFER_NONE);
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;
}

@ -0,0 +1,235 @@
/*
ESP8266 web server - platform-dependent routines, FreeRTOS version
Thanks to my collague at Espressif for writing the foundations of this code.
*/
#include "httpd.h"
#include "platform.h"
#include "httpd-platform.h"
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include "logging.h"
static int httpPort;
static int httpMaxConnCt;
struct HttpdConnType {
int fd;
int needWriteDoneNotif;
int needsClose;
int port;
char ip[4];
};
static HttpdConnType s_rconn[HTTPD_MAX_CONNECTIONS];
int httpdConnSendData(ConnTypePtr conn, char *buff, int len)
{
conn->needWriteDoneNotif = 1;
return (write(conn->fd, buff, len) >= 0);
}
void httpdConnDisconnect(ConnTypePtr conn)
{
conn->needsClose = 1;
conn->needWriteDoneNotif = 1; //because the real close is done in the writable select code
}
#define RECV_BUF_SIZE 2048
void platHttpServerTask(void *pvParameters)
{
int32_t listenfd;
int32_t remotefd;
int32_t len;
int32_t ret;
int x;
int maxfdp = 0;
char *precvbuf;
fd_set readset, writeset;
struct sockaddr name;
//struct timeval timeout;
struct sockaddr_in server_addr;
struct sockaddr_in remote_addr;
struct httpd_options *options = pvParameters;
if (options == NULL) {
httpPort = 80;
} else {
httpPort = options->port;
}
for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) {
s_rconn[x].fd = -1;
}
/* Construct local address structure */
memset(&server_addr, 0, sizeof(server_addr)); /* Zero out structure */
server_addr.sin_family = AF_INET; /* Internet address family */
server_addr.sin_addr.s_addr = INADDR_ANY; /* Any incoming interface */
//server_addr.sin_len = sizeof(server_addr);
server_addr.sin_port = htons(httpPort); /* Local port */
/* Create socket for incoming connections */
do {
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) {
error("platHttpServerTask: failed to create sock!");
httpdPlatDelayMs(1000);
}
} while (listenfd == -1);
/* Bind to the local port */
do {
ret = bind(listenfd, (struct sockaddr *) &server_addr, sizeof(server_addr));
if (ret != 0) {
error("platHttpServerTask: failed to bind!");
httpdPlatDelayMs(1000);
}
} while (ret != 0);
do {
/* Listen to the local connection */
ret = listen(listenfd, HTTPD_MAX_CONNECTIONS);
if (ret != 0) {
error("platHttpServerTask: failed to listen!");
httpdPlatDelayMs(1000);
}
} while (ret != 0);
info("esphttpd: active and listening to connections.");
while (1) {
// clear fdset, and set the select function wait time
int socketsFull = 1;
maxfdp = 0;
FD_ZERO(&readset);
FD_ZERO(&writeset);
//timeout.tv_sec = 2;
//timeout.tv_usec = 0;
for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) {
if (s_rconn[x].fd != -1) {
FD_SET(s_rconn[x].fd, &readset);
if (s_rconn[x].needWriteDoneNotif) FD_SET(s_rconn[x].fd, &writeset);
if (s_rconn[x].fd > maxfdp) { maxfdp = s_rconn[x].fd; }
} else {
socketsFull = 0;
}
}
if (!socketsFull) {
FD_SET(listenfd, &readset);
if (listenfd > maxfdp) { maxfdp = listenfd; }
}
//polling all exist client handle,wait until readable/writable
ret = select(maxfdp + 1, &readset, &writeset, NULL, NULL);//&timeout
if (ret > 0) {
//See if we need to accept a new connection
if (FD_ISSET(listenfd, &readset)) {
len = sizeof(struct sockaddr_in);
remotefd = accept(listenfd, (struct sockaddr *) &remote_addr, (socklen_t *) &len);
if (remotefd < 0) {
warn("platHttpServerTask: Huh? Accept failed.");
continue;
}
for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) { if (s_rconn[x].fd == -1) { break; }}
if (x == HTTPD_MAX_CONNECTIONS) {
warn("platHttpServerTask: Huh? Got accept with all slots full.");
continue;
}
int keepAlive = 1; //enable keepalive
int keepIdle = 60; //60s
int keepInterval = 5; //5s
int keepCount = 3; //retry times
setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *) &keepAlive, sizeof(keepAlive));
setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPIDLE, (void *) &keepIdle, sizeof(keepIdle));
setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPINTVL, (void *) &keepInterval, sizeof(keepInterval));
setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPCNT, (void *) &keepCount, sizeof(keepCount));
s_rconn[x].fd = remotefd;
s_rconn[x].needWriteDoneNotif = 0;
s_rconn[x].needsClose = 0;
len = sizeof(name);
getpeername(remotefd, &name, (socklen_t *) &len);
struct sockaddr_in *piname = (struct sockaddr_in *) &name;
s_rconn[x].port = piname->sin_port;
memcpy(&s_rconn[x].ip, &piname->sin_addr.s_addr, sizeof(s_rconn[x].ip));
httpdConnectCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port);
//os_timer_disarm(&connData[x].conn->stop_watch);
//os_timer_setfn(&connData[x].conn->stop_watch, (os_timer_func_t *)httpserver_conn_watcher, connData[x].conn);
//os_timer_arm(&connData[x].conn->stop_watch, STOP_TIMER, 0);
// dbg("httpserver acpt index %d sockfd %d!", x, remotefd);
}
//See if anything happened on the existing connections.
for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) {
//Skip empty slots
if (s_rconn[x].fd == -1) { continue; }
//Check for write availability first: the read routines may write needWriteDoneNotif while
//the select didn't check for that.
if (s_rconn[x].needWriteDoneNotif && FD_ISSET(s_rconn[x].fd, &writeset)) {
s_rconn[x].needWriteDoneNotif = 0; //Do this first, httpdSentCb may write something making this 1 again.
if (s_rconn[x].needsClose) {
//Do callback and close fd.
httpdDisconCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port);
close(s_rconn[x].fd);
s_rconn[x].fd = -1;
} else {
httpdSentCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port);
}
}
if (FD_ISSET(s_rconn[x].fd, &readset)) {
precvbuf = (char *) malloc(RECV_BUF_SIZE);
if (precvbuf == NULL) {
error("platHttpServerTask: memory exhausted!");
httpdDisconCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port);
close(s_rconn[x].fd);
s_rconn[x].fd = -1;
}
ret = (int) recv(s_rconn[x].fd, precvbuf, RECV_BUF_SIZE, 0);
if (ret > 0) {
//Data received. Pass to httpd.
httpdRecvCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port, precvbuf, ret);
} else {
//recv error,connection close
httpdDisconCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port);
close(s_rconn[x].fd);
s_rconn[x].fd = -1;
}
if (precvbuf) { free(precvbuf); }
}
}
}
}
//Deinit code, not used here.
/*release data connection*/
for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) {
//find all valid handle
if (s_connData[x]->conn == NULL) { continue; }
if (s_connData[x]->conn->fd >= 0) {
//os_timer_disarm((os_timer_t *)&connData[x].conn->stop_watch); // ???
close(s_connData[x]->conn->fd);
s_connData[x]->conn->fd = -1;
s_connData[x]->conn = NULL;
if (s_connData[x]->cgi != NULL) { s_connData[x]->cgi(s_connData[x]); } //flush cgi data
}
}
/*release listen socket*/
close(listenfd);
httpdPlatTaskEnd();
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,402 @@
/*
Connector to let httpd use the espfs filesystem to serve the files in it.
*/
/*
* ----------------------------------------------------------------------------
* "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 <stddef.h>
#include <string.h>
#include "httpd.h"
#include "httpd-platform.h"
#include "httpdespfs.h"
#include "espfs.h"
#include "espfsformat.h"
#include "logging.h"
#define FILE_CHUNK_LEN 1024
// The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression.
// If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.)
static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\n"
"Connection: close\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 52\r\n"
"\r\n"
"Your browser does not accept gzip-compressed data.\r\n";
/**
* Try to open a file
* @param path - path to the file, may end with slash
* @param indexname - filename at the path
* @return file pointer or NULL
*/
static EspFsFile *tryOpenIndex_do(const char *path, const char *indexname)
{
char fname[100];
size_t url_len = strlen(path);
strncpy(fname, path, 99);
// Append slash if missing
if (path[url_len - 1] != '/') {
fname[url_len++] = '/';
}
strcpy(fname + url_len, indexname);
// Try to open, returns NULL if failed
return espFsOpen(fname);
}
/**
* Try to find index file on a path
* @param path - directory
* @return file pointer or NULL
*/
EspFsFile *tryOpenIndex(const char *path)
{
EspFsFile *file;
// A dot in the filename probably means extension
// no point in trying to look for index.
if (strchr(path, '.') != NULL) { return NULL; }
file = tryOpenIndex_do(path, "index.html");
if (file != NULL) { return file; }
file = tryOpenIndex_do(path, "index.htm");
if (file != NULL) { return file; }
file = tryOpenIndex_do(path, "index.tpl.html");
if (file != NULL) { return file; }
file = tryOpenIndex_do(path, "index.tpl");
if (file != NULL) { return file; }
return NULL; // failed to guess the right name
}
httpd_cgi_state
serveStaticFile(HttpdConnData *connData, const char *filepath)
{
EspFsFile *file = connData->cgiData;
int len;
char buff[FILE_CHUNK_LEN + 1];
char acceptEncodingBuffer[64 + 1];
int isGzip;
if (connData->conn == NULL) {
//Connection aborted. Clean up.
espFsClose(file);
return HTTPD_CGI_DONE;
}
// invalid call.
if (filepath == NULL) {
espfs_error("serveStaticFile called with NULL path!");
return HTTPD_CGI_NOTFOUND;
}
//First call to this cgi.
if (file == NULL) {
//First call to this cgi. Open the file so we can read it.
file = espFsOpen(filepath);
if (file == NULL) {
// file not found
// If this is a folder, look for index file
file = tryOpenIndex(filepath);
if (file == NULL) { return HTTPD_CGI_NOTFOUND; }
}
// The gzip checking code is intentionally without #ifdefs because checking
// for FLAG_GZIP (which indicates gzip compressed file) is very easy, doesn't
// mean additional overhead and is actually safer to be on at all times.
// If there are no gzipped files in the image, the code bellow will not cause any harm.
// Check if requested file was GZIP compressed
isGzip = espFsFlags(file) & FLAG_GZIP;
if (isGzip) {
// Check the browser's "Accept-Encoding" header. If the client does not
// advertise that he accepts GZIP send a warning message (telnet users for e.g.)
httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, 64);
if (strstr(acceptEncodingBuffer, "gzip") == NULL) {
//No Accept-Encoding: gzip header present
httpdSend(connData, gzipNonSupportedMessage, -1);
espFsClose(file);
return HTTPD_CGI_DONE;
}
}
connData->cgiData = file;
httpdStartResponse(connData, 200);
const char *mime = httpdGetMimetype(filepath);
httpdHeader(connData, "Content-Type", mime);
if (isGzip) {
httpdHeader(connData, "Content-Encoding", "gzip");
}
httpdAddCacheHeaders(connData, mime);
httpdEndHeaders(connData);
return HTTPD_CGI_MORE;
}
len = espFsRead(file, buff, FILE_CHUNK_LEN);
if (len > 0) { httpdSend(connData, buff, len); }
if (len != FILE_CHUNK_LEN) {
//We're done.
espFsClose(file);
return HTTPD_CGI_DONE;
} else {
//Ok, till next time.
return HTTPD_CGI_MORE;
}
}
//This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding
//path in the filesystem and if it exists, passes the file through. This simulates what a normal
//webserver would do with static files.
httpd_cgi_state cgiEspFsHook(HttpdConnData *connData)
{
const char *filepath = (connData->cgiArg == NULL) ? connData->url : (char *) connData->cgiArg;
return serveStaticFile(connData, filepath);
}
//cgiEspFsTemplate can be used as a template.
typedef enum {
ENCODE_PLAIN = 0,
ENCODE_HTML,
ENCODE_JS,
} TplEncode;
typedef struct {
EspFsFile *file;
void *tplArg;
char token[64];
int tokenPos;
char buff[FILE_CHUNK_LEN + 1];
bool chunk_resume;
int buff_len;
int buff_x;
int buff_sp;
char *buff_e;
TplEncode tokEncode;
} TplData;
int
tplSend(HttpdConnData *conn, const char *str, int len)
{
if (conn == NULL) { return 0; }
TplData *tpd = conn->cgiData;
if (tpd == NULL || tpd->tokEncode == ENCODE_PLAIN) { return httpdSend(conn, str, len); }
if (tpd->tokEncode == ENCODE_HTML) { return httpdSend_html(conn, str, len); }
if (tpd->tokEncode == ENCODE_JS) { return httpdSend_js(conn, str, len); }
return 0;
}
httpd_cgi_state cgiEspFsTemplate(HttpdConnData *connData)
{
TplData *tpd = connData->cgiData;
int len;
int x, sp = 0;
char *e = NULL;
int tokOfs;
if (connData->conn == NULL) {
//Connection aborted. Clean up.
((TplCallback) (connData->cgiArg))(connData, NULL, &tpd->tplArg);
espFsClose(tpd->file);
httpdPlatFree(tpd);
return HTTPD_CGI_DONE;
}
if (tpd == NULL) {
//First call to this cgi. Open the file so we can read it.
tpd = (TplData *) httpdPlatMalloc(sizeof(TplData));
if (tpd == NULL) {
espfs_error("Failed to malloc tpl struct");
return HTTPD_CGI_NOTFOUND;
}
tpd->chunk_resume = false;
const char *filepath = connData->url;
// check for custom template URL
if (connData->cgiArg2 != NULL) {
filepath = connData->cgiArg2;
espfs_dbg("Using filepath %s", filepath);
}
tpd->file = espFsOpen(filepath);
if (tpd->file == NULL) {
// maybe a folder, look for index file
tpd->file = tryOpenIndex(filepath);
if (tpd->file == NULL) {
httpdPlatFree(tpd);
return HTTPD_CGI_NOTFOUND;
}
}
tpd->tplArg = NULL;
tpd->tokenPos = -1;
if (espFsFlags(tpd->file) & FLAG_GZIP) {
espfs_error("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!", connData->url);
espFsClose(tpd->file);
httpdPlatFree(tpd);
return HTTPD_CGI_NOTFOUND;
}
connData->cgiData = tpd;
httpdStartResponse(connData, 200);
const char *mime = httpdGetMimetype(connData->url);
httpdHeader(connData, "Content-Type", mime);
httpdAddCacheHeaders(connData, mime);
httpdEndHeaders(connData);
return HTTPD_CGI_MORE;
}
char *buff = tpd->buff;
// resume the parser state from the last token,
// if subst. func wants more data to be sent.
if (tpd->chunk_resume) {
//espfs_dbg("Resuming tpl parser for multi-part subst");
len = tpd->buff_len;
e = tpd->buff_e;
sp = tpd->buff_sp;
x = tpd->buff_x;
} else {
len = espFsRead(tpd->file, buff, FILE_CHUNK_LEN);
tpd->buff_len = len;
e = buff;
sp = 0;
x = 0;
}
if (len > 0) {
for (; x < len; x++) {
if (tpd->tokenPos == -1) {
//Inside ordinary text.
if (buff[x] == '%') {
//Send raw data up to now
if (sp != 0) { httpdSend(connData, e, sp); }
sp = 0;
//Go collect token chars.
tpd->tokenPos = 0;
} else {
sp++;
}
} else {
if (buff[x] == '%') {
if (tpd->tokenPos == 0) {
//This is the second % of a %% escape string.
//Send a single % and resume with the normal program flow.
httpdSend(connData, "%", 1);
} else {
if (!tpd->chunk_resume) {
//This is an actual token.
tpd->token[tpd->tokenPos++] = 0; //zero-terminate token
tokOfs = 0;
tpd->tokEncode = ENCODE_PLAIN;
if (strneq(tpd->token, "html:", 5)) {
tokOfs = 5;
tpd->tokEncode = ENCODE_HTML;
} else if (strneq(tpd->token, "h:", 2)) {
tokOfs = 2;
tpd->tokEncode = ENCODE_HTML;
} else if (strneq(tpd->token, "js:", 3)) {
tokOfs = 3;
tpd->tokEncode = ENCODE_JS;
} else if (strneq(tpd->token, "j:", 2)) {
tokOfs = 2;
tpd->tokEncode = ENCODE_JS;
}
// do the shifting
if (tokOfs > 0) {
for (int i = tokOfs; i <= tpd->tokenPos; i++) {
tpd->token[i - tokOfs] = tpd->token[i];
}
}
}
tpd->chunk_resume = false;
httpd_cgi_state status = ((TplCallback) (connData->cgiArg))(connData, tpd->token, &tpd->tplArg);
if (status == HTTPD_CGI_MORE) {
// espfs_dbg("Multi-part tpl subst, saving parser state");
// wants to send more in this token's place.....
tpd->chunk_resume = true;
tpd->buff_len = len;
tpd->buff_e = e;
tpd->buff_sp = sp;
tpd->buff_x = x;
break;
}
}
//Go collect normal chars again.
e = &buff[x + 1];
tpd->tokenPos = -1;
} else {
// Add char to the token buf
char c = buff[x];
bool outOfSpace = tpd->tokenPos >= (sizeof(tpd->token) - 1);
if (outOfSpace ||
(!(c >= 'a' && c <= 'z') &&
!(c >= 'A' && c <= 'Z') &&
!(c >= '0' && c <= '9') &&
c != '.' && c != '_' && c != '-' && c != ':'
)) {
// looks like we collected some garbage
httpdSend(connData, "%", 1);
if (tpd->tokenPos > 0) {
httpdSend(connData, tpd->token, tpd->tokenPos);
}
// the bad char
httpdSend(connData, &c, 1);
//Go collect normal chars again.
e = &buff[x + 1];
tpd->tokenPos = -1;
} else {
// collect it
tpd->token[tpd->tokenPos++] = c;
}
}
}
}
}
if (tpd->chunk_resume) {
return HTTPD_CGI_MORE;
}
//Send remaining bit.
if (sp != 0) { httpdSend(connData, e, sp); }
if (len != FILE_CHUNK_LEN) {
//We're done.
((TplCallback) (connData->cgiArg))(connData, NULL, &tpd->tplArg);
espfs_info("Template sent.");
espFsClose(tpd->file);
httpdPlatFree(tpd);
return HTTPD_CGI_DONE;
} else {
//Ok, till next time.
return HTTPD_CGI_MORE;
}
}

@ -0,0 +1,56 @@
#include "httpd.h"
#include "httpd-platform.h"
#include "FreeRTOS/FreeRTOS.h"
#define HTTPD_STACKSIZE 4096 // TODO
static xQueueHandle httpdMux;
//Set/clear global httpd lock.
void httpdPlatLock()
{
xSemaphoreTakeRecursive(httpdMux, portMAX_DELAY);
}
void httpdPlatUnlock()
{
xSemaphoreGiveRecursive(httpdMux);
}
void httpdPlatDelayMs(uint32_t ms)
{
vTaskDelay(1000 / portTICK_RATE_MS);
}
void httpdPlatTaskEnd()
{
vTaskDelete(NULL);
}
void httpdPlatDisableTimeout(ConnTypePtr conn)
{
//Unimplemented
}
//Initialize listening socket, do general initialization
void httpdPlatStart(struct httpd_options *opts)
{
httpMaxConnCt = maxConnCt;
httpdMux = xSemaphoreCreateRecursiveMutex(); // TODO verify arguments
xTaskCreate(platHttpServerTask, (const char *) "httpd", HTTPD_STACKSIZE, opts, 4, NULL);
}
void* httpdPlatMalloc(size_t len)
{
return malloc(len);
}
void httpdPlatFree(void *ptr)
{
free(ptr);
}
char * httpdPlatStrdup(const char *s) {
return strdup(s); // FIXME
}

@ -0,0 +1,87 @@
#include "httpd.h"
#include "httpd-platform.h"
#include <pthread.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#define HTTPD_STACKSIZE 4096 // TODO
static pthread_mutex_t Mutex;
static pthread_mutexattr_t MutexAttr;
//Set/clear global httpd lock.
void httpdPlatLock()
{
pthread_mutex_lock(&Mutex);
}
void httpdPlatUnlock()
{
pthread_mutex_unlock(&Mutex);
}
void httpdPlatDelayMs(uint32_t ms)
{
usleep(ms * 1000);
}
void httpdPlatTaskEnd()
{
// TODO
}
void httpdPlatDisableTimeout(ConnTypePtr conn)
{
//Unimplemented
}
void httpdPlatInit() {
pthread_mutexattr_init(&MutexAttr);
pthread_mutexattr_settype(&MutexAttr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&Mutex, &MutexAttr);
}
struct httpd_thread_handle {
pthread_t handle;
// TODO some way to signal shutdown?
};
void* platHttpServerTaskPosix(void *pvParameters)
{
platHttpServerTask(pvParameters);
return NULL;
}
//Initialize listening socket, do general initialization
httpd_thread_handle_t *httpdPlatStart(struct httpd_options *opts)
{
struct httpd_thread_handle* handle = httpdPlatMalloc(sizeof(struct httpd_thread_handle));
if (!handle) {
return NULL;
}
pthread_create( &handle->handle, NULL, platHttpServerTaskPosix, (void*) opts);
return handle;
}
void httpdPlatJoin(httpd_thread_handle_t * handle)
{
if (handle) {
pthread_join(handle->handle, NULL);
}
}
void* httpdPlatMalloc(size_t len)
{
return malloc(len);
}
void httpdPlatFree(void *ptr)
{
free(ptr);
}
char * httpdPlatStrdup(const char *s) {
return strdup(s);
}

@ -0,0 +1,112 @@
/* base64.c : base-64 / MIME encode/decode */
/* PUBLIC DOMAIN - Jon Mayo - November 13, 2003 */
#include "base64.h"
#include <stdint.h>
#include <ctype.h>
static const int base64dec_tab[256] = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
};
#if 0
static int base64decode(const char in[4], char out[3]) {
uint8_t v[4];
v[0]=base64dec_tab[(unsigned)in[0]];
v[1]=base64dec_tab[(unsigned)in[1]];
v[2]=base64dec_tab[(unsigned)in[2]];
v[3]=base64dec_tab[(unsigned)in[3]];
out[0]=(v[0]<<2)|(v[1]>>4);
out[1]=(v[1]<<4)|(v[2]>>2);
out[2]=(v[2]<<6)|(v[3]);
return (v[0]|v[1]|v[2]|v[3])!=255 ? in[3]=='=' ? in[2]=='=' ? 1 : 2 : 3 : 0;
}
#endif
/* decode a base64 string in one shot */
int __attribute__((weak)) base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out)
{
unsigned int ii, io;
uint32_t v;
unsigned int rem;
for (io = 0, ii = 0, v = 0, rem = 0; ii < in_len; ii++) {
unsigned char ch;
if (isspace((int) in[ii])) { continue; }
if (in[ii] == '=') { break; } /* stop at = */
ch = base64dec_tab[(unsigned int) in[ii]];
if (ch == 255) { break; } /* stop at a parse error */
v = (v << 6) | ch;
rem += 6;
if (rem >= 8) {
rem -= 8;
if (io >= out_len) return -1; /* truncation is failure */
out[io++] = (v >> rem) & 255;
}
}
if (rem >= 8) {
rem -= 8;
if (io >= out_len) { return -1; } /* truncation is failure */
out[io++] = (v >> rem) & 255;
}
return (int) io;
}
static const uint8_t base64enc_tab[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#if 0
void base64encode(const unsigned char in[3], unsigned char out[4], int count) {
out[0]=base64enc_tab[(in[0]>>2)];
out[1]=base64enc_tab[((in[0]&3)<<4)|(in[1]>>4)];
out[2]=count<2 ? '=' : base64enc_tab[((in[1]&15)<<2)|(in[2]>>6)];
out[3]=count<3 ? '=' : base64enc_tab[(in[2]&63)];
}
#endif
int __attribute__((weak)) base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out)
{
unsigned ii, io;
uint32_t v;
unsigned rem;
for (io = 0, ii = 0, v = 0, rem = 0; ii < in_len; ii++) {
unsigned char ch;
ch = in[ii];
v = (v << 8) | ch;
rem += 8;
while (rem >= 6) {
rem -= 6;
if (io >= out_len) { return -1; } /* truncation is failure */
out[io++] = (char) base64enc_tab[(v >> rem) & 63];
}
}
if (rem) {
v <<= (6 - rem);
if (io >= out_len) { return -1; } /* truncation is failure */
out[io++] = (char) base64enc_tab[v & 63];
}
while (io & 3) {
if (io >= out_len) { return -1; } /* truncation is failure */
out[io++] = '=';
}
if (io >= out_len) { return -1; } /* no room for null terminator */
out[io] = 0;
return (int) io;
}

@ -0,0 +1,6 @@
#pragma once
#include <stddef.h>
int base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out);
int base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out);

@ -0,0 +1,174 @@
/* This code is public-domain - it is based on libcrypt
* placed in the public domain by Wei Dai and other contributors.
*/
// gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test
#include <stdint.h>
#include <string.h>
#include "sha1.h"
//according to http://ip.cadence.com/uploads/pdf/xtensalx_overview_handbook.pdf
// the cpu is normally defined as little ending, but can be big endian too.
// for the esp this seems to work
//#define SHA_BIG_ENDIAN
/* code */
#define SHA1_K0 0x5a827999
#define SHA1_K20 0x6ed9eba1
#define SHA1_K40 0x8f1bbcdc
#define SHA1_K60 0xca62c1d6
void sha1_init(sha1nfo *s)
{
s->state[0] = 0x67452301;
s->state[1] = 0xefcdab89;
s->state[2] = 0x98badcfe;
s->state[3] = 0x10325476;
s->state[4] = 0xc3d2e1f0;
s->byteCount = 0;
s->bufferOffset = 0;
}
uint32_t sha1_rol32(uint32_t number, uint8_t bits)
{
return ((number << bits) | (number >> (32 - bits)));
}
void sha1_hashBlock(sha1nfo *s)
{
uint8_t i;
uint32_t a, b, c, d, e, t;
a = s->state[0];
b = s->state[1];
c = s->state[2];
d = s->state[3];
e = s->state[4];
for (i = 0; i < 80; i++) {
if (i >= 16) {
t = s->buffer[(i + 13) & 15] ^ s->buffer[(i + 8) & 15] ^ s->buffer[(i + 2) & 15] ^ s->buffer[i & 15];
s->buffer[i & 15] = sha1_rol32(t, 1);
}
if (i < 20) {
t = (d ^ (b & (c ^ d))) + SHA1_K0;
} else if (i < 40) {
t = (b ^ c ^ d) + SHA1_K20;
} else if (i < 60) {
t = ((b & c) | (d & (b | c))) + SHA1_K40;
} else {
t = (b ^ c ^ d) + SHA1_K60;
}
t += sha1_rol32(a, 5) + e + s->buffer[i & 15];
e = d;
d = c;
c = sha1_rol32(b, 30);
b = a;
a = t;
}
s->state[0] += a;
s->state[1] += b;
s->state[2] += c;
s->state[3] += d;
s->state[4] += e;
}
void sha1_addUncounted(sha1nfo *s, uint8_t data)
{
uint8_t *const b = (uint8_t *) s->buffer;
#ifdef SHA_BIG_ENDIAN
b[s->bufferOffset] = data;
#else
b[s->bufferOffset ^ 3] = data;
#endif
s->bufferOffset++;
if (s->bufferOffset == BLOCK_LENGTH) {
sha1_hashBlock(s);
s->bufferOffset = 0;
}
}
void sha1_writebyte(sha1nfo *s, uint8_t data)
{
++s->byteCount;
sha1_addUncounted(s, data);
}
void sha1_write(sha1nfo *s, const char *data, size_t len)
{
for (; len--;) { sha1_writebyte(s, (uint8_t) *data++); }
}
void sha1_pad(sha1nfo *s)
{
// Implement SHA-1 padding (fips180-2 §5.1.1)
// Pad with 0x80 followed by 0x00 until the end of the block
sha1_addUncounted(s, 0x80);
while (s->bufferOffset != 56) { sha1_addUncounted(s, 0x00); }
// Append length in the last 8 bytes
sha1_addUncounted(s, 0); // We're only using 32 bit lengths
sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
sha1_addUncounted(s, 0); // So zero pad the top bits
sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as
sha1_addUncounted(s, s->byteCount >> 13); // byte.
sha1_addUncounted(s, s->byteCount >> 5);
sha1_addUncounted(s, s->byteCount << 3);
}
uint8_t *sha1_result(sha1nfo *s)
{
// Pad to complete the last block
sha1_pad(s);
#ifndef SHA_BIG_ENDIAN
// Swap byte order back
int i;
for (i = 0; i < 5; i++) {
s->state[i] =
(((s->state[i]) << 24) & 0xff000000)
| (((s->state[i]) << 8) & 0x00ff0000)
| (((s->state[i]) >> 8) & 0x0000ff00)
| (((s->state[i]) >> 24) & 0x000000ff);
}
#endif
// Return pointer to hash (20 characters)
return (uint8_t *) s->state;
}
#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5c
void sha1_initHmac(sha1nfo *s, const uint8_t *key, int keyLength)
{
uint8_t i;
memset(s->keyBuffer, 0, BLOCK_LENGTH);
if (keyLength > BLOCK_LENGTH) {
// Hash long keys
sha1_init(s);
for (; keyLength--;) { sha1_writebyte(s, *key++); }
memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH);
} else {
// Block length keys are used as is
memcpy(s->keyBuffer, key, keyLength);
}
// Start inner hash
sha1_init(s);
for (i = 0; i < BLOCK_LENGTH; i++) {
sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD);
}
}
uint8_t *sha1_resultHmac(sha1nfo *s)
{
uint8_t i;
// Complete inner hash
memcpy(s->innerHash, sha1_result(s), HASH_LENGTH);
// Calculate outer hash
sha1_init(s);
for (i = 0; i < BLOCK_LENGTH; i++) { sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD); }
for (i = 0; i < HASH_LENGTH; i++) { sha1_writebyte(s, s->innerHash[i]); }
return sha1_result(s);
}

@ -0,0 +1,34 @@
#pragma once
#define HASH_LENGTH 20
#define BLOCK_LENGTH 64
typedef struct sha1nfo {
uint32_t buffer[BLOCK_LENGTH/4];
uint32_t state[HASH_LENGTH/4];
uint32_t byteCount;
uint8_t bufferOffset;
uint8_t keyBuffer[BLOCK_LENGTH];
uint8_t innerHash[HASH_LENGTH];
} sha1nfo;
/* public API - prototypes - TODO: doxygen*/
/**
*/
void sha1_init(sha1nfo *s);
/**
*/
void sha1_writebyte(sha1nfo *s, uint8_t data);
/**
*/
void sha1_write(sha1nfo *s, const char *data, size_t len);
/**
*/
uint8_t* sha1_result(sha1nfo *s);
/**
*/
void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength);
/**
*/
uint8_t* sha1_resultHmac(sha1nfo *s);

@ -0,0 +1,9 @@
----------------------------------------------------------------------------
"THE BEER-WARE LICENSE" (Revision 42):
Martin d'Allens <martin.dallens@gmail.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.
----------------------------------------------------------------------------
Upstream: https://github.com/Caerbannog/esphttpclient

@ -0,0 +1,78 @@
# esphttpclient
This is a short library for ESP8266(EX) chips to make HTTP requests.
## Features
* Easy to use.
* Supports multiple requests in parallel.
* Supports GET and POST requests.
* Tested with Espressif SDK v1.0.0
## Building
If you don't have a toolchain yet, install one with <https://github.com/pfalcon/esp-open-sdk> then get Espressif's SDK.
### The submodule way
If your project looks like esphttpd from Sprite_tm:
```bash
git clone http://git.spritesserver.nl/esphttpd.git/
cd esphttpd
git submodule add https://github.com/Caerbannog/esphttpclient.git lib/esphttpclient
git submodule update --init
```
Now append `lib/esphttpclient` to the following `Makefile` line and you should be ready:
```
MODULES = driver user lib/esphttpclient
```
In case you want to use SSL don't forget to add `ssl` to `LIBS` in the `Makefile`
```
LIBS = c gcc hal pp phy net80211 lwip wpa main ssl
```
### The dirty way
Alternatively you could create a simple project:
```bash
git clone https://github.com/esp8266/source-code-examples.git
cd source-code-examples/basic_example
# Set your Wifi credentials in user_config.h
# I could not test this because of the UART baud rate (74880)
```
Then download this library and move the files to `user/`:
```bash
git clone https://github.com/Caerbannog/esphttpclient.git
mv esphttpclient/*.* user/
```
## Usage
Include `httpclient.h` from `user_main.c` then call one of these functions:
```c
void http_get(const char * url, const char * headers, http_callback user_callback);
void http_post(const char * url, const char * post_data, const char * headers, http_callback user_callback);
void http_callback_example(char * response_body, int http_status, char * response_headers, int body_size)
{
os_printf("http_status=%d\n", http_status);
if (http_status != HTTP_STATUS_GENERIC_ERROR) {
os_printf("strlen(headers)=%d\n", strlen(response_headers));
os_printf("body_size=%d\n", body_size);
os_printf("body=%s<EOF>\n", response_body);
}
}
```
## Example
The following code performs a single request, then calls `http_callback_example` to display your public IP address.
```c
http_get("http://wtfismyip.com/text", "", http_callback_example);
```
The output looks like this:
```
http_status=200
strlen(full_response)=244
body_size=15
response_body=208.97.177.124
<EOF>
```

@ -0,0 +1,589 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Martin d'Allens <martin.dallens@gmail.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.
* ----------------------------------------------------------------------------
*/
// FIXME: sprintf->snprintf everywhere.
#include <esp8266.h>
#include <httpd.h>
#include <httpclient.h>
#include "httpclient.h"
#include "esp_utils.h"
// Internal state.
typedef struct {
char *path;
int port;
char *post_data;
char *headers;
char *hostname;
char *buffer;
int buffer_size;
int max_buffer_size;
bool secure;
httpclient_cb user_callback;
int timeout;
ETSTimer timeout_timer;
httpd_method method;
void *userData;
} request_args;
static int ICACHE_FLASH_ATTR
chunked_decode(char *chunked, int size)
{
char *src = chunked;
char *end = chunked + size;
int i, dst = 0;
do {
char *endstr = NULL;
//[chunk-size]
i = (int) esp_strtol(src, &endstr, 16);
httpc_dbg("Chunk Size:%d\r\n", i);
if (i <= 0)
break;
//[chunk-size-end-ptr]
src = strstr(src, "\r\n") + 2;
//[chunk-data]
memmove(&chunked[dst], src, (size_t) i);
src += i + 2; /* CRLF */
dst += i;
} while (src < end);
//
//footer CRLF
//
/* decoded size */
return dst;
}
static void ICACHE_FLASH_ATTR
receive_callback(void *arg, char *buf, unsigned short len)
{
struct espconn *conn = (struct espconn *) arg;
request_args *req = (request_args *) conn->reserve;
if (req->buffer == NULL) {
return;
}
// Let's do the equivalent of a realloc().
int new_size = req->buffer_size + len;
if (new_size > req->max_buffer_size) {
system_soft_wdt_feed();
httpc_warn("Long resp, truncate (got %d, %d more rx, max %d)",
req->buffer_size, len, req->max_buffer_size);
int nlen = (req->max_buffer_size - req->buffer_size);
if (nlen <= 0) {
req->buffer[req->buffer_size - 1] = '\0';
if (req->secure) {
#ifdef USE_SECURE
espconn_secure_disconnect(conn);
#endif
}
else {
espconn_disconnect(conn);
}
return;
}
len = (unsigned short) nlen;
new_size = req->buffer_size + len;
}
char *new_buffer;
if (NULL == (new_buffer = (char *) malloc(new_size))) {
httpc_error("Failed to alloc more bytes (%d)", new_size);
req->buffer[0] = '\0'; // Discard the buffer to avoid using an incomplete response.
if (req->secure) {
#ifdef USE_SECURE
espconn_secure_disconnect(conn);
#endif
}
else {
espconn_disconnect(conn);
}
return; // The disconnect callback will be called.
}
memcpy(new_buffer, req->buffer, req->buffer_size);
memcpy(new_buffer + req->buffer_size - 1 /*overwrite the null character*/, buf, len); // Append new data.
new_buffer[new_size - 1] = '\0'; // Make sure there is an end of string.
free(req->buffer);
req->buffer = new_buffer;
req->buffer_size = new_size;
}
static void ICACHE_FLASH_ATTR
sent_callback(void *arg)
{
struct espconn *conn = (struct espconn *) arg;
request_args *req = (request_args *) conn->reserve;
if (req->post_data == NULL) {
httpc_dbg("All sent");
}
else {
// The headers were sent, now send the contents.
httpc_dbg("Sending request body");
if (req->secure) {
#ifdef USE_SECURE
espconn_secure_sent(conn, (uint8_t *)req->post_data, strlen(req->post_data));
#endif
}
else {
espconn_sent(conn, (uint8_t *) req->post_data, (uint16) strlen(req->post_data));
}
free(req->post_data);
req->post_data = NULL;
}
}
static void ICACHE_FLASH_ATTR
connect_callback(void *arg)
{
httpc_info("Connected!");
struct espconn *conn = (struct espconn *) arg;
request_args *req = (request_args *) conn->reserve;
espconn_regist_recvcb(conn, receive_callback);
espconn_regist_sentcb(conn, sent_callback);
char post_headers[32] = "";
if (req->post_data != NULL) { // If there is data this is a POST request.
sprintf(post_headers, "Content-Length: %d\r\n", strlen(req->post_data));
if (req->method == HTTPD_METHOD_GET) {
req->method = HTTPD_METHOD_POST;
}
}
const char *method = httpdMethodName(req->method);
if (req->headers == NULL) { /* Avoid NULL pointer, it may cause exception */
req->headers = (char *) malloc(sizeof(char));
req->headers[0] = '\0';
}
const size_t buflen = 80 // for literals in tpl string + some margin
+ strlen(method)
+ strlen(req->path)
+ strlen(req->hostname)
+ strlen(req->headers)
+ strlen(post_headers);
char buf[buflen];
int len = sprintf(buf,
"%s %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Connection: close\r\n"
"%s"
"%s"
"\r\n",
method, req->path, req->hostname, req->port, req->headers, post_headers);
httpc_dbg("Sending request");
if (req->secure) {
#ifdef USE_SECURE
espconn_secure_sent(conn, (uint8_t *)buf, len);
#endif
}
else {
espconn_sent(conn, (uint8_t *) buf, (uint16) len);
}
if (req->headers != NULL) {
free(req->headers);
req->headers = NULL;
}
}
/**
* @brief Free all that could be allocated in a request, including the struct itself.
* @param req : req to free
*/
static void
free_req(request_args *req)
{
if (!req) return;
if (req->buffer) free(req->buffer);
if (req->hostname) free(req->hostname);
if (req->path) free(req->path);
if (req->post_data) free(req->post_data);
if (req->headers) free(req->headers);
free(req);
}
static void ICACHE_FLASH_ATTR
disconnect_callback(void *arg)
{
httpc_dbg("Disconnected");
struct espconn *conn = (struct espconn *) arg;
if (conn == NULL) {
httpc_dbg("conn is null!");
return;
}
if (conn->reserve != NULL) {
httpc_dbg("Processing response");
request_args *req = (request_args *) conn->reserve;
int http_status = HTTP_STATUS_GENERIC_ERROR;
int body_size = 0;
char *body = "";
/* Turn off timeout timer */
os_timer_disarm(&(req->timeout_timer));
if (req->buffer == NULL) {
httpc_error("Buffer shouldn't be NULL");
}
else if (req->buffer[0] != '\0') {
// FIXME: make sure this is not a partial response, using the Content-Length header.
const char *version10 = "HTTP/1.0 ";
const char *version11 = "HTTP/1.1 ";
if (!strstarts(req->buffer, version10) && !strstarts(req->buffer, version11)) {
httpc_error("Invalid version in %s", req->buffer);
}
else {
http_status = atoi(req->buffer + strlen(version10));
/* find body and zero terminate headers */
char * headend = strstr(req->buffer, "\r\n\r\n");
if (headend != NULL) {
body = headend + 2;
*body++ = '\0';
*body++ = '\0';
body_size = (int) (req->buffer_size - (body - req->buffer));
if (strstr(req->buffer, "Transfer-Encoding: chunked")) {
body_size = chunked_decode(body, body_size);
}
}
}
}
httpc_info("Request completed.");
if (req->user_callback != NULL) { // Callback is optional.
req->user_callback(http_status, req->buffer, body, body_size, req->userData);
}
free_req(req);
} else {
httpc_error("Reserve is NULL!");
}
espconn_delete(conn);
if (conn->proto.tcp != NULL) {
free(conn->proto.tcp);
}
free(conn);
}
static void ICACHE_FLASH_ATTR
error_callback(void *arg, sint8 errType)
{
(void) errType;
httpc_error("Disconnected with error, type %d", errType);
disconnect_callback(arg);
}
static void ICACHE_FLASH_ATTR
http_timeout_callback(void *arg)
{
httpc_error("Connection timeout\n");
struct espconn *conn = (struct espconn *) arg;
if (conn == NULL) {
return;
}
request_args *req = (request_args *) conn->reserve;
if (req) {
/* Call disconnect */
if (req->secure) {
#ifdef USE_SECURE
espconn_secure_disconnect(conn);
#endif
}
else {
espconn_disconnect(conn);
}
if (req->user_callback != NULL) {
// fire callback, so user can free the userData
req->user_callback(HTTP_STATUS_TIMEOUT, req->buffer, "", 0, req->userData);
}
free_req(req);
}
// experimental - better cleanup
if (conn->proto.tcp != NULL) {
free(conn->proto.tcp);
}
free(conn);
}
static void ICACHE_FLASH_ATTR
dns_callback(const char *hostname, ip_addr_t *addr, void *arg)
{
(void)hostname;
request_args *req = (request_args *) arg;
if (addr == NULL) {
httpc_error("DNS failed for %s", hostname);
if (req->user_callback != NULL) {
req->user_callback(HTTP_STATUS_GENERIC_ERROR, "", "", 0, req->userData);
}
free(req);
}
else {
httpc_info("DNS found %s "IPSTR, hostname, IP2STR(addr));
struct espconn *conn = (struct espconn *) malloc(sizeof(struct espconn));
conn->type = ESPCONN_TCP;
conn->state = ESPCONN_NONE;
conn->proto.tcp = (esp_tcp *) malloc(sizeof(esp_tcp));
conn->proto.tcp->local_port = espconn_port();
conn->proto.tcp->remote_port = req->port;
conn->reserve = req;
memcpy(conn->proto.tcp->remote_ip, addr, 4);
espconn_regist_connectcb(conn, connect_callback);
espconn_regist_disconcb(conn, disconnect_callback);
espconn_regist_reconcb(conn, error_callback);
/* Set connection timeout timer */
os_timer_disarm(&(req->timeout_timer));
os_timer_setfn(&(req->timeout_timer), (os_timer_func_t *) http_timeout_callback, conn);
os_timer_arm(&(req->timeout_timer), req->timeout, false);
if (req->secure) {
#ifdef USE_SECURE
espconn_secure_set_size(ESPCONN_CLIENT, 5120); // set SSL buffer size
espconn_secure_connect(conn);
#endif
}
else {
espconn_connect(conn);
}
}
}
bool ICACHE_FLASH_ATTR
http_request(const httpclient_args *args, httpclient_cb user_callback)
{
// --- prepare port, secure... ---
// FIXME: handle HTTP auth with http://user:pass@host/
const char *url = args->url;
char hostname[128] = "";
int port = 80;
bool secure = false;
if (strstarts(url, "http://"))
url += strlen("http://"); // Get rid of the protocol.
else if (strstarts(url, "https://")) {
port = 443;
secure = true;
url += strlen("https://"); // Get rid of the protocol.
}
else {
httpc_error("Invalid URL protocol: %s", url);
return false;
}
char *path = strchr(url, '/');
if (path == NULL) {
path = strchr(url, '\0'); // Pointer to end of string.
}
char *colon = strchr(url, ':');
if (colon > path) {
colon = NULL; // Limit the search to characters before the path.
}
if (colon == NULL) { // The port is not present.
memcpy(hostname, url, (size_t) (path - url));
hostname[path - url] = '\0';
}
else {
port = atoi(colon + 1);
if (port == 0) {
httpc_error("Port error %s\n", url);
return false;
}
memcpy(hostname, url, (size_t) (colon - url));
hostname[colon - url] = '\0';
}
if (path[0] == '\0') { // Empty path is not allowed.
path = "/";
}
// ---
httpc_info("HTTP request: %s:%d%s", hostname, port, path);
request_args *req = (request_args *) malloc(sizeof(request_args));
req->hostname = esp_strdup(hostname);
req->path = esp_strdup(path);
// remove #anchor
char *hash = strchr(req->path, '#');
if (hash != NULL) *hash = '\0'; // remove the hash part
req->port = port;
req->secure = secure;
req->headers = esp_strdup(args->headers);
req->post_data = esp_strdup(args->body);
req->buffer_size = 1;
req->buffer = (char *) malloc(1);
req->buffer[0] = '\0'; // Empty string.
req->user_callback = user_callback;
req->timeout = HTTPCLIENT_DEF_TIMEOUT_MS;
req->method = args->method;
req->userData = args->userData;
req->max_buffer_size = (int) args->max_response_len;
ip_addr_t addr;
err_t error = espconn_gethostbyname((struct espconn *) req, // It seems we don't need a real espconn pointer here.
hostname, &addr, dns_callback);
if (error == ESPCONN_INPROGRESS) {
httpc_dbg("DNS pending");
}
else if (error == ESPCONN_OK) {
// Already in the local names table (or hostname was an IP address), execute the callback ourselves.
dns_callback(hostname, &addr, req);
}
else {
if (error == ESPCONN_ARG) {
httpc_error("DNS arg error %s", hostname);
}
else {
httpc_error("DNS error code %d", error);
}
dns_callback(hostname, NULL, req); // Handle all DNS errors the same way.
}
return true;
}
void httpclient_args_init(httpclient_args *args)
{
args->url = NULL;
args->body = NULL;
args->method = HTTPD_METHOD_GET;
args->headers = NULL;
args->max_response_len = HTTPCLIENT_DEF_MAX_LEN;
args->timeout = HTTPCLIENT_DEF_TIMEOUT_MS;
args->userData = NULL;
}
bool ICACHE_FLASH_ATTR
http_post(const char *url, const char *body, void *userData, httpclient_cb user_callback)
{
httpclient_args args;
httpclient_args_init(&args);
args.url = url;
args.body = body;
args.method = HTTPD_METHOD_POST;
args.userData = userData;
return http_request(&args, user_callback);
}
bool ICACHE_FLASH_ATTR
http_get(const char *url, void *userData, httpclient_cb user_callback)
{
httpclient_args args;
httpclient_args_init(&args);
args.url = url;
args.method = HTTPD_METHOD_GET;
args.userData = userData;
return http_request(&args, user_callback);
}
bool ICACHE_FLASH_ATTR
http_put(const char *url, const char *body, void *userData, httpclient_cb user_callback)
{
httpclient_args args;
httpclient_args_init(&args);
args.url = url;
args.body = body;
args.method = HTTPD_METHOD_PUT;
args.userData = userData;
return http_request(&args, user_callback);
}
void ICACHE_FLASH_ATTR
http_callback_example(int http_status,
char *response_headers,
char *response_body,
size_t body_size,
void *userData)
{
(void) userData;
dbg("Response: code %d", http_status);
if (http_status != HTTP_STATUS_GENERIC_ERROR) {
dbg("len(headers) = %d, len(body) = %d", (int) strlen(response_headers), body_size);
dbg("body: %s<EOF>", response_body); // FIXME: this does not handle binary data.
}
}
void ICACHE_FLASH_ATTR
http_callback_showstatus(int code,
char *response_headers,
char *response_body,
size_t body_size,
void *userData)
{
(void) response_body;
(void) response_headers;
(void) body_size;
(void) userData;
if (code == 200) {
info("Response OK (200)");
}
else if (code >= 400) {
error("Response ERROR (%d)", code);
dbg("Body: %s<EOF>", response_body);
}
else {
// ???
warn("Response %d", code);
dbg("Body: %s<EOF>", response_body);
}
}

@ -0,0 +1,96 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Martin d'Allens <martin.dallens@gmail.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.
* ----------------------------------------------------------------------------
*/
#ifndef HTTPCLIENT_H
#define HTTPCLIENT_H
#include <esp8266.h>
#include <httpd.h>
#define HTTP_STATUS_GENERIC_ERROR -1 // In case of TCP or DNS error the callback is called with this status.
#define HTTP_STATUS_TIMEOUT -2
#define HTTPCLIENT_DEF_MAX_LEN 5000 // Size of http responses that will cause an error.
#define HTTPCLIENT_DEF_TIMEOUT_MS 5000
/* Define this if ssl is needed. Also link the ssl lib */
//#define USE_SECURE
/**
* "full_response" is a string containing all response headers and the response body.
* "response_body and "http_status" are extracted from "full_response" for convenience.
*
* A successful request corresponds to an HTTP status code of 200 (OK).
* More info at http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
*/
typedef void (* httpclient_cb)(int http_status,
char *response_headers,
char *response_body,
size_t body_size,
void *userData);
typedef struct {
const char* url; //!< The full URL
const char* headers; //!< Headers string (\r\n separated lines)
const char* body; //!< Request body string
httpd_method method; //!< HTTP method constant
int timeout; //!< Timeout in ms
size_t max_response_len; //!< Max response length to allow
void *userData; //!< Opaque pointer that is passed to the user callback
} httpclient_args;
/**
* Populate args struct with defaults
* @param args
*/
void httpclient_args_init(httpclient_args *args);
/**
* @brief Download a web page from its URL.
*
* Try:
* http_get("http://wtfismyip.com/text", NULL, http_callback_example);
*/
bool http_get(const char * url, void *userData, httpclient_cb user_callback);
/**
* @brief Post data to a web form.
*
* The data should be encoded as application/x-www-form-urlencoded.
*
* Try:
* http_post("http://httpbin.org/post", "first_word=hello&second_word=world", NULL, http_callback_example);
*/
bool http_post(const char *url, const char *post_data, void *userData, httpclient_cb user_callback);
/** Like POST, but with the PUT method. */
bool http_put(const char *url, const char *body, void *userData, httpclient_cb user_callback);
/**
* @brief Send a HTTP request
* @param url : protocol://host[:port][/path]
* @param method : get, post, ...
* @param body : request body. If GET & body != NULL, method changes to POST.
* @param headers : additional headers string. Must end with \r\n
* @param user_callback : callback for parsing the response
* @return success (in sending)
*/
bool http_request(const httpclient_args *args, httpclient_cb user_callback);
/**
* Output on the UART.
*/
void http_callback_example(int http_status, char *response_headers, char *response_body, size_t body_size, void *userData);
/**
* Show status code, and body on error. Error/warn log msg on error.
*/
void http_callback_showstatus(int code, char *response_headers, char *response_body, size_t body_size, void *userData);
#endif

@ -0,0 +1,59 @@
#include <esp8266.h>
volatile uint32_t uptime = 0;
static ETSTimer prUptimeTimer;
static void uptimeTimerCb(void *arg)
{
uptime++;
}
void uptime_timer_init(void)
{
os_timer_disarm(&prUptimeTimer);
os_timer_setfn(&prUptimeTimer, uptimeTimerCb, NULL);
os_timer_arm(&prUptimeTimer, 1000, 1);
}
void uptime_str(char *buf)
{
u32 a = uptime;
u32 days = a / 86400;
a -= days * 86400;
u32 hours = a / 3600;
a -= hours * 3600;
u32 mins = a / 60;
a -= mins * 60;
u32 secs = a;
if (days > 0) {
sprintf(buf, "%ud %02u:%02u:%02u", days, hours, mins, secs);
} else {
sprintf(buf, "%02u:%02u:%02u", hours, mins, secs);
}
}
void uptime_print(void)
{
u32 a = uptime;
u32 days = a / 86400;
a -= days * 86400;
u32 hours = a / 3600;
a -= hours * 3600;
u32 mins = a / 60;
a -= mins * 60;
u32 secs = a;
if (days > 0) {
printf("%ud %02u:%02u:%02u", days, hours, mins, secs);
} else {
printf("%02u:%02u:%02u", hours, mins, secs);
}
}

@ -0,0 +1,22 @@
#ifndef UPTIME_H
#define UPTIME_H
#include <esp8266.h>
extern volatile uint32_t uptime;
/**
* Initialize the virtual timer for uptime counter.
*/
void uptime_timer_init(void);
/**
* Print uptime to a buffer in user-friendly format.
* Should be at least 20 bytes long.
*/
void uptime_str(char *buf);
/** Print uptime to stdout in user-friendly format */
void uptime_print(void);
#endif // UPTIME_H

@ -0,0 +1,3 @@
extern char webpages_espfs_start[];
extern char webpages_espfs_end[];
extern int webpages_espfs_size;

@ -0,0 +1,12 @@
OUTPUT_FORMAT("elf32-xtensa-le")
SECTIONS
{
.irom0.literal : ALIGN(4) SUBALIGN(4) {
webpages_espfs_start = .;
*(*)
webpages_espfs_end = .;
webpages_espfs_size = webpages_espfs_end - webpages_espfs_start;
}
}

@ -0,0 +1,58 @@
#include <stdio.h>
#include "httpd.h"
#include "httpd-utils.h"
#include "httpdespfs.h"
#include <httpd.h>
#include <cgiwebsocket.h>
#include <httpdespfs.h>
#include <auth.h>
#include "logging.h"
/** "About" page */
httpd_cgi_state tplIndex(HttpdConnData *connData, char *token, void **arg)
{
if (token == NULL) { return HTTPD_CGI_DONE; }
else if (streq(token, "date")) {
tplSend(connData, __DATE__, -1);
} else if (streq(token, "time")) {
tplSend(connData, __TIME__, -1);
} else if (streq(token, "vers_httpd")) {
tplSend(connData, httpdGetVersion(), -1);
}
return HTTPD_CGI_DONE;
}
/**
* Application routes
*/
const HttpdBuiltInUrl routes[] = {
// TODO password lock ...
// --- Web pages ---
ROUTE_TPL_FILE("/", tplIndex, "/index.tpl"),
ROUTE_FILESYSTEM(),
ROUTE_END(),
};
int main()
{
printf("Hello, World!\n");
struct httpd_options opts = {
.port = 80,
};
httpd_thread_handle_t *handle = httpdInit(routes, &opts);
httpdSetName("ServerName");
httpdJoin(handle);
return 0;
}
Loading…
Cancel
Save