commit
26867f4767
@ -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…
Reference in new issue