Added files

This commit is contained in:
2016-03-10 23:49:47 +01:00
commit adea64ef9c
1492 changed files with 13182 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
gitdir: ../.git/modules/libesphttpd
+10
View File
@@ -0,0 +1,10 @@
build/
espfs/mkespfsimage/*.o
espfs/mkespfsimage/mkespfsimage
webpages.espfs
libesphttpd.a
espfs/espfstest/*.o
espfs/espfstest/espfstest
*.DS_Store
html_compressed/
libwebpages-espfs.a
+3
View File
@@ -0,0 +1,3 @@
[submodule "lib/heatshrink"]
path = lib/heatshrink
url = https://github.com/atomicobject/heatshrink.git
+191
View File
@@ -0,0 +1,191 @@
# Directory the Makefile is in. Please don't include other Makefiles before this.
THISDIR:=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))
#Include httpd config from lower level, if it exists
-include ../esphttpdconfig.mk
#Default options. If you want to change them, please create ../esphttpdconfig.mk with the options you want in it.
GZIP_COMPRESSION ?= no
COMPRESS_W_YUI ?= no
YUI-COMPRESSOR ?= /usr/bin/yui-compressor
USE_HEATSHRINK ?= yes
HTTPD_WEBSOCKETS ?= yes
USE_OPENSDK ?= no
FREERTOS ?= no
HTTPD_MAX_CONNECTIONS ?= 4
#For FreeRTOS
HTTPD_STACKSIZE ?= 2048
# Output directors to store intermediate compiled files
# relative to the project directory
BUILD_BASE = build
# Base directory for the compiler. Needs a / at the end; if not set it'll use the tools that are in
# the PATH.
XTENSA_TOOLS_ROOT ?=
# base directory of the ESP8266 SDK package, absolute
# Only used for the non-FreeRTOS build
SDK_BASE ?= /opt/Espressif/ESP8266_SDK
# Base directory of the ESP8266 FreeRTOS SDK package, absolute
# Only used for the FreeRTOS build
SDK_PATH ?= /opt/Espressif/ESP8266_RTOS_SDK
# name for the target project
LIB = libesphttpd.a
# which modules (subdirectories) of the project to include in compiling
MODULES = espfs core util
EXTRA_INCDIR = ./include \
. \
lib/heatshrink/
# compiler flags using during compilation of source files
CFLAGS = -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \
-nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \
-Wno-address -DHTTPD_MAX_CONNECTIONS=$(HTTPD_MAX_CONNECTIONS) -DHTTPD_STACKSIZE=$(HTTPD_STACKSIZE) \
# various paths from the SDK used in this project
SDK_LIBDIR = lib
SDK_LDDIR = ld
ifeq ("$(FREERTOS)","yes")
CFLAGS += -DFREERTOS -DLWIP_OPEN_SRC -ffunction-sections -fdata-sections
SDK_INCDIR = include \
include/freertos \
include/espressif/esp8266 \
include/espressif \
extra_include \
include/lwip \
include/lwip/lwip \
include/lwip/ipv4 \
include/lwip/ipv6
SDK_INCDIR := $(addprefix -I$(SDK_PATH)/,$(SDK_INCDIR))
else
SDK_INCDIR = include
SDK_INCDIR := $(addprefix -I$(SDK_BASE)/,$(SDK_INCDIR))
endif
# select which tools to use as compiler, librarian and linker
CC := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-gcc
AR := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-ar
LD := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-gcc
OBJCOPY := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objcopy
####
#### no user configurable options below here
####
SRC_DIR := $(MODULES)
BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES))
SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c))
OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(SRC))
INCDIR := $(addprefix -I,$(SRC_DIR))
EXTRA_INCDIR := $(addprefix -I,$(EXTRA_INCDIR))
MODULE_INCDIR := $(addsuffix /include,$(INCDIR))
V ?= $(VERBOSE)
ifeq ("$(V)","1")
Q :=
vecho := @true
else
Q := @
vecho := @echo
endif
ifneq ("$(FREERTOS)","yes")
ifeq ("$(USE_OPENSDK)","yes")
CFLAGS += -DUSE_OPENSDK
else
CFLAGS += -D_STDINT_H
endif
endif
ifeq ("$(GZIP_COMPRESSION)","yes")
CFLAGS += -DGZIP_COMPRESSION
endif
ifeq ("$(USE_HEATSHRINK)","yes")
CFLAGS += -DESPFS_HEATSHRINK
endif
ifeq ("$(HTTPD_WEBSOCKETS)","yes")
CFLAGS += -DHTTPD_WEBSOCKETS
endif
vpath %.c $(SRC_DIR)
define compile-objects
$1/%.o: %.c
$(vecho) "CC $$<"
$(Q) $(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@
endef
.PHONY: all checkdirs clean webpages.espfs submodules
all: checkdirs $(LIB) webpages.espfs libwebpages-espfs.a
submodules: lib/heatshrink/Makefile
lib/heatshrink/Makefile:
$(Q) echo "Heatshrink isn't found. Checking out submodules to fetch it."
$(Q) git submodule init
$(Q) git submodule update
$(LIB): $(BUILD_DIR) submodules $(OBJ)
$(vecho) "AR $@"
$(Q) $(AR) cru $@ $(OBJ)
checkdirs: $(BUILD_DIR)
$(BUILD_DIR):
$(Q) mkdir -p $@
webpages.espfs: $(HTMLDIR) espfs/mkespfsimage/mkespfsimage
ifeq ("$(COMPRESS_W_YUI)","yes")
$(Q) rm -rf html_compressed;
$(Q) cp -r ../html html_compressed;
$(Q) echo "Compression assets with yui-compressor. This may take a while..."
$(Q) for file in `find html_compressed -type f -name "*.js"`; do $(YUI-COMPRESSOR) --type js $$file -o $$file; done
$(Q) for file in `find html_compressed -type f -name "*.css"`; do $(YUI-COMPRESSOR) --type css $$file -o $$file; done
$(Q) awk "BEGIN {printf \"YUI compression ratio was: %.2f%%\\n\", (`du -b -s html_compressed/ | sed 's/\([0-9]*\).*/\1/'`/`du -b -s ../html/ | sed 's/\([0-9]*\).*/\1/'`)*100}"
# mkespfsimage will compress html, css, svg and js files with gzip by default if enabled
# override with -g cmdline parameter
$(Q) cd html_compressed; find . | $(THISDIR)/espfs/mkespfsimage/mkespfsimage > $(THISDIR)/webpages.espfs; cd ..;
else
$(Q) cd ../html; find . | $(THISDIR)/espfs/mkespfsimage/mkespfsimage > $(THISDIR)/webpages.espfs; cd ..
endif
libwebpages-espfs.a: webpages.espfs
$(Q) $(OBJCOPY) -I binary -O elf32-xtensa-le -B xtensa --rename-section .data=.irom0.literal \
webpages.espfs build/webpages.espfs.o.tmp
$(Q) $(LD) -nostdlib -Wl,-r build/webpages.espfs.o.tmp -o build/webpages.espfs.o -Wl,-T webpages.espfs.ld
$(Q) $(AR) cru $@ build/webpages.espfs.o
espfs/mkespfsimage/mkespfsimage: espfs/mkespfsimage/
$(Q) $(MAKE) -C espfs/mkespfsimage USE_HEATSHRINK="$(USE_HEATSHRINK)" GZIP_COMPRESSION="$(GZIP_COMPRESSION)"
clean:
$(Q) rm -f $(LIB)
$(Q) find $(BUILD_BASE) -type f | xargs rm -f
$(Q) make -C espfs/mkespfsimage/ clean
$(Q) rm -rf $(FW_BASE)
$(Q) rm -f webpages.espfs
ifeq ("$(COMPRESS_W_YUI)","yes")
$(Q) rm -rf html_compressed
endif
$(foreach bdir,$(BUILD_DIR),$(eval $(call compile-objects,$(bdir))))
+320
View File
@@ -0,0 +1,320 @@
# Libesphttpd intro
Libesphttpd is a HTTP server library for the ESP8266. It supports integration in projects
running under the non-os and FreeRTOS-based SDK. Its core is clean and small, but it provides an
extensible architecture with plugins to handle a flash-based compressed read-only filesystem
for static files, a tiny template engine, websockets, a captive portal, and more.
# Examples
There are two example projects that integrate this code, both a [non-os](http://git.spritesserver.nl/esphttpd.git/)
as well as a [FreeRTOS-based](http://git.spritesserver.nl/esphttpd-freertos.git/) example. They show
how to use libesphttpd to serve files from an ESP8266 and illustrate a way to make an user associate
the ESP8266 with an access point from a standard webbrowser on a PC or mobile phone.
# Programming guide
Programming libesphttpd will require some knowledge of HTTP. Knowledge of the exact RFCs isn't needed,
but it helps if you know the difference between a GET and a POST request, how HTTP headers work,
what an mime-type is and so on. Furthermore, libesphttpd is written in the C language and uses the
libraries available on the ESP8266 SDK. It is assumed the developer knows C and has some experience
with the SDK.
## Initializing libesphttpd
Initializing libesphttpd is usually done in the `user_main()` of your project, but it is not mandatory
to place the call here. Initialization is done by the `httpdInit(builtInUrls, port)` call. The port
is the TCP port the webserver will listen on; the builtInUrls is the CGI list.
(As an aside: CGI actually is an abbreviation for Common Gateway Interface, which is a specification
to allow external processes to interface with a non-embedded webserver. The CGI functions mentioned here
have nothing to do with the CGI protocol specification; the term 'CGI' is just used as a quick
handle for a function interpreting headers and generating data to send to the web client.)
The CGI list is an array of the HttpdBuiltInUrl type. Here's an example:
```c
HttpdBuiltInUrl builtInUrls[]={
{"/", cgiRedirect, "/index.cgi"},
{"/index.cgi", cgiMyFunction, NULL},
{"*", cgiEspFsHook, NULL},
{NULL, NULL, NULL}
};
```
As you can see, the array consists of a number of entries, with the last entry filled with NULLs. When
the webserver gets a request, it will run down the list and try to match the URL the browser sent to the
pattern specified in the first argument in the list. If a match is detected, the corresponding CGI
function is called. This function gets the opportunity to handle the request, but it also can pass
on handling it; if this happens, the webserver will keep going down the list to look for a CGI
with a matching pattern willing to handle the request; if there is none on the list, it will
generate a 404 page itself.
The patterns can also have wildcards: a * at the end of the pattern matches any text. For instance,
the pattern `/wifi/*` will match requests for `/wifi/index.cgi` and `/wifi/picture.jpg`, but not
for example `/settings/wifi/`. The cgiEspFsHook is used like that in the example: it will be called
on any request that is not handled by the cgi functions earlier in the list.
There also is a third entry in the list. This is an optional argument for the CGI function; its
purpose differs per specific function. If this is not needed, it's okay to put NULL there instead.
### Sidenote: About the cgiEspFsHook call
While `cgiEspFsHook` isn't handled any different than any other cgi function, it may be useful
to shortly elaborate what its function is. `cgiEspFsHook` is responsible, on most implementations,
for serving up the static files that are included in the project: static HTML pages, images, Javascript
code etc. Esphttpd doesn't have a built-in method to serve static files: the code responsible
for doing it is plugged into it the same way as any cgi function is. This allows the developer to
leave away the ability to serve static files if it isn't needed, or use a different implementation
that serves e.g. files off the FAT-partition of a SD-card.
## Built-in CGI functions
The webserver provides a fair amount of general-use CGI functions. Because of the structure of
libesphttpd works and some linker magic in the Makefiles of the SDKs, the compiler will only
include them in the output binary if they're actually used.
* __cgiRedirect__ (arg: URL to redirect to)
This is a convenience function to redirect the browser requesting this URL to a different URL. For
example, an entry like {"/google", cgiRedirect, "http://google.com"} would redirect
all browsers requesting /google to the website of the search giant.
* __cgiRedirectToHostname__ (arg: hostname to redirect to)
If the host as requested by the browser isn't the hostname in the argument, the webserver will do a redirect
to the host instead. If the hostname does match, it will pass on the request.
* __cgiRedirectApClientToHostname__ (arg: hostname to redirect to)
This does the same as `cgiRedirectToHostname` but only to clients connected to the SoftAP of the
ESP8266. This and the former function are used with the captive portal mode. The captive portal consists
of a DNS-server (started by calling `captdnsInit()`) resolving all hostnames into the IP of the
ESP8266. These redirect functions can then be used to further redirect the client to the hostname of
the ESP8266.
* __cgiReadFlash__ (arg: none)
Will serve up the SPI flash of the ESP8266 as a binary file.
* __cgiGetFirmwareNext__ (arg: CgiUploadFlashDef flash description data)
For OTA firmware upgrade: indicates if the user1 or user2 firmware needs to be sent to the ESP to do
an OTA upgrade
* __cgiUploadFirmware__ (arg: CgiUploadFlashDef flash description data)
Accepts a POST request containing the user1 or user2 firmware binary and flashes it to the SPI flash
* __cgiRebootFirmware__ (arg: none)
Reboots the ESP8266 to the newly uploaded code after a firmware upload.
* __cgiWiFi* functions__ (arg: various)
These are used to change WiFi mode, scan for access points, associate to an access point etcetera. See
the example projects for an implementation that uses these function calls.
* __cgiWebsocket__ (arg: connect function)
This CGI is used to set up a websocket. Websockets are described later in this document.
* __cgiEspFsHook__ (arg: none)
Serves files from the espfs filesystem. The espFsInit function should be called first, with as argument
a pointer to the start of the espfs binary data in flash. The binary data can be both flashed separately
to a free bit of SPI flash, as well as linked in with the binary. The nonos example project can be
configured to do either.
* __cgiEspFsTemplate__ (arg: template function)
The espfs code comes with a small but efficient template routine, which can fill a template file stored on
the espfs filesystem with user-defined data.
## Writing a CGI function
A CGI function, in principle, is called when the HTTP headers have come in and the client is waiting for
the response of the webserver. The CGI function is responsible for generating this response, including
the correct headers and an appropriate body. To decide what response to generate and what other actions
to take, the CGI function can inspect various information sources, like data passed as GET- or
POST-arguments.
A simple CGI function may, for example, greet the user with a name given as a GET argument:
```c
int ICACHE_FLASH_ATTR cgiGreetUser(HttpdConnData *connData) {
int len; //length of user name
char name[128]; //Temporary buffer for name
char output[256]; //Temporary buffer for HTML output
//If the browser unexpectedly closes the connection, the CGI will be called
//with connData->conn=NULL. We can use this to clean up any data. It's not really
//used in this simple CGI function.
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
if (connData->requestType!=HTTPD_METHOD_GET) {
//Sorry, we only accept GET requests.
httpdStartResponse(connData, 406); //http error code 'unacceptable'
httpdEndHeaders(connData);
return HTTPD_CGI_DONE;
}
//Look for the 'name' GET value. If found, urldecode it and return it into the 'name' var.
len=httpdFindArg(connData->getArgs, "name", name, sizeof(name));
if (len==-1) {
//If the result of httpdFindArg is -1, the variable isn't found in the data.
strcpy(name, "unknown person");
} else {
//If len isn't -1, the variable is found and is copied to the 'name' variable
}
//Generate the header
//We want the header to start with HTTP code 200, which means the document is found.
httpdStartResponse(connData, 200);
//We are going to send some HTML.
httpdHeader(connData, "Content-Type", "text/html");
//No more headers.
httpdEndHeaders(connData);
//We're going to send the HTML as two pieces: a head and a body. We could've also done
//it in one go, but this demonstrates multiple ways of calling httpdSend.
//Send the HTML head. Using -1 as the length will make httpdSend take the length
//of the zero-terminated string it's passed as the amount of data to send.
httpdSend(connData, "<html><head><title>Page</title></head>", -1)
//Generate the HTML body.
len=sprintf(output, "<body><p>Hello, %s!</p></body></html>", name);
//Send HTML body to webbrowser. We use the length as calculated by sprintf here.
//Using -1 again would also have worked, but this is more efficient.
httpdSend(connData, output, len);
//All done.
return HTTPD_CGI_DONE;
}
```
Putting this CGI function into the HttpdBuiltInUrl array, for example with pattern `"/hello.cgi"`,
would allow an user to request the page `"http://192.168.4.1/hello.cgi?name=John+Doe"` and get a document
saying *"Hello, John Doe!"*.
A word of warning: while it may look like you could forego the entire
httpdStartResponse/httpdHeader/httpdEndHeader structure and send all the HTTP headers using httpdSend,
this will break a few things that need to know when the headers are finished, for example the
HTTP 1.1 chunked transfer mode.
The approach of parsing the arguments, building up a response and then sending it in one go is pretty
simple and works just fine for small bits of data. The gotcha here is that all http data sent during the
CGI function (headers and data) are temporarily stored in a buffer, which is sent to the client when
the function returns. The size of this buffer is typically about 2K; if the CGI tries to send more than
this, data will be lost.
The way to get around this is to send part of the data using `httpdSend` and then return with `HTTPD_CGI_MORE`
instead of `HTTPD_CGI_DONE`. The webserver will send the partial data and will call the CGI function
again so it can send another part of the data, until the CGI function finally returns with `HTTPD_CGI_DONE`.
The CGI can store it's state in connData->cgiData, which is a freely usable pointer that will persist across
all calls in the request. It is NULL on the first call, and the standard way of doing things is to allocate
a pointer to a struct that stores state here. Here's an example:
```c
typedef struct {
char *stringPos;
} LongStringState;
static char *longString="Please assume this is a very long string, way too long to be sent"\
"in one time because it won't fit in the send buffer in it's entirety; we have to"\
"break up sending it in multiple parts."
int ICACHE_FLASH_ATTR cgiSendLongString(HttpdConnData *connData) {
LongStringState *state=connData->cgiData;
int len;
//If the browser unexpectedly closes the connection, the CGI will be called
//with connData->conn=NULL. We can use this to clean up any data. It's pretty relevant
//here because otherwise we may leak memory when the browser aborts the connection.
if (connData->conn==NULL) {
//Connection aborted. Clean up.
if (state!=NULL) free(state);
return HTTPD_CGI_DONE;
}
if (state==NULL) {
//This is the first call to the CGI for this webbrowser request.
//Allocate a state structure.
state=malloc(sizeof(LongStringState);
//Save the ptr in connData so we get it passed the next time as well.
connData->cgiData=state;
//Set initial pointer to start of string
state->stringPos=longString;
//We need to send the headers before sending any data. Do that now.
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
}
//Figure out length of string to send. We will never send more than 128 bytes in this example.
len=strlen(state->stringPos); //Get remaining length
if (len>128) len=128; //Never send more than 128 bytes
//Send that amount of data
httpdSend(connData, state->stringPos, len);
//Adjust stringPos to first byte we haven't sent yet
state->stringPos+=len;
//See if we need to send more
if (strlen(state->stringPos)!=0) {
//we have more to send; let the webserver call this function again.
return HTTPD_CGI_MORE;
} else {
//We're done. Clean up here as well: if the CGI function returns HTTPD_CGI_DONE, it will
//not be called again.
free(state);
return HTTPD_CGI_DONE;
}
}
```
For POST data, a similar technique is used. For small amounts of POST data (smaller than MAX_POST, typically
1024 bytes) the entire thing will be stored in `connData->post->buff` and is accessible in its entirely
on the first call to the CGI function. For example, when using POST to send form data, if the amount of expected
data is low, it is acceptable to do a call like `len=httpdFindArg(connData->post->buff, "varname", buff, sizeof(buff));`
to get the data for the individual form elements.
In all cases, `connData->post->len` will contain the length of the entirety of the POST data, while
`connData->post->buffLen` contains the length of the data in `connData->post->buff`. In the case where the
total POST data is larger than the POST buffer, the latter will be less than the former. In this case, the
CGI function is expected to not send any headers or data out yet, but to process the incoming bit of POST data and
return with `HTTPD_CGI_MORE`. The next call will contain the next chunk of POST data. `connData->post->received`
will always contain the total amount of POST data received for the request, including the data passed
to the CGI. When that number equals `connData->post->len`, it means no more POST data is expected and
the CGI function is free to send out the reply headers and data for the request.
## The template engine
The espfs driver comes with a tiny template engine, which allows for runtime-calculated value changes in a static
html page. It can be included in the builtInUrls variable like this:
```c
{"/showname.tpl", cgiEspFsTemplate, tplShowName}
```
It requires two things. First of all, the template is needed, which specifically is a file on the espfs with the
same name as the first argument of the builtInUrls value, in this case `showname.tpl`. It is a standard HTML file
containing a number of %name% entries. For example:
```html
<html>
<head><title>Welcome</title></head>
<body>
<h1>Welcome, %username%, to the %thing%!</h1>
</body>
</html>
```
When this URL is requested, the words between percent characters will invoke the `tplShowName` function, allowing
it to output specific data. For example:
```c
int ICACHE_FLASH_ATTR tplShowName(HttpdConnData *connData, char *token, void **arg) {
if (token==NULL) return HTTPD_CGI_DONE;
if (os_strcmp(token, "username")==0) httpdSend(connData, "John Doe", -1);
if (os_strcmp(token, "thing")==0) httpdSend(connData, "ESP8266 webserver", -1);
return HTTPD_CGI_DONE;
}
```
This will result in a page stating *Welcome, John Doe, to the ESP8266 webserver!*.
## Websocket functionality
ToDo: document this
+61
View File
@@ -0,0 +1,61 @@
/*
HTTP auth implementation. Only does basic authentication for now.
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "auth.h"
#include "base64.h"
int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData) {
const char *forbidden="401 Forbidden.";
int no=0;
int r;
char hdr[(AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2)*10];
char userpass[AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2];
char user[AUTH_MAX_USER_LEN];
char pass[AUTH_MAX_PASS_LEN];
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
r=httpdGetHeader(connData, "Authorization", hdr, sizeof(hdr));
if (r && strncmp(hdr, "Basic", 5)==0) {
r=base64_decode(strlen(hdr)-6, hdr+6, sizeof(userpass), (unsigned char *)userpass);
if (r<0) r=0; //just clean out string on decode error
userpass[r]=0; //zero-terminate user:pass string
// printf("Auth: %s\n", userpass);
while (((AuthGetUserPw)(connData->cgiArg))(connData, no,
user, AUTH_MAX_USER_LEN, pass, AUTH_MAX_PASS_LEN)) {
//Check user/pass against auth header
if (strlen(userpass)==strlen(user)+strlen(pass)+1 &&
strncmp(userpass, user, strlen(user))==0 &&
userpass[strlen(user)]==':' &&
strcmp(userpass+strlen(user)+1, pass)==0) {
//Authenticated. Yay!
return HTTPD_CGI_AUTHENTICATED;
}
no++; //Not authenticated with this user/pass. Check next user/pass combo.
}
}
//Not authenticated. Go bug user with login screen.
httpdStartResponse(connData, 401);
httpdHeader(connData, "Content-Type", "text/plain");
httpdHeader(connData, "WWW-Authenticate", "Basic realm=\""HTTP_AUTH_REALM"\"");
httpdEndHeaders(connData);
httpdSend(connData, forbidden, -1);
//Okay, all done.
return HTTPD_CGI_DONE;
}
+109
View File
@@ -0,0 +1,109 @@
/* base64.c : base-64 / MIME encode/decode */
/* PUBLIC DOMAIN - Jon Mayo - November 13, 2003 */
#include <esp8266.h>
#include "base64.h"
static const int base64dec_tab[256] ICACHE_RODATA_ATTR={
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 ICACHE_FLASH_ATTR 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 ICACHE_FLASH_ATTR 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 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 ICACHE_FLASH_ATTR 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++]=base64enc_tab[(v>>rem)&63];
}
}
if(rem) {
v<<=(6-rem);
if(io>=out_len) return -1; /* truncation is failure */
out[io++]=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 io;
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef BASE64_H
#define BASE64_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);
#endif
+243
View File
@@ -0,0 +1,243 @@
/*
ESP8266 web server - platform-dependent routines, FreeRTOS version
Thanks to my collague at Espressif for writing the foundations of this code.
*/
#ifdef FREERTOS
#include <esp8266.h>
#include "httpd.h"
#include "platform.h"
#include "httpd-platform.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "lwip/lwip/sockets.h"
static int httpPort;
static int httpMaxConnCt;
struct RtosConnType{
int fd;
int needWriteDoneNotif;
int needsClose;
int port;
char ip[4];
};
static RtosConnType rconn[HTTPD_MAX_CONNECTIONS];
int ICACHE_FLASH_ATTR httpdPlatSendData(ConnTypePtr conn, char *buff, int len) {
conn->needWriteDoneNotif=1;
return (write(conn->fd, buff, len)>=0);
}
void ICACHE_FLASH_ATTR httpdPlatDisconnect(ConnTypePtr conn) {
conn->needsClose=1;
conn->needWriteDoneNotif=1; //because the real close is done in the writable select code
}
void httpdPlatDisableTimeout(ConnTypePtr conn) {
//Unimplemented for FreeRTOS
}
#define RECV_BUF_SIZE 2048
static void platHttpServerTask(void *pvParameters) {
int32 listenfd;
int32 remotefd;
int32 len;
int32 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;
for (x=0; x<HTTPD_MAX_CONNECTIONS; x++) {
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) {
httpd_printf("platHttpServerTask: failed to create sock!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while(listenfd == -1);
/* Bind to the local port */
do{
ret = bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret != 0) {
httpd_printf("platHttpServerTask: failed to bind!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while(ret != 0);
do{
/* Listen to the local connection */
ret = listen(listenfd, HTTPD_MAX_CONNECTIONS);
if (ret != 0) {
httpd_printf("platHttpServerTask: failed to listen!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while(ret != 0);
httpd_printf("esphttpd: active and listening to connections.\n");
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 (rconn[x].fd!=-1) {
FD_SET(rconn[x].fd, &readset);
if (rconn[x].needWriteDoneNotif) FD_SET(rconn[x].fd, &writeset);
if (rconn[x].fd>maxfdp) maxfdp=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) {
httpd_printf("platHttpServerTask: Huh? Accept failed.\n");
continue;
}
for(x=0; x<HTTPD_MAX_CONNECTIONS; x++) if (rconn[x].fd==-1) break;
if (x==HTTPD_MAX_CONNECTIONS) {
httpd_printf("platHttpServerTask: Huh? Got accept with all slots full.\n");
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));
rconn[x].fd=remotefd;
rconn[x].needWriteDoneNotif=0;
rconn[x].needsClose=0;
len=sizeof(name);
getpeername(remotefd, &name, (socklen_t *)&len);
struct sockaddr_in *piname=(struct sockaddr_in *)&name;
rconn[x].port=piname->sin_port;
memcpy(&rconn[x].ip, &piname->sin_addr.s_addr, sizeof(rconn[x].ip));
httpdConnectCb(&rconn[x], rconn[x].ip, 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);
// httpd_printf("httpserver acpt index %d sockfd %d!\n", x, remotefd);
}
//See if anything happened on the existing connections.
for(x=0; x < HTTPD_MAX_CONNECTIONS; x++){
//Skip empty slots
if (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 (rconn[x].needWriteDoneNotif && FD_ISSET(rconn[x].fd, &writeset)) {
rconn[x].needWriteDoneNotif=0; //Do this first, httpdSentCb may write something making this 1 again.
if (rconn[x].needsClose) {
//Do callback and close fd.
httpdDisconCb(&rconn[x], rconn[x].ip, rconn[x].port);
close(rconn[x].fd);
rconn[x].fd=-1;
} else {
httpdSentCb(&rconn[x], rconn[x].ip, rconn[x].port);
}
}
if (FD_ISSET(rconn[x].fd, &readset)) {
precvbuf=(char*)malloc(RECV_BUF_SIZE);
if (precvbuf==NULL) httpd_printf("platHttpServerTask: memory exhausted!\n");
ret=recv(rconn[x].fd, precvbuf, RECV_BUF_SIZE,0);
if (ret > 0) {
//Data received. Pass to httpd.
httpdRecvCb(&rconn[x], rconn[x].ip, rconn[x].port, precvbuf, ret);
} else {
//recv error,connection close
httpdDisconCb(&rconn[x], rconn[x].ip, rconn[x].port);
close(rconn[x].fd);
rconn[x].fd=-1;
}
if (precvbuf) free(precvbuf);
}
}
}
}
#if 0
//Deinit code, not used here.
/*release data connection*/
for(x=0; x < HTTPD_MAX_CONNECTIONS; x++){
//find all valid handle
if(connData[x].conn == NULL) continue;
if(connData[x].conn->sockfd >= 0){
os_timer_disarm((os_timer_t *)&connData[x].conn->stop_watch);
close(connData[x].conn->sockfd);
connData[x].conn->sockfd = -1;
connData[x].conn = NULL;
if(connData[x].cgi!=NULL) connData[x].cgi(&connData[x]); //flush cgi data
httpdRetireConn(&connData[x]);
}
}
/*release listen socket*/
close(listenfd);
vTaskDelete(NULL);
#endif
}
//Initialize listening socket, do general initialization
void ICACHE_FLASH_ATTR httpdPlatInit(int port, int maxConnCt) {
httpPort=port;
httpMaxConnCt=maxConnCt;
xTaskCreate(platHttpServerTask, (const signed char *)"esphttpd", HTTPD_STACKSIZE, NULL, 4, NULL);
}
#endif
+76
View File
@@ -0,0 +1,76 @@
/*
ESP8266 web server - platform-dependent routines, nonos version
*/
#include <esp8266.h>
#include "httpd.h"
#include "platform.h"
#include "httpd-platform.h"
#ifndef FREERTOS
//Listening connection data
static struct espconn httpdConn;
static esp_tcp httpdTcp;
static void ICACHE_FLASH_ATTR platReconCb(void *arg, sint8 err) {
//Yeah, this is pretty useless...
}
static void ICACHE_FLASH_ATTR platDisconCb(void *arg) {
ConnTypePtr conn=arg;
httpdDisconCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port);
}
static void ICACHE_FLASH_ATTR platRecvCb(void *arg, char *data, unsigned short len) {
ConnTypePtr conn=arg;
httpdRecvCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port, data, len);
}
static void ICACHE_FLASH_ATTR platSentCb(void *arg) {
ConnTypePtr conn=arg;
httpdSentCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port);
}
static void ICACHE_FLASH_ATTR platConnCb(void *arg) {
ConnTypePtr conn=arg;
if (httpdConnectCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port)) {
espconn_regist_recvcb(conn, platRecvCb);
espconn_regist_reconcb(conn, platReconCb);
espconn_regist_disconcb(conn, platDisconCb);
espconn_regist_sentcb(conn, platSentCb);
} else {
espconn_disconnect(conn);
}
}
int ICACHE_FLASH_ATTR httpdPlatSendData(ConnTypePtr conn, char *buff, int len) {
int r;
r=espconn_sent(conn, (uint8_t*)buff, len);
return (r>=0);
}
void ICACHE_FLASH_ATTR httpdPlatDisconnect(ConnTypePtr conn) {
espconn_disconnect(conn);
}
void ICACHE_FLASH_ATTR httpdPlatDisableTimeout(ConnTypePtr conn) {
//Can't disable timeout; set to 2 hours instead.
espconn_regist_time(conn, 7199, 1);
}
//Initialize listening socket, do general initialization
void ICACHE_FLASH_ATTR httpdPlatInit(int port, int maxConnCt) {
httpdConn.type=ESPCONN_TCP;
httpdConn.state=ESPCONN_NONE;
httpdTcp.local_port=port;
httpdConn.proto.tcp=&httpdTcp;
espconn_regist_connectcb(&httpdConn, platConnCb);
espconn_accept(&httpdConn);
espconn_tcp_set_max_con_allow(&httpdConn, maxConnCt);
}
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef HTTPD_PLATFORM_H
#define HTTPD_PLATFORM_H
int httpdPlatSendData(ConnTypePtr conn, char *buff, int len);
void httpdPlatDisconnect(ConnTypePtr conn);
void httpdPlatDisableTimeout(ConnTypePtr conn);
void httpdPlatInit(int port, int maxConnCt);
#endif
+775
View File
@@ -0,0 +1,775 @@
/*
Esp8266 http server - core routines
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "httpd.h"
#include "httpd-platform.h"
//Max length of request head. This is statically allocated for each connection.
#define MAX_HEAD_LEN 1024
//Max post buffer len. This is dynamically malloc'ed if needed.
#define MAX_POST 1024
//Max send buffer len. This is allocated on the stack.
#define MAX_SENDBUFF_LEN 2048
//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.
#define MAX_BACKLOG_SIZE (4*1024)
//This gets set at init time.
static HttpdBuiltInUrl *builtInUrls;
typedef struct HttpSendBacklogItem HttpSendBacklogItem;
struct HttpSendBacklogItem {
int len;
HttpSendBacklogItem *next;
char data[];
};
//Flags
#define HFL_HTTP11 (1<<0)
#define HFL_CHUNKED (1<<1)
#define HFL_SENDINGBODY (1<<2)
#define HFL_DISCONAFTERSENT (1<<3)
//Private data for http connection
struct HttpdPriv {
char head[MAX_HEAD_LEN];
int headPos;
char *sendBuff;
int sendBuffLen;
char *chunkHdr;
HttpSendBacklogItem *sendBacklog;
int sendBacklogSize;
int flags;
};
//Connection pool
static HttpdConnData *connData[HTTPD_MAX_CONNECTIONS];
//Struct to keep extension->mime data in
typedef struct {
const char *ext;
const char *mimetype;
} MimeMap;
//#define RSTR(a) ((const char)(a))
//The mappings from file extensions to mime types. If you need an extra mime type,
//add it here.
static const ICACHE_RODATA_ATTR MimeMap mimeTypes[]={
{"htm", "text/htm"},
{"html", "text/html"},
{"css", "text/css"},
{"js", "text/javascript"},
{"txt", "text/plain"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"png", "image/png"},
{"svg", "image/svg+xml"},
{NULL, "text/html"}, //default value
};
//Returns a static char* to a mime type for a given url to a file.
const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) {
int i=0;
//Go find the extension
char *ext=url+(strlen(url)-1);
while (ext!=url && *ext!='.') ext--;
if (*ext=='.') ext++;
//ToDo: strcmp is case sensitive; we may want to do case-intensive matching here...
while (mimeTypes[i].ext!=NULL && strcmp(ext, mimeTypes[i].ext)!=0) i++;
return mimeTypes[i].mimetype;
}
//Looks up the connData info for a specific connection
static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(ConnTypePtr conn, char *remIp, int remPort) {
for (int i=0; i<HTTPD_MAX_CONNECTIONS; i++) {
if (connData[i] && connData[i]->remote_port == remPort &&
memcmp(connData[i]->remote_ip, remIp, 4) == 0) {
connData[i]->conn=conn;
return connData[i];
}
}
//Shouldn't happen.
httpd_printf("*** Unknown connection %d.%d.%d.%d:%d\n", remIp[0]&0xff, remIp[1]&0xff, remIp[2]&0xff, remIp[3]&0xff, remPort);
httpdPlatDisconnect(conn);
return NULL;
}
//Retires a connection for re-use
static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) {
if (conn->priv->sendBacklog!=NULL) {
HttpSendBacklogItem *i, *j;
i=conn->priv->sendBacklog;
do {
j=i;
i=i->next;
free(j);
} while (i!=NULL);
}
if (conn->post->buff!=NULL) free(conn->post->buff);
if (conn->post!=NULL) free(conn->post);
if (conn->priv!=NULL) free(conn->priv);
if (conn) free(conn);
for (int i=0; i<HTTPD_MAX_CONNECTIONS; i++) {
if (connData[i]==conn) connData[i]=NULL;
}
}
//Stupid li'l helper function that returns the value of a hex char.
static int ICACHE_FLASH_ATTR httpdHexVal(char c) {
if (c>='0' && c<='9') return c-'0';
if (c>='A' && c<='F') return c-'A'+10;
if (c>='a' && c<='f') return c-'a'+10;
return 0;
}
//Decode a percent-encoded value.
//Takes the valLen bytes stored in val, and converts it into at most retLen bytes that
//are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also
//zero-terminates the ret buffer.
int ICACHE_FLASH_ATTR httpdUrlDecode(char *val, int valLen, char *ret, int retLen) {
int s=0, d=0;
int esced=0, escVal=0;
while (s<valLen && d<retLen) {
if (esced==1) {
escVal=httpdHexVal(val[s])<<4;
esced=2;
} else if (esced==2) {
escVal+=httpdHexVal(val[s]);
ret[d++]=escVal;
esced=0;
} else if (val[s]=='%') {
esced=1;
} else if (val[s]=='+') {
ret[d++]=' ';
} else {
ret[d++]=val[s];
}
s++;
}
if (d<retLen) ret[d]=0;
return d;
}
//Find a specific arg in a string of get- or post-data.
//Line is the string of post/get-data, arg is the name of the value to find. The
//zero-terminated result is written in buff, with at most buffLen bytes used. The
//function returns the length of the result, or -1 if the value wasn't found. The
//returned string will be urldecoded already.
int ICACHE_FLASH_ATTR httpdFindArg(char *line, char *arg, char *buff, int buffLen) {
char *p, *e;
if (line==NULL) return -1;
p=line;
while(p!=NULL && *p!='\n' && *p!='\r' && *p!=0) {
// httpd_printf("findArg: %s\n", p);
if (strncmp(p, arg, strlen(arg))==0 && p[strlen(arg)]=='=') {
p+=strlen(arg)+1; //move p to start of value
e=(char*)strstr(p, "&");
if (e==NULL) e=p+strlen(p);
// httpd_printf("findArg: val %s len %d\n", p, (e-p));
return httpdUrlDecode(p, (e-p), buff, buffLen);
}
p=(char*)strstr(p, "&");
if (p!=NULL) p+=1;
}
httpd_printf("Finding %s in %s: Not found :/\n", arg, line);
return -1; //not found
}
//Get the value of a certain header in the HTTP client head
//Returns true when found, false when not found.
int ICACHE_FLASH_ATTR httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen) {
char *p=conn->priv->head;
p=p+strlen(p)+1; //skip GET/POST part
p=p+strlen(p)+1; //skip HTTP part
while (p<(conn->priv->head+conn->priv->headPos)) {
while(*p<=32 && *p!=0) p++; //skip crap at start
//See if this is the header
if (strncmp(p, header, strlen(header))==0 && p[strlen(header)]==':') {
//Skip 'key:' bit of header line
p=p+strlen(header)+1;
//Skip past spaces after the colon
while(*p==' ') p++;
//Copy from p to end
while (*p!=0 && *p!='\r' && *p!='\n' && retLen>1) {
*ret++=*p++;
retLen--;
}
//Zero-terminate string
*ret=0;
//All done :)
return 1;
}
p+=strlen(p)+1; //Skip past end of string and \0 terminator
}
return 0;
}
//Call before calling httpdStartResponse to disable automatically-chosen transfer
//encodings (specifically, for now, chunking) and fall back on Connection: Close.
void ICACHE_FLASH_ATTR httpdDisableTransferEncoding(HttpdConnData *conn) {
conn->priv->flags&=~HFL_CHUNKED;
}
//Start the response headers.
void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code) {
char buff[256];
int l;
l=sprintf(buff, "HTTP/1.%d %d OK\r\nServer: esp8266-httpd/"HTTPDVER"\r\n%s\r\n",
(conn->priv->flags&HFL_HTTP11)?1:0,
code,
(conn->priv->flags&HFL_CHUNKED)?"Transfer-Encoding: chunked":"Connection: close");
httpdSend(conn, buff, l);
}
//Send a http header.
void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val) {
httpdSend(conn, field, -1);
httpdSend(conn, ": ", -1);
httpdSend(conn, val, -1);
httpdSend(conn, "\r\n", -1);
}
//Finish the headers.
void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn) {
httpdSend(conn, "\r\n", -1);
conn->priv->flags|=HFL_SENDINGBODY;
}
//Redirect to the given URL.
void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl) {
httpdStartResponse(conn, 302);
httpdHeader(conn, "Location", newUrl);
httpdEndHeaders(conn);
httpdSend(conn, "Moved to ", -1);
httpdSend(conn, newUrl, -1);
}
//Use this as a cgi function to redirect one url to another.
int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData) {
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdRedirect(connData, (char*)connData->cgiArg);
return HTTPD_CGI_DONE;
}
//Used to spit out a 404 error
static int ICACHE_FLASH_ATTR cgiNotFound(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE;
httpdStartResponse(connData, 404);
httpdEndHeaders(connData);
httpdSend(connData, "404 File not found.", -1);
return HTTPD_CGI_DONE;
}
//This CGI function redirects to a fixed url of http://[hostname]/ if hostname field of request isn't
//already that hostname. Use this in combination with a DNS server that redirects everything to the
//ESP in order to load a HTML page as soon as a phone, tablet etc connects to the ESP. Watch out:
//this will also redirect connections when the ESP is in STA mode, potentially to a hostname that is not
//in the 'official' DNS and so will fail.
int ICACHE_FLASH_ATTR cgiRedirectToHostname(HttpdConnData *connData) {
static const char hostFmt[]="http://%s/";
char *buff;
int isIP=0;
int x;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
if (connData->hostName==NULL) {
httpd_printf("Huh? No hostname.\n");
return HTTPD_CGI_NOTFOUND;
}
//Quick and dirty code to see if host is an IP
if (strlen(connData->hostName)>8) {
isIP=1;
for (x=0; x<strlen(connData->hostName); x++) {
if (connData->hostName[x]!='.' && (connData->hostName[x]<'0' || connData->hostName[x]>'9')) isIP=0;
}
}
if (isIP) return HTTPD_CGI_NOTFOUND;
//Check hostname; pass on if the same
if (strcmp(connData->hostName, (char*)connData->cgiArg)==0) return HTTPD_CGI_NOTFOUND;
//Not the same. Redirect to real hostname.
buff=malloc(strlen((char*)connData->cgiArg)+sizeof(hostFmt));
sprintf(buff, hostFmt, (char*)connData->cgiArg);
httpd_printf("Redirecting to hostname url %s\n", buff);
httpdRedirect(connData, buff);
free(buff);
return HTTPD_CGI_DONE;
}
//Same as above, but will only redirect clients with an IP that is in the range of
//the SoftAP interface. This should preclude clients connected to the STA interface
//to be redirected to nowhere.
int ICACHE_FLASH_ATTR cgiRedirectApClientToHostname(HttpdConnData *connData) {
#ifndef FREERTOS
uint32 *remadr;
struct ip_info apip;
int x=wifi_get_opmode();
//Check if we have an softap interface; bail out if not
if (x!=2 && x!=3) return HTTPD_CGI_NOTFOUND;
remadr=(uint32 *)connData->remote_ip;
wifi_get_ip_info(SOFTAP_IF, &apip);
if ((*remadr & apip.netmask.addr) == (apip.ip.addr & apip.netmask.addr)) {
return cgiRedirectToHostname(connData);
} else {
return HTTPD_CGI_NOTFOUND;
}
#else
return HTTPD_CGI_NOTFOUND;
#endif
}
//Add data to the send buffer. len is the length of the data. If len is -1
//the data is seen as a C-string.
//Returns 1 for success, 0 for out-of-memory.
int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len) {
if (conn->conn==NULL) return 0;
if (len<0) len=strlen(data);
if (len==0) return 0;
if (conn->priv->flags&HFL_CHUNKED && conn->priv->flags&HFL_SENDINGBODY && conn->priv->chunkHdr==NULL) {
if (conn->priv->sendBuffLen+len+6>MAX_SENDBUFF_LEN) return 0;
//Establish start of chunk
conn->priv->chunkHdr=&conn->priv->sendBuff[conn->priv->sendBuffLen];
strcpy(conn->priv->chunkHdr, "0000\r\n");
conn->priv->sendBuffLen+=6;
}
if (conn->priv->sendBuffLen+len>MAX_SENDBUFF_LEN) return 0;
memcpy(conn->priv->sendBuff+conn->priv->sendBuffLen, data, len);
conn->priv->sendBuffLen+=len;
return 1;
}
static char ICACHE_FLASH_ATTR httpdHexNibble(int val) {
val&=0xf;
if (val<10) return '0'+val;
return 'A'+(val-10);
}
//Function to send any data in conn->priv->sendBuff. Do not use in CGIs unless you know what you
//are doing! Also, if you do set conn->cgi to NULL to indicate the connection is closed, do it BEFORE
//calling this.
void ICACHE_FLASH_ATTR httpdFlushSendBuffer(HttpdConnData *conn) {
int r, len;
if (conn->conn==NULL) return;
if (conn->priv->chunkHdr!=NULL) {
//We're sending chunked data, and the chunk needs fixing up.
//Finish chunk with cr/lf
httpdSend(conn, "\r\n", 2);
//Calculate length of chunk
len=((&conn->priv->sendBuff[conn->priv->sendBuffLen])-conn->priv->chunkHdr)-8;
//Fix up chunk header to correct value
conn->priv->chunkHdr[0]=httpdHexNibble(len>>12);
conn->priv->chunkHdr[1]=httpdHexNibble(len>>8);
conn->priv->chunkHdr[2]=httpdHexNibble(len>>4);
conn->priv->chunkHdr[3]=httpdHexNibble(len>>0);
//Reset chunk hdr for next call
conn->priv->chunkHdr=NULL;
}
if (conn->priv->flags&HFL_CHUNKED && conn->priv->flags&HFL_SENDINGBODY && conn->cgi==NULL) {
//Connection finished sending whatever needs to be sent. Add NULL chunk to indicate this.
strcpy(&conn->priv->sendBuff[conn->priv->sendBuffLen], "0\r\n\r\n");
conn->priv->sendBuffLen+=5;
}
if (conn->priv->sendBuffLen!=0) {
r=httpdPlatSendData(conn->conn, conn->priv->sendBuff, conn->priv->sendBuffLen);
if (!r) {
//Can't send this for some reason. Dump packet in backlog, we can send it later.
if (conn->priv->sendBacklogSize+conn->priv->sendBuffLen>MAX_BACKLOG_SIZE) {
httpd_printf("Httpd: Backlog: Exceeded max backlog size, dropped %d bytes instead of sending them.\n", conn->priv->sendBuffLen);
conn->priv->sendBuffLen=0;
return;
}
HttpSendBacklogItem *i=malloc(sizeof(HttpSendBacklogItem)+conn->priv->sendBuffLen);
if (i==NULL) {
httpd_printf("Httpd: Backlog: malloc failed, out of memory!\n");
return;
}
memcpy(i->data, conn->priv->sendBuff, conn->priv->sendBuffLen);
i->len=conn->priv->sendBuffLen;
i->next=NULL;
if (conn->priv->sendBacklog==NULL) {
conn->priv->sendBacklog=i;
} else {
HttpSendBacklogItem *e=conn->priv->sendBacklog;
while (e->next!=NULL) e=e->next;
e->next=i;
}
conn->priv->sendBacklogSize+=conn->priv->sendBuffLen;
}
conn->priv->sendBuffLen=0;
}
}
void ICACHE_FLASH_ATTR httpdCgiIsDone(HttpdConnData *conn) {
conn->cgi=NULL; //no need to call this anymore
if (conn->priv->flags&HFL_CHUNKED) {
httpd_printf("Pool slot %d is done. Cleaning up for next req\n", conn->slot);
httpdFlushSendBuffer(conn);
//Note: Do not clean up sendBacklog, it may still contain data at this point.
conn->priv->headPos=0;
conn->post->len=-1;
conn->priv->flags=0;
if (conn->post->buff) free(conn->post->buff);
conn->post->buff=NULL;
conn->post->buffLen=0;
conn->post->received=0;
conn->hostName=NULL;
} else {
//Cannot re-use this connection. Mark to get it killed after all data is sent.
conn->priv->flags|=HFL_DISCONAFTERSENT;
}
}
//Callback called when the data on a socket has been successfully
//sent.
void ICACHE_FLASH_ATTR httpdSentCb(ConnTypePtr rconn, char *remIp, int remPort) {
int r;
HttpdConnData *conn=httpdFindConnData(rconn, remIp, remPort);
char *sendBuff;
if (conn==NULL) return;
if (conn->priv->sendBacklog!=NULL) {
//We have some backlog to send first.
HttpSendBacklogItem *next=conn->priv->sendBacklog->next;
httpdPlatSendData(conn->conn, conn->priv->sendBacklog->data, conn->priv->sendBacklog->len);
conn->priv->sendBacklogSize-=conn->priv->sendBacklog->len;
free(conn->priv->sendBacklog);
conn->priv->sendBacklog=next;
return;
}
if (conn->priv->flags&HFL_DISCONAFTERSENT) { //Marked for destruction?
httpd_printf("Pool slot %d is done. Closing.\n", conn->slot);
httpdPlatDisconnect(conn->conn);
return; //No need to call httpdFlushSendBuffer.
}
//If we don't have a CGI function, there's nothing to do but wait for something from the client.
if (conn->cgi==NULL) return;
sendBuff=malloc(MAX_SENDBUFF_LEN);
conn->priv->sendBuff=sendBuff;
conn->priv->sendBuffLen=0;
r=conn->cgi(conn); //Execute cgi fn.
if (r==HTTPD_CGI_DONE) {
httpdCgiIsDone(conn);
}
if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) {
httpd_printf("ERROR! CGI fn returns code %d after sending data! Bad CGI!\n", r);
httpdCgiIsDone(conn);
}
httpdFlushSendBuffer(conn);
free(sendBuff);
}
//This is called when the headers have been received and the connection is ready to send
//the result headers and data.
//We need to find the CGI function to call, call it, and dependent on what it returns either
//find the next cgi function, wait till the cgi data is sent or close up the connection.
static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) {
int r;
int i=0;
if (conn->url==NULL) {
httpd_printf("WtF? url = NULL\n");
return; //Shouldn't happen
}
//See if we can find a CGI that's happy to handle the request.
while (1) {
//Look up URL in the built-in URL table.
while (builtInUrls[i].url!=NULL) {
int match=0;
//See if there's a literal match
if (strcmp(builtInUrls[i].url, conn->url)==0) match=1;
//See if there's a wildcard match
if (builtInUrls[i].url[strlen(builtInUrls[i].url)-1]=='*' &&
strncmp(builtInUrls[i].url, conn->url, strlen(builtInUrls[i].url)-1)==0) match=1;
if (match) {
httpd_printf("Is url index %d\n", i);
conn->cgiData=NULL;
conn->cgi=builtInUrls[i].cgiCb;
conn->cgiArg=builtInUrls[i].cgiArg;
break;
}
i++;
}
if (builtInUrls[i].url==NULL) {
//Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just
//generate a built-in 404 to handle this.
httpd_printf("%s not found. 404!\n", conn->url);
conn->cgi=cgiNotFound;
}
//Okay, we have a CGI function that matches the URL. See if it wants to handle the
//particular URL we're supposed to handle.
r=conn->cgi(conn);
if (r==HTTPD_CGI_MORE) {
//Yep, it's happy to do so and has more data to send.
if (conn->recvHdl) {
//Seems the CGI is planning to do some long-term communications with the socket.
//Disable the timeout on it, so we won't run into that.
httpdPlatDisableTimeout(conn->conn);
}
httpdFlushSendBuffer(conn);
return;
} else if (r==HTTPD_CGI_DONE) {
//Yep, it's happy to do so and already is done sending data.
httpdCgiIsDone(conn);
return;
} else if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) {
//URL doesn't want to handle the request: either the data isn't found or there's no
//need to generate a login screen.
i++; //look at next url the next iteration of the loop.
}
}
}
//Parse a line of header data and modify the connection data accordingly.
static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) {
int i;
char firstLine=0;
if (strncmp(h, "GET ", 4)==0) {
conn->requestType = HTTPD_METHOD_GET;
firstLine=1;
} else if (strncmp(h, "Host:", 5)==0) {
i=5;
while (h[i]==' ') i++;
conn->hostName=&h[i];
} else if (strncmp(h, "POST ", 5)==0) {
conn->requestType = HTTPD_METHOD_POST;
firstLine=1;
}
if (firstLine) {
char *e;
//Skip past the space after POST/GET
i=0;
while (h[i]!=' ') i++;
conn->url=h+i+1;
//Figure out end of url.
e=(char*)strstr(conn->url, " ");
if (e==NULL) return; //wtf?
*e=0; //terminate url part
e++; //Skip to protocol indicator
while (*e==' ') e++; //Skip spaces.
//If HTTP/1.1, note that and set chunked encoding
if (strcasecmp(e, "HTTP/1.1")==0) conn->priv->flags|=HFL_HTTP11|HFL_CHUNKED;
httpd_printf("URL = %s\n", conn->url);
//Parse out the URL part before the GET parameters.
conn->getArgs=(char*)strstr(conn->url, "?");
if (conn->getArgs!=0) {
*conn->getArgs=0;
conn->getArgs++;
httpd_printf("GET args = %s\n", conn->getArgs);
} else {
conn->getArgs=NULL;
}
} else if (strncmp(h, "Connection:", 11)==0) {
i=11;
//Skip trailing spaces
while (h[i]==' ') i++;
if (strncmp(&h[i], "close", 5)==0) conn->priv->flags&=~HFL_CHUNKED; //Don't use chunked conn
} else if (strncmp(h, "Content-Length:", 15)==0) {
i=15;
//Skip trailing spaces
while (h[i]==' ') i++;
//Get POST data length
conn->post->len=atoi(h+i);
// Allocate the buffer
if (conn->post->len > MAX_POST) {
// we'll stream this in in chunks
conn->post->buffSize = MAX_POST;
} else {
conn->post->buffSize = conn->post->len;
}
httpd_printf("Mallocced buffer for %d + 1 bytes of post data.\n", conn->post->buffSize);
conn->post->buff=(char*)malloc(conn->post->buffSize + 1);
conn->post->buffLen=0;
} else if (strncmp(h, "Content-Type: ", 14)==0) {
if (strstr(h, "multipart/form-data")) {
// It's multipart form data so let's pull out the boundary for future use
char *b;
if ((b = strstr(h, "boundary=")) != NULL) {
conn->post->multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes
conn->post->multipartBoundary[0] = '-';
conn->post->multipartBoundary[1] = '-';
httpd_printf("boundary = %s\n", conn->post->multipartBoundary);
}
}
}
}
//Callback called when there's data available on a socket.
void httpdRecvCb(ConnTypePtr rconn, char *remIp, int remPort, char *data, unsigned short len) {
int x, r;
char *p, *e;
char *sendBuff=malloc(MAX_SENDBUFF_LEN);
HttpdConnData *conn=httpdFindConnData(rconn, remIp, remPort);
if (conn==NULL) return;
conn->priv->sendBuff=sendBuff;
conn->priv->sendBuffLen=0;
//This is slightly evil/dirty: we abuse conn->post->len as a state variable for where in the http communications we are:
//<0 (-1): Post len unknown because we're still receiving headers
//==0: No post data
//>0: Need to receive post data
//ToDo: See if we can use something more elegant for this.
for (x=0; x<len; x++) {
if (conn->post->len<0) {
//This byte is a header byte.
if (data[x]=='\n') {
//Compatibility with clients that send \n only: fake a \r in front of this.
if (conn->priv->headPos!=0 && conn->priv->head[conn->priv->headPos-1]!='\r') {
conn->priv->head[conn->priv->headPos++]='\r';
}
}
//ToDo: return http error code 431 (request header too long) if this happens
if (conn->priv->headPos!=MAX_HEAD_LEN) conn->priv->head[conn->priv->headPos++]=data[x];
conn->priv->head[conn->priv->headPos]=0;
//Scan for /r/n/r/n. Receiving this indicate the headers end.
if (data[x]=='\n' && (char *)strstr(conn->priv->head, "\r\n\r\n")!=NULL) {
//Indicate we're done with the headers.
conn->post->len=0;
//Reset url data
conn->url=NULL;
//Iterate over all received headers and parse them.
p=conn->priv->head;
while(p<(&conn->priv->head[conn->priv->headPos-4])) {
e=(char *)strstr(p, "\r\n"); //Find end of header line
if (e==NULL) break; //Shouldn't happen.
e[0]=0; //Zero-terminate header
httpdParseHeader(p, conn); //and parse it.
p=e+2; //Skip /r/n (now /0/n)
}
//If we don't need to receive post data, we can send the response now.
if (conn->post->len==0) {
httpdProcessRequest(conn);
}
}
} else if (conn->post->len!=0) {
//This byte is a POST byte.
conn->post->buff[conn->post->buffLen++]=data[x];
conn->post->received++;
conn->hostName=NULL;
if (conn->post->buffLen >= conn->post->buffSize || conn->post->received == conn->post->len) {
//Received a chunk of post data
conn->post->buff[conn->post->buffLen]=0; //zero-terminate, in case the cgi handler knows it can use strings
//Process the data
if (conn->cgi) {
r=conn->cgi(conn);
if (r==HTTPD_CGI_DONE) {
httpdCgiIsDone(conn);
}
} else {
//No CGI fn set yet: probably first call. Allow httpdProcessRequest to choose CGI and
//call it the first time.
httpdProcessRequest(conn);
}
conn->post->buffLen = 0;
}
} else {
//Let cgi handle data if it registered a recvHdl callback. If not, ignore.
if (conn->recvHdl) {
r=conn->recvHdl(conn, data+x, len-x);
if (r==HTTPD_CGI_DONE) {
httpd_printf("Recvhdl returned DONE\n");
httpdCgiIsDone(conn);
//We assume the recvhdlr has sent something; we'll kill the sock in the sent callback.
}
break; //ignore rest of data, recvhdl has parsed it.
} else {
httpd_printf("Eh? Got unexpected data from client. %s\n", data);
}
}
}
if (conn->conn) httpdFlushSendBuffer(conn);
free(sendBuff);
}
//The platform layer should ALWAYS call this function, regardless if the connection is closed by the server
//or by the client.
void ICACHE_FLASH_ATTR httpdDisconCb(ConnTypePtr rconn, char *remIp, int remPort) {
HttpdConnData *hconn=httpdFindConnData(rconn, remIp, remPort);
if (hconn==NULL) return;
httpd_printf("Pool slot %d: socket closed.\n", hconn->slot);
hconn->conn=NULL; //indicate cgi the connection is gone
if (hconn->cgi) hconn->cgi(hconn); //Execute cgi fn if needed
httpdRetireConn(hconn);
}
int ICACHE_FLASH_ATTR httpdConnectCb(ConnTypePtr conn, char *remIp, int remPort) {
int i;
//Find empty conndata in pool
for (i=0; i<HTTPD_MAX_CONNECTIONS; i++) if (connData[i]==NULL) break;
httpd_printf("Conn req from %d.%d.%d.%d:%d, using pool slot %d\n", remIp[0]&0xff, remIp[1]&0xff, remIp[2]&0xff, remIp[3]&0xff, remPort, i);
if (i==HTTPD_MAX_CONNECTIONS) {
httpd_printf("Aiee, conn pool overflow!\n");
return 0;
}
connData[i]=malloc(sizeof(HttpdConnData));
memset(connData[i], 0, sizeof(HttpdConnData));
connData[i]->priv=malloc(sizeof(HttpdPriv));
memset(connData[i]->priv, 0, sizeof(HttpdPriv));
connData[i]->conn=conn;
connData[i]->slot=i;
connData[i]->priv->headPos=0;
connData[i]->post=malloc(sizeof(HttpdPostData));
memset(connData[i]->post, 0, sizeof(HttpdPostData));
connData[i]->post->buff=NULL;
connData[i]->post->buffLen=0;
connData[i]->post->received=0;
connData[i]->post->len=-1;
connData[i]->hostName=NULL;
connData[i]->remote_port=remPort;
connData[i]->priv->sendBacklog=NULL;
connData[i]->priv->sendBacklogSize=0;
memcpy(connData[i]->remote_ip, remIp, 4);
return 1;
}
//Httpd initialization routine. Call this to kick off webserver functionality.
void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) {
int i;
for (i=0; i<HTTPD_MAX_CONNECTIONS; i++) {
connData[i]=NULL;
}
builtInUrls=fixedUrls;
httpdPlatInit(port, HTTPD_MAX_CONNECTIONS);
httpd_printf("Httpd init\n");
}
+221
View File
@@ -0,0 +1,221 @@
/*
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 <esp8266.h>
#include "httpdespfs.h"
#include "espfs.h"
#include "espfsformat.h"
// 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\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n";
EspFsFile *tryOpenIndex(char *buff, const char *path) {
// Try appending index.tpl
size_t url_len = strlen(path);
strcpy(buff, path); // add current path
// add slash if not already ending with slash
if (path[url_len-1] != '/') {
buff[url_len++] = '/';
}
// add index
strcpy(buff + url_len, "index.tpl");
return espFsOpen(buff);
}
//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.
int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) {
EspFsFile *file=connData->cgiData;
int len;
char buff[1024];
char acceptEncodingBuffer[64];
int isGzip;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
espFsClose(file);
return HTTPD_CGI_DONE;
}
if (file==NULL) {
//First call to this cgi. Open the file so we can read it.
file=espFsOpen(connData->url);
if (file==NULL) {
// file not found
file = tryOpenIndex(buff, connData->url);
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);
httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url));
if (isGzip) {
httpdHeader(connData, "Content-Encoding", "gzip");
}
httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate");
httpdEndHeaders(connData);
return HTTPD_CGI_MORE;
}
len=espFsRead(file, buff, 1024);
if (len>0) httpdSend(connData, buff, len);
if (len!=1024) {
//We're done.
espFsClose(file);
return HTTPD_CGI_DONE;
} else {
//Ok, till next time.
return HTTPD_CGI_MORE;
}
}
//cgiEspFsTemplate can be used as a template.
typedef struct {
EspFsFile *file;
void *tplArg;
char token[64];
int tokenPos;
} TplData;
typedef void (* TplCallback)(HttpdConnData *connData, char *token, void **arg);
int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) {
TplData *tpd=connData->cgiData;
int len;
int x, sp=0;
char *e=NULL;
char buff[1025];
if (connData->conn==NULL) {
//Connection aborted. Clean up.
((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg);
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_DONE;
}
if (tpd==NULL) {
//First call to this cgi. Open the file so we can read it.
tpd=(TplData *)malloc(sizeof(TplData));
tpd->file=espFsOpen(connData->url);
if (tpd->file==NULL) {
// file not found
tpd->file = tryOpenIndex(buff, connData->url);
if (tpd->file==NULL) {
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_NOTFOUND;
}
}
tpd->tplArg=NULL;
tpd->tokenPos=-1;
if (espFsFlags(tpd->file) & FLAG_GZIP) {
httpd_printf("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!\n", connData->url);
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_NOTFOUND;
}
connData->cgiData=tpd;
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url));
httpdEndHeaders(connData);
return HTTPD_CGI_MORE;
}
len=espFsRead(tpd->file, buff, 1024);
if (len>0) {
sp=0;
e=buff;
for (x=0; 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 {
//This is an actual token.
tpd->token[tpd->tokenPos++]=0; //zero-terminate token
((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg);
}
//Go collect normal chars again.
e=&buff[x+1];
tpd->tokenPos=-1;
} else {
if (tpd->tokenPos<(sizeof(tpd->token)-1)) tpd->token[tpd->tokenPos++]=buff[x];
}
}
}
}
//Send remaining bit.
if (sp!=0) httpdSend(connData, e, sp);
if (len!=1024) {
//We're done.
((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg);
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_DONE;
} else {
//Ok, till next time.
return HTTPD_CGI_MORE;
}
}
+168
View File
@@ -0,0 +1,168 @@
/* 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 <esp8266.h>
#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 ICACHE_FLASH_ATTR 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 ICACHE_FLASH_ATTR sha1_rol32(uint32_t number, uint8_t bits) {
return ((number << bits) | (number >> (32-bits)));
}
void ICACHE_FLASH_ATTR 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 ICACHE_FLASH_ATTR 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 ICACHE_FLASH_ATTR sha1_writebyte(sha1nfo *s, uint8_t data) {
++s->byteCount;
sha1_addUncounted(s, data);
}
void ICACHE_FLASH_ATTR sha1_write(sha1nfo *s, const char *data, size_t len) {
for (;len--;) sha1_writebyte(s, (uint8_t) *data++);
}
void ICACHE_FLASH_ATTR 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* ICACHE_FLASH_ATTR 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 ICACHE_FLASH_ATTR 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* ICACHE_FLASH_ATTR 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);
}
+269
View File
@@ -0,0 +1,269 @@
/*
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.
* ----------------------------------------------------------------------------
*/
//These routines can also be tested by comping them in with the espfstest tool. This
//simplifies debugging, but needs some slightly different headers. The #ifdef takes
//care of that.
#ifdef __ets__
//esp build
#include <esp8266.h>
#else
//Test build
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define ICACHE_FLASH_ATTR
#endif
#include <esp8266.h>
#include "espfsformat.h"
#include "espfs.h"
#ifdef ESPFS_HEATSHRINK
#include "heatshrink_config_custom.h"
#include "heatshrink_decoder.h"
#endif
static char* espFsData = NULL;
struct EspFsFile {
EspFsHeader *header;
char decompressor;
int32_t posDecomp;
char *posStart;
char *posComp;
void *decompData;
};
/*
Available locations, at least in my flash, with boundaries partially guessed. This
is using 0.9.1/0.9.2 SDK on a not-too-new module.
0x00000 (0x10000): Code/data (RAM data?)
0x10000 (0x02000): Gets erased by something?
0x12000 (0x2E000): Free (filled with zeroes) (parts used by ESPCloud and maybe SSL)
0x40000 (0x20000): Code/data (ROM data?)
0x60000 (0x1C000): Free
0x7c000 (0x04000): Param store
0x80000 - end of flash
Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All accesses
*must* be aligned 32-bit accesses. Reading a short, byte or unaligned word will result in
a memory exception, crashing the program.
*/
EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) {
if((uint32_t)flashAddress > 0x40200000) {
flashAddress = (void*)((uint32_t)flashAddress-0x40200000);
}
// base address must be aligned to 4 bytes
if (((int)flashAddress & 3) != 0) {
return ESPFS_INIT_RESULT_BAD_ALIGN;
}
// check if there is valid header at address
EspFsHeader testHeader;
spi_flash_read((uint32)flashAddress, (uint32*)&testHeader, sizeof(EspFsHeader));
if (testHeader.magic != ESPFS_MAGIC) {
return ESPFS_INIT_RESULT_NO_IMAGE;
}
espFsData = (char *)flashAddress;
return ESPFS_INIT_RESULT_OK;
}
//Copies len bytes over from dst to src, but does it using *only*
//aligned 32-bit reads. Yes, it's no too optimized but it's short and sweet and it works.
//ToDo: perhaps memcpy also does unaligned accesses?
#ifdef __ets__
void ICACHE_FLASH_ATTR readFlashUnaligned(char *dst, char *src, int len) {
uint8_t src_offset = ((uint32_t)src) & 3;
uint32_t src_address = ((uint32_t)src) - src_offset;
uint32_t tmp_buf[len/4 + 2];
spi_flash_read((uint32)src_address, (uint32*)tmp_buf, len+src_offset);
memcpy(dst, ((uint8_t*)tmp_buf)+src_offset, len);
}
#else
#define readFlashUnaligned memcpy
#endif
// Returns flags of opened file.
int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) {
if (fh == NULL) {
httpd_printf("File handle not ready\n");
return -1;
}
int8_t flags;
readFlashUnaligned((char*)&flags, (char*)&fh->header->flags, 1);
return (int)flags;
}
//Open a file and return a pointer to the file desc struct.
EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) {
if (espFsData == NULL) {
httpd_printf("Call espFsInit first!\n");
return NULL;
}
char *p=espFsData;
char *hpos;
char namebuf[256];
EspFsHeader h;
EspFsFile *r;
//Strip initial slashes
while(fileName[0]=='/') fileName++;
//Go find that file!
while(1) {
hpos=p;
//Grab the next file header.
spi_flash_read((uint32)p, (uint32*)&h, sizeof(EspFsHeader));
if (h.magic!=ESPFS_MAGIC) {
httpd_printf("Magic mismatch. EspFS image broken.\n");
return NULL;
}
if (h.flags&FLAG_LASTFILE) {
httpd_printf("End of image.\n");
return NULL;
}
//Grab the name of the file.
p+=sizeof(EspFsHeader);
spi_flash_read((uint32)p, (uint32*)&namebuf, sizeof(namebuf));
// httpd_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n",
// namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags);
if (strcmp(namebuf, fileName)==0) {
//Yay, this is the file we need!
p+=h.nameLen; //Skip to content.
r=(EspFsFile *)malloc(sizeof(EspFsFile)); //Alloc file desc mem
// httpd_printf("Alloc %p\n", r);
if (r==NULL) return NULL;
r->header=(EspFsHeader *)hpos;
r->decompressor=h.compression;
r->posComp=p;
r->posStart=p;
r->posDecomp=0;
if (h.compression==COMPRESS_NONE) {
r->decompData=NULL;
#ifdef ESPFS_HEATSHRINK
} else if (h.compression==COMPRESS_HEATSHRINK) {
//File is compressed with Heatshrink.
char parm;
heatshrink_decoder *dec;
//Decoder params are stored in 1st byte.
readFlashUnaligned(&parm, r->posComp, 1);
r->posComp++;
httpd_printf("Heatshrink compressed file; decode parms = %x\n", parm);
dec=heatshrink_decoder_alloc(16, (parm>>4)&0xf, parm&0xf);
r->decompData=dec;
#endif
} else {
httpd_printf("Invalid compression: %d\n", h.compression);
return NULL;
}
return r;
}
//We don't need this file. Skip name and file
p+=h.nameLen+h.fileLenComp;
if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val
}
}
//Read len bytes from the given file into buff. Returns the actual amount of bytes read.
int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) {
int flen, fdlen;
if (fh==NULL) return 0;
readFlashUnaligned((char*)&flen, (char*)&fh->header->fileLenComp, 4);
//Cache file length.
//Do stuff depending on the way the file is compressed.
if (fh->decompressor==COMPRESS_NONE) {
int toRead;
toRead=flen-(fh->posComp-fh->posStart);
if (len>toRead) len=toRead;
// httpd_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp);
readFlashUnaligned(buff, fh->posComp, len);
fh->posDecomp+=len;
fh->posComp+=len;
// httpd_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp);
return len;
#ifdef ESPFS_HEATSHRINK
} else if (fh->decompressor==COMPRESS_HEATSHRINK) {
readFlashUnaligned((char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4);
int decoded=0;
size_t elen, rlen;
char ebuff[16];
heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData;
// httpd_printf("Alloc %p\n", dec);
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) {
readFlashUnaligned(ebuff, fh->posComp, 16);
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;
// httpd_printf("Elen %d rlen %d d %d pd %ld fdl %d\n",elen,rlen,decoded, fh->posDecomp, fdlen);
if (elen == 0) {
if (fh->posDecomp == fdlen) {
// httpd_printf("Decoder finish\n");
heatshrink_decoder_finish(dec);
}
return decoded;
}
}
return len;
#endif
}
return 0;
}
//Close the file.
void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) {
if (fh==NULL) return;
#ifdef ESPFS_HEATSHRINK
if (fh->decompressor==COMPRESS_HEATSHRINK) {
heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData;
heatshrink_decoder_free(dec);
// httpd_printf("Freed %p\n", dec);
}
#endif
// httpd_printf("Freed %p\n", fh);
free(fh);
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef ESPROFSFORMAT_H
#define ESPROFSFORMAT_H
/*
Stupid cpio-like tool to make read-only 'filesystems' that live on the flash SPI chip of the module.
Can (will) use lzf compression (when I come around to it) to make shit quicker. Aligns names, files,
headers on 4-byte boundaries so the SPI abstraction hardware in the ESP8266 doesn't crap on itself
when trying to do a <4byte or unaligned read.
*/
/*
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.
*/
#define FLAG_LASTFILE (1<<0)
#define FLAG_GZIP (1<<1)
#define COMPRESS_NONE 0
#define COMPRESS_HEATSHRINK 1
#define ESPFS_MAGIC 0x73665345
typedef struct {
int32_t magic;
int8_t flags;
int8_t compression;
int16_t nameLen;
int32_t fileLenComp;
int32_t fileLenDecomp;
} __attribute__((packed)) EspFsHeader;
#endif
+13
View File
@@ -0,0 +1,13 @@
CFLAGS=-I../../lib/heatshrink -I.. -std=gnu99 -DESPFS_HEATSHRINK
espfstest: main.o espfs.o heatshrink_decoder.o
$(CC) -o $@ $^
espfs.o: ../espfs.c
$(CC) $(CFLAGS) -c $^ -o $@
heatshrink_decoder.o: ../heatshrink_decoder.c
$(CC) $(CFLAGS) -c $^ -o $@
clean:
rm -f *.o espfstest
+67
View File
@@ -0,0 +1,67 @@
/*
Simple and stupid file decompressor for an espfs image. Mostly used as a testbed for espfs.c and
the decompressors: code compiled natively is way easier to debug using gdb et all :)
*/
#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include "espfs.h"
char *espFsData;
int main(int argc, char **argv) {
int f, out;
int len;
char buff[128];
EspFsFile *ef;
off_t size;
EspFsInitResult ir;
if (argc!=3) {
printf("Usage: %s espfs-image file\nExpands file from the espfs-image archive.\n", argv[0]);
exit(0);
}
f=open(argv[1], O_RDONLY);
if (f<=0) {
perror(argv[1]);
exit(1);
}
size=lseek(f, 0, SEEK_END);
espFsData=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0);
if (espFsData==MAP_FAILED) {
perror("mmap");
exit(1);
}
ir=espFsInit(espFsData);
if (ir != ESPFS_INIT_RESULT_OK) {
printf("Couldn't init espfs filesystem (code %d)\n", ir);
exit(1);
}
ef=espFsOpen(argv[2]);
if (ef==NULL) {
printf("Couldn't find %s in image.\n", argv[2]);
exit(1);
}
out=open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (out<=0) {
perror(argv[2]);
exit(1);
}
while ((len=espFsRead(ef, buff, 128))!=0) {
write(out, buff, len);
}
espFsClose(ef);
//munmap, close, ... I can't be bothered.
}
@@ -0,0 +1,25 @@
//Heatshrink config for the decompressor.
#ifndef HEATSHRINK_CONFIG_H
#define HEATSHRINK_CONFIG_H
/* Should functionality assuming dynamic allocation be used? */
#define HEATSHRINK_DYNAMIC_ALLOC 1
#if HEATSHRINK_DYNAMIC_ALLOC
/* Optional replacement of malloc/free */
#define HEATSHRINK_MALLOC(SZ) malloc(SZ)
#define HEATSHRINK_FREE(P, SZ) free(P)
#else
/* Required parameters for static configuration */
#define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32
#define HEATSHRINK_STATIC_WINDOW_BITS 8
#define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4
#endif
/* Turn on logging for debugging. */
#define HEATSHRINK_DEBUGGING_LOGS 0
/* Use indexing for faster compression. (This requires additional space.) */
#define HEATSHRINK_USE_INDEX 1
#endif
+17
View File
@@ -0,0 +1,17 @@
#include "espfs.h"
#ifdef ESPFS_HEATSHRINK
//Stupid wrapper so we don't have to move c-files around
//Also loads httpd-specific config.
#ifdef __ets__
//esp build
#include <esp8266.h>
#endif
#include "heatshrink_config_custom.h"
#include "../lib/heatshrink/heatshrink_decoder.c"
#endif
+24
View File
@@ -0,0 +1,24 @@
GZIP_COMPRESSION ?= no
USE_HEATSHRINK ?= yes
CFLAGS=-I../../lib/heatshrink -I../../include -I.. -std=gnu99
ifeq ("$(GZIP_COMPRESSION)","yes")
CFLAGS += -DESPFS_GZIP
endif
ifeq ("$(USE_HEATSHRINK)","yes")
CFLAGS += -DESPFS_HEATSHRINK
endif
OBJS=main.o heatshrink_encoder.o
TARGET=mkespfsimage
$(TARGET): $(OBJS)
ifeq ("$(GZIP_COMPRESSION)","yes")
$(CC) -o $@ $^ -lz
else
$(CC) -o $@ $^
endif
clean:
rm -f $(TARGET) $(OBJS)
@@ -0,0 +1,4 @@
//Stupid wraparound include to make sure object file doesn't end up in heatshrink dir
#ifdef ESPFS_HEATSHRINK
#include "../lib/heatshrink/heatshrink_encoder.c"
#endif
+362
View File
@@ -0,0 +1,362 @@
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include "espfs.h"
#include "espfsformat.h"
//Heatshrink
#ifdef ESPFS_HEATSHRINK
#include "heatshrink_common.h"
#include "heatshrink_config.h"
#include "heatshrink_encoder.h"
#endif
//Gzip
#ifdef ESPFS_GZIP
// If compiler complains about missing header, try running "sudo apt-get install zlib1g-dev"
// to install missing package.
#include <zlib.h>
#endif
//Routines to convert host format to the endianness used in the xtensa
short htoxs(short in) {
char r[2];
r[0]=in;
r[1]=in>>8;
return *((short *)r);
}
int htoxl(int in) {
unsigned char r[4];
r[0]=in;
r[1]=in>>8;
r[2]=in>>16;
r[3]=in>>24;
return *((int *)r);
}
#ifdef ESPFS_HEATSHRINK
size_t compressHeatshrink(char *in, int insize, char *out, int outsize, int level) {
char *inp=in;
char *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++; outsize--;
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, outsize, &len);
if (pres!=HSER_POLL_MORE && pres!=HSER_POLL_EMPTY) break;
outp+=len; outsize-=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", insize, sres, pres);
exit(1);
}
heatshrink_encoder_free(enc);
return r;
}
#endif
#ifdef ESPFS_GZIP
size_t compressGzip(char *in, int insize, char *out, int 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;
}
char **gzipExtensions = NULL;
int shouldCompressGzip(char *name) {
char *ext = name + strlen(name);
while (*ext != '.') {
ext--;
if (ext < name) {
// no dot in file name -> no extension -> nothing to match against
return 0;
}
}
ext++;
int i = 0;
while (gzipExtensions[i] != NULL) {
if (strcmp(ext,gzipExtensions[i]) == 0) {
return 1;
}
i++;
}
return 0;
}
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;
gzipExtensions = malloc(count * sizeof(char*));
count = 0;
token = strtok(extList, ",");
while (token) {
gzipExtensions[count++] = token;
token = strtok(NULL, ",");
}
// terminate list
gzipExtensions[count] = NULL;
return 1;
}
#endif
int handleFile(int f, char *name, int compression, int level, char **compName) {
char *fdat, *cdat;
off_t size, csize;
EspFsHeader h;
int nameLen;
int8_t flags = 0;
size=lseek(f, 0, SEEK_END);
fdat=malloc(size);
lseek(f, 0, SEEK_SET);
read(f, fdat, size);
#ifdef ESPFS_GZIP
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=malloc(csize);
csize=compressGzip(fdat, size, cdat, csize, level);
compression = COMPRESS_NONE;
flags = FLAG_GZIP;
} else
#endif
if (compression==COMPRESS_NONE) {
csize=size;
cdat=fdat;
#ifdef ESPFS_HEATSHRINK
} else if (compression==COMPRESS_HEATSHRINK) {
cdat=malloc(size*2);
csize=compressHeatshrink(fdat, size, cdat, size*2, level);
#endif
} else {
fprintf(stderr, "Unknown compression - %d\n", compression);
exit(1);
}
if (csize>size) {
//Compressing enbiggened this file. Revert to uncompressed store.
compression=COMPRESS_NONE;
csize=size;
cdat=fdat;
flags=0;
}
//Fill header data
h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24);
h.flags=flags;
h.compression=compression;
h.nameLen=nameLen=strlen(name)+1;
if (h.nameLen&3) h.nameLen+=4-(h.nameLen&3); //Round to next 32bit boundary
h.nameLen=htoxs(h.nameLen);
h.fileLenComp=htoxl(csize);
h.fileLenDecomp=htoxl(size);
write(1, &h, sizeof(EspFsHeader));
write(1, name, nameLen);
while (nameLen&3) {
write(1, "\000", 1);
nameLen++;
}
write(1, cdat, csize);
//Pad out to 32bit boundary
while (csize&3) {
write(1, "\000", 1);
csize++;
}
free(fdat);
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";
}
}
return (csize*100)/size;
}
//Write final dummy header with FLAG_LASTFILE set.
void finishArchive() {
EspFsHeader h;
h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24);
h.flags=FLAG_LASTFILE;
h.compression=COMPRESS_NONE;
h.nameLen=htoxs(0);
h.fileLenComp=htoxl(0);
h.fileLenDecomp=htoxl(0);
write(1, &h, sizeof(EspFsHeader));
}
int main(int argc, char **argv) {
int f, x;
char fileName[1024];
char *realName;
struct stat statBuf;
int serr;
int rate;
int err=0;
int compType; //default compression type - heatshrink
int compLvl=-1;
#ifdef ESPFS_HEATSHRINK
compType = COMPRESS_HEATSHRINK;
#else
compType = COMPRESS_NONE;
#endif
for (x=1; x<argc; x++) {
if (strcmp(argv[x], "-c")==0 && argc>=x-2) {
compType=atoi(argv[x+1]);
x++;
} else if (strcmp(argv[x], "-l")==0 && argc>=x-2) {
compLvl=atoi(argv[x+1]);
if (compLvl<1 || compLvl>9) err=1;
x++;
#ifdef ESPFS_GZIP
} else if (strcmp(argv[x], "-g")==0 && argc>=x-2) {
if (!parseGzipExtensions(argv[x+1])) err=1;
x++;
#endif
} else {
err=1;
}
}
#ifdef ESPFS_GZIP
if (gzipExtensions == NULL) {
parseGzipExtensions(strdup("html,css,js,svg"));
}
#endif
if (err) {
fprintf(stderr, "%s - Program to create espfs images\n", argv[0]);
fprintf(stderr, "Usage: \nfind | %s [-c compressor] [-l compression_level] ", argv[0]);
#ifdef ESPFS_GZIP
fprintf(stderr, "[-g gzipped_extensions] ");
#endif
fprintf(stderr, "> out.espfs\n");
fprintf(stderr, "Compressors:\n");
#ifdef ESPFS_HEATSHRINK
fprintf(stderr, "0 - None\n1 - Heatshrink(default)\n");
#else
fprintf(stderr, "0 - None(default)\n");
#endif
fprintf(stderr, "\nCompression level: 1 is worst but low RAM usage, higher is better compression \nbut uses more ram on decompression. -1 = compressors default.\n");
#ifdef ESPFS_GZIP
fprintf(stderr, "\nGzipped extensions: list of comma separated, case sensitive file extensions \nthat will be gzipped. Defaults to 'html,css,js'\n");
#endif
exit(0);
}
while(fgets(fileName, sizeof(fileName), stdin)) {
//Kill off '\n' at the end
fileName[strlen(fileName)-1]=0;
//Only include files
serr=stat(fileName, &statBuf);
if ((serr==0) && S_ISREG(statBuf.st_mode)) {
//Strip off './' or '/' madness.
realName=fileName;
if (fileName[0]=='.') realName++;
if (realName[0]=='/') realName++;
f=open(fileName, O_RDONLY);
if (f>0) {
char *compName = "unknown";
rate=handleFile(f, realName, compType, compLvl, &compName);
fprintf(stderr, "%s (%d%%, %s)\n", realName, rate, compName);
close(f);
} else {
perror(fileName);
}
} else {
if (serr!=0) {
perror(fileName);
}
}
}
finishArchive();
return 0;
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef AUTH_H
#define AUTH_H
#include "httpd.h"
#include <esp8266.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);
int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData);
#endif
+5
View File
@@ -0,0 +1,5 @@
#ifndef CAPTDNS_H
#define CAPTDNS_H
void ICACHE_FLASH_ATTR captdnsInit(void);
#endif
+22
View File
@@ -0,0 +1,22 @@
#ifndef CGIFLASH_H
#define CGIFLASH_H
#include "httpd.h"
#define CGIFLASH_TYPE_FW 0
#define CGIFLASH_TYPE_ESPFS 1
typedef struct {
int type;
int fw1Pos;
int fw2Pos;
int fwSize;
char *tagName;
} CgiUploadFlashDef;
int cgiReadFlash(HttpdConnData *connData);
int cgiGetFirmwareNext(HttpdConnData *connData);
int cgiUploadFirmware(HttpdConnData *connData);
int cgiRebootFirmware(HttpdConnData *connData);
#endif
+37
View File
@@ -0,0 +1,37 @@
#ifndef CGIWEBSOCKET_H
#define CGIWEBSOCKET_H
#include "httpd.h"
#define WEBSOCK_FLAG_NONE 0
#define WEBSOCK_FLAG_CONT (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
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;
};
int ICACHE_FLASH_ATTR cgiWebsocket(HttpdConnData *connData);
int ICACHE_FLASH_ATTR cgiWebsocketSend(Websock *ws, char *data, int len, int flags);
void ICACHE_FLASH_ATTR cgiWebsocketClose(Websock *ws, int reason);
int ICACHE_FLASH_ATTR cgiWebSocketRecv(HttpdConnData *connData, char *data, int len);
int ICACHE_FLASH_ATTR cgiWebsockBroadcast(char *resource, char *data, int len, int flags);
#endif
+13
View File
@@ -0,0 +1,13 @@
#ifndef CGIWIFI_H
#define CGIWIFI_H
#include "httpd.h"
int cgiWiFiScan(HttpdConnData *connData);
int tplWlan(HttpdConnData *connData, char *token, void **arg);
int cgiWiFi(HttpdConnData *connData);
int cgiWiFiConnect(HttpdConnData *connData);
int cgiWiFiSetMode(HttpdConnData *connData);
int cgiWiFiConnStatus(HttpdConnData *connData);
#endif
+27
View File
@@ -0,0 +1,27 @@
// Combined include file for esp8266
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef FREERTOS
#include <stdint.h>
#include <espressif/esp_common.h>
#else
#include <c_types.h>
#include <ip_addr.h>
#include <espconn.h>
#include <ets_sys.h>
#include <gpio.h>
#include <mem.h>
#include <osapi.h>
#include <user_interface.h>
#include <upgrade.h>
#endif
#include "platform.h"
#include "espmissingincludes.h"
+23
View File
@@ -0,0 +1,23 @@
#ifndef ESPFS_H
#define ESPFS_H
// This define is done in Makefile. If you do not use default Makefile, uncomment
// to be able to use Heatshrink-compressed espfs images.
//#define ESPFS_HEATSHRINK
typedef enum {
ESPFS_INIT_RESULT_OK,
ESPFS_INIT_RESULT_NO_IMAGE,
ESPFS_INIT_RESULT_BAD_ALIGN,
} EspFsInitResult;
typedef struct EspFsFile EspFsFile;
EspFsInitResult espFsInit(void *flashAddress);
EspFsFile *espFsOpen(char *fileName);
int espFsFlags(EspFsFile *fh);
int espFsRead(EspFsFile *fh, char *buff, int len);
void espFsClose(EspFsFile *fh);
#endif
+76
View File
@@ -0,0 +1,76 @@
#ifndef ESPMISSINGINCLUDES_H
#define ESPMISSINGINCLUDES_H
#include <stdint.h>
#include <c_types.h>
int strcasecmp(const char *a, const char *b);
#ifndef FREERTOS
#include <eagle_soc.h>
#include <ets_sys.h>
//Missing function prototypes in include folders. Gcc will warn on these if we don't define 'em anywhere.
//MOST OF THESE ARE GUESSED! but they seem to swork and shut up the compiler.
typedef struct espconn espconn;
int atoi(const char *nptr);
void ets_install_putc1(void *routine);
void ets_isr_attach(int intr, void *handler, void *arg);
void ets_isr_mask(unsigned intr);
void ets_isr_unmask(unsigned intr);
int ets_memcmp(const void *s1, const void *s2, size_t n);
void *ets_memcpy(void *dest, const void *src, size_t n);
void *ets_memset(void *s, int c, size_t n);
int ets_sprintf(char *str, const char *format, ...) __attribute__ ((format (printf, 2, 3)));
int ets_str2macaddr(void *, void *);
int ets_strcmp(const char *s1, const char *s2);
char *ets_strcpy(char *dest, const char *src);
size_t ets_strlen(const char *s);
int ets_strncmp(const char *s1, const char *s2, int len);
char *ets_strncpy(char *dest, const char *src, size_t n);
char *ets_strstr(const char *haystack, const char *needle);
void ets_timer_arm_new(os_timer_t *a, int b, int c, int isMstimer);
void ets_timer_disarm(os_timer_t *a);
void ets_timer_setfn(os_timer_t *t, ETSTimerFunc *fn, void *parg);
void ets_update_cpu_frequency(int freqmhz);
int os_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
int os_snprintf(char *str, size_t size, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
int os_printf_plus(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
void uart_div_modify(int no, unsigned int freq);
uint8 wifi_get_opmode(void);
uint32 system_get_time();
int rand(void);
void ets_bzero(void *s, size_t n);
void ets_delay_us(int ms);
//Hack: this is defined in SDK 1.4.0 and undefined in 1.3.0. It's only used for this, the symbol itself
//has no meaning here.
#ifndef RC_LIMIT_P2P_11N
//Defs for SDK <1.4.0
void *pvPortMalloc(size_t xWantedSize);
void *pvPortZalloc(size_t);
void vPortFree(void *ptr);
void *vPortMalloc(size_t xWantedSize);
void pvPortFree(void *ptr);
#else
void *pvPortMalloc(size_t xWantedSize, const char *file, int line);
void *pvPortZalloc(size_t, const char *file, int line);
void vPortFree(void *ptr, const char *file, int line);
void *vPortMalloc(size_t xWantedSize, const char *file, int line);
void pvPortFree(void *ptr, const char *file, int line);
#endif
//Standard PIN_FUNC_SELECT gives a warning. Replace by a non-warning one.
#ifdef PIN_FUNC_SELECT
#undef PIN_FUNC_SELECT
#define PIN_FUNC_SELECT(PIN_NAME, FUNC) do { \
WRITE_PERI_REG(PIN_NAME, \
(READ_PERI_REG(PIN_NAME) \
& (~(PERIPHS_IO_MUX_FUNC<<PERIPHS_IO_MUX_FUNC_S))) \
|( (((FUNC&BIT2)<<2)|(FUNC&0x3))<<PERIPHS_IO_MUX_FUNC_S) ); \
} while (0)
#endif
#endif
#endif
+83
View File
@@ -0,0 +1,83 @@
#ifndef HTTPD_H
#define HTTPD_H
#include <esp8266.h>
#define HTTPDVER "0.4"
#define HTTPD_CGI_MORE 0
#define HTTPD_CGI_DONE 1
#define HTTPD_CGI_NOTFOUND 2
#define HTTPD_CGI_AUTHENTICATED 3
#define HTTPD_METHOD_GET 1
#define HTTPD_METHOD_POST 2
typedef struct HttpdPriv HttpdPriv;
typedef struct HttpdConnData HttpdConnData;
typedef struct HttpdPostData HttpdPostData;
typedef int (* cgiSendCallback)(HttpdConnData *connData);
typedef int (* cgiRecvHandler)(HttpdConnData *connData, char *data, int len);
//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.
char 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.
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 remote_ip[4]; // IP address of client
uint8 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;
} HttpdBuiltInUrl;
int cgiRedirect(HttpdConnData *connData);
int cgiRedirectToHostname(HttpdConnData *connData);
int cgiRedirectApClientToHostname(HttpdConnData *connData);
void httpdRedirect(HttpdConnData *conn, char *newUrl);
int httpdUrlDecode(char *val, int valLen, char *ret, int retLen);
int httpdFindArg(char *line, char *arg, char *buff, int buffLen);
void httpdInit(HttpdBuiltInUrl *fixedUrls, int port);
const char *httpdGetMimetype(char *url);
void httpdDisableTransferEncoding(HttpdConnData *conn);
void httpdStartResponse(HttpdConnData *conn, int code);
void httpdHeader(HttpdConnData *conn, const char *field, const char *val);
void httpdEndHeaders(HttpdConnData *conn);
int httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen);
int httpdSend(HttpdConnData *conn, const char *data, int len);
void httpdFlushSendBuffer(HttpdConnData *conn);
//Platform dependent code should call these.
void httpdSentCb(ConnTypePtr conn, char *remIp, int remPort);
void httpdRecvCb(ConnTypePtr conn, char *remIp, int remPort, char *data, unsigned short len);
void httpdDisconCb(ConnTypePtr conn, char *remIp, int remPort);
int httpdConnectCb(ConnTypePtr conn, char *remIp, int remPort);
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef HTTPDESPFS_H
#define HTTPDESPFS_H
#include "httpd.h"
int cgiEspFsHook(HttpdConnData *connData);
int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData);
#endif
+33
View File
@@ -0,0 +1,33 @@
#ifndef PLATFORM_H
#define PLATFORM_H
#ifdef FREERTOS
//#include "esp_timer.h"
typedef struct RtosConnType RtosConnType;
typedef RtosConnType* ConnTypePtr;
#define httpd_printf(fmt, ...) do { \
static const char flash_str[] ICACHE_RODATA_ATTR STORE_ATTR = fmt; \
printf(flash_str, ##__VA_ARGS__); \
} while(0)
#else
#define printf(...) os_printf(__VA_ARGS__)
#define sprintf(str, ...) os_sprintf(str, __VA_ARGS__)
#define strcpy(a, b) os_strcpy(a, b)
#define strncpy(a, b, c) os_strncpy(a, b, c)
#define strcmp(a, b) os_strcmp(a, b)
#define strncmp(a, b, c) os_strncmp(a, b, c)
#define malloc(x) os_malloc(x)
#define free(x) os_free(x)
#define memset(x, a, b) os_memset(x, a, b)
#define memcpy(x, a, b) os_memcpy(x, a, b)
#define strcat(a, b) os_strcat(a, b)
#define strstr(a, b) os_strstr(a, b)
#define strlen(a) os_strlen(a)
#define memcmp(a, b, c) os_memcmp(a, b, c)
typedef struct espconn* ConnTypePtr;
#define httpd_printf(format, ...) os_printf(format, ##__VA_ARGS__)
#endif
#endif
+39
View File
@@ -0,0 +1,39 @@
/* header */
#ifndef __SHA1_H__
#define __SHA1_H__
#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);
#endif
+1
View File
@@ -0,0 +1 @@
+3
View File
@@ -0,0 +1,3 @@
extern char webpages_espfs_start[];
extern char webpages_espfs_end[];
extern int webpages_espfs_size;
+1
View File
@@ -0,0 +1 @@
gitdir: ../../../.git/modules/libesphttpd/modules/lib/heatshrink
+6
View File
@@ -0,0 +1,6 @@
heatshrink
test_heatshrink_dynamic
test_heatshrink_static
*.o
*.core
*.dSYM
+9
View File
@@ -0,0 +1,9 @@
language: c
compiler:
- clang
- gcc
install: make test_heatshrink_dynamic
script: ./test_heatshrink_dynamic
+14
View File
@@ -0,0 +1,14 @@
Copyright (c) 2013, Scott Vokes <scott.vokes@atomicobject.com>
All rights reserved.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+49
View File
@@ -0,0 +1,49 @@
PROJECT = heatshrink
#OPTIMIZE = -O0
#OPTIMIZE = -Os
OPTIMIZE = -O3
WARN = -Wall -Wextra -pedantic #-Werror
CFLAGS += -std=c99 -g ${WARN} ${OPTIMIZE}
CFLAGS += -Wmissing-prototypes
CFLAGS += -Wstrict-prototypes
CFLAGS += -Wmissing-declarations
# If libtheft is available, build additional property-based tests.
# Uncomment these to use it in test_heatshrink_dynamic.
#CFLAGS += -DHEATSHRINK_HAS_THEFT
#LDFLAGS += -ltheft
all:
@echo "For tests, make test_heatshrink_dynamic (default) or change the"
@echo "config.h to disable static memory and build test_heatshrink_static."
@echo "For the standalone command-line tool, make heatshrink."
${PROJECT}: heatshrink.c
OBJS= heatshrink_encoder.o \
heatshrink_decoder.o \
heatshrink: ${OBJS}
test_heatshrink_dynamic: ${OBJS} test_heatshrink_dynamic_theft.o
test_heatshrink_static: ${OBJS}
*.o: Makefile heatshrink_config.h
heatshrink_decoder.o: heatshrink_decoder.h heatshrink_common.h
heatshrink_encoder.o: heatshrink_encoder.h heatshrink_common.h
tags: TAGS
TAGS:
etags *.[ch]
diagrams: dec_sm.png enc_sm.png
dec_sm.png: dec_sm.dot
dot -o $@ -Tpng $<
enc_sm.png: enc_sm.dot
dot -o $@ -Tpng $<
clean:
rm -f ${PROJECT} test_heatshrink_{dynamic,static} *.o *.core {dec,enc}_sm.png TAGS
+49
View File
@@ -0,0 +1,49 @@
# heatshrink
A data compression/decompression library for embedded/real-time systems.
## Key Features:
- **Low memory usage (as low as 50 bytes)**
It is useful for some cases with less than 50 bytes, and useful
for many general cases with < 300 bytes.
- **Incremental, bounded CPU use**
You can chew on input data in arbitrarily tiny bites.
This is a useful property in hard real-time environments.
- **Can use either static or dynamic memory allocation**
The library doesn't impose any constraints on memory management.
- **ISC license**
You can use it freely, even for commercial purposes.
## Getting Started:
There is a standalone command-line program, `heatshrink`, but the
encoder and decoder can also be used as libraries, independent of each
other. To do so, copy `heatshrink_common.h`, `heatshrink_config.h`, and
either `heatshrink_encoder.c` or `heatshrink_decoder.c` (and their
respective header) into your project.
Dynamic allocation is used by default, but in an embedded context, you
probably want to statically allocate the encoder/decoder. Set
`HEATSHRINK_DYNAMIC_ALLOC` to 0 in `heatshrink_config.h`.
## More Information and Benchmarks:
heatshrink is based on [LZSS], since it's particularly suitable for
compression in small amounts of memory. It can use an optional, small
[index] to make compression significantly faster, but otherwise can run
in under 100 bytes of memory. The index currently adds 2^(window size+1)
bytes to memory usage for compression, and temporarily allocates 512
bytes on the stack during index construction.
For more information, see the [blog post] for an overview, and the
`heatshrink_encoder.h` / `heatshrink_decoder.h` header files for API
documentation.
[blog post]: http://spin.atomicobject.com/2013/03/14/heatshrink-embedded-data-compression/
[index]: http://spin.atomicobject.com/2014/01/13/lightweight-indexing-for-embedded-systems/
[LZSS]: http://en.wikipedia.org/wiki/Lempel-Ziv-Storer-Szymanski
## Build Status
[![Build Status](https://travis-ci.org/atomicobject/heatshrink.png)](http://travis-ci.org/atomicobject/heatshrink)
+52
View File
@@ -0,0 +1,52 @@
digraph {
graph [label="Decoder state machine", labelloc="t"]
Start [style="invis", shape="point"]
empty
input_available
yield_literal
backref_index_msb
backref_index_lsb
backref_count_msb
backref_count_lsb
yield_backref
check_for_more_input
done [peripheries=2]
empty->input_available [label="sink()", color="blue", weight=10]
Start->empty
input_available->yield_literal [label="pop 1-bit"]
input_available->backref_index_msb [label="pop 0-bit", weight=10]
input_available->backref_index_lsb [label="pop 0-bit, index <8 bits", weight=10]
yield_literal->yield_literal [label="sink()", color="blue"]
yield_literal->yield_literal [label="poll()", color="red"]
yield_literal->check_for_more_input [label="poll(), done", color="red"]
backref_index_msb->backref_index_msb [label="sink()", color="blue"]
backref_index_msb->backref_index_lsb [label="pop index, upper bits", weight=10]
backref_index_msb->done [label="finish()", color="blue"]
backref_index_lsb->backref_index_lsb [label="sink()", color="blue"]
backref_index_lsb->backref_count_msb [label="pop index, lower bits", weight=10]
backref_index_lsb->backref_count_lsb [label="pop index, count <=8 bits", weight=10]
backref_index_lsb->done [label="finish()", color="blue"]
backref_count_msb->backref_count_msb [label="sink()", color="blue"]
backref_count_msb->backref_count_lsb [label="pop count, upper bits", weight=10]
backref_count_msb->done [label="finish()", color="blue"]
backref_count_lsb->backref_count_lsb [label="sink()", color="blue"]
backref_count_lsb->yield_backref [label="pop count, lower bits", weight=10]
backref_count_lsb->done [label="finish()", color="blue"]
yield_backref->yield_backref [label="sink()", color="blue"]
yield_backref->yield_backref [label="poll()", color="red"]
yield_backref->check_for_more_input [label="poll(), done",
color="red", weight=10]
check_for_more_input->empty [label="no"]
check_for_more_input->input_available [label="yes"]
empty->done [label="finish()", color="blue"]
}
+51
View File
@@ -0,0 +1,51 @@
digraph {
graph [label="Encoder state machine", labelloc="t"]
start [style="invis", shape="point"]
not_full
filled
search
yield_tag_bit
yield_literal
yield_br_length
yield_br_index
save_backlog
flush_bits
done [peripheries=2]
start->not_full [label="start"]
not_full->not_full [label="sink(), not full", color="blue"]
not_full->filled [label="sink(), buffer is full", color="blue"]
not_full->filled [label="finish(), set is_finished", color="blue"]
filled->search [label="indexing (if any)"]
search->search [label="step"]
search->yield_tag_bit [label="literal"]
search->yield_tag_bit [label="match found"]
search->save_backlog [label="input exhausted"]
yield_tag_bit->yield_tag_bit [label="poll(), full buf", color="red"]
yield_tag_bit->yield_literal [label="poll(), literal", color="red"]
yield_tag_bit->yield_br_index [label="poll(), no literal", color="red"]
yield_tag_bit->flush_bits [label="finishing, no literal"]
yield_literal->yield_literal [label="poll(), full buf", color="red"]
yield_literal->search [label="poll(), no match", color="red"]
yield_literal->yield_tag_bit [label="poll(), match", color="red"]
yield_literal->flush_bits [label="poll(), final literal", color="red"]
yield_br_index->yield_br_index [label="poll(), full buf", color="red"]
yield_br_index->yield_br_length [label="poll()", color="red"]
yield_br_length->yield_br_length [label="poll(), full buf", color="red"]
yield_br_length->search [label="done"]
save_backlog->flush_bits [label="finishing, no literal"]
save_backlog->yield_tag_bit [label="finishing, literal"]
save_backlog->not_full [label="expect more input"]
flush_bits->flush_bits [label="poll(), full buf", color="red"]
flush_bits->done [label="poll(), flushed", color="red"]
flush_bits->done [label="no more output"]
}
+591
View File
@@ -0,0 +1,591 @@
/*
* Copyright (c) 2011 Scott Vokes <vokes.s@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef GREATEST_H
#define GREATEST_H
#define GREATEST_VERSION_MAJOR 0
#define GREATEST_VERSION_MINOR 9
#define GREATEST_VERSION_PATCH 3
/* A unit testing system for C, contained in 1 file.
* It doesn't use dynamic allocation or depend on anything
* beyond ANSI C89. */
/*********************************************************************
* Minimal test runner template
*********************************************************************/
#if 0
#include "greatest.h"
TEST foo_should_foo() {
PASS();
}
static void setup_cb(void *data) {
printf("setup callback for each test case\n");
}
static void teardown_cb(void *data) {
printf("teardown callback for each test case\n");
}
SUITE(suite) {
/* Optional setup/teardown callbacks which will be run before/after
* every test case in the suite.
* Cleared when the suite finishes. */
SET_SETUP(setup_cb, voidp_to_callback_data);
SET_TEARDOWN(teardown_cb, voidp_to_callback_data);
RUN_TEST(foo_should_foo);
}
/* Add all the definitions that need to be in the test runner's main file. */
GREATEST_MAIN_DEFS();
int main(int argc, char **argv) {
GREATEST_MAIN_BEGIN(); /* command-line arguments, initialization. */
RUN_SUITE(suite);
GREATEST_MAIN_END(); /* display results */
}
#endif
/*********************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
/***********
* Options *
***********/
/* Default column width for non-verbose output. */
#ifndef GREATEST_DEFAULT_WIDTH
#define GREATEST_DEFAULT_WIDTH 72
#endif
/* FILE *, for test logging. */
#ifndef GREATEST_STDOUT
#define GREATEST_STDOUT stdout
#endif
/* Remove GREATEST_ prefix from most commonly used symbols? */
#ifndef GREATEST_USE_ABBREVS
#define GREATEST_USE_ABBREVS 1
#endif
/*********
* Types *
*********/
/* Info for the current running suite. */
typedef struct greatest_suite_info {
unsigned int tests_run;
unsigned int passed;
unsigned int failed;
unsigned int skipped;
/* timers, pre/post running suite and individual tests */
clock_t pre_suite;
clock_t post_suite;
clock_t pre_test;
clock_t post_test;
} greatest_suite_info;
/* Type for a suite function. */
typedef void (greatest_suite_cb)(void);
/* Types for setup/teardown callbacks. If non-NULL, these will be run
* and passed the pointer to their additional data. */
typedef void (greatest_setup_cb)(void *udata);
typedef void (greatest_teardown_cb)(void *udata);
typedef enum {
GREATEST_FLAG_VERBOSE = 0x01,
GREATEST_FLAG_FIRST_FAIL = 0x02,
GREATEST_FLAG_LIST_ONLY = 0x04
} GREATEST_FLAG;
typedef struct greatest_run_info {
unsigned int flags;
unsigned int tests_run; /* total test count */
/* Overall pass/fail/skip counts. */
unsigned int passed;
unsigned int failed;
unsigned int skipped;
/* currently running test suite */
greatest_suite_info suite;
/* info to print about the most recent failure */
const char *fail_file;
unsigned int fail_line;
const char *msg;
/* current setup/teardown hooks and userdata */
greatest_setup_cb *setup;
void *setup_udata;
greatest_teardown_cb *teardown;
void *teardown_udata;
/* formatting info for ".....s...F"-style output */
unsigned int col;
unsigned int width;
/* only run a specific suite or test */
char *suite_filter;
char *test_filter;
/* overall timers */
clock_t begin;
clock_t end;
} greatest_run_info;
/* Global var for the current testing context.
* Initialized by GREATEST_MAIN_DEFS(). */
extern greatest_run_info greatest_info;
/**********************
* Exported functions *
**********************/
void greatest_do_pass(const char *name);
void greatest_do_fail(const char *name);
void greatest_do_skip(const char *name);
int greatest_pre_test(const char *name);
void greatest_post_test(const char *name, int res);
void greatest_usage(const char *name);
void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata);
void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata);
/**********
* Macros *
**********/
/* Define a suite. */
#define GREATEST_SUITE(NAME) void NAME(void)
/* Start defining a test function.
* The arguments are not included, to allow parametric testing. */
#define GREATEST_TEST static int
/* Run a suite. */
#define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME)
/* Run a test in the current suite. */
#define GREATEST_RUN_TEST(TEST) \
do { \
if (greatest_pre_test(#TEST) == 1) { \
int res = TEST(); \
greatest_post_test(#TEST, res); \
} else if (GREATEST_LIST_ONLY()) { \
fprintf(GREATEST_STDOUT, " %s\n", #TEST); \
} \
} while (0)
/* Run a test in the current suite with one void* argument,
* which can be a pointer to a struct with multiple arguments. */
#define GREATEST_RUN_TEST1(TEST, ENV) \
do { \
if (greatest_pre_test(#TEST) == 1) { \
int res = TEST(ENV); \
greatest_post_test(#TEST, res); \
} else if (GREATEST_LIST_ONLY()) { \
fprintf(GREATEST_STDOUT, " %s\n", #TEST); \
} \
} while (0)
/* If __VA_ARGS__ (C99) is supported, allow parametric testing
* without needing to manually manage the argument struct. */
#if __STDC_VERSION__ >= 19901L
#define GREATEST_RUN_TESTp(TEST, ...) \
do { \
if (greatest_pre_test(#TEST) == 1) { \
int res = TEST(__VA_ARGS__); \
greatest_post_test(#TEST, res); \
} else if (GREATEST_LIST_ONLY()) { \
fprintf(GREATEST_STDOUT, " %s\n", #TEST); \
} \
} while (0)
#endif
/* Check if the test runner is in verbose mode. */
#define GREATEST_IS_VERBOSE() (greatest_info.flags & GREATEST_FLAG_VERBOSE)
#define GREATEST_LIST_ONLY() (greatest_info.flags & GREATEST_FLAG_LIST_ONLY)
#define GREATEST_FIRST_FAIL() (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL)
#define GREATEST_FAILURE_ABORT() (greatest_info.suite.failed > 0 && GREATEST_FIRST_FAIL())
/* Message-less forms. */
#define GREATEST_PASS() GREATEST_PASSm(NULL)
#define GREATEST_FAIL() GREATEST_FAILm(NULL)
#define GREATEST_SKIP() GREATEST_SKIPm(NULL)
#define GREATEST_ASSERT(COND) GREATEST_ASSERTm(#COND, COND)
#define GREATEST_ASSERT_FALSE(COND) GREATEST_ASSERT_FALSEm(#COND, COND)
#define GREATEST_ASSERT_EQ(EXP, GOT) GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT)
#define GREATEST_ASSERT_STR_EQ(EXP, GOT) GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT)
/* The following forms take an additional message argument first,
* to be displayed by the test runner. */
/* Fail if a condition is not true, with message. */
#define GREATEST_ASSERTm(MSG, COND) \
do { \
greatest_info.msg = MSG; \
greatest_info.fail_file = __FILE__; \
greatest_info.fail_line = __LINE__; \
if (!(COND)) return -1; \
greatest_info.msg = NULL; \
} while (0)
#define GREATEST_ASSERT_FALSEm(MSG, COND) \
do { \
greatest_info.msg = MSG; \
greatest_info.fail_file = __FILE__; \
greatest_info.fail_line = __LINE__; \
if ((COND)) return -1; \
greatest_info.msg = NULL; \
} while (0)
#define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \
do { \
greatest_info.msg = MSG; \
greatest_info.fail_file = __FILE__; \
greatest_info.fail_line = __LINE__; \
if ((EXP) != (GOT)) return -1; \
greatest_info.msg = NULL; \
} while (0)
#define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \
do { \
const char *exp_s = (EXP); \
const char *got_s = (GOT); \
greatest_info.msg = MSG; \
greatest_info.fail_file = __FILE__; \
greatest_info.fail_line = __LINE__; \
if (0 != strcmp(exp_s, got_s)) { \
fprintf(GREATEST_STDOUT, \
"Expected:\n####\n%s\n####\n", exp_s); \
fprintf(GREATEST_STDOUT, \
"Got:\n####\n%s\n####\n", got_s); \
return -1; \
} \
greatest_info.msg = NULL; \
} while (0)
#define GREATEST_PASSm(MSG) \
do { \
greatest_info.msg = MSG; \
return 0; \
} while (0)
#define GREATEST_FAILm(MSG) \
do { \
greatest_info.fail_file = __FILE__; \
greatest_info.fail_line = __LINE__; \
greatest_info.msg = MSG; \
return -1; \
} while (0)
#define GREATEST_SKIPm(MSG) \
do { \
greatest_info.msg = MSG; \
return 1; \
} while (0)
#define GREATEST_SET_TIME(NAME) \
NAME = clock(); \
if (NAME == (clock_t) -1) { \
fprintf(GREATEST_STDOUT, \
"clock error: %s\n", #NAME); \
exit(EXIT_FAILURE); \
}
#define GREATEST_CLOCK_DIFF(C1, C2) \
fprintf(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \
(long unsigned int) (C2) - (C1), \
(double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) \
/* Include several function definitions in the main test file. */
#define GREATEST_MAIN_DEFS() \
\
/* Is FILTER a subset of NAME? */ \
static int greatest_name_match(const char *name, \
const char *filter) { \
size_t offset = 0; \
size_t filter_len = strlen(filter); \
while (name[offset] != '\0') { \
if (name[offset] == filter[0]) { \
if (0 == strncmp(&name[offset], filter, filter_len)) { \
return 1; \
} \
} \
offset++; \
} \
\
return 0; \
} \
\
int greatest_pre_test(const char *name) { \
if (!GREATEST_LIST_ONLY() \
&& (!GREATEST_FIRST_FAIL() || greatest_info.suite.failed == 0) \
&& (greatest_info.test_filter == NULL || \
greatest_name_match(name, greatest_info.test_filter))) { \
GREATEST_SET_TIME(greatest_info.suite.pre_test); \
if (greatest_info.setup) { \
greatest_info.setup(greatest_info.setup_udata); \
} \
return 1; /* test should be run */ \
} else { \
return 0; /* skipped */ \
} \
} \
\
void greatest_post_test(const char *name, int res) { \
GREATEST_SET_TIME(greatest_info.suite.post_test); \
if (greatest_info.teardown) { \
void *udata = greatest_info.teardown_udata; \
greatest_info.teardown(udata); \
} \
\
if (res < 0) { \
greatest_do_fail(name); \
} else if (res > 0) { \
greatest_do_skip(name); \
} else if (res == 0) { \
greatest_do_pass(name); \
} \
greatest_info.suite.tests_run++; \
greatest_info.col++; \
if (GREATEST_IS_VERBOSE()) { \
GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \
greatest_info.suite.post_test); \
fprintf(GREATEST_STDOUT, "\n"); \
} else if (greatest_info.col % greatest_info.width == 0) { \
fprintf(GREATEST_STDOUT, "\n"); \
greatest_info.col = 0; \
} \
if (GREATEST_STDOUT == stdout) fflush(stdout); \
} \
\
static void greatest_run_suite(greatest_suite_cb *suite_cb, \
const char *suite_name) { \
if (greatest_info.suite_filter && \
!greatest_name_match(suite_name, greatest_info.suite_filter)) \
return; \
if (GREATEST_FIRST_FAIL() && greatest_info.failed > 0) return; \
greatest_info.suite.tests_run = 0; \
greatest_info.suite.failed = 0; \
greatest_info.suite.passed = 0; \
greatest_info.suite.skipped = 0; \
greatest_info.suite.pre_suite = 0; \
greatest_info.suite.post_suite = 0; \
greatest_info.suite.pre_test = 0; \
greatest_info.suite.post_test = 0; \
greatest_info.col = 0; \
fprintf(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \
GREATEST_SET_TIME(greatest_info.suite.pre_suite); \
suite_cb(); \
GREATEST_SET_TIME(greatest_info.suite.post_suite); \
if (greatest_info.suite.tests_run > 0) { \
fprintf(GREATEST_STDOUT, \
"\n%u tests - %u pass, %u fail, %u skipped", \
greatest_info.suite.tests_run, \
greatest_info.suite.passed, \
greatest_info.suite.failed, \
greatest_info.suite.skipped); \
GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \
greatest_info.suite.post_suite); \
fprintf(GREATEST_STDOUT, "\n"); \
} \
greatest_info.setup = NULL; \
greatest_info.setup_udata = NULL; \
greatest_info.teardown = NULL; \
greatest_info.teardown_udata = NULL; \
greatest_info.passed += greatest_info.suite.passed; \
greatest_info.failed += greatest_info.suite.failed; \
greatest_info.skipped += greatest_info.suite.skipped; \
greatest_info.tests_run += greatest_info.suite.tests_run; \
} \
\
void greatest_do_pass(const char *name) { \
if (GREATEST_IS_VERBOSE()) { \
fprintf(GREATEST_STDOUT, "PASS %s: %s", \
name, greatest_info.msg ? greatest_info.msg : ""); \
} else { \
fprintf(GREATEST_STDOUT, "."); \
} \
greatest_info.suite.passed++; \
} \
\
void greatest_do_fail(const char *name) { \
if (GREATEST_IS_VERBOSE()) { \
fprintf(GREATEST_STDOUT, \
"FAIL %s: %s (%s:%u)", \
name, greatest_info.msg ? greatest_info.msg : "", \
greatest_info.fail_file, greatest_info.fail_line); \
} else { \
fprintf(GREATEST_STDOUT, "F"); \
/* add linebreak if in line of '.'s */ \
if (greatest_info.col % greatest_info.width != 0) \
fprintf(GREATEST_STDOUT, "\n"); \
greatest_info.col = 0; \
fprintf(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \
name, \
greatest_info.msg ? greatest_info.msg : "", \
greatest_info.fail_file, greatest_info.fail_line); \
} \
greatest_info.suite.failed++; \
} \
\
void greatest_do_skip(const char *name) { \
if (GREATEST_IS_VERBOSE()) { \
fprintf(GREATEST_STDOUT, "SKIP %s: %s", \
name, \
greatest_info.msg ? \
greatest_info.msg : "" ); \
} else { \
fprintf(GREATEST_STDOUT, "s"); \
} \
greatest_info.suite.skipped++; \
} \
\
void greatest_usage(const char *name) { \
fprintf(GREATEST_STDOUT, \
"Usage: %s [-hlfv] [-s SUITE] [-t TEST]\n" \
" -h print this Help\n" \
" -l List suites and their tests, then exit\n" \
" -f Stop runner after first failure\n" \
" -v Verbose output\n" \
" -s SUITE only run suite named SUITE\n" \
" -t TEST only run test named TEST\n", \
name); \
} \
\
void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \
greatest_info.setup = cb; \
greatest_info.setup_udata = udata; \
} \
\
void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \
void *udata) { \
greatest_info.teardown = cb; \
greatest_info.teardown_udata = udata; \
} \
\
greatest_run_info greatest_info
/* Handle command-line arguments, etc. */
#define GREATEST_MAIN_BEGIN() \
do { \
int i = 0; \
memset(&greatest_info, 0, sizeof(greatest_info)); \
if (greatest_info.width == 0) { \
greatest_info.width = GREATEST_DEFAULT_WIDTH; \
} \
for (i = 1; i < argc; i++) { \
if (0 == strcmp("-t", argv[i])) { \
if (argc <= i + 1) { \
greatest_usage(argv[0]); \
exit(EXIT_FAILURE); \
} \
greatest_info.test_filter = argv[i+1]; \
i++; \
} else if (0 == strcmp("-s", argv[i])) { \
if (argc <= i + 1) { \
greatest_usage(argv[0]); \
exit(EXIT_FAILURE); \
} \
greatest_info.suite_filter = argv[i+1]; \
i++; \
} else if (0 == strcmp("-f", argv[i])) { \
greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \
} else if (0 == strcmp("-v", argv[i])) { \
greatest_info.flags |= GREATEST_FLAG_VERBOSE; \
} else if (0 == strcmp("-l", argv[i])) { \
greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; \
} else if (0 == strcmp("-h", argv[i])) { \
greatest_usage(argv[0]); \
exit(EXIT_SUCCESS); \
} else { \
fprintf(GREATEST_STDOUT, \
"Unknown argument '%s'\n", argv[i]); \
greatest_usage(argv[0]); \
exit(EXIT_FAILURE); \
} \
} \
} while (0); \
GREATEST_SET_TIME(greatest_info.begin)
#define GREATEST_MAIN_END() \
do { \
if (!GREATEST_LIST_ONLY()) { \
GREATEST_SET_TIME(greatest_info.end); \
fprintf(GREATEST_STDOUT, \
"\nTotal: %u tests", greatest_info.tests_run); \
GREATEST_CLOCK_DIFF(greatest_info.begin, \
greatest_info.end); \
fprintf(GREATEST_STDOUT, "\n"); \
fprintf(GREATEST_STDOUT, \
"Pass: %u, fail: %u, skip: %u.\n", \
greatest_info.passed, \
greatest_info.failed, greatest_info.skipped); \
} \
return (greatest_info.failed > 0 \
? EXIT_FAILURE : EXIT_SUCCESS); \
} while (0)
/* Make abbreviations without the GREATEST_ prefix for the
* most commonly used symbols. */
#if GREATEST_USE_ABBREVS
#define TEST GREATEST_TEST
#define SUITE GREATEST_SUITE
#define RUN_TEST GREATEST_RUN_TEST
#define RUN_TEST1 GREATEST_RUN_TEST1
#define RUN_SUITE GREATEST_RUN_SUITE
#define ASSERT GREATEST_ASSERT
#define ASSERTm GREATEST_ASSERTm
#define ASSERT_FALSE GREATEST_ASSERT_FALSE
#define ASSERT_EQ GREATEST_ASSERT_EQ
#define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ
#define ASSERT_FALSEm GREATEST_ASSERT_FALSEm
#define ASSERT_EQm GREATEST_ASSERT_EQm
#define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm
#define PASS GREATEST_PASS
#define FAIL GREATEST_FAIL
#define SKIP GREATEST_SKIP
#define PASSm GREATEST_PASSm
#define FAILm GREATEST_FAILm
#define SKIPm GREATEST_SKIPm
#define SET_SETUP GREATEST_SET_SETUP_CB
#define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB
#if __STDC_VERSION__ >= 19901L
#endif /* C99 */
#define RUN_TESTp GREATEST_RUN_TESTp
#endif /* USE_ABBREVS */
#endif
+446
View File
@@ -0,0 +1,446 @@
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include <err.h>
#include <fcntl.h>
#include "heatshrink_encoder.h"
#include "heatshrink_decoder.h"
#define DEF_WINDOW_SZ2 11
#define DEF_LOOKAHEAD_SZ2 4
#define DEF_DECODER_INPUT_BUFFER_SIZE 256
#define DEF_BUFFER_SIZE (64 * 1024)
#if 0
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#else
#define LOG(...) /* NO-OP */
#endif
static const int version_major = HEATSHRINK_VERSION_MAJOR;
static const int version_minor = HEATSHRINK_VERSION_MINOR;
static const int version_patch = HEATSHRINK_VERSION_PATCH;
static const char author[] = HEATSHRINK_AUTHOR;
static const char url[] = HEATSHRINK_URL;
static void usage(void) {
fprintf(stderr, "heatshrink version %u.%u.%u by %s\n",
version_major, version_minor, version_patch, author);
fprintf(stderr, "Home page: %s\n\n", url);
fprintf(stderr,
"Usage:\n"
" heatshrink [-h] [-e|-d] [-v] [-w SIZE] [-l BITS] [IN_FILE] [OUT_FILE]\n"
"\n"
"heatshrink compresses or uncompresses byte streams using LZSS, and is\n"
"designed especially for embedded, low-memory, and/or hard real-time\n"
"systems.\n"
"\n"
" -h print help\n"
" -e encode (compress, default)\n"
" -d decode (uncompress)\n"
" -v verbose (print input & output sizes, compression ratio, etc.)\n"
"\n"
" -w SIZE Base-2 log of LZSS sliding window size\n"
"\n"
" A larger value allows searches a larger history of the data for repeated\n"
" patterns, potentially compressing more effectively, but will use\n"
" more memory and processing time.\n"
" Recommended default: -w 8 (embedded systems), -w 10 (elsewhere)\n"
" \n"
" -l BITS Number of bits used for back-reference lengths\n"
"\n"
" A larger value allows longer substitutions, but since all\n"
" back-references must use -w + -l bits, larger -w or -l can be\n"
" counterproductive if most patterns are small and/or local.\n"
" Recommended default: -l 4\n"
"\n"
" If IN_FILE or OUT_FILE are unspecified, they will default to\n"
" \"-\" for standard input and standard output, respectively.\n");
exit(1);
}
typedef enum { IO_READ, IO_WRITE, } IO_mode;
typedef enum { OP_ENC, OP_DEC, } Operation;
typedef struct {
int fd; /* file descriptor */
IO_mode mode;
size_t fill; /* fill index */
size_t read; /* read index */
size_t size;
size_t total;
uint8_t buf[];
} io_handle;
typedef struct {
uint8_t window_sz2;
uint8_t lookahead_sz2;
size_t decoder_input_buffer_size;
size_t buffer_size;
uint8_t verbose;
Operation cmd;
char *in_fname;
char *out_fname;
io_handle *in;
io_handle *out;
} config;
static void die(char *msg) {
fprintf(stderr, "%s\n", msg);
exit(EXIT_FAILURE);
}
static void report(config *cfg);
/* Open an IO handle. Returns NULL on error. */
static io_handle *handle_open(char *fname, IO_mode m, size_t buf_sz) {
io_handle *io = NULL;
io = malloc(sizeof(*io) + buf_sz);
if (io == NULL) { return NULL; }
memset(io, 0, sizeof(*io) + buf_sz);
io->fd = -1;
io->size = buf_sz;
io->mode = m;
if (m == IO_READ) {
if (0 == strcmp("-", fname)) {
io->fd = STDIN_FILENO;
} else {
io->fd = open(fname, O_RDONLY);
}
} else if (m == IO_WRITE) {
if (0 == strcmp("-", fname)) {
io->fd = STDOUT_FILENO;
} else {
io->fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC /*| O_EXCL*/, 0644);
}
}
if (io->fd == -1) { /* failed to open */
free(io);
err(1, "open");
return NULL;
}
return io;
}
/* Read SIZE bytes from an IO handle and return a pointer to the content.
* BUF contains at least size_t bytes. Returns 0 on EOF, -1 on error. */
static ssize_t handle_read(io_handle *io, size_t size, uint8_t **buf) {
LOG("@ read %zd\n", size);
if (buf == NULL) { return -1; }
if (size > io->size) {
printf("size %zd, io->size %zd\n", size, io->size);
return -1;
}
if (io->mode != IO_READ) { return -1; }
size_t rem = io->fill - io->read;
if (rem >= size) {
*buf = &io->buf[io->read];
return size;
} else { /* read and replenish */
if (io->fd == -1) { /* already closed, return what we've got */
*buf = &io->buf[io->read];
return rem;
}
memmove(io->buf, &io->buf[io->read], rem);
io->fill -= io->read;
io->read = 0;
ssize_t read_sz = read(io->fd, &io->buf[io->fill], io->size - io->fill);
if (read_sz < 0) { err(1, "read"); }
io->total += read_sz;
if (read_sz == 0) { /* EOF */
if (close(io->fd) < 0) { err(1, "close"); }
io->fd = -1;
}
io->fill += read_sz;
*buf = io->buf;
return io->fill > size ? size : io->fill;
}
}
/* Drop the oldest SIZE bytes from the buffer. Returns <0 on error. */
static int handle_drop(io_handle *io, size_t size) {
LOG("@ drop %zd\n", size);
if (io->read + size <= io->fill) {
io->read += size;
} else {
return -1;
}
if (io->read == io->fill) {
io->read = 0;
io->fill = 0;
}
return 0;
}
/* Sink SIZE bytes from INPUT into the io handle. Returns the number of
* bytes written, or -1 on error. */
static ssize_t handle_sink(io_handle *io, size_t size, uint8_t *input) {
LOG("@ sink %zd\n", size);
if (size > io->size) { return -1; }
if (io->mode != IO_WRITE) { return -1; }
if (io->fill + size > io->size) {
ssize_t written = write(io->fd, io->buf, io->fill);
LOG("@ flushing %zd, wrote %zd\n", io->fill, written);
io->total += written;
if (written == -1) { err(1, "write"); }
memmove(io->buf, &io->buf[written], io->fill - written);
io->fill -= written;
}
memcpy(&io->buf[io->fill], input, size);
io->fill += size;
return size;
}
static void handle_close(io_handle *io) {
if (io->fd != -1) {
if (io->mode == IO_WRITE) {
ssize_t written = write(io->fd, io->buf, io->fill);
io->total += written;
LOG("@ close: flushing %zd, wrote %zd\n", io->fill, written);
if (written == -1) { err(1, "write"); }
}
close(io->fd);
io->fd = -1;
}
}
static void close_and_report(config *cfg) {
handle_close(cfg->in);
handle_close(cfg->out);
if (cfg->verbose) { report(cfg); }
free(cfg->in);
free(cfg->out);
}
static int encoder_sink_read(config *cfg, heatshrink_encoder *hse,
uint8_t *data, size_t data_sz) {
size_t out_sz = 4096;
uint8_t out_buf[out_sz];
memset(out_buf, 0, out_sz);
size_t sink_sz = 0;
size_t poll_sz = 0;
HSE_sink_res sres;
HSE_poll_res pres;
HSE_finish_res fres;
io_handle *out = cfg->out;
size_t sunk = 0;
do {
if (data_sz > 0) {
sres = heatshrink_encoder_sink(hse, &data[sunk], data_sz - sunk, &sink_sz);
if (sres < 0) { die("sink"); }
sunk += sink_sz;
}
do {
pres = heatshrink_encoder_poll(hse, out_buf, out_sz, &poll_sz);
if (pres < 0) { die("poll"); }
if (handle_sink(out, poll_sz, out_buf) < 0) die("handle_sink");
} while (pres == HSER_POLL_MORE);
if (poll_sz == 0 && data_sz == 0) {
fres = heatshrink_encoder_finish(hse);
if (fres < 0) { die("finish"); }
if (fres == HSER_FINISH_DONE) { return 1; }
}
} while (sunk < data_sz);
return 0;
}
static int encode(config *cfg) {
uint8_t window_sz2 = cfg->window_sz2;
size_t window_sz = 1 << window_sz2;
heatshrink_encoder *hse = heatshrink_encoder_alloc(window_sz2, cfg->lookahead_sz2);
if (hse == NULL) { die("failed to init encoder: bad settings"); }
ssize_t read_sz = 0;
io_handle *in = cfg->in;
/* Process input until end of stream */
while (1) {
uint8_t *input = NULL;
read_sz = handle_read(in, window_sz, &input);
if (input == NULL) {
printf("handle read failure\n");
die("read");
}
if (read_sz < 0) { die("read"); }
/* Pass read to encoder and check if input is fully processed. */
if (encoder_sink_read(cfg, hse, input, read_sz)) break;
if (handle_drop(in, read_sz) < 0) { die("drop"); }
};
if (read_sz == -1) { err(1, "read"); }
heatshrink_encoder_free(hse);
close_and_report(cfg);
return 0;
}
static int decoder_sink_read(config *cfg, heatshrink_decoder *hsd,
uint8_t *data, size_t data_sz) {
io_handle *out = cfg->out;
size_t sink_sz = 0;
size_t poll_sz = 0;
size_t out_sz = 4096;
uint8_t out_buf[out_sz];
memset(out_buf, 0, out_sz);
HSD_sink_res sres;
HSD_poll_res pres;
HSD_finish_res fres;
size_t sunk = 0;
do {
if (data_sz > 0) {
sres = heatshrink_decoder_sink(hsd, &data[sunk], data_sz - sunk, &sink_sz);
if (sres < 0) { die("sink"); }
sunk += sink_sz;
}
do {
pres = heatshrink_decoder_poll(hsd, out_buf, out_sz, &poll_sz);
if (pres < 0) { die("poll"); }
if (handle_sink(out, poll_sz, out_buf) < 0) die("handle_sink");
} while (pres == HSDR_POLL_MORE);
if (data_sz == 0 && poll_sz == 0) {
fres = heatshrink_decoder_finish(hsd);
if (fres < 0) { die("finish"); }
if (fres == HSDR_FINISH_DONE) { return 1; }
}
} while (sunk < data_sz);
return 0;
}
static int decode(config *cfg) {
uint8_t window_sz2 = cfg->window_sz2;
size_t window_sz = 1 << window_sz2;
size_t ibs = cfg->decoder_input_buffer_size;
heatshrink_decoder *hsd = heatshrink_decoder_alloc(ibs,
window_sz2, cfg->lookahead_sz2);
if (hsd == NULL) { die("failed to init decoder"); }
ssize_t read_sz = 0;
io_handle *in = cfg->in;
HSD_finish_res fres;
/* Process input until end of stream */
while (1) {
uint8_t *input = NULL;
read_sz = handle_read(in, window_sz, &input);
if (input == NULL) {
printf("handle read failure\n");
die("read");
}
if (read_sz == 0) {
fres = heatshrink_decoder_finish(hsd);
if (fres < 0) { die("finish"); }
if (fres == HSDR_FINISH_DONE) break;
} else if (read_sz < 0) {
die("read");
} else {
if (decoder_sink_read(cfg, hsd, input, read_sz)) { break; }
if (handle_drop(in, read_sz) < 0) { die("drop"); }
}
}
if (read_sz == -1) { err(1, "read"); }
heatshrink_decoder_free(hsd);
close_and_report(cfg);
return 0;
}
static void report(config *cfg) {
size_t inb = cfg->in->total;
size_t outb = cfg->out->total;
fprintf(cfg->out->fd == STDOUT_FILENO ? stderr : stdout,
"%s %0.2f %%\t %zd -> %zd (-w %u -l %u)\n",
cfg->in_fname, 100.0 - (100.0 * outb) / inb, inb, outb,
cfg->window_sz2, cfg->lookahead_sz2);
}
static void proc_args(config *cfg, int argc, char **argv) {
cfg->window_sz2 = DEF_WINDOW_SZ2;
cfg->lookahead_sz2 = DEF_LOOKAHEAD_SZ2;
cfg->buffer_size = DEF_BUFFER_SIZE;
cfg->decoder_input_buffer_size = DEF_DECODER_INPUT_BUFFER_SIZE;
cfg->cmd = OP_ENC;
cfg->verbose = 0;
cfg->in_fname = "-";
cfg->out_fname = "-";
int a = 0;
while ((a = getopt(argc, argv, "hedi:w:l:v")) != -1) {
switch (a) {
case 'h': /* help */
usage();
case 'e': /* encode */
cfg->cmd = OP_ENC; break;
case 'd': /* decode */
cfg->cmd = OP_DEC; break;
case 'i': /* input buffer size */
cfg->decoder_input_buffer_size = atoi(optarg);
break;
case 'w': /* window bits */
cfg->window_sz2 = atoi(optarg);
break;
case 'l': /* lookahead bits */
cfg->lookahead_sz2 = atoi(optarg);
break;
case 'v': /* verbosity++ */
cfg->verbose++;
break;
case '?': /* unknown argument */
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc > 0) {
cfg->in_fname = argv[0];
argc--;
argv++;
}
if (argc > 0) { cfg->out_fname = argv[0]; }
}
int main(int argc, char **argv) {
config cfg;
memset(&cfg, 0, sizeof(cfg));
proc_args(&cfg, argc, argv);
if (0 == strcmp(cfg.in_fname, cfg.out_fname)
&& (0 != strcmp("-", cfg.in_fname))) {
printf("Refusing to overwrite file '%s' with itself.\n", cfg.in_fname);
exit(1);
}
cfg.in = handle_open(cfg.in_fname, IO_READ, cfg.buffer_size);
if (cfg.in == NULL) { die("Failed to open input file for read"); }
cfg.out = handle_open(cfg.out_fname, IO_WRITE, cfg.buffer_size);
if (cfg.out == NULL) { die("Failed to open output file for write"); }
if (cfg.cmd == OP_ENC) {
return encode(&cfg);
} else if (cfg.cmd == OP_DEC) {
return decode(&cfg);
} else {
usage();
}
}
@@ -0,0 +1,20 @@
#ifndef HEATSHRINK_H
#define HEATSHRINK_H
#define HEATSHRINK_AUTHOR "Scott Vokes <scott.vokes@atomicobject.com>"
#define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink"
/* Version 0.3.1 */
#define HEATSHRINK_VERSION_MAJOR 0
#define HEATSHRINK_VERSION_MINOR 3
#define HEATSHRINK_VERSION_PATCH 1
#define HEATSHRINK_MIN_WINDOW_BITS 4
#define HEATSHRINK_MAX_WINDOW_BITS 15
#define HEATSHRINK_MIN_LOOKAHEAD_BITS 2
#define HEATSHRINK_LITERAL_MARKER 0x01
#define HEATSHRINK_BACKREF_MARKER 0x00
#endif
@@ -0,0 +1,24 @@
#ifndef HEATSHRINK_CONFIG_H
#define HEATSHRINK_CONFIG_H
/* Should functionality assuming dynamic allocation be used? */
#define HEATSHRINK_DYNAMIC_ALLOC 1
#if HEATSHRINK_DYNAMIC_ALLOC
/* Optional replacement of malloc/free */
#define HEATSHRINK_MALLOC(SZ) malloc(SZ)
#define HEATSHRINK_FREE(P, SZ) free(P)
#else
/* Required parameters for static configuration */
#define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32
#define HEATSHRINK_STATIC_WINDOW_BITS 8
#define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4
#endif
/* Turn on logging for debugging. */
#define HEATSHRINK_DEBUGGING_LOGS 0
/* Use indexing for faster compression. (This requires additional space.) */
#define HEATSHRINK_USE_INDEX 1
#endif
@@ -0,0 +1,382 @@
#include <stdlib.h>
#include <string.h>
#include "heatshrink_decoder.h"
/* States for the polling state machine. */
typedef enum {
HSDS_EMPTY, /* no input to process */
HSDS_INPUT_AVAILABLE, /* new input, completely unprocessed */
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 */
HSDS_CHECK_FOR_MORE_INPUT, /* check if input is exhausted */
} 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[] = {
"empty",
"input_available",
"yield_literal",
"backref_index",
"backref_count",
"yield_backref",
"check_for_more_input",
};
#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 ((uint32_t)-1)
/* Forward references. */
static uint32_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_EMPTY;
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;
hsd->bit_accumulator = 0x00000000;
}
/* 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 += size;
if (hsd->state == HSDS_EMPTY) {
hsd->state = HSDS_INPUT_AVAILABLE;
hsd->input_index = 0;
}
*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_input_available(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);
static HSD_state st_check_for_input(heatshrink_decoder *hsd);
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;
switch (in_state) {
case HSDS_EMPTY:
return HSDR_POLL_EMPTY;
case HSDS_INPUT_AVAILABLE:
hsd->state = st_input_available(hsd);
break;
case HSDS_YIELD_LITERAL:
hsd->state = st_yield_literal(hsd, &oi);
break;
case HSDS_BACKREF_INDEX_MSB:
hsd->state = st_backref_index_msb(hsd);
break;
case HSDS_BACKREF_INDEX_LSB:
hsd->state = st_backref_index_lsb(hsd);
break;
case HSDS_BACKREF_COUNT_MSB:
hsd->state = st_backref_count_msb(hsd);
break;
case HSDS_BACKREF_COUNT_LSB:
hsd->state = st_backref_count_lsb(hsd);
break;
case HSDS_YIELD_BACKREF:
hsd->state = st_yield_backref(hsd, &oi);
break;
case HSDS_CHECK_FOR_MORE_INPUT:
hsd->state = st_check_for_input(hsd);
break;
default:
return HSDR_POLL_ERROR_UNKNOWN;
}
/* 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_input_available(heatshrink_decoder *hsd) {
uint32_t bits = get_bits(hsd, 1); // get tag bit
if (bits) {
return HSDS_YIELD_LITERAL;
} else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) {
return HSDS_BACKREF_INDEX_MSB;
} 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) {
uint32_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 = (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_CHECK_FOR_MORE_INPUT;
} 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);
uint32_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 = 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);
uint32_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);
uint32_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 = 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);
uint32_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) {
if (hsd->output_count < count) count = hsd->output_count;
uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)];
uint16_t mask = (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 <= 1 << BACKREF_COUNT_BITS(hsd));
for (size_t 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 -= count;
if (hsd->output_count == 0) { return HSDS_CHECK_FOR_MORE_INPUT; }
}
return HSDS_YIELD_BACKREF;
}
static HSD_state st_check_for_input(heatshrink_decoder *hsd) {
return (hsd->input_size == 0) ? HSDS_EMPTY : HSDS_INPUT_AVAILABLE;
}
/* Get the next COUNT bits from the input buffer, saving incremental progress.
* Returns NO_BITS on end of input, or if more than 31 bits are requested. */
static uint32_t get_bits(heatshrink_decoder *hsd, uint8_t count) {
if (count > 31) { 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 (int 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",
hsd->bit_accumulator, hsd->bit_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;
}
hsd->bit_accumulator <<= 1;
if (hsd->current_byte & hsd->bit_index) {
hsd->bit_accumulator |= 0x01;
if (0) {
LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n",
hsd->bit_accumulator, hsd->bit_index);
}
} else {
if (0) {
LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n",
hsd->bit_accumulator, hsd->bit_index);
}
}
hsd->bit_index >>= 1;
}
uint32_t res = 0;
res = hsd->bit_accumulator;
hsd->bit_accumulator = 0x00000000;
if (count > 1) { LOG(" -- accumulated %08x\n", res); }
return res;
}
HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) {
if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; }
switch (hsd->state) {
case HSDS_EMPTY:
return HSDR_FINISH_DONE;
/* 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,101 @@
#ifndef HEATSHRINK_DECODER_H
#define HEATSHRINK_DECODER_H
#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 */
uint16_t bit_accumulator;
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 dencoder 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);
#endif
@@ -0,0 +1,650 @@
#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,
FLAG_HAS_LITERAL = 0x02,
FLAG_ON_FINAL_LITERAL = 0x04,
FLAG_BACKLOG_IS_PARTIAL = 0x08,
FLAG_BACKLOG_IS_FILLED = 0x10,
};
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 int backlog_is_partial(heatshrink_encoder *hse);
static int backlog_is_filled(heatshrink_encoder *hse);
static int on_final_literal(heatshrink_encoder *hse);
static void save_backlog(heatshrink_encoder *hse);
static int has_literal(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 = (2 << 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 = 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 = (2 << 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 = (2 << 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 : 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);
uint8_t in_state = hse->state;
switch (in_state) {
case HSES_NOT_FULL:
return HSER_POLL_EMPTY;
case HSES_FILLED:
do_indexing(hse);
hse->state = HSES_SEARCH;
break;
case HSES_SEARCH:
hse->state = st_step_search(hse);
break;
case HSES_YIELD_TAG_BIT:
hse->state = st_yield_tag_bit(hse, &oi);
break;
case HSES_YIELD_LITERAL:
hse->state = st_yield_literal(hse, &oi);
break;
case HSES_YIELD_BR_INDEX:
hse->state = st_yield_br_index(hse, &oi);
break;
case HSES_YIELD_BR_LENGTH:
hse->state = st_yield_br_length(hse, &oi);
break;
case HSES_SAVE_BACKLOG:
hse->state = st_save_backlog(hse);
break;
case HSES_FLUSH_BITS:
hse->state = st_flush_bit_buffer(hse, &oi);
case HSES_DONE:
return HSER_POLL_EMPTY;
default:
LOG("-- bad state %s\n", state_names[hse->state]);
return HSER_POLL_ERROR_MISUSE;
}
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 ? 0 : lookahead_sz)) {
/* Current search buffer is exhausted, copy it into the
* backlog and await more input. */
LOG("-- end of search @ %d, saving backlog\n", msi);
return HSES_SAVE_BACKLOG;
}
uint16_t input_offset = get_input_offset(hse);
uint16_t end = input_offset + msi;
uint16_t start = 0;
if (backlog_is_filled(hse)) { /* last WINDOW_LENGTH bytes */
start = end - window_length + 1;
} else if (backlog_is_partial(hse)) { /* clamp to available data */
start = end - window_length + 1;
if (start < lookahead_sz) { start = lookahead_sz; }
} else { /* only scan available input */
start = input_offset;
}
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->flags |= FLAG_HAS_LITERAL;
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 << hse->window_sz2 /*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);
hse->flags &= ~FLAG_HAS_LITERAL;
if (on_final_literal(hse)) { return HSES_FLUSH_BITS; }
return hse->match_length > 0 ? HSES_YIELD_TAG_BIT : 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) {
if (is_finishing(hse)) {
/* copy remaining literal (if necessary) */
if (has_literal(hse)) {
hse->flags |= FLAG_ON_FINAL_LITERAL;
return HSES_YIELD_TAG_BIT;
} else {
return HSES_FLUSH_BITS;
}
} else {
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) {
return (1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse));
(void)hse;
}
static uint16_t get_lookahead_size(heatshrink_encoder *hse) {
return (1 << HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse));
(void)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);
uint16_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 (uint16_t i=0; i<end; i++) {
uint8_t v = data[i];
uint16_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 backlog_is_partial(heatshrink_encoder *hse) {
return hse->flags & FLAG_BACKLOG_IS_PARTIAL;
}
static int backlog_is_filled(heatshrink_encoder *hse) {
return hse->flags & FLAG_BACKLOG_IS_FILLED;
}
static int on_final_literal(heatshrink_encoder *hse) {
return hse->flags & FLAG_ON_FINAL_LITERAL;
}
static int has_literal(heatshrink_encoder *hse) {
return (hse->flags & FLAG_HAS_LITERAL);
}
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;
const uint16_t break_even_point = 3;
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 >= start) {
uint8_t * const pospoint = &buf[pos];
len = 0;
/* 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 = pos;
if (len == maxlen) { break; } /* won't find better */
}
pos = hsi->index[pos];
}
#else
for (int16_t pos=end - 1; pos >= start; 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
if (match_maxlen >= break_even_point) {
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 = hse->outgoing_bits >> (hse->outgoing_bits_count - 8);
} else {
count = hse->outgoing_bits_count;
bits = 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 = input_buf_sz - msi; // unprocessed bytes
uint16_t shift_sz = input_buf_sz + rem;
memmove(&hse->buffer[0],
&hse->buffer[input_buf_sz - rem],
shift_sz);
if (backlog_is_partial(hse)) {
/* The whole backlog is filled in now, so include it in scans. */
hse->flags |= FLAG_BACKLOG_IS_FILLED;
} else {
/* Include backlog, except for the first lookahead_sz bytes, which
* are still undefined. */
hse->flags |= FLAG_BACKLOG_IS_PARTIAL;
}
hse->match_scan_index = 0;
hse->input_size -= input_buf_sz - rem;
}
@@ -0,0 +1,109 @@
#ifndef HEATSHRINK_ENCODER_H
#define HEATSHRINK_ENCODER_H
#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);
#endif
@@ -0,0 +1,999 @@
#include <stdint.h>
#include <ctype.h>
#include <assert.h>
#include "heatshrink_encoder.h"
#include "heatshrink_decoder.h"
#include "greatest.h"
#if !HEATSHRINK_DYNAMIC_ALLOC
#error Must set HEATSHRINK_DYNAMIC_ALLOC to 1 for dynamic allocation test suite.
#endif
SUITE(encoding);
SUITE(decoding);
SUITE(integration);
#ifdef HEATSHRINK_HAS_THEFT
SUITE(properties);
#endif
static void dump_buf(char *name, uint8_t *buf, uint16_t count) {
for (int i=0; i<count; i++) {
uint8_t c = (uint8_t)buf[i];
printf("%s %d: 0x%02x ('%c')\n", name, i, c, isprint(c) ? c : '.');
}
}
TEST encoder_alloc_should_reject_invalid_arguments(void) {
ASSERT_EQ(NULL, heatshrink_encoder_alloc(
HEATSHRINK_MIN_WINDOW_BITS - 1, 8));
ASSERT_EQ(NULL, heatshrink_encoder_alloc(
HEATSHRINK_MAX_WINDOW_BITS + 1, 8));
ASSERT_EQ(NULL, heatshrink_encoder_alloc(8, HEATSHRINK_MIN_LOOKAHEAD_BITS - 1));
ASSERT_EQ(NULL, heatshrink_encoder_alloc(8, 9));
PASS();
}
TEST encoder_sink_should_reject_nulls(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 7);
uint8_t input[] = {'f', 'o', 'o'};
size_t input_size = 0;
ASSERT(hse);
ASSERT_EQ(HSER_SINK_ERROR_NULL, heatshrink_encoder_sink(NULL, input, 3, &input_size));
ASSERT_EQ(HSER_SINK_ERROR_NULL, heatshrink_encoder_sink(hse, NULL, 3, &input_size));
ASSERT_EQ(HSER_SINK_ERROR_NULL, heatshrink_encoder_sink(hse, input, 3, NULL));
heatshrink_encoder_free(hse);
PASS();
}
TEST encoder_poll_should_reject_nulls(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 7);
uint8_t output[256];
size_t output_size = 0;
ASSERT_EQ(HSER_POLL_ERROR_NULL, heatshrink_encoder_poll(NULL,
output, 256, &output_size));
ASSERT_EQ(HSER_POLL_ERROR_NULL, heatshrink_encoder_poll(hse,
NULL, 256, &output_size));
ASSERT_EQ(HSER_POLL_ERROR_NULL, heatshrink_encoder_poll(hse,
output, 256, NULL));
heatshrink_encoder_free(hse);
PASS();
}
TEST encoder_finish_should_reject_nulls(void) {
ASSERT_EQ(HSER_FINISH_ERROR_NULL, heatshrink_encoder_finish(NULL));
PASS();
}
TEST encoder_sink_should_accept_input_when_it_will_fit(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 7);
ASSERT(hse);
uint8_t input[256];
size_t bytes_copied = 0;
memset(input, '*', 256);
ASSERT_EQ(HSER_SINK_OK, heatshrink_encoder_sink(hse,
input, 256, &bytes_copied));
ASSERT_EQ(256, bytes_copied);
heatshrink_encoder_free(hse);
PASS();
}
TEST encoder_sink_should_accept_partial_input_when_some_will_fit(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 7);
ASSERT(hse);
uint8_t input[512];
size_t bytes_copied = 0;
memset(input, '*', 512);
ASSERT_EQ(HSER_SINK_OK, heatshrink_encoder_sink(hse,
input, 512, &bytes_copied));
ASSERT_EQ(256, bytes_copied);
heatshrink_encoder_free(hse);
PASS();
}
TEST encoder_poll_should_indicate_when_no_input_is_provided(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 7);
uint8_t output[512];
size_t output_size = 0;
HSE_poll_res res = heatshrink_encoder_poll(hse,
output, 512, &output_size);
ASSERT_EQ(HSER_POLL_EMPTY, res);
heatshrink_encoder_free(hse);
PASS();
}
TEST encoder_should_emit_data_without_repetitions_as_literal_sequence(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 7);
ASSERT(hse);
uint8_t input[5];
uint8_t output[1024];
size_t copied = 0;
uint8_t expected[] = { 0x80, 0x40, 0x60, 0x50, 0x38, 0x20 };
for (int i=0; i<5; i++) { input[i] = i; }
memset(output, 0, 1024);
ASSERT_EQ(HSER_SINK_OK, heatshrink_encoder_sink(hse, input, 5, &copied));
ASSERT_EQ(5, copied);
/* Should get no output yet, since encoder doesn't know input is complete. */
copied = 0;
HSE_poll_res pres = heatshrink_encoder_poll(hse, output, 1024, &copied);
ASSERT_EQ(HSER_POLL_EMPTY, pres);
ASSERT_EQ(0, copied);
/* Mark input stream as done, to force small input to be processed. */
HSE_finish_res fres = heatshrink_encoder_finish(hse);
ASSERT_EQ(HSER_FINISH_MORE, fres);
pres = heatshrink_encoder_poll(hse, output, 1024, &copied);
ASSERT_EQ(HSER_POLL_EMPTY, pres);
for (size_t i=0; i<sizeof(expected); i++) {
ASSERT_EQ(expected[i], output[i]);
}
ASSERT_EQ(HSER_FINISH_DONE, heatshrink_encoder_finish(hse));
heatshrink_encoder_free(hse);
PASS();
}
TEST encoder_should_emit_series_of_same_byte_as_literal_then_backref(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 7);
ASSERT(hse);
uint8_t input[5];
uint8_t output[1024];
size_t copied = 0;
uint8_t expected[] = {0xb0, 0x80, 0x01, 0x80};
for (int i=0; i<5; i++) { input[i] = 'a'; } /* "aaaaa" */
memset(output, 0, 1024);
ASSERT_EQ(HSER_SINK_OK, heatshrink_encoder_sink(hse, input, 5, &copied));
ASSERT_EQ(5, copied);
/* Should get no output yet, since encoder doesn't know input is complete. */
copied = 0;
HSE_poll_res pres = heatshrink_encoder_poll(hse, output, 1024, &copied);
ASSERT_EQ(HSER_POLL_EMPTY, pres);
ASSERT_EQ(0, copied);
/* Mark input stream as done, to force small input to be processed. */
HSE_finish_res fres = heatshrink_encoder_finish(hse);
ASSERT_EQ(HSER_FINISH_MORE, fres);
pres = heatshrink_encoder_poll(hse, output, 1024, &copied);
ASSERT_EQ(HSER_POLL_EMPTY, pres);
ASSERT_EQ(4, copied);
if (0) dump_buf("output", output, copied);
for (size_t i=0; i<copied; i++) ASSERT_EQ(expected[i], output[i]);
ASSERT_EQ(HSER_FINISH_DONE, heatshrink_encoder_finish(hse));
heatshrink_encoder_free(hse);
PASS();
}
TEST encoder_poll_should_detect_repeated_substring(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 3);
uint8_t input[] = {'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd'};
uint8_t output[1024];
uint8_t expected[] = {0xb0, 0xd8, 0xac, 0x76, 0x40, 0x1b };
size_t copied = 0;
memset(output, 0, 1024);
HSE_sink_res sres = heatshrink_encoder_sink(hse,
input, sizeof(input), &copied);
ASSERT_EQ(HSER_SINK_OK, sres);
ASSERT_EQ(sizeof(input), copied);
HSE_finish_res fres = heatshrink_encoder_finish(hse);
ASSERT_EQ(HSER_FINISH_MORE, fres);
ASSERT_EQ(HSER_POLL_EMPTY, heatshrink_encoder_poll(hse, output, 1024, &copied));
fres = heatshrink_encoder_finish(hse);
ASSERT_EQ(HSER_FINISH_DONE, fres);
if (0) dump_buf("output", output, copied);
ASSERT_EQ(sizeof(expected), copied);
for (size_t i=0; i<sizeof(expected); i++) ASSERT_EQ(expected[i], output[i]);
heatshrink_encoder_free(hse);
PASS();
}
TEST encoder_poll_should_detect_repeated_substring_and_preserve_trailing_literal(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 3);
uint8_t input[] = {'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'e'};
uint8_t output[1024];
uint8_t expected[] = {0xb0, 0xd8, 0xac, 0x76, 0x40, 0x1b, 0xb2, 0x80 };
size_t copied = 0;
memset(output, 0, 1024);
HSE_sink_res sres = heatshrink_encoder_sink(hse,
input, sizeof(input), &copied);
ASSERT_EQ(HSER_SINK_OK, sres);
ASSERT_EQ(sizeof(input), copied);
HSE_finish_res fres = heatshrink_encoder_finish(hse);
ASSERT_EQ(HSER_FINISH_MORE, fres);
ASSERT_EQ(HSER_POLL_EMPTY, heatshrink_encoder_poll(hse, output, 1024, &copied));
fres = heatshrink_encoder_finish(hse);
ASSERT_EQ(HSER_FINISH_DONE, fres);
if (0) dump_buf("output", output, copied);
ASSERT_EQ(sizeof(expected), copied);
for (size_t i=0; i<sizeof(expected); i++) ASSERT_EQ(expected[i], output[i]);
heatshrink_encoder_free(hse);
PASS();
}
SUITE(encoding) {
RUN_TEST(encoder_alloc_should_reject_invalid_arguments);
RUN_TEST(encoder_sink_should_reject_nulls);
RUN_TEST(encoder_sink_should_accept_input_when_it_will_fit);
RUN_TEST(encoder_sink_should_accept_partial_input_when_some_will_fit);
RUN_TEST(encoder_poll_should_reject_nulls);
RUN_TEST(encoder_poll_should_indicate_when_no_input_is_provided);
RUN_TEST(encoder_finish_should_reject_nulls);
RUN_TEST(encoder_should_emit_data_without_repetitions_as_literal_sequence);
RUN_TEST(encoder_should_emit_series_of_same_byte_as_literal_then_backref);
RUN_TEST(encoder_poll_should_detect_repeated_substring);
RUN_TEST(encoder_poll_should_detect_repeated_substring_and_preserve_trailing_literal);
}
TEST decoder_alloc_should_reject_excessively_small_window(void) {
ASSERT_EQ(NULL, heatshrink_decoder_alloc(256,
HEATSHRINK_MIN_WINDOW_BITS - 1, 4));
PASS();
}
TEST decoder_alloc_should_reject_zero_byte_input_buffer(void) {
ASSERT_EQ(NULL, heatshrink_decoder_alloc(0,
HEATSHRINK_MIN_WINDOW_BITS, 4));
PASS();
}
TEST decoder_sink_should_reject_null_hsd_pointer(void) {
uint8_t input[] = {0,1,2,3,4,5};
size_t count = 0;
ASSERT_EQ(HSDR_SINK_ERROR_NULL, heatshrink_decoder_sink(NULL, input, 6, &count));
PASS();
}
TEST decoder_sink_should_reject_null_input_pointer(void) {
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256,
HEATSHRINK_MIN_WINDOW_BITS, 4);
size_t count = 0;
ASSERT_EQ(HSDR_SINK_ERROR_NULL, heatshrink_decoder_sink(hsd, NULL, 6, &count));
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_sink_should_reject_null_count_pointer(void) {
uint8_t input[] = {0,1,2,3,4,5};
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256,
HEATSHRINK_MIN_WINDOW_BITS, 4);
ASSERT_EQ(HSDR_SINK_ERROR_NULL, heatshrink_decoder_sink(hsd, input, 6, NULL));
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_sink_should_reject_excessively_large_input(void) {
uint8_t input[] = {0,1,2,3,4,5};
heatshrink_decoder *hsd = heatshrink_decoder_alloc(1,
HEATSHRINK_MIN_WINDOW_BITS, 4);
size_t count = 0;
// Sink as much as will fit
HSD_sink_res res = heatshrink_decoder_sink(hsd, input, 6, &count);
ASSERT_EQ(HSDR_SINK_OK, res);
ASSERT_EQ(1, count);
// And now, no more should fit.
res = heatshrink_decoder_sink(hsd, &input[count], sizeof(input) - count, &count);
ASSERT_EQ(HSDR_SINK_FULL, res);
ASSERT_EQ(0, count);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_sink_should_sink_data_when_preconditions_hold(void) {
uint8_t input[] = {0,1,2,3,4,5};
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256,
HEATSHRINK_MIN_WINDOW_BITS, 4);
size_t count = 0;
HSD_sink_res res = heatshrink_decoder_sink(hsd, input, 6, &count);
ASSERT_EQ(HSDR_SINK_OK, res);
ASSERT_EQ(hsd->input_size, 6);
ASSERT_EQ(hsd->input_index, 0);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_poll_should_return_empty_if_empty(void) {
uint8_t output[256];
size_t out_sz = 0;
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256,
HEATSHRINK_MIN_WINDOW_BITS, 4);
HSD_poll_res res = heatshrink_decoder_poll(hsd, output, 256, &out_sz);
ASSERT_EQ(HSDR_POLL_EMPTY, res);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_poll_should_reject_null_hsd(void) {
uint8_t output[256];
size_t out_sz = 0;
HSD_poll_res res = heatshrink_decoder_poll(NULL, output, 256, &out_sz);
ASSERT_EQ(HSDR_POLL_ERROR_NULL, res);
PASS();
}
TEST decoder_poll_should_reject_null_output_buffer(void) {
size_t out_sz = 0;
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256,
HEATSHRINK_MIN_WINDOW_BITS, 4);
HSD_poll_res res = heatshrink_decoder_poll(hsd, NULL, 256, &out_sz);
ASSERT_EQ(HSDR_POLL_ERROR_NULL, res);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_poll_should_reject_null_output_size_pointer(void) {
uint8_t output[256];
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256,
HEATSHRINK_MIN_WINDOW_BITS, 4);
HSD_poll_res res = heatshrink_decoder_poll(hsd, output, 256, NULL);
ASSERT_EQ(HSDR_POLL_ERROR_NULL, res);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_poll_should_expand_short_literal(void) {
uint8_t input[] = {0xb3, 0x5b, 0xed, 0xe0 }; //"foo"
uint8_t output[4];
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 3);
size_t count = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, sizeof(input), &count);
ASSERT_EQ(HSDR_SINK_OK, sres);
size_t out_sz = 0;
HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, 4, &out_sz);
ASSERT_EQ(HSDR_POLL_EMPTY, pres);
ASSERT_EQ(3, out_sz);
ASSERT_EQ('f', output[0]);
ASSERT_EQ('o', output[1]);
ASSERT_EQ('o', output[2]);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_poll_should_expand_short_literal_and_backref(void) {
uint8_t input[] = {0xb3, 0x5b, 0xed, 0xe0, 0x40, 0x80}; //"foofoo"
uint8_t output[6];
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 7);
memset(output, 0, sizeof(*output));
size_t count = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, sizeof(input), &count);
ASSERT_EQ(HSDR_SINK_OK, sres);
size_t out_sz = 0;
(void)heatshrink_decoder_poll(hsd, output, 6, &out_sz);
if (0) dump_buf("output", output, out_sz);
ASSERT_EQ(6, out_sz);
ASSERT_EQ('f', output[0]);
ASSERT_EQ('o', output[1]);
ASSERT_EQ('o', output[2]);
ASSERT_EQ('f', output[3]);
ASSERT_EQ('o', output[4]);
ASSERT_EQ('o', output[5]);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_poll_should_expand_short_self_overlapping_backref(void) {
/* "aaaaa" == (literal, 1), ('a'), (backref, 1 back, 4 bytes) */
uint8_t input[] = {0xb0, 0x80, 0x01, 0x80};
uint8_t output[6];
uint8_t expected[] = {'a', 'a', 'a', 'a', 'a'};
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 8, 7);
size_t count = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, sizeof(input), &count);
ASSERT_EQ(HSDR_SINK_OK, sres);
size_t out_sz = 0;
(void)heatshrink_decoder_poll(hsd, output, sizeof(output), &out_sz);
if (0) dump_buf("output", output, out_sz);
ASSERT_EQ(sizeof(expected), out_sz);
for (size_t i=0; i<sizeof(expected); i++) ASSERT_EQ(expected[i], output[i]);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_poll_should_suspend_if_out_of_space_in_output_buffer_during_literal_expansion(void) {
uint8_t input[] = {0xb3, 0x5b, 0xed, 0xe0, 0x40, 0x80};
uint8_t output[1];
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 7);
size_t count = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, sizeof(input), &count);
ASSERT_EQ(HSDR_SINK_OK, sres);
size_t out_sz = 0;
HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, 1, &out_sz);
ASSERT_EQ(HSDR_POLL_MORE, pres);
ASSERT_EQ(1, out_sz);
ASSERT_EQ('f', output[0]);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_poll_should_suspend_if_out_of_space_in_output_buffer_during_backref_expansion(void) {
uint8_t input[] = {0xb3, 0x5b, 0xed, 0xe0, 0x40, 0x80}; //"foofoo"
uint8_t output[4];
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 7);
memset(output, 0, sizeof(*output));
size_t count = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, 6, &count);
ASSERT_EQ(HSDR_SINK_OK, sres);
size_t out_sz = 0;
HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, 4, &out_sz);
ASSERT_EQ(HSDR_POLL_MORE, pres);
ASSERT_EQ(4, out_sz);
ASSERT_EQ('f', output[0]);
ASSERT_EQ('o', output[1]);
ASSERT_EQ('o', output[2]);
ASSERT_EQ('f', output[3]);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_poll_should_expand_short_literal_and_backref_when_fed_input_byte_by_byte(void) {
uint8_t input[] = {0xb3, 0x5b, 0xed, 0xe0, 0x40, 0x80}; //"foofoo"
uint8_t output[7];
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 7);
memset(output, 0, sizeof(*output));
size_t count = 0;
HSD_sink_res sres;
for (int i=0; i<6; i++) {
sres = heatshrink_decoder_sink(hsd, &input[i], 1, &count);
ASSERT_EQ(HSDR_SINK_OK, sres);
}
heatshrink_decoder_finish(hsd);
size_t out_sz = 0;
HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, 7, &out_sz);
ASSERT_EQ(6, out_sz);
ASSERT_EQ(HSDR_POLL_EMPTY, pres);
ASSERT_EQ('f', output[0]);
ASSERT_EQ('o', output[1]);
ASSERT_EQ('o', output[2]);
ASSERT_EQ('f', output[3]);
ASSERT_EQ('o', output[4]);
ASSERT_EQ('o', output[5]);
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_finish_should_reject_null_input(void) {
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 7);
HSD_finish_res exp = HSDR_FINISH_ERROR_NULL;
ASSERT_EQ(exp, heatshrink_decoder_finish(NULL));
heatshrink_decoder_free(hsd);
PASS();
}
TEST decoder_finish_should_note_when_done(void) {
uint8_t input[] = {0xb3, 0x5b, 0xed, 0xe0, 0x40, 0x80}; //"foofoo"
uint8_t output[7];
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 7);
memset(output, 0, sizeof(*output));
size_t count = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, sizeof(input), &count);
ASSERT_EQ(HSDR_SINK_OK, sres);
size_t out_sz = 0;
HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, sizeof(output), &out_sz);
ASSERT_EQ(HSDR_POLL_EMPTY, pres);
ASSERT_EQ(6, out_sz);
ASSERT_EQ('f', output[0]);
ASSERT_EQ('o', output[1]);
ASSERT_EQ('o', output[2]);
ASSERT_EQ('f', output[3]);
ASSERT_EQ('o', output[4]);
ASSERT_EQ('o', output[5]);
HSD_finish_res fres = heatshrink_decoder_finish(hsd);
ASSERT_EQ(HSDR_FINISH_DONE, fres);
heatshrink_decoder_free(hsd);
PASS();
}
TEST gen(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 7);
uint8_t input[] = {'a', 'a', 'a', 'a', 'a'};
uint8_t output[1024];
size_t copied = 0;
memset(output, 0, 1024);
HSE_sink_res sres = heatshrink_encoder_sink(hse,
input, sizeof(input), &copied);
ASSERT_EQ(HSER_SINK_OK, sres);
ASSERT_EQ(sizeof(input), copied);
HSE_finish_res fres = heatshrink_encoder_finish(hse);
ASSERT_EQ(HSER_FINISH_MORE, fres);
ASSERT_EQ(HSER_POLL_EMPTY, heatshrink_encoder_poll(hse, output, 1024, &copied));
fres = heatshrink_encoder_finish(hse);
ASSERT_EQ(HSER_FINISH_DONE, fres);
if (0) {
printf("{");
for (size_t i=0; i<copied; i++) printf("0x%02x, ", output[i]);
printf("}\n");
}
heatshrink_encoder_free(hse);
PASS();
}
TEST decoder_should_not_get_stuck_with_finish_yielding_MORE_but_0_bytes_output_from_poll(void) {
uint8_t input[512];
memset(input, 0xff, 256);
uint8_t output[1024];
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 8, 4);
ASSERT(hsd);
/* Confirm that no byte of trailing context can lead to
* heatshrink_decoder_finish erroneously returning HSDR_FINISH_MORE
* when heatshrink_decoder_poll will yield 0 bytes.
*
* Before 0.3.1, a final byte of 0xFF could potentially cause
* this to happen, if at exactly the byte boundary. */
for (uint16_t byte = 0; byte < 256; byte++) {
for (int i = 1; i < 512; i++) {
input[i] = byte;
heatshrink_decoder_reset(hsd);
memset(output, 0, sizeof(*output));
size_t count = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, i, &count);
ASSERT_EQ(HSDR_SINK_OK, sres);
size_t out_sz = 0;
HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, sizeof(output), &out_sz);
ASSERT_EQ(HSDR_POLL_EMPTY, pres);
HSD_finish_res fres = heatshrink_decoder_finish(hsd);
ASSERT_EQ(HSDR_FINISH_DONE, fres);
input[i] = 0xff;
}
}
heatshrink_decoder_free(hsd);
PASS();
}
SUITE(decoding) {
RUN_TEST(decoder_alloc_should_reject_excessively_small_window);
RUN_TEST(decoder_alloc_should_reject_zero_byte_input_buffer);
RUN_TEST(decoder_sink_should_reject_null_hsd_pointer);
RUN_TEST(decoder_sink_should_reject_null_input_pointer);
RUN_TEST(decoder_sink_should_reject_null_count_pointer);
RUN_TEST(decoder_sink_should_reject_excessively_large_input);
RUN_TEST(decoder_sink_should_sink_data_when_preconditions_hold);
RUN_TEST(gen);
RUN_TEST(decoder_poll_should_return_empty_if_empty);
RUN_TEST(decoder_poll_should_reject_null_hsd);
RUN_TEST(decoder_poll_should_reject_null_output_buffer);
RUN_TEST(decoder_poll_should_reject_null_output_size_pointer);
RUN_TEST(decoder_poll_should_expand_short_literal);
RUN_TEST(decoder_poll_should_expand_short_literal_and_backref);
RUN_TEST(decoder_poll_should_expand_short_self_overlapping_backref);
RUN_TEST(decoder_poll_should_suspend_if_out_of_space_in_output_buffer_during_literal_expansion);
RUN_TEST(decoder_poll_should_suspend_if_out_of_space_in_output_buffer_during_backref_expansion);
RUN_TEST(decoder_poll_should_expand_short_literal_and_backref_when_fed_input_byte_by_byte);
RUN_TEST(decoder_finish_should_reject_null_input);
RUN_TEST(decoder_finish_should_note_when_done);
// Regressions
RUN_TEST(decoder_should_not_get_stuck_with_finish_yielding_MORE_but_0_bytes_output_from_poll);
}
typedef struct {
uint8_t log_lvl;
uint8_t window_sz2;
uint8_t lookahead_sz2;
size_t decoder_input_buffer_size;
} cfg_info;
static int compress_and_expand_and_check(uint8_t *input, uint32_t input_size, cfg_info *cfg) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(cfg->window_sz2,
cfg->lookahead_sz2);
heatshrink_decoder *hsd = heatshrink_decoder_alloc(cfg->decoder_input_buffer_size,
cfg->window_sz2, cfg->lookahead_sz2);
size_t comp_sz = input_size + (input_size/2) + 4;
size_t decomp_sz = input_size + (input_size/2) + 4;
uint8_t *comp = malloc(comp_sz);
uint8_t *decomp = malloc(decomp_sz);
if (comp == NULL) FAILm("malloc fail");
if (decomp == NULL) FAILm("malloc fail");
memset(comp, 0, comp_sz);
memset(decomp, 0, decomp_sz);
size_t count = 0;
if (cfg->log_lvl > 1) {
printf("\n^^ COMPRESSING\n");
dump_buf("input", input, input_size);
}
size_t sunk = 0;
size_t polled = 0;
while (sunk < input_size) {
ASSERT(heatshrink_encoder_sink(hse, &input[sunk], input_size - sunk, &count) >= 0);
sunk += count;
if (cfg->log_lvl > 1) printf("^^ sunk %zd\n", count);
if (sunk == input_size) {
ASSERT_EQ(HSER_FINISH_MORE, heatshrink_encoder_finish(hse));
}
HSE_poll_res pres;
do { /* "turn the crank" */
pres = heatshrink_encoder_poll(hse, &comp[polled], comp_sz - polled, &count);
ASSERT(pres >= 0);
polled += count;
if (cfg->log_lvl > 1) printf("^^ polled %zd\n", count);
} while (pres == HSER_POLL_MORE);
ASSERT_EQ(HSER_POLL_EMPTY, pres);
if (polled >= comp_sz) FAILm("compression should never expand that much");
if (sunk == input_size) {
ASSERT_EQ(HSER_FINISH_DONE, heatshrink_encoder_finish(hse));
}
}
if (cfg->log_lvl > 0) printf("in: %u compressed: %zu ", input_size, polled);
size_t compressed_size = polled;
sunk = 0;
polled = 0;
if (cfg->log_lvl > 1) {
printf("\n^^ DECOMPRESSING\n");
dump_buf("comp", comp, compressed_size);
}
while (sunk < compressed_size) {
ASSERT(heatshrink_decoder_sink(hsd, &comp[sunk], compressed_size - sunk, &count) >= 0);
sunk += count;
if (cfg->log_lvl > 1) printf("^^ sunk %zd\n", count);
if (sunk == compressed_size) {
ASSERT_EQ(HSDR_FINISH_MORE, heatshrink_decoder_finish(hsd));
}
HSD_poll_res pres;
do {
pres = heatshrink_decoder_poll(hsd, &decomp[polled],
decomp_sz - polled, &count);
ASSERT(pres >= 0);
ASSERT(count > 0);
polled += count;
if (cfg->log_lvl > 1) printf("^^ polled %zd\n", count);
} while (pres == HSDR_POLL_MORE);
ASSERT_EQ(HSDR_POLL_EMPTY, pres);
if (sunk == compressed_size) {
HSD_finish_res fres = heatshrink_decoder_finish(hsd);
ASSERT_EQ(HSDR_FINISH_DONE, fres);
}
if (polled > input_size) {
printf("\nExpected %zd, got %zu\n", (size_t)input_size, polled);
FAILm("Decompressed data is larger than original input");
}
}
if (cfg->log_lvl > 0) printf("decompressed: %zu\n", polled);
if (polled != input_size) {
FAILm("Decompressed length does not match original input length");
}
if (cfg->log_lvl > 1) dump_buf("decomp", decomp, polled);
for (uint32_t i=0; i<input_size; i++) {
if (input[i] != decomp[i]) {
printf("*** mismatch at %d\n", i);
if (0) {
for (uint32_t j=0; j<=/*i*/ input_size; j++) {
printf("in[%d] == 0x%02x ('%c') => out[%d] == 0x%02x ('%c') %c\n",
j, input[j], isprint(input[j]) ? input[j] : '.',
j, decomp[j], isprint(decomp[j]) ? decomp[j] : '.',
input[j] == decomp[j] ? ' ' : 'X');
}
}
}
ASSERT_EQ(input[i], decomp[i]);
}
free(comp);
free(decomp);
heatshrink_encoder_free(hse);
heatshrink_decoder_free(hsd);
PASS();
}
TEST data_without_duplication_should_match(void) {
uint8_t input[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
cfg_info cfg;
cfg.log_lvl = 0;
cfg.window_sz2 = 8;
cfg.lookahead_sz2 = 3;
cfg.decoder_input_buffer_size = 256;
return compress_and_expand_and_check(input, sizeof(input), &cfg);
}
TEST data_with_simple_repetition_should_compress_and_decompress_properly(void) {
uint8_t input[] = {'a', 'b', 'c', 'a', 'b', 'c', 'd', 'a', 'b',
'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e', 'f',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h'};
cfg_info cfg;
cfg.log_lvl = 0;
cfg.window_sz2 = 8;
cfg.lookahead_sz2 = 3;
cfg.decoder_input_buffer_size = 256;
return compress_and_expand_and_check(input, sizeof(input), &cfg);
}
TEST data_without_duplication_should_match_with_absurdly_tiny_buffers(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 3);
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 8, 3);
uint8_t input[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
uint8_t comp[60];
uint8_t decomp[60];
size_t count = 0;
int log = 0;
if (log) dump_buf("input", input, sizeof(input));
for (uint32_t i=0; i<sizeof(input); i++) {
ASSERT(heatshrink_encoder_sink(hse, &input[i], 1, &count) >= 0);
}
ASSERT_EQ(HSER_FINISH_MORE, heatshrink_encoder_finish(hse));
size_t packed_count = 0;
do {
ASSERT(heatshrink_encoder_poll(hse, &comp[packed_count], 1, &count) >= 0);
packed_count += count;
} while (heatshrink_encoder_finish(hse) == HSER_FINISH_MORE);
if (log) dump_buf("comp", comp, packed_count);
for (uint32_t i=0; i<packed_count; i++) {
HSD_sink_res sres = heatshrink_decoder_sink(hsd, &comp[i], 1, &count);
//printf("sres is %d\n", sres);
ASSERT(sres >= 0);
}
for (uint32_t i=0; i<sizeof(input); i++) {
ASSERT(heatshrink_decoder_poll(hsd, &decomp[i], 1, &count) >= 0);
}
if (log) dump_buf("decomp", decomp, sizeof(input));
for (uint32_t i=0; i<sizeof(input); i++) ASSERT_EQ(input[i], decomp[i]);
heatshrink_encoder_free(hse);
heatshrink_decoder_free(hsd);
PASS();
}
TEST data_with_simple_repetition_should_match_with_absurdly_tiny_buffers(void) {
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 3);
heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 8, 3);
uint8_t input[] = {'a', 'b', 'c', 'a', 'b', 'c', 'd', 'a', 'b',
'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e', 'f',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h'};
uint8_t comp[60];
uint8_t decomp[60];
size_t count = 0;
int log = 0;
if (log) dump_buf("input", input, sizeof(input));
for (uint32_t i=0; i<sizeof(input); i++) {
ASSERT(heatshrink_encoder_sink(hse, &input[i], 1, &count) >= 0);
}
ASSERT_EQ(HSER_FINISH_MORE, heatshrink_encoder_finish(hse));
size_t packed_count = 0;
do {
ASSERT(heatshrink_encoder_poll(hse, &comp[packed_count], 1, &count) >= 0);
packed_count += count;
} while (heatshrink_encoder_finish(hse) == HSER_FINISH_MORE);
if (log) dump_buf("comp", comp, packed_count);
for (uint32_t i=0; i<packed_count; i++) {
HSD_sink_res sres = heatshrink_decoder_sink(hsd, &comp[i], 1, &count);
//printf("sres is %d\n", sres);
ASSERT(sres >= 0);
}
for (uint32_t i=0; i<sizeof(input); i++) {
ASSERT(heatshrink_decoder_poll(hsd, &decomp[i], 1, &count) >= 0);
}
if (log) dump_buf("decomp", decomp, sizeof(input));
for (uint32_t i=0; i<sizeof(input); i++) ASSERT_EQ(input[i], decomp[i]);
heatshrink_encoder_free(hse);
heatshrink_decoder_free(hsd);
PASS();
}
static void fill_with_pseudorandom_letters(uint8_t *buf, uint32_t size, uint32_t seed) {
uint64_t rn = 9223372036854775783; /* prime under 2^64 */
for (uint32_t i=0; i<size; i++) {
rn = rn*seed + seed;
buf[i] = (rn % 26) + 'a';
}
}
TEST pseudorandom_data_should_match(uint32_t size, uint32_t seed, cfg_info *cfg) {
uint8_t input[size];
if (cfg->log_lvl > 0) {
printf("\n-- size %u, seed %u, input buf %zu\n",
size, seed, cfg->decoder_input_buffer_size);
}
fill_with_pseudorandom_letters(input, size, seed);
return compress_and_expand_and_check(input, size, cfg);
}
TEST small_input_buffer_should_not_impact_decoder_correctness(void) {
int size = 5;
uint8_t input[size];
cfg_info cfg;
cfg.log_lvl = 0;
cfg.window_sz2 = 8;
cfg.lookahead_sz2 = 3;
cfg.decoder_input_buffer_size = 5;
for (uint16_t i=0; i<size; i++) input[i] = 'a' + (i % 26);
if (compress_and_expand_and_check(input, size, &cfg) != 0) return -1;
PASS();
}
TEST regression_backreference_counters_should_not_roll_over(void) {
/* Searching was scanning the entire context buffer, not just
* the maximum range addressable by the backref index.*/
uint32_t size = 337;
uint32_t seed = 3;
uint8_t input[size];
fill_with_pseudorandom_letters(input, size, seed);
cfg_info cfg;
cfg.log_lvl = 0;
cfg.window_sz2 = 8;
cfg.lookahead_sz2 = 3;
cfg.decoder_input_buffer_size = 64; // 1
return compress_and_expand_and_check(input, size, &cfg);
}
TEST regression_index_fail(void) {
/* Failured when indexed, cause unknown.
*
* This has something to do with bad data at the very last
* byte being indexed, due to spillover. */
uint32_t size = 507;
uint32_t seed = 3;
uint8_t input[size];
fill_with_pseudorandom_letters(input, size, seed);
cfg_info cfg;
cfg.log_lvl = 0;
cfg.window_sz2 = 8;
cfg.lookahead_sz2 = 3;
cfg.decoder_input_buffer_size = 64;
return compress_and_expand_and_check(input, size, &cfg);
}
TEST sixty_four_k(void) {
/* Regression: An input buffer of 64k should not cause an
* overflow that leads to an infinite loop. */
uint32_t size = 64 * 1024;
uint32_t seed = 1;
uint8_t input[size];
fill_with_pseudorandom_letters(input, size, seed);
cfg_info cfg;
cfg.log_lvl = 0;
cfg.window_sz2 = 8;
cfg.lookahead_sz2 = 3;
cfg.decoder_input_buffer_size = 64;
return compress_and_expand_and_check(input, size, &cfg);
}
SUITE(integration) {
RUN_TEST(data_without_duplication_should_match);
RUN_TEST(data_with_simple_repetition_should_compress_and_decompress_properly);
RUN_TEST(data_without_duplication_should_match_with_absurdly_tiny_buffers);
RUN_TEST(data_with_simple_repetition_should_match_with_absurdly_tiny_buffers);
// Regressions from fuzzing
RUN_TEST(small_input_buffer_should_not_impact_decoder_correctness);
RUN_TEST(regression_backreference_counters_should_not_roll_over);
RUN_TEST(regression_index_fail);
RUN_TEST(sixty_four_k);
#if __STDC_VERSION__ >= 19901L
printf("\n\nFuzzing (single-byte sizes):\n");
for (uint8_t lsize=3; lsize < 8; lsize++) {
for (uint32_t size=1; size < 128*1024L; size <<= 1) {
if (GREATEST_IS_VERBOSE()) printf(" -- size %u\n", size);
for (uint16_t ibs=32; ibs<=8192; ibs <<= 1) { /* input buffer size */
if (GREATEST_IS_VERBOSE()) printf(" -- input buffer %u\n", ibs);
for (uint32_t seed=1; seed<=10; seed++) {
if (GREATEST_IS_VERBOSE()) printf(" -- seed %u\n", seed);
cfg_info cfg;
cfg.log_lvl = 0;
cfg.window_sz2 = 8;
cfg.lookahead_sz2 = lsize;
cfg.decoder_input_buffer_size = ibs;
RUN_TESTp(pseudorandom_data_should_match, size, seed, &cfg);
}
}
}
}
printf("\nFuzzing (multi-byte sizes):\n");
for (uint8_t lsize=6; lsize < 9; lsize++) {
for (uint32_t size=1; size < 128*1024L; size <<= 1) {
if (GREATEST_IS_VERBOSE()) printf(" -- size %u\n", size);
for (uint16_t ibs=32; ibs<=8192; ibs <<= 1) { /* input buffer size */
if (GREATEST_IS_VERBOSE()) printf(" -- input buffer %u\n", ibs);
for (uint32_t seed=1; seed<=10; seed++) {
if (GREATEST_IS_VERBOSE()) printf(" -- seed %u\n", seed);
cfg_info cfg;
cfg.log_lvl = 0;
cfg.window_sz2 = 11;
cfg.lookahead_sz2 = lsize;
cfg.decoder_input_buffer_size = ibs;
RUN_TESTp(pseudorandom_data_should_match, size, seed, &cfg);
}
}
}
}
#endif
}
/* Add all the definitions that need to be in the test runner's main file. */
GREATEST_MAIN_DEFS();
int main(int argc, char **argv) {
GREATEST_MAIN_BEGIN(); /* command-line arguments, initialization. */
RUN_SUITE(encoding);
RUN_SUITE(decoding);
RUN_SUITE(integration);
#ifdef HEATSHRINK_HAS_THEFT
RUN_SUITE(properties);
#endif
GREATEST_MAIN_END(); /* display results */
}
@@ -0,0 +1,521 @@
#include "heatshrink_config.h"
#ifdef HEATSHRINK_HAS_THEFT
#include <stdint.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
#include <sys/time.h>
#include "heatshrink_encoder.h"
#include "heatshrink_decoder.h"
#include "greatest.h"
#include "theft.h"
#include "greatest_theft.h"
#if !HEATSHRINK_DYNAMIC_ALLOC
#error Must set HEATSHRINK_DYNAMIC_ALLOC to 1 for this test suite.
#endif
SUITE(properties);
typedef struct {
int limit;
int fails;
int dots;
} test_env;
typedef struct {
size_t size;
uint8_t buf[];
} rbuf;
static void *rbuf_alloc_cb(struct theft *t, theft_hash seed, void *env) {
test_env *te = (test_env *)env;
//printf("seed is 0x%016llx\n", seed);
size_t sz = (size_t)(seed % te->limit) + 1;
rbuf *r = malloc(sizeof(rbuf) + sz);
if (r == NULL) { return THEFT_ERROR; }
r->size = sz;
for (size_t i = 0; i < sz; i += sizeof(theft_hash)) {
theft_hash s = theft_random(t);
for (uint8_t b = 0; b < sizeof(theft_hash); b++) {
if (i + b >= sz) { break; }
r->buf[i + b] = (uint8_t) (s >> (8*b)) & 0xff;
}
}
return r;
}
static void rbuf_free_cb(void *instance, void *env) {
free(instance);
(void)env;
}
static uint64_t rbuf_hash_cb(void *instance, void *env) {
rbuf *r = (rbuf *)instance;
(void)env;
return theft_hash_onepass(r->buf, r->size);
}
/* Make a copy of a buffer, keeping NEW_SZ bytes starting at OFFSET. */
static void *copy_rbuf_subset(rbuf *cur, size_t new_sz, size_t byte_offset) {
if (new_sz == 0) { return THEFT_DEAD_END; }
rbuf *nr = malloc(sizeof(rbuf) + new_sz);
if (nr == NULL) { return THEFT_ERROR; }
nr->size = new_sz;
memcpy(nr->buf, &cur->buf[byte_offset], new_sz);
/* printf("%zu -> %zu\n", cur->size, new_sz); */
return nr;
}
/* Make a copy of a buffer, but only PORTION, starting OFFSET in
* (e.g. the third quarter is (0.25 at +0.75). Rounds to ints. */
static void *copy_rbuf_percent(rbuf *cur, float portion, float offset) {
size_t new_sz = cur->size * portion;
size_t byte_offset = (size_t)(cur->size * offset);
return copy_rbuf_subset(cur, new_sz, byte_offset);
}
/* How to shrink a random buffer to a simpler one. */
static void *rbuf_shrink_cb(void *instance, uint32_t tactic, void *env) {
rbuf *cur = (rbuf *)instance;
if (tactic == 0) { /* first half */
return copy_rbuf_percent(cur, 0.5, 0);
} else if (tactic == 1) { /* second half */
return copy_rbuf_percent(cur, 0.5, 0.5);
} else if (tactic <= 18) { /* drop 1-16 bytes at start */
const int last_tactic = 1;
const size_t drop = tactic - last_tactic;
if (cur->size < drop) { return THEFT_DEAD_END; }
return copy_rbuf_subset(cur, cur->size - drop, drop);
} else if (tactic <= 34) { /* drop 1-16 bytes at end */
const int last_tactic = 18;
const size_t drop = tactic - last_tactic;
if (cur->size < drop) { return THEFT_DEAD_END; }
return copy_rbuf_subset(cur, cur->size - drop, 0);
} else if (tactic == 35) {
/* Divide every byte by 2, saturating at 0 */
rbuf *cp = copy_rbuf_percent(cur, 1, 0);
if (cp == NULL) { return THEFT_ERROR; }
for (size_t i = 0; i < cp->size; i++) { cp->buf[i] /= 2; }
return cp;
} else if (tactic == 36) {
/* subtract 1 from every byte, saturating at 0 */
rbuf *cp = copy_rbuf_percent(cur, 1, 0);
if (cp == NULL) { return THEFT_ERROR; }
for (size_t i = 0; i < cp->size; i++) {
if (cp->buf[i] > 0) { cp->buf[i]--; }
}
return cp;
} else {
(void)env;
return THEFT_NO_MORE_TACTICS;
}
return THEFT_NO_MORE_TACTICS;
}
static void rbuf_print_cb(FILE *f, void *instance, void *env) {
rbuf *r = (rbuf *)instance;
(void)env;
fprintf(f, "buf[%zd]:\n ", r->size);
uint8_t bytes = 0;
for (size_t i = 0; i < r->size; i++) {
fprintf(f, "%02x", r->buf[i]);
bytes++;
if (bytes == 16) {
fprintf(f, "\n ");
bytes = 0;
}
}
fprintf(f, "\n");
}
static struct theft_type_info rbuf_info = {
.alloc = rbuf_alloc_cb,
.free = rbuf_free_cb,
.hash = rbuf_hash_cb,
.shrink = rbuf_shrink_cb,
.print = rbuf_print_cb,
};
static theft_progress_callback_res
progress_cb(struct theft_trial_info *info, void *env) {
test_env *te = (test_env *)env;
if ((info->trial & 0xff) == 0) {
printf(".");
fflush(stdout);
te->dots++;
if (te->dots == 64) {
printf("\n");
te->dots = 0;
}
}
if (info->status == THEFT_TRIAL_FAIL) {
te->fails++;
rbuf *cur = info->args[0];
if (cur->size < 5) { return THEFT_PROGRESS_HALT; }
}
if (te->fails > 10) {
return THEFT_PROGRESS_HALT;
}
return THEFT_PROGRESS_CONTINUE;
}
/* For an arbitrary input buffer, it should never get stuck in a
* state where the data has been sunk but no data can be polled. */
static theft_trial_res prop_should_not_get_stuck(void *input) {
/* Make a buffer large enough for the output: 4 KB of input with
* each 16 bits becoming up to 16 bytes will fit in a 64 KB buffer.
* (4 KB of input comes from `env.limit = 1 << 12;` below.) */
uint8_t output[64 * 1024];
heatshrink_decoder *hsd = heatshrink_decoder_alloc((64 * 1024L) - 1, 12, 4);
if (hsd == NULL) { return THEFT_TRIAL_ERROR; }
rbuf *r = (rbuf *)input;
size_t count = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, r->buf, r->size, &count);
if (sres != HSDR_SINK_OK) { return THEFT_TRIAL_ERROR; }
size_t out_sz = 0;
HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, sizeof(output), &out_sz);
if (pres != HSDR_POLL_EMPTY) { return THEFT_TRIAL_FAIL; }
HSD_finish_res fres = heatshrink_decoder_finish(hsd);
heatshrink_decoder_free(hsd);
if (fres != HSDR_FINISH_DONE) { return THEFT_TRIAL_FAIL; }
return THEFT_TRIAL_PASS;
}
static bool get_time_seed(theft_seed *seed)
{
struct timeval tv;
if (-1 == gettimeofday(&tv, NULL)) { return false; }
*seed = (theft_seed)((tv.tv_sec << 32) | tv.tv_usec);
/* printf("seed is 0x%016llx\n", *seed); */
return true;
}
TEST decoder_fuzzing_should_not_detect_stuck_state(void) {
// Get a random number seed based on the time
theft_seed seed;
if (!get_time_seed(&seed)) { FAIL(); }
/* Pass the max buffer size for this property (4 KB) in a closure */
test_env env = { .limit = 1 << 12 };
theft_seed always_seeds = { 0xe87bb1f61032a061 };
struct theft *t = theft_init(0);
struct theft_cfg cfg = {
.name = __func__,
.fun = prop_should_not_get_stuck,
.type_info = { &rbuf_info },
.seed = seed,
.trials = 100000,
.progress_cb = progress_cb,
.env = &env,
.always_seeds = &always_seeds,
.always_seed_count = 1,
};
theft_run_res res = theft_run(t, &cfg);
theft_free(t);
printf("\n");
GREATEST_ASSERT_EQm("should_not_get_stuck", THEFT_RUN_PASS, res);
PASS();
}
static theft_trial_res prop_encoded_and_decoded_data_should_match(void *input) {
uint8_t e_output[64 * 1024];
uint8_t d_output[64 * 1024];
heatshrink_encoder *hse = heatshrink_encoder_alloc(12, 4);
if (hse == NULL) { return THEFT_TRIAL_ERROR; }
heatshrink_decoder *hsd = heatshrink_decoder_alloc(4096, 12, 4);
if (hsd == NULL) { return THEFT_TRIAL_ERROR; }
rbuf *r = (rbuf *)input;
size_t e_input_size = 0;
HSE_sink_res esres = heatshrink_encoder_sink(hse,
r->buf, r->size, &e_input_size);
if (esres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; }
if (e_input_size != r->size) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
HSE_finish_res efres = heatshrink_encoder_finish(hse);
if (efres != HSER_FINISH_MORE) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
size_t e_output_size = 0;
HSE_poll_res epres = heatshrink_encoder_poll(hse,
e_output, sizeof(e_output), &e_output_size);
if (epres != HSER_POLL_EMPTY) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
size_t count = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, e_output, e_output_size, &count);
if (sres != HSDR_SINK_OK) { return THEFT_TRIAL_ERROR; }
size_t d_output_size = 0;
HSD_poll_res pres = heatshrink_decoder_poll(hsd, d_output,
sizeof(d_output), &d_output_size);
if (pres != HSDR_POLL_EMPTY) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
if (d_output_size != r->size) {
printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL;
}
if (0 != memcmp(d_output, r->buf, d_output_size)) {
return THEFT_TRIAL_FAIL;
}
HSD_finish_res fres = heatshrink_decoder_finish(hsd);
if (fres != HSDR_FINISH_DONE) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
heatshrink_encoder_free(hse);
heatshrink_decoder_free(hsd);
return THEFT_TRIAL_PASS;
}
TEST encoded_and_decoded_data_should_match(void) {
test_env env = { .limit = 1 << 11 };
theft_seed seed;
if (!get_time_seed(&seed)) { FAIL(); }
struct theft *t = theft_init(0);
struct theft_cfg cfg = {
.name = __func__,
.fun = prop_encoded_and_decoded_data_should_match,
.type_info = { &rbuf_info },
.seed = seed,
.trials = 1000000,
.env = &env,
.progress_cb = progress_cb,
};
theft_run_res res = theft_run(t, &cfg);
theft_free(t);
printf("\n");
ASSERT_EQ(THEFT_RUN_PASS, res);
PASS();
}
static size_t ceil_nine_eighths(size_t sz) {
return sz + sz/8 + (sz & 0x07 ? 1 : 0);
}
static theft_trial_res
prop_encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst(void *input) {
uint8_t output[32 * 1024];
heatshrink_encoder *hse = heatshrink_encoder_alloc(12, 4);
if (hse == NULL) { return THEFT_TRIAL_ERROR; }
rbuf *r = (rbuf *)input;
size_t input_size = 0;
HSE_sink_res esres = heatshrink_encoder_sink(hse,
r->buf, r->size, &input_size);
if (esres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; }
/* Assumes data fits in one sink, failure here means buffer must be larger. */
if (input_size != r->size) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
HSE_finish_res efres = heatshrink_encoder_finish(hse);
if (efres != HSER_FINISH_MORE) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
size_t output_size = 0;
HSE_poll_res epres = heatshrink_encoder_poll(hse,
output, sizeof(output), &output_size);
if (epres != HSER_POLL_EMPTY) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
size_t ceil_9_8s = ceil_nine_eighths(r->size);
if (output_size > ceil_9_8s) {
return THEFT_TRIAL_FAIL;
}
heatshrink_encoder_free(hse);
return THEFT_TRIAL_PASS;
}
TEST encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst(void) {
test_env env = { .limit = 1 << 11 };
theft_seed seed;
if (!get_time_seed(&seed)) { FAIL(); }
struct theft *t = theft_init(0);
struct theft_cfg cfg = {
.name = __func__,
.fun = prop_encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst,
.type_info = { &rbuf_info },
.seed = seed,
.trials = 10000,
.env = &env,
.progress_cb = progress_cb,
};
theft_run_res res = theft_run(t, &cfg);
theft_free(t);
printf("\n");
ASSERT_EQ(THEFT_RUN_PASS, res);
PASS();
}
static theft_trial_res
prop_encoder_should_always_make_progress(void *instance) {
uint8_t output[64 * 1024];
heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 4);
if (hse == NULL) { return THEFT_TRIAL_ERROR; }
rbuf *r = (rbuf *)instance;
size_t sunk = 0;
int no_progress = 0;
while (1) {
if (sunk < r->size) {
size_t input_size = 0;
HSE_sink_res esres = heatshrink_encoder_sink(hse,
&r->buf[sunk], r->size - sunk, &input_size);
if (esres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; }
sunk += input_size;
} else {
HSE_finish_res efres = heatshrink_encoder_finish(hse);
if (efres == HSER_FINISH_DONE) {
break;
} else if (efres != HSER_FINISH_MORE) {
printf("FAIL %d\n", __LINE__);
return THEFT_TRIAL_FAIL;
}
}
size_t output_size = 0;
HSE_poll_res epres = heatshrink_encoder_poll(hse,
output, sizeof(output), &output_size);
if (epres < 0) { return THEFT_TRIAL_ERROR; }
if (output_size == 0 && sunk == r->size) {
no_progress++;
if (no_progress > 2) {
return THEFT_TRIAL_FAIL;
}
} else {
no_progress = 0;
}
}
heatshrink_encoder_free(hse);
return THEFT_TRIAL_PASS;
}
TEST encoder_should_always_make_progress(void) {
test_env env = { .limit = 1 << 15 };
theft_seed seed;
if (!get_time_seed(&seed)) { FAIL(); }
struct theft *t = theft_init(0);
struct theft_cfg cfg = {
.name = __func__,
.fun = prop_encoder_should_always_make_progress,
.type_info = { &rbuf_info },
.seed = seed,
.trials = 10000,
.env = &env,
.progress_cb = progress_cb,
};
theft_run_res res = theft_run(t, &cfg);
theft_free(t);
printf("\n");
ASSERT_EQ(THEFT_RUN_PASS, res);
PASS();
}
static theft_trial_res
prop_decoder_should_always_make_progress(void *instance) {
uint8_t output[64 * 1024];
heatshrink_decoder *hsd = heatshrink_decoder_alloc(512, 8, 4);
if (hsd == NULL) { return THEFT_TRIAL_ERROR; }
rbuf *r = (rbuf *)instance;
size_t sunk = 0;
int no_progress = 0;
while (1) {
if (sunk < r->size) {
size_t input_size = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd,
&r->buf[sunk], r->size - sunk, &input_size);
if (sres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; }
sunk += input_size;
} else {
HSD_finish_res fres = heatshrink_decoder_finish(hsd);
if (fres == HSDR_FINISH_DONE) {
break;
} else if (fres != HSDR_FINISH_MORE) {
printf("FAIL %d\n", __LINE__);
return THEFT_TRIAL_FAIL;
}
}
size_t output_size = 0;
HSD_poll_res pres = heatshrink_decoder_poll(hsd,
output, sizeof(output), &output_size);
if (pres < 0) { return THEFT_TRIAL_ERROR; }
if (output_size == 0 && sunk == r->size) {
no_progress++;
if (no_progress > 2) {
return THEFT_TRIAL_FAIL;
}
} else {
no_progress = 0;
}
}
heatshrink_decoder_free(hsd);
return THEFT_TRIAL_PASS;
}
TEST decoder_should_always_make_progress(void) {
test_env env = { .limit = 1 << 15 };
theft_seed seed;
if (!get_time_seed(&seed)) { FAIL(); }
struct theft *t = theft_init(0);
struct theft_cfg cfg = {
.name = __func__,
.fun = prop_decoder_should_always_make_progress,
.type_info = { &rbuf_info },
.seed = seed,
.trials = 10000,
.env = &env,
.progress_cb = progress_cb,
};
theft_run_res res = theft_run(t, &cfg);
theft_free(t);
printf("\n");
ASSERT_EQ(THEFT_RUN_PASS, res);
PASS();
}
SUITE(properties) {
RUN_TEST(decoder_fuzzing_should_not_detect_stuck_state);
RUN_TEST(encoded_and_decoded_data_should_match);
RUN_TEST(encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst);
RUN_TEST(encoder_should_always_make_progress);
RUN_TEST(decoder_should_always_make_progress);
}
#else
struct because_iso_c_requires_at_least_one_declaration;
#endif
@@ -0,0 +1,167 @@
#include <stdint.h>
#include <ctype.h>
#include "heatshrink_encoder.h"
#include "heatshrink_decoder.h"
#include "greatest.h"
#if HEATSHRINK_DYNAMIC_ALLOC
#error HEATSHRINK_DYNAMIC_ALLOC must be false for static allocation test suite.
#endif
SUITE(integration);
/* The majority of the tests are in test_heatshrink_dynamic, because that allows
* instantiating encoders/decoders with different settings at run-time. */
static heatshrink_encoder hse;
static heatshrink_decoder hsd;
static void fill_with_pseudorandom_letters(uint8_t *buf, uint16_t size, uint32_t seed) {
uint64_t rn = 9223372036854775783; /* prime under 2^64 */
for (int i=0; i<size; i++) {
rn = rn*seed + seed;
buf[i] = (rn % 26) + 'a';
}
}
static void dump_buf(char *name, uint8_t *buf, uint16_t count) {
for (int i=0; i<count; i++) {
uint8_t c = (uint8_t)buf[i];
printf("%s %d: 0x%02x ('%c')\n", name, i, c, isprint(c) ? c : '.');
}
}
static int compress_and_expand_and_check(uint8_t *input, uint32_t input_size, int log_lvl) {
heatshrink_encoder_reset(&hse);
heatshrink_decoder_reset(&hsd);
size_t comp_sz = input_size + (input_size/2) + 4;
size_t decomp_sz = input_size + (input_size/2) + 4;
uint8_t *comp = malloc(comp_sz);
uint8_t *decomp = malloc(decomp_sz);
if (comp == NULL) FAILm("malloc fail");
if (decomp == NULL) FAILm("malloc fail");
memset(comp, 0, comp_sz);
memset(decomp, 0, decomp_sz);
size_t count = 0;
if (log_lvl > 1) {
printf("\n^^ COMPRESSING\n");
dump_buf("input", input, input_size);
}
uint32_t sunk = 0;
uint32_t polled = 0;
while (sunk < input_size) {
ASSERT(heatshrink_encoder_sink(&hse, &input[sunk], input_size - sunk, &count) >= 0);
sunk += count;
if (log_lvl > 1) printf("^^ sunk %zd\n", count);
if (sunk == input_size) {
ASSERT_EQ(HSER_FINISH_MORE, heatshrink_encoder_finish(&hse));
}
HSE_poll_res pres;
do { /* "turn the crank" */
pres = heatshrink_encoder_poll(&hse, &comp[polled], comp_sz - polled, &count);
ASSERT(pres >= 0);
polled += count;
if (log_lvl > 1) printf("^^ polled %zd\n", count);
} while (pres == HSER_POLL_MORE);
ASSERT_EQ(HSER_POLL_EMPTY, pres);
if (polled >= comp_sz) FAILm("compression should never expand that much");
if (sunk == input_size) {
ASSERT_EQ(HSER_FINISH_DONE, heatshrink_encoder_finish(&hse));
}
}
if (log_lvl > 0) printf("in: %u compressed: %u ", input_size, polled);
uint32_t compressed_size = polled;
sunk = 0;
polled = 0;
if (log_lvl > 1) {
printf("\n^^ DECOMPRESSING\n");
dump_buf("comp", comp, compressed_size);
}
while (sunk < compressed_size) {
ASSERT(heatshrink_decoder_sink(&hsd, &comp[sunk], compressed_size - sunk, &count) >= 0);
sunk += count;
if (log_lvl > 1) printf("^^ sunk %zd\n", count);
if (sunk == compressed_size) {
ASSERT_EQ(HSDR_FINISH_MORE, heatshrink_decoder_finish(&hsd));
}
HSD_poll_res pres;
do {
pres = heatshrink_decoder_poll(&hsd, &decomp[polled],
decomp_sz - polled, &count);
ASSERT(pres >= 0);
polled += count;
if (log_lvl > 1) printf("^^ polled %zd\n", count);
} while (pres == HSDR_POLL_MORE);
ASSERT_EQ(HSDR_POLL_EMPTY, pres);
if (sunk == compressed_size) {
HSD_finish_res fres = heatshrink_decoder_finish(&hsd);
ASSERT_EQ(HSDR_FINISH_DONE, fres);
}
if (polled > input_size) {
FAILm("Decompressed data is larger than original input");
}
}
if (log_lvl > 0) printf("decompressed: %u\n", polled);
if (polled != input_size) {
FAILm("Decompressed length does not match original input length");
}
if (log_lvl > 1) dump_buf("decomp", decomp, polled);
for (size_t i=0; i<input_size; i++) {
if (input[i] != decomp[i]) {
printf("*** mismatch at %zd\n", i);
if (0) {
for (size_t j=0; j<=/*i*/ input_size; j++) {
printf("in[%zd] == 0x%02x ('%c') => out[%zd] == 0x%02x ('%c')\n",
j, input[j], isprint(input[j]) ? input[j] : '.',
j, decomp[j], isprint(decomp[j]) ? decomp[j] : '.');
}
}
}
ASSERT_EQ(input[i], decomp[i]);
}
free(comp);
free(decomp);
PASS();
}
TEST pseudorandom_data_should_match(uint32_t size, uint32_t seed) {
uint8_t input[size];
fill_with_pseudorandom_letters(input, size, seed);
return compress_and_expand_and_check(input, size, 0);
}
SUITE(integration) {
#if __STDC_VERSION__ >= 19901L
for (uint32_t size=1; size < 64*1024; size <<= 1) {
if (GREATEST_IS_VERBOSE()) printf(" -- size %u\n", size);
for (uint32_t seed=1; seed<=100; seed++) {
if (GREATEST_IS_VERBOSE()) printf(" -- seed %u\n", seed);
RUN_TESTp(pseudorandom_data_should_match, size, seed);
}
}
#endif
}
/* Add all the definitions that need to be in the test runner's main file. */
GREATEST_MAIN_DEFS();
int main(int argc, char **argv) {
GREATEST_MAIN_BEGIN(); /* command-line arguments, initialization. */
printf("INPUT_BUFFER_SIZE: %u\n", HEATSHRINK_STATIC_INPUT_BUFFER_SIZE);
printf("WINDOW_BITS: %u\n", HEATSHRINK_STATIC_WINDOW_BITS);
printf("LOOKAHEAD_BITS: %u\n", HEATSHRINK_STATIC_LOOKAHEAD_BITS);
printf("sizeof(heatshrink_encoder): %zd\n", sizeof(heatshrink_encoder));
printf("sizeof(heatshrink_decoder): %zd\n", sizeof(heatshrink_decoder));
RUN_SUITE(integration);
GREATEST_MAIN_END(); /* display results */
}
+1
View File
@@ -0,0 +1 @@
mkupgimg
+5
View File
@@ -0,0 +1,5 @@
mkupgimg: mkupgimg.c
$(CC) -o $@ $^
clean:
rm -f mkupgimg
+92
View File
@@ -0,0 +1,92 @@
#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
typedef struct __attribute__((packed)) {
char magic[4];
char tag[28];
int32_t len1;
int32_t len2;
} Header;
int openFile(char *file) {
int r=open(file, O_RDONLY);
if (r<=0) {
perror(file);
exit(1);
}
return r;
}
int32_t intToEsp(int32_t v) {
int32_t ret;
char *p=(char*)&ret;
*p++=(v>>0)&0xff;
*p++=(v>>8)&0xff;
*p++=(v>>16)&0xff;
*p++=(v>>24)&0xff;
return ret;
}
size_t fileLen(int f) {
size_t r;
r=lseek(f, 0, SEEK_END);
lseek(f, 0, SEEK_SET);
return r;
}
int main(int argc, char **argv) {
int u1, u2;
size_t l1, l2;
int of;
char *fc1, *fc2;
Header hdr;
if (argc!=5) {
printf("Usage: %s user1.bin user2.bin tagname outfile.bin\n", argv[0]);
exit(1);
}
if (strlen(argv[3])>27) {
printf("Error: Tag can't be longer than 27 characters.\n");
exit(1);
}
memset(&hdr, 0, sizeof(hdr));
memcpy(hdr.magic, "EHUG", 4);
strcpy(hdr.tag, argv[3]);
u1=openFile(argv[1]);
u2=openFile(argv[2]);
l1=fileLen(u1);
l2=fileLen(u2);
hdr.len1=intToEsp(l1);
hdr.len2=intToEsp(l2);
fc1=malloc(l1);
fc2=malloc(l2);
if (read(u1, fc1, l1)!=l1) {
perror(argv[1]);
exit(1);
}
if (read(u2, fc2, l2)!=l2) {
perror(argv[2]);
exit(1);
}
close(u1);
close(u2);
of=open(argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666);
if (of<=0) {
perror(argv[4]);
exit(1);
}
write(of, &hdr, sizeof(hdr));
write(of, fc1, l1);
write(of, fc2, l2);
printf("Header: %d bytes, user1: %d bytes, user2: %d bytes.\n", sizeof(hdr), (int)l1, (int)l2);
close(of);
exit(0);
}
+331
View File
@@ -0,0 +1,331 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
/*
This is a 'captive portal' DNS server: it basically replies with a fixed IP (in this case:
the one of the SoftAP interface of this ESP module) for any and all DNS queries. This can
be used to send mobile phones, tablets etc which connect to the ESP in AP mode directly to
the internal webserver.
*/
#include <esp8266.h>
#ifdef FREERTOS
#include "espressif/esp_common.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "lwip/sockets.h"
#include "lwip/err.h"
static int sockFd;
#endif
#define DNS_LEN 512
typedef struct __attribute__ ((packed)) {
uint16_t id;
uint8_t flags;
uint8_t rcode;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
} DnsHeader;
typedef struct __attribute__ ((packed)) {
uint8_t len;
uint8_t data;
} DnsLabel;
typedef struct __attribute__ ((packed)) {
//before: label
uint16_t type;
uint16_t class;
} DnsQuestionFooter;
typedef struct __attribute__ ((packed)) {
//before: label
uint16_t type;
uint16_t class;
uint32_t ttl;
uint16_t rdlength;
//after: rdata
} DnsResourceFooter;
typedef struct __attribute__ ((packed)) {
uint16_t prio;
uint16_t weight;
} DnsUriHdr;
#define FLAG_QR (1<<7)
#define FLAG_AA (1<<2)
#define FLAG_TC (1<<1)
#define FLAG_RD (1<<0)
#define QTYPE_A 1
#define QTYPE_NS 2
#define QTYPE_CNAME 5
#define QTYPE_SOA 6
#define QTYPE_WKS 11
#define QTYPE_PTR 12
#define QTYPE_HINFO 13
#define QTYPE_MINFO 14
#define QTYPE_MX 15
#define QTYPE_TXT 16
#define QTYPE_URI 256
#define QCLASS_IN 1
#define QCLASS_ANY 255
#define QCLASS_URI 256
//Function to put unaligned 16-bit network values
static void ICACHE_FLASH_ATTR setn16(void *pp, int16_t n) {
char *p=pp;
*p++=(n>>8);
*p++=(n&0xff);
}
//Function to put unaligned 32-bit network values
static void ICACHE_FLASH_ATTR setn32(void *pp, int32_t n) {
char *p=pp;
*p++=(n>>24)&0xff;
*p++=(n>>16)&0xff;
*p++=(n>>8)&0xff;
*p++=(n&0xff);
}
static uint16_t ICACHE_FLASH_ATTR my_ntohs(uint16_t *in) {
char *p=(char*)in;
return ((p[0]<<8)&0xff00)|(p[1]&0xff);
}
//Parses a label into a C-string containing a dotted
//Returns pointer to start of next fields in packet
static char* ICACHE_FLASH_ATTR labelToStr(char *packet, char *labelPtr, int packetSz, char *res, int resMaxLen) {
int i, j, k;
char *endPtr=NULL;
i=0;
do {
if ((*labelPtr&0xC0)==0) {
j=*labelPtr++; //skip past length
//Add separator period if there already is data in res
if (i<resMaxLen && i!=0) res[i++]='.';
//Copy label to res
for (k=0; k<j; k++) {
if ((labelPtr-packet)>packetSz) return NULL;
if (i<resMaxLen) res[i++]=*labelPtr++;
}
} else if ((*labelPtr&0xC0)==0xC0) {
//Compressed label pointer
endPtr=labelPtr+2;
int offset=my_ntohs(((uint16_t *)labelPtr))&0x3FFF;
//Check if offset points to somewhere outside of the packet
if (offset>packetSz) return NULL;
labelPtr=&packet[offset];
}
//check for out-of-bound-ness
if ((labelPtr-packet)>packetSz) return NULL;
} while (*labelPtr!=0);
res[i]=0; //zero-terminate
if (endPtr==NULL) endPtr=labelPtr+1;
return endPtr;
}
//Converts a dotted hostname to the weird label form dns uses.
static char ICACHE_FLASH_ATTR *strToLabel(char *str, char *label, int maxLen) {
char *len=label; //ptr to len byte
char *p=label+1; //ptr to next label byte to be written
while (1) {
if (*str=='.' || *str==0) {
*len=((p-len)-1); //write len of label bit
len=p; //pos of len for next part
p++; //data ptr is one past len
if (*str==0) break; //done
str++;
} else {
*p++=*str++; //copy byte
// if ((p-label)>maxLen) return NULL; //check out of bounds
}
}
*len=0;
return p; //ptr to first free byte in resp
}
//Receive a DNS packet and maybe send a response back
#ifndef FREERTOS
static void ICACHE_FLASH_ATTR captdnsRecv(void* arg, char *pusrdata, unsigned short length) {
struct espconn *conn=(struct espconn *)arg;
#else
static void ICACHE_FLASH_ATTR captdnsRecv(struct sockaddr_in *premote_addr, char *pusrdata, unsigned short length) {
#endif
char buff[DNS_LEN];
char reply[DNS_LEN];
int i;
char *rend=&reply[length];
char *p=pusrdata;
DnsHeader *hdr=(DnsHeader*)p;
DnsHeader *rhdr=(DnsHeader*)&reply[0];
p+=sizeof(DnsHeader);
// httpd_printf("DNS packet: id 0x%X flags 0x%X rcode 0x%X qcnt %d ancnt %d nscount %d arcount %d len %d\n",
// my_ntohs(&hdr->id), hdr->flags, hdr->rcode, my_ntohs(&hdr->qdcount), my_ntohs(&hdr->ancount), my_ntohs(&hdr->nscount), my_ntohs(&hdr->arcount), length);
//Some sanity checks:
if (length>DNS_LEN) return; //Packet is longer than DNS implementation allows
if (length<sizeof(DnsHeader)) return; //Packet is too short
if (hdr->ancount || hdr->nscount || hdr->arcount) return; //this is a reply, don't know what to do with it
if (hdr->flags&FLAG_TC) return; //truncated, can't use this
//Reply is basically the request plus the needed data
memcpy(reply, pusrdata, length);
rhdr->flags|=FLAG_QR;
for (i=0; i<my_ntohs(&hdr->qdcount); i++) {
//Grab the labels in the q string
p=labelToStr(pusrdata, p, length, buff, sizeof(buff));
if (p==NULL) return;
DnsQuestionFooter *qf=(DnsQuestionFooter*)p;
p+=sizeof(DnsQuestionFooter);
httpd_printf("DNS: Q (type 0x%X class 0x%X) for %s\n", my_ntohs(&qf->type), my_ntohs(&qf->class), buff);
if (my_ntohs(&qf->type)==QTYPE_A) {
//They want to know the IPv4 address of something.
//Build the response.
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
if (rend==NULL) return;
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
rend+=sizeof(DnsResourceFooter);
setn16(&rf->type, QTYPE_A);
setn16(&rf->class, QCLASS_IN);
setn32(&rf->ttl, 0);
setn16(&rf->rdlength, 4); //IPv4 addr is 4 bytes;
//Grab the current IP of the softap interface
struct ip_info info;
wifi_get_ip_info(SOFTAP_IF, &info);
*rend++=ip4_addr1(&info.ip);
*rend++=ip4_addr2(&info.ip);
*rend++=ip4_addr3(&info.ip);
*rend++=ip4_addr4(&info.ip);
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
// httpd_printf("Added A rec to resp. Resp len is %d\n", (rend-reply));
} else if (my_ntohs(&qf->type)==QTYPE_NS) {
//Give ns server. Basically can be whatever we want because it'll get resolved to our IP later anyway.
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
rend+=sizeof(DnsResourceFooter);
setn16(&rf->type, QTYPE_NS);
setn16(&rf->class, QCLASS_IN);
setn16(&rf->ttl, 0);
setn16(&rf->rdlength, 4);
*rend++=2;
*rend++='n';
*rend++='s';
*rend++=0;
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
// httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
} else if (my_ntohs(&qf->type)==QTYPE_URI) {
//Give uri to us
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
rend+=sizeof(DnsResourceFooter);
DnsUriHdr *uh=(DnsUriHdr *)rend;
rend+=sizeof(DnsUriHdr);
setn16(&rf->type, QTYPE_URI);
setn16(&rf->class, QCLASS_URI);
setn16(&rf->ttl, 0);
setn16(&rf->rdlength, 4+16);
setn16(&uh->prio, 10);
setn16(&uh->weight, 1);
memcpy(rend, "http://esp.nonet", 16);
rend+=16;
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
// httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
}
}
//Send the response
#ifndef FREERTOS
remot_info *remInfo=NULL;
//Send data to port/ip it came from, not to the ip/port we listen on.
if (espconn_get_connection_info(conn, &remInfo, 0)==ESPCONN_OK) {
conn->proto.udp->remote_port=remInfo->remote_port;
memcpy(conn->proto.udp->remote_ip, remInfo->remote_ip, sizeof(remInfo->remote_ip));
}
espconn_sendto(conn, (uint8*)reply, rend-reply);
#else
sendto(sockFd,(uint8*)reply, rend-reply, 0, (struct sockaddr *)premote_addr, sizeof(struct sockaddr_in));
#endif
}
#ifdef FREERTOS
static void captdnsTask(void *pvParameters) {
struct sockaddr_in server_addr;
int32 ret;
struct sockaddr_in from;
socklen_t fromlen;
struct ip_info ipconfig;
char udp_msg[DNS_LEN];
memset(&ipconfig, 0, sizeof(ipconfig));
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(53);
server_addr.sin_len = sizeof(server_addr);
do {
sockFd=socket(AF_INET, SOCK_DGRAM, 0);
if (sockFd==-1) {
httpd_printf("captdns_task failed to create sock!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while (sockFd==-1);
do {
ret=bind(sockFd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret!=0) {
httpd_printf("captdns_task failed to bind sock!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while (ret!=0);
httpd_printf("CaptDNS inited.\n");
while(1) {
memset(&from, 0, sizeof(from));
fromlen=sizeof(struct sockaddr_in);
ret=recvfrom(sockFd, (u8 *)udp_msg, DNS_LEN, 0,(struct sockaddr *)&from,(socklen_t *)&fromlen);
if (ret>0) captdnsRecv(&from,udp_msg,ret);
}
close(sockFd);
vTaskDelete(NULL);
}
void captdnsInit(void) {
xTaskCreate(captdnsTask, (const signed char *)"captdns_task", 1200, NULL, 3, NULL);
}
#else
void ICACHE_FLASH_ATTR captdnsInit(void) {
static struct espconn conn;
static esp_udp udpconn;
conn.type=ESPCONN_UDP;
conn.proto.udp=&udpconn;
conn.proto.udp->local_port = 53;
espconn_regist_recvcb(&conn, captdnsRecv);
espconn_create(&conn);
}
#endif
+323
View File
@@ -0,0 +1,323 @@
/*
Some flash handling cgi routines. Used for reading the existing flash and updating the ESPFS image.
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "cgiflash.h"
#include "espfs.h"
#include "cgiflash.h"
#include "espfs.h"
//#include <osapi.h>
#include "cgiflash.h"
#include "espfs.h"
#ifndef UPGRADE_FLAG_FINISH
#define UPGRADE_FLAG_FINISH 0x02
#endif
// Check that the header of the firmware blob looks like actual firmware...
static int ICACHE_FLASH_ATTR checkBinHeader(void *buf) {
uint8_t *cd = (uint8_t *)buf;
if (cd[0] != 0xEA) return 0;
if (cd[1] != 4 || cd[2] > 3 || cd[3] > 0x40) return 0;
if (((uint16_t *)buf)[3] != 0x4010) return 0;
if (((uint32_t *)buf)[2] != 0) return 0;
return 1;
}
static int ICACHE_FLASH_ATTR checkEspfsHeader(void *buf) {
if (memcmp(buf, "ESfs", 4)!=0) return 0;
return 1;
}
// Cgi to query which firmware needs to be uploaded next
int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) {
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
uint8 id = system_upgrade_userbin_check();
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdHeader(connData, "Content-Length", "9");
httpdEndHeaders(connData);
char *next = id == 1 ? "user1.bin" : "user2.bin";
httpdSend(connData, next, -1);
httpd_printf("Next firmware: %s (got %d)\n", next, id);
return HTTPD_CGI_DONE;
}
//Cgi that reads the SPI flash. Assumes 512KByte flash.
//ToDo: Figure out real flash size somehow?
int ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) {
int *pos=(int *)&connData->cgiData;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
if (*pos==0) {
httpd_printf("Start flash download.\n");
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "application/bin");
httpdEndHeaders(connData);
*pos=0x40200000;
return HTTPD_CGI_MORE;
}
//Send 1K of flash per call. We will get called again if we haven't sent 512K yet.
httpdSend(connData, (char*)(*pos), 1024);
*pos+=1024;
if (*pos>=0x40200000+(512*1024)) return HTTPD_CGI_DONE; else return HTTPD_CGI_MORE;
}
//Cgi that allows the firmware to be replaced via http POST This takes
//a direct POST from e.g. Curl or a Javascript AJAX call with either the
//firmware given by cgiGetFirmwareNext or an OTA upgrade image.
//Because we don't have the buffer to allocate an entire sector but will
//have to buffer some data because the post buffer may be misaligned, we
//write SPI data in pages. The page size is a software thing, not
//a hardware one.
#define PAGELEN 64
#define FLST_START 0
#define FLST_WRITE 1
#define FLST_SKIP 2
#define FLST_DONE 3
#define FLST_ERROR 4
#define FILETYPE_ESPFS 0
#define FILETYPE_FLASH 1
#define FILETYPE_OTA 2
typedef struct {
int state;
int filetype;
int flashPos;
char pageData[PAGELEN];
int pagePos;
int address;
int len;
int skip;
char *err;
} UploadState;
typedef struct __attribute__((packed)) {
char magic[4];
char tag[28];
int32_t len1;
int32_t len2;
} OtaHeader;
int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) {
CgiUploadFlashDef *def=(CgiUploadFlashDef*)connData->cgiArg;
UploadState *state=(UploadState *)connData->cgiData;
int len;
char buff[128];
if (connData->conn==NULL) {
//Connection aborted. Clean up.
if (state!=NULL) free(state);
return HTTPD_CGI_DONE;
}
if (state==NULL) {
//First call. Allocate and initialize state variable.
httpd_printf("Firmware upload cgi start.\n");
state=malloc(sizeof(UploadState));
if (state==NULL) {
httpd_printf("Can't allocate firmware upload struct!\n");
return HTTPD_CGI_DONE;
}
memset(state, 0, sizeof(UploadState));
state->state=FLST_START;
connData->cgiData=state;
state->err="Premature end";
}
char *data=connData->post->buff;
int dataLen=connData->post->buffLen;
while (dataLen!=0) {
if (state->state==FLST_START) {
//First call. Assume the header of whatever we're uploading already is in the POST buffer.
if (def->type==CGIFLASH_TYPE_FW && memcmp(data, "EHUG", 4)==0) {
//Type is combined flash1/flash2 file
OtaHeader *h=(OtaHeader*)data;
strncpy(buff, h->tag, 27);
buff[27]=0;
if (strcmp(buff, def->tagName)!=0) {
httpd_printf("OTA tag mismatch! Current=`%s` uploaded=`%s`.\n",
def->tagName, buff);
len=httpdFindArg(connData->getArgs, "force", buff, sizeof(buff));
if (len!=-1 && atoi(buff)) {
httpd_printf("Forcing firmware flash.\n");
} else {
state->err="Firmware not intended for this device!\n";
state->state=FLST_ERROR;
}
}
if (state->state!=FLST_ERROR && connData->post->len > def->fwSize*2+sizeof(OtaHeader)) {
state->err="Firmware image too large";
state->state=FLST_ERROR;
}
if (state->state!=FLST_ERROR) {
//Flash header seems okay.
dataLen-=sizeof(OtaHeader); //skip header when parsing data
data+=sizeof(OtaHeader);
if (system_upgrade_userbin_check()==1) {
httpd_printf("Flashing user1.bin from ota image\n");
state->len=h->len1;
state->skip=h->len2;
state->state=FLST_WRITE;
state->address=def->fw1Pos;
} else {
httpd_printf("Flashing user2.bin from ota image\n");
state->len=h->len2;
state->skip=h->len1;
state->state=FLST_SKIP;
state->address=def->fw2Pos;
}
}
} else if (def->type==CGIFLASH_TYPE_FW && checkBinHeader(connData->post->buff)) {
if (connData->post->len > def->fwSize) {
state->err="Firmware image too large";
state->state=FLST_ERROR;
} else {
state->len=connData->post->len;
state->address=def->fw1Pos;
state->state=FLST_WRITE;
}
} else if (def->type==CGIFLASH_TYPE_ESPFS && checkEspfsHeader(connData->post->buff)) {
if (connData->post->len > def->fwSize) {
state->err="Firmware image too large";
state->state=FLST_ERROR;
} else {
state->len=connData->post->len;
state->address=def->fw1Pos;
state->state=FLST_WRITE;
}
} else {
state->err="Invalid flash image type!";
state->state=FLST_ERROR;
httpd_printf("Did not recognize flash image type!\n");
}
} else if (state->state==FLST_SKIP) {
//Skip bytes without doing anything with them
if (state->skip>dataLen) {
//Skip entire buffer
state->skip-=dataLen;
dataLen=0;
} else {
//Only skip part of buffer
dataLen-=state->skip;
data+=state->skip;
state->skip=0;
if (state->len) state->state=FLST_WRITE; else state->state=FLST_DONE;
}
} else if (state->state==FLST_WRITE) {
//Copy bytes to page buffer, and if page buffer is full, flash the data.
//First, calculate the amount of bytes we need to finish the page buffer.
int lenLeft=PAGELEN-state->pagePos;
if (state->len<lenLeft) lenLeft=state->len; //last buffer can be a cut-off one
//See if we need to write the page.
if (dataLen<lenLeft) {
//Page isn't done yet. Copy data to buffer and exit.
memcpy(&state->pageData[state->pagePos], data, dataLen);
state->pagePos+=dataLen;
state->len-=dataLen;
dataLen=0;
} else {
//Finish page; take data we need from post buffer
memcpy(&state->pageData[state->pagePos], data, lenLeft);
data+=lenLeft;
dataLen-=lenLeft;
state->pagePos+=lenLeft;
state->len-=lenLeft;
//Erase sector, if needed
if ((state->address&(SPI_FLASH_SEC_SIZE-1))==0) {
spi_flash_erase_sector(state->address/SPI_FLASH_SEC_SIZE);
}
//Write page
//httpd_printf("Writing %d bytes of data to SPI pos 0x%x...\n", state->pagePos, state->address);
spi_flash_write(state->address, (uint32 *)state->pageData, state->pagePos);
state->address+=PAGELEN;
state->pagePos=0;
if (state->len==0) {
//Done.
if (state->skip) state->state=FLST_SKIP; else state->state=FLST_DONE;
}
}
} else if (state->state==FLST_DONE) {
httpd_printf("Huh? %d bogus bytes received after data received.\n", dataLen);
//Ignore those bytes.
dataLen=0;
} else if (state->state==FLST_ERROR) {
//Just eat up any bytes we receive.
dataLen=0;
}
}
if (connData->post->len==connData->post->received) {
//We're done! Format a response.
httpd_printf("Upload done. Sending response.\n");
httpdStartResponse(connData, state->state==FLST_ERROR?400:200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
if (state->state!=FLST_DONE) {
httpdSend(connData, "Firmware image error:", -1);
httpdSend(connData, state->err, -1);
httpdSend(connData, "\n", -1);
}
free(state);
return HTTPD_CGI_DONE;
}
return HTTPD_CGI_MORE;
}
static os_timer_t resetTimer;
static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
system_upgrade_flag_set(UPGRADE_FLAG_FINISH);
system_upgrade_reboot();
}
// Handle request to reboot into the new firmware
int ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) {
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
// TODO: sanity-check that the 'next' partition actually contains something that looks like
// valid firmware
//Do reboot in a timer callback so we still have time to send the response.
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, 200, 0);
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
httpdSend(connData, "Rebooting...", -1);
return HTTPD_CGI_DONE;
}
+354
View File
@@ -0,0 +1,354 @@
/*
Websocket support for esphttpd. Inspired by https://github.com/dangrie158/ESP-8266-WebSocket
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "httpd.h"
#include "sha1.h"
#include "base64.h"
#include "cgiwebsocket.h"
#define WS_KEY_IDENTIFIER "Sec-WebSocket-Key: "
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
/* from IEEE RFC6455 sec 5.2
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
*/
#define FLAG_FIN (1 << 7)
#define OPCODE_CONTINUE 0x0
#define OPCODE_TEXT 0x1
#define OPCODE_BINARY 0x2
#define OPCODE_CLOSE 0x8
#define OPCODE_PING 0x9
#define OPCODE_PONG 0xA
#define FLAGS_MASK ((uint8_t)0xF0)
#define OPCODE_MASK ((uint8_t)0x0F)
#define IS_MASKED ((uint8_t)(1<<7))
#define PAYLOAD_MASK ((uint8_t)0x7F)
typedef struct WebsockFrame WebsockFrame;
#define ST_FLAGS 0
#define ST_LEN0 1
#define ST_LEN1 2
#define ST_LEN2 3
//...
#define ST_LEN8 9
#define ST_MASK1 10
#define ST_MASK4 13
#define ST_PAYLOAD 14
struct WebsockFrame {
uint8_t flags;
uint8_t len8;
uint64_t len;
uint8_t mask[4];
};
struct WebsockPriv {
struct WebsockFrame fr;
uint8_t maskCtr;
uint8 frameCont;
uint8 closedHere;
int wsStatus;
Websock *next; //in linked list
};
static Websock *llStart=NULL;
static int ICACHE_FLASH_ATTR sendFrameHead(Websock *ws, int opcode, int len) {
char buf[14];
int i=0;
buf[i++]=opcode;
if (len>65535) {
buf[i++]=127;
buf[i++]=0; buf[i++]=0; buf[i++]=0; buf[i++]=0;
buf[i++]=len>>24;
buf[i++]=len>>16;
buf[i++]=len>>8;
buf[i++]=len;
} else if (len>125) {
buf[i++]=126;
buf[i++]=len>>8;
buf[i++]=len;
} else {
buf[i++]=len;
}
httpd_printf("WS: Sent frame head for payload of %d bytes.\n", len);
return httpdSend(ws->conn, buf, i);
}
int ICACHE_FLASH_ATTR cgiWebsocketSend(Websock *ws, char *data, int len, int flags) {
int r=0;
int fl=0;
if (flags&WEBSOCK_FLAG_BIN) fl=OPCODE_BINARY; else fl=OPCODE_TEXT;
if (!(flags&WEBSOCK_FLAG_CONT)) fl|=FLAG_FIN;
sendFrameHead(ws, fl, len);
if (len!=0) r=httpdSend(ws->conn, data, len);
httpdFlushSendBuffer(ws->conn);
return r;
}
//Broadcast data to all websockets at a specific url. Returns the amount of connections sent to.
int ICACHE_FLASH_ATTR cgiWebsockBroadcast(char *resource, char *data, int len, int flags) {
//This is majorly broken (and actually, always, it just tended to work because the circumstances
//were juuuust right). Because the socket is used outside of the httpd send/receive context, it
//will not have an associated send buffer. This means httpdSend will write to a dangling pointer!
//Disabled for now. If you really need this, open an issue on github or otherwise poke me and I'll
//see what I can do.
/*
Websock *lw=llStart;
int ret=0;
while (lw!=NULL) {
if (strcmp(lw->conn->url, resource)==0) {
cgiWebsocketSend(lw, data, len, flags);
ret++;
}
lw=lw->priv->next;
}
return ret;
*/
return 0;
}
void ICACHE_FLASH_ATTR cgiWebsocketClose(Websock *ws, int reason) {
char rs[2]={reason>>8, reason&0xff};
sendFrameHead(ws, FLAG_FIN|OPCODE_CLOSE, 2);
httpdSend(ws->conn, rs, 2);
ws->priv->closedHere=1;
httpdFlushSendBuffer(ws->conn);
}
static void ICACHE_FLASH_ATTR websockFree(Websock *ws) {
httpd_printf("Ws: Free\n");
if (ws->closeCb) ws->closeCb(ws);
//Clean up linked list
if (llStart==ws) {
llStart=ws->priv->next;
} else if (llStart) {
Websock *lws=llStart;
//Find ws that links to this one.
while (lws!=NULL && lws->priv->next!=ws) lws=lws->priv->next;
if (lws!=NULL) lws->priv->next=ws->priv->next;
}
if (ws->priv) free(ws->priv);
}
int ICACHE_FLASH_ATTR cgiWebSocketRecv(HttpdConnData *connData, char *data, int len) {
int i, j, sl;
int r=HTTPD_CGI_MORE;
int wasHeaderByte;
Websock *ws=(Websock*)connData->cgiData;
for (i=0; i<len; i++) {
// httpd_printf("Ws: State %d byte 0x%02X\n", ws->priv->wsStatus, data[i]);
wasHeaderByte=1;
if (ws->priv->wsStatus==ST_FLAGS) {
ws->priv->maskCtr=0;
ws->priv->frameCont=0;
ws->priv->fr.flags=(uint8_t)data[i];
ws->priv->wsStatus=ST_LEN0;
} else if (ws->priv->wsStatus==ST_LEN0) {
ws->priv->fr.len8=(uint8_t)data[i];
if ((ws->priv->fr.len8&127)>=126) {
ws->priv->fr.len=0;
ws->priv->wsStatus=ST_LEN1;
} else {
ws->priv->fr.len=ws->priv->fr.len8&127;
ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD;
}
} else if (ws->priv->wsStatus<=ST_LEN8) {
ws->priv->fr.len=(ws->priv->fr.len<<8)|data[i];
if (((ws->priv->fr.len8&127)==126 && ws->priv->wsStatus==ST_LEN2) || ws->priv->wsStatus==ST_LEN8) {
ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD;
} else {
ws->priv->wsStatus++;
}
} else if (ws->priv->wsStatus<=ST_MASK4) {
ws->priv->fr.mask[ws->priv->wsStatus-ST_MASK1]=data[i];
ws->priv->wsStatus++;
} else {
//Was a payload byte.
wasHeaderByte=0;
}
if (ws->priv->wsStatus==ST_PAYLOAD && wasHeaderByte) {
//We finished parsing the header, but i still is on the last header byte. Move one forward so
//the payload code works as usual.
i++;
}
//Also finish parsing frame if we haven't received any payload bytes yet, but the length of the frame
//is zero.
if (ws->priv->wsStatus==ST_PAYLOAD) {
//Okay, header is in; this is a data byte. We're going to process all the data bytes we have
//received here at the same time; no more byte iterations till the end of this frame.
//First, unmask the data
sl=len-i;
httpd_printf("Ws: Frame payload. wasHeaderByte %d fr.len %d sl %d cmd 0x%x\n", wasHeaderByte, (int)ws->priv->fr.len, (int)sl, ws->priv->fr.flags);
if (sl > ws->priv->fr.len) sl=ws->priv->fr.len;
for (j=0; j<sl; j++) data[i+j]^=(ws->priv->fr.mask[(ws->priv->maskCtr++)&3]);
// httpd_printf("Unmasked: ");
// for (j=0; j<sl; j++) httpd_printf("%02X ", data[i+j]&0xff);
// httpd_printf("\n");
//Inspect the header to see what we need to do.
if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_PING) {
if (ws->priv->fr.len>125) {
if (!ws->priv->frameCont) cgiWebsocketClose(ws, 1002);
r=HTTPD_CGI_DONE;
break;
} else {
if (!ws->priv->frameCont) sendFrameHead(ws, OPCODE_PONG|FLAG_FIN, ws->priv->fr.len);
if (sl>0) httpdSend(ws->conn, data+i, sl);
}
} else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_TEXT ||
(ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY ||
(ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CONTINUE) {
if (sl>ws->priv->fr.len) sl=ws->priv->fr.len;
if (!(ws->priv->fr.len8&IS_MASKED)) {
//We're a server; client should send us masked packets.
cgiWebsocketClose(ws, 1002);
r=HTTPD_CGI_DONE;
break;
} else {
int flags=0;
if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY) flags|=WEBSOCK_FLAG_BIN;
if ((ws->priv->fr.flags&FLAG_FIN)==0) flags|=WEBSOCK_FLAG_CONT;
if (ws->recvCb) ws->recvCb(ws, data+i, sl, flags);
}
} else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CLOSE) {
httpd_printf("WS: Got close frame\n");
if (!ws->priv->closedHere) {
httpd_printf("WS: Sending response close frame\n");
cgiWebsocketClose(ws, ((data[i]<<8)&0xff00)+(data[i+1]&0xff));
}
r=HTTPD_CGI_DONE;
break;
} else {
if (!ws->priv->frameCont) httpd_printf("WS: Unknown opcode 0x%X\n", ws->priv->fr.flags&OPCODE_MASK);
}
i+=sl-1;
ws->priv->fr.len-=sl;
if (ws->priv->fr.len==0) {
ws->priv->wsStatus=ST_FLAGS; //go receive next frame
} else {
ws->priv->frameCont=1; //next payload is continuation of this frame.
}
}
}
if (r==HTTPD_CGI_DONE) {
//We're going to tell the main webserver we're done. The webserver expects us to clean up by ourselves
//we're chosing to be done. Do so.
websockFree(ws);
free(connData->cgiData);
connData->cgiData=NULL;
}
return r;
}
//Websocket 'cgi' implementation
int ICACHE_FLASH_ATTR cgiWebsocket(HttpdConnData *connData) {
char buff[256];
int i;
sha1nfo s;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
httpd_printf("WS: Cleanup\n");
if (connData->cgiData) {
Websock *ws=(Websock*)connData->cgiData;
websockFree(ws);
free(connData->cgiData);
connData->cgiData=NULL;
}
return HTTPD_CGI_DONE;
}
if (connData->cgiData==NULL) {
// httpd_printf("WS: First call\n");
//First call here. Check if client headers are OK, send server header.
i=httpdGetHeader(connData, "Upgrade", buff, sizeof(buff)-1);
httpd_printf("WS: Upgrade: %s\n", buff);
if (i && strcasecmp(buff, "websocket")==0) {
i=httpdGetHeader(connData, "Sec-WebSocket-Key", buff, sizeof(buff)-1);
if (i) {
// httpd_printf("WS: Key: %s\n", buff);
//Seems like a WebSocket connection.
// Alloc structs
connData->cgiData=malloc(sizeof(Websock));
memset(connData->cgiData, 0, sizeof(Websock));
Websock *ws=(Websock*)connData->cgiData;
ws->priv=malloc(sizeof(WebsockPriv));
memset(ws->priv, 0, sizeof(WebsockPriv));
ws->conn=connData;
//Reply with the right headers.
strcat(buff, WS_GUID);
sha1_init(&s);
sha1_write(&s, buff, strlen(buff));
httpdDisableTransferEncoding(connData);
httpdStartResponse(connData, 101);
httpdHeader(connData, "Upgrade", "websocket");
httpdHeader(connData, "Connection", "upgrade");
base64_encode(20, sha1_result(&s), sizeof(buff), buff);
httpdHeader(connData, "Sec-WebSocket-Accept", buff);
httpdEndHeaders(connData);
//Set data receive handler
connData->recvHdl=cgiWebSocketRecv;
//Inform CGI function we have a connection
WsConnectedCb connCb=connData->cgiArg;
connCb(ws);
//Insert ws into linked list
if (llStart==NULL) {
llStart=ws;
} else {
Websock *lw=llStart;
while (lw->priv->next) lw=lw->priv->next;
lw->priv->next=ws;
}
return HTTPD_CGI_MORE;
}
}
//No valid websocket connection
httpdStartResponse(connData, 500);
httpdEndHeaders(connData);
return HTTPD_CGI_DONE;
}
//Sending is done. Call the sent callback if we have one.
Websock *ws=(Websock*)connData->cgiData;
if (ws && ws->sentCb) ws->sentCb(ws);
return HTTPD_CGI_MORE;
}
+314
View File
@@ -0,0 +1,314 @@
/*
Cgi/template routines for the /wifi url.
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "cgiwifi.h"
//Enable this to disallow any changes in AP settings
//#define DEMO_MODE
//WiFi access point data
typedef struct {
char ssid[32];
char bssid[8];
int channel;
char rssi;
char enc;
} ApData;
//Scan result
typedef struct {
char scanInProgress; //if 1, don't access the underlying stuff from the webpage.
ApData **apData;
int noAps;
} ScanResultData;
//Static scan status storage.
static ScanResultData cgiWifiAps;
#define CONNTRY_IDLE 0
#define CONNTRY_WORKING 1
#define CONNTRY_SUCCESS 2
#define CONNTRY_FAIL 3
//Connection result var
static int connTryStatus=CONNTRY_IDLE;
static os_timer_t resetTimer;
//Callback the code calls when a wlan ap scan is done. Basically stores the result in
//the cgiWifiAps struct.
void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) {
int n;
struct bss_info *bss_link = (struct bss_info *)arg;
httpd_printf("wifiScanDoneCb %d\n", status);
if (status!=OK) {
cgiWifiAps.scanInProgress=0;
return;
}
//Clear prev ap data if needed.
if (cgiWifiAps.apData!=NULL) {
for (n=0; n<cgiWifiAps.noAps; n++) free(cgiWifiAps.apData[n]);
free(cgiWifiAps.apData);
}
//Count amount of access points found.
n=0;
while (bss_link != NULL) {
bss_link = bss_link->next.stqe_next;
n++;
}
//Allocate memory for access point data
cgiWifiAps.apData=(ApData **)malloc(sizeof(ApData *)*n);
cgiWifiAps.noAps=n;
httpd_printf("Scan done: found %d APs\n", n);
//Copy access point data to the static struct
n=0;
bss_link = (struct bss_info *)arg;
while (bss_link != NULL) {
if (n>=cgiWifiAps.noAps) {
//This means the bss_link changed under our nose. Shouldn't happen!
//Break because otherwise we will write in unallocated memory.
httpd_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps);
break;
}
//Save the ap data.
cgiWifiAps.apData[n]=(ApData *)malloc(sizeof(ApData));
cgiWifiAps.apData[n]->rssi=bss_link->rssi;
cgiWifiAps.apData[n]->channel=bss_link->channel;
cgiWifiAps.apData[n]->enc=bss_link->authmode;
strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32);
strncpy(cgiWifiAps.apData[n]->bssid, (char*)bss_link->bssid, 6);
bss_link = bss_link->next.stqe_next;
n++;
}
//We're done.
cgiWifiAps.scanInProgress=0;
}
//Routine to start a WiFi access point scan.
static void ICACHE_FLASH_ATTR wifiStartScan() {
// int x;
if (cgiWifiAps.scanInProgress) return;
cgiWifiAps.scanInProgress=1;
wifi_station_scan(NULL, wifiScanDoneCb);
}
//This CGI is called from the bit of AJAX-code in wifi.tpl. It will initiate a
//scan for access points and if available will return the result of an earlier scan.
//The result is embedded in a bit of JSON parsed by the javascript in wifi.tpl.
int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) {
int pos=(int)connData->cgiData;
int len;
char buff[1024];
if (!cgiWifiAps.scanInProgress && pos!=0) {
//Fill in json code for an access point
if (pos-1<cgiWifiAps.noAps) {
len=sprintf(buff, "{\"essid\": \"%s\", \"bssid\": \"" MACSTR "\", \"rssi\": \"%d\", \"enc\": \"%d\", \"channel\": \"%d\"}%s\n",
cgiWifiAps.apData[pos-1]->ssid, MAC2STR(cgiWifiAps.apData[pos-1]->bssid), cgiWifiAps.apData[pos-1]->rssi,
cgiWifiAps.apData[pos-1]->enc, cgiWifiAps.apData[pos-1]->channel, (pos-1==cgiWifiAps.noAps-1)?"":",");
httpdSend(connData, buff, len);
}
pos++;
if ((pos-1)>=cgiWifiAps.noAps) {
len=sprintf(buff, "]\n}\n}\n");
httpdSend(connData, buff, len);
//Also start a new scan.
wifiStartScan();
return HTTPD_CGI_DONE;
} else {
connData->cgiData=(void*)pos;
return HTTPD_CGI_MORE;
}
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/json");
httpdEndHeaders(connData);
if (cgiWifiAps.scanInProgress==1) {
//We're still scanning. Tell Javascript code that.
len=sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n");
httpdSend(connData, buff, len);
return HTTPD_CGI_DONE;
} else {
//We have a scan result. Pass it on.
len=sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"0\", \n\"APs\": [\n");
httpdSend(connData, buff, len);
if (cgiWifiAps.apData==NULL) cgiWifiAps.noAps=0;
connData->cgiData=(void *)1;
return HTTPD_CGI_MORE;
}
}
//Temp store for new ap info.
static struct station_config stconf;
//This routine is ran some time after a connection attempt to an access point. If
//the connect succeeds, this gets the module in STA-only mode.
static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
int x=wifi_station_get_connect_status();
if (x==STATION_GOT_IP) {
//Go to STA mode. This needs a reset, so do that.
httpd_printf("Got IP. Going into STA mode..\n");
wifi_set_opmode(1);
system_restart();
} else {
connTryStatus=CONNTRY_FAIL;
httpd_printf("Connect fail. Not going into STA-only mode.\n");
//Maybe also pass this through on the webpage?
}
}
//Actually connect to a station. This routine is timed because I had problems
//with immediate connections earlier. It probably was something else that caused it,
//but I can't be arsed to put the code back :P
static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) {
int x;
httpd_printf("Try to connect to AP....\n");
wifi_station_disconnect();
wifi_station_set_config(&stconf);
wifi_station_connect();
x=wifi_get_opmode();
connTryStatus=CONNTRY_WORKING;
if (x!=1) {
//Schedule disconnect/connect
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, 15000, 0); //time out after 15 secs of trying to connect
}
}
//This cgi uses the routines above to connect to a specific access point with the
//given ESSID using the given password.
int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) {
char essid[128];
char passwd[128];
static os_timer_t reassTimer;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdFindArg(connData->post->buff, "essid", essid, sizeof(essid));
httpdFindArg(connData->post->buff, "passwd", passwd, sizeof(passwd));
strncpy((char*)stconf.ssid, essid, 32);
strncpy((char*)stconf.password, passwd, 64);
httpd_printf("Try to connect to AP %s pw %s\n", essid, passwd);
//Schedule disconnect/connect
os_timer_disarm(&reassTimer);
os_timer_setfn(&reassTimer, reassTimerCb, NULL);
//Set to 0 if you want to disable the actual reconnecting bit
#ifdef DEMO_MODE
httpdRedirect(connData, "/wifi");
#else
os_timer_arm(&reassTimer, 500, 0);
httpdRedirect(connData, "connecting.html");
#endif
return HTTPD_CGI_DONE;
}
//This cgi uses the routines above to connect to a specific access point with the
//given ESSID using the given password.
int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) {
int len;
char buff[1024];
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff));
if (len!=0) {
httpd_printf("cgiWifiSetMode: %s\n", buff);
#ifndef DEMO_MODE
wifi_set_opmode(atoi(buff));
system_restart();
#endif
}
httpdRedirect(connData, "/wifi");
return HTTPD_CGI_DONE;
}
int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) {
char buff[1024];
int len;
struct ip_info info;
int st=wifi_station_get_connect_status();
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/json");
httpdEndHeaders(connData);
if (connTryStatus==CONNTRY_IDLE) {
len=sprintf(buff, "{\n \"status\": \"idle\"\n }\n");
} else if (connTryStatus==CONNTRY_WORKING || connTryStatus==CONNTRY_SUCCESS) {
if (st==STATION_GOT_IP) {
wifi_get_ip_info(0, &info);
len=sprintf(buff, "{\n \"status\": \"success\",\n \"ip\": \"%d.%d.%d.%d\" }\n",
(info.ip.addr>>0)&0xff, (info.ip.addr>>8)&0xff,
(info.ip.addr>>16)&0xff, (info.ip.addr>>24)&0xff);
//Reset into AP-only mode sooner.
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, 1000, 0);
} else {
len=sprintf(buff, "{\n \"status\": \"working\"\n }\n");
}
} else {
len=sprintf(buff, "{\n \"status\": \"fail\"\n }\n");
}
httpdSend(connData, buff, len);
return HTTPD_CGI_DONE;
}
//Template code for the WLAN page.
int ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg) {
char buff[1024];
int x;
static struct station_config stconf;
if (token==NULL) return HTTPD_CGI_DONE;
wifi_station_get_config(&stconf);
strcpy(buff, "Unknown");
if (strcmp(token, "WiFiMode")==0) {
x=wifi_get_opmode();
if (x==1) strcpy(buff, "Client");
if (x==2) strcpy(buff, "SoftAP");
if (x==3) strcpy(buff, "STA+AP");
} else if (strcmp(token, "currSsid")==0) {
strcpy(buff, (char*)stconf.ssid);
} else if (strcmp(token, "WiFiPasswd")==0) {
strcpy(buff, (char*)stconf.password);
} else if (strcmp(token, "WiFiapwarn")==0) {
x=wifi_get_opmode();
if (x==2) {
strcpy(buff, "<b>Can't scan in this mode.</b> Click <a href=\"setmode.cgi?mode=3\">here</a> to go to STA+AP mode.");
} else {
strcpy(buff, "Click <a href=\"setmode.cgi?mode=2\">here</a> to go to standalone AP mode.");
}
}
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
+12
View File
@@ -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;
}
}