From b37efbe891dee2af8341df7721a40aac47c4f843 Mon Sep 17 00:00:00 2001 From: cpq Date: Sun, 24 Sep 2023 11:52:26 +0100 Subject: [PATCH] Commonise flash-based OTA. Add h7 support. --- Makefile | 5 +- examples/device-dashboard/net.c | 18 +- examples/device-dashboard/packed_fs.c | 1431 +++++++++-------- examples/device-dashboard/web_root/main.css | 2 +- examples/device-dashboard/web_root/main.js | 40 +- .../Makefile | 12 +- .../main.c | 19 + .../mongoose_custom.h | 12 + .../Makefile | 11 +- .../main.c | 4 + .../mongoose_custom.h | 12 + mongoose.c | 734 +++++++-- mongoose.h | 76 +- src/config.h | 8 + src/ota.h | 32 +- src/ota_dummy.c | 33 +- src/ota_flash.c | 274 ++++ src/ota_stm32h5.c | 191 --- src/sys.h | 21 + src/sys_stm32h5.c | 143 ++ src/sys_stm32h7.c | 149 ++ src/util.h | 15 + 22 files changed, 2069 insertions(+), 1173 deletions(-) create mode 100644 examples/stm32/nucleo-h563zi-make-baremetal-builtin/mongoose_custom.h create mode 100644 examples/stm32/nucleo-h743zi-make-baremetal-builtin/mongoose_custom.h create mode 100644 src/ota_flash.c delete mode 100644 src/ota_stm32h5.c create mode 100644 src/sys.h create mode 100644 src/sys_stm32h5.c create mode 100644 src/sys_stm32h7.c diff --git a/Makefile b/Makefile index 2c484dc8..2d3bdf92 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,8 @@ INCS ?= -Isrc -I. SSL ?= CWD ?= $(realpath $(CURDIR)) ENV ?= -e Tmp=. -e WINEDEBUG=-all -DOCKER ?= docker run --platform linux/amd64 --rm $(ENV) -v $(CWD):$(CWD) -w $(CWD) +DOCKER_BIN ?= docker +DOCKER ?= $(DOCKER_BIN) run --platform linux/amd64 --rm $(ENV) -v $(CWD):$(CWD) -w $(CWD) VCFLAGS = /nologo /W3 /O2 /MD /I. $(DEFS) $(TFLAGS) IPV6 ?= 1 ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common @@ -175,7 +176,7 @@ mongoose.c: Makefile $(wildcard src/*.c) $(wildcard src/drivers/*.c) (cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ mongoose.h: $(HDRS) Makefile - (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/ota.h src/net_builtin.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ + (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/ota.h src/sys.h src/net_builtin.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ clean: clean_examples clean_embedded diff --git a/examples/device-dashboard/net.c b/examples/device-dashboard/net.c index ecd72845..a6423409 100644 --- a/examples/device-dashboard/net.c +++ b/examples/device-dashboard/net.c @@ -238,20 +238,16 @@ static void handle_firmware_rollback(struct mg_connection *c) { } static size_t print_status(void (*out)(char, void *), void *ptr, va_list *ap) { - struct mg_ota_data *os = va_arg(*ap, struct mg_ota_data *); - return mg_xprintf( - out, ptr, "{%m:%s,%m:%c%x%c,%m:%u,%m:%u,%m:%u,%m:%u,%m:%u}", - MG_ESC("valid"), os->magic == MG_OTA_MAGIC ? "true" : "false", - MG_ESC("magic"), '"', os->magic, '"', MG_ESC("crc32"), os->crc32, - MG_ESC("size"), os->size, MG_ESC("time"), os->time, MG_ESC("booted"), - os->booted, MG_ESC("golden"), os->golden); + int fw = va_arg(*ap, int); + return mg_xprintf(out, ptr, "{%m:%d,%m:%c%lx%c,%m:%u,%m:%u}", + MG_ESC("status"), mg_ota_status(fw), MG_ESC("crc32"), '"', + mg_ota_crc32(fw), '"', MG_ESC("size"), mg_ota_size(fw), + MG_ESC("timestamp"), mg_ota_timestamp(fw)); } static void handle_firmware_status(struct mg_connection *c) { - struct mg_ota_data od[2]; - mg_ota_status(od); - mg_http_reply(c, 200, s_json_header, "[%M,%M]\n", print_status, &od[0], - print_status, &od[1]); + mg_http_reply(c, 200, s_json_header, "[%M,%M]\n", print_status, + MG_FIRMWARE_CURRENT, print_status, MG_FIRMWARE_PREVIOUS); } static void handle_sys_reset(struct mg_connection *c) { diff --git a/examples/device-dashboard/packed_fs.c b/examples/device-dashboard/packed_fs.c index b8f9fec1..3a1549d8 100644 --- a/examples/device-dashboard/packed_fs.c +++ b/examples/device-dashboard/packed_fs.c @@ -652,7 +652,7 @@ static const unsigned char v1[] = { 0, 0 // . }; static const unsigned char v2[] = { - 31, 139, 8, 8, 236, 230, 244, 100, 0, 3, 99, 111, // .......d..co + 31, 139, 8, 8, 189, 48, 247, 100, 0, 3, 99, 111, // .....0.d..co 109, 112, 111, 110, 101, 110, 116, 115, 46, 106, 115, 0, // mponents.js. 237, 93, 109, 115, 219, 70, 146, 254, 238, 95, 49, 97, // .]ms.F..._1a 185, 150, 228, 70, 128, 240, 78, 64, 182, 180, 229, 36, // ...F..N@...$ @@ -1669,7 +1669,7 @@ static const unsigned char v4[] = { 199, 3, 0, 0, 0 // .... }; static const unsigned char v5[] = { - 31, 139, 8, 8, 124, 212, 245, 100, 0, 3, 109, 97, // ....|..d..ma + 31, 139, 8, 8, 255, 19, 16, 101, 0, 3, 109, 97, // .......e..ma 105, 110, 46, 99, 115, 115, 0, 237, 59, 107, 143, 227, // in.css..;k.. 200, 141, 127, 69, 55, 139, 5, 186, 247, 36, 141, 36, // ...E7....$.$ 203, 118, 183, 140, 4, 1, 14, 8, 178, 64, 246, 62, // .v.......@.> @@ -1813,716 +1813,721 @@ static const unsigned char v5[] = { 194, 89, 113, 93, 41, 239, 118, 152, 156, 62, 149, 116, // .Yq]).v..>.t 144, 143, 234, 80, 202, 146, 14, 178, 80, 4, 31, 185, // ...P....P... 26, 166, 206, 84, 44, 38, 90, 13, 244, 234, 66, 203, // ...T,&Z...B. - 154, 57, 197, 82, 248, 84, 135, 20, 60, 163, 47, 10, // .9.R.T..<./. - 159, 155, 49, 69, 172, 199, 8, 222, 131, 70, 51, 227, // ..1E.....F3. - 216, 6, 119, 15, 13, 155, 199, 56, 21, 8, 219, 56, // ..w....8...8 - 37, 239, 18, 133, 13, 217, 107, 132, 81, 96, 176, 83, // %.....k.Q`.S - 232, 81, 78, 160, 104, 171, 183, 113, 53, 75, 24, 54, // .QN.h..q5K.6 - 122, 155, 121, 172, 113, 120, 100, 31, 112, 239, 112, 175, // z.y.qxd.p.p. - 231, 113, 53, 0, 204, 19, 176, 5, 135, 44, 59, 153, // .q5......,;. - 36, 241, 4, 213, 106, 128, 9, 208, 174, 133, 99, 173, // $...j.....c. - 128, 66, 132, 6, 42, 2, 213, 28, 48, 54, 33, 48, // .B..*...06!0 - 54, 109, 72, 236, 219, 161, 183, 143, 176, 57, 164, 91, // 6mH......9.[ - 60, 90, 174, 203, 91, 158, 208, 228, 17, 44, 203, 253, // - 172, 13, 8, 12, 221, 255, 123, 128, 179, 184, 158, 195, // ......{..... - 255, 164, 193, 145, 17, 13, 122, 212, 14, 44, 216, 23, // ......z..,.. - 152, 63, 233, 24, 232, 211, 107, 147, 153, 30, 58, 208, // .?....k...:. - 194, 42, 205, 114, 74, 35, 183, 198, 136, 78, 213, 211, // .*.rJ#...N.. - 233, 31, 73, 79, 167, 169, 158, 78, 186, 158, 240, 169, // ..IO...N.... - 57, 165, 177, 21, 53, 161, 38, 76, 173, 199, 223, 183, // 9...5.&L.... - 138, 126, 247, 74, 78, 244, 232, 93, 27, 152, 246, 129, // .~.JN..].... - 104, 74, 153, 16, 151, 204, 133, 176, 57, 127, 236, 33, // hJ......9..! - 86, 204, 147, 44, 164, 129, 43, 80, 178, 6, 13, 253, // V..,..+P.... - 160, 105, 184, 181, 129, 15, 156, 176, 50, 146, 124, 135, // .i......2.|. - 222, 3, 158, 28, 236, 216, 111, 241, 104, 84, 79, 15, // ......o.hTO. - 216, 44, 11, 199, 66, 91, 145, 203, 105, 107, 234, 149, // .,..B[..ik.. - 16, 193, 18, 36, 44, 241, 165, 67, 117, 0, 123, 154, // ...$,..Cu.{. - 161, 251, 170, 193, 209, 210, 223, 13, 201, 74, 170, 99, // .........J.c - 22, 176, 213, 16, 187, 150, 36, 108, 30, 92, 19, 28, // ......$l.... - 55, 184, 104, 136, 198, 221, 93, 133, 171, 100, 232, 234, // 7.h...]..d.. - 93, 238, 234, 77, 186, 82, 191, 10, 84, 204, 193, 198, // ]..M.R..T... - 9, 134, 249, 168, 18, 214, 17, 130, 115, 76, 165, 136, // ........sL.. - 27, 5, 139, 102, 133, 84, 72, 0, 16, 120, 124, 195, // ...f.TH..x|. - 120, 53, 156, 199, 195, 170, 233, 104, 82, 131, 102, 167, // x5.....hR.f. - 234, 128, 185, 121, 4, 11, 26, 100, 48, 161, 80, 120, // ...y...d0.Px - 68, 132, 42, 90, 37, 97, 160, 222, 198, 27, 138, 100, // D.*Z%a.....d - 119, 18, 36, 178, 201, 5, 209, 201, 182, 164, 57, 226, // w.$.......9. - 227, 88, 167, 163, 103, 22, 129, 5, 213, 133, 37, 234, // .X..g.....%. - 59, 28, 226, 38, 10, 121, 177, 151, 12, 184, 88, 15, // ;..&.y....X. - 80, 143, 129, 6, 128, 79, 1, 174, 108, 191, 227, 254, // P....O..l... - 67, 115, 51, 237, 89, 146, 124, 163, 3, 4, 253, 98, // Cs3.Y.|....b - 74, 191, 158, 116, 88, 171, 61, 252, 73, 15, 87, 163, // J..tX.=.I.W. - 31, 228, 57, 65, 208, 128, 143, 86, 10, 229, 227, 178, // ..9A...V.... - 67, 27, 48, 42, 223, 127, 11, 43, 175, 121, 16, 57, // C.0*...+.y.9 - 165, 199, 255, 29, 55, 153, 39, 17, 212, 21, 156, 196, // ....7.'..... - 170, 154, 12, 145, 61, 11, 65, 97, 27, 70, 15, 120, // ....=.Aa.F.x - 104, 252, 97, 216, 154, 227, 30, 143, 143, 27, 53, 200, // h.a.......5. - 29, 58, 60, 184, 134, 101, 156, 239, 5, 107, 83, 192, // .:<..e...kS. - 252, 111, 37, 40, 106, 230, 46, 65, 105, 135, 91, 5, // .o%(j..Ai.[. - 61, 221, 35, 232, 105, 42, 40, 6, 215, 151, 71, 61, // =.#.i*(...G= - 41, 163, 106, 71, 145, 179, 243, 83, 250, 244, 118, 156, // ).jG...S..v. - 96, 138, 209, 58, 221, 34, 167, 160, 85, 4, 157, 92, // `..:."..U... - 162, 177, 81, 39, 198, 212, 187, 162, 184, 242, 42, 147, // ..Q'......*. - 30, 166, 181, 238, 23, 122, 74, 161, 49, 193, 98, 121, // .....zJ.1.by - 142, 115, 135, 236, 67, 238, 119, 116, 237, 83, 237, 183, // .s..C.wt.S.. - 15, 158, 247, 108, 120, 11, 23, 62, 203, 207, 250, 232, // ...lx..>.... - 188, 27, 181, 44, 73, 119, 35, 199, 134, 128, 145, 95, // ...,Iw#...._ - 227, 20, 21, 105, 162, 131, 74, 195, 65, 189, 45, 178, // ...i..J.A.-. - 41, 236, 52, 45, 115, 43, 236, 224, 44, 145, 252, 224, // ).4-s+..,... - 162, 165, 94, 20, 130, 211, 184, 63, 60, 216, 121, 66, // ..^....?<.yB - 161, 106, 243, 8, 3, 96, 61, 107, 51, 77, 209, 0, // .j...`=k3M.. - 49, 222, 23, 145, 52, 77, 74, 8, 164, 205, 161, 47, // 1...4MJ..../ - 133, 75, 6, 130, 0, 206, 15, 200, 131, 173, 35, 198, // .K........#. - 162, 155, 227, 74, 111, 130, 72, 44, 66, 112, 204, 160, // ...Jo.H,Bp.. - 182, 181, 244, 167, 28, 203, 226, 221, 49, 201, 51, 252, // ........1.3. - 195, 8, 70, 144, 164, 123, 157, 199, 152, 69, 54, 25, // ..F..{...E6. - 67, 184, 103, 65, 241, 174, 15, 33, 15, 171, 20, 218, // C.gA...!.... - 141, 146, 84, 174, 114, 142, 240, 198, 40, 79, 65, 109, // ..T.r...(OAm - 187, 185, 181, 171, 118, 150, 131, 210, 188, 208, 20, 47, // ....v....../ - 199, 101, 73, 161, 41, 129, 88, 219, 163, 179, 201, 189, // .eI.).X..... - 171, 119, 220, 13, 87, 175, 202, 125, 113, 7, 119, 7, // .w..W..}q.w. - 184, 187, 11, 248, 60, 223, 192, 157, 197, 129, 119, 9, // ....<.....w. - 191, 2, 193, 61, 195, 243, 157, 155, 217, 223, 37, 253, // ...=......%. - 130, 73, 239, 221, 162, 27, 26, 43, 194, 17, 18, 66, // .I.....+...B - 131, 110, 196, 138, 230, 174, 129, 108, 143, 41, 98, 98, // .n.....l.)bb - 173, 164, 0, 123, 117, 112, 253, 134, 20, 5, 88, 172, // ...{up....X. - 141, 231, 103, 205, 52, 251, 209, 208, 123, 221, 224, 87, // ..g.4...{..W - 121, 206, 27, 124, 202, 21, 98, 26, 60, 65, 222, 206, // y..|..b._.. - 91, 214, 122, 57, 225, 235, 128, 239, 198, 164, 127, 154, // [.z9........ - 90, 209, 41, 204, 65, 251, 78, 135, 45, 216, 154, 171, // Z.).A.N.-... - 237, 42, 246, 57, 142, 153, 198, 151, 200, 103, 63, 92, // .*.9.....g?. - 108, 159, 48, 191, 95, 21, 175, 132, 117, 197, 221, 203, // l.0._...u... - 218, 208, 249, 233, 217, 221, 122, 18, 63, 248, 23, 65, // ......z.?..A - 16, 109, 227, 37, 113, 37, 129, 203, 17, 194, 109, 195, // .m.%q%....m. - 153, 8, 158, 22, 226, 230, 132, 63, 161, 240, 44, 32, // .......?.., - 64, 46, 135, 44, 7, 136, 120, 22, 150, 152, 69, 227, // @..,..x...E. - 80, 158, 13, 102, 121, 101, 1, 227, 25, 223, 129, 220, // P..fye...... - 211, 200, 231, 168, 7, 98, 60, 182, 140, 233, 87, 179, // .....b<...W. - 252, 87, 195, 0, 11, 173, 195, 122, 142, 126, 45, 201, // .W.....z.~-. - 253, 49, 185, 59, 37, 118, 5, 169, 38, 138, 59, 59, // .1.;%v..&.;; - 213, 65, 53, 152, 224, 16, 56, 118, 135, 32, 90, 195, // .A5...8v. Z. - 37, 2, 18, 169, 115, 164, 9, 108, 119, 204, 87, 228, // %...s..lw.W. - 223, 221, 129, 177, 59, 238, 48, 79, 175, 144, 79, 134, // ....;.0O..O. - 88, 172, 103, 187, 136, 232, 163, 60, 41, 118, 26, 238, // X.g....<)v.. - 43, 116, 114, 73, 188, 26, 17, 187, 179, 212, 226, 188, // +trI........ - 86, 166, 152, 231, 29, 107, 146, 165, 123, 203, 74, 49, // V....k..{.J1 - 158, 102, 168, 70, 87, 38, 131, 157, 209, 29, 15, 12, // .f.FW&...... - 145, 57, 11, 5, 89, 101, 2, 182, 123, 155, 197, 180, // .9..Ye..{... - 172, 74, 65, 47, 206, 225, 181, 11, 172, 251, 203, 151, // .JA/........ - 21, 75, 213, 202, 210, 167, 21, 77, 79, 209, 42, 195, // .K.....MO.*. - 76, 1, 219, 79, 76, 157, 227, 186, 68, 85, 146, 119, // L..OL...DU.w - 181, 110, 76, 44, 156, 49, 249, 90, 37, 175, 85, 238, // .nL,.1.Z%.U. - 51, 204, 89, 18, 27, 73, 176, 62, 110, 84, 48, 183, // 3.Y..I.>nT0. - 198, 167, 60, 5, 164, 216, 30, 225, 22, 18, 151, 17, // ..<......... - 136, 38, 179, 17, 114, 41, 145, 121, 81, 101, 97, 58, // .&..r).yQea: - 66, 250, 18, 89, 147, 44, 153, 12, 186, 66, 116, 91, // B..Y.,...Bt[ - 150, 164, 138, 194, 154, 232, 101, 92, 18, 129, 23, 141, // ......e..... - 33, 53, 214, 74, 47, 226, 20, 41, 189, 232, 21, 209, // !5.J/..).... - 224, 154, 72, 7, 143, 146, 70, 201, 81, 219, 14, 45, // ..H...F.Q..- - 137, 99, 26, 26, 199, 48, 44, 220, 215, 10, 99, 206, // .c...0,...c. - 132, 46, 42, 45, 205, 72, 98, 155, 134, 47, 254, 85, // ..*-.Hb../.U - 118, 238, 114, 101, 184, 171, 133, 225, 174, 111, 98, 184, // v.re.....ob. - 188, 206, 208, 89, 27, 174, 235, 27, 174, 247, 116, 11, // ...Y......t. - 195, 235, 19, 94, 47, 141, 167, 37, 204, 249, 22, 110, // ...^/..%...n - 235, 171, 220, 224, 41, 186, 2, 134, 238, 45, 220, 158, // ....)....-.. - 174, 27, 195, 53, 32, 228, 89, 222, 164, 186, 231, 235, // ...5 .Y..... - 170, 131, 192, 193, 55, 22, 215, 103, 138, 33, 228, 117, // ....7..g.!.u - 211, 174, 49, 198, 243, 110, 50, 4, 50, 188, 110, 9, // ..1..n2.2.n. - 100, 6, 75, 101, 237, 223, 196, 239, 250, 124, 33, 178, // d.Ke.....|!. - 125, 90, 24, 254, 53, 237, 97, 108, 123, 125, 182, 24, // }Z..5.al{}.. - 206, 185, 112, 166, 128, 207, 13, 252, 110, 48, 134, 7, // ..p.....n0.. - 214, 128, 141, 118, 205, 26, 218, 225, 231, 194, 100, 103, // ...v......dg - 206, 60, 23, 56, 222, 176, 117, 113, 198, 184, 117, 159, // .<.8..uq..u. - 174, 25, 68, 132, 198, 215, 247, 174, 3, 250, 3, 135, // ..D......... - 112, 117, 13, 50, 142, 55, 108, 55, 151, 110, 183, 219, // pu.2.7l7.n.. - 166, 124, 131, 81, 32, 6, 134, 21, 227, 93, 97, 167, // .|.Q ....]a. - 4, 215, 231, 237, 49, 23, 83, 207, 114, 227, 113, 245, // ....1.S.r.q. - 13, 210, 129, 121, 169, 61, 206, 51, 148, 21, 232, 157, // ...y.=.3.... - 86, 50, 110, 209, 58, 92, 137, 238, 109, 17, 117, 59, // V2n.:...m.u; - 178, 60, 210, 25, 128, 24, 227, 75, 9, 240, 210, 22, // .<.....K.... - 11, 167, 120, 238, 82, 84, 126, 185, 229, 187, 177, 128, // ..x.RT~..... - 15, 175, 241, 114, 28, 55, 52, 25, 212, 131, 15, 230, // ...r.74..... - 119, 7, 196, 153, 242, 176, 129, 197, 144, 177, 84, 104, // w.........Th - 30, 117, 126, 243, 84, 66, 62, 147, 127, 179, 28, 146, // .u~.TB>..... - 44, 45, 30, 238, 164, 38, 5, 111, 166, 172, 80, 83, // ,-...&.o..PS - 46, 182, 148, 170, 183, 89, 60, 67, 201, 81, 113, 52, // .....Y{1..Y}. - 204, 254, 245, 210, 112, 23, 149, 72, 249, 117, 26, 255, // ....p..H.u.. - 128, 254, 229, 20, 219, 20, 2, 102, 85, 3, 210, 209, // .......fU... - 106, 44, 169, 203, 165, 130, 125, 214, 164, 25, 220, 45, // j,....}....- - 109, 143, 115, 171, 55, 48, 157, 77, 174, 206, 178, 22, // m.s.70.M.... - 254, 120, 57, 195, 219, 177, 29, 150, 27, 193, 171, 80, // .x9........P - 246, 165, 20, 165, 164, 109, 165, 22, 162, 200, 106, 86, // .....m....jV - 5, 40, 234, 88, 21, 144, 172, 96, 85, 96, 67, 237, // .(.X...`U`C. - 170, 2, 100, 85, 171, 10, 64, 212, 165, 170, 32, 172, // ..dU..@... . - 72, 85, 218, 74, 13, 46, 38, 126, 212, 242, 91, 158, // HU.J..&~..[. - 248, 25, 85, 228, 226, 159, 135, 167, 242, 93, 167, 53, // ..U......].5 - 135, 38, 159, 188, 184, 119, 215, 224, 138, 54, 212, 254, // .&...w...6.. - 170, 90, 166, 213, 190, 115, 216, 25, 69, 77, 107, 126, // .Z...s..EMk~ - 231, 144, 179, 170, 211, 42, 127, 231, 48, 98, 25, 204, // .....*..0b.. - 160, 102, 244, 60, 46, 1, 126, 220, 252, 83, 13, 168, // .f.<..~..S.. - 6, 94, 237, 66, 11, 123, 187, 225, 167, 85, 2, 9, // .^.B.{...U.. - 140, 119, 10, 232, 126, 52, 245, 188, 165, 169, 222, 0, // .w..~4...... - 152, 122, 16, 197, 160, 152, 100, 52, 89, 70, 208, 228, // .z....d4YF.. - 50, 154, 131, 207, 54, 135, 42, 27, 102, 0, 243, 204, // 2...6.*.f... - 250, 220, 252, 109, 165, 250, 85, 75, 115, 139, 206, 154, // ...m..UKs... - 36, 67, 127, 184, 107, 115, 94, 175, 210, 110, 147, 200, // $C..ks^..n.. - 218, 146, 143, 132, 84, 15, 182, 111, 194, 3, 213, 51, // ....T..o...3 - 221, 71, 181, 75, 220, 242, 23, 40, 109, 119, 89, 171, // .G.K...(mwY. - 43, 196, 194, 247, 20, 231, 52, 0, 240, 95, 108, 76, // +.....4.._lL - 170, 169, 250, 151, 91, 156, 223, 86, 112, 209, 164, 55, // ....[..Vp..7 - 43, 179, 100, 158, 74, 181, 56, 71, 181, 0, 42, 18, // +.d.J.8G..*. - 214, 4, 54, 62, 190, 171, 215, 221, 47, 36, 239, 127, // ..6>..../$.. - 79, 103, 103, 232, 171, 188, 25, 246, 18, 140, 82, 89, // Ogg.......RY - 51, 239, 158, 125, 131, 228, 214, 165, 241, 190, 241, 80, // 3..}.......P - 7, 188, 191, 127, 9, 196, 133, 37, 28, 200, 3, 10, // .......%.... - 186, 245, 190, 102, 249, 12, 71, 115, 199, 240, 252, 179, // ...f..Gs.... - 183, 118, 195, 8, 60, 93, 119, 223, 0, 120, 25, 196, // .v..<]w..x.. - 238, 196, 206, 94, 54, 242, 17, 70, 41, 75, 117, 144, // ...^6..F)Ku. - 159, 147, 184, 84, 121, 139, 116, 227, 77, 188, 111, 74, // ...Ty.t.M.oJ - 58, 210, 215, 94, 95, 130, 15, 216, 32, 1, 253, 45, // :..^_... ..- - 95, 193, 240, 28, 137, 21, 47, 149, 98, 193, 31, 167, // _...../.b... - 18, 47, 201, 226, 225, 138, 191, 217, 79, 95, 154, 210, // ./......O_.. - 222, 63, 165, 245, 8, 26, 23, 30, 24, 59, 99, 78, // .?.......;cN - 162, 199, 48, 42, 59, 96, 112, 170, 95, 115, 244, 127, // ..0*;`p._s.. - 119, 220, 127, 227, 169, 243, 175, 57, 44, 81, 109, 89, // w......9,QmY - 111, 73, 157, 108, 83, 34, 117, 30, 140, 192, 82, 229, // oI.lS"u...R. - 236, 117, 88, 106, 190, 115, 61, 45, 239, 76, 95, 165, // .uXj.s=-.L_. - 222, 228, 76, 207, 177, 157, 39, 12, 52, 123, 79, 251, // ..L...'.4{O. - 203, 141, 52, 223, 159, 151, 59, 120, 203, 213, 130, 108, // ..4...;x...l - 177, 218, 138, 189, 242, 250, 18, 240, 226, 215, 188, 192, // ............ - 251, 160, 180, 56, 146, 120, 242, 62, 172, 130, 83, 123, // ...8.x.>..S{ - 10, 31, 132, 110, 78, 118, 185, 209, 75, 60, 163, 171, // ...nNv..K<.. - 195, 204, 217, 217, 235, 248, 209, 56, 116, 39, 187, 127, // .......8t'.. - 197, 56, 158, 15, 103, 48, 223, 191, 228, 239, 134, 129, // .8..g0...... - 198, 78, 99, 60, 212, 207, 242, 27, 191, 195, 139, 165, // .Nc<........ - 208, 120, 40, 43, 178, 35, 85, 205, 228, 179, 234, 232, // .x(+.#U..... - 64, 50, 18, 196, 97, 245, 250, 216, 217, 248, 245, 18, // @2..a....... - 168, 181, 60, 242, 238, 224, 122, 53, 204, 244, 18, 97, // ..<...z5...a - 90, 10, 195, 7, 216, 235, 153, 210, 235, 74, 156, 166, // Z........J.. - 76, 53, 13, 82, 206, 131, 246, 188, 27, 174, 1, 102, // L5.R.......f - 10, 93, 116, 103, 171, 176, 253, 86, 217, 88, 105, 137, // .]tg...V.Xi. - 225, 165, 152, 149, 239, 192, 1, 180, 179, 235, 236, 37, // ...........% - 152, 121, 103, 73, 128, 245, 247, 98, 16, 126, 203, 235, // .ygI...b.~.. - 13, 19, 58, 115, 2, 250, 123, 175, 205, 159, 153, 208, // ..:s..{..... - 84, 23, 252, 46, 115, 76, 122, 238, 109, 135, 205, 63, // T...sLz.m..? - 128, 66, 110, 169, 150, 31, 232, 206, 87, 204, 15, 52, // .Bn.....W..4 - 23, 170, 230, 145, 232, 206, 146, 120, 236, 50, 83, 226, // .......x.2S. - 194, 192, 43, 165, 168, 69, 49, 28, 110, 38, 79, 187, // ..+..E1.n&O. - 181, 159, 92, 195, 123, 99, 250, 219, 107, 2, 176, 203, // ....{c..k... - 197, 155, 240, 153, 221, 187, 94, 61, 209, 221, 155, 129, // ......^=.... - 251, 254, 25, 239, 71, 96, 247, 185, 82, 158, 153, 17, // ....G`..R... - 93, 199, 243, 233, 144, 233, 254, 37, 152, 125, 57, 18, // ]......%.}9. - 17, 7, 203, 239, 70, 101, 9, 8, 60, 90, 229, 123, // ....Fe....T ....K. + 54, 100, 175, 17, 70, 129, 193, 150, 160, 103, 54, 129, // 6d..F....g6. + 162, 173, 222, 198, 101, 43, 97, 216, 232, 109, 230, 154, // ....e+a..m.. + 198, 113, 144, 125, 192, 77, 194, 221, 155, 199, 213, 6, // .q.}.M...... + 48, 79, 192, 22, 28, 178, 236, 100, 54, 196, 19, 84, // 0O.....d6..T + 171, 1, 38, 64, 187, 22, 206, 175, 2, 10, 161, 24, // ..&@........ + 168, 8, 84, 115, 192, 32, 132, 192, 216, 180, 33, 177, // ..Ts. ....!. + 111, 135, 222, 62, 194, 46, 144, 254, 239, 104, 185, 46, // o..>.....h.. + 111, 121, 66, 147, 71, 48, 33, 119, 196, 188, 189, 20, // oyB.G0!w.... + 109, 33, 201, 17, 36, 17, 32, 1, 89, 123, 2, 244, // m!..$. .Y{.. + 196, 33, 207, 130, 200, 243, 57, 132, 10, 59, 132, 141, // .!....9..;.. + 76, 86, 14, 166, 63, 85, 20, 70, 152, 86, 157, 41, // LV..?U.F.V.) + 161, 38, 231, 131, 70, 128, 229, 132, 95, 129, 107, 184, // .&..F..._.k. + 6, 82, 83, 88, 125, 128, 128, 224, 21, 230, 167, 180, // .RSX}....... + 112, 150, 236, 215, 8, 234, 10, 232, 148, 26, 14, 141, // p........... + 71, 1, 197, 223, 212, 189, 134, 117, 82, 131, 223, 90, // G......uR..Z + 64, 72, 3, 158, 139, 226, 40, 44, 64, 8, 186, 95, // @H....(,@.._ + 22, 164, 146, 50, 196, 103, 95, 167, 31, 214, 197, 1, // ...2.g_..... + 209, 214, 162, 89, 231, 92, 236, 43, 34, 167, 105, 244, // ...Y...+".i. + 91, 138, 52, 155, 140, 212, 222, 194, 234, 97, 150, 205, // [.4......a.. + 163, 113, 14, 117, 122, 236, 109, 53, 130, 102, 38, 152, // .q.uz.m5.f&. + 196, 213, 22, 51, 197, 132, 210, 180, 85, 8, 60, 42, // ...3....U.<* + 228, 233, 94, 194, 31, 228, 208, 10, 233, 163, 57, 3, // ..^.......9. + 61, 61, 62, 26, 44, 244, 24, 250, 176, 54, 32, 48, // ==>.,....6 0 + 70, 255, 239, 1, 206, 2, 120, 14, 255, 147, 6, 71, // F.....x....G + 70, 52, 186, 81, 59, 176, 168, 94, 96, 254, 164, 99, // F4.Q;..^`..c + 160, 79, 175, 77, 102, 122, 186, 64, 11, 171, 52, 203, // .O.Mfz.@..4. + 41, 141, 220, 26, 35, 58, 85, 79, 167, 127, 36, 61, // )...#:UO..$= + 157, 166, 122, 58, 233, 122, 194, 199, 227, 148, 198, 86, // ..z:.z.....V + 212, 132, 154, 48, 181, 30, 127, 223, 42, 250, 221, 43, // ...0....*..+ + 57, 209, 51, 118, 109, 96, 126, 7, 194, 38, 101, 66, // 9.3vm`~..&eB + 92, 50, 23, 226, 227, 252, 177, 135, 160, 48, 79, 178, // .2.......0O. + 144, 70, 168, 64, 201, 26, 52, 198, 131, 166, 225, 214, // .F.@..4..... + 6, 62, 112, 194, 202, 72, 242, 29, 122, 15, 120, 114, // .>p..H..z.xr + 176, 243, 189, 197, 195, 78, 61, 15, 96, 179, 116, 27, // .....N=.`.t. + 139, 97, 69, 210, 166, 173, 169, 87, 66, 4, 203, 132, // .aE....WB... + 176, 12, 151, 14, 213, 1, 236, 105, 134, 238, 171, 6, // .......i.... + 71, 75, 127, 55, 36, 43, 169, 142, 89, 100, 86, 67, // GK.7$+..YdVC + 144, 90, 146, 176, 121, 112, 77, 112, 220, 224, 162, 33, // .Z..ypMp...! + 236, 118, 119, 21, 174, 146, 161, 171, 119, 185, 171, 55, // .vw.....w..7 + 233, 74, 253, 42, 80, 49, 7, 27, 39, 24, 207, 163, // .J.*P1..'... + 74, 88, 71, 142, 199, 64, 190, 147, 191, 2, 252, 3, // JXG..@...... + 241, 57, 102, 83, 196, 165, 130, 69, 19, 67, 42, 36, // .9fS...E.C*$ + 0, 8, 60, 216, 65, 146, 26, 142, 228, 97, 213, 116, // ..<.A....a.t + 52, 175, 65, 19, 84, 117, 192, 30, 0, 8, 22, 52, // 4.A.Tu.....4 + 200, 96, 66, 161, 240, 136, 8, 53, 129, 74, 194, 64, // .`B....5.J.@ + 189, 141, 151, 20, 201, 238, 36, 72, 100, 147, 11, 162, // ......$Hd... + 147, 109, 73, 115, 196, 7, 181, 78, 71, 143, 45, 2, // .mIs...NG.-. + 11, 74, 13, 75, 180, 68, 56, 132, 78, 20, 242, 98, // .J.K.D8.N..b + 47, 25, 112, 177, 30, 160, 30, 3, 13, 0, 159, 2, // /.p......... + 92, 217, 126, 199, 157, 137, 11, 129, 233, 213, 146, 228, // ..~......... + 27, 29, 32, 232, 23, 83, 250, 245, 164, 195, 90, 237, // .. ..S....Z. + 225, 79, 122, 184, 26, 253, 32, 207, 9, 194, 9, 124, // .Oz... ....| + 232, 82, 40, 31, 151, 157, 219, 128, 81, 249, 254, 91, // .R(.....Q..[ + 88, 147, 205, 131, 72, 43, 61, 254, 239, 184, 201, 124, // X...H+=....| + 140, 160, 174, 224, 48, 86, 213, 100, 8, 238, 89, 20, // ....0V.d..Y. + 10, 27, 52, 122, 192, 115, 227, 15, 195, 166, 29, 247, // ..4z.s...... + 120, 124, 220, 168, 113, 238, 208, 225, 193, 53, 44, 227, // x|..q....5,. + 124, 47, 88, 181, 2, 230, 127, 43, 65, 81, 51, 119, // |/X....+AQ3w + 9, 74, 59, 220, 42, 232, 233, 30, 65, 79, 83, 65, // .J;.*...AOSA + 49, 236, 190, 60, 234, 73, 25, 85, 59, 141, 156, 157, // 1..<.I.U;... + 159, 210, 167, 183, 227, 4, 179, 140, 214, 233, 22, 57, // ...........9 + 5, 173, 34, 232, 228, 30, 141, 141, 58, 49, 166, 222, // ..".....:1.. + 21, 197, 149, 183, 153, 244, 60, 173, 117, 191, 208, 83, // ......<.u..S + 10, 141, 57, 22, 203, 115, 156, 59, 100, 31, 210, 191, // ..9..s.;d... + 163, 155, 159, 106, 191, 125, 240, 188, 103, 195, 91, 184, // ...j.}..g.[. + 240, 89, 126, 214, 71, 231, 221, 168, 101, 73, 186, 27, // .Y~.G...eI.. + 57, 54, 4, 140, 252, 26, 167, 168, 72, 19, 29, 84, // 96......H..T + 26, 14, 234, 109, 145, 80, 97, 7, 106, 153, 94, 97, // ...m.Pa.j.^a + 103, 103, 137, 228, 71, 26, 45, 251, 162, 16, 156, 198, // gg..G.-..... + 253, 225, 145, 207, 115, 10, 85, 155, 71, 24, 26, 235, // ....s.U.G... + 137, 155, 105, 150, 6, 136, 241, 202, 136, 164, 105, 82, // ..i.......iR + 66, 136, 109, 14, 125, 41, 92, 50, 16, 4, 112, 178, // B.m.}).2..p. + 64, 30, 108, 29, 49, 22, 221, 28, 87, 122, 25, 68, // @.l.1...Wz.D + 98, 17, 156, 99, 18, 181, 173, 165, 63, 229, 88, 22, // b..c....?.X. + 9, 143, 73, 158, 225, 31, 198, 54, 130, 36, 221, 235, // ..I....6.$.. + 60, 198, 44, 178, 201, 24, 194, 61, 11, 138, 119, 125, // <.,....=..w} + 8, 121, 140, 165, 208, 110, 148, 167, 114, 149, 19, 134, // .y...n..r... + 55, 70, 121, 10, 106, 219, 205, 173, 93, 181, 179, 28, // 7Fy.j...]... + 148, 166, 134, 166, 120, 57, 46, 203, 11, 77, 9, 196, // ....x9...M.. + 218, 30, 157, 90, 238, 93, 189, 227, 110, 184, 122, 85, // ...Z.]..n.zU + 238, 139, 59, 184, 59, 192, 221, 93, 192, 231, 249, 6, // ..;.;..].... + 238, 44, 66, 188, 75, 248, 21, 8, 238, 25, 158, 239, // .,B.K....... + 220, 204, 254, 46, 233, 23, 76, 122, 239, 22, 221, 208, // ......Lz.... + 40, 18, 14, 151, 16, 26, 116, 35, 86, 52, 125, 13, // (.....t#V4}. + 100, 123, 204, 18, 19, 107, 37, 5, 216, 171, 131, 235, // d{...k%..... + 151, 164, 40, 192, 98, 109, 60, 63, 107, 166, 217, 143, // ..(.bm.b.r/p}K + 138, 148, 52, 119, 41, 1, 22, 151, 183, 128, 197, 189, // ..4w)....... + 244, 47, 240, 165, 78, 250, 118, 115, 129, 78, 217, 231, // ./..N.vs.N.. + 2, 203, 19, 60, 20, 224, 169, 115, 239, 50, 240, 159, // ...<...s.2.. + 13, 247, 249, 18, 95, 222, 178, 214, 203, 9, 95, 7, // ...._....._. + 124, 55, 230, 253, 211, 212, 138, 78, 97, 14, 218, 119, // |7.....Na..w + 58, 108, 193, 214, 92, 109, 87, 177, 207, 113, 204, 52, // :l...mW..q.4 + 190, 68, 62, 251, 225, 98, 251, 132, 41, 254, 170, 120, // .D>..b..)..x + 37, 172, 43, 238, 94, 214, 134, 206, 79, 207, 238, 214, // %.+.^...O... + 147, 248, 193, 191, 8, 130, 104, 27, 47, 137, 43, 9, // ......h./.+. + 92, 142, 16, 110, 27, 206, 68, 240, 180, 16, 151, 39, // ...n..D....' + 252, 9, 133, 103, 1, 1, 114, 57, 100, 57, 64, 196, // ...g..r9d9@. + 179, 176, 196, 252, 26, 135, 242, 132, 48, 75, 45, 11, // ........0K-. + 24, 79, 250, 14, 228, 158, 70, 62, 71, 61, 16, 227, // .O....F>G=.. + 177, 101, 76, 191, 154, 229, 191, 26, 6, 88, 104, 29, // .eL......Xh. + 214, 115, 244, 107, 73, 238, 143, 201, 221, 41, 177, 43, // .s.kI....).+ + 72, 53, 81, 220, 217, 169, 14, 170, 193, 212, 135, 192, // H5Q......... + 177, 107, 4, 209, 26, 238, 17, 144, 72, 157, 35, 77, // .k......H.#M + 109, 187, 99, 190, 34, 5, 239, 14, 140, 221, 113, 135, // m.c.".....q. + 121, 122, 133, 124, 50, 196, 98, 61, 219, 69, 68, 31, // yz.|2.b=.ED. + 229, 73, 177, 211, 112, 101, 161, 147, 75, 226, 213, 136, // .I..pe..K... + 216, 157, 165, 22, 231, 181, 50, 197, 12, 240, 88, 147, // ......2...X. + 44, 17, 92, 86, 138, 241, 52, 67, 53, 186, 50, 25, // ,..V..4C5.2. + 236, 140, 238, 120, 96, 136, 204, 89, 40, 200, 138, 19, // ...x`..Y(... + 176, 221, 219, 44, 166, 101, 133, 10, 122, 125, 14, 47, // ...,.e..z}./ + 95, 96, 221, 95, 190, 172, 88, 18, 87, 86, 63, 173, // _`._..X.WV?. + 104, 226, 138, 22, 26, 102, 10, 216, 126, 98, 234, 28, // h....f..~b.. + 151, 38, 170, 146, 188, 171, 165, 99, 98, 225, 140, 201, // .&.....cb... + 215, 42, 121, 173, 114, 159, 97, 206, 210, 219, 72, 130, // .*y.r.a...H. + 37, 114, 163, 154, 185, 53, 62, 229, 41, 32, 197, 246, // %r...5>.) .. + 8, 183, 144, 184, 140, 64, 52, 153, 141, 144, 75, 137, // .....@4...K. + 204, 139, 42, 11, 211, 17, 210, 151, 200, 154, 100, 201, // ..*.......d. + 100, 208, 21, 162, 219, 178, 36, 85, 20, 214, 68, 175, // d.....$U..D. + 228, 146, 8, 188, 107, 12, 169, 177, 86, 122, 29, 167, // ....k...Vz.. + 72, 246, 69, 175, 136, 6, 215, 68, 58, 120, 148, 52, // H.E....D:x.4 + 74, 246, 218, 118, 104, 85, 28, 211, 208, 56, 134, 97, // J..vhU...8.a + 225, 190, 86, 27, 115, 38, 116, 81, 105, 105, 174, 18, // ..V.s&tQii.. + 219, 52, 124, 241, 175, 178, 115, 151, 43, 195, 93, 45, // .4|...s.+.]- + 12, 119, 125, 19, 195, 229, 117, 134, 206, 218, 112, 93, // .w}...u...p] + 223, 112, 189, 167, 91, 24, 94, 159, 240, 122, 105, 60, // .p..[.^..zi< + 45, 97, 206, 183, 112, 91, 95, 229, 6, 79, 209, 21, // -a..p[_..O.. + 48, 116, 111, 225, 246, 116, 221, 24, 174, 1, 33, 207, // 0to..t....!. + 242, 38, 213, 61, 95, 87, 29, 4, 14, 190, 177, 184, // .&.=_W...... + 62, 83, 12, 33, 175, 155, 118, 141, 49, 158, 119, 147, // >S.!..v.1.w. + 33, 144, 225, 117, 75, 32, 51, 88, 42, 107, 255, 38, // !..uK 3X*k.& + 126, 215, 231, 11, 145, 237, 211, 194, 240, 175, 105, 15, // ~.........i. + 99, 219, 235, 179, 197, 112, 206, 133, 51, 5, 124, 110, // c....p..3.|n + 224, 119, 131, 49, 60, 176, 6, 108, 180, 107, 214, 208, // .w.1<..l.k.. + 14, 63, 23, 38, 59, 115, 230, 185, 192, 241, 134, 173, // .?.&;s...... + 139, 51, 198, 173, 251, 116, 205, 32, 34, 52, 190, 190, // .3...t. "4.. + 119, 29, 208, 31, 56, 132, 171, 107, 144, 113, 188, 97, // w...8..k.q.a + 187, 185, 116, 187, 221, 54, 229, 27, 140, 2, 49, 48, // ..t..6....10 + 172, 24, 239, 10, 59, 37, 184, 62, 111, 143, 185, 152, // ....;%.>o... + 122, 150, 27, 143, 171, 111, 144, 14, 204, 75, 237, 113, // z....o...K.q + 158, 161, 44, 66, 239, 180, 170, 113, 139, 150, 226, 74, // ..,B...q...J + 116, 111, 139, 168, 219, 145, 21, 146, 206, 0, 196, 24, // to.......... + 95, 74, 128, 215, 185, 88, 59, 197, 115, 151, 162, 248, // _J...X;.s... + 203, 45, 223, 141, 5, 124, 120, 153, 151, 227, 184, 161, // .-...|x..... + 201, 160, 30, 124, 48, 191, 59, 32, 206, 84, 136, 13, // ...|0.; .T.. + 44, 134, 140, 165, 66, 243, 168, 243, 155, 167, 18, 242, // ,...B....... + 153, 252, 155, 229, 144, 100, 117, 241, 112, 91, 53, 169, // .....du.p[5. + 121, 51, 101, 145, 154, 114, 229, 165, 20, 190, 205, 226, // y3e..r...... + 25, 74, 142, 138, 163, 105, 138, 113, 64, 84, 119, 137, // .J...i.q@Tw. + 66, 47, 84, 37, 0, 55, 31, 218, 43, 68, 248, 183, // B/T%.7..+D.. + 104, 103, 204, 231, 172, 138, 70, 76, 47, 171, 8, 131, // hg....FL/... + 176, 169, 21, 61, 213, 138, 78, 124, 193, 88, 222, 5, // ...=..N|.X.. + 99, 109, 254, 63, 116, 94, 41, 55, 251, 51, 53, 140, // cm.?t^)7.35. + 99, 134, 180, 130, 235, 209, 112, 232, 255, 89, 145, 232, // c.....p..Y.. + 121, 238, 113, 30, 199, 103, 165, 203, 119, 105, 12, 154, // y.q..g..wi.. + 204, 190, 48, 144, 62, 146, 48, 14, 155, 149, 201, 190, // ..0.>.0..... + 221, 27, 23, 239, 188, 250, 38, 74, 83, 244, 217, 139, // ......&JS... + 1, 126, 205, 234, 195, 101, 246, 175, 151, 134, 187, 168, // .~...e...... + 68, 202, 175, 211, 248, 7, 244, 47, 167, 216, 166, 16, // D....../.... + 48, 171, 26, 144, 142, 86, 99, 73, 93, 46, 21, 236, // 0....VcI]... + 179, 38, 205, 224, 110, 105, 123, 156, 91, 189, 129, 233, // .&..ni{.[... + 108, 114, 117, 150, 181, 240, 199, 203, 25, 222, 142, 237, // lru......... + 176, 220, 8, 94, 133, 178, 47, 165, 92, 37, 109, 43, // ...^../..%m+ + 181, 68, 69, 22, 180, 42, 64, 81, 202, 170, 128, 100, // .DE..*@Q...d + 17, 171, 2, 27, 202, 87, 21, 32, 43, 92, 85, 0, // .....W. +.U. + 162, 52, 85, 5, 97, 81, 170, 210, 86, 202, 112, 49, // .4U.aQ..V.p1 + 241, 163, 86, 224, 242, 196, 207, 168, 40, 23, 255, 60, // ..V.....(..< + 60, 149, 239, 58, 173, 57, 52, 249, 228, 197, 141, 188, // <..:.94..... + 6, 87, 180, 161, 246, 87, 213, 50, 45, 248, 157, 195, // .W...W.2-... + 206, 40, 106, 90, 246, 59, 135, 156, 85, 157, 86, 252, // .(jZ.;..U.V. + 59, 135, 17, 203, 96, 6, 53, 163, 231, 113, 21, 240, // ;...`.5..q.. + 227, 230, 159, 106, 64, 53, 240, 58, 24, 90, 219, 219, // ...j@5.:.Z.. + 13, 63, 173, 18, 72, 96, 188, 83, 64, 247, 163, 169, // .?..H`.S@... + 231, 45, 77, 245, 6, 192, 212, 131, 40, 6, 197, 36, // .-M.....(..$ + 163, 201, 50, 130, 38, 151, 209, 28, 124, 182, 57, 212, // ..2.&...|.9. + 223, 48, 3, 152, 103, 214, 231, 230, 111, 43, 213, 175, // .0..g...o+.. + 90, 154, 91, 116, 214, 36, 25, 250, 195, 93, 155, 243, // Z.[t.$...].. + 74, 150, 118, 155, 68, 214, 150, 124, 36, 164, 122, 176, // J.v.D..|$.z. + 125, 19, 30, 168, 158, 233, 62, 170, 93, 226, 150, 191, // }.....>.]... + 67, 105, 187, 203, 90, 93, 33, 22, 190, 170, 56, 167, // Ci..Z]!...8. + 1, 128, 255, 98, 99, 82, 77, 213, 191, 220, 226, 252, // ...bcRM..... + 182, 130, 139, 38, 189, 89, 153, 37, 243, 84, 170, 197, // ...&.Y.%.T.. + 57, 170, 5, 80, 145, 176, 38, 176, 241, 241, 117, 189, // 9..P..&...u. + 238, 126, 33, 121, 255, 123, 58, 59, 67, 95, 229, 229, // .~!y.{:;C_.. + 176, 151, 96, 148, 202, 154, 121, 253, 236, 27, 36, 183, // ..`...y...$. + 46, 141, 247, 141, 135, 58, 224, 253, 253, 75, 32, 46, // .....:...K . + 44, 225, 64, 30, 80, 208, 173, 247, 53, 203, 103, 56, // ,.@.P...5.g8 + 154, 59, 134, 231, 159, 189, 181, 27, 70, 224, 233, 186, // .;......F... + 251, 6, 192, 203, 32, 118, 39, 118, 246, 178, 145, 143, // .... v'v.... + 48, 74, 89, 170, 131, 252, 156, 196, 165, 202, 91, 164, // 0JY.......[. + 27, 111, 226, 125, 83, 210, 145, 190, 249, 250, 18, 124, // .o.}S......| + 192, 6, 9, 232, 111, 249, 22, 134, 231, 72, 172, 120, // ....o....H.x + 175, 20, 75, 1, 57, 149, 120, 79, 22, 15, 87, 252, // ..K.9.xO..W. + 229, 126, 250, 222, 148, 246, 10, 42, 173, 71, 208, 184, // .~.....*.G.. + 240, 192, 216, 25, 115, 18, 61, 134, 81, 217, 1, 131, // ....s.=.Q... + 83, 253, 154, 163, 255, 187, 227, 254, 27, 79, 157, 127, // S........O.. + 205, 97, 137, 106, 203, 122, 75, 234, 100, 155, 18, 169, // .a.j.zK.d... + 243, 96, 4, 150, 42, 103, 111, 196, 82, 243, 157, 235, // .`..*go.R... + 105, 121, 103, 250, 42, 245, 38, 103, 122, 142, 237, 60, // iyg.*.&gz..< + 97, 160, 217, 123, 218, 95, 110, 164, 249, 254, 188, 220, // a..{._n..... + 193, 91, 174, 22, 100, 139, 213, 86, 236, 173, 215, 151, // .[..d..V.... + 128, 151, 197, 230, 5, 222, 7, 165, 197, 145, 196, 147, // ............ + 87, 98, 21, 156, 218, 83, 248, 32, 116, 115, 178, 203, // Wb...S. ts.. + 141, 94, 226, 25, 93, 29, 102, 206, 206, 94, 199, 143, // .^..].f..^.. + 198, 161, 59, 217, 253, 43, 198, 241, 124, 56, 131, 249, // ..;..+..|8.. + 254, 37, 127, 55, 12, 52, 118, 26, 227, 161, 126, 150, // .%.7.4v...~. + 223, 248, 29, 94, 44, 133, 198, 67, 89, 145, 29, 169, // ...^,..CY... + 106, 38, 159, 85, 71, 7, 146, 145, 32, 14, 171, 215, // j&.UG... ... + 199, 206, 198, 175, 151, 64, 173, 229, 145, 119, 7, 215, // .....@...w.. + 171, 97, 166, 151, 8, 211, 82, 24, 62, 192, 94, 207, // .a....R.>.^. + 148, 94, 87, 226, 52, 101, 170, 105, 144, 114, 30, 180, // .^W.4e.i.r.. + 231, 221, 112, 13, 48, 83, 232, 162, 59, 91, 133, 237, // ..p.0S..;[.. + 183, 202, 198, 74, 75, 12, 175, 203, 172, 124, 7, 14, // ...JK....|.. + 160, 157, 93, 103, 47, 193, 204, 219, 76, 2, 172, 191, // ..]g/...L... + 49, 131, 240, 91, 94, 124, 152, 208, 153, 19, 208, 223, // 1..[^|...... + 123, 213, 254, 204, 132, 166, 186, 224, 119, 153, 99, 210, // {.......w.c. + 115, 239, 65, 108, 254, 1, 20, 114, 75, 181, 252, 64, // s.Al...rK..@ + 119, 190, 98, 126, 160, 185, 80, 53, 143, 68, 119, 150, // w.b~..P5.Dw. + 196, 99, 151, 153, 18, 23, 6, 94, 41, 69, 45, 138, // .c.....^)E-. + 225, 112, 51, 121, 218, 173, 253, 228, 26, 222, 27, 211, // .p3y........ + 223, 94, 19, 128, 93, 46, 222, 132, 207, 236, 222, 245, // .^..]....... + 234, 137, 238, 222, 12, 220, 247, 207, 120, 115, 2, 187, // ........xs.. + 207, 149, 242, 204, 140, 232, 58, 158, 79, 135, 76, 247, // ......:.O.L. + 47, 193, 236, 107, 147, 136, 56, 88, 126, 55, 42, 75, // /..k..8X~7*K + 64, 224, 209, 42, 223, 59, 165, 44, 20, 97, 63, 67, // @..*.;.,.a?C + 232, 113, 119, 255, 114, 119, 127, 190, 59, 125, 217, 96, // .qw.rw..;}.` + 53, 125, 217, 96, 242, 62, 131, 88, 7, 116, 214, 191, // 5}.`.>.X.t.. + 68, 101, 99, 255, 127, 68, 49, 210, 39, 101, 75, 0, // Dec..D1.'eK. + 0, 0 // . }; static const unsigned char v6[] = { - 31, 139, 8, 8, 119, 212, 245, 100, 0, 3, 109, 97, // ....w..d..ma - 105, 110, 46, 106, 115, 0, 181, 91, 255, 114, 219, 70, // in.js..[.r.F - 146, 254, 223, 79, 49, 225, 122, 77, 210, 33, 64, 130, // ...O1.zM.!@. - 164, 126, 152, 150, 148, 114, 28, 39, 242, 150, 99, 187, // .~...r.'..c. - 44, 217, 119, 89, 149, 42, 1, 137, 33, 137, 8, 4, // ,.wY.*..!... - 80, 0, 40, 146, 171, 229, 59, 221, 51, 220, 147, 221, // P.(...;.3... - 215, 61, 3, 96, 64, 66, 178, 178, 201, 37, 10, 3, // .=.`@B...%.. - 204, 143, 158, 158, 158, 158, 238, 175, 123, 6, 221, 174, // ........{... - 16, 239, 63, 92, 190, 25, 137, 87, 31, 223, 138, 137, // ..?....W.... - 27, 4, 169, 88, 44, 211, 76, 164, 153, 155, 100, 98, // ...X,.L...db - 229, 103, 115, 209, 116, 99, 191, 219, 20, 126, 40, 162, // .gs.tc...~(. - 196, 147, 137, 200, 34, 145, 202, 228, 86, 138, 108, 46, // ...."...V.l. - 133, 27, 199, 194, 205, 132, 27, 110, 196, 231, 79, 111, // .......n..Oo - 159, 60, 105, 46, 83, 137, 206, 137, 63, 201, 154, 47, // .....?.A.z - 63, 250, 129, 84, 84, 114, 30, 192, 194, 36, 194, 184, // ?..TTr...$.. - 161, 12, 179, 84, 177, 241, 132, 70, 207, 104, 244, 72, // ...T...F.h.H - 156, 138, 56, 137, 226, 84, 156, 158, 241, 12, 126, 59, // ..8..T....~; - 73, 111, 103, 98, 18, 184, 105, 122, 250, 244, 142, 171, // Iogb..iz.... - 108, 126, 219, 138, 245, 34, 8, 211, 211, 198, 60, 203, // l~..."....<. - 226, 81, 183, 187, 90, 173, 236, 213, 192, 142, 146, 89, // .Q..Z......Y - 183, 223, 235, 245, 186, 232, 214, 16, 183, 190, 92, 125, // ...........} - 31, 173, 79, 27, 61, 209, 19, 78, 223, 62, 62, 226, // ..O.=..N.>>. - 223, 131, 198, 217, 137, 39, 167, 233, 217, 73, 154, 109, // .....'...I.m - 2, 121, 102, 7, 129, 53, 9, 82, 203, 185, 155, 250, // .yf..5.R.... - 65, 48, 10, 193, 219, 75, 8, 62, 186, 145, 163, 191, // A0...K.>.... - 129, 150, 126, 182, 22, 62, 68, 25, 248, 248, 223, 200, // ..~..>D..... - 41, 10, 87, 190, 151, 205, 71, 61, 251, 32, 94, 191, // ).W...G=. ^. - 220, 158, 116, 21, 193, 147, 174, 34, 63, 19, 190, 119, // ..t...."?..w - 218, 120, 231, 110, 100, 242, 107, 191, 33, 60, 55, 115, // .x.nd.k.!<7s - 173, 208, 93, 72, 93, 38, 250, 141, 106, 27, 199, 170, // ..]H]&..j... - 107, 229, 160, 85, 236, 66, 125, 148, 24, 26, 57, 183, // k..U.B}...9. - 104, 122, 218, 248, 25, 19, 58, 236, 119, 28, 251, 184, // hz....:.w... - 255, 229, 216, 126, 225, 188, 114, 236, 131, 227, 14, 255, // ...~..r..... - 244, 240, 175, 211, 113, 240, 215, 179, 135, 199, 231, 67, // ....q......C - 215, 177, 135, 195, 14, 255, 112, 149, 229, 88, 246, 224, // ......p..X.. - 232, 149, 125, 248, 2, 127, 186, 53, 164, 131, 38, 189, // ..}....5..&. - 192, 178, 81, 233, 244, 93, 251, 24, 148, 29, 221, 222, // ..Q..]...... - 118, 14, 44, 80, 250, 114, 96, 31, 28, 161, 230, 8, // v.,P.r`..... - 127, 186, 155, 125, 124, 104, 225, 191, 243, 161, 125, 52, // ...}|h....}4 - 248, 114, 100, 247, 143, 81, 125, 136, 63, 174, 238, 117, // .rd..Q}.?..u - 248, 229, 224, 252, 133, 61, 36, 138, 7, 248, 43, 42, // .....=$...+* - 64, 241, 248, 224, 203, 192, 30, 30, 188, 170, 116, 1, // @.........t. - 207, 206, 160, 51, 232, 216, 71, 135, 248, 43, 10, 153, // ...3..G..+.. - 65, 215, 238, 191, 192, 159, 42, 36, 54, 241, 31, 205, // A.....*$6... - 235, 133, 250, 81, 197, 60, 185, 243, 190, 61, 120, 241, // ...Q.<...=x. - 133, 132, 67, 114, 57, 234, 240, 143, 98, 121, 216, 177, // ..Cr9...by.. - 251, 7, 231, 142, 179, 87, 81, 10, 244, 159, 141, 238, // .....WQ..... - 131, 146, 39, 177, 42, 225, 126, 113, 246, 5, 143, 213, // ..'.*.~q.... - 96, 90, 231, 181, 195, 99, 112, 44, 13, 38, 254, 98, // `Z...cp,.&.b - 184, 207, 1, 58, 96, 162, 96, 31, 52, 220, 234, 196, // ...:`.`.4... - 28, 234, 59, 56, 10, 48, 107, 219, 113, 13, 233, 160, // ..;8.0k.q... - 216, 113, 108, 103, 104, 72, 145, 138, 134, 246, 240, 40, // .qlghH.....( - 95, 145, 66, 240, 142, 165, 94, 64, 223, 25, 228, 203, // _.B...^@.... - 89, 10, 223, 82, 203, 114, 120, 78, 203, 98, 46, 52, // Y..R.rxN.b.4 - 213, 80, 179, 47, 88, 73, 83, 53, 176, 144, 206, 1, // .P./XIS5.... - 196, 0, 174, 88, 105, 74, 141, 162, 154, 129, 237, 56, // ...XiJ.....8 - 157, 170, 238, 245, 212, 44, 88, 192, 221, 153, 254, 15, // .....,X..... - 91, 246, 236, 55, 88, 131, 233, 50, 156, 144, 13, 17, // [..7X..2.... - 231, 210, 133, 157, 107, 221, 5, 48, 12, 75, 101, 218, // ....k..0.Ke. - 96, 245, 82, 153, 93, 204, 163, 213, 133, 239, 201, 177, // `.R.]....... - 75, 239, 229, 203, 182, 45, 238, 158, 8, 216, 199, 108, // K....-.....l - 153, 132, 202, 126, 60, 57, 241, 252, 219, 124, 253, 198, // ...~<9...|.. - 51, 107, 53, 199, 46, 134, 89, 245, 39, 55, 27, 216, // 3k5...Y.'7.. - 223, 216, 234, 137, 127, 89, 87, 195, 227, 107, 177, 94, // .....YW..k.^ - 89, 211, 101, 16, 136, 49, 219, 102, 107, 44, 226, 141, // Y.e..1.fk,.. - 213, 23, 79, 239, 12, 250, 226, 217, 51, 209, 140, 3, // ..O.....3... - 235, 168, 223, 220, 138, 44, 113, 195, 212, 39, 70, 45, // .....,q..'F- - 24, 123, 225, 45, 19, 182, 124, 214, 160, 215, 83, 117, // .{.-..|...Su - 211, 40, 89, 52, 206, 192, 143, 201, 66, 188, 6, 81, // .(Y4....B..Q - 61, 18, 6, 232, 137, 5, 253, 76, 3, 185, 22, 96, // =......L...` - 108, 145, 90, 19, 216, 69, 153, 112, 63, 244, 28, 43, // l.Z..E.p?..+ - 219, 153, 109, 98, 152, 3, 245, 210, 16, 81, 56, 9, // ..mb.....Q8. - 192, 63, 12, 162, 188, 37, 67, 89, 149, 72, 139, 203, // .?...%CY.H.. - 190, 185, 109, 111, 243, 49, 51, 185, 206, 172, 52, 128, // ..mo.13...4. - 167, 176, 134, 189, 158, 38, 13, 226, 79, 239, 216, 238, // .....&..O... - 219, 232, 148, 14, 138, 214, 115, 235, 176, 33, 186, 122, // ......s..!.z - 252, 110, 254, 96, 76, 129, 153, 165, 31, 203, 17, 51, // .n.`L......3 - 55, 182, 214, 214, 16, 44, 4, 83, 11, 38, 81, 102, // 7....,.S.&Qf - 147, 185, 8, 102, 35, 85, 126, 88, 142, 101, 244, 79, // ...f#U~X.e.O - 36, 88, 241, 225, 245, 12, 66, 48, 113, 249, 80, 53, // $X....B0q.P5 - 131, 153, 146, 41, 134, 172, 25, 5, 93, 211, 216, 13, // ...)....]... - 171, 243, 94, 136, 221, 249, 67, 159, 102, 210, 35, 55, // ..^...C.f.#7 - 236, 166, 35, 172, 48, 233, 213, 214, 24, 191, 202, 193, // ..#.0....... - 220, 247, 60, 25, 210, 112, 227, 32, 154, 220, 208, 195, // ..<..p. .... - 92, 13, 191, 178, 226, 53, 151, 207, 172, 89, 226, 66, // .....5...Y.B - 91, 64, 92, 184, 137, 239, 90, 170, 15, 56, 72, 150, // [@....Z..8H. - 178, 50, 53, 150, 186, 114, 136, 208, 32, 63, 11, 200, // .25..r.. ?.. - 202, 179, 122, 55, 132, 143, 197, 56, 205, 215, 68, 233, // ..z7...8..D. - 252, 214, 88, 235, 188, 164, 20, 83, 183, 186, 74, 252, // ..X....S..J. - 63, 250, 193, 38, 218, 154, 251, 40, 87, 140, 187, 101, // ?..&...(W..e - 18, 168, 253, 162, 55, 138, 242, 187, 239, 221, 219, 119, // ....7......w - 126, 120, 3, 215, 219, 186, 99, 150, 58, 204, 10, 32, // ~x....c.:.. - 68, 66, 64, 2, 157, 208, 60, 247, 199, 90, 157, 245, // DB@...<..Z.. - 192, 46, 183, 57, 109, 252, 237, 233, 29, 61, 108, 27, // ...9m....=l. - 185, 208, 212, 187, 56, 61, 165, 238, 226, 59, 209, 132, // ....8=...;.. - 140, 212, 10, 28, 244, 212, 122, 140, 131, 165, 180, 14, // ......z..... - 177, 87, 102, 73, 180, 140, 155, 98, 36, 154, 92, 206, // .WfI...b$... - 146, 60, 66, 249, 60, 186, 149, 201, 168, 218, 86, 149, // ..;o.*)u.. - 148, 1, 32, 121, 214, 194, 19, 49, 118, 91, 174, 6, // .. y...1v[.. - 1, 204, 9, 96, 141, 117, 40, 166, 81, 136, 18, 185, // ...`.u(.Q... - 240, 199, 81, 224, 153, 251, 129, 230, 93, 108, 132, 21, // ..Q.....]l.. - 90, 210, 102, 40, 4, 254, 84, 137, 103, 155, 11, 189, // Z.f(..T.g... - 144, 58, 4, 254, 176, 213, 185, 245, 163, 64, 102, 150, // .:.......@f. - 67, 83, 72, 45, 154, 68, 224, 110, 140, 71, 43, 138, // CSH-.D.n.G+. - 101, 56, 98, 171, 193, 66, 90, 91, 189, 66, 97, 232, // e8b..BZ[.Ba. - 31, 203, 172, 98, 219, 241, 40, 235, 83, 161, 49, 245, // ...b..(.S.1. - 215, 80, 122, 101, 242, 2, 57, 205, 240, 191, 113, 4, // .Pze..9...q. - 69, 92, 40, 19, 120, 216, 187, 134, 93, 58, 234, 139, // E.(.x...]:.. - 194, 72, 106, 67, 152, 84, 168, 232, 194, 92, 223, 5, // .HjC.T...... - 77, 96, 26, 68, 43, 107, 99, 185, 75, 194, 179, 147, // M`.D+kc.K... - 36, 10, 2, 104, 155, 181, 169, 244, 83, 118, 148, 13, // $..h....Sv.. - 104, 101, 154, 88, 187, 196, 159, 205, 51, 213, 59, 103, // he.X....3.;g - 104, 207, 108, 150, 166, 98, 18, 5, 98, 129, 253, 71, // h.l..b..b..G - 139, 189, 41, 246, 254, 94, 219, 57, 132, 13, 85, 79, // ..)..^.9..UO - 160, 216, 152, 95, 173, 1, 97, 45, 32, 13, 80, 26, // ..._..a- .P. - 178, 14, 76, 67, 113, 80, 53, 148, 180, 71, 13, 11, // ..LCqP5..G.. - 73, 75, 0, 189, 16, 191, 68, 203, 68, 124, 143, 249, // IK....D.D|.. - 120, 143, 51, 151, 249, 12, 76, 210, 122, 251, 21, 230, // x.3...L.z... - 224, 7, 55, 157, 143, 35, 55, 241, 118, 44, 194, 60, // ..7..#7.v,.< - 90, 200, 173, 222, 111, 221, 6, 237, 43, 84, 209, 230, // Z...o...+T.. - 52, 44, 194, 62, 53, 141, 229, 211, 29, 98, 169, 46, // 4,.>5....b.. - 46, 8, 166, 69, 187, 71, 18, 254, 209, 79, 22, 43, // ...E.G...O.+ - 55, 145, 0, 250, 64, 172, 114, 135, 190, 23, 173, 66, // 7...@.r....B - 194, 255, 5, 253, 165, 110, 245, 72, 234, 111, 110, 41, // .....n.H.on) - 60, 216, 33, 234, 6, 50, 201, 10, 138, 82, 55, 217, // <.!..2...R7. - 167, 248, 53, 123, 168, 136, 183, 238, 42, 86, 240, 74, // ..5{....*V.J - 209, 99, 124, 161, 26, 92, 195, 34, 230, 33, 86, 235, // .c|....".!V. - 234, 186, 253, 178, 108, 27, 187, 51, 201, 45, 17, 241, // ....l..3.-.. - 200, 74, 59, 7, 205, 138, 118, 96, 52, 145, 233, 156, // .J;...v`4... - 44, 43, 89, 80, 102, 110, 74, 46, 178, 197, 145, 160, // ,+YPfnJ..... - 26, 177, 59, 147, 89, 179, 195, 156, 168, 127, 22, 50, // ..;.Y......2 - 155, 71, 30, 236, 225, 199, 15, 23, 151, 168, 25, 71, // .G.........G - 222, 102, 36, 254, 113, 241, 225, 189, 77, 225, 96, 56, // .f$.q...M.`8 - 243, 167, 155, 214, 29, 177, 48, 18, 244, 187, 109, 119, // ......0...mw - 116, 223, 109, 219, 70, 36, 25, 182, 18, 50, 215, 9, // t.m.F$...2.. - 130, 171, 40, 108, 181, 219, 5, 97, 163, 178, 152, 100, // ..(l...a...d - 43, 105, 43, 142, 139, 16, 178, 165, 185, 238, 168, 105, // +i+........i - 94, 239, 86, 243, 84, 52, 187, 90, 2, 45, 102, 46, // ^.V.T4.Z.-f. - 6, 140, 144, 45, 248, 72, 55, 184, 200, 162, 4, 229, // ...-.H7..... - 54, 166, 246, 22, 91, 175, 213, 36, 74, 205, 118, 155, // 6...[..$J.v. - 101, 200, 156, 130, 250, 131, 148, 43, 100, 210, 10, 153, // e......+d... - 14, 207, 218, 206, 162, 11, 150, 70, 75, 81, 221, 86, // .......FKQ.V - 249, 85, 11, 112, 57, 223, 15, 40, 1, 231, 211, 73, // .U.p9..(...I - 68, 120, 138, 54, 98, 190, 75, 119, 16, 161, 211, 43, // Dx.6b.Kw...+ - 113, 160, 126, 80, 118, 129, 236, 107, 105, 33, 103, 48, // q.~Pv..ki!g0 - 220, 238, 196, 207, 224, 177, 14, 8, 206, 1, 196, 139, // ............ - 152, 108, 11, 91, 18, 178, 178, 133, 247, 169, 248, 28, // .l.[........ - 211, 210, 188, 32, 138, 238, 228, 198, 3, 155, 228, 233, // ... ........ - 146, 242, 13, 225, 40, 99, 193, 60, 242, 85, 238, 231, // ....(c.<.U.. - 164, 155, 205, 149, 203, 209, 147, 244, 106, 38, 233, 21, // ........j&.. - 94, 140, 56, 5, 42, 154, 72, 43, 140, 86, 137, 27, // ^.8.*.H+.V.. - 223, 51, 49, 50, 230, 140, 120, 153, 255, 56, 129, 31, // .312..x..8.. - 173, 193, 79, 47, 200, 44, 22, 236, 160, 156, 184, 241, // ..O/.,...... - 76, 110, 62, 38, 126, 196, 80, 34, 198, 195, 214, 88, // Ln>&~.P"...X - 81, 85, 77, 125, 80, 125, 213, 156, 195, 234, 99, 41, // QUM}P}....c) - 155, 11, 233, 249, 203, 5, 61, 193, 139, 52, 175, 175, // ......=..4.. - 168, 219, 245, 75, 163, 199, 132, 115, 14, 212, 167, 72, // ...K...s...H - 64, 216, 156, 123, 40, 95, 55, 50, 64, 103, 179, 100, // @..{(_72@g.d - 150, 72, 25, 86, 136, 153, 238, 25, 6, 71, 103, 48, // .H.V.....Gg0 - 182, 154, 60, 44, 136, 122, 216, 50, 135, 120, 229, 201, // ..<,.z.2.x.. - 9, 237, 219, 183, 134, 78, 241, 190, 225, 25, 74, 19, // .....N....J. - 23, 157, 100, 9, 27, 157, 167, 119, 151, 94, 65, 228, // ..d....w.^A. - 170, 25, 71, 43, 153, 208, 236, 230, 176, 232, 100, 47, // ..G+......d/ - 233, 57, 243, 101, 50, 200, 31, 134, 152, 52, 180, 25, // .9.e2....4.. - 8, 255, 90, 91, 176, 42, 141, 156, 99, 18, 236, 86, // ..Z[.*..c..V - 208, 140, 8, 248, 219, 44, 94, 176, 87, 219, 7, 244, // .....,^.W... - 252, 133, 4, 238, 106, 133, 114, 37, 126, 32, 195, 164, // ....j.r%~ .. - 139, 158, 11, 128, 144, 94, 27, 134, 34, 122, 71, 91, // .....^.."zG[ - 76, 230, 155, 136, 0, 152, 243, 226, 168, 103, 245, 28, // L........g.. - 252, 53, 239, 35, 171, 165, 146, 219, 213, 39, 247, 162, // .5.#.....'.. - 30, 242, 209, 120, 5, 244, 180, 54, 249, 195, 62, 106, // ...x...6..>j - 96, 175, 175, 241, 90, 177, 175, 246, 189, 127, 101, 239, // `...Z.....e. - 236, 135, 4, 37, 112, 36, 144, 24, 19, 6, 252, 125, // ...%p$.....} - 137, 29, 61, 221, 88, 99, 153, 173, 160, 10, 98, 111, // ..=.Xc....bo - 27, 212, 1, 135, 63, 50, 76, 109, 104, 179, 72, 172, // ....?2Lmh.H. - 97, 227, 236, 205, 151, 55, 239, 47, 197, 187, 15, 63, // a....7./...? - 157, 116, 75, 168, 108, 60, 98, 45, 139, 68, 25, 20, // .tK.l..E.27 - 59, 152, 158, 34, 140, 81, 172, 125, 148, 9, 119, 236, // ;..".Q.}..w. - 247, 68, 233, 243, 170, 60, 249, 97, 224, 135, 210, 82, // .D...<.a...R - 193, 203, 194, 15, 45, 29, 125, 186, 129, 63, 11, 173, // ....-.}..?.. - 5, 34, 149, 0, 126, 153, 19, 87, 152, 128, 187, 182, // ."..~..W.... - 230, 146, 0, 217, 72, 28, 247, 111, 231, 47, 139, 165, // ....H..o./.. - 26, 9, 90, 171, 151, 185, 216, 50, 119, 28, 200, 98, // ..Z....2w..b - 218, 37, 213, 220, 166, 72, 120, 3, 183, 4, 145, 36, // .%...Hx....$ - 121, 194, 222, 134, 216, 224, 140, 92, 207, 136, 139, 212, // y........... - 254, 41, 94, 161, 120, 243, 2, 14, 92, 98, 119, 52, // .)^.x....bw4 - 68, 247, 254, 6, 180, 57, 30, 108, 112, 9, 229, 127, // D....9.lp... - 176, 193, 15, 18, 208, 213, 143, 105, 77, 42, 237, 96, // .......iM*.` - 223, 10, 198, 200, 242, 26, 76, 159, 100, 228, 154, 203, // ......L.d... - 166, 79, 239, 90, 202, 165, 219, 110, 146, 96, 243, 25, // .O.Z...n.`.. - 47, 35, 114, 119, 246, 194, 141, 91, 146, 77, 70, 139, // /#rw...[.MF. - 173, 8, 188, 62, 108, 72, 123, 91, 146, 47, 9, 226, // ...>lH{[./.. - 133, 68, 252, 32, 144, 121, 13, 163, 146, 181, 238, 40, // .D. .y.....( - 99, 88, 193, 50, 33, 204, 19, 21, 218, 129, 12, 103, // cX.2!......g - 240, 119, 221, 231, 2, 163, 37, 190, 76, 197, 243, 110, // .w....%.L..n - 71, 172, 80, 77, 10, 163, 74, 55, 130, 115, 152, 92, // G.PM..J7.s.. - 19, 144, 157, 117, 14, 80, 195, 190, 139, 247, 11, 42, // ...u.P.....* - 74, 235, 78, 254, 148, 162, 24, 180, 192, 70, 136, 231, // J.N......F.. - 66, 41, 11, 119, 222, 144, 11, 37, 2, 7, 84, 253, // B).w...%..T. - 139, 112, 215, 126, 42, 84, 25, 85, 143, 153, 54, 119, // .p.~*T.U..6w - 85, 48, 191, 134, 252, 6, 250, 71, 204, 29, 24, 69, // U0.....G...E - 100, 107, 125, 18, 90, 107, 46, 44, 80, 105, 139, 110, // dk}.Zk.,Pi.n - 62, 212, 115, 209, 242, 197, 183, 194, 49, 160, 217, 152, // >.s.....1... - 61, 126, 165, 249, 115, 188, 119, 137, 235, 151, 162, 219, // =~..s.w..... - 21, 223, 187, 137, 102, 186, 236, 179, 217, 235, 131, 223, // ....f....... - 121, 43, 54, 232, 2, 227, 207, 36, 89, 125, 78, 231, // y+6....$Y}N. - 3, 251, 249, 255, 34, 4, 152, 201, 152, 125, 192, 171, // ...."....}.. - 4, 102, 193, 166, 4, 119, 235, 78, 9, 125, 196, 77, // .f...w.N.}.M - 128, 74, 90, 191, 34, 158, 230, 70, 62, 49, 76, 93, // .JZ."..F>1L] - 196, 191, 255, 13, 166, 193, 57, 19, 227, 81, 192, 25, // ......9..Q.. - 13, 132, 48, 145, 66, 255, 150, 151, 182, 31, 140, 41, // ..0.B......) - 23, 155, 191, 218, 188, 6, 188, 142, 203, 56, 150, 201, // .........8.. - 196, 77, 229, 99, 172, 236, 144, 1, 3, 147, 187, 148, // .M.c........ - 11, 116, 116, 193, 45, 164, 2, 170, 153, 232, 15, 231, // .tt.-....... - 245, 246, 40, 79, 255, 228, 150, 164, 76, 240, 83, 164, // ..(O....L.S. - 172, 92, 185, 181, 70, 108, 159, 103, 201, 96, 82, 171, // ....Fl.g.`R. - 153, 252, 167, 119, 225, 243, 213, 183, 1, 124, 53, 188, // ...w.....|5. - 227, 182, 176, 40, 79, 239, 120, 145, 90, 189, 92, 21, // ...(O.x.Z... - 213, 134, 243, 205, 220, 133, 222, 104, 100, 17, 197, 218, // .......hd... - 57, 237, 137, 141, 3, 19, 187, 201, 90, 126, 123, 43, // 9.......Z~{+ - 214, 125, 74, 179, 164, 223, 130, 250, 86, 108, 250, 101, // .}J.....Vl.e - 133, 153, 235, 63, 237, 217, 131, 18, 52, 114, 121, 129, // ...?....4ry. - 12, 27, 121, 75, 15, 49, 156, 75, 42, 113, 218, 112, // ..yK.1.K*q.p - 58, 78, 213, 158, 48, 2, 90, 211, 208, 249, 0, 86, // :N..0.Z....V - 191, 154, 170, 187, 58, 140, 215, 215, 130, 206, 34, 204, // ....:.....". - 172, 21, 26, 147, 117, 166, 159, 174, 154, 223, 243, 150, // ....u....... - 255, 173, 211, 54, 51, 87, 191, 21, 182, 196, 16, 70, // ...63W.....F - 168, 228, 176, 174, 145, 67, 2, 216, 13, 94, 120, 214, // .....C...^x. - 107, 158, 53, 158, 199, 155, 22, 25, 143, 171, 245, 245, // k.5......... - 115, 108, 154, 46, 13, 8, 25, 168, 201, 59, 125, 189, // sl.......;}. - 121, 168, 221, 188, 174, 93, 178, 62, 237, 23, 138, 69, // y....].>...E - 83, 152, 108, 220, 144, 195, 233, 58, 41, 152, 35, 147, // S.l....:).#. - 93, 121, 140, 24, 214, 207, 251, 219, 81, 175, 87, 51, // ]y......Q.W3 - 237, 175, 5, 126, 63, 192, 34, 7, 128, 254, 201, 251, // ...~?."..... - 8, 136, 136, 33, 94, 71, 76, 230, 126, 224, 193, 3, // ...!^GL.~... - 127, 45, 121, 204, 27, 34, 214, 153, 135, 254, 222, 38, // .-y..".....& - 170, 1, 203, 101, 14, 225, 193, 140, 230, 126, 66, 214, // ...e.....~B. - 15, 167, 101, 182, 65, 167, 86, 233, 16, 113, 236, 166, // ..e.A.V..q.. - 126, 106, 93, 13, 122, 36, 152, 89, 130, 125, 98, 228, // ~j].z$.Y.}b. - 56, 244, 246, 4, 216, 225, 253, 9, 40, 210, 55, 68, // 8.......(.7D - 126, 47, 206, 105, 156, 21, 82, 161, 211, 60, 185, 155, // ~/.i..R..<.. - 85, 100, 183, 198, 171, 5, 163, 213, 108, 182, 237, 52, // Ud......l..4 - 14, 252, 172, 213, 180, 155, 74, 173, 110, 11, 181, 18, // ......J.n... - 39, 177, 97, 158, 250, 130, 214, 234, 150, 181, 51, 95, // '.a.......3_ - 31, 128, 236, 92, 216, 15, 45, 211, 207, 174, 31, 238, // ......-..... - 70, 231, 16, 128, 14, 206, 41, 206, 174, 198, 230, 33, // F.....)....! - 204, 132, 105, 170, 171, 81, 183, 25, 113, 51, 21, 14, // ..i..Q..q3.. - 184, 107, 35, 229, 106, 128, 204, 3, 169, 248, 184, 62, // .k#.j......> - 60, 86, 41, 1, 127, 42, 90, 223, 48, 225, 118, 174, // %... - 240, 255, 3, 106, 85, 206, 33, 234, 116, 41, 111, 45, // ...jU.!.t)o- - 118, 145, 86, 117, 245, 157, 198, 217, 235, 79, 175, 7, // v.Vu.....O.. - 125, 58, 1, 163, 169, 219, 183, 136, 8, 61, 132, 45, // }:.......=.- - 252, 50, 73, 38, 131, 126, 153, 99, 115, 14, 57, 65, // .2I&.~.cs.9A - 16, 118, 221, 230, 182, 54, 203, 173, 8, 94, 0, 242, // .v...6...^.. - 214, 211, 35, 48, 252, 24, 10, 63, 226, 101, 142, 149, // ..#0...?.e.. - 118, 179, 61, 58, 69, 42, 131, 75, 205, 108, 70, 109, // v.=:E*.K.lFm - 50, 227, 171, 188, 146, 239, 216, 27, 68, 17, 159, 193, // 2.......D... - 49, 202, 144, 142, 175, 122, 116, 120, 53, 137, 22, 11, // 1....ztx5... - 136, 92, 122, 124, 76, 245, 254, 195, 165, 160, 18, 63, // ..z|L......? - 163, 146, 154, 177, 30, 118, 115, 251, 122, 168, 50, 232, // .....vs.z.2. - 187, 30, 79, 169, 35, 165, 62, 241, 80, 205, 69, 223, // ..O.#.>.P.E. - 33, 172, 184, 219, 94, 63, 206, 231, 77, 245, 40, 188, // !...^?..M.(. - 99, 150, 233, 99, 60, 31, 141, 248, 117, 199, 167, 70, // c..c<...u..F - 38, 170, 146, 51, 122, 88, 31, 202, 105, 121, 126, 146, // &..3zX..iy~. - 109, 232, 97, 18, 72, 55, 108, 94, 95, 41, 145, 170, // m.a.H7l^_).. - 118, 192, 16, 189, 182, 248, 187, 24, 92, 151, 20, 162, // v........... - 80, 73, 19, 68, 212, 121, 118, 29, 243, 170, 73, 83, // PI.D.yv...IS - 37, 179, 239, 205, 114, 235, 10, 197, 104, 219, 28, 34, // %...r...h.." - 145, 227, 40, 170, 29, 34, 221, 164, 93, 52, 151, 143, // ..(.."..]4.. - 37, 78, 21, 164, 138, 31, 97, 170, 252, 84, 22, 66, // %N....a..T.B - 163, 12, 67, 180, 204, 90, 106, 128, 187, 124, 73, 90, // ..C..Zj..|IZ - 237, 151, 34, 161, 31, 172, 217, 128, 83, 111, 85, 190, // ..".....SoU. - 248, 180, 108, 114, 243, 224, 228, 243, 70, 21, 14, 243, // ..lr....F... - 57, 85, 200, 45, 249, 214, 21, 136, 229, 74, 214, 138, // 9U.-.....J.. - 110, 16, 1, 184, 11, 169, 66, 214, 182, 78, 198, 50, // n.....B..N.2 - 104, 137, 110, 10, 196, 50, 117, 131, 84, 86, 18, 165, // h.n..2u.TV.. - 127, 106, 138, 58, 103, 90, 164, 112, 73, 19, 40, 140, // .j.:gZ.pI.(. - 135, 34, 92, 245, 174, 245, 102, 123, 246, 172, 40, 48, // ."....f{..(0 - 246, 219, 195, 97, 111, 14, 123, 190, 230, 30, 27, 58, // ...ao.{....: - 121, 89, 53, 245, 5, 52, 121, 173, 114, 110, 34, 151, // yY5..4y.rn". - 176, 242, 73, 13, 230, 231, 84, 89, 4, 176, 181, 45, // ..I...TY...- - 112, 77, 126, 98, 63, 9, 84, 146, 175, 223, 40, 40, // pM~b?.T...(( - 41, 213, 205, 200, 209, 229, 212, 26, 218, 123, 149, 103, // )........{.g - 247, 185, 138, 111, 171, 7, 90, 25, 160, 208, 248, 115, // ...o..Z....s - 12, 175, 237, 167, 148, 236, 241, 40, 13, 77, 178, 50, // .......(.M.2 - 49, 213, 206, 109, 129, 79, 74, 145, 61, 121, 235, 79, // 1..m.OJ.=y.O - 100, 163, 50, 134, 210, 135, 157, 49, 244, 26, 49, 239, // d.2....1..1. - 136, 159, 221, 113, 26, 5, 75, 236, 68, 58, 233, 24, // ...q..K.D:.. - 234, 179, 215, 97, 195, 24, 111, 247, 226, 94, 17, 136, // ...a..o..^.. - 44, 50, 204, 91, 79, 77, 51, 163, 218, 178, 174, 228, // ,2.[OM3..... - 147, 31, 193, 131, 50, 250, 177, 199, 126, 72, 209, 155, // ....2...~H.. - 28, 53, 10, 197, 100, 54, 213, 163, 130, 10, 116, 120, // .5..d6....tx - 215, 168, 232, 186, 170, 109, 8, 119, 50, 145, 49, 32, // .....m.w2.1 - 4, 145, 233, 216, 203, 105, 30, 204, 228, 30, 239, 222, // .....i...... - 181, 253, 152, 64, 52, 209, 50, 125, 112, 113, 157, 154, // ...@4.2}pq.. - 197, 205, 5, 156, 239, 72, 128, 176, 234, 178, 86, 133, // .....H....V. - 173, 155, 229, 144, 167, 34, 116, 170, 64, 23, 175, 178, // ....."t.@... - 178, 122, 96, 173, 253, 208, 117, 222, 116, 91, 99, 90, // .z`...u.t[cZ - 79, 238, 65, 177, 26, 84, 148, 96, 172, 224, 189, 138, // O.A..T.`.... - 190, 106, 83, 210, 37, 110, 160, 127, 254, 11, 166, 163, // .jS.%n...... - 178, 94, 192, 97, 64, 179, 83, 229, 118, 1, 130, 240, // .^.a@.S.v... - 162, 28, 5, 33, 182, 37, 116, 42, 240, 105, 2, 29, // ...!.%t*.i.. - 209, 8, 163, 172, 32, 83, 248, 191, 134, 45, 222, 26, // .... S...-.. - 215, 89, 199, 18, 53, 82, 52, 202, 122, 209, 186, 149, // .Y..5R4.z... - 137, 63, 245, 165, 215, 238, 8, 183, 28, 150, 238, 198, // .?.......... - 22, 228, 198, 178, 164, 104, 11, 241, 118, 106, 182, 4, // .....h..vj.. - 31, 24, 186, 108, 208, 33, 12, 168, 32, 99, 72, 113, // ...l.!.. cHq - 41, 41, 125, 65, 9, 139, 15, 128, 154, 138, 114, 5, // ))}A......r. - 129, 208, 118, 53, 194, 46, 18, 174, 181, 241, 177, 18, // ..v5........ - 153, 16, 5, 81, 134, 177, 63, 125, 126, 43, 72, 53, // ...Q..?}~+H5 - 83, 147, 55, 210, 111, 224, 90, 0, 101, 0, 125, 136, // S.7.o.Z.e.}. - 12, 108, 206, 151, 225, 13, 1, 85, 245, 160, 88, 40, // .l.....U..X( - 225, 44, 111, 91, 58, 193, 76, 83, 32, 149, 60, 249, // .,o[:.LS .<. - 175, 27, 71, 211, 41, 172, 108, 71, 101, 250, 203, 65, // ..G.).lGe..A - 24, 59, 209, 32, 174, 26, 143, 204, 249, 168, 32, 89, // .;. ...... Y - 179, 123, 190, 83, 132, 78, 255, 251, 25, 83, 58, 253, // .{.S.N...S:. - 229, 25, 223, 39, 253, 231, 238, 180, 77, 160, 252, 87, // ...'....M..W - 234, 94, 169, 110, 197, 45, 129, 165, 199, 32, 71, 44, // .^.n.-... G, - 36, 194, 129, 208, 79, 23, 16, 197, 212, 15, 101, 42, // $...O.....e* - 6, 12, 224, 115, 127, 149, 66, 92, 110, 86, 145, 89, // ...s..B.nV.Y - 230, 38, 4, 243, 149, 232, 212, 157, 106, 127, 17, 7, // .&......j... - 114, 1, 193, 141, 4, 38, 248, 235, 88, 206, 124, 184, // r....&..X.|. - 233, 14, 191, 172, 18, 176, 13, 20, 68, 2, 163, 119, // ........D..w - 44, 76, 171, 253, 7, 23, 252, 211, 155, 139, 203, 233, // ,L.......... - 50, 96, 206, 192, 174, 135, 160, 39, 37, 36, 196, 4, // 2`.....'%$.. - 215, 235, 53, 200, 83, 21, 221, 119, 113, 233, 34, 91, // ..5.S..wq."[ - 62, 75, 20, 240, 78, 178, 13, 213, 145, 42, 31, 218, // >K..N....*.. - 179, 116, 50, 94, 235, 5, 44, 239, 140, 168, 22, 44, // .t2^..,...., - 210, 17, 213, 100, 46, 32, 79, 186, 186, 83, 42, 107, // ...d. O..S*k - 65, 74, 93, 161, 0, 27, 164, 56, 52, 174, 183, 99, // AJ]....84..c - 226, 254, 224, 36, 79, 220, 226, 94, 43, 93, 255, 170, // ...$O..^+].. - 222, 180, 162, 240, 37, 161, 52, 105, 163, 236, 32, 244, // ....%.4i.. . - 213, 11, 186, 114, 157, 142, 186, 221, 133, 14, 116, 237, // ...r......t. - 85, 218, 93, 73, 216, 105, 55, 73, 187, 0, 214, 203, // U.]I.i7I.... - 49, 29, 165, 140, 89, 28, 116, 65, 6, 14, 72, 10, // 1...Y.tA..H. - 221, 224, 164, 235, 158, 161, 188, 160, 169, 86, 54, 115, // .........V6s - 161, 215, 158, 144, 235, 56, 112, 213, 185, 24, 118, 217, // .....8p...v. - 24, 80, 67, 196, 17, 54, 10, 29, 53, 21, 83, 85, // .PC..6..5.SU - 98, 32, 43, 69, 209, 227, 204, 151, 105, 185, 21, 176, // b +E....i... - 228, 133, 106, 48, 25, 208, 94, 68, 15, 170, 125, 237, // ..j0..^D..}. - 133, 57, 125, 25, 102, 47, 9, 165, 203, 85, 30, 74, // .9}.f/...U.J - 191, 60, 148, 138, 186, 162, 101, 250, 36, 211, 101, 144, // .<....e.$.e. - 169, 62, 197, 235, 127, 154, 192, 210, 131, 170, 28, 214, // .>.......... - 163, 241, 170, 193, 238, 215, 112, 125, 193, 197, 226, 6, // ......p}.... - 189, 166, 4, 219, 110, 248, 104, 228, 118, 151, 16, 39, // ....n.h.v..' - 148, 63, 140, 127, 7, 13, 155, 204, 217, 140, 146, 118, // .?.........v - 29, 177, 70, 108, 114, 117, 115, 61, 18, 183, 91, 194, // ..Flrus=..[. - 186, 194, 64, 167, 188, 89, 234, 32, 120, 62, 171, 212, // ..@..Y. x>.. - 184, 10, 83, 92, 131, 129, 10, 100, 247, 93, 131, 201, // ..S....d.].. - 123, 182, 105, 152, 7, 174, 191, 236, 200, 162, 88, 6, // {.i.......X. - 146, 70, 109, 228, 80, 36, 246, 138, 1, 42, 185, 61, // .Fm.P$...*.= - 53, 163, 32, 154, 125, 136, 149, 182, 34, 0, 186, 234, // 5. .}..."... - 33, 232, 249, 65, 249, 251, 230, 53, 196, 233, 224, 253, // !..A...5.... - 77, 146, 68, 9, 191, 245, 241, 70, 129, 21, 191, 12, // M.D....F.... - 168, 169, 28, 47, 103, 205, 235, 235, 175, 35, 222, 93, // .../g....#.] - 172, 171, 32, 240, 194, 219, 65, 188, 187, 137, 128, 7, // .. ...A..... - 18, 19, 101, 206, 167, 114, 179, 238, 190, 67, 241, 63, // ..e..r...C.? - 155, 177, 16, 148, 122, 39, 235, 157, 43, 79, 53, 195, // ....z'..+O5. - 188, 195, 53, 223, 65, 57, 48, 239, 204, 149, 87, 255, // ..5.A90...W. - 118, 114, 26, 124, 177, 176, 88, 77, 10, 41, 242, 235, // vr.|..XM.).. - 18, 230, 151, 41, 91, 17, 17, 104, 43, 27, 218, 10, // ...)[..h+... - 226, 236, 164, 171, 42, 45, 22, 50, 77, 249, 216, 125, // ....*-.2M..} - 18, 192, 196, 161, 78, 237, 197, 170, 246, 240, 198, 229, // ....N....... - 91, 34, 219, 39, 101, 74, 76, 79, 178, 188, 204, 22, // [".'eJLO.... - 242, 97, 249, 187, 136, 174, 215, 1, 253, 45, 165, 58, // .a.......-.: - 214, 103, 73, 208, 113, 31, 28, 0, 131, 68, 62, 248, // .gI.q....D>. - 159, 18, 140, 212, 155, 175, 213, 52, 106, 155, 24, 73, // .......4j..I - 93, 4, 79, 87, 62, 246, 78, 163, 114, 131, 110, 119, // ].OW>.N.r.nw - 80, 140, 38, 222, 145, 135, 190, 103, 200, 128, 234, 238, // P.&....g.... - 25, 144, 235, 140, 225, 100, 128, 61, 14, 72, 238, 121, // .....d.=.H.y - 81, 248, 78, 78, 129, 202, 123, 214, 160, 97, 98, 219, // Q.NN..{..ab. - 111, 234, 167, 19, 169, 221, 161, 110, 47, 235, 173, 178, // o......n/... - 125, 136, 235, 239, 57, 32, 1, 44, 168, 147, 212, 184, // }...9 .,.... - 168, 172, 225, 187, 172, 44, 25, 15, 17, 94, 201, 68, // .....,...^.D - 51, 254, 137, 143, 173, 26, 127, 127, 88, 108, 90, 79, // 3.......XlZO - 223, 187, 116, 93, 96, 143, 3, 133, 65, 126, 37, 52, // ..t]`...A~%4 - 85, 195, 130, 81, 91, 242, 112, 207, 25, 204, 98, 12, // U..Q[.p...b. - 213, 70, 68, 53, 80, 250, 13, 191, 55, 145, 80, 112, // .FD5P...7.Pp - 236, 37, 108, 55, 74, 26, 159, 25, 145, 73, 245, 234, // .%l7J....I.. - 38, 212, 111, 91, 9, 69, 84, 73, 126, 225, 147, 108, // &.o[.ETI~..l - 108, 121, 235, 179, 123, 246, 159, 160, 61, 35, 3, 204, // ly..{...=#.. - 27, 91, 6, 129, 31, 167, 0, 193, 149, 147, 229, 186, // .[.......... - 156, 240, 215, 50, 194, 175, 32, 212, 196, 151, 217, 6, // ...2.. ..... - 96, 151, 172, 104, 6, 18, 240, 245, 9, 163, 116, 75, // `..h......tK - 97, 66, 186, 165, 44, 36, 217, 25, 66, 239, 158, 34, // aB..,$..B.." - 199, 104, 207, 128, 34, 89, 52, 155, 97, 83, 141, 245, // .h.."Y4.aS.. - 39, 106, 116, 95, 142, 46, 157, 10, 165, 172, 148, 205, // 'jt_........ - 141, 151, 20, 224, 203, 192, 83, 31, 223, 241, 141, 6, // ......S..... - 66, 8, 137, 62, 252, 47, 80, 3, 105, 71, 106, 231, // B..>./P.iGj. - 38, 42, 95, 108, 102, 170, 200, 238, 19, 160, 119, 33, // &*_lf.....w! - 133, 112, 102, 246, 220, 241, 200, 69, 42, 186, 163, 193, // .pf....E*... - 156, 50, 235, 105, 73, 147, 220, 151, 136, 148, 207, 52, // .2.iI......4 - 8, 189, 166, 181, 36, 88, 167, 83, 225, 236, 40, 245, // ....$X.S..(. - 183, 31, 234, 83, 194, 93, 55, 105, 116, 222, 201, 127, // ...S.]7it... - 239, 193, 26, 229, 171, 94, 197, 177, 153, 22, 218, 65, // .....^.....A - 54, 20, 53, 240, 39, 127, 32, 253, 78, 61, 87, 16, // 6.5.'. .N=W. - 10, 125, 200, 96, 226, 26, 245, 37, 129, 204, 62, 39, // .}.`...%..>' - 65, 165, 93, 179, 219, 172, 52, 203, 191, 216, 249, 140, // A.]...4..... - 135, 106, 195, 74, 187, 180, 242, 65, 79, 229, 115, 150, // .j.J...AO.s. - 58, 54, 76, 7, 76, 32, 177, 6, 40, 169, 154, 230, // :6L.L ..(... - 78, 98, 147, 216, 160, 161, 219, 21, 31, 238, 19, 200, // Nb.......... - 225, 38, 223, 36, 118, 116, 35, 190, 51, 164, 208, 226, // .&.$vt#.3... - 32, 189, 77, 158, 37, 239, 207, 70, 95, 140, 10, 132, // .M.%..F_... - 161, 151, 162, 102, 164, 196, 38, 1, 20, 55, 112, 109, // ...f..&..7pm - 40, 55, 214, 105, 83, 52, 170, 142, 81, 127, 29, 182, // (7.iS4..Q... - 58, 39, 63, 204, 167, 196, 47, 237, 18, 180, 17, 90, // :'?.../....Z - 209, 171, 104, 98, 21, 190, 89, 66, 226, 20, 99, 64, // ..hb..YB..c@ - 235, 27, 190, 58, 75, 90, 230, 135, 126, 230, 35, 200, // ...:KZ..~.#. - 164, 46, 57, 214, 97, 102, 119, 47, 74, 242, 55, 161, // ..9.afw/J.7. - 91, 37, 38, 190, 143, 22, 20, 5, 145, 62, 170, 227, // [%&......>.. - 203, 241, 60, 199, 170, 17, 45, 110, 178, 171, 15, 75, // ..<...-n...K - 117, 36, 81, 156, 248, 93, 70, 138, 42, 127, 216, 53, // u$Q..]F.*..5 - 194, 54, 92, 248, 97, 151, 127, 213, 167, 94, 78, 151, // .6..a....^N. - 127, 213, 75, 159, 95, 56, 9, 244, 27, 223, 227, 121, // ..K._8.....y - 59, 229, 172, 64, 241, 45, 143, 250, 176, 69, 47, 39, // ;..@.-...E/' - 226, 15, 41, 195, 39, 15, 2, 43, 63, 180, 230, 150, // ..).'..+?... - 106, 40, 138, 175, 83, 28, 125, 100, 79, 158, 65, 127, // j(..S.}dO.A. - 83, 102, 94, 48, 167, 33, 78, 43, 159, 132, 149, 247, // Sf^0.!N+.... - 36, 213, 215, 106, 91, 173, 147, 198, 7, 59, 196, 248, // $..j[....;.. - 169, 254, 202, 200, 252, 90, 109, 151, 80, 85, 239, 149, // .....Zm.PU.. - 219, 185, 216, 31, 201, 152, 195, 95, 241, 113, 26, 243, // ......._.q.. - 174, 62, 19, 38, 175, 242, 122, 78, 215, 70, 204, 79, // .>.&..zN.F.O - 203, 176, 197, 91, 242, 214, 198, 252, 225, 211, 230, 126, // ...[.......~ - 154, 69, 9, 221, 18, 57, 87, 79, 54, 4, 8, 193, // .E...9WO6... - 157, 99, 169, 117, 73, 11, 205, 12, 7, 75, 135, 249, // .c.uI....K.. - 91, 10, 246, 93, 0, 38, 58, 191, 197, 22, 222, 214, // [..].&:..... - 185, 96, 56, 117, 250, 214, 146, 224, 70, 233, 188, 202, // .`8u....F... - 86, 213, 195, 146, 188, 109, 254, 89, 129, 217, 82, 93, // V....m.Y..R] - 108, 207, 91, 228, 159, 9, 60, 124, 54, 184, 242, 67, // l.[...<|6..C - 120, 15, 59, 10, 117, 250, 92, 237, 61, 245, 161, 117, // x.;.u...=..u - 107, 222, 130, 241, 196, 86, 243, 162, 201, 146, 220, 144, // k....V...... - 77, 161, 8, 118, 221, 255, 1, 192, 60, 52, 217, 243, // M..v....<4.. - 61, 0, 0, 0 // =.. + 31, 139, 8, 8, 81, 135, 16, 101, 0, 3, 109, 97, // ....Q..e..ma + 105, 110, 46, 106, 115, 0, 181, 91, 235, 118, 219, 70, // in.js..[.v.F + 146, 254, 239, 167, 232, 96, 28, 147, 116, 8, 144, 32, // .....`..t.. + 41, 201, 166, 37, 229, 200, 151, 68, 158, 163, 216, 62, // )..%...D...> + 150, 236, 221, 140, 142, 142, 3, 18, 77, 18, 17, 110, // ........M..n + 3, 128, 34, 57, 26, 190, 211, 62, 195, 62, 217, 126, // .."9...>.>.~ + 85, 221, 184, 145, 20, 237, 76, 178, 51, 10, 13, 160, // U.....L.3... + 187, 171, 171, 171, 235, 242, 85, 161, 209, 233, 8, 241, // ......U..... + 238, 253, 213, 155, 161, 56, 251, 240, 86, 140, 29, 223, // .....8..V... + 79, 69, 48, 79, 51, 145, 102, 78, 146, 137, 133, 151, // OE0O3.fN.... + 205, 68, 195, 137, 189, 78, 67, 120, 161, 136, 18, 87, // .D...NCx...W + 38, 34, 139, 68, 42, 147, 59, 41, 178, 153, 20, 78, // &".D*.;)...N + 28, 11, 39, 19, 78, 184, 18, 159, 62, 190, 125, 244, // ..'.N...>.}. + 168, 49, 79, 37, 6, 39, 222, 56, 107, 188, 120, 228, // .1O%.'.8k.x. + 5, 113, 4, 50, 247, 98, 214, 22, 137, 12, 49, 186, // .q.2.b....1. + 45, 208, 225, 50, 115, 50, 201, 87, 111, 38, 19, 57, // -..2s2.Wo&.9 + 206, 248, 242, 163, 156, 180, 197, 44, 11, 252, 182, 248, // .......,.... + 24, 205, 51, 76, 180, 22, 147, 36, 10, 132, 104, 88, // ..3L...$..hX + 157, 209, 60, 116, 125, 105, 253, 158, 86, 137, 190, 29, // ...X.Gh9...f + 61, 59, 52, 241, 223, 249, 192, 58, 234, 127, 62, 178, // =;4....:..>. + 122, 207, 208, 124, 136, 63, 110, 238, 182, 249, 230, 224, // z..|.?n..... + 252, 185, 53, 32, 138, 7, 248, 43, 26, 64, 241, 217, // ..5 ...+.@.. + 193, 231, 190, 53, 56, 56, 171, 13, 1, 207, 118, 191, // ...588....v. + 221, 111, 91, 71, 135, 248, 43, 30, 50, 131, 142, 213, // .o[G..+.2... + 123, 142, 63, 245, 144, 216, 196, 127, 180, 174, 231, 234, // {.?......... + 71, 61, 230, 197, 157, 247, 172, 254, 243, 207, 36, 28, // G=........$. + 146, 203, 81, 155, 127, 20, 203, 131, 182, 213, 59, 56, // ..Q.......;8 + 183, 237, 173, 134, 82, 160, 255, 48, 58, 123, 37, 79, // ....R..0:{%O + 98, 85, 194, 253, 108, 111, 11, 30, 187, 193, 180, 206, // bU..lo...... + 119, 78, 143, 201, 177, 53, 88, 248, 243, 193, 54, 7, // wN...5X...6. + 24, 128, 133, 130, 125, 208, 112, 234, 11, 179, 105, 108, // ....}.p...il + 255, 200, 199, 170, 45, 219, 169, 72, 7, 143, 109, 219, // ....-..H..m. + 178, 7, 21, 41, 210, 163, 129, 53, 56, 202, 119, 164, // ...)...58.w. + 16, 188, 109, 170, 27, 208, 183, 251, 249, 118, 150, 194, // ..m......v.. + 55, 213, 182, 28, 158, 211, 182, 84, 55, 154, 90, 168, // 7......T7.Z. + 219, 103, 236, 100, 85, 53, 176, 145, 246, 1, 196, 0, // .g.dU5...... + 174, 88, 105, 74, 141, 162, 150, 190, 101, 219, 237, 186, // .XiJ....e... + 238, 117, 213, 42, 88, 192, 157, 169, 254, 15, 38, 123, // .u.*X.....&{ + 250, 27, 188, 193, 100, 30, 142, 201, 135, 136, 115, 233, // ....d.....s. + 192, 207, 53, 239, 125, 56, 134, 185, 114, 109, 240, 122, // ..5.}8..rm.z + 169, 204, 46, 103, 209, 226, 210, 115, 229, 200, 161, 251, // ...g...s.... + 242, 102, 221, 18, 247, 143, 4, 252, 99, 54, 79, 66, // .f......c6OB + 229, 63, 30, 29, 187, 222, 93, 190, 127, 163, 169, 185, // .?....]..... + 152, 193, 138, 225, 86, 189, 241, 237, 10, 254, 55, 54, // ....V.....76 + 187, 226, 95, 230, 245, 224, 217, 141, 88, 46, 204, 201, // .._.....X... + 220, 247, 197, 136, 125, 179, 57, 18, 241, 202, 236, 137, // ....}.9..... + 199, 247, 21, 250, 226, 201, 19, 209, 136, 125, 243, 168, // .........}.. + 215, 88, 139, 44, 113, 194, 212, 35, 70, 77, 56, 123, // .X.,q..#FM8{ + 225, 206, 19, 246, 124, 102, 191, 219, 85, 109, 147, 40, // ....|f..Um.( + 9, 140, 83, 240, 83, 101, 33, 94, 130, 168, 158, 9, // ..S.Se!^.... + 19, 116, 69, 64, 63, 19, 95, 46, 5, 24, 11, 82, // .tE@?._....R + 115, 12, 191, 40, 19, 30, 135, 145, 35, 229, 59, 179, // s..(....#.;. + 85, 12, 119, 160, 110, 12, 17, 133, 99, 31, 252, 195, // U.w.n...c... + 33, 202, 59, 114, 148, 117, 137, 52, 249, 217, 119, 119, // !.;r.u.4..ww + 173, 117, 62, 103, 38, 151, 153, 153, 250, 136, 20, 230, // .u>g&....... + 160, 219, 213, 164, 65, 252, 241, 61, 251, 125, 11, 131, // ....A..=.}.. + 210, 126, 209, 123, 102, 30, 26, 162, 163, 231, 239, 228, // .~.{f....... + 23, 149, 37, 48, 179, 244, 99, 218, 98, 234, 196, 230, // ..%0..c.b... + 210, 28, 128, 5, 127, 98, 194, 37, 202, 108, 60, 19, // .....b.%.l<. + 254, 116, 168, 158, 31, 150, 115, 85, 198, 39, 18, 172, // .t....sU.'.. + 120, 136, 122, 21, 66, 112, 113, 249, 84, 59, 38, 171, // x.z.Bpq.T;&. + 74, 166, 152, 114, 199, 44, 24, 154, 198, 78, 88, 95, // J..r.,...NX_ + 119, 32, 54, 215, 15, 125, 154, 74, 151, 194, 176, 147, // w 6..}.J.... + 14, 177, 195, 164, 87, 235, 202, 252, 117, 14, 102, 158, // ....W...u.f. + 235, 202, 144, 166, 27, 249, 209, 248, 150, 46, 102, 106, // ..........fj + 250, 133, 25, 47, 249, 249, 212, 156, 38, 14, 180, 5, // .../....&... + 196, 133, 147, 120, 142, 169, 198, 128, 131, 100, 46, 107, // ...x.....d.k + 75, 99, 169, 171, 128, 8, 13, 242, 50, 159, 188, 60, // Kc......2..< + 171, 183, 33, 60, 108, 198, 73, 190, 39, 74, 231, 215, // ..!....*)u..<. + 64, 114, 205, 192, 21, 49, 172, 45, 87, 3, 31, 238, // @r...1.-W... + 4, 176, 198, 60, 20, 147, 40, 196, 19, 25, 120, 163, // ...<..(...x. + 200, 119, 171, 246, 64, 235, 46, 12, 97, 129, 158, 100, // .w..@...a..d + 12, 133, 192, 31, 43, 241, 172, 115, 161, 23, 82, 135, // ....+..s..R. + 192, 247, 123, 157, 59, 47, 242, 101, 102, 218, 180, 132, // ..{.;/.ef... + 212, 164, 69, 248, 206, 170, 114, 105, 70, 177, 12, 135, // ..E...riF... + 236, 53, 88, 72, 75, 179, 91, 40, 12, 253, 207, 172, // .5XHK.[(.... + 54, 177, 239, 248, 38, 239, 83, 163, 49, 241, 150, 80, // 6...&.S.1..P + 122, 229, 242, 124, 57, 201, 240, 207, 40, 130, 34, 6, // z..|9...(.". + 202, 5, 30, 118, 111, 224, 151, 142, 122, 162, 112, 146, // ...vo...z.p. + 218, 17, 38, 53, 42, 250, 97, 174, 239, 130, 22, 48, // ..&5*.a....0 + 241, 163, 133, 185, 50, 157, 57, 225, 217, 113, 18, 249, // ....2.9..q.. + 62, 180, 205, 92, 213, 198, 41, 63, 202, 14, 180, 182, // >.....)?.... + 76, 236, 93, 226, 77, 103, 153, 26, 157, 51, 180, 229, // L.].Mg...3.. + 54, 75, 87, 49, 142, 124, 17, 192, 254, 104, 179, 87, // 6KW1.|...h.W + 133, 237, 111, 245, 157, 65, 216, 80, 245, 4, 138, 141, // ..o..A.P.... + 245, 237, 116, 32, 172, 5, 164, 1, 74, 67, 150, 126, // ..t ....JC.~ + 213, 81, 28, 212, 29, 37, 217, 104, 197, 67, 210, 22, // .Q...%.h.C.. + 64, 47, 196, 175, 209, 60, 17, 47, 177, 30, 247, 219, // @/...<./.... + 220, 101, 190, 130, 42, 105, 109, 126, 133, 59, 120, 237, // .e..*im~.;x. + 164, 179, 81, 228, 36, 238, 134, 71, 152, 69, 129, 92, // ..Q.$..G.E.. + 107, 123, 235, 24, 100, 87, 104, 34, 227, 172, 120, 132, // k{..dWh"..x. + 109, 106, 26, 203, 167, 27, 196, 82, 253, 184, 32, 152, // mj.....R.. . + 22, 253, 190, 145, 240, 79, 94, 18, 44, 156, 68, 2, // .....O^.,.D. + 232, 3, 177, 202, 13, 250, 110, 180, 8, 9, 255, 23, // ......n..... + 244, 231, 186, 215, 55, 82, 127, 115, 71, 233, 193, 6, // ....7R.sG... + 81, 199, 151, 73, 86, 80, 148, 186, 203, 54, 197, 175, // Q..IVP...6.. + 249, 67, 69, 188, 121, 95, 243, 130, 215, 138, 30, 227, // .CE.y_...... + 11, 213, 225, 6, 30, 49, 79, 177, 154, 215, 55, 173, // .....1O...7. + 23, 101, 223, 216, 153, 74, 238, 137, 140, 71, 214, 250, // .e...J...G.. + 217, 232, 86, 244, 3, 163, 137, 76, 103, 228, 89, 201, // ..V....Lg.Y. + 131, 50, 115, 19, 10, 145, 77, 206, 4, 213, 140, 157, // .2s...M..... + 169, 204, 26, 109, 230, 68, 253, 47, 144, 217, 44, 114, // ...m.D./..,r + 225, 15, 63, 188, 191, 188, 66, 203, 40, 114, 87, 67, // ..?...B.(rWC + 241, 247, 203, 247, 239, 44, 74, 7, 195, 169, 55, 89, // .....,J...7Y + 53, 239, 137, 133, 161, 160, 223, 117, 171, 173, 199, 174, // 5......u.... + 91, 22, 50, 201, 176, 153, 144, 187, 78, 144, 92, 69, // [.2.....N..E + 97, 179, 213, 42, 8, 87, 26, 139, 69, 54, 147, 150, // a..*.W..E6.. + 226, 184, 72, 33, 155, 154, 235, 182, 90, 230, 205, 102, // ..H!....Z..f + 51, 47, 69, 179, 171, 37, 208, 100, 230, 98, 192, 8, // 3/E..%.d.b.. + 217, 68, 140, 116, 252, 203, 44, 74, 240, 220, 194, 210, // .D.t..,J.... + 222, 194, 244, 154, 13, 162, 212, 104, 181, 88, 134, 204, // .......h.X.. + 41, 168, 239, 165, 92, 35, 147, 214, 200, 180, 121, 213, // )....#....y. + 86, 22, 93, 178, 52, 154, 138, 234, 186, 206, 175, 218, // V.].4....... + 128, 171, 217, 118, 66, 9, 56, 159, 142, 35, 194, 83, // ...vB.8..#.S + 100, 136, 185, 149, 110, 32, 66, 187, 91, 226, 64, 125, // d...n B.[.@} + 161, 252, 2, 249, 215, 210, 67, 78, 225, 184, 157, 177, // ......CN.... + 151, 33, 98, 29, 16, 156, 3, 136, 23, 49, 249, 22, // .!b......1.. + 246, 36, 228, 101, 139, 232, 83, 139, 57, 85, 79, 243, // .$.e..S.9UO. + 156, 40, 58, 227, 91, 23, 108, 82, 164, 75, 202, 59, // .(:.[.lR.K.; + 164, 163, 140, 5, 243, 204, 87, 133, 159, 227, 78, 54, // ......W...N6 + 83, 33, 71, 47, 210, 221, 177, 72, 183, 136, 98, 196, // S!G/...H..b. + 41, 80, 209, 88, 154, 97, 180, 72, 156, 248, 129, 133, // )P.X.a.H.... + 145, 51, 103, 196, 203, 252, 199, 9, 226, 232, 14, 252, // .3g......... + 244, 156, 220, 98, 193, 14, 158, 19, 55, 110, 149, 155, // ...b....7n.. + 15, 137, 23, 49, 148, 136, 113, 177, 174, 236, 168, 106, // ...1..q....j + 166, 49, 104, 190, 110, 204, 224, 245, 177, 149, 141, 64, // .1h.n......@ + 186, 222, 60, 160, 43, 68, 145, 198, 205, 53, 13, 187, // ..<.+D...5.. + 121, 81, 25, 49, 230, 154, 3, 141, 41, 10, 16, 22, // yQ.1....)... + 215, 30, 202, 219, 149, 244, 49, 184, 250, 100, 154, 72, // ......1..d.H + 25, 214, 136, 85, 195, 51, 28, 142, 174, 96, 172, 53, // ...U.3...`.5 + 121, 120, 16, 117, 177, 102, 14, 113, 203, 139, 19, 58, // yx.u.f.q...: + 182, 175, 43, 58, 197, 118, 195, 43, 148, 85, 92, 116, // ..+:.v.+.U.t + 156, 37, 236, 116, 30, 223, 95, 185, 5, 145, 235, 70, // .%.t.._....F + 28, 45, 100, 66, 171, 155, 193, 163, 147, 191, 164, 235, // .-dB........ + 204, 147, 73, 63, 191, 24, 96, 209, 208, 102, 32, 252, // ..I?..`..f . + 27, 237, 193, 234, 52, 114, 142, 73, 176, 107, 65, 43, // ....4r.I.kA+ + 34, 224, 111, 177, 120, 193, 222, 206, 49, 160, 231, 5, // ".o.x...1... + 18, 184, 171, 25, 202, 133, 120, 77, 142, 73, 63, 122, // ......xM.I?z + 42, 0, 66, 186, 45, 56, 138, 232, 130, 76, 76, 230, // *.B.-8...LL. + 70, 68, 0, 204, 126, 126, 212, 53, 187, 54, 254, 26, // FD..~~.5.6.. + 15, 145, 213, 82, 201, 253, 234, 163, 7, 81, 15, 197, // ...R.....Q.. + 104, 220, 2, 122, 154, 171, 252, 98, 27, 53, 112, 212, // h..z...b.5p. + 215, 120, 173, 176, 171, 237, 232, 95, 179, 157, 237, 148, // .x....._.... + 160, 4, 142, 4, 18, 99, 194, 128, 191, 207, 97, 209, // .....c....a. + 147, 149, 57, 146, 217, 2, 170, 32, 182, 204, 96, 23, // ..9.... ..`. + 112, 248, 35, 211, 236, 76, 109, 130, 196, 28, 24, 167, // p.#..Lm..... + 111, 62, 191, 121, 119, 37, 46, 222, 255, 124, 220, 41, // o>.yw%...|.) + 161, 114, 229, 18, 123, 89, 20, 202, 160, 128, 243, 36, // .r..{Y.....$ + 1, 125, 242, 163, 84, 226, 34, 135, 158, 251, 213, 159, // .}..T."..... + 40, 246, 233, 107, 108, 66, 148, 57, 62, 249, 193, 244, // (..klB.9>... + 4, 105, 140, 98, 237, 131, 76, 120, 96, 175, 43, 202, // .i.b..Lx`.+. + 152, 87, 231, 201, 11, 125, 47, 148, 166, 74, 94, 2, // .W...}/..J^. + 47, 52, 117, 246, 233, 248, 222, 52, 52, 3, 100, 42, // /4u....44.d* + 62, 226, 50, 23, 174, 176, 0, 103, 105, 206, 36, 1, // >.2....gi.$. + 178, 161, 120, 214, 187, 155, 189, 40, 182, 106, 40, 104, // ..x....(.j(h + 175, 94, 228, 98, 203, 156, 145, 47, 139, 101, 151, 84, // .^.b.../.e.T + 115, 159, 34, 17, 13, 156, 18, 68, 146, 228, 9, 123, // s."....D...{ + 87, 196, 134, 96, 228, 184, 149, 188, 72, 217, 79, 113, // W..`....H.Oq + 11, 197, 155, 21, 112, 224, 10, 214, 97, 136, 206, 195, // ....p...a... + 29, 200, 56, 246, 118, 184, 130, 242, 239, 237, 240, 90, // ..8.v......Z + 2, 186, 122, 49, 237, 73, 173, 31, 252, 91, 193, 24, // ..z1.I...[.. + 121, 222, 10, 211, 199, 25, 133, 230, 178, 235, 227, 251, // y........... + 166, 10, 233, 150, 147, 36, 48, 190, 202, 205, 144, 194, // .....$0..... + 157, 21, 56, 113, 83, 178, 203, 104, 178, 23, 65, 212, // ..8qS..h..A. + 135, 15, 105, 173, 75, 242, 37, 65, 220, 144, 136, 247, // ..i.K.%A.... + 2, 153, 87, 112, 42, 89, 243, 158, 42, 134, 53, 44, // ..Wp*Y..*.5, + 19, 194, 61, 209, 67, 203, 151, 225, 20, 241, 174, 243, // ..=.C....... + 84, 96, 182, 196, 147, 169, 120, 218, 105, 139, 5, 154, // T`....x.i... + 73, 97, 212, 211, 149, 224, 26, 38, 183, 248, 228, 103, // Ia.....&...g + 237, 3, 180, 112, 236, 98, 123, 65, 67, 233, 221, 41, // ...p.b{AC..) + 158, 82, 22, 131, 30, 48, 132, 120, 38, 148, 178, 240, // .R...0.x&... + 224, 21, 133, 80, 34, 112, 64, 205, 191, 10, 103, 233, // ...P"p@...g. + 165, 66, 61, 163, 230, 17, 211, 230, 161, 10, 230, 239, // .B=......... + 32, 191, 130, 254, 17, 115, 7, 149, 71, 228, 107, 61, // ....s..G.k= + 18, 90, 115, 38, 76, 80, 105, 137, 78, 62, 213, 83, // .Zs&LPi.N>.S + 209, 244, 196, 15, 194, 174, 64, 179, 17, 71, 252, 90, // ......@..G.Z + 247, 167, 184, 239, 16, 215, 47, 68, 167, 35, 94, 58, // ....../D.#^: + 137, 102, 186, 28, 179, 218, 26, 131, 223, 89, 51, 174, // .f.......Y3. + 208, 5, 198, 159, 74, 242, 250, 92, 206, 7, 246, 243, // ....J....... + 254, 69, 8, 48, 147, 49, 199, 128, 179, 4, 110, 193, // .E.0.1....n. + 162, 2, 119, 243, 94, 9, 125, 200, 93, 128, 74, 154, // ..w.^.}.].J. + 95, 144, 79, 115, 39, 143, 24, 166, 33, 226, 223, 255, // _.Os'...!... + 6, 211, 224, 156, 137, 241, 44, 224, 140, 38, 66, 154, // ......,..&B. + 72, 169, 127, 211, 77, 91, 123, 115, 202, 96, 245, 87, // H...M[{s.`.W + 187, 87, 159, 247, 113, 30, 199, 50, 25, 59, 169, 252, // .W..q..2.;.. + 22, 47, 59, 96, 192, 192, 228, 174, 100, 128, 129, 14, // ./;`....d... + 184, 133, 84, 64, 53, 19, 189, 193, 108, 183, 63, 202, // ..T@5...l.?. + 203, 63, 185, 39, 41, 11, 252, 148, 41, 171, 80, 110, // .?.')...).Pn + 46, 145, 219, 231, 85, 50, 184, 212, 122, 37, 255, 241, // ....U2..z%.. + 125, 248, 116, 241, 131, 143, 88, 141, 232, 184, 46, 60, // }.t...X....< + 202, 227, 123, 222, 164, 102, 55, 87, 69, 101, 112, 94, // ..{..f7WEep^ + 181, 118, 161, 13, 141, 60, 162, 88, 218, 39, 93, 177, // .v...<.X.']. + 178, 225, 98, 87, 89, 211, 107, 173, 197, 178, 71, 101, // ..bWY.k...Ge + 150, 244, 7, 80, 95, 139, 85, 175, 108, 168, 214, 250, // ...P_.U.l... + 79, 186, 86, 191, 4, 141, 252, 188, 64, 134, 70, 222, // O.V.....@.F. + 211, 69, 14, 231, 144, 74, 156, 24, 118, 219, 174, 251, // .E...J..v... + 19, 70, 64, 75, 154, 58, 159, 192, 236, 213, 75, 117, // .F@K.:....Ku + 215, 135, 241, 242, 70, 208, 187, 136, 106, 213, 10, 157, // ....F...j... + 201, 59, 211, 79, 71, 173, 239, 105, 211, 251, 193, 110, // .;.OG..i...n + 85, 43, 87, 191, 21, 190, 164, 34, 140, 80, 201, 97, // U+W....".P.a + 185, 67, 14, 9, 96, 55, 120, 225, 85, 47, 121, 213, // .C..`7x.U/y. + 184, 30, 173, 154, 228, 60, 174, 151, 55, 79, 97, 52, // .....<..7Oa4 + 29, 154, 16, 50, 80, 139, 183, 123, 218, 120, 168, 223, // ...2P..{.x.. + 108, 87, 191, 100, 121, 210, 43, 20, 139, 150, 48, 94, // lW.dy.+...0^ + 57, 33, 167, 211, 187, 164, 80, 157, 153, 252, 202, 183, // 9!....P..... + 136, 97, 249, 180, 183, 30, 118, 187, 59, 150, 253, 181, // .a....v.;... + 196, 239, 53, 60, 178, 15, 232, 159, 188, 139, 128, 136, // ..5<........ + 24, 226, 181, 197, 120, 230, 249, 46, 34, 240, 215, 138, // ....x..."... + 199, 108, 16, 177, 174, 60, 244, 182, 140, 104, 7, 88, // .l...<...h.X + 46, 107, 8, 123, 43, 154, 219, 5, 89, 47, 156, 148, // .k.{+...Y/.. + 213, 6, 93, 90, 165, 151, 136, 35, 39, 245, 82, 243, // ..]Z...#'.R. + 186, 223, 37, 193, 76, 19, 216, 73, 165, 198, 161, 205, // ..%.L..I.... + 19, 96, 135, 237, 19, 80, 164, 87, 17, 249, 131, 56, // .`...P.W...8 + 199, 56, 45, 164, 66, 111, 243, 228, 102, 85, 145, 195, // .8-.Bo..fU.. + 26, 239, 22, 156, 86, 163, 209, 178, 210, 216, 247, 178, // ....V....... + 102, 195, 106, 40, 181, 186, 43, 212, 74, 28, 199, 21, // f.j(..+.J... + 247, 212, 19, 180, 87, 119, 172, 157, 249, 254, 0, 100, // ....Ww.....d + 231, 194, 222, 183, 77, 191, 56, 94, 184, 153, 157, 67, // ....M.8^...C + 0, 58, 57, 167, 60, 187, 158, 155, 135, 112, 19, 85, // .:9.<....p.U + 87, 93, 207, 186, 171, 25, 55, 83, 225, 132, 123, 103, // W]....7S..{g + 166, 92, 79, 144, 121, 34, 149, 31, 239, 78, 143, 85, // ..O.y"...N.U + 73, 192, 155, 136, 230, 119, 76, 184, 149, 43, 79, 163, // I....wL..+O. + 177, 215, 121, 239, 82, 31, 82, 172, 52, 24, 82, 189, // ..y.R.R.4.R. + 50, 88, 42, 191, 61, 77, 60, 151, 127, 168, 98, 148, // 2X*.=M<...b. + 162, 129, 42, 222, 197, 173, 210, 195, 65, 174, 96, 143, // ..*.....A.`. + 239, 137, 221, 18, 238, 148, 254, 216, 80, 232, 221, 0, // ........P... + 146, 36, 38, 1, 225, 139, 166, 181, 248, 223, 255, 121, // .$&........y + 101, 80, 218, 116, 197, 93, 166, 81, 228, 242, 237, 219, // eP.t.].Q.... + 106, 217, 37, 226, 178, 140, 206, 172, 40, 51, 170, 103, // j.%.....(3.g + 89, 101, 217, 101, 131, 135, 243, 57, 192, 37, 114, 227, // Ye.e...9.%r. + 77, 6, 102, 250, 249, 90, 124, 95, 153, 27, 185, 81, // M.f..Z|_...Q + 184, 61, 55, 61, 125, 104, 118, 21, 42, 202, 233, 119, // .=7=}hv.*..w + 189, 232, 129, 168, 8, 123, 134, 144, 158, 66, 162, 213, // .....{...B.. + 226, 48, 124, 180, 11, 27, 242, 167, 6, 158, 18, 199, // .0|......... + 92, 96, 170, 154, 99, 205, 97, 232, 52, 200, 96, 189, // .`..c.a.4.`. + 96, 124, 37, 0, 112, 224, 66, 37, 162, 153, 171, 222, // `|%.p.B%.... + 110, 211, 139, 252, 95, 162, 16, 130, 68, 8, 165, 60, // n..._...D..< + 94, 134, 238, 230, 219, 148, 29, 49, 241, 235, 187, 111, // ^......1...o + 215, 119, 191, 87, 236, 126, 46, 122, 198, 130, 107, 102, // .w.W.~.z..kf + 235, 36, 151, 116, 28, 121, 0, 160, 44, 161, 45, 17, // .$.t.y..,.-. + 49, 130, 152, 45, 205, 222, 96, 179, 226, 251, 103, 68, // 1..-..`...gD + 84, 56, 120, 37, 170, 171, 25, 36, 52, 38, 206, 72, // T8x%...$4&.H + 84, 78, 40, 46, 63, 255, 44, 188, 128, 171, 103, 83, // TN(.?.,...gS + 25, 146, 26, 66, 114, 244, 130, 107, 70, 136, 99, 85, // ...Br..kF.cU + 8, 177, 160, 195, 98, 86, 150, 68, 16, 102, 165, 143, // ....bV.D.f.. + 74, 84, 76, 185, 56, 111, 177, 83, 206, 187, 252, 75, // JTL.8o.S...K + 94, 186, 164, 141, 156, 167, 229, 75, 15, 120, 221, 173, // ^......K.x.. + 112, 160, 92, 10, 77, 38, 185, 108, 241, 233, 221, 217, // p...M&.l.... + 231, 179, 183, 23, 103, 47, 47, 222, 80, 2, 255, 211, // ....g//.P... + 219, 143, 151, 87, 95, 94, 190, 127, 79, 133, 186, 198, // ...W_^..O... + 187, 247, 87, 95, 94, 189, 255, 229, 151, 183, 87, 87, // ..W_^.....WW + 111, 94, 211, 131, 242, 230, 230, 186, 73, 244, 173, 148, // o^......I... + 231, 36, 95, 218, 109, 137, 239, 197, 224, 166, 244, 91, // .$_.m......[ + 119, 200, 200, 168, 148, 83, 237, 119, 42, 186, 95, 123, // w....S.w*._{ + 183, 160, 182, 142, 138, 79, 37, 32, 172, 111, 228, 255, // .....O% .o.. + 7, 224, 171, 189, 13, 217, 165, 209, 121, 111, 177, 137, // ........yo.. + 247, 234, 58, 104, 27, 167, 106, 27, 232, 69, 28, 139, // ..:h..j..E.. + 121, 189, 179, 138, 174, 186, 190, 250, 248, 170, 223, 163, // y........... + 158, 74, 86, 63, 42, 89, 141, 147, 113, 191, 87, 214, // .JV?*Y..q.W. + 3, 237, 67, 46, 102, 132, 29, 167, 177, 143, 214, 37, // ..C.f......% + 224, 249, 22, 41, 194, 236, 223, 50, 248, 39, 220, 204, // ...)...2.'.. + 160, 144, 78, 86, 37, 81, 20, 91, 152, 22, 213, 91, // ..NV%Q.[...[ + 176, 162, 32, 206, 139, 46, 59, 107, 46, 245, 153, 246, // .. ...;k.... + 71, 200, 109, 21, 86, 197, 247, 205, 96, 169, 52, 153, // G.m.V...`.4. + 170, 166, 184, 168, 151, 177, 239, 145, 145, 220, 175, 111, // ...........o + 190, 45, 92, 78, 244, 44, 29, 165, 141, 223, 18, 52, // .-.N.,.....4 + 105, 198, 175, 199, 76, 53, 115, 20, 142, 163, 32, 240, // i...L5s... . + 40, 193, 83, 47, 178, 119, 77, 173, 186, 52, 84, 21, // (.S/.wM..4T. + 251, 193, 242, 182, 110, 80, 211, 212, 166, 72, 228, 40, // ....nP...H.( + 138, 118, 78, 145, 174, 210, 14, 186, 203, 111, 37, 78, // .vN......o%N + 13, 180, 195, 31, 224, 163, 188, 84, 22, 75, 166, 210, // .......T.K.. + 66, 52, 207, 154, 106, 130, 251, 92, 160, 205, 214, 11, // B4..j....... + 145, 208, 15, 36, 222, 231, 154, 91, 157, 47, 126, 77, // ...$...[./~M + 54, 190, 221, 187, 248, 188, 83, 141, 195, 124, 77, 53, // 6.....S..|M5 + 114, 115, 62, 110, 5, 98, 185, 138, 52, 163, 91, 64, // rs>n.b..4.[@ + 127, 39, 144, 42, 87, 109, 233, 42, 44, 163, 149, 232, // .'.*Wm.*,... + 182, 128, 42, 19, 199, 79, 101, 173, 66, 250, 167, 150, // ..*..Oe.B... + 168, 139, 165, 69, 237, 214, 151, 78, 168, 93, 218, 117, // ...E...N.].u + 247, 198, 82, 134, 242, 228, 73, 241, 96, 10, 4, 42, // ..R...I.`..* + 67, 122, 79, 188, 223, 207, 229, 111, 252, 6, 95, 139, // CzO....o.._. + 139, 134, 174, 90, 214, 125, 124, 129, 73, 94, 169, 98, // ...Z.}|.I^.b + 155, 200, 37, 172, 130, 145, 193, 252, 32, 108, 106, 182, // ..%..... lj. + 214, 251, 222, 226, 113, 13, 189, 204, 1, 116, 24, 220, // ....q....t.. + 120, 167, 255, 74, 41, 118, 70, 241, 47, 159, 203, 40, // x..J)vF./..( + 130, 90, 249, 82, 63, 55, 129, 117, 253, 77, 87, 6, // .Z.R?7.u.MW. + 140, 52, 250, 20, 35, 156, 123, 41, 85, 129, 92, 170, // .4..#.{)U... + 79, 147, 44, 235, 111, 205, 54, 38, 253, 168, 84, 221, // O.,.o.6&..T. + 149, 119, 222, 88, 26, 181, 89, 148, 198, 108, 204, 162, // .w.X..Y..l.. + 119, 145, 50, 141, 20, 169, 181, 51, 74, 35, 127, 142, // w.2....3J#.. + 88, 66, 47, 65, 6, 250, 181, 236, 192, 168, 205, 184, // XB/A........ + 121, 170, 175, 18, 240, 21, 15, 170, 3, 43, 81, 190, // y........+Q. + 238, 33, 98, 42, 227, 33, 107, 228, 133, 148, 207, 201, // .!b*.!k..... + 161, 81, 104, 44, 115, 167, 46, 115, 90, 244, 66, 207, // .Qh,s..sZ.B. + 168, 153, 129, 106, 55, 132, 51, 30, 203, 24, 176, 130, // ...j7.3..... + 8, 181, 173, 249, 164, 247, 16, 180, 122, 112, 255, 63, // ........zp.? + 36, 16, 78, 52, 79, 247, 42, 128, 93, 40, 192, 182, // $.N4O.*.](.. + 136, 115, 171, 5, 66, 171, 111, 110, 93, 220, 186, 91, // .s..B.on]..[ + 190, 164, 154, 216, 169, 1, 67, 220, 218, 238, 234, 137, // ......C..... + 181, 133, 192, 30, 216, 48, 215, 149, 170, 238, 163, 7, // .....0...... + 32, 174, 142, 245, 37, 82, 43, 120, 175, 67, 179, 157, // ...%R+x.C.. + 245, 234, 85, 69, 139, 133, 248, 47, 184, 23, 225, 212, // ..UE.../.... + 54, 15, 48, 13, 96, 119, 162, 194, 29, 48, 18, 110, // 6.0.`w...0.n + 52, 52, 193, 226, 3, 39, 185, 165, 32, 136, 196, 12, // 44...'.. ... + 185, 126, 146, 102, 95, 72, 203, 12, 171, 32, 120, 53, // .~.f_H... x5 + 115, 114, 228, 55, 135, 14, 250, 30, 87, 141, 155, 115, // sr.7....W..s + 173, 245, 0, 127, 173, 98, 38, 75, 156, 241, 9, 47, // .....b&K.../ + 80, 93, 105, 141, 41, 149, 43, 130, 107, 184, 147, 156, // P]i.).+.k... + 2, 231, 194, 7, 230, 201, 55, 179, 160, 86, 178, 173, // ......7..V.. + 49, 101, 58, 31, 165, 242, 159, 115, 152, 124, 65, 139, // 1e:....s.|A. + 88, 76, 45, 241, 138, 54, 11, 81, 56, 239, 105, 40, // XL-..6.Q8.i( + 34, 134, 208, 71, 174, 212, 9, 94, 35, 152, 126, 137, // "..G...^#.~. + 50, 231, 139, 106, 108, 182, 140, 194, 191, 22, 244, 176, // 2..jl....... + 11, 227, 153, 102, 33, 85, 64, 54, 95, 81, 81, 215, // ...f!U@6_QQ. + 221, 153, 134, 43, 225, 11, 81, 145, 22, 68, 245, 243, // ...+..Q..D.. + 167, 183, 130, 180, 29, 66, 43, 87, 67, 70, 3, 33, // .....B+WCF.! + 2, 143, 35, 159, 128, 232, 225, 89, 103, 243, 240, 150, // ..#....Yg... + 240, 176, 186, 80, 18, 41, 81, 51, 187, 0, 122, 81, // ...P.)Q3..zQ + 154, 166, 180, 70, 253, 142, 65, 119, 142, 38, 19, 248, // ...F..Aw.&.. + 244, 182, 122, 161, 80, 78, 194, 216, 135, 38, 113, 212, // ..z.PN...&q. + 124, 20, 60, 134, 5, 201, 29, 6, 249, 163, 34, 116, // |.<......."t + 242, 223, 79, 152, 210, 201, 175, 79, 248, 216, 234, 63, // ..O....O...? + 54, 151, 93, 53, 206, 191, 82, 139, 75, 197, 45, 14, // 6.]5..R.K.-. + 35, 168, 99, 6, 34, 144, 72, 58, 66, 47, 13, 32, // #.c.".H:B/. + 137, 137, 23, 202, 84, 244, 57, 77, 200, 55, 143, 246, // ....T.9M.7.. + 201, 201, 120, 179, 50, 39, 129, 138, 111, 72, 78, 157, // ..x.2'..oHN. + 220, 246, 130, 216, 151, 1, 228, 54, 20, 90, 13, 70, // .......6.Z.F + 114, 234, 1, 22, 180, 243, 251, 69, 2, 222, 1, 155, // r......E.... + 72, 106, 250, 17, 54, 168, 217, 250, 131, 27, 255, 241, // Hj..6....... + 205, 229, 213, 100, 238, 51, 139, 224, 219, 69, 142, 149, // ...d.3...E.. + 146, 45, 8, 34, 184, 92, 46, 49, 3, 53, 209, 241, // .-."...1.5.. + 26, 231, 174, 212, 47, 122, 192, 150, 89, 53, 56, 169, // ..../z..Y58. + 202, 175, 93, 83, 215, 254, 181, 126, 192, 155, 79, 137, // ..]S...~..O. + 106, 193, 162, 214, 91, 200, 149, 78, 10, 149, 142, 172, // j...[..N.... + 32, 165, 69, 57, 103, 5, 162, 121, 221, 13, 167, 249, // .E9g..y.... + 7, 23, 121, 236, 20, 199, 104, 233, 180, 89, 253, 96, // ..y...h..Y.` + 23, 229, 41, 9, 85, 101, 141, 114, 128, 208, 39, 61, // ..).Ue.r..'= + 232, 132, 119, 58, 236, 116, 2, 157, 87, 91, 139, 180, // ..w:.t..W[.. + 179, 144, 8, 1, 78, 146, 118, 140, 211, 191, 71, 116, // ....N.v...Gt + 158, 126, 158, 32, 119, 148, 82, 232, 134, 227, 142, 115, // .~. w.R....s + 10, 241, 20, 180, 40, 79, 116, 101, 230, 64, 175, 93, // ....(Ote.@.] + 33, 151, 177, 239, 168, 215, 111, 176, 178, 17, 128, 141, // !.....o..... + 136, 35, 24, 10, 249, 166, 73, 93, 147, 200, 219, 81, // .#....I]...Q + 146, 58, 245, 100, 90, 154, 2, 118, 187, 208, 13, 38, // .:.dZ..v...& + 3, 218, 65, 180, 87, 237, 119, 158, 203, 211, 103, 110, // ..A.W.w...gn + 182, 106, 93, 250, 185, 42, 119, 233, 155, 125, 21, 175, // .j]..*w..}.. + 107, 218, 158, 143, 50, 157, 251, 153, 26, 83, 220, 254, // k...2....S.. + 167, 117, 50, 61, 169, 42, 149, 125, 51, 58, 174, 176, // .u2=.*.}3:.. + 251, 181, 28, 160, 224, 34, 184, 197, 168, 9, 129, 196, // ....."...... + 91, 126, 3, 115, 183, 73, 136, 235, 214, 239, 71, 191, // [~.s.I....G. + 131, 134, 69, 238, 108, 74, 181, 193, 182, 88, 34, 143, // ..E.lJ...X". + 185, 190, 189, 25, 138, 187, 53, 33, 107, 81, 193, 194, // ......5!kQ.. + 108, 36, 187, 0, 127, 190, 170, 180, 114, 226, 166, 56, // l$......r..8 + 109, 3, 21, 200, 30, 58, 109, 147, 143, 108, 209, 52, // m....:m..l.4 + 123, 78, 217, 108, 200, 162, 216, 6, 146, 198, 206, 60, // {N.l.......< + 165, 168, 31, 22, 19, 212, 74, 136, 106, 69, 126, 52, // ......J.jE~4 + 125, 31, 43, 109, 61, 17, 215, 215, 221, 182, 104, 188, // }.+m=.....h. + 86, 200, 161, 113, 3, 113, 218, 184, 127, 147, 36, 81, // V..q.q....$Q + 194, 119, 61, 220, 81, 18, 198, 55, 125, 234, 42, 71, // .w=.Q..7}.*G + 243, 105, 227, 230, 230, 235, 248, 122, 19, 89, 43, 192, // .i.....z.Y+. + 29, 184, 27, 248, 122, 51, 211, 223, 83, 121, 40, 75, // ....z3..Sy(K + 75, 181, 3, 124, 15, 189, 123, 255, 179, 37, 9, 65, // K..|..{..%.A + 21, 126, 114, 223, 185, 242, 212, 11, 217, 27, 92, 243, // .~r......... + 81, 151, 131, 234, 209, 188, 242, 132, 225, 70, 209, 130, // Q........F.. + 207, 47, 22, 187, 73, 9, 76, 126, 42, 163, 250, 1, // ./..I.L~*... + 204, 90, 68, 4, 255, 202, 142, 186, 138, 179, 81, 21, // .ZD.......Q. + 171, 245, 8, 100, 154, 242, 219, 253, 177, 15, 215, 134, // ...d........ + 54, 101, 139, 117, 237, 97, 195, 229, 195, 40, 235, 71, // 6e.u.a...(.G + 37, 22, 215, 139, 44, 207, 204, 133, 140, 174, 46, 34, // %...,......" + 58, 197, 7, 28, 57, 151, 234, 244, 0, 75, 130, 222, // :...9....K.. + 42, 194, 241, 51, 220, 228, 243, 5, 19, 2, 164, 218, // *..3........ + 248, 154, 141, 74, 107, 3, 51, 169, 243, 230, 233, 194, // ...Jk.3..... + 131, 237, 212, 19, 128, 205, 73, 49, 155, 184, 160, 8, // ......I1.... + 253, 192, 148, 62, 181, 61, 48, 33, 183, 85, 166, 147, // ...>.=0!.U.. + 62, 108, 28, 40, 223, 117, 163, 240, 66, 78, 0, 244, // >l.(.u..BN.. + 187, 102, 223, 168, 162, 228, 239, 118, 47, 39, 82, 214, // .f.....v/'R. + 161, 14, 73, 107, 83, 89, 239, 227, 250, 37, 39, 55, // ..IkSY...%'7 + 192, 5, 187, 36, 53, 42, 26, 119, 240, 93, 54, 150, // ...$5*.w.]6. + 140, 135, 72, 214, 100, 162, 25, 255, 200, 111, 199, 140, // ..H.d....o.. + 239, 247, 139, 77, 235, 233, 59, 135, 78, 37, 108, 113, // ...M..;.N%lq + 160, 64, 200, 23, 66, 83, 59, 88, 168, 180, 150, 60, // .@..BS;X...< + 60, 240, 170, 39, 24, 65, 181, 131, 204, 236, 43, 253, // <..'.A....+. + 70, 220, 27, 75, 40, 56, 108, 9, 230, 70, 181, 233, // F..K(8l..F.. + 211, 74, 142, 83, 63, 33, 10, 245, 91, 215, 146, 26, // .J.S?!..[... + 245, 36, 63, 87, 74, 62, 182, 60, 92, 218, 57, 253, // .$?WJ>.<..9. + 79, 208, 94, 165, 208, 204, 134, 45, 125, 223, 139, 83, // O.^....-}..S + 128, 224, 218, 11, 236, 93, 165, 231, 175, 21, 158, 207, // .....]...... + 32, 212, 196, 147, 217, 10, 96, 151, 188, 104, 6, 18, // .....`..h.. + 136, 245, 9, 39, 13, 166, 2, 133, 116, 24, 90, 72, // ...'....t.ZH + 242, 51, 132, 3, 93, 69, 142, 225, 94, 5, 130, 100, // .3..]E..^..d + 209, 116, 10, 163, 26, 233, 47, 225, 232, 88, 30, 157, // .t..../..X.. + 109, 21, 74, 89, 169, 104, 28, 207, 169, 156, 32, 125, // m.JY.h.... } + 87, 125, 227, 199, 7, 39, 8, 33, 36, 250, 140, 65, // W}...'.!$..A + 129, 26, 72, 59, 144, 120, 232, 173, 207, 55, 155, 153, // ..H;.x...7.. + 42, 94, 34, 16, 160, 71, 226, 129, 134, 234, 200, 141, // *^"..G...... + 136, 92, 84, 188, 219, 26, 196, 41, 183, 158, 150, 52, // ..T....)...4 + 41, 124, 137, 72, 197, 204, 10, 161, 205, 156, 135, 3, // )|.H........ + 101, 45, 223, 217, 12, 147, 149, 193, 27, 101, 246, 45, // e-.......e.- + 88, 163, 98, 213, 89, 28, 87, 139, 80, 27, 200, 134, // X.b.Y.W.P... + 178, 6, 254, 178, 16, 164, 47, 212, 117, 13, 161, 208, // ....../.u... + 247, 18, 85, 92, 163, 62, 88, 144, 217, 167, 196, 175, // ..U..>X..... + 245, 107, 116, 26, 181, 110, 249, 135, 65, 159, 112, 81, // .kt..n..A.pQ + 239, 88, 235, 151, 214, 190, 27, 170, 125, 53, 179, 139, // .X......}5.. + 141, 106, 0, 38, 144, 184, 3, 40, 169, 150, 198, 70, // .j.&...(...F + 17, 148, 216, 160, 169, 91, 181, 24, 238, 17, 200, 225, // .....[...... + 46, 223, 37, 86, 116, 43, 126, 172, 72, 161, 201, 233, // ..%Vt+~.H... + 126, 139, 34, 75, 62, 158, 157, 190, 24, 22, 8, 67, // ~."K>......C + 111, 197, 142, 153, 18, 139, 4, 80, 28, 244, 181, 160, // o......P.... + 220, 216, 167, 85, 209, 169, 62, 199, 238, 83, 183, 245, // ...U..>..S.. + 53, 121, 97, 190, 36, 190, 105, 149, 160, 141, 208, 138, // 5ya.$.i..... + 222, 197, 42, 86, 225, 3, 44, 36, 78, 49, 2, 180, // ..*V..,$N1.. + 190, 229, 19, 186, 164, 101, 94, 232, 101, 30, 146, 76, // .....e^.e..L + 26, 146, 99, 29, 102, 118, 243, 60, 38, 127, 122, 186, // ..c.fv.<&.z. + 86, 98, 226, 99, 111, 126, 241, 32, 210, 111, 4, 249, // Vb.co~. .o.. + 12, 62, 175, 177, 238, 68, 139, 3, 243, 234, 251, 85, // .>...D.....U + 157, 65, 20, 47, 22, 175, 34, 69, 149, 191, 31, 27, // .A./.."E.... + 194, 12, 3, 47, 236, 240, 175, 250, 162, 204, 238, 240, // .../........ + 175, 186, 233, 241, 13, 215, 149, 126, 227, 227, 66, 111, // .......~..Bo + 39, 34, 140, 120, 251, 212, 39, 67, 234, 251, 25, 189, // '".x..'C.... + 157, 233, 152, 94, 128, 62, 218, 11, 172, 188, 208, 156, // ...^.>...... + 153, 170, 163, 40, 62, 130, 177, 245, 201, 0, 138, 12, // ...(>....... + 250, 211, 181, 234, 57, 118, 154, 226, 164, 246, 229, 89, // ....9v.....Y + 121, 28, 83, 125, 20, 183, 214, 58, 89, 249, 46, 136, // y.S}...:Y... + 24, 63, 209, 31, 51, 85, 63, 138, 219, 36, 84, 215, // .?..3U?..$T. + 123, 21, 118, 46, 183, 103, 170, 172, 225, 175, 248, 6, // {.v..g...... + 142, 121, 87, 95, 35, 83, 84, 121, 53, 163, 211, 41, // .yW_#STy5..) + 213, 47, 216, 96, 226, 77, 121, 103, 97, 253, 136, 105, // ./.`.Myga..i + 51, 47, 205, 162, 132, 14, 163, 156, 171, 43, 11, 2, // 3/.......+.. + 132, 224, 206, 177, 213, 250, 73, 19, 221, 42, 1, 150, // ......I..*.. + 206, 12, 172, 41, 219, 119, 0, 152, 232, 53, 49, 76, // ...).w...51L + 120, 189, 43, 4, 35, 168, 211, 39, 157, 4, 55, 202, // x.+.#..'..7. + 224, 85, 246, 170, 191, 88, 201, 251, 230, 95, 47, 84, // .U...X..._/T + 123, 170, 243, 243, 121, 143, 252, 107, 132, 253, 175, 32, // {...y..k... + 23, 94, 136, 232, 97, 69, 161, 46, 214, 43, 219, 83, // .^..aE...+.S + 223, 115, 55, 103, 77, 56, 79, 152, 154, 27, 141, 231, // .s7gM8O..... + 20, 134, 44, 74, 69, 96, 117, 255, 7, 63, 152, 70, // ..,JE`u..?.F + 97, 90, 62, 0, 0, 0 // aZ>.. }; static const unsigned char v7[] = { 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 67, // -----BEGIN C @@ -2590,11 +2595,11 @@ static const struct packed_file { time_t mtime; } packed_files[] = { {"/web_root/bundle.js.gz", v1, sizeof(v1), 1693654553}, - {"/web_root/components.js.gz", v2, sizeof(v2), 1693771500}, + {"/web_root/components.js.gz", v2, sizeof(v2), 1693921469}, {"/web_root/history.min.js.gz", v3, sizeof(v3), 1693654553}, {"/web_root/index.html.gz", v4, sizeof(v4), 1693654553}, - {"/web_root/main.css.gz", v5, sizeof(v5), 1693832316}, - {"/web_root/main.js.gz", v6, sizeof(v6), 1693832311}, + {"/web_root/main.css.gz", v5, sizeof(v5), 1695552511}, + {"/web_root/main.js.gz", v6, sizeof(v6), 1695582033}, {"/certs/server_cert.pem", v7, sizeof(v7), 1692695603}, {"/certs/server_key.pem", v8, sizeof(v8), 1692695603}, {NULL, NULL, 0, 0} diff --git a/examples/device-dashboard/web_root/main.css b/examples/device-dashboard/web_root/main.css index fbd256d7..ab34a988 100644 --- a/examples/device-dashboard/web_root/main.css +++ b/examples/device-dashboard/web_root/main.css @@ -1 +1 @@ -/*! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter var,Helvetica,sans-serif;font-feature-settings:"cv11","ss01";font-variation-settings:"opsz" 32}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.left-0{left:0}.right-4{right:1rem}.right-auto{right:auto}.top-0{top:0}.top-4{top:1rem}.isolate{isolation:isolate}.z-10{z-index:10}.z-\[48\]{z-index:48}.z-\[60\]{z-index:60}.col-span-2{grid-column:span 2/span 2}.m-4{margin:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-0{margin-top:0;margin-bottom:0}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-5{margin-top:1.25rem;margin-bottom:1.25rem}.mb-1{margin-bottom:.25rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-7{margin-top:1.75rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-0{width:0}.w-11{width:2.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-72{width:18rem}.w-96{width:24rem}.w-full{width:100%}.min-w-full{min-width:100%}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow-0{flex-grow:0}.basis-\[30px\]{flex-basis:30px}.border-separate{border-collapse:initial}.border-spacing-0{--tw-border-spacing-x:0px;--tw-border-spacing-y:0px;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.-translate-x-full{--tw-translate-x:-100%}.-translate-x-full,.translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x:0px}.translate-x-5{--tw-translate-x:1.25rem}.translate-x-5,.translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.translate-y-2{--tw-translate-y:0.5rem}.transform,.translate-y-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.place-content-end{place-content:end}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-6{row-gap:1.5rem}.-space-x-px>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(-1px*var(--tw-space-x-reverse));margin-left:calc(-1px*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;white-space:nowrap}.text-ellipsis,.truncate{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity))}.border-slate-300{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity))}.border-transparent{border-color:#0000}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(220 252 231/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity))}.bg-slate-50{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.bg-violet-100{--tw-bg-opacity:1;background-color:rgb(237 233 254/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity))}.bg-opacity-75{--tw-bg-opacity:0.75}.fill-cyan-500{fill:#06b6d4}.fill-slate-400{fill:#94a3b8}.stroke-cyan-600{stroke:#0891b2}.stroke-slate-300{stroke:#cbd5e1}.stroke-1{stroke-width:1}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pl-72{padding-left:18rem}.pr-3{padding-right:.75rem}.pt-0{padding-top:0}.pt-0\.5{padding-top:.125rem}.text-left{text-align:left}.align-middle{vertical-align:middle}.text-\[6px\]{font-size:6px}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-light{font-weight:300}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-6{line-height:1.5rem}.tracking-wide{letter-spacing:.025em}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-green-400{--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.text-green-900{--tw-text-opacity:1;color:rgb(20 83 45/var(--tw-text-opacity))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity))}.text-red-900{--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-yellow-900{--tw-text-opacity:1;color:rgb(113 63 18/var(--tw-text-opacity))}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-0{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-0,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-inset{--tw-ring-inset:inset}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur:blur(8px)}.backdrop-blur,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.placeholder\:text-gray-400::-moz-placeholder{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.placeholder\:text-gray-400::placeholder{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.focus\:z-20:focus{z-index:20}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:outline-offset-0:focus{outline-offset:0}.focus\:ring-0:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:outline:focus-visible{outline-style:solid}.focus-visible\:outline-2:focus-visible{outline-width:2px}.focus-visible\:outline-offset-0:focus-visible{outline-offset:0}.focus-visible\:outline-blue-600:focus-visible{outline-color:#2563eb}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-blue-400:disabled{--tw-bg-opacity:1;background-color:rgb(96 165 250/var(--tw-bg-opacity))}.disabled\:bg-gray-100:disabled{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.disabled\:text-gray-500:disabled{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.dark\:border-gray-800{--tw-border-opacity:1;border-color:rgb(31 41 55/var(--tw-border-opacity))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity))}.dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}}@media (min-width:640px){.sm\:flex{display:flex}.sm\:flex-1{flex:1 1 0%}.sm\:translate-x-0{--tw-translate-x:0px}.sm\:translate-x-0,.sm\:translate-x-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:translate-x-2{--tw-translate-x:0.5rem}.sm\:translate-y-0{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:items-start{align-items:flex-start}.sm\:items-end{align-items:flex-end}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:p-2{padding:.5rem}.sm\:p-6{padding:1.5rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}.sm\:leading-6{line-height:1.5rem}}@media (min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:p-5{padding:1.25rem}}@media (min-width:1024px){.lg\:block{display:block}.lg\:h-4{height:1rem}.lg\:w-px{width:1px}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.lg\:bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}} \ No newline at end of file +/*! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter var,Helvetica,sans-serif;font-feature-settings:"cv11","ss01";font-variation-settings:"opsz" 32}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.left-0{left:0}.right-4{right:1rem}.right-auto{right:auto}.top-0{top:0}.top-4{top:1rem}.isolate{isolation:isolate}.z-10{z-index:10}.z-\[48\]{z-index:48}.z-\[60\]{z-index:60}.col-span-2{grid-column:span 2/span 2}.m-4{margin:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-0{margin-top:0;margin-bottom:0}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-5{margin-top:1.25rem;margin-bottom:1.25rem}.mb-1{margin-bottom:.25rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mt-1{margin-top:.25rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-7{margin-top:1.75rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-0{width:0}.w-11{width:2.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-72{width:18rem}.w-96{width:24rem}.w-full{width:100%}.min-w-full{min-width:100%}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow-0{flex-grow:0}.basis-\[30px\]{flex-basis:30px}.border-separate{border-collapse:initial}.border-spacing-0{--tw-border-spacing-x:0px;--tw-border-spacing-y:0px;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.-translate-x-full{--tw-translate-x:-100%}.-translate-x-full,.translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x:0px}.translate-x-5{--tw-translate-x:1.25rem}.translate-x-5,.translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.translate-y-2{--tw-translate-y:0.5rem}.transform,.translate-y-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-content-end{place-content:end}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-6{row-gap:1.5rem}.-space-x-px>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(-1px*var(--tw-space-x-reverse));margin-left:calc(-1px*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;white-space:nowrap}.text-ellipsis,.truncate{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity))}.border-slate-300{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity))}.border-transparent{border-color:#0000}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(220 252 231/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity))}.bg-slate-50{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.bg-violet-100{--tw-bg-opacity:1;background-color:rgb(237 233 254/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity))}.bg-opacity-75{--tw-bg-opacity:0.75}.fill-cyan-500{fill:#06b6d4}.fill-slate-400{fill:#94a3b8}.stroke-cyan-600{stroke:#0891b2}.stroke-slate-300{stroke:#cbd5e1}.stroke-1{stroke-width:1}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pl-72{padding-left:18rem}.pr-3{padding-right:.75rem}.pt-0{padding-top:0}.pt-0\.5{padding-top:.125rem}.text-left{text-align:left}.align-middle{vertical-align:middle}.text-\[6px\]{font-size:6px}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-light{font-weight:300}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-6{line-height:1.5rem}.tracking-wide{letter-spacing:.025em}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-green-400{--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.text-green-900{--tw-text-opacity:1;color:rgb(20 83 45/var(--tw-text-opacity))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity))}.text-red-900{--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-yellow-900{--tw-text-opacity:1;color:rgb(113 63 18/var(--tw-text-opacity))}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-0{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-0,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-inset{--tw-ring-inset:inset}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur:blur(8px)}.backdrop-blur,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.placeholder\:text-gray-400::-moz-placeholder{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.placeholder\:text-gray-400::placeholder{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.focus\:z-20:focus{z-index:20}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:outline-offset-0:focus{outline-offset:0}.focus\:ring-0:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:outline:focus-visible{outline-style:solid}.focus-visible\:outline-2:focus-visible{outline-width:2px}.focus-visible\:outline-offset-0:focus-visible{outline-offset:0}.focus-visible\:outline-blue-600:focus-visible{outline-color:#2563eb}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-blue-400:disabled{--tw-bg-opacity:1;background-color:rgb(96 165 250/var(--tw-bg-opacity))}.disabled\:bg-gray-100:disabled{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.disabled\:text-gray-500:disabled{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.dark\:border-gray-800{--tw-border-opacity:1;border-color:rgb(31 41 55/var(--tw-border-opacity))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity))}.dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}}@media (min-width:640px){.sm\:flex{display:flex}.sm\:flex-1{flex:1 1 0%}.sm\:translate-x-0{--tw-translate-x:0px}.sm\:translate-x-0,.sm\:translate-x-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:translate-x-2{--tw-translate-x:0.5rem}.sm\:translate-y-0{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:items-start{align-items:flex-start}.sm\:items-end{align-items:flex-end}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:p-2{padding:.5rem}.sm\:p-6{padding:1.5rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}.sm\:leading-6{line-height:1.5rem}}@media (min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:p-5{padding:1.25rem}}@media (min-width:1024px){.lg\:block{display:block}.lg\:h-4{height:1rem}.lg\:w-px{width:1px}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.lg\:bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}} \ No newline at end of file diff --git a/examples/device-dashboard/web_root/main.js b/examples/device-dashboard/web_root/main.js index df270f7f..d08406bb 100644 --- a/examples/device-dashboard/web_root/main.js +++ b/examples/device-dashboard/web_root/main.js @@ -186,16 +186,18 @@ function Main({}) { }; function FirmwareStatus({title, info, children}) { + const state = ['UNAVAILABLE', 'FIRST_BOOT', 'NOT_COMMITTED', 'COMMITTED'][(info.status || 0) % 4]; + const valid = info.status > 0; return html`
${title}
-
CRC32: ${info.valid ? info.crc32.toString(16) : 'n/a'} -
Size: ${info.valid ? info.size : 'n/a'} -
Flashed at: ${info.valid ? new Date(info.time * 1000).toLocaleString() : 'n/a'} -
State: ${info.valid ? (info.golden == 0 ? 'commtited' : 'NOT committed') : 'n/a'} +
Status: ${state} +
CRC32: ${valid ? info.crc32.toString(16) : 'n/a'} +
Size: ${valid ? info.size : 'n/a'} +
Flashed at: ${valid ? new Date(info.timestamp * 1000).toLocaleString() : 'n/a'} ${children} `; @@ -206,7 +208,6 @@ function FirmwareUpdate({}) { const [info, setInfo] = useState([{}, {}]); const refresh = () => fetch('api/firmware/status').then(r => r.json()).then(r => setInfo(r)); useEffect(refresh, []); - const state = ['new', 'dirty', 'clean'][(info.state || 0) % 3]; const oncommit = ev => fetch('api/firmware/commit') .then(r => r.json()) .then(refresh); @@ -223,12 +224,14 @@ function FirmwareUpdate({}) { return html`
<${FirmwareStatus} title="Current firmware image" info=${info[0]}> - <${Button} cls="mr-2" title="Commit this firmware" - onclick=${oncommit} icon=${Icons.thumbUp} disabled=${clean} /> - <${Button} title="Reboot device" onclick=${onreboot} icon=${Icons.refresh} clsx="absolute top-4 right-4" /> - <${UploadFileButton} class="mt-2" - title="Upload new firmware: choose .bin file:" onupload=${onupload} - url="api/firmware/upload" accept=".bin,.uf2" /> +
+ <${Button} title="Commit this firmware" + onclick=${oncommit} icon=${Icons.thumbUp} disabled=${clean} /> + <${Button} title="Reboot device" onclick=${onreboot} icon=${Icons.refresh} clsx="absolute top-4 right-4" /> + <${UploadFileButton} + title="Upload new firmware: choose .bin file:" onupload=${onupload} + url="api/firmware/upload" accept=".bin,.uf2" /> + <${FirmwareStatus} title="Previous firmware image" info=${info[1]}> <${Button} title="Rollback to this firmware" onclick=${onrollback} @@ -238,10 +241,11 @@ function FirmwareUpdate({}) {
<${DeveloperNote}>
- When new firmware gets flashed, its status is unreliable, "not - committed". In order to become "committed" (verified), a firmware must - be committed. If a firmware is not committed, then the next boot - reverts back to the previous firmware. + When a new firmware gets flashed, its status is marked as, "first_boot". + That is an unreliable (uncommitted) firmware. A user may choose + to revert back to the previous committed firmware on the subsequent + boots. Clicking on the "commit" button calls "mg_ota_commit()" function + which commits the firmware.
This GUI loads a firmware file and sends it chunk by chunk to the @@ -254,8 +258,8 @@ function FirmwareUpdate({}) {
<${DeveloperNote}>
- Firmware udpdate mechanism defines 3 API functions that the - target device must implement: ota_begin(), ota_write() and ota_end() + Firmware update mechanism defines 3 API functions that the target + device must implement: mg_ota_begin(), mg_ota_write() and mg_ota_end()
RESTful API handlers use ota_xxx() API to save firmware to flash. @@ -264,7 +268,7 @@ function FirmwareUpdate({}) {
Subscribe to our free webinar to + href="https://mongoose.ws/webinars/">Join our free webinar to get detailed explanations about possible firmware updates strategies and implementation demo diff --git a/examples/stm32/nucleo-h563zi-make-baremetal-builtin/Makefile b/examples/stm32/nucleo-h563zi-make-baremetal-builtin/Makefile index bc1fec7f..21435a86 100644 --- a/examples/stm32/nucleo-h563zi-make-baremetal-builtin/Makefile +++ b/examples/stm32/nucleo-h563zi-make-baremetal-builtin/Makefile @@ -2,18 +2,12 @@ CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion CFLAGS += -g3 -Os -ffunction-sections -fdata-sections CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_h5/Include -CFLAGS += -mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard +CFLAGS += -mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard $(CFLAGS_EXTRA) LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nano.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map -SOURCES = main.c syscalls.c sysinit.c +SOURCES = main.c syscalls.c sysinit.c mongoose.c net.c packed_fs.c SOURCES += cmsis_h5/Source/Templates/gcc/startup_stm32h563xx.s # ST startup file. Compiler-dependent! -# Mongoose-specific. See https://mongoose.ws/documentation/#build-options -SOURCES += mongoose.c net.c packed_fs.c -CFLAGS += -DMG_ENABLE_TCPIP=1 -DMG_ARCH=MG_ARCH_NEWLIB -DMG_ENABLE_CUSTOM_MILLIS=1 -CFLAGS += -DMG_ENABLE_CUSTOM_RANDOM=1 -DMG_ENABLE_PACKED_FS=1 -CFLAGS += -DMG_ENABLE_DRIVER_STM32H=1 -DMG_OTA=MG_OTA_STM32H5 $(CFLAGS_EXTRA) - # Example specific build options. See README.md CFLAGS += -DHTTP_URL=\"http://0.0.0.0/\" -DHTTPS_URL=\"https://0.0.0.0/\" @@ -32,7 +26,7 @@ firmware.elf: cmsis_core cmsis_h5 $(SOURCES) hal.h link.ld Makefile arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@ flash: firmware.bin - st-flash --debug --freq=200 --reset write $< 0x8000000 + st-flash --reset write $< 0x8000000 cmsis_core: # ARM CMSIS core headers git clone --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ diff --git a/examples/stm32/nucleo-h563zi-make-baremetal-builtin/main.c b/examples/stm32/nucleo-h563zi-make-baremetal-builtin/main.c index bc43faeb..94d58764 100644 --- a/examples/stm32/nucleo-h563zi-make-baremetal-builtin/main.c +++ b/examples/stm32/nucleo-h563zi-make-baremetal-builtin/main.c @@ -42,6 +42,25 @@ int main(void) { mg_mgr_init(&mgr); // Mongoose event manager mg_log_set(MG_LL_DEBUG); // Set log level +#if MG_OTA == MG_OTA_FLASH + // If we don't have any OTA info saved, e.g. we're pre-flashed, then + // call mg_ota_commit() to mark this firmware as reliable + if (mg_ota_status(MG_FIRMWARE_CURRENT) == MG_OTA_UNAVAILABLE) mg_ota_commit(); + + // Demonstrate the use of mg_flash_{load/save} functions for keeping device + // configuration data on flash. Increment boot count on every boot. + struct deviceconfig { + uint32_t boot_count; + int some_other_data; + }; + uint32_t key = 0x12345678; // A unique key, one per data type + struct deviceconfig dc = {}; // Initialise to some default values + mg_flash_load(NULL, key, &dc, sizeof(dc)); // Load from flash + dc.boot_count++; // Increment boot count + mg_flash_save(NULL, key, &dc, sizeof(dc)); // And save back + MG_INFO(("Boot count: %u", dc.boot_count)); +#endif + // Initialise Mongoose network stack struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4}; struct mg_tcpip_if mif = {.mac = GENERATE_LOCALLY_ADMINISTERED_MAC(), diff --git a/examples/stm32/nucleo-h563zi-make-baremetal-builtin/mongoose_custom.h b/examples/stm32/nucleo-h563zi-make-baremetal-builtin/mongoose_custom.h new file mode 100644 index 00000000..0a2df18d --- /dev/null +++ b/examples/stm32/nucleo-h563zi-make-baremetal-builtin/mongoose_custom.h @@ -0,0 +1,12 @@ +#pragma once + +#define MG_ARCH MG_ARCH_NEWLIB +#define MG_OTA MG_OTA_FLASH + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_PACKED_FS 1 +#define MG_ENABLE_DRIVER_STM32H 1 +#define MG_ENABLE_STM32H5 1 +#define MG_ENABLE_LINES 1 diff --git a/examples/stm32/nucleo-h743zi-make-baremetal-builtin/Makefile b/examples/stm32/nucleo-h743zi-make-baremetal-builtin/Makefile index 841c714c..e5bb9a79 100644 --- a/examples/stm32/nucleo-h743zi-make-baremetal-builtin/Makefile +++ b/examples/stm32/nucleo-h743zi-make-baremetal-builtin/Makefile @@ -2,18 +2,12 @@ CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion CFLAGS += -g3 -Os -ffunction-sections -fdata-sections CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include -CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 +CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 $(CFLAGS_EXTRA) LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nano.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map -SOURCES = main.c syscalls.c sysinit.c +SOURCES = main.c syscalls.c sysinit.c mongoose.c net.c packed_fs.c SOURCES += cmsis_h7/Source/Templates/gcc/startup_stm32h743xx.s # ST startup file. Compiler-dependent! -# Mongoose-specific. See https://mongoose.ws/documentation/#build-options -SOURCES += mongoose.c net.c packed_fs.c -CFLAGS += -DMG_ENABLE_TCPIP=1 -DMG_ARCH=MG_ARCH_NEWLIB -DMG_ENABLE_CUSTOM_MILLIS=1 -CFLAGS += -DMG_ENABLE_CUSTOM_RANDOM=1 -DMG_ENABLE_PACKED_FS=1 -CFLAGS += -DMG_ENABLE_DRIVER_STM32H=1 $(CFLAGS_EXTRA) - # Example specific build options. See README.md CFLAGS += -DHTTP_URL=\"http://0.0.0.0/\" -DHTTPS_URL=\"https://0.0.0.0/\" @@ -56,7 +50,6 @@ test update: CFLAGS += -DUART_DEBUG=USART1 test: update curl --fail-with-body -su :$(VCON_API_KEY) $(DEVICE_URL)/tx?t=5 | tee /tmp/output.txt grep 'READY, IP:' /tmp/output.txt # Check for network init -# grep 'MQTT connected' /tmp/output.txt # Check for MQTT connection success clean: $(RM) firmware.* *.su cmsis_core cmsis_h7 mbedtls diff --git a/examples/stm32/nucleo-h743zi-make-baremetal-builtin/main.c b/examples/stm32/nucleo-h743zi-make-baremetal-builtin/main.c index aae28353..e01b08e3 100644 --- a/examples/stm32/nucleo-h743zi-make-baremetal-builtin/main.c +++ b/examples/stm32/nucleo-h743zi-make-baremetal-builtin/main.c @@ -45,6 +45,10 @@ int main(void) { mg_mgr_init(&mgr); // Mongoose event manager mg_log_set(MG_LL_DEBUG); // Set log level + // If we don't have any OTA info saved, e.g. we're pre-flashed, then + // call mg_ota_commit() to mark this firmware as reliable + if (mg_ota_status(MG_FIRMWARE_CURRENT) == MG_OTA_UNAVAILABLE) mg_ota_commit(); + // Initialise Mongoose network stack struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4}; struct mg_tcpip_if mif = {.mac = GENERATE_LOCALLY_ADMINISTERED_MAC(), diff --git a/examples/stm32/nucleo-h743zi-make-baremetal-builtin/mongoose_custom.h b/examples/stm32/nucleo-h743zi-make-baremetal-builtin/mongoose_custom.h new file mode 100644 index 00000000..1316e8b1 --- /dev/null +++ b/examples/stm32/nucleo-h743zi-make-baremetal-builtin/mongoose_custom.h @@ -0,0 +1,12 @@ +#pragma once + +#define MG_ARCH MG_ARCH_NEWLIB +#define MG_OTA MG_OTA_FLASH + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_PACKED_FS 1 +#define MG_ENABLE_DRIVER_STM32H 1 +#define MG_ENABLE_STM32H7 1 +#define MG_ENABLE_LINES 1 diff --git a/mongoose.c b/mongoose.c index 7ca2bcbc..7b42365a 100644 --- a/mongoose.c +++ b/mongoose.c @@ -4946,232 +4946,318 @@ bool mg_send(struct mg_connection *c, const void *buf, size_t len) { #if MG_OTA == MG_OTA_NONE -static struct mg_ota_data s_od[2] = { - {MG_OTA_MAGIC, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, - (uint32_t) -1}, - {MG_OTA_MAGIC, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, - (uint32_t) -1}, -}; bool mg_ota_begin(size_t new_firmware_size) { - MG_DEBUG(("Starting firmware update, size %lu", new_firmware_size)); + (void) new_firmware_size; return true; } bool mg_ota_write(const void *buf, size_t len) { - MG_DEBUG(("%p %lu", buf, len)); + (void) buf, (void) len; return true; } bool mg_ota_end(void) { return true; } -bool mg_ota_status(struct mg_ota_data od[2]) { - od[0] = s_od[0]; - od[1] = s_od[1]; - return true; -} bool mg_ota_commit(void) { - s_od[0].golden = 0; return true; } bool mg_ota_rollback(void) { return true; } +int mg_ota_status(int fw) { + (void) fw; + return 0; +} +uint32_t mg_ota_crc32(int fw) { + (void) fw; + return 0; +} +uint32_t mg_ota_timestamp(int fw) { + (void) fw; + return 0; +} +size_t mg_ota_size(int fw) { + (void) fw; + return 0; +} void mg_sys_reset(void) { - MG_DEBUG(("Resetting device...")); } #endif #ifdef MG_ENABLE_LINES -#line 1 "src/ota_stm32h5.c" +#line 1 "src/ota_flash.c" #endif -#if MG_OTA == MG_OTA_STM32H5 -#define FLASH_BANK1 0x08000000 -#define FLASH_SIZE1 0x00100000 -#define FLASH_BANK2 0x08100000 -#define FLASH_SIZE2 0x00100000 -#define FLASH_SSIZE 8192 // Sector (page) size, 8k +// This OTA implementation uses the internal flash API outlined in sys.h +// It splits flash into 2 equal partitions, and stores OTA status in the +// last sector of the partition. -// Keep OTA data at the beginning of the last sector of the flash bank -#define FLASH_BANK1_OTA_DATA (FLASH_BANK1 + FLASH_SIZE1 - FLASH_SSIZE) -#define FLASH_BANK2_OTA_DATA (FLASH_BANK2 + FLASH_SIZE2 - FLASH_SSIZE) +#if MG_OTA == MG_OTA_FLASH -#define FLASH_BASE 0x40022000 // Base address of the flash controller -#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 -#define FLASH_OPTKEYR (FLASH_BASE + 0xc) -#define FLASH_OPTCR (FLASH_BASE + 0x1c) -#define FLASH_NSSR (FLASH_BASE + 0x20) -#define FLASH_NSCR (FLASH_BASE + 0x28) -#define FLASH_NSCCR (FLASH_BASE + 0x30) -#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) -#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) +#define MG_OTADATA_KEY 0xb07afed0 -#undef REG -#define REG(x) ((volatile uint32_t *) (x))[0] -#undef BIT -#define BIT(x) (((uint32_t) 1U) << (x)) -#undef SETBITS -#define SETBITS(R, CLEARMASK, SETMASK) (R) = ((R) & ~(CLEARMASK)) | (SETMASK) - -static uint32_t s_addr; // Current address to write to +static char *s_addr; // Current address to write to +static size_t s_size; // Firmware size to flash. In-progress indicator static uint32_t s_crc32; // Firmware checksum -static void flash_unlock(void) { - static bool unlocked = false; - if (unlocked == false) { - REG(FLASH_KEYR) = 0x45670123; - REG(FLASH_KEYR) = 0Xcdef89ab; - REG(FLASH_OPTKEYR) = 0x08192a3b; - REG(FLASH_OPTKEYR) = 0x4c5d6e7f; - unlocked = true; - } -} - -static int flash_page_start(volatile uint32_t *dst) { - uint32_t addr = (uint32_t) (uintptr_t) dst; - return (addr & (FLASH_SSIZE - 1)) == 0; -} - -static bool flash_is_err(void) { - return REG(FLASH_NSSR) & ((BIT(8) - 1) << 17); // RM0481 7.11.9 -} - -static void flash_wait(void) { - while ((REG(FLASH_NSSR) & BIT(0)) && (REG(FLASH_NSSR) & BIT(16)) == 0) { - (void) 0; - } -} - -static void flash_prep(void) { - flash_wait(); // Wait until ready - REG(FLASH_NSCR) = 0UL; // Clear control reg - REG(FLASH_NSCCR) = ((BIT(9) - 1) << 16U); // Clear all errors -} - -static bool flash_bank_is_swapped(void) { - return REG(FLASH_OPTCR) & BIT(31); // RM0481 7.11.8 -} - -static void flash_erase(uint32_t sector) { - flash_prep(); - if ((sector < 128 && flash_bank_is_swapped()) || - (sector > 127 && !flash_bank_is_swapped())) { - REG(FLASH_NSCR) |= BIT(31); // Set FLASH_CR_BKSEL - } - if (sector > 127) sector -= 128; - REG(FLASH_NSCR) |= BIT(2) | (sector << 6); // SectorErase | sector_num - REG(FLASH_NSCR) |= BIT(5); // Start erasing - // MG_INFO(("ERASE %lu, CR %#lx SR %#lx", sector, REG(FLASH_NSCR), - // REG(FLASH_NSSR))); - flash_prep(); -} - -static bool flash_swap_bank(void) { - uint32_t desired = flash_bank_is_swapped() ? 0 : BIT(31); - flash_unlock(); - flash_prep(); - // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); - SETBITS(REG(FLASH_OPTSR_PRG), BIT(31), desired); - // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); - REG(FLASH_OPTCR) |= BIT(1); // OPTSTART - while ((REG(FLASH_OPTSR_CUR) & BIT(31)) != desired) (void) 0; - return true; -} - -static bool flash_write(uint32_t addr, const void *buf, size_t len) { - volatile uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool success = true; - flash_unlock(); - flash_prep(); - while (success && src < end) { - uint32_t pageno = ((uint32_t) dst - FLASH_BANK1) / FLASH_SSIZE; - if (flash_page_start(dst)) flash_erase(pageno); - REG(FLASH_NSCR) = BIT(1); // Set programming flag - do { - *dst++ = *src++; - flash_wait(); - } while (src < end && !flash_page_start(dst) && !flash_is_err()); -#if 0 - do { - *dst++ = *src++; - printf("WRITE %p, CR %#lx SR %#lx OCR %#lx %#lx %#lx\n", dst, FLASH->NSCR, - FLASH->NSSR, FLASH->OPTCR, src[-1], dst[-1]); - } while (src < end && !flash_page_start(dst)); -#endif - // if (FLASH->NSSR & FLASH_SR_WBNE) FLASH->NSCR |= FLASH_CR_FW; - if (REG(FLASH_NSSR) & BIT(1)) REG(FLASH_NSCR) |= BIT(4); - MG_INFO(("W %p, CR %#lx SR %#lx OCR %#lx %#lx %#lx", dst, REG(FLASH_NSCR), - REG(FLASH_NSSR), REG(FLASH_OPTCR), src[-1], dst[-1])); - flash_wait(); - if (flash_is_err() || (src[-1] != dst[-1])) { - // printf(" E %lx CR %#lx SR %#lx, %#lx %#lx\n", (unsigned long) dst, - // FLASH->NSCR, FLASH->NSSR, src[-1], dst[-1]); - success = false; - } - } - return success; -} -void mg_sys_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; -} +struct mg_otadata { + uint32_t crc32, size, timestamp, status; +}; bool mg_ota_begin(size_t new_firmware_size) { - s_crc32 = 0; - s_addr = FLASH_BANK2; - MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, FLASH_SIZE2)); - return new_firmware_size < FLASH_SIZE2; + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size(); + s_crc32 = 0; + s_addr = (char *) mg_flash_start() + half; + MG_DEBUG(("Firmware %lu bytes, max %lu", s_size, max)); + if (new_firmware_size < max) { + ok = true; + s_size = new_firmware_size; + MG_INFO(("Starting OTA, firmware size %lu", s_size)); + } else { + MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, max)); + } + } + return ok; } bool mg_ota_write(const void *buf, size_t len) { - bool ok = flash_write(s_addr, buf, len); // Write chunk to flash - s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC - MG_DEBUG(("%#x %p %lu -> %d", s_addr, buf, len, ok)); - s_addr += len; + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t align = mg_flash_write_align(); + size_t len_aligned_down = MG_ROUND_DOWN(len, align); + if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp)); + } + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); + s_addr += len; + } return ok; } -static bool w32(uint32_t addr, uint32_t value) { - return flash_write(addr, &value, sizeof(value)); -} - bool mg_ota_end(void) { + char *base = (char *) mg_flash_start() + mg_flash_size() / 2; bool ok = false; - size_t size = s_addr - FLASH_BANK2; - uint32_t crc = mg_crc32(0, (char *) FLASH_BANK2, size); - if (crc == s_crc32) { - uint32_t now = (uint32_t) (mg_now() / 1000); - struct mg_ota_data od = {MG_OTA_MAGIC, crc, size, now, -1, -1}; - ok = flash_write(FLASH_BANK2_OTA_DATA, &od, sizeof(od)); + if (s_size) { + size_t size = s_addr - base; + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) { + uint32_t now = (uint32_t) (mg_now() / 1000); + struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT}; + uint32_t key = MG_OTADATA_KEY + (mg_flash_bank() == 2 ? 1 : 2); + ok = mg_flash_save(NULL, key, &od, sizeof(od)); + } + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); + s_size = 0; + if (ok) ok = mg_flash_swap_bank(); } - MG_DEBUG(("CRC check: %x %x", s_crc32, crc)); - return ok && flash_swap_bank(); + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; } -bool mg_ota_status(struct mg_ota_data od[2]) { - od[0] = *(struct mg_ota_data *) FLASH_BANK1_OTA_DATA; - od[1] = *(struct mg_ota_data *) FLASH_BANK2_OTA_DATA; - return od[0].magic == MG_OTA_MAGIC; +static struct mg_otadata mg_otadata(int fw) { + struct mg_otadata od = {}; + int bank = mg_flash_bank(); + uint32_t key = MG_OTADATA_KEY + 1; + if ((fw == MG_FIRMWARE_CURRENT && bank == 2)) key++; + if ((fw == MG_FIRMWARE_PREVIOUS && bank == 1)) key++; + mg_flash_load(NULL, key, &od, sizeof(od)); + // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key)); + // mg_hexdump(&od, sizeof(od)); + return od; +} + +int mg_ota_status(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.status; +} +uint32_t mg_ota_crc32(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.crc32; +} +uint32_t mg_ota_timestamp(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.timestamp; +} +size_t mg_ota_size(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.size; } bool mg_ota_commit(void) { - struct mg_ota_data *od = (struct mg_ota_data *) FLASH_BANK1_OTA_DATA; + struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT); + od.status = MG_OTA_COMMITTED; + uint32_t key = MG_OTADATA_KEY + mg_flash_bank(); + return mg_flash_save(NULL, key, &od, sizeof(od)); +} + +bool mg_ota_rollback(void) { + MG_DEBUG(("Rolling firmware back")); + return mg_flash_swap_bank(); +} + +// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1) +// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an +// object, we pad it at the end for alignment. +// +// Objects in the flash sector are stored sequentially: +// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ...... +// +// In order to get to the next object, read its size, then align up. + +// Traverse the list of saved objects +size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) { + size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p; + uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2; + if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) { + if (size) *size = (size_t) p32[0]; + if (key) *key = p32[1]; + aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align); + if (left < aligned_size) aligned_size = 0; // Out of bounds, fail + } + return aligned_size; +} + +// Return the last sector of Bank 2 +static char *flash_last_sector(void) { + size_t ss = mg_flash_sector_size(), size = mg_flash_size(); + char *base = (char *) mg_flash_start(), *last = base + size - ss; + if (mg_flash_bank() == 2) last -= size / 2; + return last; +} + +// Find a saved object with a given key +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { + char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL; + size_t ss = mg_flash_sector_size(), ofs = 0, n, sz; bool ok = false; - if (od->magic == MG_OTA_MAGIC && od->golden == 0) { - ok = true; // Already clean, do nothing - } else if (od->magic == MG_OTA_MAGIC) { // Dirty! - ok = w32((uint32_t) &od->golden, 0); + if (s == NULL) s = flash_last_sector(); + if (s < base || s >= base + mg_flash_size()) { + MG_ERROR(("%p is outsize of flash", sector)); + } else if (((s - base) % ss) != 0) { + MG_ERROR(("%p is not a sector boundary", sector)); + } else { + uint32_t k, scanned = 0; + while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) { + // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key)); + // mg_hexdump(s + ofs, n); + if (k == key && sz == len) { + res = s + ofs + sizeof(uint32_t) * 2; + memcpy(buf, res, len); // Copy object + ok = true; // Keep scanning for the newer versions of it + } + ofs += n, scanned++; + } + MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res)); } return ok; } -bool mg_ota_rollback(void) { - return flash_swap_bank(); +static bool mg_flash_writev(char *location, struct mg_str *strings, size_t n) { + size_t align = mg_flash_write_align(), i, j, k = 0, nwritten = 0; + char buf[align]; + bool ok = true; + for (i = 0; ok && i < n; i++) { + for (j = 0; ok && j < strings[i].len; j++) { + buf[k++] = strings[i].ptr[j]; + if (k >= sizeof(buf)) { + ok = mg_flash_write(location + nwritten, buf, sizeof(buf)); + k = 0, nwritten += sizeof(buf); + } + } + } + if (k > 0) { + while (k < sizeof(buf)) buf[k++] = 0xff; + ok = mg_flash_write(location + nwritten, buf, sizeof(buf)); + } + return ok; +} + +// For all saved objects in the sector, delete old versions of objects +static void mg_flash_sector_cleanup(char *sector) { + // Buffer all saved objects into an IO buffer (backed by RAM) + // erase sector, and re-save them. + struct mg_iobuf io = {0, 0, 0, 2048}; + size_t ss = mg_flash_sector_size(); + size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2; + uint32_t key; + // Traverse all objects + MG_DEBUG(("Cleaning up sector %p", sector)); + while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) { + // Delete an old copy of this object in the cache + for (size_t o = 0; o < io.len; o += size2 + hs) { + uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t)); + size2 = *(uint32_t *) (io.buf + o); + if (k == key) { + mg_iobuf_del(&io, o, size2 + hs); + break; + } + } + // And add the new copy + mg_iobuf_add(&io, io.len, sector + ofs, size + hs); + ofs += n; + } + // All objects are cached in RAM now + if (mg_flash_erase(sector)) { // Erase sector. If successful, + for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse cached objects + size = *(uint32_t *) (io.buf + ofs); + key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t)); + mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to flash + } + } + mg_iobuf_free(&io); +} + +// Save an object with a given key - append to the end of an object list +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { + char *base = (char *) mg_flash_start(), *s = (char *) sector; + size_t ss = mg_flash_sector_size(), ofs = 0, n; + bool ok = false; + if (s == NULL) s = flash_last_sector(); + if (s < base || s >= base + mg_flash_size()) { + MG_ERROR(("%p is outsize of flash", sector)); + } else if (((s - base) % ss) != 0) { + MG_ERROR(("%p is not a sector boundary", sector)); + } else { + size_t needed = sizeof(uint32_t) * 2 + len; + size_t needed_aligned = MG_ROUND_UP(needed, mg_flash_write_align()); + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; + + // If there is not enough space left, cleanup sector and re-eval ofs + if (ofs + needed_aligned > ss) { + mg_flash_sector_cleanup(s); + ofs = 0; + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; + } + + if (ofs + needed_aligned <= ss) { + // Enough space to save this object + uint32_t hdr[2] = {(uint32_t) len, key}; + struct mg_str data[] = {mg_str_n((char *) hdr, sizeof(hdr)), + mg_str_n(buf, len)}; + ok = mg_flash_writev(s + ofs, data, 2); + MG_DEBUG(("Saving %lu bytes @ %p, key %x: %d", len, s + ofs, key, ok)); + MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned)); + } else { + MG_ERROR(("Sector is full")); + } + } + return ok; } #endif @@ -6758,6 +6844,306 @@ bool mg_path_is_sane(const char *path) { return true; } +#ifdef MG_ENABLE_LINES +#line 1 "src/sys_stm32h5.c" +#endif + + + + +#if MG_ENABLE_STM32H5 + +#define FLASH_BASE 0x40022000 // Base address of the flash controller +#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 +#define FLASH_OPTKEYR (FLASH_BASE + 0xc) +#define FLASH_OPTCR (FLASH_BASE + 0x1c) +#define FLASH_NSSR (FLASH_BASE + 0x20) +#define FLASH_NSCR (FLASH_BASE + 0x28) +#define FLASH_NSCCR (FLASH_BASE + 0x30) +#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) +#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) + +void *mg_flash_start(void) { + return (void *) 0x08000000; +} +size_t mg_flash_size(void) { + return 2 * 1024 * 1024; // 2Mb +} +size_t mg_flash_sector_size(void) { + return 8 * 1024; // 8k +} +size_t mg_flash_write_align(void) { + return 16; // 128 bit +} +int mg_flash_bank(void) { + return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; +} + +static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0Xcdef89ab; + MG_REG(FLASH_OPTKEYR) = 0x08192a3b; + MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f; + unlocked = true; + } +} + +static int flash_page_start(volatile uint32_t *dst) { + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; +} + +static bool flash_is_err(void) { + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9 +} + +static void flash_wait(void) { + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) && + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) { + (void) 0; + } +} + +static void flash_clear_err(void) { + flash_wait(); // Wait until ready + MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all errors +} + +static bool flash_bank_is_swapped(void) { + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 +} + +bool mg_flash_erase(void *location) { + bool ok = false; + if (flash_page_start(location) == false) { + MG_ERROR(("%p is not on a sector boundary")); + } else { + uintptr_t diff = (char *) location - (char *) mg_flash_start(); + uint32_t sector = diff / mg_flash_sector_size(); + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = 0; + if ((sector < 128 && flash_bank_is_swapped()) || + (sector > 127 && !flash_bank_is_swapped())) { + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL + } + if (sector > 127) sector -= 128; + MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase | sector_num + MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing + flash_wait(); + ok = !flash_is_err(); + MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, + ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); + // mg_hexdump(location, 32); + } + return ok; +} + +bool mg_flash_swap_bank(void) { + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +bool mg_flash_write(void *addr, const void *buf, size_t len) { + if ((len % mg_flash_write_align()) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); + return false; + } + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + flash_unlock(); + flash_clear_err(); + MG_ARM_DISABLE_IRQ(); + // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); + while (ok && src < end) { + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag + *(volatile uint32_t *) dst++ = *src++; + flash_wait(); + if (flash_is_err()) ok = false; + } + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), + MG_REG(FLASH_NSSR))); + if (flash_is_err()) ok = false; + // mg_hexdump(addr, len > 32 ? 32 : len); + // MG_REG(FLASH_NSCR) &= ~MG_BIT(1); // Set programming flag + MG_REG(FLASH_NSCR) = 0; // Clear flags + MG_ARM_ENABLE_IRQ(); + return ok; +} + +void mg_sys_reset(void) { + // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/sys_stm32h7.c" +#endif + + + + +#if MG_ENABLE_STM32H7 + +#define FLASH_BASE1 0x52002000 // Base address for bank1 +#define FLASH_BASE2 0x52002100 // Base address for bank2 +#define FLASH_KEYR 0x04 // See RM0433 4.9.2 +#define FLASH_OPTKEYR 0x08 +#define FLASH_OPTCR 0x18 +#define FLASH_SR 0x10 +#define FLASH_CR 0x0c +#define FLASH_CCR 0x14 +#define FLASH_OPTSR_CUR 0x1c +#define FLASH_OPTSR_PRG 0x20 + +void *mg_flash_start(void) { + return (void *) 0x08000000; +} +size_t mg_flash_size(void) { + return 2 * 1024 * 1024; // 2Mb +} +size_t mg_flash_sector_size(void) { + return 128 * 1024; // 128k +} +size_t mg_flash_write_align(void) { + return 32; // 256 bit +} +int mg_flash_bank(void) { + return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; +} + +static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once + unlocked = true; + } +} + +static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; +} + +static bool flash_is_err(uint32_t bank) { + return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 +} + +static void flash_wait(uint32_t bank) { + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; +} + +static void flash_clear_err(uint32_t bank) { + flash_wait(bank); // Wait until ready + MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors +} + +static bool flash_bank_is_swapped(uint32_t bank) { + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 +} + +// Figure out flash bank based on the address +static uint32_t flash_bank(void *addr) { + size_t ofs = (char *) addr - (char *) mg_flash_start(); + return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2; +} + +bool mg_flash_erase(void *addr) { + bool ok = false; + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + } else { + uintptr_t diff = (char *) addr - (char *) mg_flash_start(); + uint32_t sector = diff / mg_flash_sector_size(); + uint32_t bank = flash_bank(addr); + + flash_unlock(); + if (sector > 7) sector -= 8; + // MG_INFO(("Erasing @ %p, sector %lu, bank %#x", addr, sector, bank)); + + flash_clear_err(bank); + MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase + MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit + MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing + ok = !flash_is_err(bank); + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + // mg_hexdump(addr, 32); + } + return ok; +} + +bool mg_flash_swap_bank() { + uint32_t bank = FLASH_BASE1; + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(bank); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +bool mg_flash_write(void *addr, const void *buf, size_t len) { + if ((len % mg_flash_write_align()) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); + return false; + } + uint32_t bank = flash_bank(addr); + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + flash_unlock(); + flash_clear_err(bank); + MG_ARM_DISABLE_IRQ(); + MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag + // MG_INFO(("Writing flash @ %p, %lu bytes", addr, len)); + while (ok && src < end) { + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + *(volatile uint32_t *) dst++ = *src++; + flash_wait(bank); + if (flash_is_err(bank)) ok = false; + } + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + // mg_hexdump(addr, len > 32 ? 32 : len); + MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag + MG_ARM_ENABLE_IRQ(); + return ok; +} + +void mg_sys_reset(void) { + // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/timer.c" #endif diff --git a/mongoose.h b/mongoose.h index 29becf93..354d8d02 100644 --- a/mongoose.h +++ b/mongoose.h @@ -815,6 +815,14 @@ struct timeval { #define MG_EPOLL_MOD(c, wr) #endif +#ifndef MG_ENABLE_STM32H5 +#define MG_ENABLE_STM32H5 0 +#endif + +#ifndef MG_ENABLE_STM32H7 +#define MG_ENABLE_STM32H7 0 +#endif + @@ -1041,6 +1049,21 @@ uint64_t mg_now(void); // Return milliseconds since Epoch #define MG_IPADDR_PARTS(ADDR) \ MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3] +#define MG_REG(x) ((volatile uint32_t *) (x))[0] +#define MG_BIT(x) (((uint32_t) 1U) << (x)) +#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) & ~(CLRMASK)) | (SETMASK) + +#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) / (a)) * (a))) +#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a))) + +#ifdef __GNUC__ +#define MG_ARM_DISABLE_IRQ() asm volatile ("cpsid i" : : : "memory") +#define MG_ARM_ENABLE_IRQ() asm volatile ("cpsie i" : : : "memory") +#else +#define MG_ARM_DISABLE_IRQ() +#define MG_ARM_ENABLE_IRQ() +#endif + struct mg_addr; int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip); @@ -1652,33 +1675,56 @@ void mg_rpc_list(struct mg_rpc_req *r); #define MG_OTA_NONE 0 // No OTA support -#define MG_OTA_STM32H5 1 // STM32 H5 series +#define MG_OTA_FLASH 1 // OTA via an internal flash #define MG_OTA_CUSTOM 100 // Custom implementation -#define MG_OTA_MAGIC 0xb07afeed - #ifndef MG_OTA #define MG_OTA MG_OTA_NONE #endif // Firmware update API bool mg_ota_begin(size_t new_firmware_size); // Start writing -bool mg_ota_write(const void *buf, size_t len); // Write firmware chunk +bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k bool mg_ota_end(void); // Stop writing -void mg_sys_reset(void); // Reboot device -struct mg_ota_data { - uint32_t magic; // Must be MG_OTA_MAGIC - uint32_t crc32; // Checksum of the current firmware - uint32_t size; // Firware size - uint32_t time; // Flashing timestamp. Unix epoch, seconds since 1970 - uint32_t booted; // -1: not yet booted before, otherwise booted - uint32_t golden; // -1: not yet comitted, otherwise clean, committed +enum { + MG_OTA_UNAVAILABLE = 0, // No OTA information is present + MG_OTA_FIRST_BOOT = 1, // Device booting the first time after the OTA + MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback + MG_OTA_COMMITTED = 3, // The firmware is good }; +enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 }; -bool mg_ota_status(struct mg_ota_data[2]); // Get status for curr and prev fw -bool mg_ota_commit(void); // Commit current firmware -bool mg_ota_rollback(void); // Rollback to prev firmware +int mg_ota_status(int firmware); // Return firmware status MG_OTA_* +uint32_t mg_ota_crc32(int firmware); // Return firmware checksum +uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch +size_t mg_ota_size(int firmware); // Firmware size + +bool mg_ota_commit(void); // Commit current firmware +bool mg_ota_rollback(void); // Rollback to the previous firmware + +void mg_sys_reset(void); // Reboot device immediately +// Copyright (c) 2023 Cesanta Software Limited +// All rights reserved + + + +// Flash information +void *mg_flash_start(void); // Return flash start address +size_t mg_flash_size(void); // Return flash size +size_t mg_flash_sector_size(void); // Return flash sector size +size_t mg_flash_write_align(void); // Return flash write align, minimum 4 +int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2 + +// Write, erase, swap bank +bool mg_flash_write(void *addr, const void *buf, size_t len); +bool mg_flash_erase(void *addr); // Must be at sector boundary +bool mg_flash_swap_bank(void); + +// Convenience functions to store data on a flash sector with wear levelling +// If `sector` is NULL, then the last sector of flash is used +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len); +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len); diff --git a/src/config.h b/src/config.h index f69da657..7314105f 100644 --- a/src/config.h +++ b/src/config.h @@ -149,3 +149,11 @@ #define MG_EPOLL_ADD(c) #define MG_EPOLL_MOD(c, wr) #endif + +#ifndef MG_ENABLE_STM32H5 +#define MG_ENABLE_STM32H5 0 +#endif + +#ifndef MG_ENABLE_STM32H7 +#define MG_ENABLE_STM32H7 0 +#endif diff --git a/src/ota.h b/src/ota.h index 2ffeca44..3a979e12 100644 --- a/src/ota.h +++ b/src/ota.h @@ -6,30 +6,32 @@ #include "arch.h" #define MG_OTA_NONE 0 // No OTA support -#define MG_OTA_STM32H5 1 // STM32 H5 series +#define MG_OTA_FLASH 1 // OTA via an internal flash #define MG_OTA_CUSTOM 100 // Custom implementation -#define MG_OTA_MAGIC 0xb07afeed - #ifndef MG_OTA #define MG_OTA MG_OTA_NONE #endif // Firmware update API bool mg_ota_begin(size_t new_firmware_size); // Start writing -bool mg_ota_write(const void *buf, size_t len); // Write firmware chunk +bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k bool mg_ota_end(void); // Stop writing -void mg_sys_reset(void); // Reboot device -struct mg_ota_data { - uint32_t magic; // Must be MG_OTA_MAGIC - uint32_t crc32; // Checksum of the current firmware - uint32_t size; // Firware size - uint32_t time; // Flashing timestamp. Unix epoch, seconds since 1970 - uint32_t booted; // -1: not yet booted before, otherwise booted - uint32_t golden; // -1: not yet comitted, otherwise clean, committed +enum { + MG_OTA_UNAVAILABLE = 0, // No OTA information is present + MG_OTA_FIRST_BOOT = 1, // Device booting the first time after the OTA + MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback + MG_OTA_COMMITTED = 3, // The firmware is good }; +enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 }; -bool mg_ota_status(struct mg_ota_data[2]); // Get status for curr and prev fw -bool mg_ota_commit(void); // Commit current firmware -bool mg_ota_rollback(void); // Rollback to prev firmware +int mg_ota_status(int firmware); // Return firmware status MG_OTA_* +uint32_t mg_ota_crc32(int firmware); // Return firmware checksum +uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch +size_t mg_ota_size(int firmware); // Firmware size + +bool mg_ota_commit(void); // Commit current firmware +bool mg_ota_rollback(void); // Rollback to the previous firmware + +void mg_sys_reset(void); // Reboot device immediately diff --git a/src/ota_dummy.c b/src/ota_dummy.c index 31f56021..25b81831 100644 --- a/src/ota_dummy.c +++ b/src/ota_dummy.c @@ -2,36 +2,39 @@ #include "ota.h" #if MG_OTA == MG_OTA_NONE -static struct mg_ota_data s_od[2] = { - {MG_OTA_MAGIC, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, - (uint32_t) -1}, - {MG_OTA_MAGIC, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, - (uint32_t) -1}, -}; bool mg_ota_begin(size_t new_firmware_size) { - MG_DEBUG(("Starting firmware update, size %lu", new_firmware_size)); + (void) new_firmware_size; return true; } bool mg_ota_write(const void *buf, size_t len) { - MG_DEBUG(("%p %lu", buf, len)); + (void) buf, (void) len; return true; } bool mg_ota_end(void) { return true; } -bool mg_ota_status(struct mg_ota_data od[2]) { - od[0] = s_od[0]; - od[1] = s_od[1]; - return true; -} bool mg_ota_commit(void) { - s_od[0].golden = 0; return true; } bool mg_ota_rollback(void) { return true; } +int mg_ota_status(int fw) { + (void) fw; + return 0; +} +uint32_t mg_ota_crc32(int fw) { + (void) fw; + return 0; +} +uint32_t mg_ota_timestamp(int fw) { + (void) fw; + return 0; +} +size_t mg_ota_size(int fw) { + (void) fw; + return 0; +} void mg_sys_reset(void) { - MG_DEBUG(("Resetting device...")); } #endif diff --git a/src/ota_flash.c b/src/ota_flash.c new file mode 100644 index 00000000..c87df0d1 --- /dev/null +++ b/src/ota_flash.c @@ -0,0 +1,274 @@ +#include "arch.h" +#include "log.h" +#include "ota.h" +#include "sys.h" + +// This OTA implementation uses the internal flash API outlined in sys.h +// It splits flash into 2 equal partitions, and stores OTA status in the +// last sector of the partition. + +#if MG_OTA == MG_OTA_FLASH + +#define MG_OTADATA_KEY 0xb07afed0 + +static char *s_addr; // Current address to write to +static size_t s_size; // Firmware size to flash. In-progress indicator +static uint32_t s_crc32; // Firmware checksum + +struct mg_otadata { + uint32_t crc32, size, timestamp, status; +}; + +bool mg_ota_begin(size_t new_firmware_size) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size(); + s_crc32 = 0; + s_addr = (char *) mg_flash_start() + half; + MG_DEBUG(("Firmware %lu bytes, max %lu", s_size, max)); + if (new_firmware_size < max) { + ok = true; + s_size = new_firmware_size; + MG_INFO(("Starting OTA, firmware size %lu", s_size)); + } else { + MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, max)); + } + } + return ok; +} + +bool mg_ota_write(const void *buf, size_t len) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t align = mg_flash_write_align(); + size_t len_aligned_down = MG_ROUND_DOWN(len, align); + if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp)); + } + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); + s_addr += len; + } + return ok; +} + +bool mg_ota_end(void) { + char *base = (char *) mg_flash_start() + mg_flash_size() / 2; + bool ok = false; + if (s_size) { + size_t size = s_addr - base; + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) { + uint32_t now = (uint32_t) (mg_now() / 1000); + struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT}; + uint32_t key = MG_OTADATA_KEY + (mg_flash_bank() == 2 ? 1 : 2); + ok = mg_flash_save(NULL, key, &od, sizeof(od)); + } + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); + s_size = 0; + if (ok) ok = mg_flash_swap_bank(); + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; +} + +static struct mg_otadata mg_otadata(int fw) { + struct mg_otadata od = {}; + int bank = mg_flash_bank(); + uint32_t key = MG_OTADATA_KEY + 1; + if ((fw == MG_FIRMWARE_CURRENT && bank == 2)) key++; + if ((fw == MG_FIRMWARE_PREVIOUS && bank == 1)) key++; + mg_flash_load(NULL, key, &od, sizeof(od)); + // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key)); + // mg_hexdump(&od, sizeof(od)); + return od; +} + +int mg_ota_status(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.status; +} +uint32_t mg_ota_crc32(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.crc32; +} +uint32_t mg_ota_timestamp(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.timestamp; +} +size_t mg_ota_size(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.size; +} + +bool mg_ota_commit(void) { + struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT); + od.status = MG_OTA_COMMITTED; + uint32_t key = MG_OTADATA_KEY + mg_flash_bank(); + return mg_flash_save(NULL, key, &od, sizeof(od)); +} + +bool mg_ota_rollback(void) { + MG_DEBUG(("Rolling firmware back")); + return mg_flash_swap_bank(); +} + +// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1) +// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an +// object, we pad it at the end for alignment. +// +// Objects in the flash sector are stored sequentially: +// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ...... +// +// In order to get to the next object, read its size, then align up. + +// Traverse the list of saved objects +size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) { + size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p; + uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2; + if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) { + if (size) *size = (size_t) p32[0]; + if (key) *key = p32[1]; + aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align); + if (left < aligned_size) aligned_size = 0; // Out of bounds, fail + } + return aligned_size; +} + +// Return the last sector of Bank 2 +static char *flash_last_sector(void) { + size_t ss = mg_flash_sector_size(), size = mg_flash_size(); + char *base = (char *) mg_flash_start(), *last = base + size - ss; + if (mg_flash_bank() == 2) last -= size / 2; + return last; +} + +// Find a saved object with a given key +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { + char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL; + size_t ss = mg_flash_sector_size(), ofs = 0, n, sz; + bool ok = false; + if (s == NULL) s = flash_last_sector(); + if (s < base || s >= base + mg_flash_size()) { + MG_ERROR(("%p is outsize of flash", sector)); + } else if (((s - base) % ss) != 0) { + MG_ERROR(("%p is not a sector boundary", sector)); + } else { + uint32_t k, scanned = 0; + while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) { + // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key)); + // mg_hexdump(s + ofs, n); + if (k == key && sz == len) { + res = s + ofs + sizeof(uint32_t) * 2; + memcpy(buf, res, len); // Copy object + ok = true; // Keep scanning for the newer versions of it + } + ofs += n, scanned++; + } + MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res)); + } + return ok; +} + +static bool mg_flash_writev(char *location, struct mg_str *strings, size_t n) { + size_t align = mg_flash_write_align(), i, j, k = 0, nwritten = 0; + char buf[align]; + bool ok = true; + for (i = 0; ok && i < n; i++) { + for (j = 0; ok && j < strings[i].len; j++) { + buf[k++] = strings[i].ptr[j]; + if (k >= sizeof(buf)) { + ok = mg_flash_write(location + nwritten, buf, sizeof(buf)); + k = 0, nwritten += sizeof(buf); + } + } + } + if (k > 0) { + while (k < sizeof(buf)) buf[k++] = 0xff; + ok = mg_flash_write(location + nwritten, buf, sizeof(buf)); + } + return ok; +} + +// For all saved objects in the sector, delete old versions of objects +static void mg_flash_sector_cleanup(char *sector) { + // Buffer all saved objects into an IO buffer (backed by RAM) + // erase sector, and re-save them. + struct mg_iobuf io = {0, 0, 0, 2048}; + size_t ss = mg_flash_sector_size(); + size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2; + uint32_t key; + // Traverse all objects + MG_DEBUG(("Cleaning up sector %p", sector)); + while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) { + // Delete an old copy of this object in the cache + for (size_t o = 0; o < io.len; o += size2 + hs) { + uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t)); + size2 = *(uint32_t *) (io.buf + o); + if (k == key) { + mg_iobuf_del(&io, o, size2 + hs); + break; + } + } + // And add the new copy + mg_iobuf_add(&io, io.len, sector + ofs, size + hs); + ofs += n; + } + // All objects are cached in RAM now + if (mg_flash_erase(sector)) { // Erase sector. If successful, + for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse cached objects + size = *(uint32_t *) (io.buf + ofs); + key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t)); + mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to flash + } + } + mg_iobuf_free(&io); +} + +// Save an object with a given key - append to the end of an object list +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { + char *base = (char *) mg_flash_start(), *s = (char *) sector; + size_t ss = mg_flash_sector_size(), ofs = 0, n; + bool ok = false; + if (s == NULL) s = flash_last_sector(); + if (s < base || s >= base + mg_flash_size()) { + MG_ERROR(("%p is outsize of flash", sector)); + } else if (((s - base) % ss) != 0) { + MG_ERROR(("%p is not a sector boundary", sector)); + } else { + size_t needed = sizeof(uint32_t) * 2 + len; + size_t needed_aligned = MG_ROUND_UP(needed, mg_flash_write_align()); + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; + + // If there is not enough space left, cleanup sector and re-eval ofs + if (ofs + needed_aligned > ss) { + mg_flash_sector_cleanup(s); + ofs = 0; + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; + } + + if (ofs + needed_aligned <= ss) { + // Enough space to save this object + uint32_t hdr[2] = {(uint32_t) len, key}; + struct mg_str data[] = {mg_str_n((char *) hdr, sizeof(hdr)), + mg_str_n(buf, len)}; + ok = mg_flash_writev(s + ofs, data, 2); + MG_DEBUG(("Saving %lu bytes @ %p, key %x: %d", len, s + ofs, key, ok)); + MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned)); + } else { + MG_ERROR(("Sector is full")); + } + } + return ok; +} +#endif diff --git a/src/ota_stm32h5.c b/src/ota_stm32h5.c deleted file mode 100644 index 34b3dbf1..00000000 --- a/src/ota_stm32h5.c +++ /dev/null @@ -1,191 +0,0 @@ -#include "arch.h" -#include "log.h" -#include "ota.h" - -#if MG_OTA == MG_OTA_STM32H5 - -#define FLASH_BANK1 0x08000000 -#define FLASH_SIZE1 0x00100000 -#define FLASH_BANK2 0x08100000 -#define FLASH_SIZE2 0x00100000 -#define FLASH_SSIZE 8192 // Sector (page) size, 8k - -// Keep OTA data at the beginning of the last sector of the flash bank -#define FLASH_BANK1_OTA_DATA (FLASH_BANK1 + FLASH_SIZE1 - FLASH_SSIZE) -#define FLASH_BANK2_OTA_DATA (FLASH_BANK2 + FLASH_SIZE2 - FLASH_SSIZE) - -#define FLASH_BASE 0x40022000 // Base address of the flash controller -#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 -#define FLASH_OPTKEYR (FLASH_BASE + 0xc) -#define FLASH_OPTCR (FLASH_BASE + 0x1c) -#define FLASH_NSSR (FLASH_BASE + 0x20) -#define FLASH_NSCR (FLASH_BASE + 0x28) -#define FLASH_NSCCR (FLASH_BASE + 0x30) -#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) -#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) - -#undef REG -#define REG(x) ((volatile uint32_t *) (x))[0] -#undef BIT -#define BIT(x) (((uint32_t) 1U) << (x)) -#undef SETBITS -#define SETBITS(R, CLEARMASK, SETMASK) (R) = ((R) & ~(CLEARMASK)) | (SETMASK) - -static uint32_t s_addr; // Current address to write to -static uint32_t s_crc32; // Firmware checksum - -static void flash_unlock(void) { - static bool unlocked = false; - if (unlocked == false) { - REG(FLASH_KEYR) = 0x45670123; - REG(FLASH_KEYR) = 0Xcdef89ab; - REG(FLASH_OPTKEYR) = 0x08192a3b; - REG(FLASH_OPTKEYR) = 0x4c5d6e7f; - unlocked = true; - } -} - -static int flash_page_start(volatile uint32_t *dst) { - uint32_t addr = (uint32_t) (uintptr_t) dst; - return (addr & (FLASH_SSIZE - 1)) == 0; -} - -static bool flash_is_err(void) { - return REG(FLASH_NSSR) & ((BIT(8) - 1) << 17); // RM0481 7.11.9 -} - -static void flash_wait(void) { - while ((REG(FLASH_NSSR) & BIT(0)) && (REG(FLASH_NSSR) & BIT(16)) == 0) { - (void) 0; - } -} - -static void flash_prep(void) { - flash_wait(); // Wait until ready - REG(FLASH_NSCR) = 0UL; // Clear control reg - REG(FLASH_NSCCR) = ((BIT(9) - 1) << 16U); // Clear all errors -} - -static bool flash_bank_is_swapped(void) { - return REG(FLASH_OPTCR) & BIT(31); // RM0481 7.11.8 -} - -static void flash_erase(uint32_t sector) { - flash_prep(); - if ((sector < 128 && flash_bank_is_swapped()) || - (sector > 127 && !flash_bank_is_swapped())) { - REG(FLASH_NSCR) |= BIT(31); // Set FLASH_CR_BKSEL - } - if (sector > 127) sector -= 128; - REG(FLASH_NSCR) |= BIT(2) | (sector << 6); // SectorErase | sector_num - REG(FLASH_NSCR) |= BIT(5); // Start erasing - // MG_INFO(("ERASE %lu, CR %#lx SR %#lx", sector, REG(FLASH_NSCR), - // REG(FLASH_NSSR))); - flash_prep(); -} - -static bool flash_swap_bank(void) { - uint32_t desired = flash_bank_is_swapped() ? 0 : BIT(31); - flash_unlock(); - flash_prep(); - // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); - SETBITS(REG(FLASH_OPTSR_PRG), BIT(31), desired); - // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); - REG(FLASH_OPTCR) |= BIT(1); // OPTSTART - while ((REG(FLASH_OPTSR_CUR) & BIT(31)) != desired) (void) 0; - return true; -} - -static bool flash_write(uint32_t addr, const void *buf, size_t len) { - volatile uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool success = true; - flash_unlock(); - flash_prep(); - while (success && src < end) { - uint32_t pageno = ((uint32_t) dst - FLASH_BANK1) / FLASH_SSIZE; - if (flash_page_start(dst)) flash_erase(pageno); - REG(FLASH_NSCR) = BIT(1); // Set programming flag - do { - *dst++ = *src++; - flash_wait(); - } while (src < end && !flash_page_start(dst) && !flash_is_err()); -#if 0 - do { - *dst++ = *src++; - printf("WRITE %p, CR %#lx SR %#lx OCR %#lx %#lx %#lx\n", dst, FLASH->NSCR, - FLASH->NSSR, FLASH->OPTCR, src[-1], dst[-1]); - } while (src < end && !flash_page_start(dst)); -#endif - // if (FLASH->NSSR & FLASH_SR_WBNE) FLASH->NSCR |= FLASH_CR_FW; - if (REG(FLASH_NSSR) & BIT(1)) REG(FLASH_NSCR) |= BIT(4); - MG_INFO(("W %p, CR %#lx SR %#lx OCR %#lx %#lx %#lx", dst, REG(FLASH_NSCR), - REG(FLASH_NSSR), REG(FLASH_OPTCR), src[-1], dst[-1])); - flash_wait(); - if (flash_is_err() || (src[-1] != dst[-1])) { - // printf(" E %lx CR %#lx SR %#lx, %#lx %#lx\n", (unsigned long) dst, - // FLASH->NSCR, FLASH->NSSR, src[-1], dst[-1]); - success = false; - } - } - return success; -} -void mg_sys_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; -} - -bool mg_ota_begin(size_t new_firmware_size) { - s_crc32 = 0; - s_addr = FLASH_BANK2; - MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, FLASH_SIZE2)); - return new_firmware_size < FLASH_SIZE2; -} - -bool mg_ota_write(const void *buf, size_t len) { - bool ok = flash_write(s_addr, buf, len); // Write chunk to flash - s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC - MG_DEBUG(("%#x %p %lu -> %d", s_addr, buf, len, ok)); - s_addr += len; - return ok; -} - -static bool w32(uint32_t addr, uint32_t value) { - return flash_write(addr, &value, sizeof(value)); -} - -bool mg_ota_end(void) { - bool ok = false; - size_t size = s_addr - FLASH_BANK2; - uint32_t crc = mg_crc32(0, (char *) FLASH_BANK2, size); - if (crc == s_crc32) { - uint32_t now = (uint32_t) (mg_now() / 1000); - struct mg_ota_data od = {MG_OTA_MAGIC, crc, size, now, -1, -1}; - ok = flash_write(FLASH_BANK2_OTA_DATA, &od, sizeof(od)); - } - MG_DEBUG(("CRC check: %x %x", s_crc32, crc)); - return ok && flash_swap_bank(); -} - -bool mg_ota_status(struct mg_ota_data od[2]) { - od[0] = *(struct mg_ota_data *) FLASH_BANK1_OTA_DATA; - od[1] = *(struct mg_ota_data *) FLASH_BANK2_OTA_DATA; - return od[0].magic == MG_OTA_MAGIC; -} - -bool mg_ota_commit(void) { - struct mg_ota_data *od = (struct mg_ota_data *) FLASH_BANK1_OTA_DATA; - bool ok = false; - if (od->magic == MG_OTA_MAGIC && od->golden == 0) { - ok = true; // Already clean, do nothing - } else if (od->magic == MG_OTA_MAGIC) { // Dirty! - ok = w32((uint32_t) &od->golden, 0); - } - return ok; -} - -bool mg_ota_rollback(void) { - return flash_swap_bank(); -} -#endif diff --git a/src/sys.h b/src/sys.h new file mode 100644 index 00000000..94897220 --- /dev/null +++ b/src/sys.h @@ -0,0 +1,21 @@ +// Copyright (c) 2023 Cesanta Software Limited +// All rights reserved + +#pragma once + +// Flash information +void *mg_flash_start(void); // Return flash start address +size_t mg_flash_size(void); // Return flash size +size_t mg_flash_sector_size(void); // Return flash sector size +size_t mg_flash_write_align(void); // Return flash write align, minimum 4 +int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2 + +// Write, erase, swap bank +bool mg_flash_write(void *addr, const void *buf, size_t len); +bool mg_flash_erase(void *addr); // Must be at sector boundary +bool mg_flash_swap_bank(void); + +// Convenience functions to store data on a flash sector with wear levelling +// If `sector` is NULL, then the last sector of flash is used +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len); +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len); diff --git a/src/sys_stm32h5.c b/src/sys_stm32h5.c new file mode 100644 index 00000000..63185338 --- /dev/null +++ b/src/sys_stm32h5.c @@ -0,0 +1,143 @@ +#include "arch.h" +#include "log.h" +#include "ota.h" + +#if MG_ENABLE_STM32H5 + +#define FLASH_BASE 0x40022000 // Base address of the flash controller +#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 +#define FLASH_OPTKEYR (FLASH_BASE + 0xc) +#define FLASH_OPTCR (FLASH_BASE + 0x1c) +#define FLASH_NSSR (FLASH_BASE + 0x20) +#define FLASH_NSCR (FLASH_BASE + 0x28) +#define FLASH_NSCCR (FLASH_BASE + 0x30) +#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) +#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) + +void *mg_flash_start(void) { + return (void *) 0x08000000; +} +size_t mg_flash_size(void) { + return 2 * 1024 * 1024; // 2Mb +} +size_t mg_flash_sector_size(void) { + return 8 * 1024; // 8k +} +size_t mg_flash_write_align(void) { + return 16; // 128 bit +} +int mg_flash_bank(void) { + return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; +} + +static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0Xcdef89ab; + MG_REG(FLASH_OPTKEYR) = 0x08192a3b; + MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f; + unlocked = true; + } +} + +static int flash_page_start(volatile uint32_t *dst) { + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; +} + +static bool flash_is_err(void) { + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9 +} + +static void flash_wait(void) { + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) && + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) { + (void) 0; + } +} + +static void flash_clear_err(void) { + flash_wait(); // Wait until ready + MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all errors +} + +static bool flash_bank_is_swapped(void) { + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 +} + +bool mg_flash_erase(void *location) { + bool ok = false; + if (flash_page_start(location) == false) { + MG_ERROR(("%p is not on a sector boundary")); + } else { + uintptr_t diff = (char *) location - (char *) mg_flash_start(); + uint32_t sector = diff / mg_flash_sector_size(); + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = 0; + if ((sector < 128 && flash_bank_is_swapped()) || + (sector > 127 && !flash_bank_is_swapped())) { + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL + } + if (sector > 127) sector -= 128; + MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase | sector_num + MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing + flash_wait(); + ok = !flash_is_err(); + MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, + ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); + // mg_hexdump(location, 32); + } + return ok; +} + +bool mg_flash_swap_bank(void) { + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +bool mg_flash_write(void *addr, const void *buf, size_t len) { + if ((len % mg_flash_write_align()) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); + return false; + } + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + flash_unlock(); + flash_clear_err(); + MG_ARM_DISABLE_IRQ(); + // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); + while (ok && src < end) { + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag + *(volatile uint32_t *) dst++ = *src++; + flash_wait(); + if (flash_is_err()) ok = false; + } + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), + MG_REG(FLASH_NSSR))); + if (flash_is_err()) ok = false; + // mg_hexdump(addr, len > 32 ? 32 : len); + // MG_REG(FLASH_NSCR) &= ~MG_BIT(1); // Set programming flag + MG_REG(FLASH_NSCR) = 0; // Clear flags + MG_ARM_ENABLE_IRQ(); + return ok; +} + +void mg_sys_reset(void) { + // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} +#endif diff --git a/src/sys_stm32h7.c b/src/sys_stm32h7.c new file mode 100644 index 00000000..1bdc585a --- /dev/null +++ b/src/sys_stm32h7.c @@ -0,0 +1,149 @@ +#include "arch.h" +#include "log.h" +#include "ota.h" + +#if MG_ENABLE_STM32H7 + +#define FLASH_BASE1 0x52002000 // Base address for bank1 +#define FLASH_BASE2 0x52002100 // Base address for bank2 +#define FLASH_KEYR 0x04 // See RM0433 4.9.2 +#define FLASH_OPTKEYR 0x08 +#define FLASH_OPTCR 0x18 +#define FLASH_SR 0x10 +#define FLASH_CR 0x0c +#define FLASH_CCR 0x14 +#define FLASH_OPTSR_CUR 0x1c +#define FLASH_OPTSR_PRG 0x20 + +void *mg_flash_start(void) { + return (void *) 0x08000000; +} +size_t mg_flash_size(void) { + return 2 * 1024 * 1024; // 2Mb +} +size_t mg_flash_sector_size(void) { + return 128 * 1024; // 128k +} +size_t mg_flash_write_align(void) { + return 32; // 256 bit +} +int mg_flash_bank(void) { + return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; +} + +static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once + unlocked = true; + } +} + +static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; +} + +static bool flash_is_err(uint32_t bank) { + return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 +} + +static void flash_wait(uint32_t bank) { + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; +} + +static void flash_clear_err(uint32_t bank) { + flash_wait(bank); // Wait until ready + MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors +} + +static bool flash_bank_is_swapped(uint32_t bank) { + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 +} + +// Figure out flash bank based on the address +static uint32_t flash_bank(void *addr) { + size_t ofs = (char *) addr - (char *) mg_flash_start(); + return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2; +} + +bool mg_flash_erase(void *addr) { + bool ok = false; + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + } else { + uintptr_t diff = (char *) addr - (char *) mg_flash_start(); + uint32_t sector = diff / mg_flash_sector_size(); + uint32_t bank = flash_bank(addr); + + flash_unlock(); + if (sector > 7) sector -= 8; + // MG_INFO(("Erasing @ %p, sector %lu, bank %#x", addr, sector, bank)); + + flash_clear_err(bank); + MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase + MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit + MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing + ok = !flash_is_err(bank); + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + // mg_hexdump(addr, 32); + } + return ok; +} + +bool mg_flash_swap_bank() { + uint32_t bank = FLASH_BASE1; + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(bank); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +bool mg_flash_write(void *addr, const void *buf, size_t len) { + if ((len % mg_flash_write_align()) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); + return false; + } + uint32_t bank = flash_bank(addr); + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + flash_unlock(); + flash_clear_err(bank); + MG_ARM_DISABLE_IRQ(); + MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag + // MG_INFO(("Writing flash @ %p, %lu bytes", addr, len)); + while (ok && src < end) { + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + *(volatile uint32_t *) dst++ = *src++; + flash_wait(bank); + if (flash_is_err(bank)) ok = false; + } + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + // mg_hexdump(addr, len > 32 ? 32 : len); + MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag + MG_ARM_ENABLE_IRQ(); + return ok; +} + +void mg_sys_reset(void) { + // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} +#endif diff --git a/src/util.h b/src/util.h index 00ccb1f6..0ec33ff7 100644 --- a/src/util.h +++ b/src/util.h @@ -32,6 +32,21 @@ uint64_t mg_now(void); // Return milliseconds since Epoch #define MG_IPADDR_PARTS(ADDR) \ MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3] +#define MG_REG(x) ((volatile uint32_t *) (x))[0] +#define MG_BIT(x) (((uint32_t) 1U) << (x)) +#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) & ~(CLRMASK)) | (SETMASK) + +#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) / (a)) * (a))) +#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a))) + +#ifdef __GNUC__ +#define MG_ARM_DISABLE_IRQ() asm volatile ("cpsid i" : : : "memory") +#define MG_ARM_ENABLE_IRQ() asm volatile ("cpsie i" : : : "memory") +#else +#define MG_ARM_DISABLE_IRQ() +#define MG_ARM_ENABLE_IRQ() +#endif + struct mg_addr; int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip);