From 851b47f389d3c2e835e653e079a988896be59452 Mon Sep 17 00:00:00 2001 From: DatanoiseTV <6614616+DatanoiseTV@users.noreply.github.com> Date: Wed, 10 Jun 2026 20:05:55 +0200 Subject: [PATCH 1/4] docs: add blog post on Rust smoltcp as an alternative TCP/IP stack for ESP-IDF New community article under content/blog/2026/06/ covering the smoltcp ESP-IDF components: the linker --wrap BSD-socket compatibility shim, the single-task poll architecture, measured ESP32-P4 throughput (91.15 Mbit/s on a 100 Mbit Ethernet link), and the current limitations. Adds the sylwester-sosnowski author page, data entry, and avatar. Follows the suggestion in espressif/esp-idf#18549. --- assets/img/authors/sylwester-sosnowski.webp | Bin 0 -> 11590 bytes content/authors/sylwester-sosnowski/_index.md | 3 + .../index.md | 300 ++++++++++++++++++ data/authors/sylwester-sosnowski.json | 9 + 4 files changed, 312 insertions(+) create mode 100644 assets/img/authors/sylwester-sosnowski.webp create mode 100644 content/authors/sylwester-sosnowski/_index.md create mode 100644 content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md create mode 100644 data/authors/sylwester-sosnowski.json diff --git a/assets/img/authors/sylwester-sosnowski.webp b/assets/img/authors/sylwester-sosnowski.webp new file mode 100644 index 0000000000000000000000000000000000000000..3b1016a01324788ad608e00dcd86660319a3eb78 GIT binary patch literal 11590 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, and the +key detail is that they are defined as `static inline` wrappers: + +```c +/* lwip/sockets.h, simplified */ +static inline int socket(int domain, int type, int protocol) { + return lwip_socket(domain, type, protocol); +} +static inline int bind(int s, const struct sockaddr *name, socklen_t len) { + return lwip_bind(s, name, len); +} +``` + +So when `esp_http_server` calls `socket()`, the symbol that actually ends +up in the link is `lwip_socket()`. That's the seam the whole project hangs +on. + +GNU `ld` provides `--wrap=symbol`: every reference to `symbol` is redirected +to `__wrap_symbol`, and the original remains reachable as `__real_symbol`. +The component adds one of these for each BSD-socket entry point: + +``` +-Wl,--wrap=lwip_socket +-Wl,--wrap=lwip_bind +-Wl,--wrap=lwip_listen +-Wl,--wrap=lwip_accept +-Wl,--wrap=lwip_send +-Wl,--wrap=lwip_recv +-Wl,--wrap=lwip_select +/* ... and so on for the full BSD-socket surface */ +``` + +With that in place, every BSD-socket call site across the IDF — none of +which is modified — lands in a thin C shim that talks to smoltcp instead. +lwIP is still compiled into the image, because its headers define types the +IDF networking source needs, but its socket layer is never reached at +runtime. The application source change required to switch stacks is zero. + +## Using it + +The intended workflow is "install your own Ethernet driver, hand over the +handle, and let the component take it from there." You bring up `esp_eth` +the way your board requires — pins, PHY, clocks, all board-specific — and +then attach it: + +```c +#include "esp_smoltcp.h" + +void app_main(void) +{ + nvs_flash_init(); + esp_event_loop_create_default(); + + /* Your driver, your pins. The stack does not need to know the details. */ + esp_eth_handle_t eth = my_install_eth_driver(); + + esp_smoltcp_init(); + esp_smoltcp_attach_eth(eth); + esp_smoltcp_wait_for_ip(ESP_SMOLTCP_IFACE_ETH, 15000); /* DHCP by default */ + + /* BSD sockets work from here, so unmodified IDF code works too: */ + httpd_handle_t server; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + httpd_start(&server, &config); + /* register handlers as usual */ +} +``` + +`httpd_start()` and everything beneath it are stock IDF. They just happen +to be running on smoltcp now. + +## Architecture + +The implementation is three components stacked on top of each other: + +``` + esp_http_server | esp-tls | esp-mqtt | mdns (unchanged IDF source) + | + BSD sockets: socket/bind/recv/select/getaddrinfo + | + +---------------------v---------------------+ + | esp_smoltcp_lwip_compat | linker --wrap shim: + | FD table, select scan, getaddrinfo, | rewrites every lwip_* call + | esp_netif shim, in-RAM 127.0.0.0/8 | to __wrap_lwip_*; provides + | loopback for httpd's control socket | an in-RAM loopback + +---------------------+---------------------+ + | + +---------------------v---------------------+ + | esp_smoltcp | single poll task owns the + | poll loop, slab-allocated RX frame pool, | stack; FreeRTOS event-group + | L2 frame tap, per-interface stats, SNTP | wakeups; per-iface counters + +---------------------+---------------------+ + | + +---------------------v---------------------+ + | esp_smoltcp_glue | Rust no_std staticlib, + | smoltcp 0.12 + DNS resolver + C FFI | riscv32imafc-unknown-none-elf + +---------------------+---------------------+ + | + esp_eth_handle_t (your driver, attached above) +``` + +Three decisions shape how this behaves under load: + +**A single task owns the stack.** There's one poll loop. It blocks on a +FreeRTOS event group, wakes when a frame arrives or a socket needs service, +runs exactly one `iface.poll()` pass, and goes back to sleep. Because +nothing else ever touches smoltcp, there are no locks protecting it. The +BSD shim marshals work onto that task instead of reaching into the stack +from arbitrary contexts. + +**RX frames come from a slab pool, not the heap.** The pool is fixed in +size and count and allocated once. Under load that gives you bounded RAM +and an explicit drop counter (`esp_smoltcp_frame_pool_drops()`) instead of +heap fragmentation and a mystery. If the pool runs dry, that's a number you +can read, not a leak you have to hunt. + +**The Rust side is `no_std`** with an internal-RAM-first allocator, built +for `riscv32imafc-unknown-none-elf` to match the ESP32-P4 high-performance +cores (hard float, compressed instructions). The TX scratch buffer is a +static pool. The pinned toolchain is recorded in a `rust-toolchain.toml`, +so `rustup` selects it automatically. + +## Performance + +The hardware is a Waveshare ESP32-P4-Nano with the built-in 100 Mbit/s +EMAC, ESP-IDF v6.0, MTU 1500. The example application serves a synthetic +download endpoint; the measurement is a 200 MB transfer pulled with `curl` +(curl reports it as 190M because it counts in MiB): + +```bash +$ curl http:///dl/200000000 --output foo +100 190M 0 190M 0 0 10.8M 0 --:--:-- 0:00:17 --:--:-- 10.8M + +# the server logs, after the transfer completes: +I (...) app: dl: 200000000 bytes in 17552922 us = 91.15 Mbit/s +``` + +That's **91.15 Mbit/s sustained.** The practical ceiling on a 100 Mbit +link at MTU 1500, after Ethernet, IP and TCP framing, is about 94.85 +Mbit/s, so this is roughly 96% of the realistic maximum; the remaining +few percent is framing overhead that nothing can get back. Across the +200 MB transfer there were 0 TX failures and 0 frame-pool drops, and +round-trip ping on the wire sits at 0.4–0.7 ms. + +To be clear, that number is the result of tuning, not the first run. +Getting there took a 1 kHz FreeRTOS tick so the poll loop is serviced often +enough, enabling the EMAC hardware flow control, and moving the hot path +into IRAM so it isn't stalled on flash access. A throughput figure without +its conditions isn't worth much, so those are the conditions. + +The cost on the build is roughly +80 KiB of code and ~120 KiB of BSS (the +slab pool plus the Rust scratch). On a P4 that is negligible; on a smaller +part it is a real consideration. + +## The `select()` problem, and how the RFC fixed it + +The first release (v0.1.0) had one mandatory and unattractive constraint: +you had to set `CONFIG_VFS_SUPPORT_SELECT=n`. + +The reason is that `select()` in the IDF doesn't only go through the BSD +symbol that `--wrap` can intercept. The VFS layer keeps its own per-FD-range +table of function pointers, and for sockets it points directly at lwIP's +select implementation. A linker `--wrap` rewrites *call sites*; it can't +reach a runtime function-pointer table that lwIP populates during init. So +`select()` would dispatch to lwIP, query lwIP's empty socket table, and the +smoltcp sockets were simply invisible to it. Disabling VFS select avoided +the table entirely — but "set this unrelated-looking Kconfig option or +nothing works" is not a constraint I wanted to ship long-term. + +I posted the design as an +[RFC on the esp-idf tracker](https://github.com/espressif/esp-idf/issues/18549) +and asked whether there was a cleaner option. David Cermak from Espressif +pointed to `esp_vfs_register_fd_range()`. + +The fix that came out of that: after lwIP registers its VFS, the component +claims the BSD-socket FD range `[LWIP_SOCKET_OFFSET, MAX_FDS)` for its own +VFS and supplies its own `esp_vfs_select_ops_t`. The IDF's `select()` then +dispatches through the proper VFS mechanism into the component — the +`__wrap_lwip_*` functions remain the implementation underneath, but FD +ownership and the select hook are now handled the way the IDF intends. On +the v0.2 development branch the `CONFIG_VFS_SUPPORT_SELECT=n` requirement +is gone and the IDF default just works. A considerably better answer than +the one I originally shipped. + +## Current status and limitations + +What is verified and in use: + +- BSD sockets, `select()`/`poll()`, and the full `esp_http_server` stack + including chunked encoding and WebSockets +- `esp_https_server` with mbedTLS, plus `esp-tls`, `esp_http_client` and + `esp-mqtt`, all over BSD sockets and all unchanged +- An in-RAM `127.0.0.0/8` loopback (the httpd internal control socket needs + one; it never reaches the wire) +- IGMPv2 multicast, ICMP echo, and IPv6 link-local with ping6 and + NDP/ICMPv6 +- A raw L2 frame tap for PTP, LLDP and custom EtherTypes, with the P4 + hardware timestamp wired up +- Per-interface statistics and the frame-pool drop counter + +And what I'm not claiming: + +- **Only the ESP32-P4 is hardware-verified.** Other RISC-V parts should + work; the classic Xtensa ESP32 needs a different Rust target and hasn't + been done. +- **The ESP-Hosted Wi-Fi path is scaffolded but not yet flashed.** The code + exists; until it's proven on hardware it stays listed as untested. +- **No SLAAC and no DHCPv6.** IPv6 support is link-local plus the native + socket API. smoltcp doesn't ship a Router-Solicitation client and I + haven't written one yet. +- **The bundled DNS resolver is minimal** — single-shot, A-records only. + Production use should bring its own resolver. + +For a sense of how hard the working list has been exercised: the +socket-handle map used to run out of slots after about 24 connections until +it got proper recycling, and an early `select()` implementation spun on a +`vTaskDelay(1)` until it was made event-driven. Both are fixed — and +finding that class of bug is what separates "works" from "demo", which is +why I mention them. + +## When to use it + +Choose this if the safety properties of a Rust IP stack matter for your +project, or if a single predictable poll loop with bounded RAM and explicit +drop counters fits your reliability requirements better than lwIP's +threaded model. Those are the two cases it was built for. + +Stay on lwIP otherwise. It integrates with the whole ecosystem, it's +thoroughly documented, and "already present and proven" is a strong +argument on its own. The aim here is to make the alternative exist, not to +argue that everyone should switch. + +## Getting started + +The three components are published on the +[ESP Component Registry](https://components.espressif.com/), so adding them +is a one-liner: + +```bash +idf.py add-dependency "datanoisetv/esp_smoltcp^0.1.0" +idf.py add-dependency "datanoisetv/esp_smoltcp_lwip_compat^0.1.0" +``` + +- Source, the complete `eth_basic` example, and architecture notes: + [github.com/DatanoiseTV/esp-smoltcp](https://github.com/DatanoiseTV/esp-smoltcp) +- Registry pages: + [esp_smoltcp](https://components.espressif.com/components/datanoisetv/esp_smoltcp) + and + [esp_smoltcp_lwip_compat](https://components.espressif.com/components/datanoisetv/esp_smoltcp_lwip_compat) +- Design RFC and discussion: + [esp-idf#18549](https://github.com/espressif/esp-idf/issues/18549) + +If you run it on hardware I haven't tested — another RISC-V part, or the +ESP-Hosted Wi-Fi path — I'd genuinely like to hear how it went, working or +not. At this stage the failure reports are the more valuable ones. diff --git a/data/authors/sylwester-sosnowski.json b/data/authors/sylwester-sosnowski.json new file mode 100644 index 000000000..31d1d0902 --- /dev/null +++ b/data/authors/sylwester-sosnowski.json @@ -0,0 +1,9 @@ +{ + "name": "Sylwester Sosnowski", + "type": "community", + "bio": "Embedded systems developer", + "image": "img/authors/sylwester-sosnowski.webp", + "social": [ + { "github": "https://github.com/DatanoiseTV" } + ] +} From 581a5355cdd1736192d6456c2df831f4bb5a80f6 Mon Sep 17 00:00:00 2001 From: DatanoiseTV <6614616+DatanoiseTV@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:54:26 +0200 Subject: [PATCH 2/4] docs: address review feedback on the smoltcp article - Move the publish date to 2026-06-19 as suggested - Replace the RFC-history select() section with a short, present-state caveat (v0.1.x needs CONFIG_VFS_SUPPORT_SELECT=n, v0.2 does not) - Add a footprint comparison vs lwIP (idf.py size-components, ESP32-C3), with the static-vs-heap RAM caveat spelled out - Getting started: rustup install instructions, the required sdkconfig.defaults options, and a note about the lwip_compat 0.1.1 packaging fix - Add a screenshot of the component on the ESP Component Registry --- .../img/esp-smoltcp-registry.webp | Bin 0 -> 63364 bytes .../index.md | 121 ++++++++++++------ 2 files changed, 84 insertions(+), 37 deletions(-) create mode 100644 content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/img/esp-smoltcp-registry.webp diff --git a/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/img/esp-smoltcp-registry.webp b/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/img/esp-smoltcp-registry.webp new file mode 100644 index 0000000000000000000000000000000000000000..6343866a14421b19b6b1b1083a9fa762b3b2328d GIT binary patch literal 63364 zcmb@tV{|3&w#FMf9jjw@Y<8@UZQHiZj&0kvZQHi(WZnMneYVco0|BWB3&<+VvJ=?;`G1oSnh8wl3LXf`qeq`8Qb3ePNF>iu;tL0D zX8oZJ*x}pTh~r{aG>?JVP<^|;+wPk=t6JnAfoEYV#w zm+&6z902q_S9~S`GXUT#naArVl^xKx>*r~;?x(CF{LSvwcX02%H-oRO4-9}7KRL16sOWq%-5w;@$0|@(O+nnC7kYu?HZ1$-FOLJU8-2d^A4-uDXx@9NGf1!H@&;p>ifIS0Dch3Sm07w9b z50#wk8=vj&A;9w2BLD!1p2g4f(E-SPo_}0_B3$VJ0Ow!6KKNg5UjgscuQne(``rM* z$vf4@_Lt9t&c4sPJKJaar_QI$gHDEyF7HHltkZ@kniW>%13>@q2OwfF8M zp=uL*2e1N76(R*B?8>j+^N?2Y{@1Ih5c_2-1Q4;=Zkm6X534XRcA8#n*ofB)cdG+o z;vZkm8tmp8tP6p>=U^BwNCvOp0o@@$zC!uUQ;$40(>5uat~+qtMg}e8Ha=?3{fghFDt!B!MVQDu!=G4Sdk> zUxrJ{!qF69g{kveUc59TOuKng-VX*~(jick{~w9e)}pBAkZAuw%@)b_1Ac6zPw$xrTq;vU%1;>ye?FB0IdAYHO5(_L2r9+6 z$E%bgDz1=X@{7LAAj_tP31*XD5v*2th#kh6vPM*`(Y%JB`t#>+A=<={j=c%VdNxDa z-aF%|Ie84GTir*sfb8(F?v7Ln6_IfrsQD;93rJ!Y6TcrV-1Bgt%|I`{5~uX&r-(%I z6FR1D>*M>;z3tyrp=@%81T@TuJn6kE=kTx6j^)nI%ukK1E(z)Wb=Nfw{U_*;$Y2lX zl6)#XJr@z$T#IGH#5`;oZ1sg%XT3rwNu%-_oScMK+ANRWK5k~Ui6-Hm!kxkxD7tMM zF%83!_>hdQ{y8zvZY7F`y_a!NF=w{k6<_bnL1+$lhCx2=#)C!#;eVAM`PX|VXaZOY z(U>jHMq&b)dZ}bQiR++d!XP+IKmZtE#wjl9vRZN~ z89`@l{Ey!Gq!_oA7Hy_{x~8Ra$nQbSTw@`W)BM}CJxb#@p+yoD`v1GI{kLi0xnxZE z3?GZn)F8J<`*8xEEzE8Yey#kV*N_^`0f$x47QX zbWmfoAmkS#9vOJ@IfUd{V6uEl+Vq-Fi_!1Dm-NFgP=g~FB|DR~a~39Q%mcenz6{__ z%}*okMbEbaQ#pbVjex=ZTYt(Fe>Jqhs4dEeXoy+u0YyyF^+2LM-rX(1Lm_GRiImr<5Fn~tI zI)snu*@t*{F1}oCD(~Rq)?7k^nt#}o(U9^=r-v!@x&Zg1NqB&0lnR73_eqZz7Kg-~ zT#_){&y2^{krJz7s52cn#lH?$%Bs68OL`R2M}L1JVQei$m!G@COJs-9MQ=~gIK2ShOkmB$R?dUF= z*!h@C6sQ56)5C4Ewdc@8{GczsE!<&ZW)q1Ib`~r<{Hfwa0FgEo8WbETGG*Ol$9%h~ zDa9pg?NW{EJvL*CX7bmniwcS~=yJRa?qY>#)T!|e@a`W?##DuJnkgXpZ#wjUW&6Kn z_rIz7zcw#jP&>Z7$E0yB=PyFF?$iHKe@_u!+b-^UZNL2zE2RikH5(! zj(?=NHKVL|UyKcVNDI4FgHsBfFPi|H>?wh@^kWm+z(SP9`Zg)alT0XV7>gDCCRq%Y z3cjM2h!NA7&W^;?u(TIL=z$E--WW{+1(qZa<;xBL6tGi#``!RN!g7FtMsW9sOv+wF zHF;_->%G|p#NdFf$7qSOY*M6JF}-rDj@l-ylMtKUz)2!TN~@gban`K0%nB~N+@`#^ zRA|h)0<}ja44n@p|ESLp_!bU&2oVwU6LEFEvPK*VZu7a@GQ!aB)OD4<>6vpl%czic zv;CuJ<6!-nJD~I2inM^s<>e3UjRJ!FmDLt8q$cgW=injwWt6n-u5I(5lic#%vzp}V zd^o%*m!PEc&l#J01$Yty%`7?7dcQcQbGK|s*!Y$>TvmMu>aV=4EI*T zf5fXgjIeGj{TCd<3EXco3!=NfW#K<5F1}oYNx~y`dl(kB*`}+)3T_3V`Ts&&l}_3< z5lqMzogpXFubZ%{q!1AD%PFGQUhRK)4f{m)LuSugI2Pi<|f6QuxyJe?it}Od$-R%lqml zhGVV^{wggk+b^bmgM=x!^BDF=pcYi~Og*k3!oNYvUl1&-&GqQ2o;p&EOn7)n$1v-~ z=lJULbG|;O>IJwj3aEVohV`h_G=tk!@Bgq^<*l5X7Eb*_BEuoC_dx5!W+o^Iv_Mt% z;|z!FjOP~6Uz+tloIPAkmPLWg_J88zC)Qm^{&=l9dhJjL)J3Bn)S5SfL!E^90n+<% zhA=^+OS;+)i_mgPzX$j)!TrDdK3`0BB1($juErPsTH9$sL?)@3eDU({z}84ccJ)IL z^b&GOEs|G65f!`oghyI-+57iQR?Q_3^_EB-{e$JIUD!?K_J7{B&R^C&0ePy@4x+^6mntF^po?Egx#*uyPCb)r(8``I!RCdei!NF*_+~EsC6&armThTd!XCxq z5~v$3(=N~t7ZE7aGVpx90ypE+HKs1b{1*`l$3s;_Jh}kZ0+KqSxC@$F>t;zk-|eqx zmD&VwH*=eSN0`&hhNe*amJ|Oce4-6KIVm2XLDHx_(dd-3r(R2eJO2K{m8XEQ635P9 zxxa+q@68!Cli(=&k+EJ_7{)kXlsEOG{C;@Kk9XHx@cn}9I)d+-8@QiJ3IXDiY^*rO z(h_=5(n#k~jGNI}21K!Ty?Koe1Hqq@vtJrPg$qj!meM34qlwQ1H2u-N0}j>qF{!4af~4 zZEqRT@HXU4S`fR=C;qLN|HXMqH;YWX`m`hKI4Vj*zg7ONzsZ{c-nFm(1D_f+f23_S z_n&hxs8a6TbLBZb3`S&~05bp@rk2o6AkG3Vx=~bv307HiN{;{ArD`IzqV-HLP^SZO z4B19!nPo{ydDi;6grF|6<~yQ`T`xuuQbw3*>K=hG_i z8$iN`OT2$-^et2+OdN7A+>?fL=;UdDI?z%7i3$H+F~#|V^tzD)VLkopUlUlSGTS-S zG*z9{)@`t9DhEPaqW{6sVhD{;WXG!xwF!XiM*%hN(}0nSt{e;gAcq>Gd|aQrQ0|@n zs{A*NWAlWgzy0tf*ZZ+O4Yr+^kbBA<(em?luFR2DZv~x=zT|BRn%}Hw*Vr;j7lTs z4gh1T=6te|f!lZ9L>Z!HN*VTVtLzeq;;J9I>JWF( zY1Byv%?_+fTCGR_83w^rqY6F*Q3R~#r)m%H2Net~m6a^x5nHa=w{u<8Z0f$0%EI|y zMUyky7t<9XrLK;0w^WLs@k88Xux5m$F?sg8Fo?&O9`z%Y0%lcT{dDOgrwNsZf{ZET6&0Ok*GEls8kh6mLGORe7gFf1??##ZP~Fl|7yRq^)lXcLEW2T#qwRX zJu{S}FEF3cEwOXU@&Wb*zIF1NL}(@sCkHp>r@W6f>Ax_Qhc8+P-T;*{0m6pnm9JYU+e@UM}?6Q*e&K& zV@)^ef-~T>YEiS5(G3ib=rwO0k2u>AILWJ;M#R^)mytxAxEwkB@)mcX4m=Z?l}?a= z@AEdZ>Rgw`S&vVUO(PQiQA^^1flOIzTm%&ct)8dia!)zWg9t6lP38Rl!^N^(N=n5A zEst+g3tUP3{c2e0`eF+=r17K8G>Ah4SdEI4e!J|x0Zw@tCt2xFMaX{TTKgf62S;-SrP9wydRF{$u|N!_kS-cXyjrOHGi(r$HN zx*D1S<;{cnR}0Y|_T(0p8w6|{kIdOxkUnBdDi-~u@82?Coz4{9#bbzWlp3+>?MVuP z?Nb|eEm_B!`(X~y5gz9xq2=Ak?f6J7;=MLCr_4K)QPW$e$p&X#?dA%bYv5YwB8+*i zyHR^XqFXI4{|8cn>c&74bN^aam=uA=;x3k@qW+WiBoH&3`~u`=nHQtT8-EL3Szxyu zAu(Fwj~M){j((;ctFK<52gV5s9RT}XSi?__Yp?~whz}UQEp}BvR4doSajXQ2@CWX@7tbZ6i8n;{jA*&|!R zljr{D6X>B7=4!V>`cl19O`3p&z^XijrAs>p)rGUtMlF%Q$QS3-T37>r>w21%8ybRs zPR||IcMH7#$msvM`f7n6KYoO=Ugc-i{+gvjDp+5u!#)#MtP7YeFbCeuTeG~Spb&Fa zW=*;9Zf+8_ouID?FcfKf?j~U+hT{I~F6mS_;Bw3oQH(`;SFwr^_ADub^>hg@I}sAn zQ?{gG;HMjybpLZM{q4+5$K9H%*ixt1*OOX~q>O?yyuC`w`4$N+E0NPis#*bP53TI(ykC=X!Y6SV z(C{3I)&EJ026?Eg~EKRgxBfVasZX2DDSA{*}aWN5B3 z6Yxwm98ky{q4Hf)%k(7Uqdl}(`gb}OIg$^uzl|(;QFW~v-OO=?(w@sJ63DS4_f*N< zx9v+a)T$=?K~V00y8p2f$~a7>nB4X0|I6ohgM%C~w%VM;VXw*{_1tBkPcSjJ$<}UW zD!&_TR46RE>o-Q{NFH%-cu2`p!cwPHGA_^J=%=I8quH6%a}Ib@{+}_8NC59V%q3pq z58(W!j;#*2Bk z{Dg_oh43rqy+zo4l<9f07G@-2rBJK5aA<(nKd_8vmxK`MD&O1LR}YqGCT`|$k8_Uw zN#EHGk7Nkk-hL@U-0Cz{^bYufl+cV51d;lS<^GXokkr637C}{73W5MQQivg@@g+-2 zOk2D2{)foN1}Z&ih_b)Dm=rb0h}=FIn9N2a#SV_-OfjCk_k$*}{zQRsMf-huYmrog zv;o8`7;5~Pps>GKUq>SA_d>u5YDN9KsAtQ0bLI^|97N9&$abze|HR{L4;$9?-sqqq zu=@RsHDX0L!n^WEi4lxUQ|QqG7F$WPsd>;oD47((5#o$Ii_#5k;w_)kI0%1 zL79?0)>I(4!U9nXkxxG+ov3Jv^po<>d!-p6fkWkOWey%s1+g;q=~MO09+D|^#$Ba7 z7zb5jdG|r02=Po#PeIa58g|Xf!N^a@z7=|pv0^C^g}V2X7TU&n33ppzJvAsLflruF zkhEKD=h;#+tedR0mOO%G&+4beJb@l4 zg#I+)CiSw{B6=|W?u&`DNn5>5#Hvw>CfdRzz!>kp=y+B9;FW=m28nn!m;DsU;)Sur z-nG%MqA*INpYxhGxT~AuXH%}uWV@^Ks!uhNiB`L_>9q+Z~%P)U)IDB^SRJc|@ zI}Az3Vb$c_bUid=TwA=Mh74+gb~~d`M%a{a-DLUnJ}wf|rwZ-MUKDBd#Oo#hVFt0p zAADsy69zn(6s(O2XufT=5So`!YaLc}dCZ6B?R~zN7J2pkR^Vs{8}-NV599H?&EpL0 zzP!Q?g7a`>M8bB$o-6(CUKZBZw~QN$QKMl7MhVG$p^)Ci=&ORKNN=HBi?W6sx$q$c zkP5mYg|pNFx_r0q(Bw{bwq>{Ag@;JI(5E1`l*OR3>I*~*@1s;*+N`;OY%DD&k{w8Z z>W&f@NPK^*{h8+M>G(Hl<`N^Iqf30iwk3R&W(=61b81j1jpYt46mM=O zrOZ=)d>mf=Rw|4iM4`QZzh+qedCIZPZN)dfCvrVtTz~2TSsk=DsBEp6n?;{z30G?~ zhA*|b&hoEZZ~ZUCH^>q|`FhPf-)c|Q6#oq!al&}_R7y%WhKHcNP{u?QlG6KEd?;Z``V=;?aOjtEQti$G={+_I=1g z%OkJz;O>IGr*G_lV0u#&g=z?yqF6h?8aRd~v5H;Z8yl#-t~0-O>ozAzrsKvGDYrMy zpZz>WG3SeAp72uTX4XImh8=1{m-nv8e>=q!L+#7PL-AdtocFRv4IuN3IS}0Ob$DCeCS4>x5oIG+ct=JC{*7}d=Qx&9vPmg0 zSr`K_qLBU2foo4p;Sm^~J_%I<|7imyu?^@xgVbA1AB1x1ME1BwPyKr45T_!hgS`3+ zCW<7K4Q1GROdBgVHBW=WY2NPRa#uzR4CrRkNjh0K_70xI5Szs~y3TcmW*$@)vKITSRRDrl%_W3jGc*AnF@yj) z#gu zbCK)qvfGHkbEBc7a^g`C%mUksx|)+{QK;2sSJ(J``}4q9GU*NK&H~5#-q4vjTCgXF z5)LHa!MJTsLo?MKcCQaK7^fWL@t~6i4M!gmf^ocCE1xe}cx_N2IXnhm8aBr2oF826V|FYZ2%!R^ zTI%@%5AJ%M1AUG$cl?6Bml^)`66r{6(KHAJ!QN8B8NxhCT(Q*PsDer4kgwyso3~DP z&mQOeu0l>!a~buK^&kNhW%pa6Gk7hxDZ?tiy75vo!;oqYRFZIQWj9c7!~uoIVofYu z9lu9dv`U+FF5Bh02?*Q59v{rJ^nXU>*YDVx%FlKbBx;y19S*kW;0?CC>Ic+qbB;Mx z#-+WPZ~(&*bFz`uDYjJi3U$vJ=nCc=e|MAcjiKFcn@ANl>&E*XTkIcc>C>&*yJ3Uq z<0h5b+b)!nF(NQS^TrjraVnV~oqFzN=+u(%jr1@re_xqdI>wE_G?+iUu>VoR7}XyU zG|KRXTe7WSw(wHcuD~y3*v8Hw$g~Z}0K>RE~}nqSZPJDVA@|p}D#DU#uZSQ+G<$8fm)f zx>tp91J?9t0me0`2N!WH4Sqw;F=j~L>}}X{fPkopo-8^<0PtdJ~V+}p5Gs+GzF7WtRS!ZYnq@R=4YNH@$eet~Lj8>)BD zY%_1It0ptiQ&bJC+RjX6S_+!o@Vo9tM@J$dS!ZgX>fyfm@*K1-!y|LrA@4l|4DG4# zhu}+2vrZR0WwPUi`} zSAXwujYdXfZxe;7p7m-*z0OxJtIx=!Fw@g4%ubjnD^@DVf&KmI3PUL%Ug(7X)IxH> z*fG_#j=IUx68u6jJgYtnyw#?oNpQr8$Ir!2o4=4b>o;m=(d3q;n@(5Org>)-wL_0#TC4NPZj(Xd20X!zGkM;8)7MT zyfo!MS1Q6hA%j?IJvE_a0PDXz)&ZK-!Vd(@=9r5kR-ij>;FD7u48S{If}`^ZFqIJb zbP8p})-(p(yBWvX0vUMkVoq@Em92%kfje{q<#$f^06+#tI&sZOi(DXKlLPFWVO%HK z+Lr1kWr+HYZE=e_>I?W1DHy#1dFlAR&O}pCd+_@ZPiSO?X_fdy;?>!Vu-79tlwB$I zk{OVOG*|J@b^oybzyBa#OJ#Jn2C)bE;QYMk1VHK)#1PDb4h9n_dd~4HR1k$cCAonR zV8qv+opZ1=qZ!vwUVkc|e6w{(F6ZR~wqKn*I;bOoIGb%0Bj6@)v3^l7SE@M(1NEuA zIN%LPM?hwUUL35fhVPF7xiNP@B!C@+llqWdXhm#llA-hY${gL7j5(v%l|zIvfOQ8- zi0_Z~f`D4RHW;{emHgJg4p}b*N+M+S>64zk0nl$KtNaB`h|quJ`{C{>_>9kl?6Oa_ zE`_5vht#a6Anb@^ywydN%5*aSKeyY&^@q#7>Mf7&xlIl&An@;UQn@NY{(laO$tfovR z!+JY$s+rP_a=~0NMh;1K%quTgP-L6ZGl$eaGZHykyhyIftz3hr*WqKsvHyHc%(@!g z4`+b0D#&u0py~4IZX;eLJZgCOsdgcLS0uT$LTW?}K7K}UfW;q9(g|@rfqHDUTR*zd zn6kv@e1#Yci%OMd_hJdr^occeCJCI~4c~506?wBtR5pw}cL6SC0DJ2qe4->(Ao6uz zH8uLe?6up@0;*HzZJO!_Cw-}CcfNPmHHfMomR1RCdApXP#}u#4V2X$rKc>m=-twkp zQrzdlT3_)+;MZ4xL$=J7K=g^|&oHQjf?))#$0WFH$ksLoCFgK}dVuEoV}0}kFQ?Wf%b9g@NZqA0 zB&Uwi9RR+L=|IyOhfAR6BG+sBPIYf!q3Iy&Z=o7rO1XMEe>e`+Z#z@D_M(@qlXW0O zBO@AGf1(n29C@y;?$oZ~2Be#Ku8zFBe9iyu`beh^aRDlXUs%+KAMbq<7 zhC<*M0B*j14DASm^$Q0$kmYf=IOo%Oq;6>Ul*$efta}Q{Lq{EOs8*yEbTgc>l<({> zuFIrWx#lWr6;U&%dEAJ>uLMG+hBC)A>`U5IAP=?I(kHR42 zBk0X=R)`6bEbpKC=PZ;}(`ff!tHq>0hUl)Dp{~@XHl95 z;ZJaoTgFQA9EdB}AnBxNQ=D#LN@q8j(Bm~PI$os>U0Wwi&D>1X{YEKa#4PAZKz?-S zj>I592_q*YN9!!V4fG0jz0K9RQehX9lMS($mwx{^p_GLoNjS}E)*-UpHq5x9~p92jSj*_rK@>*_)4Vkb|C#3p~qsZB%7 zyYbpZo6&N_}T zwet2zql}B-RCZT?$Wo3}u`1Ex&p~F}fru@m2_^2nbC8;S*%|U*eF09*bqlE^W@uG; ztcFDMmwH1PDl$M;dr~ciuqX{gGJiVrO+n8!ZuOF>R?x{;-BW^r#CuvHVNBt>M?oxkZI zZPmJR?Z%|0*6wsT9unBU6UP)MateAXuBCo4+G>HI7!25S5sw*JExdgSI@NMd&6(QS zVi1ID`a@?ZyVEqdr#|&efhn1#k}6S%3tzJ%R?5@|3N=B)bUs@Jhh}kORZIETBrXQ( zfw1JkaeQSu!$&QN((6eR%d)%U!`5g6duL=L6&kgDwl zF*UOt`80)YC$>dU1`Wq0p^v@;hdseAVRm zL)1Hc#eT@X)&14wELw$EaNwE*#3k~8F|0{tv!%t}m3KaXJCfZ-cyRIXMjYgjOpXXWU?9)AYnnHv!>ANlfB*8oL{6=f&)7!-J5JjS#}geL7cZ;@5s{OTf> zR|-?@NQ>x#ND`pyh$>&j+^}tee8XR}fP_xbMtplIY-IWDs{|j65fK4tv+d#EFIRjx zjix{(%;H{j*`ldONw+U%8RN*$pNvx};P+v8LC3`>g^c$ki@d?|3ygrDci?)Exs>w{ zY5KY2BLi@hRH3MGfueA~F3_2(NJ>uvGnQPRIT#M;_q?D*h*5+`2;$x-Mf^bVdQr=} zB(^7-K}pREZLn(=97|ANz^HS`u&y*qmUo%c(Wb9y~o=w!sZu8Bf3`S*q{~_{2Veo0S~e zTTO}S*5p^!91>MhwY=lJGp2E;`yMaxlqSHxS*L3hP78`ew^wa--+w)iQ4YsbQi4bb zov9jACe{HXNZ3UWmp>inef+SdvCE!jYz2#`6MWUFItIVewXA+|o+ut!Ljb*JgkJ|3 zhUFEM9mef*<{NH$!Tac+iLk3R2QeP=H>`Yq3OolZXnQZ!XFBDhLlx)8waa-NBVQ(; zxY>IJo|p6($@a||D-v|}YPyGmS4KQhMJawAeUpT*$=V@FbqX6tMX!uenak@w9weR|rJe#kGn0fpWLl*GyGI&lZ*o z$R`Jj%ZPspSK!Jpt~2saA&{Zxdkb&h9`hfMG7v+FwkBig7252Om51iZv;&ze1UKpu z;4itdkI{_yGK9I>*GUk%&rr4wc_^3!4S95Nt$2AfNlq-O3+_^*;-&^ zx_9IO;dzTFwt`Di%?Qbh@3A=0L4?i`P8p*Uffin|{S)RWl7!86S}42D>@{vIE8Y4& z^UZIooV+yKK+i#T6`gQZwl8GdR&ZBjsNM-ZOfP34Qx&s~HIkO`Coamt_dXPD%itmo z^yN7O>nnpXw597gmEtpU3d<+;f$NFUd$$a>_3u`M>!4lkm@wtz=op+zV=U6;5{+SK zHqGDyz=pqUTWx3AiJWYj;bfFZC3*~8pvz{n5aF9daMw|lbJb||bWtB{K%D_>FH=jn zU_L)2#U#?3{cPquB@dL~8&B^QeDc%ptLO9jn}W#Ygg?byGHi#ula!rXwvK>g(hfLA z9<`N;PtR31B}X<7+Ktf&IbKVx944uOKA?HimK`aCT2X*`P-u5YW;ji?pr6I;<&mg8 z1PUq4F{V18BzqrX!-**I(f}QP*ZE1(aU=oGuk~^ukvlVr5aXSMDGkQ)p%J-$T!nt&|;>e^O3W^%zc5RS31Yq2?YdL)Nte?9LDcj{EMw zhM{$mJ;$no+3AFmFj!$RZFZ@?zOA~n-*r)BR|>bJo-l0z&OR*n&DHSErqV@=KT8NZ z^#WH~9V8V|#z6xk@611aCgF0K#~1BRvm>w^ZRUcq8BTFlz*@L`zn5EQlP%NceFl-LH!y%^ntSW-PVeM)`B71 zW7Fv`yLa6QW^Hm47V8y<;#e3t%CqP23%UQW0;B6^mit{ukQ#-mRav;K{e{W|aQtt# zuJnhX#DK!qN87-nT)m*5z-RGdw~$IZD^B@8t&GIu3yLJcjdH%-r%dQNw}Y_vF}q}` zU&AK6lmjQ%ER%YzNb<9d2!89l z-iFP6AX4u|a0MN-dvpSy?5764b58;Dgc>XGW#6$4FZReL3?Pda1;y(W;;Zv&H+0JAT0SRuMPHFl z1zH=yriko;_#hj>A{x&<65jk4G3=-ET;$NZnFUA1EG3P5I?w5eTyfty==ugdmvDU>}`;vw78%b;p7$~Smr=TaNB>og40jItiC{p2Qs^K z3QO@0K|3?ITMGmvbsFGnfFm*-z^4iMOLZ~~(AY>K>LtgrNg7<c-p9vlyrk}>|uQSy3{W^YA8M=uoX>K0I_xGWpAxvXTqV^J+90CX%Rf#|a>PJ+6D0dHJ zMVuWZI5vDyAu)-#Ck5y&ZCAP-AN^L2T?M(a><%7otmibO_K`SWO$tcE(pPV!C>J_qLKg3DU3deIV*=AJ*a@okxzA`0j?7=_P| z*{Cg6aiq#OyudqckQP80hs6QUR10DWX&3@uo73GtRQ<4Gq$6rE)JT*_+ONi4G3F~ zAwRWC(mm6=b{kQImK|~?R)h49DwzL#K_PS{r*T~M8VWVj+LM+`Nvy(H_R(N!yRvSt zd*tyZnUqz-r?p~oW4m}O`NdLLws3|F^wViYm<+PyoxX47`T310nz)hp&yqn5YVVj5Pqwo@+9 z1)(fR|8AKE9?f>&73tILwuTj%gFRjNX%cnZ@>@yOi-aG*27cilV@1+g$1||=Kxv9? zI*PLtX^nDsiFFrAH3V0cs_caaS>UlpU7v8y#)5_cNUBOMuyBNAqiZb|!p2}TA6yt!W5G{f&3a42z$L7rlzeLlC5Z5dD z-b1kMO@ml4BTjr+)?eDZ+XJ>eTHkBDqfHHGJ3$DgTDC>c$$-k6pvJQ$F{ADp`3t_Z z03BFSY}+c1C(JDr*qYGjIyvGF7a2B|EuH6A?PKIfYTt6+sC^|@h<*qkr=_X|3B z>20GaarDaN8ew3;4>|A0@Y)0ZLwcYnzotEx+5izrqPk;4W7MmVs+eoP`#tPH-&1njL6~SgZd&W?!{-d7tbiKY8ov!0+2W`!#V04u zRCtl!G9I>dQ_^q|N^>;6C^}@sq`NJvnKoH9A|tU+{PRSmoV}@RC@`4933IA;zPrAL2SH z{8+MKsL%xoibtLD^#k{Tbhc#=b?A(7xM;jy`?!DCL;A&Y%-1!yb{%{hz}h2leFdIz zt<_qEd?kikMcm7K!O?0Uk(tgX4b(+h>GsWPLff}%RBZpc2lZkA$SFDjK7H{s)qP@yHG>j(^n855|32B&fK+ztK?dSKW7q^2%R zFKc_JTp6sxa<8<~owdhKOw)21?5IZzx=L_cyYwnpPe|P)G}CXoipCTHA0H zha^;uUg@CdjYAR-!kIa{msB|MQ~$w97FU=n}tQmZKypKOLZfXdBS8|USgL-ejg z_ox*^UzZ2YcZNsNv~SzxLKL0KJepuHS`-pD3$Z+&x%Mjwz%W94BTUU!yUCc!8k@y0g_l3k?S)@3 zcSWkud~mp+R4|)oYmXIrFB)tv+Rdc->~KkZd!l&hMwK-bZVxy$TtLpG=G-96fZuHK z|534g+Pc@(q(DpEQTItF7Z%G;BFU62>2zkVifN2%59sNDYUuZ$zFbuAkU`ca9bmPR zzNU}ccxzR(*JMw03H;K`9bt(+8ok0qY=351RPLgzkg^5HL?8-$@tTG$TCX|vYJY1+ zyU+jl&8Mb7S$Lv9k1UKpJjH4%n_6p%_DK?Q7ANg6BWs$jpp@hG_d~DE=~?rjY)uaN zw0AL-wFa95@X_LPCA;NOa5^o+V2MOGJJ`LV*sseyxb4hywglQ%JlxfuwT2G1DYy3k zF9@Y8=otnGL8-vjNTjC>jXrIAb2X=#XabL-(eF%O>VRV}>|eGl8zHO1%dQk(!oPd*>sDH(8X=iqdT}ko%psYCTIS8RCq_*YCIb--Mm~=y8e<30w;aNDppteG+{6i_Jvbj_{-Q4j zY78c+klZr$h>YJoObrWRw9fvc6pgeQu4eSUtD8Y-QJ)Vz>oQ#c22g%7f1rLgwiE!i zb37W8r2Tu+lAjwyyp{+yY^wst04qAPi3|f^BXLp~0UK-!OD+o=KaXK3yCU;^>qi#i zk0^@;^vxqvvyQya4^e=MWaS=JdE~uoiCpJH|AHw6eWj>!1y`9TvEtb<_m3r9{%WZv zp6+9#$;fSW1w9C;n3hn@#ml(QMS*!e1D9r$p#Mv}VmZ9D%zB;wA|vPlrC=XerQJm- z6xOifu|On~S~t))uV)e6=Lol_!+PzjM4l%NgXr4zEv=1w<^(z92xf!CHe)ds?ldEQ zZ*#i=`cFDMmiSw+0m6tOCx>4HO zy83VyC=m4NGqqNjTIo&Pi_QGhkDlLu7(hG(olTQqN6}G#$ zZwH!S&h`BX=0f=B0AJw8c7-ozt7nD9sE02G_mrN_wGV za#qXrxzo#yfOKr@)md+E?JySTRA$%wzR65ySHwCHrjd6}%>qI{OOmZ#3oWk10uq0= zD;<>g5CIFMrHY((Vi0`^`K&L_KhNXi`F-~jgV=rO#>N>2teWLm%lvBuFIH0av|h9T zqe{-HqkN)NJx&*tT^WzS5pCmcu<+!h{Bh7}C=Gu5E&?i=Zkw%9c5sqPG=vcELAkqv zmm>fZ7Mc3X^=rmmGlDg(05fcqqOI+_nHU5SQ7o4-dpV9`w6~BQ12I?W^G=tmK4ObR zeAc}N70mU^w4aP{YOn~~Fne%&Jkg~c#WC3t+M~iG(J*8j8=W2dFITzEfjJ>*a=M@) zm@UJdOb_C$@;=lb{K1(`^?$><>{%rf50RGQMj0c;4R^YG%12-OKdbp@Y2Wz7q7LC7 zIH-5X+GN>?w$9R!e?N1)bh>hdd*Zjbr4zYdK=2PQO3 z7FGaIc!q@-Po}ph)@EuXbE_M*(i(7k4G25WjZ}gIur%g^ z@?0gW!LK@O9S}toZ{LXR<87e1FUY!;W5D+^e$IgoKY2G z(sXFX+3hbwC|&>f1do_lVXh0wtYeeK0Wh*n_S%WywkeCe*mG%5wP@Ge31;}q8xbz0 zpATDHX;W!OY!*Igk(zICfm9hC5iMe&{{iFxutn(Ok z06AiBgZMKXBoaT$KLJD^_y(696X&cb2DQI;Gq>J$&O072Me- zAvUa$Zny@+7Olg_`v`-ne0=(0zdF^>8H(A!M{SfF1P_59Qhf*roy2^iBkL?^%LKEkr>1$ny`!-z?xmDfN zGOVl!L6!IqxZXWR%)5Tf zg9UcOtV+7%1Nl>i*wwOyj$PAI2-6eII-&uWOI9JCZ=?sEL@O!o%_UyPh3)+|!x$9z z7W)ZOIrPuj@zB#ti^rkzpdSkA$!vH6lc3jYgM0Nim1H0ToEJ?(h6Hzos1kQnc`NgA zd7_?sv7J~t7eP&LgJwFCw-E}Rc7kVT5H}lrlp9IbBe>XGOvvQnV8k{ssaUeF$`-#a zgmHO9#PfQv=ie50)?R}_;ChQqIE(q!>mwjZw#h~JeSGvJw<&N^nZZH4Y&jXOBwhK$ z#B%#D_=PH4&+8dSmYx?n`7+itH{+YxW;#wQ`d}9 znGJkE9A)}i{}}?lt^gtk>AWT#PN=bRQ^y&EFM_2}DL@tt13$ffZwoH1yGy@sxo;w8 zRa5oK&I#$}Bk#PyVZghu@i2)8hpgkzCh!kIj0}b(%@gH^JXq&lwN=`~?3x8?`Cx)^ zd6!Ky@qXg=#wq>E|N^~*V zB9~%b%1g|{=WK7>QNUK7jt@x!o9-eH*v|*LBP{%l%tCDn*Ku6IP}omUfiJpW3CI3}ON#FHc-uOz`u=?`Os3*ubn3ND3M$U_T3pK$SOP(pfemX-w z;i?h?d6F%Fal==8LQN>Y{n=+XEx#En6E6DJ!GWM7$-RGb9eM0HUyqjmwPB&$oD*|6(2 z!roP163>Gj(Ue|x+{9}}bC@QQ?f-pPo5^TV5@o8JC+MIE9?7)!`b6WQ_| z##z7v?E-FydWcuu(5g@phrdrVPyzauH9N0YaRGX?0AVp{dU#%vy*-k$K=Px64^murzziTKa$!c<%A>-}U~ zw{^}Rto@1AG8a=<$sdq(0Pvynw}C2ed^Gv|*qh{oBFe0KAv1Ec^YdAeX_*0D*8kP1 z$UFO|M8?jwlTQkDxx}2MYJ)jP0@-C5(v16$kbbL~D{K`W3~tog7grm%BarYbJD? z3nyyrjY4~`V6K~O-TTujT|w$T0<;mhcuuHH581zJ2i9J$hpwsbr$K)qzEm=Qov`r$ zMt?=o{vV!@{`{J@23@o&8SU+5Ce24df9+3Ks>9oE_bLnO`i_z%+&-wIWLh0gUK%5F zf@Ac6b}PZv%^i$tSH`c$m0QQCvR;SnvmdxBax)C{iEVQPRm(z9|ANe0b(e1-V0k}*g z1QF8Ps07gml|)VcFXzn+SNX0cYdHgzFsFd~VjmOhHWCevbL0n>RKJBgL#p8fb%EA! z0+g}K2x#H)`gfP+dowPHshB}=;?L=_(#WA%EHXt9v~R<5A6|7CMHmUwWls4$koOW& zjbbk=o4tYB+6RKg*Cz4I6jOec=TBeb+MwVjDBCvNm2Ta7X}_E|;%e#kxU5$v4M(1i z5IW_nOo;1d4B@W>jXu+*j$ZlM9WlPDb((+_idmx&W{H>x!worF<+Kmp%mxWrO}H02 z0DZ8d2Rxg5J}}MD|MuDqO9AYbFmT7^o36En6pl0++ow7?*;)=4ohszetsyHx6V`tD z;=L#yB%}u>H|9Geo%2YFI%WFu)rk-4ag21JrMbGpXHbKQY`>T(s{wrUkXD7ifLS@N z6D5oO)9s@lKmK${57jGaXOUmKTr@M`>`yvkfZ(M(6i@bdP4ZH!FoTMy7TP!fUf@o-11l{GNSVtq-)8lLH5bs3bKDpzfDRYWjF9KO7N>E({WQ~I{YMnWD#}4 z))+{(WGJzTPh*Qe?l78HM)j*3U+FE7LlUc@q>HhxsD~pZ0nvf^iN!RYGI_vnwHN2H zViHzB{oluq9%F!p7Nsz@z2xMWh0C-%k4uedtZY9$)iTQrj7DABE||?L%fn;}#lNUE z=m=vxPsg!E7Pai?bF~RfE{V1<1M&N$SzLjr0IyJZfT4 z?p^ReROOqDugL=#&f&JrTdjw{B1`Sv@5IwD$gLrLkmfE$xk_%o!M}Tp!0!e|b4Q-) zQ`MN7kW;9g>eNZt3%a)#&|g_PM23ihP=%EMSs) zjb+-R^;2>0!sK25Kii{w_cV%dTQpz?46aPo1Bm5e@UYV#n&imWaQdK-M?iJ^wen8oSk=dWu94wx=i-UZE@rhI4~k$@gJyFY|qzG_)hP^^&a z6q%+un*+F~CZ602GAv*Ptt2R#jx+9GW$5Nv`1|q(=?LrceGTA9|xG`Acwy_Jupkg3JZ>`Aqqm&NmbPJT7eqNBf6Mdc7PFya(y*afR zXJFcnP7c~zTd$UHrmuyzK8!xecfCvjtE{y45{)Ot@jaDe->6GEB}|~p;hZpeXT5x| zaA9XC!OO|OKujNTKhXFq8YOfZ07IHk*JIE_{xjm*r5tEMPUyKm`5`YPKdY}QvsyJ9 zUxahF5*TH7RZiD=`<(i9jNefd6l7ZrD;J#yR9jxDo*K5}81F358LyqTrANM6_7P*J zJh5IjzYWD1oYj8m&f^@mFLNQEeGD=&Aolbglz=b9$epr0hJIs?!og%RxII8&;(b z9~hiEU4oo$xXqVSLYpvIQkf#|{ID(mf>EqX`q?W-d}(bonv*gU_A551Lb%@XVc)mL zjMb41NzDe(vxBQWI2Vr1POL zqt!chQF{8Gs)%;XE(eUn@$2?R&wa$&89#MprE@d1(HBrmtN5ZRG|yzx=r64)*0+`M zEt|e|@N-h|JB#)Ol z3srI0N_63*V&BKVZrXKRuO=ai=mL-uEpI$W9Xw|xEDzI*b5^m!oQyb)_Xm|%)CIU4 zP?hME(dxtDPh4S)5-*a1f$Rz}$(WGh0vdFNl5`1IV~<3%&}B@YoD?y;S20v0roROpjz8n@zZ9{u_ctSG-{{h$Usi$uuSyhMMn*$jjx4% zvN~E(!U=lKoAY}5;*QOzngqPUEvL=FNLXbMZ>VDVztv_9E!yCi>^iiNb^ztS1>mQ` z_g$C2@P$q7RT{!B|G8q_?S2p(3vM5CqsPf=Ax8Sf^whg|7dC5+fil}n$(ce72bp4g zyD%Gf1gHPns?`&<0+s{$o{cn+Iy;0m|2;;@f)2D0&%*litVEUT(|CtAXy&tglx@D= zipY{J-jExk{{>i$8Ed1$nr#jKG$Q@#kGbfH@=cz1hBb4_Z8bgvT1*gl&X-^MNt=y<#l%`c}}`Q#`M^XGy+o=&x8v~lL> z)4@rFRS$dzr4I+0BpT=BY|4e+p3N3_fb`lA1UK&HviR@o{V8aE1xgV{ZXN)){YCUfr65 zC+GQx_t(&!>ZU^%B{d0SKO9lv4gKG69DiuF1x*6_@FxQv7JF&CaJmZcJC22)gfJKX zxX;x~*NcC8mlfb)%*8uYH)jF)Kf!;Kh zW=pqjan+vqt77XT zp9@DgE}8gJh>GqJL=z-8kvyy#R1*mzJ%_m#BC`yZf=5aN#FFL~Vrujz@Gk5tuz|y} zc1)-1yK(gX`srVoJggn`dPhd+b=lRodo@a`tfP3njOli}A;*=j zN5{XpA6=&4S-7JwlaBYnwz%mrg*RGTrGS7thtco6^=msj1xr+v&4>5tfz5R;cE*6G zmC5N?`CcgY6(IPtiKD&@w+JfvAUyXI%Wo=3OC$AP!gkA5ysp(8pg(B2w=abGv4zMc zbP*n=v{x7L18o9+-qEA3$KI!&iiFsOgh+XNQcKFo!`yg`DLW6ylp$7P2t0&ebz-Y( z=wvINB2D%Y8<*dA)l@jKe$KH$Y7ZhoypX076rNKn)Y&RqGm9E6s&s4LO<^8*kLS(vZl@ zNA!3aya%R{4G__aghxBf2Gx0zzy?wk{SpVReA=C=5}J$UE=D*9fywhKxNmWBfF-Sx z9x=eB*s!Uv4u~N~L5Sqp0Se)ybWjLFEG_~|#TnBNgczyyGx{%TUHKG~>j-*50+R|; za9?e#P>3q&m~kwkKY!><2$#ySV;%>T;2Ah>WD21r$T86+)Ak?;PXWLFov1pso>xjp z$anmp5Vjx*T)$!t@+3yVab;NJ27`<`WHkyno8ajA&xN!FECw@A=@MHw{e=d^vsq^K z)^oiFI_QwUE8%D*+Av37m++;gic!lE*gbWO81 zfyx~i#^QTw6`L1FY_IIS8K_(Q16vSk2Hoi>3gUq}rav(_5P?xL`6ey+D;wCH1NA1? zGJK+EtPZibtV`>Fiw?jd^3(!WgZm1ifv}=Eq-;qCyo@+3=Wt9VEDk|B|P7Kv8055`v>G$ z2xNVu=wXGrzuz9qV6M$th)Lj4u!2hlSP><(4I?hOW_U+x6hXKz#WO4yo}jZ$T>D{P zSP22L0@kL{!|e5D`^@bjD+7%u#S8+Hy9Y|{fB*~jGn(bqP6Uf@86N^J*WyEe!_Deb z%QiJfSx%b$=k#l z%@jeXO`2l{T)ns{A^kelmrVOO9BZv#ZJ!A05*C7sU7oJ9)G4Ki*QXEB`ZYK~QveY^^4s374e}870Ej034AW-r+iC=Z!UM@!;IhcBHy=%Vm_*pL&AnWotVRZru-V0| z0dg;b;GWo|rjxv1Di8*Z2+W9;uMRV!mqcGw7mfzC_s(}zFP!;jQvZ6HoXssMb$n6a zFo$b+_UC~C2}X$ICxSgAY#!RwhZ3(wTCwBgMy;lTZk#HwT&Vw4QND()4T@2BF`XvK zC(Vko4l4q&pmu8>Vg;DX)0fjOSa;Rhg{1smSrIeEYT7AkymLBhQ?t{W>V|z{=CH0K@3HUR2Qb%09F6qzpn0|h7TjK@OuO5{RBCIr=&QR zOX-L3t`5tKz^_}+}SONEUElL`!A6LWBzIj?}x?pzvozBbVwQoubXL13cG6b z`}8f=gBlBD?t@%@{;`}<8RjRRg4()=K&Q;-cpPS|{qziMYH)%k$xX&}g1O@6&fyj` zbQ-LWR26Ig@(Q;Tg3*1Zlts#^Aj?L@>RE6rLnD_mPo_+~Vvk>rOyc0Y!I|D2QnN_v zfVLVI7?I=1(9`_>!-gntRb}YbS_PcZisQ&aQ_K5%+iBf9D#*2Hw1cOi*CgNGo{;#X zls`k^5-1tApx+Bn`TKdSA-&Qx9V>4)5TVhItpx2KB~Cz*7cixzx^y74TIsOU_RklB z52Jd3iaZxWd@e)D;%<)M2?uHa(yr8E$W_@IX&JTGBQUBPxfYfh1GbZ1AKT4`>93LAEChwe-t1**qclLvvds?i}`#C99)zYD& z|HDVKI#>Za=Z$7%m(qj=%Pk%|K(c9BgOQ)+FFb^#6@5ZQV1mEd6`IoqjZy!RBHOj*^HzSy)w_s&k%lgFV5h$*eyfQoO z#4U)6jNzgRdVvyz@*5;`8Br$u5w46b&hLByq#drDmY~?%G)6P4wJw zU9nYC)Geg9Q;Rzp?w~Drhi)!A7@XD130yibh!GS`=`1I`H16$o z7j?vzc-lHc>s)`eGO`g7Q*$DfxVLk#E^AIMM1Zyv9Dh#}y#lZ3R{{Ma$Tm_YLD!atvq-7^W|3KM$Dc!0dPPH}g;iw`c-i&X*F-~(p67luR0@68LFi zJW#GuLt%e|)@s$IY+The91Le>-Ae|apih?GP35ikm_^(9&u+=EOL?i?wn&&lSo;DE zq#h%50U6hMQu90{(RUPMqFZv2VjN~rM5c@pezM*@cFJN4A=0O%bZ}7)K6$ZJ+3|_7 z{uA*|qxPL8XF^1X-^B*4bK}tPi}WGcXn4geR3nNzi&V$-OD%KBK8RN4!4S86e2nP* z#BJX=m41lP@Fnz1&50!~nMI}EGSVtQHdq+$`J%HI6piaQ%Z<|tQojh4K}70O5Zt!C z1RHa0JE0vNV$*$RmkmpO+jh?Bk2|I2O_b|bAO9ntT9;2L0}u<8A;u+*Z;ckBV$a)o zFHvzl-UT(Q&3Ic##KQ~Ls{j`@Nnz}-oO%_&{6!J~Bfxx14}@UGNnI>PG&wX*RWGEY z`Ztj0TQ>5B4Jp~aZ(AUf`S&QX^4sMosweuUF0zSgI-XA~>+;?uv zR;SV)45adXj&_?YK&byu0xKlJ9}*K4>SYa;>aAKHH1;)%99-rodAB9J?9Nms%;Qr%{%I>>0tK8LQKN+DC@&X9+(B4TQW(i*Qw4H`mBWVj%^M5ib()2k2AsNL$H zwu{ZsikT!0Qu}qPTV|VHR-xk(i>~87*DdDq8<;-b9G+(9{G)JUbNx1uB#W(pZ-?o1 zYIFOAJyK-AIeN_wwcY)D`oEhSNH8JVL*g>qFo!J}a%Cau^8VPsO`s1`Jq`Y<9q=oP z+iegc;=nzyf=+uBA+B%eInI=|;WT;D(kiQ#kGa}X%{OKy=VQbjQUQp6#u5b7>;~E|H$pD}z$GF>hx-N3 z5xioQA!=hE9rYVD8Zd8frmR?6@k|!pdugE(A#unE!C4x+Arw4=f-DQtW8atXRB8bxmlC#*R|t-*v~en4BD<^T1QEEx4gQuSdEaXM z8goW;L<|5PJ&6?_W860_CbumjztP0L2Tp|i@L4UrEdSbY8QkfE5o}75Sojl75%K|6 zPiwNX?-VtoEh7{XLKmK7wu12IkK_=20%w#Zns%HfyLtwQJ&qF|beke@p1u|=rQbNt z8)fDEdzL7t7zc&X)H?xSH#J#mN<)LYgg+0-9%I46%1)pMlCqcgaB(ugf(|z98?yMd6Et8 z*Q{=Sg8dn?xnCu}P?J?A2!&%3-2D$6qkbbCL7?8TdlLb6^+f#u1Gv`(#qr+*t)bN5 z4S578d5ULVREnrWYRFuXSNkGBC%$^7DOIVsL10Qyw5)mmH@mi>FMiX@FAXN>jJKy+G*=-=+;^y$* zTI;C{slgM0CNZR!F2RE0y;LH#{{y=uLzizm8qmKA1n8=^n^e?@!N4I6hNl$PQ-VvG z8je^CC!*#UBq&58>z#2{D&F`DMAM#ZAQfD7F`GPplKBoAi(5c_36ybPA-?q`E_IhG zOQfM6#n$?y?LOYKgdi~E@+;rN4D|{OznsocO48aq_Z?r%itDiRQmuExuF3r^l`B5I zyq$M&u_vE7T^E?038KEYOF54CockA?MQFgi~X5(%!k`f zuumr~LkOY4JS8ew`AN~X!_Ua-cvDzi;0!IS$=MTY)pVATo^ z#f#|{UD*taP5&z(cQK9bNrst?Ym@$p!wq62!>)kV<>T#1mG8ekgNwCNpgoU&!Ey0R zTI#wvKqk+#CD{UMHgk0e3*8wZpD_q*WXGscMR_K4|HEjN4&1w*?@LsocMO{vXs78P z^_&w>>FMQz1loFvR#dwESx00cLn3N$_@+%Gmv8u+X7YdPf6`dK-}n6JW5EQIq=N~( zWmzvEdSzb^fnl8cqRj^>_Sa55`7sF#YD2R#DRQdcWEv=NsV;ye#W8l97@VLJccvg> zD5j9@l#t1-Hh3k*tUUMPr1k8WQFbq$F%P;-IYVgn9#>e(nCqKd-$50IAxlw67**~a zo(_+lF{uJghv-FVbCP`(MP5=ioD>Tgaxi~7h69GEftPBjDB%)e0b4pX$bE&Sm)QvJib;dg<;)`NU2iv=$gOINYF*W5=%R2R*Y*@lGdl zbX|$RR?kUD%Kd#rfb}Q-t1yP@C((INueHgrWYVp~wc8!WgIC|C?JF6U_A}6GO0$&t z=S!IkEy@6&NJ|E1xuI08O5`5_W~|D!;Q`HGvy&OxGru_xn8Mg}D@~$;P;5E?3a*G~ z^j8al7Q^)Qh9P&|Qr6+-#aHy#HUR|Ed;>Bs+t_5Z@SwGdh_hA}@=Mc-{KH#JJBd8V zRUQZsRve8R_9!2_p=}A-AQHO|OS0kqE{HSTK8Xv>!)9*Yp`J4hwPP9A)#5Atv#sS( zYq*dr>k4yWIx*2q3iyFpYjLXroI95U5AWj&3=}v!)mea&zA#y#X-&p}J(4k^K}Hzir}b&+nn; z&bJWrmBJMKe@61VHKJJam_Fvsh)0{!CIO^Ci&IDNha^kbfe^n}*B|1FX?)PK<`Sf_ z3J@79G{JCg$1xaJ;4!uCSivkieG}5VvlQq72qA(f{0t`8_C;1M7ZF>RTw%f??b4M^ zH2tZCKXegwl_ir< zIbh-ixAsS@r(ZE>`+$?s_9gX}hb6a8WXJg+#TAR*X_k$y3sB^5 zs9WV#;S{~ufYkO)LUNhR!JEr(1X|!!?AosO)4a$~vz$#EJ%DjU?c6{Wt_~Xrx(*9k3GMo=+N&Gc3Rln6SUMSe+us}2Qok1-4Im;!z)!{?HG0)#13+(yob zs9%ZWgLq_?DY3c`!PJ;u3UL$+FY)rw1IH z)Vl5cz=Xkc>zLas!>rxxplY-l>aydGXCPlyLiX>&99*l{P&QV^Z_$Ker$30}YZsoD zAl8w)xvd{@6>zf6qR=u#c}^_8;iw)ff@b5%Skn;s-Ko~m3hSD3NWnSG7(FOeAS0@m zN=jQ$Ijv5;sGuW8b-c-4WgvANYI)xisUJ9C(9!D~6RYe<^l#zAKGbG2hX|A|UB3ML zY7^}Z_rb~D9RB23&+A(NSSy;E!&Vr=Jvh&#q?EwfyJE=62h0 z|ELft-lX^j^K~krY+!O=g^7*01Ch>^M9m%(2z7H)?{aEl)#XX z`w#~;qh$#orlJ~xBEP3pi;u^?0}(n6>7;|#N%*T=t3I=`9?bAf)QWs#|3qGT2l$7A z#k5voUJ;14qFeb|6PkY8iJcSoBm^DSse!q9RqUnw-BAtcHCx0apOWchM8QBYJhi+P zCApT~(RmubvdNvpQcH|r-oT?ik zDHhHLV;k1Pkjc}^aoy}F?>{w?ge?V>Y|E#19K9LiFA8AuGPgo-k8bgGyjRc(*U9M`s|#03fx7Nz5wGeb|GuSVk;#9*rfxlv>o8OPK<~B zyO;c8)ahuVO%~PJOVy2dBHfP2r4^yj6t7xGomEHmz_@9eVXONUePafra|_Cl^kM}d-u-g2-C(dwL-#qkEhdY&^6BCPZg$Q2Q}>y&T3S)g0E!i?)LjEP&1{*q z%b4_Myem8(>3ll~7qY8Fu#EtlgiK^fGmj!)1issT?{;r~sb*dvPCO^tQ;0)rreLEN zB#pWu!l>IDsf|Y|5_oj6Ur4z}G7dg?vEd>>N-CZ5@0U11Wl;e$31`c_EG4s%JBF?S z(Xu$STNB+QVY&L8?3eHU_MY7lsu!r}y>d5&q)XOPfiEY&krdisa@ ziTppl@COYZq+2oah`neZ?9J!C=h?4QGi+jb8}o?u{a@~*nMP8H{-Vc8l`tIBiGc28 z7@`rpkl0J7BT%&j@%z-kbNkZmI6MENlOWh*o_G_CQyn8ZiXX{#uno($Mq_$|?4LK4 zVsQiq4*cOfP<0IFHARAtQgCiHSpo3pyxTi?R?Jl`w}n|L;u4K6 zLz(6T)S%J`c~8n7{jX$0HJ?uaQUQOR-K$L>h67T$-QN-wUHO%x6&Qiq9OG3(loS5i zwYQikO4ooV$|QN0>s~hHQl-?S zGCUrBwVx>`!dC%9;)&;bO zZm#D|sEv>VO7t>(BI3IE@Vg@QOT(>OYbhZRHhD|ShpG!DI}LGiNL{+~w(8MnI-tlF zheWCs1nB0K-R;N%Ns9QPkKDjKtR?o(U{PiJmV7BP`PWmBmDF;TZoe7d1>{LMpuBE) zgNyh!MBLBU3Ohef1{#gq(tzhCWz&M=vRI}yzy7Q#(~Yn=tX({2=7mWP7Q!vYb=I^j z+S(+CY+{8fV0k%S1R$j%k>N~f3?K#WIINJtSVnMiQa6kjRmoz{;ApsK1L>2o^c zN1KE1K?cI?1fJ?at07(;}SN}wvM4w5=L;7FmbROC%L4_FA)q7)~U4{-Y>RQp4A1GI)7e7 z^SE?G!NAVesM;o-CxZG2zhgU1$M4XaQAeqMn;&`jl-f|xYtqkO`RfM*38YMAOBCXM z!kOxwMDwRTZo&;x1pi&sSKVLpWii^BnE)wj!%Di7L}ERk5(c4F=HLzOABDOdtUT%m z&vfq4^;OnR?spRZ-R1$DhInp9_4QRy zh8e&hcYhRYqV~(@V;m^lXGB#*h65G3Z?!?-^&L3igDP+yy%aX|Vw4r8XfJ5-6E65r zQ{x&%>Dlm5fS!fL=cQ?whtEgiI6=293XJTgb_)F?^E*`EQp(%f-2VKpDJm)D!$J2$ zG>+4C_9>m~ADKCD{})CN`*uK@y$73i_t$Vsfm`vIfYlJGk+I><%FxRmh$Eun%x#u- z`PBmc9OVjVenw7^*5-~rQo*?v>QW=SG{g`Z3R0Gy$gBfW2Y*@CKATpJ!HaoXNX`|1 zP(WPg{64Ss>ThM>?fR=J2rITv&0sPKJP*bu#tvsRGx+MhDqB#v?D_rPa)l!Fbhf%hhSdkSS)@r7O@b_ zdUDX3D{AAi9TCIj&XG>)>Lfc**BfKFoMVcuUl*EvuPM@znSi_#mCj;+4Ch0Uy`Gjd z{L;Eiw~Mb^Ruu^PB*-++gs|bAMUDYyBW^qnodxawQo=xoy&DXBe6BUnsEVQ{LNY=Z zctB_%kv2HRS0Q%NJ4$v&yug%W2N*5JRffYruDj+kkwKSKv$oq%B)JV}@$*j5e{Z=) zvL+Ea_NR?gUcgm7O0Gb%KB}&LBG3?4`mu~u$3=pvDeh6U;PL%dwMg27^iu_ws!6)C z@J~1@Ok@i~uS+dY%)<0P}{%p911#%MycP zbpF8|YFE2(#?hc=l)6qVKyuEt$8gFSS0Vr{4p9#GqiaQAe|gKpHE0VA#1JE6bet`T z%JoC-gA+=!)+lxNH>mqeusf)4wE;)_>lVfrKmqJvb_W{--?FT@EJJ2@`CtF-n_K$O zSZLHm{;)2?lw{>UIJQF-+T4;j4a61nK>rC;dOLY~qj7LpQM+Z_#Y>M%TXQf5&ai?2 zO+d20C|eXE6ltD_l9sys8W^K*jp&K>Puk=TC*uD;lmf-2K&prpc(>;hhP^JJORxu( zMiL|<)b8@^4z)F4rBO@X#aOGQkz>D!`ek|SpY-^UGWmF^ut_sz`%ZHYO=yTC zP01Zgs#7q!BDxMF1;shGLKpUsi}%#1I(5>X@}GPmc<+-aiWo*TtHf*zVeePN*>h+k zJEs_D&a;x~EHs{RnTMWjKnjbe3t|TC0;T1{>fzd6wBGcNFpms(Y>{*Z@J{nB@OhaD zzs>W}Q=NPRq=I$y>+C8VvgqHUi{-Z?i6I@ADr{kQpO9fJR zaWP(uC8Klpi}R~hxFVJ4H;33vk>wtcv)41*Y_Wh={T9#`+Y|-LMBvaKS_ncxZB~N^ zL#ioY9p4EhXA_$IRZwJ6%CmKE~reMa3EGh7SlODDAK>g@A6xF^&B zv)-D4Az(;bMDj9Z-RCHUUq&_|45ZSF`kStocPWBN(=$3TSG$AmbaI+BHM-`C-bQl+ zA~a>`spau>bf{h#5s5_;`!~#w=bx=eO&%lzc+aSdZd|l&vhvi6&)r%xt4a=^u8@KR z;FzRK5i+_nS1BCV#iW@E_Y?n#0oQ570ZrXELw&9-R#j_3=6_-gC4R<~fYJ92?W|)a z%i8^CAg6kRqpN=bQH+#|!X(BL!uz6tP# zV?l}7!{Q^rlE`+4ARLqEvIE4{iG^dLELL5(n1#u;vlq*Gt`V3?fRE}2TW)Ak#mN<- z9nH#_XdG!)Hwnp8qt;v$gnuwd!My7akW~cqCAQ6St_cxrWsK{R2*kytX zTvmhD&ICrkK*tY@J@JPxF{~~xMKBiB$m)!S%D(IiS3CIURgv?(UCQzOMSmjtTOMoLi`9=vm*t^zo69kS$OTGm> z&&rv?-vcsnUe29-@;)Dff>Kygwh6=624Pf@WfTd$-* zO#2kCg`lC?tSm_PAP<{4t#G67o{R7p@URxLLzVr6`^|AaMj^7@83fof6Rz8>aIn?- zYIj&kMMe2AjJMsmi=7f~HM!a7gM+fS7$Z1<3E%lgI81(P?2Rr3(2x)zTSqw1uI(-I zvc~$M;|>|v_crAT=Jr!3vndJvkBG1RCaRws1KGZUKm?7>#g{A_UEwnJFJbYMz?&iKsXu;4*scWRUC(%61OxO2;Hx4 z1^RO0HZv#I(h;nwU(4x4Ikt&gV1xeSr~!H5sA=QzkD4TDwJkLnp(^2@>Z?@*may&C z&EH`u$Ifs;=&?Iw!!*o#;yKmIMhm&k)$M}24mikhVVQXvDt{>|i{Z<0@eYC2FcDf3)+?uv@ z-Igc4V^RT)X_z4wTzrpMlwXN`3=yYTJ=ptCN{$dgdtEH|l~UjaW%v^UT!O(=_H zC4oia3B2R)Ph$#_d?Zm7Y%a9TA}zBs5ah7KG#96l@Gw6*uZ{Yozb@H-7M{fBTTVcVgT0E~LQ53UwMj`v z?N9zz;u+$=Z0#8p!mmlX$}N+LB!xc1Xn)+8_8$*Pkna)90tW<2Xr}|S8+|A6QigWI zL{+}K5UqkYpdo_xeH^O^zn8a9tbQmGNSIqnf| z+DI}!D3UYg{bI1ZUo&HCM)CG}wbo$4vU9p}D!v{7cy%W&5A|TI||GCW7@wnXH5<<3;xY>N& zo*_OoANcJ9jYu;A%k2SILzx&%hkx+=hU@v#9cEEDzfB8#?i7q&Q8r7{x$#t((&3Pf z44whM)gsxrsk}`GrRoXRib|2QDrpgDsr67b^!{f+a^Tix57>3rEBecE*sl=RTnXqY>?URata;9G zBr9*b)wU?hV=Mga$Z`ums-vYzRPYTRjBD{g3|J=WkvY=xJakFOnP8K109VM zxmO)cS$*REPGlA03;hbFAQv%=1TcIUgt%47Sf3V}4M; z$=2Sepk)spmXaSYh`z1^U<5w*r^K0VFo<7Mvl#S*Pyv-%8m^#mwFovGqc)5hi|-g< zo*G?)Zu<-mS=9&eYP@&MNHhe#;kEg_W)j)__zFXi8HGR|)Vz_p`L#k-R{wEKFwi`g zC*WN+uezy420Rq}t*BZhfZdzc%AnzwG(d2gq$4{2@Qb@DidjKSXihwrfCt_L<9Y>q zhJCGckq*O>yKMWS0LE+cW|C$ouBlTij|IM4va>jKF2d-oI;ZP_1G8+w3vmFYLZ1s|(@fitZL1SK3JTC?vJ3 zato&k>|O4*j>A7^0D-3iM~L%lmvH-nx~aD(>VQD>ZI~Lml|^teG*wbZv8IERn0o)? z=C;<&V@+Qok&mlSU$n#T(Y7YQw6ytG-)*x$GR=v>_!%^QtQyw9@0&KEvQx7NN^oL1 zvFpxR2h+pl>0m%-U#cq|-Msc=ucjApL-Ykb#Gtp4=z%{f`9VV$dO9J52Soq?00{5( zoA@%r=NP{qyVDZWPsj1;BdJDu6fab))7i z@~yffxB8(TwzNzR{lng}WW(!%9E7O?lb3OWQZ0Jfw`kSOrswz^903EAhX}%#81mH8 zH%G`_DlmJHxcTB2i;9Mtnzu9?MFW6T(|bZ%&7SCa$0){dvnrHd-vst7n|-I&oKN{wDaA z-^f_hp*UJs`^G2^f#T~HrkPHL#pN8DpR$5!V7IJa!)q;iC!)>{qj8&^Z;1FUVL@p? z+=P>m@8i5I!A&6v?Oxss2k98D)3G-X+Fcb5eENyVG#V$SgdrxUX@F`_l*xAFqMro@ zE2ft`>1?JQF?}lJA^_&IMDC1t^>5s@lh>5vc{Ps_=6#P7ty8|yd}+|JNTyfXmh1VS z_MTchDnM8Us2)8|-$s6E`RD-0`YF?G3Nzly4x6C}%NgMQLW|MZaqRa+@*_xm%U?a!lZ}ic7Yo z-}A$k7&T5ztK(T~gxfc@xLxpI(m6PXs( zXlozsWbhxnvZbO8?YsT0iH4p^PrHFhTarYzIq5@QqK4%X+yg^5>hyQBSh4nN-RqY& zT%>)yxShBek*X3-)=IC=x71F9krma-EL6ReIIk;(|Lp*`ziuy&HX@{ZUOkHa*Gv*0 za@VL0`RnWS9}zbPVCa0TuEUmuop7Pa7%N+zlDDpv%J%^?(L;J`yQg=nUVrNX6y9@2 zLu%>Y_YVlREgX&XVIF@Gs%=n(H>z4k8$0~+?7nm1GkFgNOEP z%CH;+H~1%06AWOjU#w0YH8`lP>6!Qk$>^N0VpxB(SLZp?Q7A*xbHyM;dU{I05i@VV z;yBXue@pWBv6Exqppu0tXR@=`ujizAfgDmQPy({e$<&stUdatSSz9tneL!lI0+F8k zg}m4?-j{MKc~ul7kV}5=aDZDk5_Y?)NcD#xcStD*lOrC~6fY80Y`&ntz)T zrQ~AI%9+<>j?F8zPL6ETOcu%J zF--mC8>1!-(J}*=y>QLF5Q4zweFL0_1N85&+aGE%!0f3gi<87e2WmFp3eEzcl%x|M z4JEE?>qBR_i#3U4m`}0i`nD9dCo%SU0yWE`0Q9w0W@4v)cAjRaM(S-t1Ed;{ASF&9 zv5{#qNMm>jrw<-J%Kl_)VrV6pY-fH|7t|qB_Cz7LAU2X8Ki;37&rOrx%l9-^#>Ysg z9{%{Vy&3XaHg7N7!u$Ld96n8aPF-o^)@E4<`|4JxlmU^Nb=qP-Clad8?`ny714{GI4_=i^8Idm8<7K@3q*`jx~1J+nW($q zddffCyDe7d6|pTz73#AE-JwmrnK3uQ-xTxq`gMEOuR1@xZqh*t(3;MY%q7!nboN8P z3!Szgxoa|^7KlNR_5r;zp$9@3GJkDkE}zMwP&U@`9qu+Ro!I(vV*tbP8}d>p2wzjKLB zV)o%r?_|s?*hXzqNz(V%=Xquzhw-xFjLQ3A)Oa-SeC#q9KYlXYsLZsFGmp^jl4Uys z`MqEm5!DsrRT`mGRgf?7TzqylIZRqU2&*hsC@50bUL>k`IOPzUP|4F`K7Ok*`Bajt z-r}DVYHuxl=!Ptcs7<_)m={{*4zf2_NQ^c z<&V#RFDj3+ft=nRqj%7;3UM3CqChhZeNE#f(p7=1F}~h;UpwEJP3*vKr-A>jEXD@1 z-%4b%t)5C1|F05xxCr92q}}3xJ1Wym9XID0sXr(a^!kV!$H`0zU7KnC2<{5hPmH z0dk*MvrY1IgZf*c;DK|UqOgqMf2785NfL9?y zN0MbXCAgiC=UuzIcFfp~d_!u!p$$P$h7sU93vH`p-=hhg$wY?J4GJ}LG{H%mS`G}A zcTr_3r4BdKW|3TzKCrM$vG_T90lqZgex$B(xm+{Gz{8bh=o?)d=)0i_R<33sPnj#o zw*hpGI@bkJVH;;9P>RmZ{TQNTAqEa|fuS2RlNdEtAr?$G)vR?PFabN~J~+UH0f}98 z(?K5;Tp!Rh#N!}ofa7&V`@{tdBoQAWbt!8tY(^N}CsdJ{V^h;LOi2Ul9J&V@ihB=P zenYykm{4+4i@(*XM!akHR?d28jT4hvFY`53vL;&C{m~!{!Tv?&TT2dFpkpjti} z)r4YAc0mWcc*%~bs6sw9PD|5|+e<|FE5(^I6e{!g!o?vsHq#@p1Oc_j||lzxo6>4uHqzEENqKc z^RD^wBJ6B(VIJAD17k!Nyw+!t55Z!p^sqf0s&MxN!!B$oiM9FABnXZ!b)5+A01;J& zYE1Cv5q(#;O4*;SF5`o*8E{2+QoaK}n0QKbt_X$EI-`*R!R~@JnJRtqW1E5I3N)qk zwgRL))i-oJM*4TbcilLnbvwrYTh>-fv{~KMW#-Si$VM)v$v7P!dI}6XZQ?Vg1PucY z$I!JoLD9=JNR6W&&>14&#h6OQ5V*5{T#*wAPEn5yEV0}|Hqxhf#UJD-T8OnY=A|nC-H+S6qC<0 zFLD||#8lI(xQB>PtDeB*paRkQCx=Ka^Qa%V)|9iLxrsXtt{>5S9Y%I8) zw~1(G33}ENBSXRVoBTW=;oheUX}Hlk7d9bnod$eLPra5HL)-=oTYWsx?sn6AlK^z3 zOu$rKS$rqYO5F-gdKy9CWALZI@EbAgUZ~_{%U)ys)?id+Ju8M}Fa+(u$^_(i)Hr$B z?v!4tRmfh&JW$l`uD8pvoS}4juWe;bKyJKnMATYo>tk9G!|C#Qn2PG#t3g6V{KIO4 zt^t`(mlRNL+1mC_T%-p8O@LV#Prb;-%2P7vI8YdrYirmuLh9AbaUGGrAqAINhxjX( zgywD+&5-Z-Ev(~~o=X`IXrp+ze$OllMgm7c=Fav3>AK=#;1ji>@BY4&L>*7^3T0bl zwN@m|vjTUDd|4NnQ+>CaO2OjQQAwN;iu6SxH!rE`U!9&TDdsQ{_u)g((STP)(LCDL zs8-S>7K5Nqtx6Omn(hE6`qaAbFxt!@TrpPVFR7?HuR9G-F<2y#C?a5xHAhhDR(-Dj z)M^1;>Xh7HaoKvHD2<(pc~+k=Cadek3p8GUI4$WQplhk%MdUdBf#~$I`izw>4%4S2 z0j5Q)Udcr(Y#prF@@*yyveu7?ASP;Mj#owsF&GaU{C=707i>zsnb+&!LxXtvcUn!xk zqb`c|V093vaOC_gXfyuILYg^@Nu{tULQ*1918T8oUkZyO#v?(~8veRLa?++#CHAfs zNo3285MkbYFbv7KtVVtUZM^h6kvtIz^~IWt%=M;5JTa0I+G?7x z4amTjpVh|>5Srd-)?jH^uQ15PnzI|B%8v5B?ga3b5G!)X_110dGJlmh#aW(9_mAr% zZF1*mBIQ5k6Q7{2LDCt&MVbI%R@3X96%*y8zO_AaVV{K~IVf z-foJsj6GCH00h12^e|g`(teTSwLxNj_Qb;YP&>P2uLwO$_yufQQl>3PQQKAaBHVR1 zF^U%1);IZ4z8C-iGW!$>b$nQX02iR3k7q1&71Az&(L{UDpbsc;_fHwgQj46=olF_E5veHs_Oe=|+;@W=60Y>1qXaY+9`0-0=7gB){YC zSVL4UZV9LPT{*_7(^$Le)KR1k2XxLEbPljS0Lc=Gw6rz{@vAgllHm0{Z4UKX=kNtE zZiO~iY!cw)Jo{a=GGmUe8x!U92A+1p=eS<=(FJXEvR&A64}*ZVvWF$;Qy>d$CFIv(=I;KUr*fgxUS*O=AE zFO*Q+@tnilG)Xyrdg=`(0974$82p#xnsKkW3YIMLKa2b<=eF06E4#_3F465@HNASH z87xb-@x?Vw2I4kOs+!>RaCB8*u&a4>iA+P}n`h3~g&Y4>f_z3nF#Qq{FrxdCyrO*7 zoZ)a!r2bD?x-@FDx=cJi?Z2z|b%$Z1I-ZWQ;u5q-G06vgn#ObFD zuKUykZKMO_OmXBQrpu@c^GS+fG_&3v^}>~Mi$}!}E1-OAFAT%cP*Md9Zs{D%ODUbd zdH*7)ASH@GOtxL4p7|cH{Y=ZCQf(8q*^*MWKxK+fGo_r;+DkSEd6;6*798Y;f9nOY z*OVe9O)x>OPNc08e=~NP4pxGt%aysjz{j!p)5ILow5|h*;_%e>X!GD*ZvDnz5!eFK zYWot!z-u|rvyv{=agIETh3^N~4%&*!=}|u@9}>(EfoaN4y-nNCBO4+4d03p1$QPKc z3Te3s&nNO3Eel?%NX7jLT$a3mHRa=P)XG<(yq7;217~;$PjGb;y#D-Zuf*V~du`98 z6EvWcC7~{hOYxm0C_>Zq@LtJ%K>D9jEXJ#EnG05>FvY4b-R$Po4}4x)w~}aUZuH8D zkrBspy*q($T{~ilPqNq#KNNB$3u^wKvoX6g?^c6XToRPX)f>QWIx4Lcq|Z@f56<=K zjNGBD6(82@$9YG=nXEri(A&iO=Ud04zKmi$UmNqH!w&)eETwlaX4n|YC3QTfo{c!W zuN6jM!GyivUlt;r#2aNQ@tZeIJW@BfqFIETD&=Q2w6pwR-R5qiVr0o&wSjApyee`AqZ{zkqjYV<#ZRXbYzoR&Low6lj?naZjg zhlmcwd~SudVn)>Vkg7c*nziX8>^1&&5bHf!Q60)5GXFiYccM0}5j;9M%J?o0wmrZL z9uni{rN?mDT!<#)ifPq6Jg8J}#hP7${%O4{p6SL+`q}KIl%TFmuRV+6$XzUvD4OID z!Nw~Z3eaGS6otAOZDO>mbcpTE;b~?*2l21uDsyLeaA&@~k%JiI@0^L} zpot3V@B8kJbiosFW>Oae^c(a*1X(H&1f7L<8<1MWi|mOREt#rY!w#u5IAvg@-<=rS^v6~4q$k4LP8{KR9n zb}SyB&tX^BN;Nk=<}=`JlC2)Sf#2Z2gz%EAecNbHBU54psSMxHu^YW*rYe z9KzJ~w)f$kCOw{)REt$jag4Uli(yY=+#PJtSEbDoQ%+6*nSS7JyZVvYF}zg%j-3Xi zhp}hgy@_V4mj?x1egm+9$0IXRwCReb;PlUM$~iCu)0cOYcH8h&IIy=!7hBl3bZ16r zD8ylWFAF$ooX4SX1J1zd1{6g)mWZpg)bR)8{?dVm2R~MHoQG)uMVcrQghMMtW`Wk9 zGzKq{Xbe#_E-Q zE%v-u&nmAQevb$FdL6gbE~tG!NmeOC{hcR%WzQffK_3tJr{|}Jm-KX>3qxuL7jpMg z7x1r6`hxI(37i@3)?D zPUVDwTXPR3u`8wC5Wc=lnLL|#@hEUe?128;wlJ5ko2&cN6V<*+8KvvxKl{%2dt4tL z&-v%NANA&4jEh@juUX~@@}84Hy-5n@EUKUr)l>l7ezN$QSNn;@_(3{t8DP|5T*Ru1 z39S)=??y&w6+!s&$*}|nC95Y^>!w&inKlwCw=Sx+^D9ctANo2X(uy_uHO+2BlbI{} z+hUk+^_hz5f4OT}J!4#a00NY*I^=(|q-iaS=aIzVfV=IuyKdAOg|-~0V9JJeaEK?s zi(m=uw+fUUw)iaZSU+q18ep$x&UO9%P+Mr{NF;vEL^Bati{_QOO7v<^8yp!?;~nV; zG&8`(WqlGM+tTr*j=jU8^GUBkK&Tn4f)Z^;+{R~&@7>mTdIjC)kj64Aza>8u8MsH1 zw}UYXk$cm%Ab#nSsuB@IPDT{2rpdY^&EU*Q5497IL#Iq^BZG*2EaVtwRXg&i0YR=@ zHdDX&2=xvG%pH-bWjwxE2MxglJ;)-@;>65 z#mhkkR1XF9YAkgwHrV(*b_*Vij*aPo#-TuEj~)r=ulVJhCX#`4NZ8K5290o2TJ z+u30|t3|3~=Zt)1KEs$~xlQt1xz!II0TU3Kva*bl0l^>$#nPN6YRzLto$E!AaA?53 zXY24#?q_L|=|%U42$3!NlP{fJ;XG=*>i67;%)`?@1&rhM_?t8bM;POEY3q9^^Q&~B zJrwZFi|3~xKV5CeFCPNC*dP2~w4;;x1T*DS>}z$FV%4zDVTZVt^tViOKn?!);W^10lsUu zMN6vOLMmLi(i)Uv0yj9E8b>1z%E3^Q1f1=OwbPQ|kYSJ9CowJEi6PinS#&(@G|D89 zKsRhbbL%)7OyMgp$k%$o^);;1JAv;3QY$dN=i|#sAw)pR!|wbvZ5;j41UCqvM?wVgarz^Czl-Mc|mn!&@vj5|g53S-!Sm%8p zK(5sUaL<8{B5M=Be0ylKbLCy3ghgW_Fe?I*v9?2L%Av|-$%0_&)PiDcg3_(3!FwC< zW35a*00e?_vNptqE!hk)iCu~15Bu?;sP;{K=>K&_6izeXEh>^%cbouNs=k|CG)#>^ zTtW5>Qp4Bi)$AEvRGzdR7I|u+5nlZb+BNEs7MZT2r#i6L>}Y#0Zj&$}=<=?puzDY) zhhLaN;b%3nk$R27d!Uhh!0yVhS^2eRv=&5x zfNgZfn_d`~X=02AN8?%KtigJ7dZ0l@ZZt_^FGZNmV_sux{93j3LGAIAR`|xVJhr@Z zH0X+i4%(DrK}7K>cs{@0-n4pc$BC!2j-sQUW-fh$?aRuf#*M()I;X;R;e(#Cd`fG( zfMFn&_A8or2RZF!(TKGbcwAujD+a9Zlt-o5Y2TV2^Yi>=;OLSwP54b$+M>F>nA@G?h@hJLV=k}xg(#TJzPres1xPF+`S8IXn@JN+@T zH_qA+FfWs1Sw^3fOxKnq({4j@lcU1we|hirBRy#9v=D|mSo26j9}&}_mo51EeSXKj z`m}u&s!m7cPgJvY|F3}pM&P)kBjxsk#VyoM5~)7Zts^fbW z*8SeTme5X(Xni8v=i6SPJAZDLiQ(ZNp-numF0$l5lRwVzL$VX&JCh?kTal6FC$u#w z{GFpLXEjqPSTq%kD+272wpR1i3chiq{3!lojt~XKK=~U?t@JpDEv+uXBZ6uL!Wq%q`nb!(G_R}Yzb(l z<`>c})2#u&kCOf`6`ydBh9|7;y#>HaCE%q;WtZB!(xBA8fNin6gB@wPwp>b^_mzA!xabk}d#JT@-y4% zSHfIR>nCN!7ocf%m7wZ;1Z|`^@Bk3hcw(ua`w>@5E(CI2d*r6(OafAqIRp2Ln_1(16kJ(p z8g7FBoIXpkiAmv8oE|ErkdX?}XK&&Tm$>hlC%QgwYYZ!m@hWMA+2NxxHJZZTau_p5 zhqaQ+XmzO+s92m!&f*e<%@X(ea!Zrj8$SK`+gD4+cxs$C}czJAJ{xH zbK7%}vIIW&sv*z$SIMXP_|TJMect+TbSZMpT*?b zA&`*+Dto`yz0|FGs-N{#aI1;o#A!;zh@2O@z^#CJAL!C2y(T}10Q*%mn~HQ@k7SUN z7G|zw-PBa-AEe#l9mD6UI&8?{PuV*lT-XqNvTImr1YD%53rio2pd{FPC39j4@K*z} zb3*M{w9Wofq#Y`bXRg5o>H#2ipJvB)74OCvy7^_T&jcQ3YoSa~Nvc6gsExF}ym%`f zl;|LU$=W+hsuZQDq48;%j^0+G;CmB?OAm&giMZD#?s!TYKrO=Q&^as9mc5F6uYeOJ z3nyz8Kw;yoR3>VGMJMD#$%#;(pB-m(9Zbie#U2x3lQ%O5teY_b1Khf|VA z`X9xDuc5>XJye!*zWt$pp7ZJ(U=xvb6_1mDZ|jW0QO3$Mr?X@tdn^5W==dwH8mdk` zuQc&?{%oIJI&8sEYl3zCT?h+}7HW_gR@lQT_9tOzeG$SV_BlOCpS88>H-5gM6G%iA z&n5L0g4A;eeF*lli8(S3jK!*p#%w)kep`;T9BS>o?ttQ0ZAppg%RJ`^mzML9(p(o0 zv)%<%=QqLccdW9Ct{Bo*lfVbVstRIH;rLJL);(QdkSy_3dLl+AxSQ@4~KsXYqOYp z2*h(3$FujOTYk`WgL4Yjv4uz)r~gS<`2KVr;D6A&|wzwDt#5)y^gb{*CdD zRd?ST!=S_cls<`3%i2AOX|qUtcn@l;O;whpod{5qUny|%4Mwm($xOzQrj@%2C}ZcE z{U0BE;3R#)@xN1NAG9sMafK&NWhUZGV@}a5kiExC@Bem@c*}MzcKrqjo*KKpdghj^ zlWFiZe3H!8UEd2r$+NO{I{dhS*Ma*U}LZ_INvuc_)PsMcd_bcJqN(#{Mcf z{0<@DBZ4@r>Ghj#VIf8yt1v6>a|j_ z2-sBq7Fma>$V-`NIlNtN*T$V&Af)>FHORJrzE(gy!yO#pPG*;GKLuPPt9cqQCQ~hl ze;ujiWhcJFnL5NMOa?hs1x!@$koz4)(F$hf8`6`2@c~*cu>1}%$+fctcjgkyomi=M zzL#m=6`rNhN}Zo%+iY&eGAgzrP?y~)w=xL83QHn|t)+6A$WKn|b5G~de<7O~mlFO8 z)8HJ&eP88mOX(i;5U|sbDc!GGP^wGaBVF#DywlSzk3+CQSf(-os}>qTj*bCr{Gf@} zB`~=zZVB&tR!zt5fxbJcxI5_Sq_eY`)BoFu=;sl8Ie(WMUBj4&>eQ?n$e*q@WQM&Q zbwP}{ej>rz&K3z5(1fLg128m^3q?D99q!JmHFD+5A+bAP#^&W+vuI{13+(sgYjtrR z4HEo4e3}aXl=V7A$+Mp*oi!{0s^G=(yt(+2@OW^d9*K?SOAC2_*|nsZ%uM%r{$kJ*nqb@IVHj@5Yo~ z4h<7zf8u%0PcIwNPF7fn+NZRd)K1Vz!yr|%Qt{Ya5(jAHzWe^p79PXv5XL5x(xdxu zDR?DrU6AQ6z!E{tPraF$hL6I~G}@~F+X|0QC7r6rk~>-caARD=W-PUJ{_Kk=%7pLt zAuAV8YoXsL1KY3kK9Y)13^S_#7r(#1K1No41#MZZ%6q#k##DR1fJs;p|1Y(%6pei-ne=`*KR$5Dmu+MqO3_XDPEr=qb(<%H?b(4) zgEXm7RcD5a`?67FY;%4@rYe~p$2h})pv@FG@|ctFc_@!IcH72bgBvzId7&LEJCf=_cK|jBScDz zJAmCEJx@CUJxe@bxsrW}0Mm$|PgZ04>k_W(x#Q#NjN5h=NXmTz4EG&r`c|Fue%9$} z5Q7}e?t<9|@Zq{1bnXu>^hl88C~wJivVYtqQD)x!_c^^#nQaxl=nA=*&GhC=OV#Fp zjW~C;X3$*PvIruuJatzoo+ssyDah??$YU?qj%xpaQF-iGlpG>HubpR}9}NsA_kaFS z4|B6b=n-q!4iKcC-bzy>YeY5wSqOF4k+6luh+d$Ja_LpAiizVe)Bd;$sToXXy(V$_ z)qCbZcm(K1h>|lE?Xdr#OfTKI9;ic$QPz884pj79-|3S(&`P;2MxlIts_>F&kgvYd zwdAazuUQ)9tPclKl7}y{m&70RwqP1mX_Tx6D?HVIE(<0b?IYmi9&J34DPqm_ViX~QYZHSUy{q8x-S4;Swv5-nN^Ze7m?<>lt|So-4vCzqynbP z5bi~!-qu4u(c`GCG?T!?XWm05IZq=L(9lkEf>V$v?9rPptdjY~KI>RdfhL1xzG3^f zZd~+{-(c-a){znQQ8!?3gc*LBo*kBB^B^CgoTXsf0Pv??`K~Ji#%HytO{0}S5iGt4{aRw}O>f@5eHG^6{e9g0lIiWr=Bj`C1Pb&tRs zd$7OOjW(!{v13>^RraW<$bN-x?UaQn6a$I19T|{rDhvMI-&W}x3-Yi1OI~P4Dgin{usMqt)fgLc1;emY`$~u#e4Kz2~?_Ljq2ghOSSv% zotl9>5+QWs#+zJXLb`O}ABCKwlHh8<#=2u8FQl0*l`U2SJKIqERK9?pee8V~82L|L zq=)Zs?~5@(H8%qqim%wV7y}W=T-my9v(gkXL?d`H;Hp zZc22WYqZYkCMvi2Ii*m;kgC?3 zLAPsOeoOMro*#G#+S-6UZ#lQV&F6D%cr9k5yxbglTo2q_F zGJGFMPfuUa2K>nzQ-G{k*1WA|*wdeIuWZBI0LHt`Ffm>6H1kl30Mjjo?07!g|VSQsMweF5R~E-Mvk$p;O|oG1nI zs$rnL?T}gX#9HzmaOGj$Y7(}0o&L1&$*8={< zw+=K9LF~w}8B1|~bI>RnUP361HeMGI{y#!tEPx5v000&P@bVP75u=Avv#(tsgghqW z9!Uwlu-82bK7qI|sSr|Qf=^$*K%%{UyAXa58i?MmA1sopGOC-c;<(s99O@$}uI?aq zWz0WKf7#}M%4#C#@I@5Ch}FOTKA|c>yE}>e>3H_M2ypT$o{b~o6c|R-*iCu{NVCtS!o;XD9F{q% z9`mGBCYK!un)Q!UBkSYS9=z^v1aTf-OYuG^M?-y)(RU3p8Y$1&ivXdP;jK)5AYqsl z(Nyg!%leE9ZNeip)(HD(qEk+OoO~D-F|9YyXy^#E6nD&J+gK@;%lCe9z$-kEr~C*t z8D3U{*tr7Lv=lei*lu%&Ry)z)5x5`mL9Czvd%MMF=XgPx&_d>`T8Sa>{mjHvn}mH` zR`i8vXH(ttyGnMo$HA+oiw2*Unl7$0>+&%!WEHa}vSfJRN!GWODsjWl%v0CA?c`rK zHRKlB;NUi?53-@(%M5-lhi1tPLCfW~g9i^!eAg4%B>t7}&j|D+0_P?%Vbs9oTS)2r zI4#B&Z;9dq2h6Eb9msR~t~Z{u!KBk`$`3QJDN$h-H*xBgSKy5@-8sQyX29-Q*N~^u zSj~`$g8V-zudS}~|2bHy!{YafGH$)E5cZ_o!#svXmxx8h=1+u0&<<^;Nu2@QKm-w? z1b_erT~sNpg4UDvr%|AQL%;5Q*$P|$05V9AaLDvexE%Jd#+c_%0l|L&BoyrYn$HxO zZaPEIjfI7UqQf)!s>y!p6uSg;Y8f>xQjKN|c+(p`#8<~p%61Uzm>yx6agsFkg5r{_ z>j-*e$d0R?cjai@K199VpNNjXfK7BaB`%Y{+i9#c!=_}o`u%l1m;j#P3Tw-zU{uvv z-HdYU|3$mGJay@Q*s(9`h#rC}LHcIPqH&5?*OoezReZGPg3YC^dIlHvz0NQ_arvB&B;}*U3v+@#%mE zLqsB!`4XSR_sn-4DfDNa=BA7l#BO|iQE?b(BG6PXAIA5QRm4lm7MjFoPPX_vq7@;{;y z#c`c5dd%H^ca|Sgeu(f9>3mKq`+-Py(Q9koGsFG{uS~;fr|;Y5aiy7ghQT%2Svx?6d;<4pv9u0J&-a|D^8!J_P38h_HS} zou5iWvJf6!E)QN=TI+#|Il7>A7Wj5Jzc46Pa+ySA1K*b?;|O(qsru{>n%A2H%=$ln zqLHYsfxH@U8_*NdeOR<~ORpfkfPtLTfbC=aAeZ!6-VP10+=i)5473O9txB1A>un=G z8hMH_!&rc5%?9Y(slK#zFq93tBvoD_)C5u+KJ2MXB6cZ#>K0D%vuGvvjndlk9w zUa8{xZQg@RhqH$g*#nNMv+Pp}WUcS8t(p9=J#D=fjtaPB^NlWt#OD+g2?`joN9MYl z_#pV!S{x)zgo_O;p_<|ZbMu)3fOaG+j8XMaPY!d|u7d)f|F~pZX zvmp*E$1N}j+%|*V)(L`Ok?Uc7W+-70sUcli0JDoflWJiVDVo8(_IOJpxCFlG;B2(RuOJz`Ctd&%E9NO5uCu15*zt>V7?=)zg0yg6|V&uLQIli)980BaMsY;7BNY`9Ak^ezia^o zB;n#DP4t})bl(oR+PjexbYk`axr~dUt7LEdXK?X_2c^^X&82@0m?ZBK*c4AgL3$Lk zSG&D`#c;(UL5`4&p=lQn5iYvhz6?YT%j z_Y1t^ovZ~9T+91sdSL6e*`;5D_UjY%(}TaTE-Vvn`m0JqT@JP*(YPM8(&@*n9ID^6 z$dZC%yRW5j{MydFi~v(dRDeO8@r1Gs#p3sTO{u!bF>TmFMU>F9OLAQ2?c?dJ0mY92 zFr0bD@EGoMyYpj)Q*-Lw)J@^h>1dN%cEh{|1ue>BC;<5i#s{(^&Eu{6N|_$fGIDms zvyZc%+m&IwVM4=WL<7_*B1W{$n6DgpD<(R@Nn4xmUCZ4RTlPHZbnEAbB`{Re-m2if z1DfCSYcW4%o`bijsZ!WPC!Jj0mWuZ<+799WIoHk(W&%rEA9do_=ETP)eeEay9;YUP zC2L4&V60HOc_tluB$-stY1e-Ol7{ToxsL+H`{l(n!unT$p?B^3{VxBI5LrrJqbMs+ z7aG6eOnNr;olgXtY$X9bz~;*lFhN1vEHK{y0hlsRuj}{>lrQ$tZneoHpZ6Hone) zEt7G(N#fwtG&?K%WUkM9rIDHVcE{AZ`vmn@x`B-90|?G*VHg%^6X6XXlL5osCt~AM zkM@z)m1o!>tdIf}@{32|E{TN@u(gBziU}(-3o@?IyxHgql&cG!;LT^{TzivMj(^g_ zTfR1cU(3y-mC&Vit#X-^ zaXr?%`p-$s{IMx+M^=GUgL&+DQVNUG;YME!Xgf*-gzvp5^t^CDkmbMQ6G@p^T1fSW zpKS5Hz0$#vX*8MxLHtn;j&w}vx_O*tt5%VwQ`BKVvO8X_KnE5`?CT5&v<0smAFn@d zYuCqP0M<3QoeB-L4`Kr?=dA3frdT#)ixTeC!cl(TmPS8K1r!_$RUr

7OMXdHWY8 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 Ny9tPp!e-tjU;sc~%xnMv literal 0 HcmV?d00001 diff --git a/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md b/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md index 65e044af2..91d06dd8d 100644 --- a/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md +++ b/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md @@ -1,6 +1,6 @@ --- title: "Rust smoltcp as an alternative TCP/IP stack for ESP-IDF" -date: "2026-06-11" +date: "2026-06-19" summary: "A set of ESP-IDF components that run the Rust smoltcp stack as the IPv4/IPv6 data plane, while keeping esp_http_server, esp-tls and esp-mqtt working without source changes. This article explains the linker --wrap shim that makes it compatible, the single-task poll architecture, the throughput I measured on an ESP32-P4 (91.15 Mbit/s on a 100 Mbit link), and the limitations to be aware of." featureAsset: "img/featured/featured-rust.webp" authors: @@ -193,39 +193,51 @@ enough, enabling the EMAC hardware flow control, and moving the hot path into IRAM so it isn't stalled on flash access. A throughput figure without its conditions isn't worth much, so those are the conditions. -The cost on the build is roughly +80 KiB of code and ~120 KiB of BSS (the -slab pool plus the Rust scratch). On a P4 that is negligible; on a smaller -part it is a real consideration. - -## The `select()` problem, and how the RFC fixed it - -The first release (v0.1.0) had one mandatory and unattractive constraint: -you had to set `CONFIG_VFS_SUPPORT_SELECT=n`. - -The reason is that `select()` in the IDF doesn't only go through the BSD -symbol that `--wrap` can intercept. The VFS layer keeps its own per-FD-range -table of function pointers, and for sockets it points directly at lwIP's -select implementation. A linker `--wrap` rewrites *call sites*; it can't -reach a runtime function-pointer table that lwIP populates during init. So -`select()` would dispatch to lwIP, query lwIP's empty socket table, and the -smoltcp sockets were simply invisible to it. Disabling VFS select avoided -the table entirely — but "set this unrelated-looking Kconfig option or -nothing works" is not a constraint I wanted to ship long-term. - -I posted the design as an -[RFC on the esp-idf tracker](https://github.com/espressif/esp-idf/issues/18549) -and asked whether there was a cleaner option. David Cermak from Espressif -pointed to `esp_vfs_register_fd_range()`. - -The fix that came out of that: after lwIP registers its VFS, the component -claims the BSD-socket FD range `[LWIP_SOCKET_OFFSET, MAX_FDS)` for its own -VFS and supplies its own `esp_vfs_select_ops_t`. The IDF's `select()` then -dispatches through the proper VFS mechanism into the component — the -`__wrap_lwip_*` functions remain the implementation underneath, but FD -ownership and the select hook are now handled the way the IDF intends. On -the v0.2 development branch the `CONFIG_VFS_SUPPORT_SELECT=n` requirement -is gone and the IDF default just works. A considerably better answer than -the one I originally shipped. +### Footprint vs lwIP + +Throughput is only half the story; the other half is what it costs. The +numbers below are from `idf.py size-components` on an ESP32-C3 build with +default config (measured by David Cermak while reviewing this article — +thanks for that): + +| Archive | Flash code | Static RAM (`.data` + `.bss`) | +|---|---|---| +| `libsmoltcp_glue.a` (Rust stack + FFI) | 82.2 KiB | 24.6 KiB | +| `libesp_smoltcp.a` (poll task, frame pool) | 3.4 KiB | 49.9 KiB | +| **smoltcp total** | **~85.6 KiB** | **~74.5 KiB** | +| `liblwip.a` | 47.4 KiB | ~1 KiB | + +So smoltcp costs about 38 KiB more flash, and on paper ~73 KiB more RAM. +The RAM comparison needs one caveat to be fair in both directions: the +smoltcp figure is the *whole* budget, allocated statically up front — the +24.6 KiB `.data` is the TX scratch pool and the 49.9 KiB `.bss` is the RX +slab and socket buffers, and it never grows past that. lwIP's ~1 KiB static +figure excludes everything it allocates from the heap at runtime (pbufs, +PCBs, socket buffers), so its real RAM use under load is well above 1 KiB +and varies with traffic. A proper apples-to-apples comparison needs runtime +heap watermarks under identical load, which I haven't measured yet — until +then, read the table as "smoltcp pre-pays a fixed, bounded RAM budget; +lwIP pays a smaller but variable one as it goes." + +On an ESP32-P4 the totals are negligible either way; on smaller parts the +~75 KiB of static RAM is a real line item and the main reason to think +twice. + +## One version-specific caveat: `select()` and the VFS + +The released v0.1.x requires `CONFIG_VFS_SUPPORT_SELECT=n` in your +sdkconfig. The short reason: IDF's `select()` doesn't go through a symbol +that `--wrap` can intercept — the VFS layer dispatches it through a +per-FD-range function-pointer table that lwIP populates at init, so smoltcp +sockets were invisible to it. Disabling VFS select sidesteps the table. + +v0.2 (currently `0.2.0-rc.1` on the registry) removes the constraint +properly: the component claims the BSD-socket FD range with +`esp_vfs_register_fd_range()` and provides its own `esp_vfs_select_ops_t`, +so `select()` dispatches through the VFS the way IDF intends and the +default `CONFIG_VFS_SUPPORT_SELECT=y` just works. The design history — +including how this fix came out of the review discussion — is in the +[RFC on the esp-idf tracker](https://github.com/espressif/esp-idf/issues/18549). ## Current status and limitations @@ -277,15 +289,50 @@ argue that everyone should switch. ## Getting started -The three components are published on the -[ESP Component Registry](https://components.espressif.com/), so adding them -is a one-liner: +One prerequisite beyond a working ESP-IDF setup: the Rust toolchain, +because the glue component runs `cargo` during the build. If you don't have +it yet, [rustup](https://rustup.rs/) is the standard installer: + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +That's all the Rust setup there is — the component pins its exact toolchain +in a `rust-toolchain.toml`, so on the first build `rustup` fetches the +right nightly and the RISC-V target on its own. + +The components themselves are on the +[ESP Component Registry](https://components.espressif.com/): + +{{< figure + default=true + src="img/esp-smoltcp-registry.webp" + alt="The esp_smoltcp component on the ESP Component Registry" + caption="esp_smoltcp on the ESP Component Registry" + >}} + +Adding them to a project is two lines: ```bash idf.py add-dependency "datanoisetv/esp_smoltcp^0.1.0" idf.py add-dependency "datanoisetv/esp_smoltcp_lwip_compat^0.1.0" ``` +(Use at least `esp_smoltcp_lwip_compat` 0.1.1 — 0.1.0 had two packaging +bugs that broke standalone installs, found during the review of this very +article. The `^0.1.0` range above resolves to 0.1.1 automatically.) + +Then set the required options in your project's `sdkconfig.defaults`: + +``` +CONFIG_LWIP_COMPAT_ENABLE=y # turn the BSD-sockets shim on +CONFIG_LWIP_NETIF_LOOPBACK=y # esp_http_server compile-time check +CONFIG_VFS_SUPPORT_SELECT=n # v0.1.x only — see the select() section +``` + +The last line is the v0.1.x constraint explained above; the v0.2 release +candidate doesn't need it. + - Source, the complete `eth_basic` example, and architecture notes: [github.com/DatanoiseTV/esp-smoltcp](https://github.com/DatanoiseTV/esp-smoltcp) - Registry pages: From 9c64ed2fd34cd9555423d20d3fd91f8f70d6ed1c Mon Sep 17 00:00:00 2001 From: DatanoiseTV <6614616+DatanoiseTV@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:55:57 +0200 Subject: [PATCH 3/4] docs: editorial fixes from review - Split the opening section into "Introduction" (lwIP context) and "Why a second TCP/IP stack" (smoltcp), as suggested - Replace all standalone "IDF" with "ESP-IDF"; use "native networking stack" where suggested - Link the Waveshare ESP32-P4-Nano product page --- .../index.md | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md b/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md index 91d06dd8d..e02e019a2 100644 --- a/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md +++ b/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md @@ -8,10 +8,10 @@ authors: tags: ["Rust", "smoltcp", "Networking", "TCP/IP", "ESP-IDF", "ESP32-P4", "lwIP"] --- -## Why a second TCP/IP stack +## Introduction ESP-IDF ships with lwIP, and for most projects that's the right choice. It's -mature, well documented, and every networking component in IDF is built and +mature, well documented, and every networking component in ESP-IDF is built and tested against it. Nothing below is an argument to replace it by default. Still, there are two reasons you might want an alternative. The first is the @@ -21,16 +21,18 @@ in C. The second is the concurrency model — lwIP runs several tasks and takes a fair number of mutexes, which is harder to reason about when you need confidence that a device will stay up for months without intervention. -[smoltcp](https://github.com/smoltcp-rs/smoltcp) addresses both. It's a -`#![no_std]` TCP/IP stack written in Rust, with no heap requirement and a -single, explicit poll model: you give it the current time and a device to -read and write frames, it does one pass of work, and you call it again. The -packet parsing is safe Rust, and there's no background thread operating on -the stack behind your code. +## Why a second TCP/IP stack + +[smoltcp](https://github.com/smoltcp-rs/smoltcp) is a `#![no_std]` TCP/IP +stack written in Rust, with no heap requirement and a single, explicit poll +model: you give it the current time and a device to read and write frames, +it does one pass of work, and you call it again. The packet parsing is safe +Rust, and there's no background thread operating on the stack behind your +code — which addresses both concerns above. Now, getting smoltcp to run on an ESP32 is not the hard part. The hard part -is running it *under ESP-IDF without giving up the IDF networking stack you -already depend on* — `esp_http_server`, `esp-tls`, `esp_http_client`, +is running it *under ESP-IDF without giving up the native networking stack +you already depend on* — `esp_http_server`, `esp-tls`, `esp_http_client`, `esp-mqtt`, mbedTLS. If switching stacks means forking all of that, it's a toy. So the goal was source compatibility: keep those components, and the application code that uses them, completely unchanged. @@ -41,9 +43,9 @@ are. ## How compatibility works: a linker `--wrap` shim -Every IDF networking component eventually calls BSD sockets — `socket()`, +Every ESP-IDF networking component eventually calls BSD sockets — `socket()`, `bind()`, `listen()`, `accept()`, `send()`, `recv()`, `select()`, -`getaddrinfo()`. In the IDF, these come from ``, and the +`getaddrinfo()`. In ESP-IDF, these come from ``, and the key detail is that they are defined as `static inline` wrappers: ```c @@ -75,10 +77,10 @@ The component adds one of these for each BSD-socket entry point: /* ... and so on for the full BSD-socket surface */ ``` -With that in place, every BSD-socket call site across the IDF — none of +With that in place, every BSD-socket call site across ESP-IDF — none of which is modified — lands in a thin C shim that talks to smoltcp instead. lwIP is still compiled into the image, because its headers define types the -IDF networking source needs, but its socket layer is never reached at +ESP-IDF networking source needs, but its socket layer is never reached at runtime. The application source change required to switch stacks is zero. ## Using it @@ -103,7 +105,7 @@ void app_main(void) esp_smoltcp_attach_eth(eth); esp_smoltcp_wait_for_ip(ESP_SMOLTCP_IFACE_ETH, 15000); /* DHCP by default */ - /* BSD sockets work from here, so unmodified IDF code works too: */ + /* BSD sockets work from here, so unmodified ESP-IDF code works too: */ httpd_handle_t server; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); httpd_start(&server, &config); @@ -111,7 +113,7 @@ void app_main(void) } ``` -`httpd_start()` and everything beneath it are stock IDF. They just happen +`httpd_start()` and everything beneath it are stock ESP-IDF. They just happen to be running on smoltcp now. ## Architecture @@ -119,7 +121,7 @@ to be running on smoltcp now. The implementation is three components stacked on top of each other: ``` - esp_http_server | esp-tls | esp-mqtt | mdns (unchanged IDF source) + esp_http_server | esp-tls | esp-mqtt | mdns (unchanged ESP-IDF source) | BSD sockets: socket/bind/recv/select/getaddrinfo | @@ -167,8 +169,9 @@ so `rustup` selects it automatically. ## Performance -The hardware is a Waveshare ESP32-P4-Nano with the built-in 100 Mbit/s -EMAC, ESP-IDF v6.0, MTU 1500. The example application serves a synthetic +The hardware is a +[Waveshare ESP32-P4-Nano](https://www.waveshare.com/esp32-p4-nano.htm) with +the built-in 100 Mbit/s EMAC, ESP-IDF v6.0, MTU 1500. The example application serves a synthetic download endpoint; the measurement is a 200 MB transfer pulled with `curl` (curl reports it as 190M because it counts in MiB): @@ -226,7 +229,7 @@ twice. ## One version-specific caveat: `select()` and the VFS The released v0.1.x requires `CONFIG_VFS_SUPPORT_SELECT=n` in your -sdkconfig. The short reason: IDF's `select()` doesn't go through a symbol +sdkconfig. The short reason: ESP-IDF's `select()` doesn't go through a symbol that `--wrap` can intercept — the VFS layer dispatches it through a per-FD-range function-pointer table that lwIP populates at init, so smoltcp sockets were invisible to it. Disabling VFS select sidesteps the table. @@ -234,7 +237,7 @@ sockets were invisible to it. Disabling VFS select sidesteps the table. v0.2 (currently `0.2.0-rc.1` on the registry) removes the constraint properly: the component claims the BSD-socket FD range with `esp_vfs_register_fd_range()` and provides its own `esp_vfs_select_ops_t`, -so `select()` dispatches through the VFS the way IDF intends and the +so `select()` dispatches through the VFS the way ESP-IDF intends and the default `CONFIG_VFS_SUPPORT_SELECT=y` just works. The design history — including how this fix came out of the review discussion — is in the [RFC on the esp-idf tracker](https://github.com/espressif/esp-idf/issues/18549). From 879b17b522ff7fcbb13e52c1671e256a2fbcb687 Mon Sep 17 00:00:00 2001 From: DatanoiseTV <6614616+DatanoiseTV@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:12:30 +0200 Subject: [PATCH 4/4] docs: editorial nitpicks from second review pass - Drop the backwards-referencing "addresses both concerns above" - Reword the loopback bullet to remove the ambiguous "it" - "RISC-V parts" -> "RISC-V SoCs" / "smaller chips" - Name CONFIG_VFS_SUPPORT_SELECT explicitly instead of "the last line" --- .../index.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md b/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md index e02e019a2..c3f992480 100644 --- a/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md +++ b/content/blog/2026/06/rust-smoltcp-network-stack-for-esp-idf/index.md @@ -28,7 +28,7 @@ stack written in Rust, with no heap requirement and a single, explicit poll model: you give it the current time and a device to read and write frames, it does one pass of work, and you call it again. The packet parsing is safe Rust, and there's no background thread operating on the stack behind your -code — which addresses both concerns above. +code. Now, getting smoltcp to run on an ESP32 is not the hard part. The hard part is running it *under ESP-IDF without giving up the native networking stack @@ -222,7 +222,7 @@ heap watermarks under identical load, which I haven't measured yet — until then, read the table as "smoltcp pre-pays a fixed, bounded RAM budget; lwIP pays a smaller but variable one as it goes." -On an ESP32-P4 the totals are negligible either way; on smaller parts the +On an ESP32-P4 the totals are negligible either way; on smaller chips the ~75 KiB of static RAM is a real line item and the main reason to think twice. @@ -250,8 +250,9 @@ What is verified and in use: including chunked encoding and WebSockets - `esp_https_server` with mbedTLS, plus `esp-tls`, `esp_http_client` and `esp-mqtt`, all over BSD sockets and all unchanged -- An in-RAM `127.0.0.0/8` loopback (the httpd internal control socket needs - one; it never reaches the wire) +- An in-RAM `127.0.0.0/8` loopback — `esp_http_server`'s internal control + socket needs one, and loopback traffic is forwarded entirely in RAM + instead of going out through the Ethernet driver - IGMPv2 multicast, ICMP echo, and IPv6 link-local with ping6 and NDP/ICMPv6 - A raw L2 frame tap for PTP, LLDP and custom EtherTypes, with the P4 @@ -260,7 +261,7 @@ What is verified and in use: And what I'm not claiming: -- **Only the ESP32-P4 is hardware-verified.** Other RISC-V parts should +- **Only the ESP32-P4 is hardware-verified.** Other RISC-V SoCs should work; the classic Xtensa ESP32 needs a different Rust target and hasn't been done. - **The ESP-Hosted Wi-Fi path is scaffolded but not yet flashed.** The code @@ -333,8 +334,8 @@ CONFIG_LWIP_NETIF_LOOPBACK=y # esp_http_server compile-time check CONFIG_VFS_SUPPORT_SELECT=n # v0.1.x only — see the select() section ``` -The last line is the v0.1.x constraint explained above; the v0.2 release -candidate doesn't need it. +The option `CONFIG_VFS_SUPPORT_SELECT` is the v0.1.x constraint explained +above; the v0.2 release candidate doesn't need it. - Source, the complete `eth_basic` example, and architecture notes: [github.com/DatanoiseTV/esp-smoltcp](https://github.com/DatanoiseTV/esp-smoltcp) @@ -345,6 +346,6 @@ candidate doesn't need it. - Design RFC and discussion: [esp-idf#18549](https://github.com/espressif/esp-idf/issues/18549) -If you run it on hardware I haven't tested — another RISC-V part, or the +If you run it on hardware I haven't tested — another RISC-V SoC, or the ESP-Hosted Wi-Fi path — I'd genuinely like to hear how it went, working or not. At this stage the failure reports are the more valuable ones.