├── .gitignore ├── README.md ├── Vagrantfile ├── console.sh ├── esphttpd ├── .gitignore ├── Makefile ├── README ├── espfstest │ ├── Makefile │ ├── espfs.c │ ├── heatshrink_decoder.c │ └── main.c ├── html │ ├── cats │ │ ├── cross-eyed-cat.jpg │ │ ├── junge-katze-iv.jpg │ │ └── kitten-loves-toy.jpg │ ├── index.tpl │ ├── led.tpl │ ├── style.css │ └── wifi │ │ ├── 140medley.min.js │ │ ├── connecting.html │ │ ├── icons.png │ │ ├── style.css │ │ └── wifi.tpl ├── include │ ├── espmissingincludes.h │ ├── httpdconfig.h │ ├── lwipopts.h │ ├── mem_manager.h │ ├── stdint.h │ ├── uart_hw.h │ └── user_config.h ├── lib │ └── heatshrink │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── dec_sm.dot │ │ ├── enc_sm.dot │ │ ├── greatest.h │ │ ├── heatshrink.c │ │ ├── heatshrink_common.h │ │ ├── heatshrink_config.h │ │ ├── heatshrink_decoder.c │ │ ├── heatshrink_decoder.h │ │ ├── heatshrink_encoder.c │ │ ├── heatshrink_encoder.h │ │ ├── test_heatshrink_dynamic.c │ │ ├── test_heatshrink_dynamic_theft.c │ │ └── test_heatshrink_static.c ├── mkespfsimage │ ├── Makefile │ ├── espfsformat.h │ ├── heatshrink_encoder.c │ └── main.c └── user │ ├── auth.c │ ├── auth.h │ ├── base64.c │ ├── base64.h │ ├── cgi.c │ ├── cgi.h │ ├── cgiwifi.c │ ├── cgiwifi.h │ ├── espfs.c │ ├── espfs.h │ ├── heatshrink_config_httpd.h │ ├── heatshrink_decoder.c │ ├── httpd.c │ ├── httpd.h │ ├── httpdespfs.c │ ├── httpdespfs.h │ ├── io.c │ ├── io.h │ ├── stdout.c │ ├── stdout.h │ └── user_main.c ├── tools ├── esptool-0.0.3 └── sdk │ ├── esp_iot_sdk_v0.9.5_15_01_23.zip │ ├── esp_iot_sdk_v0.9.5_15_01_23_patch1.zip │ ├── extra-includes │ └── include.tgz │ └── extra-libs │ ├── libc.a │ └── libhal.a └── vm-bootstrap.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | esp8266-dev 2 | =========== 3 | 4 | A Vagrant-powered virtual machine providing an isolated development 5 | environment for the [ESP8266](https://github.com/esp8266/esp8266-wiki) $5 6 | dollar "Internet of Things" WiFi module. 7 | 8 | 9 | ## What you'll need 10 | 11 | 1. A way to communicate with the chip's 3.3V TTL serial interface. I've had 12 | success with a [cable from Adafruit](http://www.adafruit.com/product/954) 13 | that's based on the PL2303 USB-to-TTL module. 14 | 15 | 2. [VirtualBox](https://www.virtualbox.org/), a free open source virtualization 16 | package. 17 | 18 | 3. [Vagrant](https://www.vagrantup.com), a virtualization management tool 19 | geared towards development environments. 20 | 21 | 22 | ## Let's Do This 23 | 24 | 0. Clone this repository. 25 | 26 | 1. If you'll be using a USB-to-TTL device like the Adafruit one I noted above, 27 | you'll need to edit the `Vagrantfile` to include your device's VendorId and 28 | ProductId. If you have the exact one I mentioned above, you may be fine with 29 | the existing configuration, but you may as well do this step too to be sure. 30 | It's an easy one. 31 | 32 | Connect your device to your computer and from the command line, run 33 | `VBoxManage list usbhost`. The output should be a list of entries that 34 | look like this: 35 | 36 | $ VBoxManage list usbhost 37 | Host USB Devices: 38 | ... 39 | UUID: 738b44fd-2f57-49dd-a16a-e31a0e7fa46f 40 | VendorId: 0x067b (067B) 41 | ProductId: 0x2303 (2303) 42 | Revision: 3.0 (0300) 43 | Port: 1 44 | USB version/speed: 0/1 45 | Manufacturer: Prolific Technology Inc. 46 | Product: USB-Serial Controller 47 | Address: p=0x2303;v=0x067b;s=0x0002653c8cdc2c52;l=0x14100000 48 | Current State: Captured 49 | 50 | Note the VendorId and ProductId. Set the `$vendor_id` and `$product_id` 51 | variables in `Vagrantfile` to those values. They must be strings of the 52 | hexadecimal representations. 53 | 54 | 2. In the console, run `vagrant up`. Note that this may take a decent chunk of 55 | time -- it was ~30 minutes on my 2013 Macbook Pro. Most of it is spent 56 | building the cross-compiler. Don't worry, it's a one-time cost. 57 | 58 | 3. That's it! Now you can `vagrant ssh` and start building your images! 59 | 60 | 61 | ## Sweet! ...now what? 62 | 63 | Well, if you're brand new to Vagrant, skip down a couple of sections for a very brief primer, then come back. 64 | 65 | Oh hi! Wecome back! Now, if I were you, first thing I'd do is make sure my 66 | serial cable worked. With it plugged in, `vagrant ssh` into the machine and 67 | run `lsusb` to make sure it's in your list of devices. To see where it's 68 | attached, run `dmesg` and somewhere near the bottom you should see something 69 | like `usb 1-1: pl2303 converter now attached to ttyUSB0`. If not, you might try 70 | unplugging it and plugging it back in, then running it again -- that way it'll 71 | definitely be near the bottom of the log. If your device is attached anywhere 72 | besides `/dev/ttyUSB0`, you'll need to adjust your scripts and Makefiles as 73 | appropriate. No big deal. 74 | 75 | `console.sh` is a tiny script to start a serial console on `/dev/ttyUSB0` at 76 | 115200 baud. Most ESPs seem to ship with their baud set to that. You may need 77 | to change the device, of course, based on the output from `dmesg`. Now go ahead 78 | and run that bad boy. 79 | 80 | Plug your ESP8266 into your serial interface and tie its CH_PD and GPIO0 pins 81 | HIGH. Pinouts, along with a ton of other info, can be found [on the esp8266 82 | wiki](https://github.com/esp8266/esp8266-wiki/wiki/Hardware_versions). Now 83 | power it on. You should see a bunch of garbage in your serial terminal, 84 | followed by the word `READY`. Type `AS+RST` and hit Enter, and you should get 85 | this back: 86 | 87 | AT+RST 88 | OK 89 | 90 | ets Jan 8 2013,rst cause:4, boot mode:(3,6) 91 | 92 | wdt reset 93 | load 0x40100000, len 24236, room 16 94 | tail 12 95 | chksum 0xb7 96 | ho 0 tail 12 room 4 97 | load 0x3ffe8000, len 3008, room 12 98 | tail 4 99 | chksum 0x2c 100 | load 0x3ffe8bc0, len 4816, room 4 101 | tail 12 102 | chksum 0x46 103 | csum 0x46 104 | 105 | ready 106 | 107 | You're in business! Now the fun part -- you can either play around with [the AT 108 | commands](http://www.electrodragon.com/w/Wi07c#AT_Commands), or kill the 109 | console and go on to build... 110 | 111 | 112 | ## Sample project -- a fricken webserver 113 | 114 | I've included a very awesome sample project from 115 | [Sprite_tm](http://spritesmods.com/) -- an HTTP server dubbed 116 | [esphttpd](http://www.esp8266.com/viewtopic.php?f=6&t=376). From inside the 117 | virtual machine, `cd /vagrant/esphttpd && make` to build it. Then make sure 118 | your chip is in firmware upload mode by tieing the CH_PD and GPIO2 pins HIGH 119 | and the GPIO0 pin LOW. Power it on, and run `sudo make flash`, followed by 120 | `sudo make htmlflash`. (The `sudo`s are needed to access /dev/ttyUSB0) 121 | 122 | NOTE 1: you may receive `Failed to leave flash mode` exceptions after each 123 | flash. Don't worry, it's most likely a false alarm. 124 | 125 | NOTE 2: It's generally a good idea to power cycle the chip between uploads. 126 | `sudo make flash` includes a `sleep 3` between two flashes to different 127 | areas of memory, so you can use that delay to cycle. 128 | 129 | For more info, see `esphttpd/README`. 130 | 131 | 132 | ## Vagrant tips, for the uninitiated 133 | 134 | The project's root directory is mirrored to `/vagrant` on the virtual machine. 135 | 136 | You can `sudo` from inside the machine without a password. 137 | 138 | `vagrant ssh` - ssh into the machine 139 | 140 | `vagrant halt` - shuts down the machine 141 | 142 | `vagrant suspend` - puts the machine to sleep 143 | 144 | `vagrant destroy` - removes every trace of the machine. NOTE: after a destroy, 145 | the next `vagrant up` will have to reprovision the machine from scratch, 146 | meaning it'll take a while. 147 | 148 | `vagrant up` - spins up the machine, bringing it back up from `halt`, 149 | `suspend`, or `destroy` 150 | 151 | 152 | ## ESP8266 Resources 153 | 154 | - https://github.com/esp8266/esp8266-wiki - the wiki containing everything I 155 | needed to build this repo 156 | - http://esp8266.com - a forum for people interested in hacking on the chip 157 | - `#esp8266` on Freenode 158 | - http://www.electrodragon.com/w/Wi07c - another nice wiki I found useful for 159 | exploring the AT commands 160 | 161 | 162 | ## A final note 163 | 164 | esphttpd, along with all the other resources in this repo, was copied wholesale 165 | from another place. In the case of esphttpd, that other place is [this git 166 | repo](http://git.spritesserver.nl/esphttpd.git). I decided to do this rather 167 | than make references to everything so that _this_ repo would remain a set of 168 | tools that work together out of the box. I may change this practice in the 169 | future. 170 | 171 | *UPDATE:* it's the future, and @slaff has helped move toward not shoving 172 | everything into this repo, so crosstool-NG is now pulled in by the 173 | provisioning script. So is esptool.py. Thanks @slaff! 174 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | $vendor_id = '0x067b' 5 | $product_id = '0x2303' 6 | 7 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 8 | VAGRANTFILE_API_VERSION = "2" 9 | 10 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 11 | 12 | config.vm.box = "hashicorp/precise32" 13 | config.vm.provision :shell, path: "vm-bootstrap.sh", privileged: false 14 | 15 | config.vm.provider :virtualbox do |vb| 16 | vb.customize ['modifyvm', :id, '--usb', 'on'] 17 | vb.customize ['usbfilter', 'add', '0', '--target', :id, '--name', 'USB_to_TTL_converter', '--vendorid', $vendor_id, '--productid', $product_id] 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /console.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo python -m serial.tools.miniterm /dev/ttyUSB0 115200 3 | 4 | -------------------------------------------------------------------------------- /esphttpd/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | firmware/ 3 | mkespfsimage/*.o 4 | mkespfsimage/mkespfsimage 5 | webpages.espfs 6 | espfstest/*.o 7 | espfstest/espfstest 8 | -------------------------------------------------------------------------------- /esphttpd/Makefile: -------------------------------------------------------------------------------- 1 | # tnx to mamalala 2 | # Changelog 3 | # Changed the variables to include the header file directory 4 | # Added global var for the XTENSA tool root 5 | # 6 | # This make file still needs some work. 7 | # 8 | # 9 | # Output directors to store intermediate compiled files 10 | # relative to the project directory 11 | BUILD_BASE = build 12 | FW_BASE = firmware 13 | 14 | # Base directory for the compiler 15 | XTENSA_TOOLS_ROOT ?= /opt/Espressif/crosstool-NG/builds/xtensa-lx106-elf/bin 16 | 17 | #Extra Tensilica includes from the ESS VM 18 | SDK_EXTRA_INCLUDES ?= /opt/Espressif/include 19 | SDK_EXTRA_LIBS ?= /opt/Espressif/arch/lib 20 | 21 | # base directory of the ESP8266 SDK package, absolute 22 | SDK_BASE ?= /opt/Espressif/ESP8266_SDK 23 | 24 | #Esptool.py path and port 25 | ESPTOOL ?= esptool 26 | ESPPORT ?= /dev/ttyUSB0 27 | #ESPDELAY indicates seconds to wait between flashing the two binary images 28 | ESPDELAY ?= 3 29 | ESPBAUD ?= 115200 30 | 31 | # name for the target project 32 | TARGET = httpd 33 | 34 | # which modules (subdirectories) of the project to include in compiling 35 | #MODULES = driver user lwip/api lwip/app lwip/core lwip/core/ipv4 lwip/netif 36 | MODULES = driver user 37 | EXTRA_INCDIR = include \ 38 | . \ 39 | lib/heatshrink/ \ 40 | $(SDK_EXTRA_INCLUDES) 41 | 42 | # libraries used in this project, mainly provided by the SDK 43 | LIBS = c gcc hal phy pp net80211 wpa main lwip 44 | 45 | # compiler flags using during compilation of source files 46 | CFLAGS = -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ 47 | -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \ 48 | -Wno-address 49 | 50 | # linker flags used to generate the main object file 51 | LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static -L$(SDK_EXTRA_LIBS) 52 | 53 | # linker script used for the above linkier step 54 | LD_SCRIPT = eagle.app.v6.ld 55 | 56 | # various paths from the SDK used in this project 57 | SDK_LIBDIR = lib 58 | SDK_LDDIR = ld 59 | SDK_INCDIR = include include/json 60 | 61 | # we create two different files for uploading into the flash 62 | # these are the names and options to generate them 63 | FW_FILE_1 = 0x00000 64 | FW_FILE_1_ARGS = -bo $@ -bs .text -bs .data -bs .rodata -bc -ec 65 | FW_FILE_2 = 0x40000 66 | FW_FILE_2_ARGS = -es .irom0.text $@ -ec 67 | 68 | # select which tools to use as compiler, librarian and linker 69 | CC := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc 70 | AR := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-ar 71 | LD := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc 72 | 73 | 74 | 75 | #### 76 | #### no user configurable options below here 77 | #### 78 | SRC_DIR := $(MODULES) 79 | BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES)) 80 | 81 | SDK_LIBDIR := $(addprefix $(SDK_BASE)/,$(SDK_LIBDIR)) 82 | SDK_INCDIR := $(addprefix -I$(SDK_BASE)/,$(SDK_INCDIR)) 83 | 84 | SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c)) 85 | OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(SRC)) 86 | LIBS := $(addprefix -l,$(LIBS)) 87 | APP_AR := $(addprefix $(BUILD_BASE)/,$(TARGET)_app.a) 88 | TARGET_OUT := $(addprefix $(BUILD_BASE)/,$(TARGET).out) 89 | 90 | LD_SCRIPT := $(addprefix -T$(SDK_BASE)/$(SDK_LDDIR)/,$(LD_SCRIPT)) 91 | 92 | INCDIR := $(addprefix -I,$(SRC_DIR)) 93 | EXTRA_INCDIR := $(addprefix -I,$(EXTRA_INCDIR)) 94 | MODULE_INCDIR := $(addsuffix /include,$(INCDIR)) 95 | 96 | FW_FILE_1 := $(addprefix $(FW_BASE)/,$(FW_FILE_1).bin) 97 | FW_FILE_2 := $(addprefix $(FW_BASE)/,$(FW_FILE_2).bin) 98 | 99 | V ?= $(VERBOSE) 100 | ifeq ("$(V)","1") 101 | Q := 102 | vecho := @true 103 | else 104 | Q := @ 105 | vecho := @echo 106 | endif 107 | 108 | vpath %.c $(SRC_DIR) 109 | 110 | define compile-objects 111 | $1/%.o: %.c 112 | $(vecho) "CC $$<" 113 | $(Q) $(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ 114 | endef 115 | 116 | .PHONY: all checkdirs clean 117 | 118 | all: checkdirs $(TARGET_OUT) $(FW_FILE_1) $(FW_FILE_2) 119 | 120 | $(FW_FILE_1): $(TARGET_OUT) firmware 121 | $(vecho) "FW $@" 122 | $(Q) $(ESPTOOL) -eo $(TARGET_OUT) $(FW_FILE_1_ARGS) 123 | 124 | $(FW_FILE_2): $(TARGET_OUT) firmware 125 | $(vecho) "FW $@" 126 | $(Q) $(ESPTOOL) -eo $(TARGET_OUT) $(FW_FILE_2_ARGS) 127 | 128 | $(TARGET_OUT): $(APP_AR) 129 | $(vecho) "LD $@" 130 | $(Q) $(LD) -L$(SDK_LIBDIR) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group $(LIBS) $(APP_AR) -Wl,--end-group -o $@ 131 | 132 | $(APP_AR): $(OBJ) 133 | $(vecho) "AR $@" 134 | $(Q) $(AR) cru $@ $^ 135 | 136 | checkdirs: $(BUILD_DIR) $(FW_BASE) 137 | 138 | $(BUILD_DIR): 139 | $(Q) mkdir -p $@ 140 | 141 | firmware: 142 | $(Q) mkdir -p $@ 143 | 144 | flash: $(FW_FILE_1) $(FW_FILE_2) 145 | $(Q) $(ESPTOOL) -cp $(ESPPORT) -cb $(ESPBAUD) -ca 0x00000 -cf firmware/0x00000.bin -v 146 | $(Q) [ $(ESPDELAY) -ne 0 ] && echo "Please put the ESP in bootloader mode..." || true 147 | $(Q) sleep $(ESPDELAY) || true 148 | $(Q) $(ESPTOOL) -cp $(ESPPORT) -cb $(ESPBAUD) -ca 0x40000 -cf firmware/0x40000.bin -v 149 | 150 | webpages.espfs: html/ html/wifi/ mkespfsimage/mkespfsimage 151 | cd html; find | ../mkespfsimage/mkespfsimage > ../webpages.espfs; cd .. 152 | 153 | mkespfsimage/mkespfsimage: mkespfsimage/ 154 | make -C mkespfsimage 155 | 156 | htmlflash: webpages.espfs 157 | if [ $$(stat -c '%s' webpages.espfs) -gt $$(( 0x2E000 )) ]; then echo "webpages.espfs too big!"; false; fi 158 | $(ESPTOOL) -cp $(ESPPORT) -cb $(ESPBAUD) -ca 0x12000 -cf webpages.espfs -v 159 | 160 | clean: 161 | $(Q) rm -f $(APP_AR) 162 | $(Q) rm -f $(TARGET_OUT) 163 | $(Q) find $(BUILD_BASE) -type f | xargs rm -f 164 | 165 | 166 | $(Q) rm -f $(FW_FILE_1) 167 | $(Q) rm -f $(FW_FILE_2) 168 | $(Q) rm -rf $(FW_BASE) 169 | 170 | $(foreach bdir,$(BUILD_DIR),$(eval $(call compile-objects,$(bdir)))) 171 | -------------------------------------------------------------------------------- /esphttpd/README: -------------------------------------------------------------------------------- 1 | esp-httpd README 2 | 3 | This is a small but powerful webserver for ESP8266(EX) chips. Included is an example of how 4 | to make a module that can have the AP it connects to configured over a webbrowser. 5 | 6 | ABOUT THE WEBSERVER 7 | 8 | The Good (aka: what's awesome) 9 | - Supports multiple connections, for eg simultaneous html/css/js/images downloading 10 | - Static files stored in flash, in an (optionally compressed) RO filesystem 11 | - Pluggable using external cgi routines 12 | - Simple template engine for mixed c and html things 13 | 14 | The Bad (aka: what can be improved) 15 | - Not built for speediness, although it's reasonable fast. 16 | - Built according to what I remember of the HTTP protocol, not according to the 17 | RFCs. Should work with most modern browsers, though. 18 | - No support for https. 19 | 20 | The Ugly (aka: bugs, misbehaviour) 21 | - Possible buffer overflows (usually not remotely exploitable) due to no os_snprintf 22 | This can be theoretically remedied by either Espressif including an os_snprintf in 23 | their libs or by using some alternate printf lib, like elm-chans xprintf 24 | 25 | ABOUT THE EXAMPLE 26 | 27 | When you flash the example into an ESP8266(EX) module, you get a small webserver with a few example 28 | pages. If you've already connected your module to your WLAN before, it'll keep those settings. When 29 | you haven't or the settings are wrong, keep GPIO0 for >5 seconds. The module will reboot into 30 | its STA+AP mode. Connect a computer to the newly formed access point and browse to 31 | http://192.168.4.1/wifi in order to connect the module to your WiFi network. The example also 32 | allows you to control a LED that's connected to GPIO2. 33 | 34 | BUILDING EVERYTHING 35 | 36 | For this, you need an environment that can compile ESP8266 firmware. Environments for this still 37 | are in flux at the moment, but I'm using a crosstool-ng gcc setup combined with the libs & includes 38 | from the ESP SDK and ESP VM. You probably also need an UNIX-like system; I'm working on 39 | Debian Linux myself. 40 | 41 | To manage the paths to all this, you can source a small shell fragment into your current session. For 42 | example, I source a file with these contents: 43 | export PATH=${PWD}/crosstool-NG/builds/xtensa-lx106-elf/bin:$PATH 44 | export XTENSA_TOOLS_ROOT=${PWD}/crosstool-NG/builds/xtensa-lx106-elf/bin 45 | export SDK_BASE=${PWD}/esp_iot_sdk_v0.9.5/ 46 | export SDK_EXTRA_INCLUDES=${PWD}/esp_iot_sdk_novm_unpacked/usr/xtensa/XtDevTools/install/builds/RC-2010.1-win32/lx106/xtensa-elf/include/ 47 | export ESPTOOL=${PWD}/esptool/esptool.py 48 | export ESPPORT=/dev/ttyUSB0 49 | Actual setup of the SDK and toolchain is out of the scope of this document, so I hope this helps you 50 | enough to set up your own if you haven't already. 51 | 52 | If you have that, you can clone out the source code: 53 | git clone http://git.spritesserver.nl/esphttpd.git/ 54 | 55 | This project makes use of heatshrink, which is a git submodule. To fetch the code: 56 | cd esphttpd 57 | git submodule init 58 | git submodule update 59 | 60 | Now, build the code: 61 | make 62 | 63 | Flash the code happens in 2 steps. First the code itself gets flashed. Reset the module into bootloader 64 | mode and enter 'make flash'. You may want to reset and re-enter the bootloader halfway (at 'sleep 3') for 65 | the 2nd part of this flash to work. 66 | 67 | The 2nd step is to pack the static files the webserver will serve and flash that. Reset the module into 68 | bootloader mode again and enter 'make htmlflash'. 69 | 70 | You should have a working webserver now. 71 | 72 | WRITING CODE FOR THE WEBSERVER 73 | 74 | ...errm... to be done. For now, look at the examples. Hey, you probably managed to find out how 75 | the SDK works, this shouldn't be too hard :P 76 | 77 | 78 | CHANGE FROM SDK 0.9.3 (and earlier) TO SDK 0.9.4: 79 | Change all occurences of 80 | espconn_sent(connData->conn, (uint8 *)buff, len); 81 | to 82 | httpdSend(connData, buff, len) 83 | please. The reason for this is that you can't do multiple espconn_sent calls serially anymore, so 84 | httpd needs to buffer the writes now. This is only needed in your own code; the code that comes 85 | with httpd already has this changed. 86 | 87 | 88 | -------------------------------------------------------------------------------- /esphttpd/espfstest/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-I../lib/heatshrink -I../user -I../include -std=gnu99 2 | 3 | espfstest: main.o espfs.o heatshrink_decoder.o 4 | $(CC) -o $@ $^ 5 | 6 | espfs.o: espfs.c ../user/espfs.c 7 | 8 | 9 | clean: 10 | rm -f *.o espfstest 11 | -------------------------------------------------------------------------------- /esphttpd/espfstest/espfs.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "../user/espfs.c" 4 | -------------------------------------------------------------------------------- /esphttpd/espfstest/heatshrink_decoder.c: -------------------------------------------------------------------------------- 1 | #include "httpdconfig.h" 2 | #ifdef EFS_HEATSHRINK 3 | //Stupid wrapper so we don't have to move c-files around 4 | //Also loads httpd-specific config. 5 | 6 | #include "../lib/heatshrink/heatshrink_decoder.c" 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /esphttpd/espfstest/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | Simple and stupid file decompressor for an espfs image. Mostly used as a testbed for espfs.c and 3 | the decompressors: code compiled natively is way easier to debug using gdb et all :) 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | #include "espfs.h" 16 | 17 | char *espFsData; 18 | 19 | int main(int argc, char **argv) { 20 | int f, out; 21 | int len; 22 | char buff[128]; 23 | EspFsFile *ef; 24 | off_t size; 25 | 26 | if (argc!=3) { 27 | printf("Usage: %s espfs-image file\nExpands file from the espfs-image archive.\n", argv[0]); 28 | exit(0); 29 | } 30 | 31 | f=open(argv[1], O_RDONLY); 32 | if (f<=0) { 33 | perror(argv[1]); 34 | exit(1); 35 | } 36 | size=lseek(f, 0, SEEK_END); 37 | espFsData=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0); 38 | if (espFsData==MAP_FAILED) { 39 | perror("mmap"); 40 | exit(1); 41 | } 42 | 43 | ef=espFsOpen(argv[2]); 44 | if (ef==NULL) { 45 | printf("Couldn't find %s in image.\n", argv[2]); 46 | exit(1); 47 | } 48 | 49 | out=open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0644); 50 | if (out<=0) { 51 | perror(argv[2]); 52 | exit(1); 53 | } 54 | 55 | while ((len=espFsRead(ef, buff, 128))!=0) { 56 | write(out, buff, len); 57 | } 58 | espFsClose(ef); 59 | //munmap, close, ... I can't be bothered. 60 | } 61 | -------------------------------------------------------------------------------- /esphttpd/html/cats/cross-eyed-cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/esphttpd/html/cats/cross-eyed-cat.jpg -------------------------------------------------------------------------------- /esphttpd/html/cats/junge-katze-iv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/esphttpd/html/cats/junge-katze-iv.jpg -------------------------------------------------------------------------------- /esphttpd/html/cats/kitten-loves-toy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/esphttpd/html/cats/kitten-loves-toy.jpg -------------------------------------------------------------------------------- /esphttpd/html/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | Esp8266 web server 3 | 4 | 5 | 6 |
7 |

It Works

8 |

9 | If you see this, it means the tiny li'l website in your ESP8266 does actually work. Fyi, this page has 10 | been loaded %counter% times. 11 |

    12 |
  • If you haven't connected this device to your WLAN network now, you can do so.
  • 13 |
  • You can also control the LED.
  • 14 |
  • You can download the raw contents of the SPI flash rom
  • 15 |
  • And because I can, here's a link to my website
16 | 17 |

18 | 19 |

And because we're on the Internets now, here are the required pictures of cats:
20 |
21 |
22 |
23 |

24 |
25 | 26 | -------------------------------------------------------------------------------- /esphttpd/html/led.tpl: -------------------------------------------------------------------------------- 1 | Test 2 | 3 | 4 | 5 |
6 |

The LED

7 |

8 | If there's a LED connected to GPIO2, it's now %ledstate%. You can change that using the buttons below. 9 |

10 |
11 | 12 | 13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /esphttpd/html/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #404040; 4 | font-family: sans-serif; 5 | } 6 | 7 | #main { 8 | background-color: #d0d0FF; 9 | -moz-border-radius: 5px; 10 | -webkit-border-radius: 5px; 11 | border-radius: 5px; 12 | border: 2px solid #000000; 13 | width: 800px; 14 | margin: 0 auto; 15 | padding: 20px 16 | } 17 | 18 | -------------------------------------------------------------------------------- /esphttpd/html/wifi/140medley.min.js: -------------------------------------------------------------------------------- 1 | var t=function(a,b){return function(c,d){return a.replace(/#{([^}]*)}/g,function(a,f){return Function("x","with(x)return "+f).call(c,d||b||{})})}},s=function(a,b){return b?{get:function(c){return a[c]&&b.parse(a[c])},set:function(c,d){a[c]=b.stringify(d)}}:{}}(this.localStorage||{},JSON),p=function(a,b,c,d){c=c||document;d=c[b="on"+b];a=c[b]=function(e){d=d&&d(e=e||c.event);return(a=a&&b(e))?b:d};c=this},m=function(a,b,c){b=document;c=b.createElement("p");c.innerHTML=a;for(a=b.createDocumentFragment();b= 2 | c.firstChild;)a.appendChild(b);return a},$=function(a,b){a=a.match(/^(\W)?(.*)/);return(b||document)["getElement"+(a[1]?a[1]=="#"?"ById":"sByClassName":"sByTagName")](a[2])},j=function(a){for(a=0;a<4;a++)try{return a?new ActiveXObject([,"Msxml2","Msxml3","Microsoft"][a]+".XMLHTTP"):new XMLHttpRequest}catch(b){}}; 3 | -------------------------------------------------------------------------------- /esphttpd/html/wifi/connecting.html: -------------------------------------------------------------------------------- 1 | 2 | Connecting 3 | 4 | 5 | Connecting to AP now... 6 | 7 | 8 | -------------------------------------------------------------------------------- /esphttpd/html/wifi/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/esphttpd/html/wifi/icons.png -------------------------------------------------------------------------------- /esphttpd/html/wifi/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #404040; 4 | font-family: sans-serif; 5 | } 6 | 7 | #main { 8 | background-color: #d0d0FF; 9 | -moz-border-radius: 5px; 10 | -webkit-border-radius: 5px; 11 | border-radius: 5px; 12 | border: 2px solid #000000; 13 | width: 800px; 14 | margin: 0 auto; 15 | padding: 20px 16 | } 17 | 18 | .icon { 19 | background-image: url("icons.png"); 20 | background-color: transparent; 21 | width: 32px; 22 | height: 32px; 23 | display: inline-block; 24 | } -------------------------------------------------------------------------------- /esphttpd/html/wifi/wifi.tpl: -------------------------------------------------------------------------------- 1 | WiFi connection 2 | 3 | 4 | 74 | 75 | 76 |
77 |

78 | Current WiFi mode: %WiFiMode% 79 |

80 |

81 | Note: %WiFiapwarn% 82 |

83 |
84 |

85 | To connect to a WiFi network, please select one of the detected networks...
86 |

Scanning...
87 |
88 | WiFi password, if applicable:
89 |
90 | 91 |

92 |
93 | 94 | 95 | -------------------------------------------------------------------------------- /esphttpd/include/espmissingincludes.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPMISSINGINCLUDES_H 2 | #define ESPMISSINGINCLUDES_H 3 | 4 | #include 5 | #include 6 | 7 | //Missing function prototypes in include folders. Gcc will warn on these if we don't define 'em anywhere. 8 | //MOST OF THESE ARE GUESSED! but they seem to swork and shut up the compiler. 9 | typedef struct espconn espconn; 10 | 11 | int atoi(const char *nptr); 12 | void ets_install_putc1(void *routine); 13 | void ets_isr_attach(int intr, void *handler, void *arg); 14 | void ets_isr_mask(unsigned intr); 15 | void ets_isr_unmask(unsigned intr); 16 | int ets_memcmp(const void *s1, const void *s2, size_t n); 17 | void *ets_memcpy(void *dest, const void *src, size_t n); 18 | void *ets_memset(void *s, int c, size_t n); 19 | int ets_sprintf(char *str, const char *format, ...) __attribute__ ((format (printf, 2, 3))); 20 | int ets_str2macaddr(void *, void *); 21 | int ets_strcmp(const char *s1, const char *s2); 22 | char *ets_strcpy(char *dest, const char *src); 23 | size_t ets_strlen(const char *s); 24 | int ets_strncmp(const char *s1, const char *s2, int len); 25 | char *ets_strncpy(char *dest, const char *src, size_t n); 26 | char *ets_strstr(const char *haystack, const char *needle); 27 | void ets_timer_arm_new(ETSTimer *a, int b, int c, int isMstimer); 28 | void ets_timer_disarm(ETSTimer *a); 29 | void ets_timer_setfn(ETSTimer *t, ETSTimerFunc *fn, void *parg); 30 | void ets_update_cpu_frequency(int freqmhz); 31 | int os_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); 32 | int os_snprintf(char *str, size_t size, const char *format, ...) __attribute__ ((format (printf, 3, 4))); 33 | int os_printf_plus(const char *format, ...) __attribute__ ((format (printf, 1, 2))); 34 | void pvPortFree(void *ptr); 35 | void *pvPortMalloc(size_t xWantedSize); 36 | void *pvPortZalloc(size_t); 37 | void uart_div_modify(int no, unsigned int freq); 38 | void vPortFree(void *ptr); 39 | void *vPortMalloc(size_t xWantedSize); 40 | uint8 wifi_get_opmode(void); 41 | uint32 system_get_time(); 42 | int os_random(); 43 | int rand(void); 44 | void ets_bzero(void *s, size_t n); 45 | void ets_delay_us(int ms); 46 | #endif 47 | -------------------------------------------------------------------------------- /esphttpd/include/httpdconfig.h: -------------------------------------------------------------------------------- 1 | 2 | //Define this if you want to be able to use Heatshrink-compressed espfs images. 3 | #define EFS_HEATSHRINK 4 | 5 | //Pos of esp fs in flash 6 | #define ESPFS_POS 0x12000 7 | 8 | //If you want, you can define a realm for the authentication system. 9 | //#define HTTP_AUTH_REALM "MyRealm" -------------------------------------------------------------------------------- /esphttpd/include/mem_manager.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/esphttpd/include/mem_manager.h -------------------------------------------------------------------------------- /esphttpd/include/stdint.h: -------------------------------------------------------------------------------- 1 | 2 | //Including the system-wide stdint.h messes stuff up... but I don't want to change heatshrink 3 | //not to do it. Including this dummy file fixes it too, tho'. 4 | 5 | #ifndef __ets__ 6 | //Do include stdint for testing builds. 7 | #include_next 8 | #endif -------------------------------------------------------------------------------- /esphttpd/include/uart_hw.h: -------------------------------------------------------------------------------- 1 | //Generated at 2012-07-03 18:44:06 2 | /* 3 | * Copyright (c) 2010 - 2011 Espressif System 4 | * 5 | */ 6 | 7 | #ifndef UART_REGISTER_H_INCLUDED 8 | #define UART_REGISTER_H_INCLUDED 9 | #define REG_UART_BASE( i ) (0x60000000+(i)*0xf00) 10 | //version value:32'h062000 11 | 12 | #define UART_FIFO( i ) (REG_UART_BASE( i ) + 0x0) 13 | #define UART_RXFIFO_RD_BYTE 0x000000FF 14 | #define UART_RXFIFO_RD_BYTE_S 0 15 | 16 | #define UART_INT_RAW( i ) (REG_UART_BASE( i ) + 0x4) 17 | #define UART_RXFIFO_TOUT_INT_RAW (BIT(8)) 18 | #define UART_BRK_DET_INT_RAW (BIT(7)) 19 | #define UART_CTS_CHG_INT_RAW (BIT(6)) 20 | #define UART_DSR_CHG_INT_RAW (BIT(5)) 21 | #define UART_RXFIFO_OVF_INT_RAW (BIT(4)) 22 | #define UART_FRM_ERR_INT_RAW (BIT(3)) 23 | #define UART_PARITY_ERR_INT_RAW (BIT(2)) 24 | #define UART_TXFIFO_EMPTY_INT_RAW (BIT(1)) 25 | #define UART_RXFIFO_FULL_INT_RAW (BIT(0)) 26 | 27 | #define UART_INT_ST( i ) (REG_UART_BASE( i ) + 0x8) 28 | #define UART_RXFIFO_TOUT_INT_ST (BIT(8)) 29 | #define UART_BRK_DET_INT_ST (BIT(7)) 30 | #define UART_CTS_CHG_INT_ST (BIT(6)) 31 | #define UART_DSR_CHG_INT_ST (BIT(5)) 32 | #define UART_RXFIFO_OVF_INT_ST (BIT(4)) 33 | #define UART_FRM_ERR_INT_ST (BIT(3)) 34 | #define UART_PARITY_ERR_INT_ST (BIT(2)) 35 | #define UART_TXFIFO_EMPTY_INT_ST (BIT(1)) 36 | #define UART_RXFIFO_FULL_INT_ST (BIT(0)) 37 | 38 | #define UART_INT_ENA( i ) (REG_UART_BASE( i ) + 0xC) 39 | #define UART_RXFIFO_TOUT_INT_ENA (BIT(8)) 40 | #define UART_BRK_DET_INT_ENA (BIT(7)) 41 | #define UART_CTS_CHG_INT_ENA (BIT(6)) 42 | #define UART_DSR_CHG_INT_ENA (BIT(5)) 43 | #define UART_RXFIFO_OVF_INT_ENA (BIT(4)) 44 | #define UART_FRM_ERR_INT_ENA (BIT(3)) 45 | #define UART_PARITY_ERR_INT_ENA (BIT(2)) 46 | #define UART_TXFIFO_EMPTY_INT_ENA (BIT(1)) 47 | #define UART_RXFIFO_FULL_INT_ENA (BIT(0)) 48 | 49 | #define UART_INT_CLR( i ) (REG_UART_BASE( i ) + 0x10) 50 | #define UART_RXFIFO_TOUT_INT_CLR (BIT(8)) 51 | #define UART_BRK_DET_INT_CLR (BIT(7)) 52 | #define UART_CTS_CHG_INT_CLR (BIT(6)) 53 | #define UART_DSR_CHG_INT_CLR (BIT(5)) 54 | #define UART_RXFIFO_OVF_INT_CLR (BIT(4)) 55 | #define UART_FRM_ERR_INT_CLR (BIT(3)) 56 | #define UART_PARITY_ERR_INT_CLR (BIT(2)) 57 | #define UART_TXFIFO_EMPTY_INT_CLR (BIT(1)) 58 | #define UART_RXFIFO_FULL_INT_CLR (BIT(0)) 59 | 60 | #define UART_CLKDIV( i ) (REG_UART_BASE( i ) + 0x14) 61 | #define UART_CLKDIV_CNT 0x000FFFFF 62 | #define UART_CLKDIV_S 0 63 | 64 | #define UART_AUTOBAUD( i ) (REG_UART_BASE( i ) + 0x18) 65 | #define UART_GLITCH_FILT 0x000000FF 66 | #define UART_GLITCH_FILT_S 8 67 | #define UART_AUTOBAUD_EN (BIT(0)) 68 | 69 | #define UART_STATUS( i ) (REG_UART_BASE( i ) + 0x1C) 70 | #define UART_TXD (BIT(31)) 71 | #define UART_RTSN (BIT(30)) 72 | #define UART_DTRN (BIT(29)) 73 | #define UART_TXFIFO_CNT 0x000000FF 74 | #define UART_TXFIFO_CNT_S 16 75 | #define UART_RXD (BIT(15)) 76 | #define UART_CTSN (BIT(14)) 77 | #define UART_DSRN (BIT(13)) 78 | #define UART_RXFIFO_CNT 0x000000FF 79 | #define UART_RXFIFO_CNT_S 0 80 | 81 | #define UART_CONF0( i ) (REG_UART_BASE( i ) + 0x20) 82 | #define UART_TXFIFO_RST (BIT(18)) 83 | #define UART_RXFIFO_RST (BIT(17)) 84 | #define UART_IRDA_EN (BIT(16)) 85 | #define UART_TX_FLOW_EN (BIT(15)) 86 | #define UART_LOOPBACK (BIT(14)) 87 | #define UART_IRDA_RX_INV (BIT(13)) 88 | #define UART_IRDA_TX_INV (BIT(12)) 89 | #define UART_IRDA_WCTL (BIT(11)) 90 | #define UART_IRDA_TX_EN (BIT(10)) 91 | #define UART_IRDA_DPLX (BIT(9)) 92 | #define UART_TXD_BRK (BIT(8)) 93 | #define UART_SW_DTR (BIT(7)) 94 | #define UART_SW_RTS (BIT(6)) 95 | #define UART_STOP_BIT_NUM 0x00000003 96 | #define UART_STOP_BIT_NUM_S 4 97 | #define UART_BIT_NUM 0x00000003 98 | #define UART_BIT_NUM_S 2 99 | #define UART_PARITY_EN (BIT(1)) 100 | #define UART_PARITY (BIT(0)) 101 | 102 | #define UART_CONF1( i ) (REG_UART_BASE( i ) + 0x24) 103 | #define UART_RX_TOUT_EN (BIT(31)) 104 | #define UART_RX_TOUT_THRHD 0x0000007F 105 | #define UART_RX_TOUT_THRHD_S 24 106 | #define UART_RX_FLOW_EN (BIT(23)) 107 | #define UART_RX_FLOW_THRHD 0x0000007F 108 | #define UART_RX_FLOW_THRHD_S 16 109 | #define UART_TXFIFO_EMPTY_THRHD 0x0000007F 110 | #define UART_TXFIFO_EMPTY_THRHD_S 8 111 | #define UART_RXFIFO_FULL_THRHD 0x0000007F 112 | #define UART_RXFIFO_FULL_THRHD_S 0 113 | 114 | #define UART_LOWPULSE( i ) (REG_UART_BASE( i ) + 0x28) 115 | #define UART_LOWPULSE_MIN_CNT 0x000FFFFF 116 | #define UART_LOWPULSE_MIN_CNT_S 0 117 | 118 | #define UART_HIGHPULSE( i ) (REG_UART_BASE( i ) + 0x2C) 119 | #define UART_HIGHPULSE_MIN_CNT 0x000FFFFF 120 | #define UART_HIGHPULSE_MIN_CNT_S 0 121 | 122 | #define UART_PULSE_NUM( i ) (REG_UART_BASE( i ) + 0x30) 123 | #define UART_PULSE_NUM_CNT 0x0003FF 124 | #define UART_PULSE_NUM_CNT_S 0 125 | 126 | #define UART_DATE( i ) (REG_UART_BASE( i ) + 0x78) 127 | #define UART_ID( i ) (REG_UART_BASE( i ) + 0x7C) 128 | #endif // UART_REGISTER_H_INCLUDED 129 | 130 | #define RX_BUFF_SIZE 256 131 | #define TX_BUFF_SIZE 100 132 | #define UART0 0 133 | #define UART1 1 134 | 135 | typedef enum { 136 | FIVE_BITS = 0x0, 137 | SIX_BITS = 0x1, 138 | SEVEN_BITS = 0x2, 139 | EIGHT_BITS = 0x3 140 | } UartBitsNum4Char; 141 | 142 | typedef enum { 143 | ONE_STOP_BIT = 0, 144 | ONE_HALF_STOP_BIT = BIT2, 145 | TWO_STOP_BIT = BIT2 146 | } UartStopBitsNum; 147 | 148 | typedef enum { 149 | NONE_BITS = 0, 150 | ODD_BITS = 0, 151 | EVEN_BITS = BIT4 152 | } UartParityMode; 153 | 154 | typedef enum { 155 | STICK_PARITY_DIS = 0, 156 | STICK_PARITY_EN = BIT3 | BIT5 157 | } UartExistParity; 158 | 159 | typedef enum { 160 | BIT_RATE_9600 = 9600, 161 | BIT_RATE_19200 = 19200, 162 | BIT_RATE_38400 = 38400, 163 | BIT_RATE_57600 = 57600, 164 | BIT_RATE_74880 = 74880, 165 | BIT_RATE_115200 = 115200, 166 | BIT_RATE_230400 = 230400, 167 | BIT_RATE_460800 = 460800, 168 | BIT_RATE_921600 = 921600 169 | } UartBautRate; 170 | 171 | typedef enum { 172 | NONE_CTRL, 173 | HARDWARE_CTRL, 174 | XON_XOFF_CTRL 175 | } UartFlowCtrl; 176 | 177 | typedef enum { 178 | EMPTY, 179 | UNDER_WRITE, 180 | WRITE_OVER 181 | } RcvMsgBuffState; 182 | 183 | typedef struct { 184 | uint32 TrxBuffSize; 185 | uint8 *pTrxBuff; 186 | } TrxMsgBuff; 187 | 188 | typedef enum { 189 | BAUD_RATE_DET, 190 | WAIT_SYNC_FRM, 191 | SRCH_MSG_HEAD, 192 | RCV_MSG_BODY, 193 | RCV_ESC_CHAR, 194 | } RcvMsgState; 195 | 196 | -------------------------------------------------------------------------------- /esphttpd/include/user_config.h: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/.gitignore: -------------------------------------------------------------------------------- 1 | heatshrink/heatshrink 2 | heatshrink/test_heatshrink_dynamic 3 | heatshrink/test_heatshrink_static 4 | heatshrink/*.o 5 | heatshrink/*.core 6 | heatshrink/*.dSYM 7 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | compiler: 4 | - clang 5 | - gcc 6 | 7 | install: make test_heatshrink_dynamic 8 | 9 | script: ./test_heatshrink_dynamic 10 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Scott Vokes 2 | All rights reserved. 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = heatshrink 2 | #OPTIMIZE = -O0 3 | #OPTIMIZE = -Os 4 | OPTIMIZE = -O3 5 | WARN = -Wall -Wextra -pedantic #-Werror 6 | CFLAGS += -std=c99 -g ${WARN} ${OPTIMIZE} 7 | CFLAGS += -Wmissing-prototypes 8 | CFLAGS += -Wstrict-prototypes 9 | CFLAGS += -Wmissing-declarations 10 | 11 | # If libtheft is available, build additional property-based tests. 12 | # Uncomment these to use it in test_heatshrink_dynamic. 13 | #CFLAGS += -DHEATSHRINK_HAS_THEFT 14 | #LDFLAGS += -ltheft 15 | 16 | all: 17 | @echo "For tests, make test_heatshrink_dynamic (default) or change the" 18 | @echo "config.h to disable static memory and build test_heatshrink_static." 19 | @echo "For the standalone command-line tool, make heatshrink." 20 | 21 | ${PROJECT}: heatshrink.c 22 | 23 | OBJS= heatshrink_encoder.o \ 24 | heatshrink_decoder.o \ 25 | 26 | heatshrink: ${OBJS} 27 | test_heatshrink_dynamic: ${OBJS} test_heatshrink_dynamic_theft.o 28 | test_heatshrink_static: ${OBJS} 29 | 30 | *.o: Makefile heatshrink_config.h 31 | 32 | heatshrink_decoder.o: heatshrink_decoder.h heatshrink_common.h 33 | heatshrink_encoder.o: heatshrink_encoder.h heatshrink_common.h 34 | 35 | tags: TAGS 36 | 37 | TAGS: 38 | etags *.[ch] 39 | 40 | diagrams: dec_sm.png enc_sm.png 41 | 42 | dec_sm.png: dec_sm.dot 43 | dot -o $@ -Tpng $< 44 | 45 | enc_sm.png: enc_sm.dot 46 | dot -o $@ -Tpng $< 47 | 48 | clean: 49 | rm -f ${PROJECT} test_heatshrink_{dynamic,static} *.o *.core {dec,enc}_sm.png TAGS 50 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/README.md: -------------------------------------------------------------------------------- 1 | # heatshrink 2 | 3 | A data compression/decompression library for embedded/real-time systems. 4 | 5 | ## Key Features: 6 | 7 | - **Low memory usage (as low as 50 bytes)** 8 | It is useful for some cases with less than 50 bytes, and useful 9 | for many general cases with < 300 bytes. 10 | - **Incremental, bounded CPU use** 11 | You can chew on input data in arbitrarily tiny bites. 12 | This is a useful property in hard real-time environments. 13 | - **Can use either static or dynamic memory allocation** 14 | The library doesn't impose any constraints on memory management. 15 | - **ISC license** 16 | You can use it freely, even for commercial purposes. 17 | 18 | ## Getting Started: 19 | 20 | There is a standalone command-line program, `heatshrink`, but the 21 | encoder and decoder can also be used as libraries, independent of each 22 | other. To do so, copy `heatshrink_common.h`, `heatshrink_config.h`, and 23 | either `heatshrink_encoder.c` or `heatshrink_decoder.c` (and their 24 | respective header) into your project. 25 | 26 | Dynamic allocation is used by default, but in an embedded context, you 27 | probably want to statically allocate the encoder/decoder. Set 28 | `HEATSHRINK_DYNAMIC_ALLOC` to 0 in `heatshrink_config.h`. 29 | 30 | ## More Information and Benchmarks: 31 | 32 | heatshrink is based on [LZSS], since it's particularly suitable for 33 | compression in small amounts of memory. It can use an optional, small 34 | [index] to make compression significantly faster, but otherwise can run 35 | in under 100 bytes of memory. The index currently adds 2^(window size+1) 36 | bytes to memory usage for compression, and temporarily allocates 512 37 | bytes on the stack during index construction. 38 | 39 | For more information, see the [blog post] for an overview, and the 40 | `heatshrink_encoder.h` / `heatshrink_decoder.h` header files for API 41 | documentation. 42 | 43 | [blog post]: http://spin.atomicobject.com/2013/03/14/heatshrink-embedded-data-compression/ 44 | [index]: http://spin.atomicobject.com/2014/01/13/lightweight-indexing-for-embedded-systems/ 45 | [LZSS]: http://en.wikipedia.org/wiki/Lempel-Ziv-Storer-Szymanski 46 | 47 | ## Build Status 48 | 49 | [![Build Status](https://travis-ci.org/atomicobject/heatshrink.png)](http://travis-ci.org/atomicobject/heatshrink) 50 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/dec_sm.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | graph [label="Decoder state machine", labelloc="t"] 3 | Start [style="invis", shape="point"] 4 | empty 5 | input_available 6 | yield_literal 7 | backref_index_msb 8 | backref_index_lsb 9 | backref_count_msb 10 | backref_count_lsb 11 | yield_backref 12 | check_for_more_input 13 | done [peripheries=2] 14 | 15 | empty->input_available [label="sink()", color="blue", weight=10] 16 | Start->empty 17 | 18 | input_available->yield_literal [label="pop 1-bit"] 19 | input_available->backref_index_msb [label="pop 0-bit", weight=10] 20 | input_available->backref_index_lsb [label="pop 0-bit, index <8 bits", weight=10] 21 | 22 | yield_literal->yield_literal [label="sink()", color="blue"] 23 | yield_literal->yield_literal [label="poll()", color="red"] 24 | yield_literal->check_for_more_input [label="poll(), done", color="red"] 25 | 26 | backref_index_msb->backref_index_msb [label="sink()", color="blue"] 27 | backref_index_msb->backref_index_lsb [label="pop index, upper bits", weight=10] 28 | backref_index_msb->done [label="finish()", color="blue"] 29 | 30 | backref_index_lsb->backref_index_lsb [label="sink()", color="blue"] 31 | backref_index_lsb->backref_count_msb [label="pop index, lower bits", weight=10] 32 | backref_index_lsb->backref_count_lsb [label="pop index, count <=8 bits", weight=10] 33 | backref_index_lsb->done [label="finish()", color="blue"] 34 | 35 | backref_count_msb->backref_count_msb [label="sink()", color="blue"] 36 | backref_count_msb->backref_count_lsb [label="pop count, upper bits", weight=10] 37 | backref_count_msb->done [label="finish()", color="blue"] 38 | 39 | backref_count_lsb->backref_count_lsb [label="sink()", color="blue"] 40 | backref_count_lsb->yield_backref [label="pop count, lower bits", weight=10] 41 | backref_count_lsb->done [label="finish()", color="blue"] 42 | 43 | yield_backref->yield_backref [label="sink()", color="blue"] 44 | yield_backref->yield_backref [label="poll()", color="red"] 45 | yield_backref->check_for_more_input [label="poll(), done", 46 | color="red", weight=10] 47 | 48 | check_for_more_input->empty [label="no"] 49 | check_for_more_input->input_available [label="yes"] 50 | 51 | empty->done [label="finish()", color="blue"] 52 | } 53 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/enc_sm.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | graph [label="Encoder state machine", labelloc="t"] 3 | start [style="invis", shape="point"] 4 | not_full 5 | filled 6 | search 7 | yield_tag_bit 8 | yield_literal 9 | yield_br_length 10 | yield_br_index 11 | save_backlog 12 | flush_bits 13 | done [peripheries=2] 14 | 15 | start->not_full [label="start"] 16 | 17 | not_full->not_full [label="sink(), not full", color="blue"] 18 | not_full->filled [label="sink(), buffer is full", color="blue"] 19 | not_full->filled [label="finish(), set is_finished", color="blue"] 20 | 21 | filled->search [label="indexing (if any)"] 22 | 23 | search->search [label="step"] 24 | search->yield_tag_bit [label="literal"] 25 | search->yield_tag_bit [label="match found"] 26 | search->save_backlog [label="input exhausted"] 27 | 28 | yield_tag_bit->yield_tag_bit [label="poll(), full buf", color="red"] 29 | yield_tag_bit->yield_literal [label="poll(), literal", color="red"] 30 | yield_tag_bit->yield_br_index [label="poll(), no literal", color="red"] 31 | yield_tag_bit->flush_bits [label="finishing, no literal"] 32 | 33 | yield_literal->yield_literal [label="poll(), full buf", color="red"] 34 | yield_literal->search [label="poll(), no match", color="red"] 35 | yield_literal->yield_tag_bit [label="poll(), match", color="red"] 36 | yield_literal->flush_bits [label="poll(), final literal", color="red"] 37 | 38 | yield_br_index->yield_br_index [label="poll(), full buf", color="red"] 39 | yield_br_index->yield_br_length [label="poll()", color="red"] 40 | 41 | yield_br_length->yield_br_length [label="poll(), full buf", color="red"] 42 | yield_br_length->search [label="done"] 43 | 44 | save_backlog->flush_bits [label="finishing, no literal"] 45 | save_backlog->yield_tag_bit [label="finishing, literal"] 46 | save_backlog->not_full [label="expect more input"] 47 | 48 | flush_bits->flush_bits [label="poll(), full buf", color="red"] 49 | flush_bits->done [label="poll(), flushed", color="red"] 50 | flush_bits->done [label="no more output"] 51 | } 52 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/heatshrink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "heatshrink_encoder.h" 11 | #include "heatshrink_decoder.h" 12 | 13 | #define DEF_WINDOW_SZ2 11 14 | #define DEF_LOOKAHEAD_SZ2 4 15 | #define DEF_DECODER_INPUT_BUFFER_SIZE 256 16 | #define DEF_BUFFER_SIZE (64 * 1024) 17 | 18 | #if 0 19 | #define LOG(...) fprintf(stderr, __VA_ARGS__) 20 | #else 21 | #define LOG(...) /* NO-OP */ 22 | #endif 23 | 24 | static const int version_major = HEATSHRINK_VERSION_MAJOR; 25 | static const int version_minor = HEATSHRINK_VERSION_MINOR; 26 | static const int version_patch = HEATSHRINK_VERSION_PATCH; 27 | static const char author[] = HEATSHRINK_AUTHOR; 28 | static const char url[] = HEATSHRINK_URL; 29 | 30 | static void usage(void) { 31 | fprintf(stderr, "heatshrink version %u.%u.%u by %s\n", 32 | version_major, version_minor, version_patch, author); 33 | fprintf(stderr, "Home page: %s\n\n", url); 34 | fprintf(stderr, 35 | "Usage:\n" 36 | " heatshrink [-h] [-e|-d] [-v] [-w SIZE] [-l BITS] [IN_FILE] [OUT_FILE]\n" 37 | "\n" 38 | "heatshrink compresses or uncompresses byte streams using LZSS, and is\n" 39 | "designed especially for embedded, low-memory, and/or hard real-time\n" 40 | "systems.\n" 41 | "\n" 42 | " -h print help\n" 43 | " -e encode (compress, default)\n" 44 | " -d decode (uncompress)\n" 45 | " -v verbose (print input & output sizes, compression ratio, etc.)\n" 46 | "\n" 47 | " -w SIZE Base-2 log of LZSS sliding window size\n" 48 | "\n" 49 | " A larger value allows searches a larger history of the data for repeated\n" 50 | " patterns, potentially compressing more effectively, but will use\n" 51 | " more memory and processing time.\n" 52 | " Recommended default: -w 8 (embedded systems), -w 10 (elsewhere)\n" 53 | " \n" 54 | " -l BITS Number of bits used for back-reference lengths\n" 55 | "\n" 56 | " A larger value allows longer substitutions, but since all\n" 57 | " back-references must use -w + -l bits, larger -w or -l can be\n" 58 | " counterproductive if most patterns are small and/or local.\n" 59 | " Recommended default: -l 4\n" 60 | "\n" 61 | " If IN_FILE or OUT_FILE are unspecified, they will default to\n" 62 | " \"-\" for standard input and standard output, respectively.\n"); 63 | exit(1); 64 | } 65 | 66 | typedef enum { IO_READ, IO_WRITE, } IO_mode; 67 | typedef enum { OP_ENC, OP_DEC, } Operation; 68 | 69 | typedef struct { 70 | int fd; /* file descriptor */ 71 | IO_mode mode; 72 | size_t fill; /* fill index */ 73 | size_t read; /* read index */ 74 | size_t size; 75 | size_t total; 76 | uint8_t buf[]; 77 | } io_handle; 78 | 79 | typedef struct { 80 | uint8_t window_sz2; 81 | uint8_t lookahead_sz2; 82 | size_t decoder_input_buffer_size; 83 | size_t buffer_size; 84 | uint8_t verbose; 85 | Operation cmd; 86 | char *in_fname; 87 | char *out_fname; 88 | io_handle *in; 89 | io_handle *out; 90 | } config; 91 | 92 | static void die(char *msg) { 93 | fprintf(stderr, "%s\n", msg); 94 | exit(EXIT_FAILURE); 95 | } 96 | 97 | static void report(config *cfg); 98 | 99 | /* Open an IO handle. Returns NULL on error. */ 100 | static io_handle *handle_open(char *fname, IO_mode m, size_t buf_sz) { 101 | io_handle *io = NULL; 102 | io = malloc(sizeof(*io) + buf_sz); 103 | if (io == NULL) { return NULL; } 104 | memset(io, 0, sizeof(*io) + buf_sz); 105 | io->fd = -1; 106 | io->size = buf_sz; 107 | io->mode = m; 108 | 109 | if (m == IO_READ) { 110 | if (0 == strcmp("-", fname)) { 111 | io->fd = STDIN_FILENO; 112 | } else { 113 | io->fd = open(fname, O_RDONLY); 114 | } 115 | } else if (m == IO_WRITE) { 116 | if (0 == strcmp("-", fname)) { 117 | io->fd = STDOUT_FILENO; 118 | } else { 119 | io->fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC /*| O_EXCL*/, 0644); 120 | } 121 | } 122 | 123 | if (io->fd == -1) { /* failed to open */ 124 | free(io); 125 | err(1, "open"); 126 | return NULL; 127 | } 128 | 129 | return io; 130 | } 131 | 132 | /* Read SIZE bytes from an IO handle and return a pointer to the content. 133 | * BUF contains at least size_t bytes. Returns 0 on EOF, -1 on error. */ 134 | static ssize_t handle_read(io_handle *io, size_t size, uint8_t **buf) { 135 | LOG("@ read %zd\n", size); 136 | if (buf == NULL) { return -1; } 137 | if (size > io->size) { 138 | printf("size %zd, io->size %zd\n", size, io->size); 139 | return -1; 140 | } 141 | if (io->mode != IO_READ) { return -1; } 142 | 143 | size_t rem = io->fill - io->read; 144 | if (rem >= size) { 145 | *buf = &io->buf[io->read]; 146 | return size; 147 | } else { /* read and replenish */ 148 | if (io->fd == -1) { /* already closed, return what we've got */ 149 | *buf = &io->buf[io->read]; 150 | return rem; 151 | } 152 | 153 | memmove(io->buf, &io->buf[io->read], rem); 154 | io->fill -= io->read; 155 | io->read = 0; 156 | ssize_t read_sz = read(io->fd, &io->buf[io->fill], io->size - io->fill); 157 | if (read_sz < 0) { err(1, "read"); } 158 | io->total += read_sz; 159 | if (read_sz == 0) { /* EOF */ 160 | if (close(io->fd) < 0) { err(1, "close"); } 161 | io->fd = -1; 162 | } 163 | io->fill += read_sz; 164 | *buf = io->buf; 165 | return io->fill > size ? size : io->fill; 166 | } 167 | } 168 | 169 | /* Drop the oldest SIZE bytes from the buffer. Returns <0 on error. */ 170 | static int handle_drop(io_handle *io, size_t size) { 171 | LOG("@ drop %zd\n", size); 172 | if (io->read + size <= io->fill) { 173 | io->read += size; 174 | } else { 175 | return -1; 176 | } 177 | if (io->read == io->fill) { 178 | io->read = 0; 179 | io->fill = 0; 180 | } 181 | return 0; 182 | } 183 | 184 | /* Sink SIZE bytes from INPUT into the io handle. Returns the number of 185 | * bytes written, or -1 on error. */ 186 | static ssize_t handle_sink(io_handle *io, size_t size, uint8_t *input) { 187 | LOG("@ sink %zd\n", size); 188 | if (size > io->size) { return -1; } 189 | if (io->mode != IO_WRITE) { return -1; } 190 | 191 | if (io->fill + size > io->size) { 192 | ssize_t written = write(io->fd, io->buf, io->fill); 193 | LOG("@ flushing %zd, wrote %zd\n", io->fill, written); 194 | io->total += written; 195 | if (written == -1) { err(1, "write"); } 196 | memmove(io->buf, &io->buf[written], io->fill - written); 197 | io->fill -= written; 198 | } 199 | memcpy(&io->buf[io->fill], input, size); 200 | io->fill += size; 201 | return size; 202 | } 203 | 204 | static void handle_close(io_handle *io) { 205 | if (io->fd != -1) { 206 | if (io->mode == IO_WRITE) { 207 | ssize_t written = write(io->fd, io->buf, io->fill); 208 | io->total += written; 209 | LOG("@ close: flushing %zd, wrote %zd\n", io->fill, written); 210 | if (written == -1) { err(1, "write"); } 211 | } 212 | close(io->fd); 213 | io->fd = -1; 214 | } 215 | } 216 | 217 | static void close_and_report(config *cfg) { 218 | handle_close(cfg->in); 219 | handle_close(cfg->out); 220 | if (cfg->verbose) { report(cfg); } 221 | free(cfg->in); 222 | free(cfg->out); 223 | } 224 | 225 | static int encoder_sink_read(config *cfg, heatshrink_encoder *hse, 226 | uint8_t *data, size_t data_sz) { 227 | size_t out_sz = 4096; 228 | uint8_t out_buf[out_sz]; 229 | memset(out_buf, 0, out_sz); 230 | size_t sink_sz = 0; 231 | size_t poll_sz = 0; 232 | HSE_sink_res sres; 233 | HSE_poll_res pres; 234 | HSE_finish_res fres; 235 | io_handle *out = cfg->out; 236 | 237 | size_t sunk = 0; 238 | do { 239 | if (data_sz > 0) { 240 | sres = heatshrink_encoder_sink(hse, &data[sunk], data_sz - sunk, &sink_sz); 241 | if (sres < 0) { die("sink"); } 242 | sunk += sink_sz; 243 | } 244 | 245 | do { 246 | pres = heatshrink_encoder_poll(hse, out_buf, out_sz, &poll_sz); 247 | if (pres < 0) { die("poll"); } 248 | if (handle_sink(out, poll_sz, out_buf) < 0) die("handle_sink"); 249 | } while (pres == HSER_POLL_MORE); 250 | 251 | if (poll_sz == 0 && data_sz == 0) { 252 | fres = heatshrink_encoder_finish(hse); 253 | if (fres < 0) { die("finish"); } 254 | if (fres == HSER_FINISH_DONE) { return 1; } 255 | } 256 | } while (sunk < data_sz); 257 | return 0; 258 | } 259 | 260 | static int encode(config *cfg) { 261 | uint8_t window_sz2 = cfg->window_sz2; 262 | size_t window_sz = 1 << window_sz2; 263 | heatshrink_encoder *hse = heatshrink_encoder_alloc(window_sz2, cfg->lookahead_sz2); 264 | if (hse == NULL) { die("failed to init encoder: bad settings"); } 265 | ssize_t read_sz = 0; 266 | io_handle *in = cfg->in; 267 | 268 | /* Process input until end of stream */ 269 | while (1) { 270 | uint8_t *input = NULL; 271 | read_sz = handle_read(in, window_sz, &input); 272 | if (input == NULL) { 273 | printf("handle read failure\n"); 274 | die("read"); 275 | } 276 | if (read_sz < 0) { die("read"); } 277 | 278 | /* Pass read to encoder and check if input is fully processed. */ 279 | if (encoder_sink_read(cfg, hse, input, read_sz)) break; 280 | 281 | if (handle_drop(in, read_sz) < 0) { die("drop"); } 282 | }; 283 | 284 | if (read_sz == -1) { err(1, "read"); } 285 | 286 | heatshrink_encoder_free(hse); 287 | close_and_report(cfg); 288 | return 0; 289 | } 290 | 291 | static int decoder_sink_read(config *cfg, heatshrink_decoder *hsd, 292 | uint8_t *data, size_t data_sz) { 293 | io_handle *out = cfg->out; 294 | size_t sink_sz = 0; 295 | size_t poll_sz = 0; 296 | size_t out_sz = 4096; 297 | uint8_t out_buf[out_sz]; 298 | memset(out_buf, 0, out_sz); 299 | 300 | HSD_sink_res sres; 301 | HSD_poll_res pres; 302 | HSD_finish_res fres; 303 | 304 | size_t sunk = 0; 305 | do { 306 | if (data_sz > 0) { 307 | sres = heatshrink_decoder_sink(hsd, &data[sunk], data_sz - sunk, &sink_sz); 308 | if (sres < 0) { die("sink"); } 309 | sunk += sink_sz; 310 | } 311 | 312 | do { 313 | pres = heatshrink_decoder_poll(hsd, out_buf, out_sz, &poll_sz); 314 | if (pres < 0) { die("poll"); } 315 | if (handle_sink(out, poll_sz, out_buf) < 0) die("handle_sink"); 316 | } while (pres == HSDR_POLL_MORE); 317 | 318 | if (data_sz == 0 && poll_sz == 0) { 319 | fres = heatshrink_decoder_finish(hsd); 320 | if (fres < 0) { die("finish"); } 321 | if (fres == HSDR_FINISH_DONE) { return 1; } 322 | } 323 | } while (sunk < data_sz); 324 | 325 | return 0; 326 | } 327 | 328 | static int decode(config *cfg) { 329 | uint8_t window_sz2 = cfg->window_sz2; 330 | size_t window_sz = 1 << window_sz2; 331 | size_t ibs = cfg->decoder_input_buffer_size; 332 | heatshrink_decoder *hsd = heatshrink_decoder_alloc(ibs, 333 | window_sz2, cfg->lookahead_sz2); 334 | if (hsd == NULL) { die("failed to init decoder"); } 335 | 336 | ssize_t read_sz = 0; 337 | 338 | io_handle *in = cfg->in; 339 | 340 | HSD_finish_res fres; 341 | 342 | /* Process input until end of stream */ 343 | while (1) { 344 | uint8_t *input = NULL; 345 | read_sz = handle_read(in, window_sz, &input); 346 | if (input == NULL) { 347 | printf("handle read failure\n"); 348 | die("read"); 349 | } 350 | if (read_sz == 0) { 351 | fres = heatshrink_decoder_finish(hsd); 352 | if (fres < 0) { die("finish"); } 353 | if (fres == HSDR_FINISH_DONE) break; 354 | } else if (read_sz < 0) { 355 | die("read"); 356 | } else { 357 | if (decoder_sink_read(cfg, hsd, input, read_sz)) { break; } 358 | if (handle_drop(in, read_sz) < 0) { die("drop"); } 359 | } 360 | } 361 | if (read_sz == -1) { err(1, "read"); } 362 | 363 | heatshrink_decoder_free(hsd); 364 | close_and_report(cfg); 365 | return 0; 366 | } 367 | 368 | static void report(config *cfg) { 369 | size_t inb = cfg->in->total; 370 | size_t outb = cfg->out->total; 371 | fprintf(cfg->out->fd == STDOUT_FILENO ? stderr : stdout, 372 | "%s %0.2f %%\t %zd -> %zd (-w %u -l %u)\n", 373 | cfg->in_fname, 100.0 - (100.0 * outb) / inb, inb, outb, 374 | cfg->window_sz2, cfg->lookahead_sz2); 375 | } 376 | 377 | static void proc_args(config *cfg, int argc, char **argv) { 378 | cfg->window_sz2 = DEF_WINDOW_SZ2; 379 | cfg->lookahead_sz2 = DEF_LOOKAHEAD_SZ2; 380 | cfg->buffer_size = DEF_BUFFER_SIZE; 381 | cfg->decoder_input_buffer_size = DEF_DECODER_INPUT_BUFFER_SIZE; 382 | cfg->cmd = OP_ENC; 383 | cfg->verbose = 0; 384 | cfg->in_fname = "-"; 385 | cfg->out_fname = "-"; 386 | 387 | int a = 0; 388 | while ((a = getopt(argc, argv, "hedi:w:l:v")) != -1) { 389 | switch (a) { 390 | case 'h': /* help */ 391 | usage(); 392 | case 'e': /* encode */ 393 | cfg->cmd = OP_ENC; break; 394 | case 'd': /* decode */ 395 | cfg->cmd = OP_DEC; break; 396 | case 'i': /* input buffer size */ 397 | cfg->decoder_input_buffer_size = atoi(optarg); 398 | break; 399 | case 'w': /* window bits */ 400 | cfg->window_sz2 = atoi(optarg); 401 | break; 402 | case 'l': /* lookahead bits */ 403 | cfg->lookahead_sz2 = atoi(optarg); 404 | break; 405 | case 'v': /* verbosity++ */ 406 | cfg->verbose++; 407 | break; 408 | case '?': /* unknown argument */ 409 | default: 410 | usage(); 411 | } 412 | } 413 | argc -= optind; 414 | argv += optind; 415 | if (argc > 0) { 416 | cfg->in_fname = argv[0]; 417 | argc--; 418 | argv++; 419 | } 420 | if (argc > 0) { cfg->out_fname = argv[0]; } 421 | } 422 | 423 | int main(int argc, char **argv) { 424 | config cfg; 425 | memset(&cfg, 0, sizeof(cfg)); 426 | proc_args(&cfg, argc, argv); 427 | 428 | if (0 == strcmp(cfg.in_fname, cfg.out_fname) 429 | && (0 != strcmp("-", cfg.in_fname))) { 430 | printf("Refusing to overwrite file '%s' with itself.\n", cfg.in_fname); 431 | exit(1); 432 | } 433 | 434 | cfg.in = handle_open(cfg.in_fname, IO_READ, cfg.buffer_size); 435 | if (cfg.in == NULL) { die("Failed to open input file for read"); } 436 | cfg.out = handle_open(cfg.out_fname, IO_WRITE, cfg.buffer_size); 437 | if (cfg.out == NULL) { die("Failed to open output file for write"); } 438 | 439 | if (cfg.cmd == OP_ENC) { 440 | return encode(&cfg); 441 | } else if (cfg.cmd == OP_DEC) { 442 | return decode(&cfg); 443 | } else { 444 | usage(); 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/heatshrink_common.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_H 2 | #define HEATSHRINK_H 3 | 4 | #define HEATSHRINK_AUTHOR "Scott Vokes " 5 | #define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink" 6 | 7 | /* Version 0.3.1 */ 8 | #define HEATSHRINK_VERSION_MAJOR 0 9 | #define HEATSHRINK_VERSION_MINOR 3 10 | #define HEATSHRINK_VERSION_PATCH 1 11 | 12 | #define HEATSHRINK_MIN_WINDOW_BITS 4 13 | #define HEATSHRINK_MAX_WINDOW_BITS 15 14 | 15 | #define HEATSHRINK_MIN_LOOKAHEAD_BITS 2 16 | 17 | #define HEATSHRINK_LITERAL_MARKER 0x01 18 | #define HEATSHRINK_BACKREF_MARKER 0x00 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/heatshrink_config.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_CONFIG_H 2 | #define HEATSHRINK_CONFIG_H 3 | 4 | /* Should functionality assuming dynamic allocation be used? */ 5 | #define HEATSHRINK_DYNAMIC_ALLOC 1 6 | 7 | #if HEATSHRINK_DYNAMIC_ALLOC 8 | /* Optional replacement of malloc/free */ 9 | #define HEATSHRINK_MALLOC(SZ) malloc(SZ) 10 | #define HEATSHRINK_FREE(P, SZ) free(P) 11 | #else 12 | /* Required parameters for static configuration */ 13 | #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32 14 | #define HEATSHRINK_STATIC_WINDOW_BITS 8 15 | #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 16 | #endif 17 | 18 | /* Turn on logging for debugging. */ 19 | #define HEATSHRINK_DEBUGGING_LOGS 0 20 | 21 | /* Use indexing for faster compression. (This requires additional space.) */ 22 | #define HEATSHRINK_USE_INDEX 1 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/heatshrink_decoder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "heatshrink_decoder.h" 4 | 5 | /* States for the polling state machine. */ 6 | typedef enum { 7 | HSDS_EMPTY, /* no input to process */ 8 | HSDS_INPUT_AVAILABLE, /* new input, completely unprocessed */ 9 | HSDS_YIELD_LITERAL, /* ready to yield literal byte */ 10 | HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */ 11 | HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */ 12 | HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */ 13 | HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */ 14 | HSDS_YIELD_BACKREF, /* ready to yield back-reference */ 15 | HSDS_CHECK_FOR_MORE_INPUT, /* check if input is exhausted */ 16 | } HSD_state; 17 | 18 | #if HEATSHRINK_DEBUGGING_LOGS 19 | #include 20 | #include 21 | #include 22 | #define LOG(...) fprintf(stderr, __VA_ARGS__) 23 | #define ASSERT(X) assert(X) 24 | static const char *state_names[] = { 25 | "empty", 26 | "input_available", 27 | "yield_literal", 28 | "backref_index", 29 | "backref_count", 30 | "yield_backref", 31 | "check_for_more_input", 32 | }; 33 | #else 34 | #define LOG(...) /* no-op */ 35 | #define ASSERT(X) /* no-op */ 36 | #endif 37 | 38 | typedef struct { 39 | uint8_t *buf; /* output buffer */ 40 | size_t buf_size; /* buffer size */ 41 | size_t *output_size; /* bytes pushed to buffer, so far */ 42 | } output_info; 43 | 44 | #define NO_BITS ((uint32_t)-1) 45 | 46 | /* Forward references. */ 47 | static uint32_t get_bits(heatshrink_decoder *hsd, uint8_t count); 48 | static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte); 49 | 50 | #if HEATSHRINK_DYNAMIC_ALLOC 51 | heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, 52 | uint8_t window_sz2, 53 | uint8_t lookahead_sz2) { 54 | if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || 55 | (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || 56 | (input_buffer_size == 0) || 57 | (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || 58 | (lookahead_sz2 > window_sz2)) { 59 | return NULL; 60 | } 61 | size_t buffers_sz = (1 << window_sz2) + input_buffer_size; 62 | size_t sz = sizeof(heatshrink_decoder) + buffers_sz; 63 | heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz); 64 | if (hsd == NULL) { return NULL; } 65 | hsd->input_buffer_size = input_buffer_size; 66 | hsd->window_sz2 = window_sz2; 67 | hsd->lookahead_sz2 = lookahead_sz2; 68 | heatshrink_decoder_reset(hsd); 69 | LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n", 70 | sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size); 71 | return hsd; 72 | } 73 | 74 | void heatshrink_decoder_free(heatshrink_decoder *hsd) { 75 | size_t buffers_sz = (1 << hsd->window_sz2) + hsd->input_buffer_size; 76 | size_t sz = sizeof(heatshrink_decoder) + buffers_sz; 77 | HEATSHRINK_FREE(hsd, sz); 78 | (void)sz; /* may not be used by free */ 79 | } 80 | #endif 81 | 82 | void heatshrink_decoder_reset(heatshrink_decoder *hsd) { 83 | size_t buf_sz = 1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd); 84 | size_t input_sz = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd); 85 | memset(hsd->buffers, 0, buf_sz + input_sz); 86 | hsd->state = HSDS_EMPTY; 87 | hsd->input_size = 0; 88 | hsd->input_index = 0; 89 | hsd->bit_index = 0x00; 90 | hsd->current_byte = 0x00; 91 | hsd->output_count = 0; 92 | hsd->output_index = 0; 93 | hsd->head_index = 0; 94 | hsd->bit_accumulator = 0x00000000; 95 | } 96 | 97 | /* Copy SIZE bytes into the decoder's input buffer, if it will fit. */ 98 | HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, 99 | uint8_t *in_buf, size_t size, size_t *input_size) { 100 | if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) { 101 | return HSDR_SINK_ERROR_NULL; 102 | } 103 | 104 | size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size; 105 | if (rem == 0) { 106 | *input_size = 0; 107 | return HSDR_SINK_FULL; 108 | } 109 | 110 | size = rem < size ? rem : size; 111 | LOG("-- sinking %zd bytes\n", size); 112 | /* copy into input buffer (at head of buffers) */ 113 | memcpy(&hsd->buffers[hsd->input_size], in_buf, size); 114 | hsd->input_size += size; 115 | if (hsd->state == HSDS_EMPTY) { 116 | hsd->state = HSDS_INPUT_AVAILABLE; 117 | hsd->input_index = 0; 118 | } 119 | *input_size = size; 120 | return HSDR_SINK_OK; 121 | } 122 | 123 | 124 | /***************** 125 | * Decompression * 126 | *****************/ 127 | 128 | #define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD)) 129 | #define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD)) 130 | 131 | // States 132 | static HSD_state st_input_available(heatshrink_decoder *hsd); 133 | static HSD_state st_yield_literal(heatshrink_decoder *hsd, 134 | output_info *oi); 135 | static HSD_state st_backref_index_msb(heatshrink_decoder *hsd); 136 | static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd); 137 | static HSD_state st_backref_count_msb(heatshrink_decoder *hsd); 138 | static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd); 139 | static HSD_state st_yield_backref(heatshrink_decoder *hsd, 140 | output_info *oi); 141 | static HSD_state st_check_for_input(heatshrink_decoder *hsd); 142 | 143 | HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, 144 | uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { 145 | if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) { 146 | return HSDR_POLL_ERROR_NULL; 147 | } 148 | *output_size = 0; 149 | 150 | output_info oi; 151 | oi.buf = out_buf; 152 | oi.buf_size = out_buf_size; 153 | oi.output_size = output_size; 154 | 155 | while (1) { 156 | LOG("-- poll, state is %d (%s), input_size %d\n", 157 | hsd->state, state_names[hsd->state], hsd->input_size); 158 | uint8_t in_state = hsd->state; 159 | switch (in_state) { 160 | case HSDS_EMPTY: 161 | return HSDR_POLL_EMPTY; 162 | case HSDS_INPUT_AVAILABLE: 163 | hsd->state = st_input_available(hsd); 164 | break; 165 | case HSDS_YIELD_LITERAL: 166 | hsd->state = st_yield_literal(hsd, &oi); 167 | break; 168 | case HSDS_BACKREF_INDEX_MSB: 169 | hsd->state = st_backref_index_msb(hsd); 170 | break; 171 | case HSDS_BACKREF_INDEX_LSB: 172 | hsd->state = st_backref_index_lsb(hsd); 173 | break; 174 | case HSDS_BACKREF_COUNT_MSB: 175 | hsd->state = st_backref_count_msb(hsd); 176 | break; 177 | case HSDS_BACKREF_COUNT_LSB: 178 | hsd->state = st_backref_count_lsb(hsd); 179 | break; 180 | case HSDS_YIELD_BACKREF: 181 | hsd->state = st_yield_backref(hsd, &oi); 182 | break; 183 | case HSDS_CHECK_FOR_MORE_INPUT: 184 | hsd->state = st_check_for_input(hsd); 185 | break; 186 | default: 187 | return HSDR_POLL_ERROR_UNKNOWN; 188 | } 189 | 190 | /* If the current state cannot advance, check if input or output 191 | * buffer are exhausted. */ 192 | if (hsd->state == in_state) { 193 | if (*output_size == out_buf_size) { return HSDR_POLL_MORE; } 194 | return HSDR_POLL_EMPTY; 195 | } 196 | } 197 | } 198 | 199 | static HSD_state st_input_available(heatshrink_decoder *hsd) { 200 | uint32_t bits = get_bits(hsd, 1); // get tag bit 201 | if (bits) { 202 | return HSDS_YIELD_LITERAL; 203 | } else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) { 204 | return HSDS_BACKREF_INDEX_MSB; 205 | } else { 206 | hsd->output_index = 0; 207 | return HSDS_BACKREF_INDEX_LSB; 208 | } 209 | } 210 | 211 | static HSD_state st_yield_literal(heatshrink_decoder *hsd, 212 | output_info *oi) { 213 | /* Emit a repeated section from the window buffer, and add it (again) 214 | * to the window buffer. (Note that the repetition can include 215 | * itself.)*/ 216 | if (*oi->output_size < oi->buf_size) { 217 | uint32_t byte = get_bits(hsd, 8); 218 | if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */ 219 | uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; 220 | uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; 221 | uint8_t c = byte & 0xFF; 222 | LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.'); 223 | buf[hsd->head_index++ & mask] = c; 224 | push_byte(hsd, oi, c); 225 | return HSDS_CHECK_FOR_MORE_INPUT; 226 | } else { 227 | return HSDS_YIELD_LITERAL; 228 | } 229 | } 230 | 231 | static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) { 232 | uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); 233 | ASSERT(bit_ct > 8); 234 | uint32_t bits = get_bits(hsd, bit_ct - 8); 235 | LOG("-- backref index (msb), got 0x%04x (+1)\n", bits); 236 | if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; } 237 | hsd->output_index = bits << 8; 238 | return HSDS_BACKREF_INDEX_LSB; 239 | } 240 | 241 | static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) { 242 | uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); 243 | uint32_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8); 244 | LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits); 245 | if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; } 246 | hsd->output_index |= bits; 247 | hsd->output_index++; 248 | uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); 249 | hsd->output_count = 0; 250 | return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB; 251 | } 252 | 253 | static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) { 254 | uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); 255 | ASSERT(br_bit_ct > 8); 256 | uint32_t bits = get_bits(hsd, br_bit_ct - 8); 257 | LOG("-- backref count (msb), got 0x%04x (+1)\n", bits); 258 | if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; } 259 | hsd->output_count = bits << 8; 260 | return HSDS_BACKREF_COUNT_LSB; 261 | } 262 | 263 | static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) { 264 | uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); 265 | uint32_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8); 266 | LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits); 267 | if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; } 268 | hsd->output_count |= bits; 269 | hsd->output_count++; 270 | return HSDS_YIELD_BACKREF; 271 | } 272 | 273 | static HSD_state st_yield_backref(heatshrink_decoder *hsd, 274 | output_info *oi) { 275 | size_t count = oi->buf_size - *oi->output_size; 276 | if (count > 0) { 277 | if (hsd->output_count < count) count = hsd->output_count; 278 | uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; 279 | uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; 280 | uint16_t neg_offset = hsd->output_index; 281 | LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset); 282 | ASSERT(neg_offset < mask + 1); 283 | ASSERT(count <= 1 << BACKREF_COUNT_BITS(hsd)); 284 | 285 | for (size_t i=0; ihead_index - neg_offset) & mask]; 287 | push_byte(hsd, oi, c); 288 | buf[hsd->head_index & mask] = c; 289 | hsd->head_index++; 290 | LOG(" -- ++ 0x%02x\n", c); 291 | } 292 | hsd->output_count -= count; 293 | if (hsd->output_count == 0) { return HSDS_CHECK_FOR_MORE_INPUT; } 294 | } 295 | return HSDS_YIELD_BACKREF; 296 | } 297 | 298 | static HSD_state st_check_for_input(heatshrink_decoder *hsd) { 299 | return (hsd->input_size == 0) ? HSDS_EMPTY : HSDS_INPUT_AVAILABLE; 300 | } 301 | 302 | /* Get the next COUNT bits from the input buffer, saving incremental progress. 303 | * Returns NO_BITS on end of input, or if more than 31 bits are requested. */ 304 | static uint32_t get_bits(heatshrink_decoder *hsd, uint8_t count) { 305 | if (count > 31) { return NO_BITS; } 306 | LOG("-- popping %u bit(s)\n", count); 307 | 308 | /* If we aren't able to get COUNT bits, suspend immediately, because we 309 | * don't track how many bits of COUNT we've accumulated before suspend. */ 310 | if (hsd->input_size == 0) { 311 | if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; } 312 | } 313 | 314 | for (int i = 0; i < count; i++) { 315 | if (hsd->bit_index == 0x00) { 316 | if (hsd->input_size == 0) { 317 | LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n", 318 | hsd->bit_accumulator, hsd->bit_accumulator); 319 | return NO_BITS; 320 | } 321 | hsd->current_byte = hsd->buffers[hsd->input_index++]; 322 | LOG(" -- pulled byte 0x%02x\n", hsd->current_byte); 323 | if (hsd->input_index == hsd->input_size) { 324 | hsd->input_index = 0; /* input is exhausted */ 325 | hsd->input_size = 0; 326 | } 327 | hsd->bit_index = 0x80; 328 | } 329 | hsd->bit_accumulator <<= 1; 330 | if (hsd->current_byte & hsd->bit_index) { 331 | hsd->bit_accumulator |= 0x01; 332 | if (0) { 333 | LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n", 334 | hsd->bit_accumulator, hsd->bit_index); 335 | } 336 | } else { 337 | if (0) { 338 | LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n", 339 | hsd->bit_accumulator, hsd->bit_index); 340 | } 341 | } 342 | hsd->bit_index >>= 1; 343 | } 344 | 345 | uint32_t res = 0; 346 | res = hsd->bit_accumulator; 347 | hsd->bit_accumulator = 0x00000000; 348 | if (count > 1) { LOG(" -- accumulated %08x\n", res); } 349 | return res; 350 | } 351 | 352 | HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) { 353 | if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; } 354 | switch (hsd->state) { 355 | case HSDS_EMPTY: 356 | return HSDR_FINISH_DONE; 357 | 358 | /* If we want to finish with no input, but are in these states, it's 359 | * because the 0-bit padding to the last byte looks like a backref 360 | * marker bit followed by all 0s for index and count bits. */ 361 | case HSDS_BACKREF_INDEX_LSB: 362 | case HSDS_BACKREF_INDEX_MSB: 363 | case HSDS_BACKREF_COUNT_LSB: 364 | case HSDS_BACKREF_COUNT_MSB: 365 | return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; 366 | 367 | /* If the output stream is padded with 0xFFs (possibly due to being in 368 | * flash memory), also explicitly check the input size rather than 369 | * uselessly returning MORE but yielding 0 bytes when polling. */ 370 | case HSDS_YIELD_LITERAL: 371 | return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; 372 | 373 | default: 374 | return HSDR_FINISH_MORE; 375 | } 376 | } 377 | 378 | static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) { 379 | LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.'); 380 | oi->buf[(*oi->output_size)++] = byte; 381 | (void)hsd; 382 | } 383 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/heatshrink_decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_DECODER_H 2 | #define HEATSHRINK_DECODER_H 3 | 4 | #include 5 | #include 6 | #include "heatshrink_common.h" 7 | #include "heatshrink_config.h" 8 | 9 | typedef enum { 10 | HSDR_SINK_OK, /* data sunk, ready to poll */ 11 | HSDR_SINK_FULL, /* out of space in internal buffer */ 12 | HSDR_SINK_ERROR_NULL=-1, /* NULL argument */ 13 | } HSD_sink_res; 14 | 15 | typedef enum { 16 | HSDR_POLL_EMPTY, /* input exhausted */ 17 | HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */ 18 | HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */ 19 | HSDR_POLL_ERROR_UNKNOWN=-2, 20 | } HSD_poll_res; 21 | 22 | typedef enum { 23 | HSDR_FINISH_DONE, /* output is done */ 24 | HSDR_FINISH_MORE, /* more output remains */ 25 | HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */ 26 | } HSD_finish_res; 27 | 28 | #if HEATSHRINK_DYNAMIC_ALLOC 29 | #define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \ 30 | ((BUF)->input_buffer_size) 31 | #define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \ 32 | ((BUF)->window_sz2) 33 | #define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ 34 | ((BUF)->lookahead_sz2) 35 | #else 36 | #define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \ 37 | HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 38 | #define HEATSHRINK_DECODER_WINDOW_BITS(_) \ 39 | (HEATSHRINK_STATIC_WINDOW_BITS) 40 | #define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ 41 | (HEATSHRINK_STATIC_LOOKAHEAD_BITS) 42 | #endif 43 | 44 | typedef struct { 45 | uint16_t input_size; /* bytes in input buffer */ 46 | uint16_t input_index; /* offset to next unprocessed input byte */ 47 | uint16_t output_count; /* how many bytes to output */ 48 | uint16_t output_index; /* index for bytes to output */ 49 | uint16_t head_index; /* head of window buffer */ 50 | uint16_t bit_accumulator; 51 | uint8_t state; /* current state machine node */ 52 | uint8_t current_byte; /* current byte of input */ 53 | uint8_t bit_index; /* current bit index */ 54 | 55 | #if HEATSHRINK_DYNAMIC_ALLOC 56 | /* Fields that are only used if dynamically allocated. */ 57 | uint8_t window_sz2; /* window buffer bits */ 58 | uint8_t lookahead_sz2; /* lookahead bits */ 59 | uint16_t input_buffer_size; /* input buffer size */ 60 | 61 | /* Input buffer, then expansion window buffer */ 62 | uint8_t buffers[]; 63 | #else 64 | /* Input buffer, then expansion window buffer */ 65 | uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_)) 66 | + HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)]; 67 | #endif 68 | } heatshrink_decoder; 69 | 70 | #if HEATSHRINK_DYNAMIC_ALLOC 71 | /* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes, 72 | * an expansion buffer size of 2^WINDOW_SZ2, and a lookahead 73 | * size of 2^lookahead_sz2. (The window buffer and lookahead sizes 74 | * must match the settings used when the data was compressed.) 75 | * Returns NULL on error. */ 76 | heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, 77 | uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2); 78 | 79 | /* Free a decoder. */ 80 | void heatshrink_decoder_free(heatshrink_decoder *hsd); 81 | #endif 82 | 83 | /* Reset a decoder. */ 84 | void heatshrink_decoder_reset(heatshrink_decoder *hsd); 85 | 86 | /* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to 87 | * indicate how many bytes were actually sunk (in case a buffer was filled). */ 88 | HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, 89 | uint8_t *in_buf, size_t size, size_t *input_size); 90 | 91 | /* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into 92 | * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ 93 | HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, 94 | uint8_t *out_buf, size_t out_buf_size, size_t *output_size); 95 | 96 | /* Notify the dencoder that the input stream is finished. 97 | * If the return value is HSDR_FINISH_MORE, there is still more output, so 98 | * call heatshrink_decoder_poll and repeat. */ 99 | HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd); 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/heatshrink_encoder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "heatshrink_encoder.h" 5 | 6 | typedef enum { 7 | HSES_NOT_FULL, /* input buffer not full enough */ 8 | HSES_FILLED, /* buffer is full */ 9 | HSES_SEARCH, /* searching for patterns */ 10 | HSES_YIELD_TAG_BIT, /* yield tag bit */ 11 | HSES_YIELD_LITERAL, /* emit literal byte */ 12 | HSES_YIELD_BR_INDEX, /* yielding backref index */ 13 | HSES_YIELD_BR_LENGTH, /* yielding backref length */ 14 | HSES_SAVE_BACKLOG, /* copying buffer to backlog */ 15 | HSES_FLUSH_BITS, /* flush bit buffer */ 16 | HSES_DONE, /* done */ 17 | } HSE_state; 18 | 19 | #if HEATSHRINK_DEBUGGING_LOGS 20 | #include 21 | #include 22 | #include 23 | #define LOG(...) fprintf(stderr, __VA_ARGS__) 24 | #define ASSERT(X) assert(X) 25 | static const char *state_names[] = { 26 | "not_full", 27 | "filled", 28 | "search", 29 | "yield_tag_bit", 30 | "yield_literal", 31 | "yield_br_index", 32 | "yield_br_length", 33 | "save_backlog", 34 | "flush_bits", 35 | "done", 36 | }; 37 | #else 38 | #define LOG(...) /* no-op */ 39 | #define ASSERT(X) /* no-op */ 40 | #endif 41 | 42 | // Encoder flags 43 | enum { 44 | FLAG_IS_FINISHING = 0x01, 45 | FLAG_HAS_LITERAL = 0x02, 46 | FLAG_ON_FINAL_LITERAL = 0x04, 47 | FLAG_BACKLOG_IS_PARTIAL = 0x08, 48 | FLAG_BACKLOG_IS_FILLED = 0x10, 49 | }; 50 | 51 | typedef struct { 52 | uint8_t *buf; /* output buffer */ 53 | size_t buf_size; /* buffer size */ 54 | size_t *output_size; /* bytes pushed to buffer, so far */ 55 | } output_info; 56 | 57 | #define MATCH_NOT_FOUND ((uint16_t)-1) 58 | 59 | static uint16_t get_input_offset(heatshrink_encoder *hse); 60 | static uint16_t get_input_buffer_size(heatshrink_encoder *hse); 61 | static uint16_t get_lookahead_size(heatshrink_encoder *hse); 62 | static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag); 63 | static int can_take_byte(output_info *oi); 64 | static int is_finishing(heatshrink_encoder *hse); 65 | static int backlog_is_partial(heatshrink_encoder *hse); 66 | static int backlog_is_filled(heatshrink_encoder *hse); 67 | static int on_final_literal(heatshrink_encoder *hse); 68 | static void save_backlog(heatshrink_encoder *hse); 69 | static int has_literal(heatshrink_encoder *hse); 70 | 71 | /* Push COUNT (max 8) bits to the output buffer, which has room. */ 72 | static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits, 73 | output_info *oi); 74 | static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi); 75 | static void push_literal_byte(heatshrink_encoder *hse, output_info *oi); 76 | 77 | #if HEATSHRINK_DYNAMIC_ALLOC 78 | heatshrink_encoder *heatshrink_encoder_alloc(uint8_t window_sz2, 79 | uint8_t lookahead_sz2) { 80 | if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || 81 | (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || 82 | (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || 83 | (lookahead_sz2 > window_sz2)) { 84 | return NULL; 85 | } 86 | 87 | /* Note: 2 * the window size is used because the buffer needs to fit 88 | * (1 << window_sz2) bytes for the current input, and an additional 89 | * (1 << window_sz2) bytes for the previous buffer of input, which 90 | * will be scanned for useful backreferences. */ 91 | size_t buf_sz = (2 << window_sz2); 92 | 93 | heatshrink_encoder *hse = HEATSHRINK_MALLOC(sizeof(*hse) + buf_sz); 94 | if (hse == NULL) { return NULL; } 95 | hse->window_sz2 = window_sz2; 96 | hse->lookahead_sz2 = lookahead_sz2; 97 | heatshrink_encoder_reset(hse); 98 | 99 | #if HEATSHRINK_USE_INDEX 100 | size_t index_sz = buf_sz*sizeof(uint16_t); 101 | hse->search_index = HEATSHRINK_MALLOC(index_sz + sizeof(struct hs_index)); 102 | if (hse->search_index == NULL) { 103 | HEATSHRINK_FREE(hse, sizeof(*hse) + buf_sz); 104 | return NULL; 105 | } 106 | hse->search_index->size = index_sz; 107 | #endif 108 | 109 | LOG("-- allocated encoder with buffer size of %zu (%u byte input size)\n", 110 | buf_sz, get_input_buffer_size(hse)); 111 | return hse; 112 | } 113 | 114 | void heatshrink_encoder_free(heatshrink_encoder *hse) { 115 | size_t buf_sz = (2 << HEATSHRINK_ENCODER_WINDOW_BITS(hse)); 116 | #if HEATSHRINK_USE_INDEX 117 | size_t index_sz = sizeof(struct hs_index) + hse->search_index->size; 118 | HEATSHRINK_FREE(hse->search_index, index_sz); 119 | (void)index_sz; 120 | #endif 121 | HEATSHRINK_FREE(hse, sizeof(heatshrink_encoder) + buf_sz); 122 | (void)buf_sz; 123 | } 124 | #endif 125 | 126 | void heatshrink_encoder_reset(heatshrink_encoder *hse) { 127 | size_t buf_sz = (2 << HEATSHRINK_ENCODER_WINDOW_BITS(hse)); 128 | memset(hse->buffer, 0, buf_sz); 129 | hse->input_size = 0; 130 | hse->state = HSES_NOT_FULL; 131 | hse->match_scan_index = 0; 132 | hse->flags = 0; 133 | hse->bit_index = 0x80; 134 | hse->current_byte = 0x00; 135 | hse->match_length = 0; 136 | 137 | hse->outgoing_bits = 0x0000; 138 | hse->outgoing_bits_count = 0; 139 | 140 | #ifdef LOOP_DETECT 141 | hse->loop_detect = (uint32_t)-1; 142 | #endif 143 | } 144 | 145 | HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse, 146 | uint8_t *in_buf, size_t size, size_t *input_size) { 147 | if ((hse == NULL) || (in_buf == NULL) || (input_size == NULL)) { 148 | return HSER_SINK_ERROR_NULL; 149 | } 150 | 151 | /* Sinking more content after saying the content is done, tsk tsk */ 152 | if (is_finishing(hse)) { return HSER_SINK_ERROR_MISUSE; } 153 | 154 | /* Sinking more content before processing is done */ 155 | if (hse->state != HSES_NOT_FULL) { return HSER_SINK_ERROR_MISUSE; } 156 | 157 | uint16_t write_offset = get_input_offset(hse) + hse->input_size; 158 | uint16_t ibs = get_input_buffer_size(hse); 159 | uint16_t rem = ibs - hse->input_size; 160 | uint16_t cp_sz = rem < size ? rem : size; 161 | 162 | memcpy(&hse->buffer[write_offset], in_buf, cp_sz); 163 | *input_size = cp_sz; 164 | hse->input_size += cp_sz; 165 | 166 | LOG("-- sunk %u bytes (of %zu) into encoder at %d, input buffer now has %u\n", 167 | cp_sz, size, write_offset, hse->input_size); 168 | if (cp_sz == rem) { 169 | LOG("-- internal buffer is now full\n"); 170 | hse->state = HSES_FILLED; 171 | } 172 | 173 | return HSER_SINK_OK; 174 | } 175 | 176 | 177 | /*************** 178 | * Compression * 179 | ***************/ 180 | 181 | static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start, 182 | uint16_t end, const uint16_t maxlen, uint16_t *match_length); 183 | static void do_indexing(heatshrink_encoder *hse); 184 | 185 | static HSE_state st_step_search(heatshrink_encoder *hse); 186 | static HSE_state st_yield_tag_bit(heatshrink_encoder *hse, 187 | output_info *oi); 188 | static HSE_state st_yield_literal(heatshrink_encoder *hse, 189 | output_info *oi); 190 | static HSE_state st_yield_br_index(heatshrink_encoder *hse, 191 | output_info *oi); 192 | static HSE_state st_yield_br_length(heatshrink_encoder *hse, 193 | output_info *oi); 194 | static HSE_state st_save_backlog(heatshrink_encoder *hse); 195 | static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse, 196 | output_info *oi); 197 | 198 | HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse, 199 | uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { 200 | if ((hse == NULL) || (out_buf == NULL) || (output_size == NULL)) { 201 | return HSER_POLL_ERROR_NULL; 202 | } 203 | if (out_buf_size == 0) { 204 | LOG("-- MISUSE: output buffer size is 0\n"); 205 | return HSER_POLL_ERROR_MISUSE; 206 | } 207 | *output_size = 0; 208 | 209 | output_info oi; 210 | oi.buf = out_buf; 211 | oi.buf_size = out_buf_size; 212 | oi.output_size = output_size; 213 | 214 | while (1) { 215 | LOG("-- polling, state %u (%s), flags 0x%02x\n", 216 | hse->state, state_names[hse->state], hse->flags); 217 | 218 | uint8_t in_state = hse->state; 219 | switch (in_state) { 220 | case HSES_NOT_FULL: 221 | return HSER_POLL_EMPTY; 222 | case HSES_FILLED: 223 | do_indexing(hse); 224 | hse->state = HSES_SEARCH; 225 | break; 226 | case HSES_SEARCH: 227 | hse->state = st_step_search(hse); 228 | break; 229 | case HSES_YIELD_TAG_BIT: 230 | hse->state = st_yield_tag_bit(hse, &oi); 231 | break; 232 | case HSES_YIELD_LITERAL: 233 | hse->state = st_yield_literal(hse, &oi); 234 | break; 235 | case HSES_YIELD_BR_INDEX: 236 | hse->state = st_yield_br_index(hse, &oi); 237 | break; 238 | case HSES_YIELD_BR_LENGTH: 239 | hse->state = st_yield_br_length(hse, &oi); 240 | break; 241 | case HSES_SAVE_BACKLOG: 242 | hse->state = st_save_backlog(hse); 243 | break; 244 | case HSES_FLUSH_BITS: 245 | hse->state = st_flush_bit_buffer(hse, &oi); 246 | case HSES_DONE: 247 | return HSER_POLL_EMPTY; 248 | default: 249 | LOG("-- bad state %s\n", state_names[hse->state]); 250 | return HSER_POLL_ERROR_MISUSE; 251 | } 252 | 253 | if (hse->state == in_state) { 254 | /* Check if output buffer is exhausted. */ 255 | if (*output_size == out_buf_size) return HSER_POLL_MORE; 256 | } 257 | } 258 | } 259 | 260 | HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse) { 261 | if (hse == NULL) { return HSER_FINISH_ERROR_NULL; } 262 | LOG("-- setting is_finishing flag\n"); 263 | hse->flags |= FLAG_IS_FINISHING; 264 | if (hse->state == HSES_NOT_FULL) { hse->state = HSES_FILLED; } 265 | return hse->state == HSES_DONE ? HSER_FINISH_DONE : HSER_FINISH_MORE; 266 | } 267 | 268 | static HSE_state st_step_search(heatshrink_encoder *hse) { 269 | uint16_t window_length = get_input_buffer_size(hse); 270 | uint16_t lookahead_sz = get_lookahead_size(hse); 271 | uint16_t msi = hse->match_scan_index; 272 | LOG("## step_search, scan @ +%d (%d/%d), input size %d\n", 273 | msi, hse->input_size + msi, 2*window_length, hse->input_size); 274 | 275 | bool fin = is_finishing(hse); 276 | if (msi >= hse->input_size - (fin ? 0 : lookahead_sz)) { 277 | /* Current search buffer is exhausted, copy it into the 278 | * backlog and await more input. */ 279 | LOG("-- end of search @ %d, saving backlog\n", msi); 280 | return HSES_SAVE_BACKLOG; 281 | } 282 | 283 | uint16_t input_offset = get_input_offset(hse); 284 | uint16_t end = input_offset + msi; 285 | 286 | uint16_t start = 0; 287 | if (backlog_is_filled(hse)) { /* last WINDOW_LENGTH bytes */ 288 | start = end - window_length + 1; 289 | } else if (backlog_is_partial(hse)) { /* clamp to available data */ 290 | start = end - window_length + 1; 291 | if (start < lookahead_sz) { start = lookahead_sz; } 292 | } else { /* only scan available input */ 293 | start = input_offset; 294 | } 295 | 296 | uint16_t max_possible = lookahead_sz; 297 | if (hse->input_size - msi < lookahead_sz) { 298 | max_possible = hse->input_size - msi; 299 | } 300 | 301 | uint16_t match_length = 0; 302 | uint16_t match_pos = find_longest_match(hse, 303 | start, end, max_possible, &match_length); 304 | 305 | if (match_pos == MATCH_NOT_FOUND) { 306 | LOG("ss Match not found\n"); 307 | hse->match_scan_index++; 308 | hse->flags |= FLAG_HAS_LITERAL; 309 | hse->match_length = 0; 310 | return HSES_YIELD_TAG_BIT; 311 | } else { 312 | LOG("ss Found match of %d bytes at %d\n", match_length, match_pos); 313 | hse->match_pos = match_pos; 314 | hse->match_length = match_length; 315 | ASSERT(match_pos < 1 << hse->window_sz2 /*window_length*/); 316 | 317 | return HSES_YIELD_TAG_BIT; 318 | } 319 | } 320 | 321 | static HSE_state st_yield_tag_bit(heatshrink_encoder *hse, 322 | output_info *oi) { 323 | if (can_take_byte(oi)) { 324 | if (hse->match_length == 0) { 325 | add_tag_bit(hse, oi, HEATSHRINK_LITERAL_MARKER); 326 | return HSES_YIELD_LITERAL; 327 | } else { 328 | add_tag_bit(hse, oi, HEATSHRINK_BACKREF_MARKER); 329 | hse->outgoing_bits = hse->match_pos - 1; 330 | hse->outgoing_bits_count = HEATSHRINK_ENCODER_WINDOW_BITS(hse); 331 | return HSES_YIELD_BR_INDEX; 332 | } 333 | } else { 334 | return HSES_YIELD_TAG_BIT; /* output is full, continue */ 335 | } 336 | } 337 | 338 | static HSE_state st_yield_literal(heatshrink_encoder *hse, 339 | output_info *oi) { 340 | if (can_take_byte(oi)) { 341 | push_literal_byte(hse, oi); 342 | hse->flags &= ~FLAG_HAS_LITERAL; 343 | if (on_final_literal(hse)) { return HSES_FLUSH_BITS; } 344 | return hse->match_length > 0 ? HSES_YIELD_TAG_BIT : HSES_SEARCH; 345 | } else { 346 | return HSES_YIELD_LITERAL; 347 | } 348 | } 349 | 350 | static HSE_state st_yield_br_index(heatshrink_encoder *hse, 351 | output_info *oi) { 352 | if (can_take_byte(oi)) { 353 | LOG("-- yielding backref index %u\n", hse->match_pos); 354 | if (push_outgoing_bits(hse, oi) > 0) { 355 | return HSES_YIELD_BR_INDEX; /* continue */ 356 | } else { 357 | hse->outgoing_bits = hse->match_length - 1; 358 | hse->outgoing_bits_count = HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse); 359 | return HSES_YIELD_BR_LENGTH; /* done */ 360 | } 361 | } else { 362 | return HSES_YIELD_BR_INDEX; /* continue */ 363 | } 364 | } 365 | 366 | static HSE_state st_yield_br_length(heatshrink_encoder *hse, 367 | output_info *oi) { 368 | if (can_take_byte(oi)) { 369 | LOG("-- yielding backref length %u\n", hse->match_length); 370 | if (push_outgoing_bits(hse, oi) > 0) { 371 | return HSES_YIELD_BR_LENGTH; 372 | } else { 373 | hse->match_scan_index += hse->match_length; 374 | hse->match_length = 0; 375 | return HSES_SEARCH; 376 | } 377 | } else { 378 | return HSES_YIELD_BR_LENGTH; 379 | } 380 | } 381 | 382 | static HSE_state st_save_backlog(heatshrink_encoder *hse) { 383 | if (is_finishing(hse)) { 384 | /* copy remaining literal (if necessary) */ 385 | if (has_literal(hse)) { 386 | hse->flags |= FLAG_ON_FINAL_LITERAL; 387 | return HSES_YIELD_TAG_BIT; 388 | } else { 389 | return HSES_FLUSH_BITS; 390 | } 391 | } else { 392 | LOG("-- saving backlog\n"); 393 | save_backlog(hse); 394 | return HSES_NOT_FULL; 395 | } 396 | } 397 | 398 | static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse, 399 | output_info *oi) { 400 | if (hse->bit_index == 0x80) { 401 | LOG("-- done!\n"); 402 | return HSES_DONE; 403 | } else if (can_take_byte(oi)) { 404 | LOG("-- flushing remaining byte (bit_index == 0x%02x)\n", hse->bit_index); 405 | oi->buf[(*oi->output_size)++] = hse->current_byte; 406 | LOG("-- done!\n"); 407 | return HSES_DONE; 408 | } else { 409 | return HSES_FLUSH_BITS; 410 | } 411 | } 412 | 413 | static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag) { 414 | LOG("-- adding tag bit: %d\n", tag); 415 | push_bits(hse, 1, tag, oi); 416 | } 417 | 418 | static uint16_t get_input_offset(heatshrink_encoder *hse) { 419 | return get_input_buffer_size(hse); 420 | } 421 | 422 | static uint16_t get_input_buffer_size(heatshrink_encoder *hse) { 423 | return (1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse)); 424 | (void)hse; 425 | } 426 | 427 | static uint16_t get_lookahead_size(heatshrink_encoder *hse) { 428 | return (1 << HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse)); 429 | (void)hse; 430 | } 431 | 432 | static void do_indexing(heatshrink_encoder *hse) { 433 | #if HEATSHRINK_USE_INDEX 434 | /* Build an index array I that contains flattened linked lists 435 | * for the previous instances of every byte in the buffer. 436 | * 437 | * For example, if buf[200] == 'x', then index[200] will either 438 | * be an offset i such that buf[i] == 'x', or a negative offset 439 | * to indicate end-of-list. This significantly speeds up matching, 440 | * while only using sizeof(uint16_t)*sizeof(buffer) bytes of RAM. 441 | * 442 | * Future optimization options: 443 | * 1. Since any negative value represents end-of-list, the other 444 | * 15 bits could be used to improve the index dynamically. 445 | * 446 | * 2. Likewise, the last lookahead_sz bytes of the index will 447 | * not be usable, so temporary data could be stored there to 448 | * dynamically improve the index. 449 | * */ 450 | struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse); 451 | uint16_t last[256]; 452 | memset(last, 0xFF, sizeof(last)); 453 | 454 | uint8_t * const data = hse->buffer; 455 | int16_t * const index = hsi->index; 456 | 457 | const uint16_t input_offset = get_input_offset(hse); 458 | const uint16_t end = input_offset + hse->input_size; 459 | 460 | for (uint16_t i=0; iflags & FLAG_IS_FINISHING; 473 | } 474 | 475 | static int backlog_is_partial(heatshrink_encoder *hse) { 476 | return hse->flags & FLAG_BACKLOG_IS_PARTIAL; 477 | } 478 | 479 | static int backlog_is_filled(heatshrink_encoder *hse) { 480 | return hse->flags & FLAG_BACKLOG_IS_FILLED; 481 | } 482 | 483 | static int on_final_literal(heatshrink_encoder *hse) { 484 | return hse->flags & FLAG_ON_FINAL_LITERAL; 485 | } 486 | 487 | static int has_literal(heatshrink_encoder *hse) { 488 | return (hse->flags & FLAG_HAS_LITERAL); 489 | } 490 | 491 | static int can_take_byte(output_info *oi) { 492 | return *oi->output_size < oi->buf_size; 493 | } 494 | 495 | /* Return the longest match for the bytes at buf[end:end+maxlen] between 496 | * buf[start] and buf[end-1]. If no match is found, return -1. */ 497 | static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start, 498 | uint16_t end, const uint16_t maxlen, uint16_t *match_length) { 499 | LOG("-- scanning for match of buf[%u:%u] between buf[%u:%u] (max %u bytes)\n", 500 | end, end + maxlen, start, end + maxlen - 1, maxlen); 501 | uint8_t *buf = hse->buffer; 502 | 503 | uint16_t match_maxlen = 0; 504 | uint16_t match_index = MATCH_NOT_FOUND; 505 | const uint16_t break_even_point = 3; 506 | uint16_t len = 0; 507 | uint8_t * const needlepoint = &buf[end]; 508 | #if HEATSHRINK_USE_INDEX 509 | struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse); 510 | int16_t pos = hsi->index[end]; 511 | 512 | while (pos >= start) { 513 | uint8_t * const pospoint = &buf[pos]; 514 | len = 0; 515 | 516 | /* Only check matches that will potentially beat the current maxlen. 517 | * This is redundant with the index if match_maxlen is 0, but the 518 | * added branch overhead to check if it == 0 seems to be worse. */ 519 | if (pospoint[match_maxlen] != needlepoint[match_maxlen]) { 520 | pos = hsi->index[pos]; 521 | continue; 522 | } 523 | 524 | for (len = 1; len < maxlen; len++) { 525 | if (pospoint[len] != needlepoint[len]) break; 526 | } 527 | 528 | if (len > match_maxlen) { 529 | match_maxlen = len; 530 | match_index = pos; 531 | if (len == maxlen) { break; } /* won't find better */ 532 | } 533 | pos = hsi->index[pos]; 534 | } 535 | #else 536 | for (int16_t pos=end - 1; pos >= start; pos--) { 537 | uint8_t * const pospoint = &buf[pos]; 538 | if ((pospoint[match_maxlen] == needlepoint[match_maxlen]) 539 | && (*pospoint == *needlepoint)) { 540 | for (len=1; len cmp buf[%d] == 0x%02x against %02x (start %u)\n", 543 | pos + len, pospoint[len], needlepoint[len], start); 544 | } 545 | if (pospoint[len] != needlepoint[len]) { break; } 546 | } 547 | if (len > match_maxlen) { 548 | match_maxlen = len; 549 | match_index = pos; 550 | if (len == maxlen) { break; } /* don't keep searching */ 551 | } 552 | } 553 | } 554 | #endif 555 | 556 | if (match_maxlen >= break_even_point) { 557 | LOG("-- best match: %u bytes at -%u\n", 558 | match_maxlen, end - match_index); 559 | *match_length = match_maxlen; 560 | return end - match_index; 561 | } 562 | LOG("-- none found\n"); 563 | return MATCH_NOT_FOUND; 564 | } 565 | 566 | static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi) { 567 | uint8_t count = 0; 568 | uint8_t bits = 0; 569 | if (hse->outgoing_bits_count > 8) { 570 | count = 8; 571 | bits = hse->outgoing_bits >> (hse->outgoing_bits_count - 8); 572 | } else { 573 | count = hse->outgoing_bits_count; 574 | bits = hse->outgoing_bits; 575 | } 576 | 577 | if (count > 0) { 578 | LOG("-- pushing %d outgoing bits: 0x%02x\n", count, bits); 579 | push_bits(hse, count, bits, oi); 580 | hse->outgoing_bits_count -= count; 581 | } 582 | return count; 583 | } 584 | 585 | /* Push COUNT (max 8) bits to the output buffer, which has room. 586 | * Bytes are set from the lowest bits, up. */ 587 | static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits, 588 | output_info *oi) { 589 | ASSERT(count <= 8); 590 | LOG("++ push_bits: %d bits, input of 0x%02x\n", count, bits); 591 | 592 | /* If adding a whole byte and at the start of a new output byte, 593 | * just push it through whole and skip the bit IO loop. */ 594 | if (count == 8 && hse->bit_index == 0x80) { 595 | oi->buf[(*oi->output_size)++] = bits; 596 | } else { 597 | for (int i=count - 1; i>=0; i--) { 598 | bool bit = bits & (1 << i); 599 | if (bit) { hse->current_byte |= hse->bit_index; } 600 | if (0) { 601 | LOG(" -- setting bit %d at bit index 0x%02x, byte => 0x%02x\n", 602 | bit ? 1 : 0, hse->bit_index, hse->current_byte); 603 | } 604 | hse->bit_index >>= 1; 605 | if (hse->bit_index == 0x00) { 606 | hse->bit_index = 0x80; 607 | LOG(" > pushing byte 0x%02x\n", hse->current_byte); 608 | oi->buf[(*oi->output_size)++] = hse->current_byte; 609 | hse->current_byte = 0x00; 610 | } 611 | } 612 | } 613 | } 614 | 615 | static void push_literal_byte(heatshrink_encoder *hse, output_info *oi) { 616 | uint16_t processed_offset = hse->match_scan_index - 1; 617 | uint16_t input_offset = get_input_offset(hse) + processed_offset; 618 | uint8_t c = hse->buffer[input_offset]; 619 | LOG("-- yielded literal byte 0x%02x ('%c') from +%d\n", 620 | c, isprint(c) ? c : '.', input_offset); 621 | push_bits(hse, 8, c, oi); 622 | } 623 | 624 | static void save_backlog(heatshrink_encoder *hse) { 625 | size_t input_buf_sz = get_input_buffer_size(hse); 626 | 627 | uint16_t msi = hse->match_scan_index; 628 | 629 | /* Copy processed data to beginning of buffer, so it can be 630 | * used for future matches. Don't bother checking whether the 631 | * input is less than the maximum size, because if it isn't, 632 | * we're done anyway. */ 633 | uint16_t rem = input_buf_sz - msi; // unprocessed bytes 634 | uint16_t shift_sz = input_buf_sz + rem; 635 | 636 | memmove(&hse->buffer[0], 637 | &hse->buffer[input_buf_sz - rem], 638 | shift_sz); 639 | 640 | if (backlog_is_partial(hse)) { 641 | /* The whole backlog is filled in now, so include it in scans. */ 642 | hse->flags |= FLAG_BACKLOG_IS_FILLED; 643 | } else { 644 | /* Include backlog, except for the first lookahead_sz bytes, which 645 | * are still undefined. */ 646 | hse->flags |= FLAG_BACKLOG_IS_PARTIAL; 647 | } 648 | hse->match_scan_index = 0; 649 | hse->input_size -= input_buf_sz - rem; 650 | } 651 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/heatshrink_encoder.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_ENCODER_H 2 | #define HEATSHRINK_ENCODER_H 3 | 4 | #include 5 | #include 6 | #include "heatshrink_common.h" 7 | #include "heatshrink_config.h" 8 | 9 | typedef enum { 10 | HSER_SINK_OK, /* data sunk into input buffer */ 11 | HSER_SINK_ERROR_NULL=-1, /* NULL argument */ 12 | HSER_SINK_ERROR_MISUSE=-2, /* API misuse */ 13 | } HSE_sink_res; 14 | 15 | typedef enum { 16 | HSER_POLL_EMPTY, /* input exhausted */ 17 | HSER_POLL_MORE, /* poll again for more output */ 18 | HSER_POLL_ERROR_NULL=-1, /* NULL argument */ 19 | HSER_POLL_ERROR_MISUSE=-2, /* API misuse */ 20 | } HSE_poll_res; 21 | 22 | typedef enum { 23 | HSER_FINISH_DONE, /* encoding is complete */ 24 | HSER_FINISH_MORE, /* more output remaining; use poll */ 25 | HSER_FINISH_ERROR_NULL=-1, /* NULL argument */ 26 | } HSE_finish_res; 27 | 28 | #if HEATSHRINK_DYNAMIC_ALLOC 29 | #define HEATSHRINK_ENCODER_WINDOW_BITS(HSE) \ 30 | ((HSE)->window_sz2) 31 | #define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(HSE) \ 32 | ((HSE)->lookahead_sz2) 33 | #define HEATSHRINK_ENCODER_INDEX(HSE) \ 34 | ((HSE)->search_index) 35 | struct hs_index { 36 | uint16_t size; 37 | int16_t index[]; 38 | }; 39 | #else 40 | #define HEATSHRINK_ENCODER_WINDOW_BITS(_) \ 41 | (HEATSHRINK_STATIC_WINDOW_BITS) 42 | #define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(_) \ 43 | (HEATSHRINK_STATIC_LOOKAHEAD_BITS) 44 | #define HEATSHRINK_ENCODER_INDEX(HSE) \ 45 | (&(HSE)->search_index) 46 | struct hs_index { 47 | uint16_t size; 48 | int16_t index[2 << HEATSHRINK_STATIC_WINDOW_BITS]; 49 | }; 50 | #endif 51 | 52 | typedef struct { 53 | uint16_t input_size; /* bytes in input buffer */ 54 | uint16_t match_scan_index; 55 | uint16_t match_length; 56 | uint16_t match_pos; 57 | uint16_t outgoing_bits; /* enqueued outgoing bits */ 58 | uint8_t outgoing_bits_count; 59 | uint8_t flags; 60 | uint8_t state; /* current state machine node */ 61 | uint8_t current_byte; /* current byte of output */ 62 | uint8_t bit_index; /* current bit index */ 63 | #if HEATSHRINK_DYNAMIC_ALLOC 64 | uint8_t window_sz2; /* 2^n size of window */ 65 | uint8_t lookahead_sz2; /* 2^n size of lookahead */ 66 | #if HEATSHRINK_USE_INDEX 67 | struct hs_index *search_index; 68 | #endif 69 | /* input buffer and / sliding window for expansion */ 70 | uint8_t buffer[]; 71 | #else 72 | #if HEATSHRINK_USE_INDEX 73 | struct hs_index search_index; 74 | #endif 75 | /* input buffer and / sliding window for expansion */ 76 | uint8_t buffer[2 << HEATSHRINK_ENCODER_WINDOW_BITS(_)]; 77 | #endif 78 | } heatshrink_encoder; 79 | 80 | #if HEATSHRINK_DYNAMIC_ALLOC 81 | /* Allocate a new encoder struct and its buffers. 82 | * Returns NULL on error. */ 83 | heatshrink_encoder *heatshrink_encoder_alloc(uint8_t window_sz2, 84 | uint8_t lookahead_sz2); 85 | 86 | /* Free an encoder. */ 87 | void heatshrink_encoder_free(heatshrink_encoder *hse); 88 | #endif 89 | 90 | /* Reset an encoder. */ 91 | void heatshrink_encoder_reset(heatshrink_encoder *hse); 92 | 93 | /* Sink up to SIZE bytes from IN_BUF into the encoder. 94 | * INPUT_SIZE is set to the number of bytes actually sunk (in case a 95 | * buffer was filled.). */ 96 | HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse, 97 | uint8_t *in_buf, size_t size, size_t *input_size); 98 | 99 | /* Poll for output from the encoder, copying at most OUT_BUF_SIZE bytes into 100 | * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ 101 | HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse, 102 | uint8_t *out_buf, size_t out_buf_size, size_t *output_size); 103 | 104 | /* Notify the encoder that the input stream is finished. 105 | * If the return value is HSER_FINISH_MORE, there is still more output, so 106 | * call heatshrink_encoder_poll and repeat. */ 107 | HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse); 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/test_heatshrink_dynamic_theft.c: -------------------------------------------------------------------------------- 1 | #include "heatshrink_config.h" 2 | #ifdef HEATSHRINK_HAS_THEFT 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "heatshrink_encoder.h" 11 | #include "heatshrink_decoder.h" 12 | #include "greatest.h" 13 | #include "theft.h" 14 | #include "greatest_theft.h" 15 | 16 | #if !HEATSHRINK_DYNAMIC_ALLOC 17 | #error Must set HEATSHRINK_DYNAMIC_ALLOC to 1 for this test suite. 18 | #endif 19 | 20 | SUITE(properties); 21 | 22 | typedef struct { 23 | int limit; 24 | int fails; 25 | int dots; 26 | } test_env; 27 | 28 | typedef struct { 29 | size_t size; 30 | uint8_t buf[]; 31 | } rbuf; 32 | 33 | static void *rbuf_alloc_cb(struct theft *t, theft_hash seed, void *env) { 34 | test_env *te = (test_env *)env; 35 | //printf("seed is 0x%016llx\n", seed); 36 | 37 | size_t sz = (size_t)(seed % te->limit) + 1; 38 | rbuf *r = malloc(sizeof(rbuf) + sz); 39 | if (r == NULL) { return THEFT_ERROR; } 40 | r->size = sz; 41 | 42 | for (size_t i = 0; i < sz; i += sizeof(theft_hash)) { 43 | theft_hash s = theft_random(t); 44 | for (uint8_t b = 0; b < sizeof(theft_hash); b++) { 45 | if (i + b >= sz) { break; } 46 | r->buf[i + b] = (uint8_t) (s >> (8*b)) & 0xff; 47 | } 48 | } 49 | 50 | return r; 51 | } 52 | 53 | static void rbuf_free_cb(void *instance, void *env) { 54 | free(instance); 55 | (void)env; 56 | } 57 | 58 | static uint64_t rbuf_hash_cb(void *instance, void *env) { 59 | rbuf *r = (rbuf *)instance; 60 | (void)env; 61 | return theft_hash_onepass(r->buf, r->size); 62 | } 63 | 64 | /* Make a copy of a buffer, keeping NEW_SZ bytes starting at OFFSET. */ 65 | static void *copy_rbuf_subset(rbuf *cur, size_t new_sz, size_t byte_offset) { 66 | if (new_sz == 0) { return THEFT_DEAD_END; } 67 | rbuf *nr = malloc(sizeof(rbuf) + new_sz); 68 | if (nr == NULL) { return THEFT_ERROR; } 69 | nr->size = new_sz; 70 | memcpy(nr->buf, &cur->buf[byte_offset], new_sz); 71 | /* printf("%zu -> %zu\n", cur->size, new_sz); */ 72 | return nr; 73 | } 74 | 75 | /* Make a copy of a buffer, but only PORTION, starting OFFSET in 76 | * (e.g. the third quarter is (0.25 at +0.75). Rounds to ints. */ 77 | static void *copy_rbuf_percent(rbuf *cur, float portion, float offset) { 78 | size_t new_sz = cur->size * portion; 79 | size_t byte_offset = (size_t)(cur->size * offset); 80 | return copy_rbuf_subset(cur, new_sz, byte_offset); 81 | } 82 | 83 | /* How to shrink a random buffer to a simpler one. */ 84 | static void *rbuf_shrink_cb(void *instance, uint32_t tactic, void *env) { 85 | rbuf *cur = (rbuf *)instance; 86 | 87 | if (tactic == 0) { /* first half */ 88 | return copy_rbuf_percent(cur, 0.5, 0); 89 | } else if (tactic == 1) { /* second half */ 90 | return copy_rbuf_percent(cur, 0.5, 0.5); 91 | } else if (tactic <= 18) { /* drop 1-16 bytes at start */ 92 | const int last_tactic = 1; 93 | const size_t drop = tactic - last_tactic; 94 | if (cur->size < drop) { return THEFT_DEAD_END; } 95 | return copy_rbuf_subset(cur, cur->size - drop, drop); 96 | } else if (tactic <= 34) { /* drop 1-16 bytes at end */ 97 | const int last_tactic = 18; 98 | const size_t drop = tactic - last_tactic; 99 | if (cur->size < drop) { return THEFT_DEAD_END; } 100 | return copy_rbuf_subset(cur, cur->size - drop, 0); 101 | } else if (tactic == 35) { 102 | /* Divide every byte by 2, saturating at 0 */ 103 | rbuf *cp = copy_rbuf_percent(cur, 1, 0); 104 | if (cp == NULL) { return THEFT_ERROR; } 105 | for (size_t i = 0; i < cp->size; i++) { cp->buf[i] /= 2; } 106 | return cp; 107 | } else if (tactic == 36) { 108 | /* subtract 1 from every byte, saturating at 0 */ 109 | rbuf *cp = copy_rbuf_percent(cur, 1, 0); 110 | if (cp == NULL) { return THEFT_ERROR; } 111 | for (size_t i = 0; i < cp->size; i++) { 112 | if (cp->buf[i] > 0) { cp->buf[i]--; } 113 | } 114 | return cp; 115 | } else { 116 | (void)env; 117 | return THEFT_NO_MORE_TACTICS; 118 | } 119 | 120 | return THEFT_NO_MORE_TACTICS; 121 | } 122 | 123 | static void rbuf_print_cb(FILE *f, void *instance, void *env) { 124 | rbuf *r = (rbuf *)instance; 125 | (void)env; 126 | fprintf(f, "buf[%zd]:\n ", r->size); 127 | uint8_t bytes = 0; 128 | for (size_t i = 0; i < r->size; i++) { 129 | fprintf(f, "%02x", r->buf[i]); 130 | bytes++; 131 | if (bytes == 16) { 132 | fprintf(f, "\n "); 133 | bytes = 0; 134 | } 135 | } 136 | fprintf(f, "\n"); 137 | } 138 | 139 | static struct theft_type_info rbuf_info = { 140 | .alloc = rbuf_alloc_cb, 141 | .free = rbuf_free_cb, 142 | .hash = rbuf_hash_cb, 143 | .shrink = rbuf_shrink_cb, 144 | .print = rbuf_print_cb, 145 | }; 146 | 147 | static theft_progress_callback_res 148 | progress_cb(struct theft_trial_info *info, void *env) { 149 | test_env *te = (test_env *)env; 150 | if ((info->trial & 0xff) == 0) { 151 | printf("."); 152 | fflush(stdout); 153 | te->dots++; 154 | if (te->dots == 64) { 155 | printf("\n"); 156 | te->dots = 0; 157 | } 158 | } 159 | 160 | if (info->status == THEFT_TRIAL_FAIL) { 161 | te->fails++; 162 | rbuf *cur = info->args[0]; 163 | if (cur->size < 5) { return THEFT_PROGRESS_HALT; } 164 | } 165 | 166 | if (te->fails > 10) { 167 | return THEFT_PROGRESS_HALT; 168 | } 169 | return THEFT_PROGRESS_CONTINUE; 170 | } 171 | 172 | /* For an arbitrary input buffer, it should never get stuck in a 173 | * state where the data has been sunk but no data can be polled. */ 174 | static theft_trial_res prop_should_not_get_stuck(void *input) { 175 | /* Make a buffer large enough for the output: 4 KB of input with 176 | * each 16 bits becoming up to 16 bytes will fit in a 64 KB buffer. 177 | * (4 KB of input comes from `env.limit = 1 << 12;` below.) */ 178 | uint8_t output[64 * 1024]; 179 | heatshrink_decoder *hsd = heatshrink_decoder_alloc((64 * 1024L) - 1, 12, 4); 180 | if (hsd == NULL) { return THEFT_TRIAL_ERROR; } 181 | 182 | rbuf *r = (rbuf *)input; 183 | 184 | size_t count = 0; 185 | HSD_sink_res sres = heatshrink_decoder_sink(hsd, r->buf, r->size, &count); 186 | if (sres != HSDR_SINK_OK) { return THEFT_TRIAL_ERROR; } 187 | 188 | size_t out_sz = 0; 189 | HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, sizeof(output), &out_sz); 190 | if (pres != HSDR_POLL_EMPTY) { return THEFT_TRIAL_FAIL; } 191 | 192 | HSD_finish_res fres = heatshrink_decoder_finish(hsd); 193 | heatshrink_decoder_free(hsd); 194 | if (fres != HSDR_FINISH_DONE) { return THEFT_TRIAL_FAIL; } 195 | 196 | return THEFT_TRIAL_PASS; 197 | } 198 | 199 | static bool get_time_seed(theft_seed *seed) 200 | { 201 | struct timeval tv; 202 | if (-1 == gettimeofday(&tv, NULL)) { return false; } 203 | *seed = (theft_seed)((tv.tv_sec << 32) | tv.tv_usec); 204 | /* printf("seed is 0x%016llx\n", *seed); */ 205 | return true; 206 | } 207 | 208 | TEST decoder_fuzzing_should_not_detect_stuck_state(void) { 209 | // Get a random number seed based on the time 210 | theft_seed seed; 211 | if (!get_time_seed(&seed)) { FAIL(); } 212 | 213 | /* Pass the max buffer size for this property (4 KB) in a closure */ 214 | test_env env = { .limit = 1 << 12 }; 215 | 216 | theft_seed always_seeds = { 0xe87bb1f61032a061 }; 217 | 218 | struct theft *t = theft_init(0); 219 | struct theft_cfg cfg = { 220 | .name = __func__, 221 | .fun = prop_should_not_get_stuck, 222 | .type_info = { &rbuf_info }, 223 | .seed = seed, 224 | .trials = 100000, 225 | .progress_cb = progress_cb, 226 | .env = &env, 227 | 228 | .always_seeds = &always_seeds, 229 | .always_seed_count = 1, 230 | }; 231 | 232 | theft_run_res res = theft_run(t, &cfg); 233 | theft_free(t); 234 | printf("\n"); 235 | GREATEST_ASSERT_EQm("should_not_get_stuck", THEFT_RUN_PASS, res); 236 | PASS(); 237 | } 238 | 239 | static theft_trial_res prop_encoded_and_decoded_data_should_match(void *input) { 240 | uint8_t e_output[64 * 1024]; 241 | uint8_t d_output[64 * 1024]; 242 | heatshrink_encoder *hse = heatshrink_encoder_alloc(12, 4); 243 | if (hse == NULL) { return THEFT_TRIAL_ERROR; } 244 | heatshrink_decoder *hsd = heatshrink_decoder_alloc(4096, 12, 4); 245 | if (hsd == NULL) { return THEFT_TRIAL_ERROR; } 246 | 247 | rbuf *r = (rbuf *)input; 248 | 249 | size_t e_input_size = 0; 250 | HSE_sink_res esres = heatshrink_encoder_sink(hse, 251 | r->buf, r->size, &e_input_size); 252 | if (esres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; } 253 | if (e_input_size != r->size) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } 254 | 255 | HSE_finish_res efres = heatshrink_encoder_finish(hse); 256 | if (efres != HSER_FINISH_MORE) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } 257 | 258 | size_t e_output_size = 0; 259 | HSE_poll_res epres = heatshrink_encoder_poll(hse, 260 | e_output, sizeof(e_output), &e_output_size); 261 | if (epres != HSER_POLL_EMPTY) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } 262 | 263 | size_t count = 0; 264 | HSD_sink_res sres = heatshrink_decoder_sink(hsd, e_output, e_output_size, &count); 265 | if (sres != HSDR_SINK_OK) { return THEFT_TRIAL_ERROR; } 266 | 267 | size_t d_output_size = 0; 268 | HSD_poll_res pres = heatshrink_decoder_poll(hsd, d_output, 269 | sizeof(d_output), &d_output_size); 270 | if (pres != HSDR_POLL_EMPTY) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } 271 | if (d_output_size != r->size) { 272 | printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; 273 | } 274 | 275 | if (0 != memcmp(d_output, r->buf, d_output_size)) { 276 | return THEFT_TRIAL_FAIL; 277 | } 278 | 279 | HSD_finish_res fres = heatshrink_decoder_finish(hsd); 280 | if (fres != HSDR_FINISH_DONE) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } 281 | 282 | heatshrink_encoder_free(hse); 283 | heatshrink_decoder_free(hsd); 284 | return THEFT_TRIAL_PASS; 285 | } 286 | 287 | 288 | TEST encoded_and_decoded_data_should_match(void) { 289 | test_env env = { .limit = 1 << 11 }; 290 | 291 | theft_seed seed; 292 | if (!get_time_seed(&seed)) { FAIL(); } 293 | 294 | struct theft *t = theft_init(0); 295 | struct theft_cfg cfg = { 296 | .name = __func__, 297 | .fun = prop_encoded_and_decoded_data_should_match, 298 | .type_info = { &rbuf_info }, 299 | .seed = seed, 300 | .trials = 1000000, 301 | .env = &env, 302 | .progress_cb = progress_cb, 303 | }; 304 | 305 | theft_run_res res = theft_run(t, &cfg); 306 | theft_free(t); 307 | printf("\n"); 308 | ASSERT_EQ(THEFT_RUN_PASS, res); 309 | PASS(); 310 | } 311 | 312 | static size_t ceil_nine_eighths(size_t sz) { 313 | return sz + sz/8 + (sz & 0x07 ? 1 : 0); 314 | } 315 | 316 | static theft_trial_res 317 | prop_encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst(void *input) { 318 | uint8_t output[32 * 1024]; 319 | heatshrink_encoder *hse = heatshrink_encoder_alloc(12, 4); 320 | if (hse == NULL) { return THEFT_TRIAL_ERROR; } 321 | 322 | rbuf *r = (rbuf *)input; 323 | 324 | size_t input_size = 0; 325 | HSE_sink_res esres = heatshrink_encoder_sink(hse, 326 | r->buf, r->size, &input_size); 327 | if (esres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; } 328 | /* Assumes data fits in one sink, failure here means buffer must be larger. */ 329 | if (input_size != r->size) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } 330 | 331 | HSE_finish_res efres = heatshrink_encoder_finish(hse); 332 | if (efres != HSER_FINISH_MORE) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } 333 | 334 | size_t output_size = 0; 335 | HSE_poll_res epres = heatshrink_encoder_poll(hse, 336 | output, sizeof(output), &output_size); 337 | if (epres != HSER_POLL_EMPTY) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } 338 | 339 | size_t ceil_9_8s = ceil_nine_eighths(r->size); 340 | if (output_size > ceil_9_8s) { 341 | return THEFT_TRIAL_FAIL; 342 | } 343 | 344 | heatshrink_encoder_free(hse); 345 | return THEFT_TRIAL_PASS; 346 | } 347 | 348 | TEST encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst(void) { 349 | test_env env = { .limit = 1 << 11 }; 350 | 351 | theft_seed seed; 352 | if (!get_time_seed(&seed)) { FAIL(); } 353 | 354 | struct theft *t = theft_init(0); 355 | struct theft_cfg cfg = { 356 | .name = __func__, 357 | .fun = prop_encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst, 358 | .type_info = { &rbuf_info }, 359 | .seed = seed, 360 | .trials = 10000, 361 | .env = &env, 362 | .progress_cb = progress_cb, 363 | }; 364 | 365 | theft_run_res res = theft_run(t, &cfg); 366 | theft_free(t); 367 | printf("\n"); 368 | ASSERT_EQ(THEFT_RUN_PASS, res); 369 | PASS(); 370 | } 371 | 372 | static theft_trial_res 373 | prop_encoder_should_always_make_progress(void *instance) { 374 | uint8_t output[64 * 1024]; 375 | heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 4); 376 | if (hse == NULL) { return THEFT_TRIAL_ERROR; } 377 | 378 | rbuf *r = (rbuf *)instance; 379 | 380 | size_t sunk = 0; 381 | int no_progress = 0; 382 | 383 | while (1) { 384 | if (sunk < r->size) { 385 | size_t input_size = 0; 386 | HSE_sink_res esres = heatshrink_encoder_sink(hse, 387 | &r->buf[sunk], r->size - sunk, &input_size); 388 | if (esres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; } 389 | sunk += input_size; 390 | } else { 391 | HSE_finish_res efres = heatshrink_encoder_finish(hse); 392 | if (efres == HSER_FINISH_DONE) { 393 | break; 394 | } else if (efres != HSER_FINISH_MORE) { 395 | printf("FAIL %d\n", __LINE__); 396 | return THEFT_TRIAL_FAIL; 397 | } 398 | } 399 | 400 | size_t output_size = 0; 401 | HSE_poll_res epres = heatshrink_encoder_poll(hse, 402 | output, sizeof(output), &output_size); 403 | if (epres < 0) { return THEFT_TRIAL_ERROR; } 404 | if (output_size == 0 && sunk == r->size) { 405 | no_progress++; 406 | if (no_progress > 2) { 407 | return THEFT_TRIAL_FAIL; 408 | } 409 | } else { 410 | no_progress = 0; 411 | } 412 | } 413 | 414 | heatshrink_encoder_free(hse); 415 | return THEFT_TRIAL_PASS; 416 | } 417 | 418 | TEST encoder_should_always_make_progress(void) { 419 | test_env env = { .limit = 1 << 15 }; 420 | 421 | theft_seed seed; 422 | if (!get_time_seed(&seed)) { FAIL(); } 423 | 424 | struct theft *t = theft_init(0); 425 | struct theft_cfg cfg = { 426 | .name = __func__, 427 | .fun = prop_encoder_should_always_make_progress, 428 | .type_info = { &rbuf_info }, 429 | .seed = seed, 430 | .trials = 10000, 431 | .env = &env, 432 | .progress_cb = progress_cb, 433 | }; 434 | 435 | theft_run_res res = theft_run(t, &cfg); 436 | theft_free(t); 437 | printf("\n"); 438 | ASSERT_EQ(THEFT_RUN_PASS, res); 439 | PASS(); 440 | } 441 | 442 | static theft_trial_res 443 | prop_decoder_should_always_make_progress(void *instance) { 444 | uint8_t output[64 * 1024]; 445 | heatshrink_decoder *hsd = heatshrink_decoder_alloc(512, 8, 4); 446 | if (hsd == NULL) { return THEFT_TRIAL_ERROR; } 447 | 448 | rbuf *r = (rbuf *)instance; 449 | 450 | size_t sunk = 0; 451 | int no_progress = 0; 452 | 453 | while (1) { 454 | if (sunk < r->size) { 455 | size_t input_size = 0; 456 | HSD_sink_res sres = heatshrink_decoder_sink(hsd, 457 | &r->buf[sunk], r->size - sunk, &input_size); 458 | if (sres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; } 459 | sunk += input_size; 460 | } else { 461 | HSD_finish_res fres = heatshrink_decoder_finish(hsd); 462 | if (fres == HSDR_FINISH_DONE) { 463 | break; 464 | } else if (fres != HSDR_FINISH_MORE) { 465 | printf("FAIL %d\n", __LINE__); 466 | return THEFT_TRIAL_FAIL; 467 | } 468 | } 469 | 470 | size_t output_size = 0; 471 | HSD_poll_res pres = heatshrink_decoder_poll(hsd, 472 | output, sizeof(output), &output_size); 473 | if (pres < 0) { return THEFT_TRIAL_ERROR; } 474 | if (output_size == 0 && sunk == r->size) { 475 | no_progress++; 476 | if (no_progress > 2) { 477 | return THEFT_TRIAL_FAIL; 478 | } 479 | } else { 480 | no_progress = 0; 481 | } 482 | } 483 | 484 | heatshrink_decoder_free(hsd); 485 | return THEFT_TRIAL_PASS; 486 | } 487 | 488 | TEST decoder_should_always_make_progress(void) { 489 | test_env env = { .limit = 1 << 15 }; 490 | 491 | theft_seed seed; 492 | if (!get_time_seed(&seed)) { FAIL(); } 493 | 494 | struct theft *t = theft_init(0); 495 | struct theft_cfg cfg = { 496 | .name = __func__, 497 | .fun = prop_decoder_should_always_make_progress, 498 | .type_info = { &rbuf_info }, 499 | .seed = seed, 500 | .trials = 10000, 501 | .env = &env, 502 | .progress_cb = progress_cb, 503 | }; 504 | 505 | theft_run_res res = theft_run(t, &cfg); 506 | theft_free(t); 507 | printf("\n"); 508 | ASSERT_EQ(THEFT_RUN_PASS, res); 509 | PASS(); 510 | } 511 | 512 | SUITE(properties) { 513 | RUN_TEST(decoder_fuzzing_should_not_detect_stuck_state); 514 | RUN_TEST(encoded_and_decoded_data_should_match); 515 | RUN_TEST(encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst); 516 | RUN_TEST(encoder_should_always_make_progress); 517 | RUN_TEST(decoder_should_always_make_progress); 518 | } 519 | #else 520 | struct because_iso_c_requires_at_least_one_declaration; 521 | #endif 522 | -------------------------------------------------------------------------------- /esphttpd/lib/heatshrink/test_heatshrink_static.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "heatshrink_encoder.h" 5 | #include "heatshrink_decoder.h" 6 | #include "greatest.h" 7 | 8 | #if HEATSHRINK_DYNAMIC_ALLOC 9 | #error HEATSHRINK_DYNAMIC_ALLOC must be false for static allocation test suite. 10 | #endif 11 | 12 | SUITE(integration); 13 | 14 | /* The majority of the tests are in test_heatshrink_dynamic, because that allows 15 | * instantiating encoders/decoders with different settings at run-time. */ 16 | 17 | static heatshrink_encoder hse; 18 | static heatshrink_decoder hsd; 19 | 20 | static void fill_with_pseudorandom_letters(uint8_t *buf, uint16_t size, uint32_t seed) { 21 | uint64_t rn = 9223372036854775783; /* prime under 2^64 */ 22 | for (int i=0; i 1) { 50 | printf("\n^^ COMPRESSING\n"); 51 | dump_buf("input", input, input_size); 52 | } 53 | 54 | uint32_t sunk = 0; 55 | uint32_t polled = 0; 56 | while (sunk < input_size) { 57 | ASSERT(heatshrink_encoder_sink(&hse, &input[sunk], input_size - sunk, &count) >= 0); 58 | sunk += count; 59 | if (log_lvl > 1) printf("^^ sunk %zd\n", count); 60 | if (sunk == input_size) { 61 | ASSERT_EQ(HSER_FINISH_MORE, heatshrink_encoder_finish(&hse)); 62 | } 63 | 64 | HSE_poll_res pres; 65 | do { /* "turn the crank" */ 66 | pres = heatshrink_encoder_poll(&hse, &comp[polled], comp_sz - polled, &count); 67 | ASSERT(pres >= 0); 68 | polled += count; 69 | if (log_lvl > 1) printf("^^ polled %zd\n", count); 70 | } while (pres == HSER_POLL_MORE); 71 | ASSERT_EQ(HSER_POLL_EMPTY, pres); 72 | if (polled >= comp_sz) FAILm("compression should never expand that much"); 73 | if (sunk == input_size) { 74 | ASSERT_EQ(HSER_FINISH_DONE, heatshrink_encoder_finish(&hse)); 75 | } 76 | } 77 | if (log_lvl > 0) printf("in: %u compressed: %u ", input_size, polled); 78 | uint32_t compressed_size = polled; 79 | sunk = 0; 80 | polled = 0; 81 | 82 | if (log_lvl > 1) { 83 | printf("\n^^ DECOMPRESSING\n"); 84 | dump_buf("comp", comp, compressed_size); 85 | } 86 | while (sunk < compressed_size) { 87 | ASSERT(heatshrink_decoder_sink(&hsd, &comp[sunk], compressed_size - sunk, &count) >= 0); 88 | sunk += count; 89 | if (log_lvl > 1) printf("^^ sunk %zd\n", count); 90 | if (sunk == compressed_size) { 91 | ASSERT_EQ(HSDR_FINISH_MORE, heatshrink_decoder_finish(&hsd)); 92 | } 93 | 94 | HSD_poll_res pres; 95 | do { 96 | pres = heatshrink_decoder_poll(&hsd, &decomp[polled], 97 | decomp_sz - polled, &count); 98 | ASSERT(pres >= 0); 99 | polled += count; 100 | if (log_lvl > 1) printf("^^ polled %zd\n", count); 101 | } while (pres == HSDR_POLL_MORE); 102 | ASSERT_EQ(HSDR_POLL_EMPTY, pres); 103 | if (sunk == compressed_size) { 104 | HSD_finish_res fres = heatshrink_decoder_finish(&hsd); 105 | ASSERT_EQ(HSDR_FINISH_DONE, fres); 106 | } 107 | 108 | if (polled > input_size) { 109 | FAILm("Decompressed data is larger than original input"); 110 | } 111 | } 112 | if (log_lvl > 0) printf("decompressed: %u\n", polled); 113 | if (polled != input_size) { 114 | FAILm("Decompressed length does not match original input length"); 115 | } 116 | 117 | if (log_lvl > 1) dump_buf("decomp", decomp, polled); 118 | for (size_t i=0; i out[%zd] == 0x%02x ('%c')\n", 124 | j, input[j], isprint(input[j]) ? input[j] : '.', 125 | j, decomp[j], isprint(decomp[j]) ? decomp[j] : '.'); 126 | } 127 | } 128 | } 129 | ASSERT_EQ(input[i], decomp[i]); 130 | } 131 | free(comp); 132 | free(decomp); 133 | PASS(); 134 | } 135 | 136 | TEST pseudorandom_data_should_match(uint32_t size, uint32_t seed) { 137 | uint8_t input[size]; 138 | fill_with_pseudorandom_letters(input, size, seed); 139 | return compress_and_expand_and_check(input, size, 0); 140 | } 141 | 142 | SUITE(integration) { 143 | #if __STDC_VERSION__ >= 19901L 144 | for (uint32_t size=1; size < 64*1024; size <<= 1) { 145 | if (GREATEST_IS_VERBOSE()) printf(" -- size %u\n", size); 146 | for (uint32_t seed=1; seed<=100; seed++) { 147 | if (GREATEST_IS_VERBOSE()) printf(" -- seed %u\n", seed); 148 | RUN_TESTp(pseudorandom_data_should_match, size, seed); 149 | } 150 | } 151 | #endif 152 | } 153 | 154 | /* Add all the definitions that need to be in the test runner's main file. */ 155 | GREATEST_MAIN_DEFS(); 156 | 157 | int main(int argc, char **argv) { 158 | GREATEST_MAIN_BEGIN(); /* command-line arguments, initialization. */ 159 | printf("INPUT_BUFFER_SIZE: %u\n", HEATSHRINK_STATIC_INPUT_BUFFER_SIZE); 160 | printf("WINDOW_BITS: %u\n", HEATSHRINK_STATIC_WINDOW_BITS); 161 | printf("LOOKAHEAD_BITS: %u\n", HEATSHRINK_STATIC_LOOKAHEAD_BITS); 162 | 163 | printf("sizeof(heatshrink_encoder): %zd\n", sizeof(heatshrink_encoder)); 164 | printf("sizeof(heatshrink_decoder): %zd\n", sizeof(heatshrink_decoder)); 165 | RUN_SUITE(integration); 166 | GREATEST_MAIN_END(); /* display results */ 167 | } 168 | -------------------------------------------------------------------------------- /esphttpd/mkespfsimage/Makefile: -------------------------------------------------------------------------------- 1 | 2 | CFLAGS=-I../lib/heatshrink -std=gnu99 3 | OBJS=main.o heatshrink_encoder.o 4 | TARGET=mkespfsimage 5 | 6 | $(TARGET): $(OBJS) 7 | $(CC) -o $@ $^ 8 | 9 | clean: 10 | rm -f $(TARGET) $(OBJS) -------------------------------------------------------------------------------- /esphttpd/mkespfsimage/espfsformat.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPROFSFORMAT_H 2 | #define ESPROFSFORMAT_H 3 | 4 | /* 5 | Stupid cpio-like tool to make read-only 'filesystems' that live on the flash SPI chip of the module. 6 | Can (will) use lzf compression (when I come around to it) to make shit quicker. Aligns names, files, 7 | headers on 4-byte boundaries so the SPI abstraction hardware in the ESP8266 doesn't crap on itself 8 | when trying to do a <4byte or unaligned read. 9 | */ 10 | 11 | /* 12 | The idea 'borrows' from cpio: it's basically a concatenation of {header, filename, file} data. 13 | Header, filename and file data is 32-bit aligned. The last file is indicated by data-less header 14 | with the FLAG_LASTFILE flag set. 15 | */ 16 | 17 | 18 | #define FLAG_LASTFILE (1<<0) 19 | #define COMPRESS_NONE 0 20 | #define COMPRESS_HEATSHRINK 1 21 | 22 | typedef struct { 23 | int32_t magic; 24 | int8_t flags; 25 | int8_t compression; 26 | int16_t nameLen; 27 | int32_t fileLenComp; 28 | int32_t fileLenDecomp; 29 | } __attribute__((packed)) EspFsHeader; 30 | 31 | #endif -------------------------------------------------------------------------------- /esphttpd/mkespfsimage/heatshrink_encoder.c: -------------------------------------------------------------------------------- 1 | //Stupid wraparound include to make sure object file doesn't end up in heatshrink dir 2 | 3 | #include "../lib/heatshrink/heatshrink_encoder.c" 4 | -------------------------------------------------------------------------------- /esphttpd/mkespfsimage/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "espfsformat.h" 12 | 13 | //Heatshrink 14 | #include "heatshrink_common.h" 15 | #include "heatshrink_config.h" 16 | #include "heatshrink_encoder.h" 17 | 18 | 19 | //Routines to convert host format to the endianness used in the xtensa 20 | short htoxs(short in) { 21 | char r[2]; 22 | r[0]=in; 23 | r[1]=in>>8; 24 | return *((short *)r); 25 | } 26 | 27 | int htoxl(int in) { 28 | unsigned char r[4]; 29 | r[0]=in; 30 | r[1]=in>>8; 31 | r[2]=in>>16; 32 | r[3]=in>>24; 33 | return *((int *)r); 34 | } 35 | 36 | size_t compressHeatshrink(char *in, int insize, char *out, int outsize, int level) { 37 | char *inp=in; 38 | char *outp=out; 39 | size_t len; 40 | int ws[]={5, 6, 8, 11, 13}; 41 | int ls[]={3, 3, 4, 4, 4}; 42 | HSE_poll_res pres; 43 | HSE_sink_res sres; 44 | size_t r; 45 | if (level==-1) level=8; 46 | level=(level-1)/2; //level is now 0, 1, 2, 3, 4 47 | heatshrink_encoder *enc=heatshrink_encoder_alloc(ws[level], ls[level]); 48 | if (enc==NULL) { 49 | perror("allocating mem for heatshrink"); 50 | exit(1); 51 | } 52 | //Save encoder parms as first byte 53 | *outp=(ws[level]<<4)|ls[level]; 54 | outp++; outsize--; 55 | 56 | r=1; 57 | do { 58 | if (insize>0) { 59 | sres=heatshrink_encoder_sink(enc, inp, insize, &len); 60 | if (sres!=HSER_SINK_OK) break; 61 | inp+=len; insize-=len; 62 | if (insize==0) heatshrink_encoder_finish(enc); 63 | } 64 | do { 65 | pres=heatshrink_encoder_poll(enc, outp, outsize, &len); 66 | if (pres!=HSER_POLL_MORE && pres!=HSER_POLL_EMPTY) break; 67 | outp+=len; outsize-=len; 68 | r+=len; 69 | } while (pres==HSER_POLL_MORE); 70 | } while (insize!=0); 71 | 72 | if (insize!=0) { 73 | fprintf(stderr, "Heatshrink: Bug? insize is still %d. sres=%d pres=%d\n", insize, sres, pres); 74 | exit(1); 75 | } 76 | 77 | heatshrink_encoder_free(enc); 78 | return r; 79 | } 80 | 81 | int handleFile(int f, char *name, int compression, int level) { 82 | char *fdat, *cdat; 83 | off_t size, csize; 84 | EspFsHeader h; 85 | int nameLen; 86 | size=lseek(f, 0, SEEK_END); 87 | fdat=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0); 88 | if (fdat==MAP_FAILED) { 89 | perror("mmap"); 90 | return 0; 91 | } 92 | 93 | if (compression==COMPRESS_NONE) { 94 | csize=size; 95 | cdat=fdat; 96 | } else if (compression==COMPRESS_HEATSHRINK) { 97 | cdat=malloc(size*2); 98 | csize=compressHeatshrink(fdat, size, cdat, size*2, level); 99 | } else { 100 | fprintf(stderr, "Unknown compression - %d\n", compression); 101 | exit(1); 102 | } 103 | 104 | if (csize>size) { 105 | //Compressing enbiggened this file. Revert to uncompressed store. 106 | compression=COMPRESS_NONE; 107 | csize=size; 108 | cdat=fdat; 109 | } 110 | 111 | //Fill header data 112 | h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24); 113 | h.flags=0; 114 | h.compression=compression; 115 | nameLen=strlen(name)+1; 116 | if (nameLen&3) nameLen+=4-(nameLen&3); //Round to next 32bit boundary 117 | h.nameLen=htoxs(nameLen); 118 | h.fileLenComp=htoxl(csize); 119 | h.fileLenDecomp=htoxl(size); 120 | 121 | write(1, &h, sizeof(EspFsHeader)); 122 | write(1, name, nameLen); //ToDo: this can eat up a few bytes after the buffer. 123 | write(1, cdat, csize); 124 | //Pad out to 32bit boundary 125 | while (csize&3) { 126 | write(1, "\000", 1); 127 | csize++; 128 | } 129 | munmap(fdat, size); 130 | return (csize*100)/size; 131 | } 132 | 133 | //Write final dummy header with FLAG_LASTFILE set. 134 | void finishArchive() { 135 | EspFsHeader h; 136 | h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24); 137 | h.flags=FLAG_LASTFILE; 138 | h.compression=COMPRESS_NONE; 139 | h.nameLen=htoxs(0); 140 | h.fileLenComp=htoxl(0); 141 | h.fileLenDecomp=htoxl(0); 142 | write(1, &h, sizeof(EspFsHeader)); 143 | } 144 | 145 | 146 | int main(int argc, char **argv) { 147 | int f, x; 148 | char fileName[1024]; 149 | char *realName; 150 | struct stat statBuf; 151 | int serr; 152 | int rate; 153 | int err=0; 154 | int compType=1; //default compression type - heatshrink 155 | int compLvl=-1; 156 | 157 | for (x=1; x=x-2) { 159 | compType=atoi(argv[x=1]); 160 | x++; 161 | } else if (strcmp(argv[x], "-l")==0 && argc>=x-2) { 162 | compLvl=atoi(argv[x=1]); 163 | if (compLvl<1 || compLvl>9) err=1; 164 | x++; 165 | } else { 166 | err=1; 167 | } 168 | } 169 | 170 | if (err) { 171 | fprintf(stderr, "%s - Program to create espfs images\n", argv[0]); 172 | fprintf(stderr, "Usage: \nfind | %s [-c compressor] [-l compression_level] > out.espfs\n", argv[0]); 173 | fprintf(stderr, "Compressors:\n0 - None\n1 - Heatshrink(defautl\n"); 174 | fprintf(stderr, "Compression level: 1 is worst but low RAM usage, higher is better compression \nbut uses more ram on decompression. -1 = compressors default.\n"); 175 | exit(0); 176 | } 177 | 178 | while(fgets(fileName, sizeof(fileName), stdin)) { 179 | //Kill off '\n' at the end 180 | fileName[strlen(fileName)-1]=0; 181 | //Only include files 182 | serr=stat(fileName, &statBuf); 183 | if ((serr==0) && S_ISREG(statBuf.st_mode)) { 184 | //Strip off './' or '/' madness. 185 | realName=fileName; 186 | if (fileName[0]=='.') realName++; 187 | if (realName[0]=='/') realName++; 188 | f=open(fileName, O_RDONLY); 189 | if (f>0) { 190 | rate=handleFile(f, realName, compType, compLvl); 191 | fprintf(stderr, "%s (%d%%)\n", realName, rate); 192 | close(f); 193 | } else { 194 | perror(fileName); 195 | } 196 | } else { 197 | if (serr!=0) { 198 | perror(fileName); 199 | } 200 | } 201 | } 202 | finishArchive(); 203 | return 0; 204 | } 205 | 206 | -------------------------------------------------------------------------------- /esphttpd/user/auth.c: -------------------------------------------------------------------------------- 1 | /* 2 | HTTP auth implementation. Only does basic authentication for now. 3 | */ 4 | 5 | /* 6 | * ---------------------------------------------------------------------------- 7 | * "THE BEER-WARE LICENSE" (Revision 42): 8 | * Jeroen Domburg wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | 15 | #include 16 | #include 17 | #include "user_interface.h" 18 | #include "mem.h" 19 | #include "httpd.h" 20 | #include "cgi.h" 21 | #include "auth.h" 22 | #include "io.h" 23 | #include "base64.h" 24 | #include 25 | #include "espmissingincludes.h" 26 | 27 | int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData) { 28 | const char *forbidden="401 Forbidden."; 29 | int no=0; 30 | int r; 31 | char hdr[(AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2)*10]; 32 | char userpass[AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2]; 33 | char user[AUTH_MAX_USER_LEN]; 34 | char pass[AUTH_MAX_PASS_LEN]; 35 | if (connData->conn==NULL) { 36 | //Connection aborted. Clean up. 37 | return HTTPD_CGI_DONE; 38 | } 39 | 40 | r=httpdGetHeader(connData, "Authorization", hdr, sizeof(hdr)); 41 | if (r && strncmp(hdr, "Basic", 5)==0) { 42 | r=base64_decode(strlen(hdr)-6, hdr+6, sizeof(userpass), (unsigned char *)userpass); 43 | if (r<0) r=0; //just clean out string on decode error 44 | userpass[r]=0; //zero-terminate user:pass string 45 | // os_printf("Auth: %s\n", userpass); 46 | while (((AuthGetUserPw)(connData->cgiArg))(connData, no, 47 | user, AUTH_MAX_USER_LEN, pass, AUTH_MAX_PASS_LEN)) { 48 | //Check user/pass against auth header 49 | if (strlen(userpass)==strlen(user)+strlen(pass)+1 && 50 | os_strncmp(userpass, user, strlen(user))==0 && 51 | userpass[strlen(user)]==':' && 52 | os_strcmp(userpass+strlen(user)+1, pass)==0) { 53 | //Authenticated. Yay! 54 | return HTTPD_CGI_AUTHENTICATED; 55 | } 56 | no++; //Not authenticated with this user/pass. Check next user/pass combo. 57 | } 58 | } 59 | 60 | //Not authenticated. Go bug user with login screen. 61 | httpdStartResponse(connData, 401); 62 | httpdHeader(connData, "Content-Type", "text/plain"); 63 | httpdHeader(connData, "WWW-Authenticate", "Basic realm=\""HTTP_AUTH_REALM"\""); 64 | httpdEndHeaders(connData); 65 | httpdSend(connData, forbidden, -1); 66 | //Okay, all done. 67 | return HTTPD_CGI_DONE; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /esphttpd/user/auth.h: -------------------------------------------------------------------------------- 1 | #ifndef AUTH_H 2 | #define AUTH_H 3 | 4 | #include "httpdconfig.h" 5 | 6 | #ifndef HTTP_AUTH_REALM 7 | #define HTTP_AUTH_REALM "Protected" 8 | #endif 9 | 10 | #define HTTPD_AUTH_SINGLE 0 11 | #define HTTPD_AUTH_CALLBACK 1 12 | 13 | #define AUTH_MAX_USER_LEN 32 14 | #define AUTH_MAX_PASS_LEN 32 15 | 16 | //Parameter given to authWhatever functions. This callback returns the usernames/passwords the device 17 | //has. 18 | typedef int (* AuthGetUserPw)(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen); 19 | 20 | int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData); 21 | 22 | #endif -------------------------------------------------------------------------------- /esphttpd/user/base64.c: -------------------------------------------------------------------------------- 1 | /* base64.c : base-64 / MIME encode/decode */ 2 | /* PUBLIC DOMAIN - Jon Mayo - November 13, 2003 */ 3 | #include "c_types.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "base64.h" 9 | 10 | static const uint8_t base64dec_tab[256]= { 11 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 12 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 13 | 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, 14 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, 15 | 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, 17 | 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 18 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, 19 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 20 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 21 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 22 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 23 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 24 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 25 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 26 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 27 | }; 28 | 29 | #if 0 30 | static int ICACHE_FLASH_ATTR base64decode(const char in[4], char out[3]) { 31 | uint8_t v[4]; 32 | 33 | v[0]=base64dec_tab[(unsigned)in[0]]; 34 | v[1]=base64dec_tab[(unsigned)in[1]]; 35 | v[2]=base64dec_tab[(unsigned)in[2]]; 36 | v[3]=base64dec_tab[(unsigned)in[3]]; 37 | 38 | out[0]=(v[0]<<2)|(v[1]>>4); 39 | out[1]=(v[1]<<4)|(v[2]>>2); 40 | out[2]=(v[2]<<6)|(v[3]); 41 | return (v[0]|v[1]|v[2]|v[3])!=255 ? in[3]=='=' ? in[2]=='=' ? 1 : 2 : 3 : 0; 42 | } 43 | #endif 44 | 45 | /* decode a base64 string in one shot */ 46 | int ICACHE_FLASH_ATTR base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out) { 47 | unsigned int ii, io; 48 | uint32_t v; 49 | unsigned int rem; 50 | 51 | for(io=0,ii=0,v=0,rem=0;ii=8) { 60 | rem-=8; 61 | if(io>=out_len) return -1; /* truncation is failure */ 62 | out[io++]=(v>>rem)&255; 63 | } 64 | } 65 | if(rem>=8) { 66 | rem-=8; 67 | if(io>=out_len) return -1; /* truncation is failure */ 68 | out[io++]=(v>>rem)&255; 69 | } 70 | return io; 71 | } 72 | 73 | //Only need decode functions for now. 74 | #if 0 75 | 76 | static const uint8_t base64enc_tab[64]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 77 | 78 | void base64encode(const unsigned char in[3], unsigned char out[4], int count) { 79 | out[0]=base64enc_tab[(in[0]>>2)]; 80 | out[1]=base64enc_tab[((in[0]&3)<<4)|(in[1]>>4)]; 81 | out[2]=count<2 ? '=' : base64enc_tab[((in[1]&15)<<2)|(in[2]>>6)]; 82 | out[3]=count<3 ? '=' : base64enc_tab[(in[2]&63)]; 83 | } 84 | 85 | 86 | int base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out) { 87 | unsigned ii, io; 88 | uint_least32_t v; 89 | unsigned rem; 90 | 91 | for(io=0,ii=0,v=0,rem=0;ii=6) { 97 | rem-=6; 98 | if(io>=out_len) return -1; /* truncation is failure */ 99 | out[io++]=base64enc_tab[(v>>rem)&63]; 100 | } 101 | } 102 | if(rem) { 103 | v<<=(6-rem); 104 | if(io>=out_len) return -1; /* truncation is failure */ 105 | out[io++]=base64enc_tab[v&63]; 106 | } 107 | while(io&3) { 108 | if(io>=out_len) return -1; /* truncation is failure */ 109 | out[io++]='='; 110 | } 111 | if(io>=out_len) return -1; /* no room for null terminator */ 112 | out[io]=0; 113 | return io; 114 | } 115 | 116 | #endif -------------------------------------------------------------------------------- /esphttpd/user/base64.h: -------------------------------------------------------------------------------- 1 | int base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out); 2 | -------------------------------------------------------------------------------- /esphttpd/user/cgi.c: -------------------------------------------------------------------------------- 1 | /* 2 | Some random cgi routines. Used in the LED example and the page that returns the entire 3 | flash as a binary. Also handles the hit counter on the main page. 4 | */ 5 | 6 | /* 7 | * ---------------------------------------------------------------------------- 8 | * "THE BEER-WARE LICENSE" (Revision 42): 9 | * Jeroen Domburg wrote this file. As long as you retain 10 | * this notice you can do whatever you want with this stuff. If we meet some day, 11 | * and you think this stuff is worth it, you can buy me a beer in return. 12 | * ---------------------------------------------------------------------------- 13 | */ 14 | 15 | 16 | #include 17 | #include 18 | #include "user_interface.h" 19 | #include "mem.h" 20 | #include "httpd.h" 21 | #include "cgi.h" 22 | #include "io.h" 23 | #include 24 | #include "espmissingincludes.h" 25 | 26 | 27 | //cause I can't be bothered to write an ioGetLed() 28 | static char currLedState=0; 29 | 30 | //Cgi that turns the LED on or off according to the 'led' param in the POST data 31 | int ICACHE_FLASH_ATTR cgiLed(HttpdConnData *connData) { 32 | int len; 33 | char buff[1024]; 34 | 35 | if (connData->conn==NULL) { 36 | //Connection aborted. Clean up. 37 | return HTTPD_CGI_DONE; 38 | } 39 | 40 | len=httpdFindArg(connData->postBuff, "led", buff, sizeof(buff)); 41 | if (len!=0) { 42 | currLedState=atoi(buff); 43 | ioLed(currLedState); 44 | } 45 | 46 | httpdRedirect(connData, "led.tpl"); 47 | return HTTPD_CGI_DONE; 48 | } 49 | 50 | 51 | 52 | //Template code for the led page. 53 | void ICACHE_FLASH_ATTR tplLed(HttpdConnData *connData, char *token, void **arg) { 54 | char buff[128]; 55 | if (token==NULL) return; 56 | 57 | os_strcpy(buff, "Unknown"); 58 | if (os_strcmp(token, "ledstate")==0) { 59 | if (currLedState) { 60 | os_strcpy(buff, "on"); 61 | } else { 62 | os_strcpy(buff, "off"); 63 | } 64 | } 65 | httpdSend(connData, buff, -1); 66 | } 67 | 68 | static long hitCounter=0; 69 | 70 | //Template code for the counter on the index page. 71 | void ICACHE_FLASH_ATTR tplCounter(HttpdConnData *connData, char *token, void **arg) { 72 | char buff[128]; 73 | if (token==NULL) return; 74 | 75 | if (os_strcmp(token, "counter")==0) { 76 | hitCounter++; 77 | os_sprintf(buff, "%ld", hitCounter); 78 | } 79 | httpdSend(connData, buff, -1); 80 | } 81 | 82 | 83 | //Cgi that reads the SPI flash. Assumes 512KByte flash. 84 | int ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) { 85 | int *pos=(int *)&connData->cgiData; 86 | if (connData->conn==NULL) { 87 | //Connection aborted. Clean up. 88 | return HTTPD_CGI_DONE; 89 | } 90 | 91 | if (*pos==0) { 92 | os_printf("Start flash download.\n"); 93 | httpdStartResponse(connData, 200); 94 | httpdHeader(connData, "Content-Type", "application/bin"); 95 | httpdEndHeaders(connData); 96 | *pos=0x40200000; 97 | return HTTPD_CGI_MORE; 98 | } 99 | //Send 1K of flash per call. We will get called again if we haven't sent 512K yet. 100 | espconn_sent(connData->conn, (uint8 *)(*pos), 1024); 101 | *pos+=1024; 102 | if (*pos>=0x40200000+(512*1024)) return HTTPD_CGI_DONE; else return HTTPD_CGI_MORE; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /esphttpd/user/cgi.h: -------------------------------------------------------------------------------- 1 | #ifndef CGI_H 2 | #define CGI_H 3 | 4 | #include "httpd.h" 5 | 6 | int cgiLed(HttpdConnData *connData); 7 | void tplLed(HttpdConnData *connData, char *token, void **arg); 8 | int cgiReadFlash(HttpdConnData *connData); 9 | void tplCounter(HttpdConnData *connData, char *token, void **arg); 10 | 11 | #endif -------------------------------------------------------------------------------- /esphttpd/user/cgiwifi.c: -------------------------------------------------------------------------------- 1 | /* 2 | Cgi/template routines for the /wifi url. 3 | */ 4 | 5 | /* 6 | * ---------------------------------------------------------------------------- 7 | * "THE BEER-WARE LICENSE" (Revision 42): 8 | * Jeroen Domburg wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | 15 | #include 16 | #include 17 | #include "user_interface.h" 18 | #include "mem.h" 19 | #include "httpd.h" 20 | #include "cgi.h" 21 | #include "io.h" 22 | #include "espmissingincludes.h" 23 | 24 | //Enable this to disallow any changes in AP settings 25 | //#define DEMO_MODE 26 | 27 | //WiFi access point data 28 | typedef struct { 29 | char ssid[32]; 30 | char rssi; 31 | char enc; 32 | } ApData; 33 | 34 | //Scan result 35 | typedef struct { 36 | char scanInProgress; //if 1, don't access the underlying stuff from the webpage. 37 | ApData **apData; 38 | int noAps; 39 | } ScanResultData; 40 | 41 | //Static scan status storage. 42 | ScanResultData cgiWifiAps; 43 | 44 | //Callback the code calls when a wlan ap scan is done. Basically stores the result in 45 | //the cgiWifiAps struct. 46 | void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) { 47 | int n; 48 | struct bss_info *bss_link = (struct bss_info *)arg; 49 | os_printf("wifiScanDoneCb %d\n", status); 50 | if (status!=OK) { 51 | cgiWifiAps.scanInProgress=0; 52 | return; 53 | } 54 | 55 | //Clear prev ap data if needed. 56 | if (cgiWifiAps.apData!=NULL) { 57 | for (n=0; nnext.stqe_next; 65 | n++; 66 | } 67 | //Allocate memory for access point data 68 | cgiWifiAps.apData=(ApData **)os_malloc(sizeof(ApData *)*n); 69 | cgiWifiAps.noAps=n; 70 | os_printf("Scan done: found %d APs\n", n); 71 | 72 | //Copy access point data to the static struct 73 | n=0; 74 | bss_link = (struct bss_info *)arg; 75 | while (bss_link != NULL) { 76 | if (n>=cgiWifiAps.noAps) { 77 | //This means the bss_link changed under our nose. Shouldn't happen! 78 | //Break because otherwise we will write in unallocated memory. 79 | os_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps); 80 | break; 81 | } 82 | //Save the ap data. 83 | cgiWifiAps.apData[n]=(ApData *)os_malloc(sizeof(ApData)); 84 | cgiWifiAps.apData[n]->rssi=bss_link->rssi; 85 | cgiWifiAps.apData[n]->enc=bss_link->authmode; 86 | strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32); 87 | 88 | bss_link = bss_link->next.stqe_next; 89 | n++; 90 | } 91 | //We're done. 92 | cgiWifiAps.scanInProgress=0; 93 | } 94 | 95 | 96 | //Routine to start a WiFi access point scan. 97 | static void ICACHE_FLASH_ATTR wifiStartScan() { 98 | // int x; 99 | if (cgiWifiAps.scanInProgress) return; 100 | cgiWifiAps.scanInProgress=1; 101 | wifi_station_scan(NULL, wifiScanDoneCb); 102 | } 103 | 104 | //This CGI is called from the bit of AJAX-code in wifi.tpl. It will initiate a 105 | //scan for access points and if available will return the result of an earlier scan. 106 | //The result is embedded in a bit of JSON parsed by the javascript in wifi.tpl. 107 | int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { 108 | int len; 109 | int i; 110 | char buff[1024]; 111 | httpdStartResponse(connData, 200); 112 | httpdHeader(connData, "Content-Type", "text/json"); 113 | httpdEndHeaders(connData); 114 | 115 | if (cgiWifiAps.scanInProgress==1) { 116 | //We're still scanning. Tell Javascript code that. 117 | len=os_sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n"); 118 | httpdSend(connData, buff, len); 119 | } else { 120 | //We have a scan result. Pass it on. 121 | len=os_sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"0\", \n\"APs\": [\n"); 122 | httpdSend(connData, buff, len); 123 | if (cgiWifiAps.apData==NULL) cgiWifiAps.noAps=0; 124 | for (i=0; issid, cgiWifiAps.apData[i]->rssi, 128 | cgiWifiAps.apData[i]->enc, (i==cgiWifiAps.noAps-1)?"":","); 129 | httpdSend(connData, buff, len); 130 | } 131 | len=os_sprintf(buff, "]\n}\n}\n"); 132 | httpdSend(connData, buff, len); 133 | //Also start a new scan. 134 | wifiStartScan(); 135 | } 136 | return HTTPD_CGI_DONE; 137 | } 138 | 139 | //Temp store for new ap info. 140 | static struct station_config stconf; 141 | 142 | 143 | //This routine is ran some time after a connection attempt to an access point. If 144 | //the connect succeeds, this gets the module in STA-only mode. 145 | static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { 146 | int x=wifi_station_get_connect_status(); 147 | if (x==STATION_GOT_IP) { 148 | //Go to STA mode. This needs a reset, so do that. 149 | os_printf("Got IP. Going into STA mode..\n"); 150 | wifi_set_opmode(1); 151 | system_restart(); 152 | } else { 153 | os_printf("Connect fail. Not going into STA-only mode.\n"); 154 | //Maybe also pass this through on the webpage? 155 | } 156 | } 157 | 158 | //Actually connect to a station. This routine is timed because I had problems 159 | //with immediate connections earlier. It probably was something else that caused it, 160 | //but I can't be arsed to put the code back :P 161 | static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { 162 | int x; 163 | static ETSTimer resetTimer; 164 | os_printf("Try to connect to AP....\n"); 165 | wifi_station_disconnect(); 166 | wifi_station_set_config(&stconf); 167 | wifi_station_connect(); 168 | x=wifi_get_opmode(); 169 | if (x!=1) { 170 | //Schedule disconnect/connect 171 | os_timer_disarm(&resetTimer); 172 | os_timer_setfn(&resetTimer, resetTimerCb, NULL); 173 | os_timer_arm(&resetTimer, 4000, 0); 174 | } 175 | } 176 | 177 | 178 | //This cgi uses the routines above to connect to a specific access point with the 179 | //given ESSID using the given password. 180 | int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { 181 | char essid[128]; 182 | char passwd[128]; 183 | static ETSTimer reassTimer; 184 | 185 | if (connData->conn==NULL) { 186 | //Connection aborted. Clean up. 187 | return HTTPD_CGI_DONE; 188 | } 189 | 190 | httpdFindArg(connData->postBuff, "essid", essid, sizeof(essid)); 191 | httpdFindArg(connData->postBuff, "passwd", passwd, sizeof(passwd)); 192 | 193 | os_strncpy((char*)stconf.ssid, essid, 32); 194 | os_strncpy((char*)stconf.password, passwd, 64); 195 | os_printf("Try to connect to AP %s pw %s\n", essid, passwd); 196 | 197 | //Schedule disconnect/connect 198 | os_timer_disarm(&reassTimer); 199 | os_timer_setfn(&reassTimer, reassTimerCb, NULL); 200 | //Set to 0 if you want to disable the actual reconnecting bit 201 | #ifdef DEMO_MODE 202 | httpdRedirect(connData, "/wifi"); 203 | #else 204 | os_timer_arm(&reassTimer, 1000, 0); 205 | httpdRedirect(connData, "connecting.html"); 206 | #endif 207 | return HTTPD_CGI_DONE; 208 | } 209 | 210 | //This cgi uses the routines above to connect to a specific access point with the 211 | //given ESSID using the given password. 212 | int ICACHE_FLASH_ATTR cgiWifiSetMode(HttpdConnData *connData) { 213 | int len; 214 | char buff[1024]; 215 | 216 | if (connData->conn==NULL) { 217 | //Connection aborted. Clean up. 218 | return HTTPD_CGI_DONE; 219 | } 220 | 221 | len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); 222 | if (len!=0) { 223 | os_printf("cgiWifiSetMode: %s\n", buff); 224 | #ifndef DEMO_MODE 225 | wifi_set_opmode(atoi(buff)); 226 | system_restart(); 227 | #endif 228 | } 229 | httpdRedirect(connData, "/wifi"); 230 | return HTTPD_CGI_DONE; 231 | } 232 | 233 | //Template code for the WLAN page. 234 | void ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg) { 235 | char buff[1024]; 236 | int x; 237 | static struct station_config stconf; 238 | if (token==NULL) return; 239 | wifi_station_get_config(&stconf); 240 | 241 | os_strcpy(buff, "Unknown"); 242 | if (os_strcmp(token, "WiFiMode")==0) { 243 | x=wifi_get_opmode(); 244 | if (x==1) os_strcpy(buff, "Client"); 245 | if (x==2) os_strcpy(buff, "SoftAP"); 246 | if (x==3) os_strcpy(buff, "STA+AP"); 247 | } else if (os_strcmp(token, "currSsid")==0) { 248 | os_strcpy(buff, (char*)stconf.ssid); 249 | } else if (os_strcmp(token, "WiFiPasswd")==0) { 250 | os_strcpy(buff, (char*)stconf.password); 251 | } else if (os_strcmp(token, "WiFiapwarn")==0) { 252 | x=wifi_get_opmode(); 253 | if (x==2) { 254 | os_strcpy(buff, "Can't scan in this mode. Click here to go to STA+AP mode."); 255 | } else { 256 | os_strcpy(buff, "Click here to go to standalone AP mode."); 257 | } 258 | } 259 | httpdSend(connData, buff, -1); 260 | } 261 | 262 | 263 | -------------------------------------------------------------------------------- /esphttpd/user/cgiwifi.h: -------------------------------------------------------------------------------- 1 | #ifndef CGIWIFI_H 2 | #define CGIWIFI_H 3 | 4 | #include "httpd.h" 5 | 6 | int cgiWiFiScan(HttpdConnData *connData); 7 | void tplWlan(HttpdConnData *connData, char *token, void **arg); 8 | int cgiWiFi(HttpdConnData *connData); 9 | int cgiWiFiConnect(HttpdConnData *connData); 10 | int cgiWifiSetMode(HttpdConnData *connData); 11 | 12 | #endif -------------------------------------------------------------------------------- /esphttpd/user/espfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | This is a simple read-only implementation of a file system. It uses a block of data coming from the 3 | mkespfsimg tool, and can use that block to do abstracted operations on the files that are in there. 4 | It's written for use with httpd, but doesn't need to be used as such. 5 | */ 6 | 7 | /* 8 | * ---------------------------------------------------------------------------- 9 | * "THE BEER-WARE LICENSE" (Revision 42): 10 | * Jeroen Domburg wrote this file. As long as you retain 11 | * this notice you can do whatever you want with this stuff. If we meet some day, 12 | * and you think this stuff is worth it, you can buy me a beer in return. 13 | * ---------------------------------------------------------------------------- 14 | */ 15 | 16 | 17 | //These routines can also be tested by comping them in with the espfstest tool. This 18 | //simplifies debugging, but needs some slightly different headers. The #ifdef takes 19 | //care of that. 20 | 21 | #ifdef __ets__ 22 | //esp build 23 | #include "c_types.h" 24 | #include "user_interface.h" 25 | #include "espconn.h" 26 | #include "mem.h" 27 | #include "osapi.h" 28 | #include "espmissingincludes.h" 29 | #else 30 | //Test build 31 | #include 32 | #include 33 | #include 34 | #include 35 | #define os_malloc malloc 36 | #define os_free free 37 | #define os_memcpy memcpy 38 | #define os_strncmp strncmp 39 | #define os_strcmp strcmp 40 | #define os_strcpy strcpy 41 | #define os_printf printf 42 | #define ICACHE_FLASH_ATTR 43 | extern char* espFsData; 44 | #endif 45 | 46 | #include "../mkespfsimage/espfsformat.h" 47 | #include "espfs.h" 48 | #include "httpdconfig.h" 49 | #ifdef EFS_HEATSHRINK 50 | #include "heatshrink_decoder.h" 51 | #endif 52 | 53 | 54 | 55 | 56 | struct EspFsFile { 57 | EspFsHeader *header; 58 | char decompressor; 59 | int32_t posDecomp; 60 | char *posStart; 61 | char *posComp; 62 | void *decompData; 63 | }; 64 | 65 | /* 66 | Available locations, at least in my flash, with boundaries partially guessed. This 67 | is using 0.9.1/0.9.2 SDK on a not-too-new module. 68 | 0x00000 (0x10000): Code/data (RAM data?) 69 | 0x10000 (0x02000): Gets erased by something? 70 | 0x12000 (0x2E000): Free (filled with zeroes) (parts used by ESPCloud and maybe SSL) 71 | 0x40000 (0x20000): Code/data (ROM data?) 72 | 0x60000 (0x1C000): Free 73 | 0x7c000 (0x04000): Param store 74 | 0x80000 - end of flash 75 | 76 | Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All accesses 77 | *must* be aligned 32-bit accesses. Reading a short, byte or unaligned word will result in 78 | a memory exception, crashing the program. 79 | */ 80 | 81 | 82 | //Copies len bytes over from dst to src, but does it using *only* 83 | //aligned 32-bit reads. Yes, it's no too optimized but it's short and sweet and it works. 84 | 85 | //ToDo: perhaps os_memcpy also does unaligned accesses? 86 | void ICACHE_FLASH_ATTR memcpyAligned(char *dst, char *src, int len) { 87 | int x; 88 | int w, b; 89 | for (x=0; x>0); 93 | if (b==1) *dst=(w>>8); 94 | if (b==2) *dst=(w>>16); 95 | if (b==3) *dst=(w>>24); 96 | dst++; src++; 97 | } 98 | } 99 | 100 | 101 | //Open a file and return a pointer to the file desc struct. 102 | EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { 103 | #ifdef __ets__ 104 | char *p=(char *)(ESPFS_POS+0x40200000); 105 | #else 106 | char *p=espFsData; 107 | #endif 108 | char *hpos; 109 | char namebuf[256]; 110 | EspFsHeader h; 111 | EspFsFile *r; 112 | //Strip initial slashes 113 | while(fileName[0]=='/') fileName++; 114 | //Go find that file! 115 | while(1) { 116 | hpos=p; 117 | //Grab the next file header. 118 | os_memcpy(&h, p, sizeof(EspFsHeader)); 119 | if (h.magic!=0x73665345) { 120 | os_printf("Magic mismatch. EspFS image broken.\n"); 121 | return NULL; 122 | } 123 | if (h.flags&FLAG_LASTFILE) { 124 | os_printf("End of image.\n"); 125 | return NULL; 126 | } 127 | //Grab the name of the file. 128 | p+=sizeof(EspFsHeader); 129 | os_memcpy(namebuf, p, sizeof(namebuf)); 130 | // os_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n", 131 | // namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags); 132 | if (os_strcmp(namebuf, fileName)==0) { 133 | //Yay, this is the file we need! 134 | p+=h.nameLen; //Skip to content. 135 | r=(EspFsFile *)os_malloc(sizeof(EspFsFile)); //Alloc file desc mem 136 | // os_printf("Alloc %p\n", r); 137 | if (r==NULL) return NULL; 138 | r->header=(EspFsHeader *)hpos; 139 | r->decompressor=h.compression; 140 | r->posComp=p; 141 | r->posStart=p; 142 | r->posDecomp=0; 143 | if (h.compression==COMPRESS_NONE) { 144 | r->decompData=NULL; 145 | #ifdef EFS_HEATSHRINK 146 | } else if (h.compression==COMPRESS_HEATSHRINK) { 147 | //File is compressed with Heatshrink. 148 | char parm; 149 | heatshrink_decoder *dec; 150 | //Decoder params are stored in 1st byte. 151 | memcpyAligned(&parm, r->posComp, 1); 152 | r->posComp++; 153 | os_printf("Heatshrink compressed file; decode parms = %x\n", parm); 154 | dec=heatshrink_decoder_alloc(16, (parm>>4)&0xf, parm&0xf); 155 | r->decompData=dec; 156 | #endif 157 | } else { 158 | os_printf("Invalid compression: %d\n", h.compression); 159 | return NULL; 160 | } 161 | return r; 162 | } 163 | //We don't need this file. Skip name and file 164 | p+=h.nameLen+h.fileLenComp; 165 | if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val 166 | } 167 | } 168 | 169 | //Read len bytes from the given file into buff. Returns the actual amount of bytes read. 170 | int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { 171 | int flen; 172 | if (fh==NULL) return 0; 173 | //Cache file length. 174 | memcpyAligned((char*)&flen, (char*)&fh->header->fileLenComp, 4); 175 | //Do stuff depending on the way the file is compressed. 176 | if (fh->decompressor==COMPRESS_NONE) { 177 | int toRead; 178 | toRead=flen-(fh->posComp-fh->posStart); 179 | if (len>toRead) len=toRead; 180 | // os_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp); 181 | memcpyAligned(buff, fh->posComp, len); 182 | fh->posDecomp+=len; 183 | fh->posComp+=len; 184 | // os_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp); 185 | return len; 186 | #ifdef EFS_HEATSHRINK 187 | } else if (fh->decompressor==COMPRESS_HEATSHRINK) { 188 | int decoded=0; 189 | unsigned int elen, rlen; 190 | char ebuff[16]; 191 | heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; 192 | // os_printf("Alloc %p\n", dec); 193 | while(decodedposComp - fh->posStart); 197 | if (elen==0) return decoded; //file is read 198 | if (elen>0) { 199 | memcpyAligned(ebuff, fh->posComp, 16); 200 | heatshrink_decoder_sink(dec, (uint8_t *)ebuff, (elen>16)?16:elen, &rlen); 201 | fh->posComp+=rlen; 202 | if (rlen==elen) { 203 | heatshrink_decoder_finish(dec); 204 | } 205 | } 206 | //Grab decompressed data and put into buff 207 | heatshrink_decoder_poll(dec, (uint8_t *)buff, len-decoded, &rlen); 208 | fh->posDecomp+=rlen; 209 | buff+=rlen; 210 | decoded+=rlen; 211 | } 212 | return len; 213 | #endif 214 | } 215 | return 0; 216 | } 217 | 218 | //Close the file. 219 | void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) { 220 | if (fh==NULL) return; 221 | #ifdef EFS_HEATSHRINK 222 | if (fh->decompressor==COMPRESS_HEATSHRINK) { 223 | heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; 224 | heatshrink_decoder_free(dec); 225 | // os_printf("Freed %p\n", dec); 226 | } 227 | #endif 228 | // os_printf("Freed %p\n", fh); 229 | os_free(fh); 230 | } 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /esphttpd/user/espfs.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPFS_H 2 | #define ESPFS_H 3 | 4 | 5 | 6 | typedef struct EspFsFile EspFsFile; 7 | 8 | EspFsFile *espFsOpen(char *fileName); 9 | int espFsRead(EspFsFile *fh, char *buff, int len); 10 | void espFsClose(EspFsFile *fh); 11 | 12 | 13 | #endif -------------------------------------------------------------------------------- /esphttpd/user/heatshrink_config_httpd.h: -------------------------------------------------------------------------------- 1 | //Heatshrink config for the decompressor. 2 | #ifndef HEATSHRINK_CONFIG_H 3 | #define HEATSHRINK_CONFIG_H 4 | 5 | /* Should functionality assuming dynamic allocation be used? */ 6 | #define HEATSHRINK_DYNAMIC_ALLOC 1 7 | 8 | #if HEATSHRINK_DYNAMIC_ALLOC 9 | /* Optional replacement of malloc/free */ 10 | #define HEATSHRINK_MALLOC(SZ) os_malloc(SZ) 11 | #define HEATSHRINK_FREE(P, SZ) os_free(P) 12 | #else 13 | /* Required parameters for static configuration */ 14 | #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32 15 | #define HEATSHRINK_STATIC_WINDOW_BITS 8 16 | #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 17 | #endif 18 | 19 | /* Turn on logging for debugging. */ 20 | #define HEATSHRINK_DEBUGGING_LOGS 0 21 | 22 | /* Use indexing for faster compression. (This requires additional space.) */ 23 | #define HEATSHRINK_USE_INDEX 1 24 | 25 | #endif 26 | 27 | -------------------------------------------------------------------------------- /esphttpd/user/heatshrink_decoder.c: -------------------------------------------------------------------------------- 1 | #include "httpdconfig.h" 2 | #ifdef EFS_HEATSHRINK 3 | //Stupid wrapper so we don't have to move c-files around 4 | //Also loads httpd-specific config. 5 | 6 | #define _STDLIB_H_ 7 | #define _STRING_H_ 8 | #define _STDDEF_H 9 | #define _STDINT_H 10 | 11 | #include "espmissingincludes.h" 12 | #include "c_types.h" 13 | #include "mem.h" 14 | #include "osapi.h" 15 | #include "heatshrink_config_httpd.h" 16 | #define memset(x,y,z) os_memset(x,y,z) 17 | #define memcpy(x,y,z) os_memcpy(x,y,z) 18 | #include "../lib/heatshrink/heatshrink_decoder.c" 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /esphttpd/user/httpd.c: -------------------------------------------------------------------------------- 1 | /* 2 | Esp8266 http server - core routines 3 | */ 4 | 5 | /* 6 | * ---------------------------------------------------------------------------- 7 | * "THE BEER-WARE LICENSE" (Revision 42): 8 | * Jeroen Domburg wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | 15 | #include "espmissingincludes.h" 16 | #include "c_types.h" 17 | #include "user_interface.h" 18 | #include "espconn.h" 19 | #include "mem.h" 20 | #include "osapi.h" 21 | 22 | #include "espconn.h" 23 | #include "httpd.h" 24 | #include "io.h" 25 | #include "espfs.h" 26 | 27 | 28 | //Max length of request head 29 | #define MAX_HEAD_LEN 1024 30 | //Max amount of connections 31 | #define MAX_CONN 8 32 | //Max post buffer len 33 | #define MAX_POST 1024 34 | //Max send buffer len 35 | #define MAX_SENDBUFF_LEN 2048 36 | 37 | 38 | //This gets set at init time. 39 | static HttpdBuiltInUrl *builtInUrls; 40 | 41 | //Private data for http connection 42 | struct HttpdPriv { 43 | char head[MAX_HEAD_LEN]; 44 | int headPos; 45 | int postPos; 46 | char *sendBuff; 47 | int sendBuffLen; 48 | }; 49 | 50 | //Connection pool 51 | static HttpdPriv connPrivData[MAX_CONN]; 52 | static HttpdConnData connData[MAX_CONN]; 53 | 54 | //Listening connection data 55 | static struct espconn httpdConn; 56 | static esp_tcp httpdTcp; 57 | 58 | //Struct to keep extension->mime data in 59 | typedef struct { 60 | const char *ext; 61 | const char *mimetype; 62 | } MimeMap; 63 | 64 | //The mappings from file extensions to mime types. If you need an extra mime type, 65 | //add it here. 66 | static const MimeMap mimeTypes[]={ 67 | {"htm", "text/htm"}, 68 | {"html", "text/html"}, 69 | {"css", "text/css"}, 70 | {"js", "text/javascript"}, 71 | {"txt", "text/plain"}, 72 | {"jpg", "image/jpeg"}, 73 | {"jpeg", "image/jpeg"}, 74 | {"png", "image/png"}, 75 | {NULL, "text/html"}, //default value 76 | }; 77 | 78 | //Returns a static char* to a mime type for a given url to a file. 79 | const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { 80 | int i=0; 81 | //Go find the extension 82 | char *ext=url+(strlen(url)-1); 83 | while (ext!=url && *ext!='.') ext--; 84 | if (*ext=='.') ext++; 85 | 86 | //ToDo: os_strcmp is case sensitive; we may want to do case-intensive matching here... 87 | while (mimeTypes[i].ext!=NULL && os_strcmp(ext, mimeTypes[i].ext)!=0) i++; 88 | return mimeTypes[i].mimetype; 89 | } 90 | 91 | //Looks up the connData info for a specific esp connection 92 | static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(void *arg) { 93 | int i; 94 | for (i=0; ipostBuff!=NULL) os_free(conn->postBuff); 104 | conn->postBuff=NULL; 105 | conn->cgi=NULL; 106 | conn->conn=NULL; 107 | } 108 | 109 | //Stupid li'l helper function that returns the value of a hex char. 110 | static int httpdHexVal(char c) { 111 | if (c>='0' && c<='9') return c-'0'; 112 | if (c>='A' && c<='F') return c-'A'+10; 113 | if (c>='a' && c<='f') return c-'a'+10; 114 | return 0; 115 | } 116 | 117 | //Decode a percent-encoded value. 118 | //Takes the valLen bytes stored in val, and converts it into at most retLen bytes that 119 | //are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also 120 | //zero-terminates the ret buffer. 121 | int httpdUrlDecode(char *val, int valLen, char *ret, int retLen) { 122 | int s=0, d=0; 123 | int esced=0, escVal=0; 124 | while (spriv->head; 173 | p=p+strlen(p)+1; //skip GET/POST part 174 | p=p+strlen(p)+1; //skip HTTP part 175 | while (p<(conn->priv->head+conn->priv->headPos)) { 176 | while(*p<=32 && *p!=0) p++; //skip crap at start 177 | // os_printf("Looking for %s, Header: '%s'\n", header, p); 178 | //See if this is the header 179 | if (os_strncmp(p, header, strlen(header))==0 && p[strlen(header)]==':') { 180 | //Skip 'key:' bit of header line 181 | p=p+strlen(header)+1; 182 | //Skip past spaces after the colon 183 | while(*p==' ') p++; 184 | //Copy from p to end 185 | while (*p!=0 && *p!='\r' && *p!='\n' && retLen>1) { 186 | *ret++=*p++; 187 | retLen--; 188 | } 189 | //Zero-terminate string 190 | *ret=0; 191 | //All done :) 192 | return 1; 193 | } 194 | p+=strlen(p)+1; //Skip past end of string and \0 terminator 195 | } 196 | return 0; 197 | } 198 | 199 | //Start the response headers. 200 | void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code) { 201 | char buff[128]; 202 | int l; 203 | l=os_sprintf(buff, "HTTP/1.0 %d OK\r\nServer: esp8266-httpd/"HTTPDVER"\r\n", code); 204 | httpdSend(conn, buff, l); 205 | } 206 | 207 | //Send a http header. 208 | void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val) { 209 | char buff[256]; 210 | int l; 211 | 212 | l=os_sprintf(buff, "%s: %s\r\n", field, val); 213 | httpdSend(conn, buff, l); 214 | } 215 | 216 | //Finish the headers. 217 | void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn) { 218 | httpdSend(conn, "\r\n", -1); 219 | } 220 | 221 | //ToDo: sprintf->snprintf everywhere... esp doesn't have snprintf tho' :/ 222 | //Redirect to the given URL. 223 | void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl) { 224 | char buff[1024]; 225 | int l; 226 | l=os_sprintf(buff, "HTTP/1.1 302 Found\r\nLocation: %s\r\n\r\nMoved to %s\r\n", newUrl, newUrl); 227 | httpdSend(conn, buff, l); 228 | } 229 | 230 | //Use this as a cgi function to redirect one url to another. 231 | int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData) { 232 | if (connData->conn==NULL) { 233 | //Connection aborted. Clean up. 234 | return HTTPD_CGI_DONE; 235 | } 236 | httpdRedirect(connData, (char*)connData->cgiArg); 237 | return HTTPD_CGI_DONE; 238 | } 239 | 240 | 241 | //Add data to the send buffer. len is the length of the data. If len is -1 242 | //the data is seen as a C-string. 243 | //Returns 1 for success, 0 for out-of-memory. 244 | int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len) { 245 | if (len<0) len=strlen(data); 246 | if (conn->priv->sendBuffLen+len>MAX_SENDBUFF_LEN) return 0; 247 | os_memcpy(conn->priv->sendBuff+conn->priv->sendBuffLen, data, len); 248 | conn->priv->sendBuffLen+=len; 249 | return 1; 250 | } 251 | 252 | //Helper function to send any data in conn->priv->sendBuff 253 | static void ICACHE_FLASH_ATTR xmitSendBuff(HttpdConnData *conn) { 254 | if (conn->priv->sendBuffLen!=0) { 255 | espconn_sent(conn->conn, (uint8_t*)conn->priv->sendBuff, conn->priv->sendBuffLen); 256 | conn->priv->sendBuffLen=0; 257 | } 258 | } 259 | 260 | //Callback called when the data on a socket has been successfully 261 | //sent. 262 | static void ICACHE_FLASH_ATTR httpdSentCb(void *arg) { 263 | int r; 264 | HttpdConnData *conn=httpdFindConnData(arg); 265 | char sendBuff[MAX_SENDBUFF_LEN]; 266 | 267 | // os_printf("Sent callback on conn %p\n", conn); 268 | if (conn==NULL) return; 269 | conn->priv->sendBuff=sendBuff; 270 | conn->priv->sendBuffLen=0; 271 | 272 | if (conn->cgi==NULL) { //Marked for destruction? 273 | os_printf("Conn %p is done. Closing.\n", conn->conn); 274 | espconn_disconnect(conn->conn); 275 | httpdRetireConn(conn); 276 | return; //No need to call xmitSendBuff. 277 | } 278 | 279 | r=conn->cgi(conn); //Execute cgi fn. 280 | if (r==HTTPD_CGI_DONE) { 281 | conn->cgi=NULL; //mark for destruction. 282 | } 283 | xmitSendBuff(conn); 284 | } 285 | 286 | static const char *httpNotFoundHeader="HTTP/1.0 404 Not Found\r\nServer: esp8266-httpd/0.1\r\nContent-Type: text/plain\r\n\r\nNot Found.\r\n"; 287 | 288 | //This is called when the headers have been received and the connection is ready to send 289 | //the result headers and data. 290 | static void ICACHE_FLASH_ATTR httpdSendResp(HttpdConnData *conn) { 291 | int i=0; 292 | int r; 293 | //See if the url is somewhere in our internal url table. 294 | while (builtInUrls[i].url!=NULL && conn->url!=NULL) { 295 | int match=0; 296 | // os_printf("%s == %s?\n", builtInUrls[i].url, conn->url); 297 | if (os_strcmp(builtInUrls[i].url, conn->url)==0) match=1; 298 | if (builtInUrls[i].url[os_strlen(builtInUrls[i].url)-1]=='*' && 299 | os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url)-1)==0) match=1; 300 | if (match) { 301 | os_printf("Is url index %d\n", i); 302 | conn->cgiData=NULL; 303 | conn->cgi=builtInUrls[i].cgiCb; 304 | conn->cgiArg=builtInUrls[i].cgiArg; 305 | r=conn->cgi(conn); 306 | if (r!=HTTPD_CGI_NOTFOUND) { 307 | if (r==HTTPD_CGI_DONE) conn->cgi=NULL; //If cgi finishes immediately: mark conn for destruction. 308 | return; 309 | } 310 | } 311 | i++; 312 | } 313 | //Can't find :/ 314 | os_printf("%s not found. 404!\n", conn->url); 315 | httpdSend(conn, httpNotFoundHeader, -1); 316 | conn->cgi=NULL; //mark for destruction 317 | } 318 | 319 | //Parse a line of header data and modify the connection data accordingly. 320 | static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) { 321 | int i; 322 | // os_printf("Got header %s\n", h); 323 | if (os_strncmp(h, "GET ", 4)==0 || os_strncmp(h, "POST ", 5)==0) { 324 | char *e; 325 | 326 | //Skip past the space after POST/GET 327 | i=0; 328 | while (h[i]!=' ') i++; 329 | conn->url=h+i+1; 330 | 331 | //Figure out end of url. 332 | e=(char*)os_strstr(conn->url, " "); 333 | if (e==NULL) return; //wtf? 334 | *e=0; //terminate url part 335 | 336 | os_printf("URL = %s\n", conn->url); 337 | //Parse out the URL part before the GET parameters. 338 | conn->getArgs=(char*)os_strstr(conn->url, "?"); 339 | if (conn->getArgs!=0) { 340 | *conn->getArgs=0; 341 | conn->getArgs++; 342 | os_printf("GET args = %s\n", conn->getArgs); 343 | } else { 344 | conn->getArgs=NULL; 345 | } 346 | } else if (os_strncmp(h, "Content-Length: ", 16)==0) { 347 | i=0; 348 | //Skip trailing spaces 349 | while (h[i]!=' ') i++; 350 | //Get POST data length 351 | conn->postLen=atoi(h+i+1); 352 | //Clamp if too big. Hmm, maybe we should error out instead? 353 | if (conn->postLen>MAX_POST) conn->postLen=MAX_POST; 354 | os_printf("Mallocced buffer for %d bytes of post data.\n", conn->postLen); 355 | //Alloc the memory. 356 | conn->postBuff=(char*)os_malloc(conn->postLen+1); 357 | conn->priv->postPos=0; 358 | } 359 | } 360 | 361 | 362 | //Callback called when there's data available on a socket. 363 | static void ICACHE_FLASH_ATTR httpdRecvCb(void *arg, char *data, unsigned short len) { 364 | int x; 365 | char *p, *e; 366 | char sendBuff[MAX_SENDBUFF_LEN]; 367 | HttpdConnData *conn=httpdFindConnData(arg); 368 | if (conn==NULL) return; 369 | conn->priv->sendBuff=sendBuff; 370 | conn->priv->sendBuffLen=0; 371 | 372 | for (x=0; xpostLen<0) { 374 | //This byte is a header byte. 375 | if (conn->priv->headPos!=MAX_HEAD_LEN) conn->priv->head[conn->priv->headPos++]=data[x]; 376 | conn->priv->head[conn->priv->headPos]=0; 377 | //Scan for /r/n/r/n 378 | if (data[x]=='\n' && (char *)os_strstr(conn->priv->head, "\r\n\r\n")!=NULL) { 379 | //Indicate we're done with the headers. 380 | conn->postLen=0; 381 | //Reset url data 382 | conn->url=NULL; 383 | //Find end of next header line 384 | p=conn->priv->head; 385 | while(p<(&conn->priv->head[conn->priv->headPos-4])) { 386 | e=(char *)os_strstr(p, "\r\n"); 387 | if (e==NULL) break; 388 | e[0]=0; 389 | httpdParseHeader(p, conn); 390 | p=e+2; 391 | } 392 | //If we don't need to receive post data, we can send the response now. 393 | if (conn->postLen==0) { 394 | httpdSendResp(conn); 395 | } 396 | } 397 | } else if (conn->priv->postPos!=-1 && conn->postLen!=0 && conn->priv->postPos <= conn->postLen) { 398 | //This byte is a POST byte. 399 | conn->postBuff[conn->priv->postPos++]=data[x]; 400 | if (conn->priv->postPos>=conn->postLen) { 401 | //Received post stuff. 402 | conn->postBuff[conn->priv->postPos]=0; //zero-terminate 403 | conn->priv->postPos=-1; 404 | os_printf("Post data: %s\n", conn->postBuff); 405 | //Send the response. 406 | httpdSendResp(conn); 407 | break; 408 | } 409 | } 410 | } 411 | xmitSendBuff(conn); 412 | } 413 | 414 | static void ICACHE_FLASH_ATTR httpdReconCb(void *arg, sint8 err) { 415 | HttpdConnData *conn=httpdFindConnData(arg); 416 | os_printf("ReconCb\n"); 417 | if (conn==NULL) return; 418 | //Yeah... No idea what to do here. ToDo: figure something out. 419 | } 420 | 421 | static void ICACHE_FLASH_ATTR httpdDisconCb(void *arg) { 422 | #if 0 423 | //Stupid esp sdk passes through wrong arg here, namely the one of the *listening* socket. 424 | //If it ever gets fixed, be sure to update the code in this snippet; it's probably out-of-date. 425 | HttpdConnData *conn=httpdFindConnData(arg); 426 | os_printf("Disconnected, conn=%p\n", conn); 427 | if (conn==NULL) return; 428 | conn->conn=NULL; 429 | if (conn->cgi!=NULL) conn->cgi(conn); //flush cgi data 430 | #endif 431 | //Just look at all the sockets and kill the slot if needed. 432 | int i; 433 | for (i=0; i=ESPCONN_CLOSE and not ==? Well, seems the stack sometimes de-allocates 436 | //espconns under our noses, especially when connections are interrupted. The memory 437 | //is then used for something else, and we can use that to capture *most* of the 438 | //disconnect cases. 439 | if (connData[i].conn->state==ESPCONN_NONE || connData[i].conn->state>=ESPCONN_CLOSE) { 440 | connData[i].conn=NULL; 441 | if (connData[i].cgi!=NULL) connData[i].cgi(&connData[i]); //flush cgi data 442 | httpdRetireConn(&connData[i]); 443 | } 444 | } 445 | } 446 | } 447 | 448 | 449 | static void ICACHE_FLASH_ATTR httpdConnectCb(void *arg) { 450 | struct espconn *conn=arg; 451 | int i; 452 | //Find empty conndata in pool 453 | for (i=0; iheadPos=0; 463 | connData[i].postBuff=NULL; 464 | connData[i].priv->postPos=0; 465 | connData[i].postLen=-1; 466 | 467 | espconn_regist_recvcb(conn, httpdRecvCb); 468 | espconn_regist_reconcb(conn, httpdReconCb); 469 | espconn_regist_disconcb(conn, httpdDisconCb); 470 | espconn_regist_sentcb(conn, httpdSentCb); 471 | } 472 | 473 | 474 | void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) { 475 | int i; 476 | 477 | for (i=0; i 4 | #include 5 | #include 6 | 7 | #define HTTPDVER "0.2" 8 | 9 | #define HTTPD_CGI_MORE 0 10 | #define HTTPD_CGI_DONE 1 11 | #define HTTPD_CGI_NOTFOUND 2 12 | #define HTTPD_CGI_AUTHENTICATED 2 //for now 13 | 14 | typedef struct HttpdPriv HttpdPriv; 15 | typedef struct HttpdConnData HttpdConnData; 16 | 17 | typedef int (* cgiSendCallback)(HttpdConnData *connData); 18 | 19 | //A struct describing a http connection. This gets passed to cgi functions. 20 | struct HttpdConnData { 21 | struct espconn *conn; 22 | char *url; 23 | char *getArgs; 24 | const void *cgiArg; 25 | void *cgiData; 26 | HttpdPriv *priv; 27 | cgiSendCallback cgi; 28 | int postLen; 29 | char *postBuff; 30 | }; 31 | 32 | //A struct describing an url. This is the main struct that's used to send different URL requests to 33 | //different routines. 34 | typedef struct { 35 | const char *url; 36 | cgiSendCallback cgiCb; 37 | const void *cgiArg; 38 | } HttpdBuiltInUrl; 39 | 40 | int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData); 41 | void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl); 42 | int httpdUrlDecode(char *val, int valLen, char *ret, int retLen); 43 | int ICACHE_FLASH_ATTR httpdFindArg(char *line, char *arg, char *buff, int buffLen); 44 | void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port); 45 | const char *httpdGetMimetype(char *url); 46 | void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code); 47 | void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val); 48 | void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn); 49 | int ICACHE_FLASH_ATTR httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen); 50 | int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len); 51 | 52 | #endif -------------------------------------------------------------------------------- /esphttpd/user/httpdespfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | Connector to let httpd use the espfs filesystem to serve the files in it. 3 | */ 4 | 5 | /* 6 | * ---------------------------------------------------------------------------- 7 | * "THE BEER-WARE LICENSE" (Revision 42): 8 | * Jeroen Domburg wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | #include "espmissingincludes.h" 15 | #include 16 | #include 17 | #include "c_types.h" 18 | #include "user_interface.h" 19 | #include "espconn.h" 20 | #include "mem.h" 21 | 22 | #include "httpd.h" 23 | #include "espfs.h" 24 | #include "httpdespfs.h" 25 | 26 | 27 | //This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding 28 | //path in the filesystem and if it exists, passes the file through. This simulates what a normal 29 | //webserver would do with static files. 30 | int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) { 31 | EspFsFile *file=connData->cgiData; 32 | int len; 33 | char buff[1024]; 34 | 35 | if (connData->conn==NULL) { 36 | //Connection aborted. Clean up. 37 | espFsClose(file); 38 | return HTTPD_CGI_DONE; 39 | } 40 | 41 | if (file==NULL) { 42 | //First call to this cgi. Open the file so we can read it. 43 | file=espFsOpen(connData->url); 44 | if (file==NULL) { 45 | return HTTPD_CGI_NOTFOUND; 46 | } 47 | connData->cgiData=file; 48 | httpdStartResponse(connData, 200); 49 | httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); 50 | httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); 51 | httpdEndHeaders(connData); 52 | return HTTPD_CGI_MORE; 53 | } 54 | 55 | len=espFsRead(file, buff, 1024); 56 | if (len>0) espconn_sent(connData->conn, (uint8 *)buff, len); 57 | if (len!=1024) { 58 | //We're done. 59 | espFsClose(file); 60 | return HTTPD_CGI_DONE; 61 | } else { 62 | //Ok, till next time. 63 | return HTTPD_CGI_MORE; 64 | } 65 | } 66 | 67 | 68 | //cgiEspFsTemplate can be used as a template. 69 | 70 | typedef struct { 71 | EspFsFile *file; 72 | void *tplArg; 73 | char token[64]; 74 | int tokenPos; 75 | } TplData; 76 | 77 | typedef void (* TplCallback)(HttpdConnData *connData, char *token, void **arg); 78 | 79 | int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) { 80 | TplData *tpd=connData->cgiData; 81 | int len; 82 | int x, sp=0; 83 | char *e=NULL; 84 | char buff[1025]; 85 | 86 | if (connData->conn==NULL) { 87 | //Connection aborted. Clean up. 88 | ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); 89 | espFsClose(tpd->file); 90 | os_free(tpd); 91 | return HTTPD_CGI_DONE; 92 | } 93 | 94 | if (tpd==NULL) { 95 | //First call to this cgi. Open the file so we can read it. 96 | tpd=(TplData *)os_malloc(sizeof(TplData)); 97 | tpd->file=espFsOpen(connData->url); 98 | tpd->tplArg=NULL; 99 | tpd->tokenPos=-1; 100 | if (tpd->file==NULL) { 101 | return HTTPD_CGI_NOTFOUND; 102 | } 103 | connData->cgiData=tpd; 104 | httpdStartResponse(connData, 200); 105 | httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); 106 | httpdEndHeaders(connData); 107 | return HTTPD_CGI_MORE; 108 | } 109 | 110 | len=espFsRead(tpd->file, buff, 1024); 111 | if (len>0) { 112 | sp=0; 113 | e=buff; 114 | for (x=0; xtokenPos==-1) { 116 | //Inside ordinary text. 117 | if (buff[x]=='%') { 118 | //Send raw data up to now 119 | if (sp!=0) httpdSend(connData, e, sp); 120 | sp=0; 121 | //Go collect token chars. 122 | tpd->tokenPos=0; 123 | } else { 124 | sp++; 125 | } 126 | } else { 127 | if (buff[x]=='%') { 128 | if (tpd->tokenPos==0) { 129 | //This is the second % of a %% escape string. 130 | //Send a single % and resume with the normal program flow. 131 | httpdSend(connData, "%", 1); 132 | } else { 133 | //This is an actual token. 134 | tpd->token[tpd->tokenPos++]=0; //zero-terminate token 135 | ((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg); 136 | } 137 | //Go collect normal chars again. 138 | e=&buff[x+1]; 139 | tpd->tokenPos=-1; 140 | } else { 141 | if (tpd->tokenPos<(sizeof(tpd->token)-1)) tpd->token[tpd->tokenPos++]=buff[x]; 142 | } 143 | } 144 | } 145 | } 146 | //Send remaining bit. 147 | if (sp!=0) httpdSend(connData, e, sp); 148 | if (len!=1024) { 149 | //We're done. 150 | ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); 151 | espFsClose(tpd->file); 152 | return HTTPD_CGI_DONE; 153 | } else { 154 | //Ok, till next time. 155 | return HTTPD_CGI_MORE; 156 | } 157 | } 158 | 159 | -------------------------------------------------------------------------------- /esphttpd/user/httpdespfs.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTPDESPFS_H 2 | #define HTTPDESPFS_H 3 | 4 | #include "httpd.h" 5 | #include "espfs.h" 6 | 7 | int cgiEspFsHook(HttpdConnData *connData); 8 | int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData); 9 | 10 | #endif -------------------------------------------------------------------------------- /esphttpd/user/io.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * ---------------------------------------------------------------------------- 4 | * "THE BEER-WARE LICENSE" (Revision 42): 5 | * Jeroen Domburg wrote this file. As long as you retain 6 | * this notice you can do whatever you want with this stuff. If we meet some day, 7 | * and you think this stuff is worth it, you can buy me a beer in return. 8 | * ---------------------------------------------------------------------------- 9 | */ 10 | 11 | 12 | #include "c_types.h" 13 | #include "user_interface.h" 14 | #include "espconn.h" 15 | #include "mem.h" 16 | #include "osapi.h" 17 | #include "gpio.h" 18 | #include "espmissingincludes.h" 19 | 20 | #define LEDGPIO 2 21 | #define BTNGPIO 0 22 | 23 | static ETSTimer resetBtntimer; 24 | 25 | void ICACHE_FLASH_ATTR ioLed(int ena) { 26 | //gpio_output_set is overkill. ToDo: use better mactos 27 | if (ena) { 28 | gpio_output_set((1<=6) { //3 sec pressed 40 | wifi_station_disconnect(); 41 | wifi_set_opmode(0x3); //reset to AP+STA mode 42 | os_printf("Reset to AP mode. Restarting system...\n"); 43 | system_restart(); 44 | } 45 | resetCnt=0; 46 | } 47 | } 48 | 49 | void ioInit() { 50 | PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2); 51 | PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0); 52 | gpio_output_set(0, 0, (1< wrote this file. As long as you retain 7 | * this notice you can do whatever you want with this stuff. If we meet some day, 8 | * and you think this stuff is worth it, you can buy me a beer in return. 9 | * ---------------------------------------------------------------------------- 10 | */ 11 | 12 | 13 | #include "espmissingincludes.h" 14 | #include "ets_sys.h" 15 | #include "osapi.h" 16 | #include "uart_hw.h" 17 | 18 | static void ICACHE_FLASH_ATTR stdoutUartTxd(char c) { 19 | //Wait until there is room in the FIFO 20 | while (((READ_PERI_REG(UART_STATUS(0))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ; 21 | //Send the character 22 | WRITE_PERI_REG(UART_FIFO(0), c); 23 | } 24 | 25 | static void ICACHE_FLASH_ATTR stdoutPutchar(char c) { 26 | //convert \n -> \r\n 27 | if (c=='\n') stdoutUartTxd('\r'); 28 | stdoutUartTxd(c); 29 | } 30 | 31 | 32 | void stdoutInit() { 33 | //Enable TxD pin 34 | PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U); 35 | PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD); 36 | 37 | //Set baud rate and other serial parameters to 115200,n,8,1 38 | uart_div_modify(0, UART_CLK_FREQ/BIT_RATE_115200); 39 | WRITE_PERI_REG(UART_CONF0(0), (STICK_PARITY_DIS)|(ONE_STOP_BIT << UART_STOP_BIT_NUM_S)| \ 40 | (EIGHT_BITS << UART_BIT_NUM_S)); 41 | 42 | //Reset tx & rx fifo 43 | SET_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST|UART_TXFIFO_RST); 44 | CLEAR_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST|UART_TXFIFO_RST); 45 | //Clear pending interrupts 46 | WRITE_PERI_REG(UART_INT_CLR(0), 0xffff); 47 | 48 | //Install our own putchar handler 49 | os_install_putc1((void *)stdoutPutchar); 50 | } 51 | -------------------------------------------------------------------------------- /esphttpd/user/stdout.h: -------------------------------------------------------------------------------- 1 | void stdoutInit(); 2 | -------------------------------------------------------------------------------- /esphttpd/user/user_main.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | * ---------------------------------------------------------------------------- 5 | * "THE BEER-WARE LICENSE" (Revision 42): 6 | * Jeroen Domburg wrote this file. As long as you retain 7 | * this notice you can do whatever you want with this stuff. If we meet some day, 8 | * and you think this stuff is worth it, you can buy me a beer in return. 9 | * ---------------------------------------------------------------------------- 10 | */ 11 | 12 | 13 | #include "espmissingincludes.h" 14 | #include "ets_sys.h" 15 | #include "osapi.h" 16 | #include "httpd.h" 17 | #include "io.h" 18 | #include "httpdespfs.h" 19 | #include "cgi.h" 20 | #include "cgiwifi.h" 21 | #include "stdout.h" 22 | #include "auth.h" 23 | 24 | //Function that tells the authentication system what users/passwords live on the system. 25 | //This is disabled in the default build; if you want to try it, enable the authBasic line in 26 | //the builtInUrls below. 27 | int myPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen) { 28 | if (no==0) { 29 | os_strcpy(user, "admin"); 30 | os_strcpy(pass, "s3cr3t"); 31 | return 1; 32 | //Add more users this way. Check against incrementing no for each user added. 33 | // } else if (no==1) { 34 | // os_strcpy(user, "user1"); 35 | // os_strcpy(pass, "something"); 36 | // return 1; 37 | } 38 | return 0; 39 | } 40 | 41 | 42 | /* 43 | This is the main url->function dispatching data struct. 44 | In short, it's a struct with various URLs plus their handlers. The handlers can 45 | be 'standard' CGI functions you wrote, or 'special' CGIs requiring an argument. 46 | They can also be auth-functions. An asterisk will match any url starting with 47 | everything before the asterisks; "*" matches everything. The list will be 48 | handled top-down, so make sure to put more specific rules above the more 49 | general ones. Authorization things (like authBasic) act as a 'barrier' and 50 | should be placed above the URLs they protect. 51 | */ 52 | HttpdBuiltInUrl builtInUrls[]={ 53 | {"/", cgiRedirect, "/index.tpl"}, 54 | {"/flash.bin", cgiReadFlash, NULL}, 55 | {"/led.tpl", cgiEspFsTemplate, tplLed}, 56 | {"/index.tpl", cgiEspFsTemplate, tplCounter}, 57 | {"/led.cgi", cgiLed, NULL}, 58 | 59 | //Routines to make the /wifi URL and everything beneath it work. 60 | 61 | //Enable the line below to protect the WiFi configuration with an username/password combo. 62 | // {"/wifi/*", authBasic, myPassFn}, 63 | 64 | {"/wifi", cgiRedirect, "/wifi/wifi.tpl"}, 65 | {"/wifi/", cgiRedirect, "/wifi/wifi.tpl"}, 66 | {"/wifi/wifiscan.cgi", cgiWiFiScan, NULL}, 67 | {"/wifi/wifi.tpl", cgiEspFsTemplate, tplWlan}, 68 | {"/wifi/connect.cgi", cgiWiFiConnect, NULL}, 69 | {"/wifi/setmode.cgi", cgiWifiSetMode, NULL}, 70 | 71 | {"*", cgiEspFsHook, NULL}, //Catch-all cgi function for the filesystem 72 | {NULL, NULL, NULL} 73 | }; 74 | 75 | 76 | //Main routine. Initialize stdout, the I/O and the webserver and we're done. 77 | void user_init(void) { 78 | stdoutInit(); 79 | ioInit(); 80 | httpdInit(builtInUrls, 80); 81 | os_printf("\nReady\n"); 82 | } 83 | -------------------------------------------------------------------------------- /tools/esptool-0.0.3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/tools/esptool-0.0.3 -------------------------------------------------------------------------------- /tools/sdk/esp_iot_sdk_v0.9.5_15_01_23.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/tools/sdk/esp_iot_sdk_v0.9.5_15_01_23.zip -------------------------------------------------------------------------------- /tools/sdk/esp_iot_sdk_v0.9.5_15_01_23_patch1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/tools/sdk/esp_iot_sdk_v0.9.5_15_01_23_patch1.zip -------------------------------------------------------------------------------- /tools/sdk/extra-includes/include.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/tools/sdk/extra-includes/include.tgz -------------------------------------------------------------------------------- /tools/sdk/extra-libs/libc.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/tools/sdk/extra-libs/libc.a -------------------------------------------------------------------------------- /tools/sdk/extra-libs/libhal.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mziwisky/esp8266-dev/aed9a110274ad3c9ccfdc0dd6b298f0acd35580c/tools/sdk/extra-libs/libhal.a -------------------------------------------------------------------------------- /vm-bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | 4 | # This is based on https://github.com/esp8266/esp8266-wiki/wiki/Toolchain 5 | 6 | # prepare the machine 7 | sudo apt-get update 8 | sudo apt-get -y install git autoconf build-essential \ 9 | gperf bison flex texinfo libtool libncurses5-dev \ 10 | wget gawk libc6-dev-amd64 python-pip libexpat-dev unzip 11 | 12 | sudo pip install pyserial 13 | 14 | if [ ! -d /opt/Espressif ]; then 15 | sudo mkdir /opt/Espressif 16 | fi 17 | sudo chown vagrant /opt/Espressif 18 | 19 | # Build the cross-compiler 20 | cd /opt/Espressif 21 | if [ ! -d /opt/Espressif/crosstool-NG ]; then 22 | git clone -b lx106 https://github.com/jcmvbkbc/crosstool-NG.git 23 | fi 24 | 25 | cd /opt/Espressif/crosstool-NG 26 | 27 | function gitsha() { git show --format=%H | head -1; } 28 | OLDSHA=`gitsha` 29 | git pull origin lx106 30 | 31 | if [ "$OLDSHA" != `gitsha` ] || [ ! -d /opt/Espressif/crosstool-NG/builds ]; then 32 | ./bootstrap && ./configure -- prefix=`pwd` && make && make install 33 | ./ct-ng xtensa-lx106-elf 34 | ./ct-ng build 35 | fi 36 | 37 | PATH=$PWD/builds/xtensa-lx106-elf/bin:$PATH # for building the RTOS SDK later 38 | 39 | # Setup the cross compiler 40 | HAS_PATH=`cat ~/.bashrc | grep "$PWD/builds/xtensa-lx106-elf/bin:" || :` 41 | if [ -z "$HAS_PATH" ]; then 42 | echo "# Add Xtensa Compiler Path" >> ~/.bashrc 43 | echo "export PATH=$PWD/builds/xtensa-lx106-elf/bin:\$PATH" >> ~/.bashrc 44 | echo "export XTENSA_TOOLS_ROOT=$PWD/builds/xtensa-lx106-elf/bin" >> ~/.bashrc 45 | fi 46 | 47 | cd /opt/Espressif/crosstool-NG/builds/xtensa-lx106-elf/bin 48 | chmod u+w . 49 | rm -f xt-* 50 | for i in `ls xtensa-lx106*`; do 51 | XT_NAME=`echo -n $i | sed s/xtensa-lx106-elf-/xt-/` 52 | echo "symlinking: $XT_NAME" 53 | ln -s "$i" "$XT_NAME" 54 | done 55 | ln -s xt-cc xt-xcc # the RTOS SDK needs it 56 | 57 | HAS_CROSS_COMPILE=`cat ~/.bashrc | grep "export CROSS_COMPILE" || :` 58 | if [ -z "$HAS_CROSS_COMPILE" ]; then 59 | echo "# Cross Compilation Settings" >> ~/.bashrc 60 | echo "export CROSS_COMPILE=xtensa-lx106-elf-" >> ~/.bashrc 61 | fi 62 | 63 | # Set up the SDK 64 | # TODO: can this be automated? is there a place to check what the latest SDK 65 | # is, and then install it the same way each time? probably not -- updates to 66 | # this repo will have to manually change this chunk of steps. 67 | cd /opt/Espressif 68 | LATEST_SDK_VERSION="esp_iot_sdk_v0.9.5" 69 | CURRENT_SDK_VERSION=`readlink esp8266_sdk || :` 70 | 71 | if [ "$LATEST_SDK_VERSION" != "$CURRENT_SDK_VERSION" ]; then 72 | rm -rf esp8266_sdk 73 | unzip -o /vagrant/tools/sdk/esp_iot_sdk_v0.9.5_15_01_23.zip 74 | mv License esp_iot_sdk_v0.9.5/ 75 | mv release_note.txt esp_iot_sdk_v0.9.5/ 76 | cd esp_iot_sdk_v0.9.5/include 77 | unzip -o /vagrant/tools/sdk/esp_iot_sdk_v0.9.5_15_01_23_patch1.zip user_interface.h 78 | cd ../lib 79 | mv libmain.a libmain.a.old 80 | 81 | unzip -o /vagrant/tools/sdk/esp_iot_sdk_v0.9.5_15_01_23_patch1.zip libmain_fix_0.9.5.a 82 | mv libmain_fix_0.9.5.a libmain.a 83 | cd ../../ 84 | ln -s esp_iot_sdk_v0.9.5 esp8266_sdk 85 | cp /vagrant/tools/sdk/extra-libs/* esp8266_sdk/lib/ 86 | cd /opt/Espressif/esp8266_sdk 87 | tar -xzf /vagrant/tools/sdk/extra-includes/include.tgz 88 | fi 89 | 90 | HAS_SDK_BASE=`cat ~/.bashrc | grep "export SDK_BASE" || :` 91 | if [ -z "$HAS_SDK_BASE" ]; then 92 | echo "# ESP8266 SDK Base" >> ~/.bashrc 93 | echo "export SDK_BASE=/opt/Espressif/esp8266_sdk" >> ~/.bashrc 94 | fi 95 | 96 | # Set up the RTOS SDK 97 | cd /opt/Espressif 98 | if [ ! -d /opt/Espressif/esp8266_rtos_sdk ]; then 99 | git clone https://github.com/espressif/esp_iot_rtos_sdk.git esp8266_rtos_sdk 100 | git clone https://github.com/espressif/esp_iot_rtos_sdk_lib.git esp8266_rtos_sdk_lib 101 | cp esp8266_rtos_sdk_lib/lib/* esp8266_rtos_sdk/lib 102 | fi 103 | 104 | cd /opt/Espressif/esp8266_rtos_sdk 105 | git pull origin master 106 | make 107 | 108 | 109 | # Install ESP tool 110 | sudo cp /vagrant/tools/esptool-0.0.3 /usr/local/bin/esptool 111 | 112 | # Install esptool-py 113 | cd /opt/Espressif 114 | if [ ! -d /opt/Espressif/esptool-py ]; then 115 | git clone https://github.com/themadinventor/esptool esptool-py 116 | fi 117 | cd /opt/Espressif/esptool-py 118 | git pull origin master 119 | sudo ln -sf $PWD/esptool.py /usr/local/bin/ 120 | 121 | --------------------------------------------------------------------------------