Added files
This commit is contained in:
@@ -0,0 +1 @@
|
||||
gitdir: ../.git/modules/libesphttpd
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "lib/heatshrink"]
|
||||
path = lib/heatshrink
|
||||
url = https://github.com/atomicobject/heatshrink.git
|
||||
@@ -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))))
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#ifndef CAPTDNS_H
|
||||
#define CAPTDNS_H
|
||||
void ICACHE_FLASH_ATTR captdnsInit(void);
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
extern char webpages_espfs_start[];
|
||||
extern char webpages_espfs_end[];
|
||||
extern int webpages_espfs_size;
|
||||
@@ -0,0 +1 @@
|
||||
gitdir: ../../../.git/modules/libesphttpd/modules/lib/heatshrink
|
||||
@@ -0,0 +1,6 @@
|
||||
heatshrink
|
||||
test_heatshrink_dynamic
|
||||
test_heatshrink_static
|
||||
*.o
|
||||
*.core
|
||||
*.dSYM
|
||||
@@ -0,0 +1,9 @@
|
||||
language: c
|
||||
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
install: make test_heatshrink_dynamic
|
||||
|
||||
script: ./test_heatshrink_dynamic
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
[](http://travis-ci.org/atomicobject/heatshrink)
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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
|
||||
Executable
+446
@@ -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 */
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
mkupgimg
|
||||
@@ -0,0 +1,5 @@
|
||||
mkupgimg: mkupgimg.c
|
||||
$(CC) -o $@ $^
|
||||
|
||||
clean:
|
||||
rm -f mkupgimg
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user