From 31b441a46e1edc6340a200fc11d3e2091fc666b0 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Fri, 17 Mar 2023 01:30:31 +0200 Subject: [PATCH] feat(s3): add S3 client application --- .../docker-compose-minio-tcp.yaml | 21 + .../docker-compose-minio-tls.yaml | 23 + .../docker-compose-toxiproxy.yaml | 16 + .ci/docker-compose-file/toxiproxy.json | 12 + apps/emqx_machine/src/emqx_machine.app.src | 2 +- apps/emqx_machine/src/emqx_machine_boot.erl | 3 +- apps/emqx_s3/BSL.txt | 94 +++ apps/emqx_s3/README.md | 133 +++++ apps/emqx_s3/docker-ct | 2 + apps/emqx_s3/docs/s3_app.png | Bin 0 -> 202227 bytes apps/emqx_s3/rebar.config | 6 + apps/emqx_s3/src/emqx_s3.app.src | 14 + apps/emqx_s3/src/emqx_s3.erl | 65 +++ apps/emqx_s3/src/emqx_s3_app.erl | 16 + apps/emqx_s3/src/emqx_s3_client.erl | 293 ++++++++++ apps/emqx_s3/src/emqx_s3_profile_conf.erl | 390 +++++++++++++ .../src/emqx_s3_profile_http_pool_clients.erl | 35 ++ .../src/emqx_s3_profile_http_pools.erl | 123 ++++ apps/emqx_s3/src/emqx_s3_profile_sup.erl | 48 ++ .../src/emqx_s3_profile_uploader_sup.erl | 73 +++ apps/emqx_s3/src/emqx_s3_schema.erl | 143 +++++ apps/emqx_s3/src/emqx_s3_sup.erl | 47 ++ apps/emqx_s3/src/emqx_s3_uploader.erl | 318 +++++++++++ apps/emqx_s3/test/certs/ca.crt | 29 + apps/emqx_s3/test/emqx_s3_SUITE.erl | 66 +++ apps/emqx_s3/test/emqx_s3_client_SUITE.erl | 104 ++++ .../test/emqx_s3_profile_conf_SUITE.erl | 293 ++++++++++ apps/emqx_s3/test/emqx_s3_schema_SUITE.erl | 154 +++++ apps/emqx_s3/test/emqx_s3_test_helpers.erl | 135 +++++ apps/emqx_s3/test/emqx_s3_uploader_SUITE.erl | 535 ++++++++++++++++++ rebar.config.erl | 3 +- scripts/ct/run.sh | 12 +- 32 files changed, 3204 insertions(+), 4 deletions(-) create mode 100644 .ci/docker-compose-file/docker-compose-minio-tcp.yaml create mode 100644 .ci/docker-compose-file/docker-compose-minio-tls.yaml create mode 100644 apps/emqx_s3/BSL.txt create mode 100644 apps/emqx_s3/README.md create mode 100644 apps/emqx_s3/docker-ct create mode 100644 apps/emqx_s3/docs/s3_app.png create mode 100644 apps/emqx_s3/rebar.config create mode 100644 apps/emqx_s3/src/emqx_s3.app.src create mode 100644 apps/emqx_s3/src/emqx_s3.erl create mode 100644 apps/emqx_s3/src/emqx_s3_app.erl create mode 100644 apps/emqx_s3/src/emqx_s3_client.erl create mode 100644 apps/emqx_s3/src/emqx_s3_profile_conf.erl create mode 100644 apps/emqx_s3/src/emqx_s3_profile_http_pool_clients.erl create mode 100644 apps/emqx_s3/src/emqx_s3_profile_http_pools.erl create mode 100644 apps/emqx_s3/src/emqx_s3_profile_sup.erl create mode 100644 apps/emqx_s3/src/emqx_s3_profile_uploader_sup.erl create mode 100644 apps/emqx_s3/src/emqx_s3_schema.erl create mode 100644 apps/emqx_s3/src/emqx_s3_sup.erl create mode 100644 apps/emqx_s3/src/emqx_s3_uploader.erl create mode 100644 apps/emqx_s3/test/certs/ca.crt create mode 100644 apps/emqx_s3/test/emqx_s3_SUITE.erl create mode 100644 apps/emqx_s3/test/emqx_s3_client_SUITE.erl create mode 100644 apps/emqx_s3/test/emqx_s3_profile_conf_SUITE.erl create mode 100644 apps/emqx_s3/test/emqx_s3_schema_SUITE.erl create mode 100644 apps/emqx_s3/test/emqx_s3_test_helpers.erl create mode 100644 apps/emqx_s3/test/emqx_s3_uploader_SUITE.erl diff --git a/.ci/docker-compose-file/docker-compose-minio-tcp.yaml b/.ci/docker-compose-file/docker-compose-minio-tcp.yaml new file mode 100644 index 000000000..93e1c4ead --- /dev/null +++ b/.ci/docker-compose-file/docker-compose-minio-tcp.yaml @@ -0,0 +1,21 @@ +version: '3.7' + +services: + minio: + hostname: minio + image: quay.io/minio/minio:RELEASE.2023-03-20T20-16-18Z + command: server --address ":9000" --console-address ":9001" /minio-data + expose: + - "9000" + - "9001" + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 5s + retries: 3 + networks: + emqx_bridge: + diff --git a/.ci/docker-compose-file/docker-compose-minio-tls.yaml b/.ci/docker-compose-file/docker-compose-minio-tls.yaml new file mode 100644 index 000000000..2e7a6bea5 --- /dev/null +++ b/.ci/docker-compose-file/docker-compose-minio-tls.yaml @@ -0,0 +1,23 @@ +version: '3.7' + +services: + minio_tls: + hostname: minio-tls + image: quay.io/minio/minio:RELEASE.2023-03-20T20-16-18Z + command: server --certs-dir /etc/certs --address ":9100" --console-address ":9101" /minio-data + volumes: + - ./certs/server.crt:/etc/certs/public.crt + - ./certs/server.key:/etc/certs/private.key + expose: + - "9100" + - "9101" + ports: + - "9100:9100" + - "9101:9101" + healthcheck: + test: ["CMD", "curl", "-k", "-f", "https://localhost:9100/minio/health/live"] + interval: 30s + timeout: 5s + retries: 3 + networks: + emqx_bridge: diff --git a/.ci/docker-compose-file/docker-compose-toxiproxy.yaml b/.ci/docker-compose-file/docker-compose-toxiproxy.yaml index 9a1d08ba6..0cf689921 100644 --- a/.ci/docker-compose-file/docker-compose-toxiproxy.yaml +++ b/.ci/docker-compose-file/docker-compose-toxiproxy.yaml @@ -13,18 +13,34 @@ services: volumes: - "./toxiproxy.json:/config/toxiproxy.json" ports: + # Toxiproxy management API - 8474:8474 + # InfluxDB - 8086:8086 + # InfluxDB TLS - 8087:8087 + # MySQL - 13306:3306 + # MySQL TLS - 13307:3307 + # PostgreSQL - 15432:5432 + # PostgreSQL TLS - 15433:5433 + # TDEngine - 16041:6041 + # DynamoDB - 18000:8000 + # RocketMQ - 19876:9876 + # Cassandra - 19042:9042 + # Cassandra TLS - 19142:9142 + # S3 + - 19000:19000 + # S3 TLS + - 19100:19100 command: - "-host=0.0.0.0" - "-config=/config/toxiproxy.json" diff --git a/.ci/docker-compose-file/toxiproxy.json b/.ci/docker-compose-file/toxiproxy.json index 708cbf1ef..d8dd8a166 100644 --- a/.ci/docker-compose-file/toxiproxy.json +++ b/.ci/docker-compose-file/toxiproxy.json @@ -95,5 +95,17 @@ "listen": "0.0.0.0:9142", "upstream": "cassandra:9142", "enabled": true + }, + { + "name": "minio_tcp", + "listen": "0.0.0.0:19000", + "upstream": "minio:9000", + "enabled": true + }, + { + "name": "minio_tls", + "listen": "0.0.0.0:19100", + "upstream": "minio-tls:9100", + "enabled": true } ] diff --git a/apps/emqx_machine/src/emqx_machine.app.src b/apps/emqx_machine/src/emqx_machine.app.src index 0bee30e35..6bd36aab5 100644 --- a/apps/emqx_machine/src/emqx_machine.app.src +++ b/apps/emqx_machine/src/emqx_machine.app.src @@ -3,7 +3,7 @@ {id, "emqx_machine"}, {description, "The EMQX Machine"}, % strict semver, bump manually! - {vsn, "0.2.1"}, + {vsn, "0.2.2"}, {modules, []}, {registered, []}, {applications, [kernel, stdlib, emqx_ctl]}, diff --git a/apps/emqx_machine/src/emqx_machine_boot.erl b/apps/emqx_machine/src/emqx_machine_boot.erl index 82b3d602f..feeb1ba75 100644 --- a/apps/emqx_machine/src/emqx_machine_boot.erl +++ b/apps/emqx_machine/src/emqx_machine_boot.erl @@ -146,7 +146,8 @@ basic_reboot_apps() -> emqx_authz, emqx_slow_subs, emqx_auto_subscribe, - emqx_plugins + emqx_plugins, + emqx_s3 ], case emqx_release:edition() of ce -> CE; diff --git a/apps/emqx_s3/BSL.txt b/apps/emqx_s3/BSL.txt new file mode 100644 index 000000000..0acc0e696 --- /dev/null +++ b/apps/emqx_s3/BSL.txt @@ -0,0 +1,94 @@ +Business Source License 1.1 + +Licensor: Hangzhou EMQ Technologies Co., Ltd. +Licensed Work: EMQX Enterprise Edition + The Licensed Work is (c) 2023 + Hangzhou EMQ Technologies Co., Ltd. +Additional Use Grant: Students and educators are granted right to copy, + modify, and create derivative work for research + or education. +Change Date: 2027-02-01 +Change License: Apache License, Version 2.0 + +For information about alternative licensing arrangements for the Software, +please contact Licensor: https://www.emqx.com/en/contact + +Notice + +The Business Source License (this document, or the “License”) is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. + +License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. +“Business Source License” is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Business Source License 1.1 + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +MariaDB hereby grants you permission to use this License’s text to license +your works, and to refer to it using the trademark “Business Source License”, +as long as you comply with the Covenants of Licensor below. + +Covenants of Licensor + +In consideration of the right to use this License’s text and the “Business +Source License” name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, + or a license that is compatible with GPL Version 2.0 or a later version, + where “compatible” means that software provided under the Change License can + be included in a program with software provided under GPL Version 2.0 or a + later version. Licensor may specify additional Change Licenses without + limitation. + +2. To either: (a) specify an additional grant of rights to use that does not + impose any additional restriction on the right granted in this License, as + the Additional Use Grant; or (b) insert the text “None”. + +3. To specify a Change Date. + +4. Not to modify this License in any other way. diff --git a/apps/emqx_s3/README.md b/apps/emqx_s3/README.md new file mode 100644 index 000000000..3f049d1a8 --- /dev/null +++ b/apps/emqx_s3/README.md @@ -0,0 +1,133 @@ +# emqx_s3 + +EMQX S3 Application + +## Description + +This application provides functionality for uploading files to S3. + +## Usage + +The steps to integrate this application are: +* Integrate S3 configuration schema where needed. +* On _client_ application start: + * Call `emqx_s3:start_profile(ProfileName, ProfileConfig)` with configuration. + * Add `emqx_config_handler` hook to call `emqx_s3:start_profile(ProfileName, ProfileConfig)` when configuration is updated. +* On _client_ application stop, call `emqx_s3:stop_profile(ProfileName)`. + +`ProfileName` is a unique name used to distinguish different sets of S3 settings. Each profile has its own connection pool and configuration. + +To use S3 from a _client_ application: +* Create an uploader process with `{ok, Pid} = emqx_s3:start_uploader(ProfileName, #{key => MyKey})`. +* Write data with `emqx_s3_uploader:write(Pid, <<"data">>)`. +* Finish the uploader with `emqx_s3_uploader:complete(Pid)` or `emqx_s3_uploader:abort(Pid)`. + +### Configuration + +Example of integrating S3 configuration schema into a _client_ application `emqx_someapp`. + +```erlang +-module(emqx_someapp_schema). + +... + +roots() -> [someapp] +... + +fields(someapp) -> + [ + {other_setting, ...}, + {s3_settings, + mk( + hoconsc:ref(emqx_s3_schema, s3), + #{ + desc => ?DESC("s3_settings"), + required => true + } + )} + ]; +... + +``` + +### Application start and config hooks + +```erlang +-module(emqx_someapp_app). + +-behaviour(application). + +-export([start/2, stop/1]). + +-export([ + pre_config_update/3, + post_config_update/5 +]). + +start(_StartType, _StartArgs) -> + ProfileConfig = emqx_config:get([someapp, s3_settings]), + ProfileName = someapp, + ok = emqx_s3:start_profile(ProfileName, ProfileConfig), + ok = emqx_config_handler:add_handler([someapp], ?MODULE). + +stop(_State) -> + ok = emqx_conf:remove_handler([someapp]), + ProfileName = someapp, + ok = emqx_s3:stop_profile(ProfileName). + +pre_config_update(_Path, NewConfig, _OldConfig) -> + {ok, NewConfig}. + +post_config_update(Path, _Req, NewConfig, _OldConfig, _AppEnvs) -> + NewProfileConfig = maps:get(s3_settings, NewConfig), + ProfileName = someapp, + %% more graceful error handling may be needed + ok = emqx_s3:update_profile(ProfileName, NewProfileConfig). + +``` + +### Uploader usage + +```erlang +-module(emqx_someapp_logic). +... + +-spec do_upload_data(Key :: string(), Data :: binary()) -> ok. +do_upload_data(Key, Data) -> + ProfileName = someapp, + {ok, Pid} = emqx_s3:start_uploader(ProfileName, #{key => Key}), + ok = emqx_s3_uploader:write(Pid, Data), + ok = emqx_s3_uploader:complete(Pid). + +``` + +## Design + +![Design](./docs/s3_app.png) + +* Each profile has its own supervisor `emqx_s3_profile_sup`. +* Under each profile supervisor, there is a + * `emqx_s3_profile_uploader_sup` supervisor for uploader processes. + * `emqx_s3_profile_conf` server for managing profile configuration. + +When an uploader process is started, it checkouts the actual S3 configuration for the profile from the `emqx_s3_profile_conf` server. It uses the obtained configuration and connection pool to upload data to S3 till the termination, even if the configuration is updated. + +`emqx_s3_profile_conf`: +* Keeps actual S3 configuration for the profile and creates a connection pool for the actual configuration. +* Creates a new connection pool when the configuration is updated. +* Keeps track of uploaders using connection pools. +* Drops connection pools when no uploaders are using it or after a timeout. + +The code is designed to allow a painless transition from `ehttpc` pool to any other HTTP pool/client. + +## Possible performance improvements + +One of the downsides of the current implementation is that there is a lot of message passing between the uploader client and the actual sockets. + +A possible improvement could be: +* Use a process-less HTTP client, like [Mint](https://github.com/elixir-mint/mint). +* Use a resource pool, like [NimblePool](https://github.com/dashbitco/nimble_pool) to manage the HTTP connections. It temporarily grants sockets to its clients. +* Do the buffering logic locally in the uploader client. +* Use `emqx_s3_client` directly from the uploader client. + +In this case, the data will be directly sent to the socket, without being sent to any intermediate processes. diff --git a/apps/emqx_s3/docker-ct b/apps/emqx_s3/docker-ct new file mode 100644 index 000000000..a5a001815 --- /dev/null +++ b/apps/emqx_s3/docker-ct @@ -0,0 +1,2 @@ +minio +toxiproxy diff --git a/apps/emqx_s3/docs/s3_app.png b/apps/emqx_s3/docs/s3_app.png new file mode 100644 index 0000000000000000000000000000000000000000..372f16d371d0b75b7d796736906eb1de004d1deb GIT binary patch literal 202227 zcmeFZXH-+&w>OFi(nOkopi-2k^xk_7q4zE|5K0Iw^sa&;#i*ck1px(VQluBfhJXg7 zN>xOp_YQXjlIQ=NbKX0~JMM@3=?sS<+1Y!oHGi|uHCOH!=xI`tGm{e#5Kuz2)QkuS zNUsnO5U-M+0asdMhN!@wQ+`I8Dg>q7muA7YM*iv+{$4>axTgyNr>N@5J5CWHH*Y_G zPEj>Z5fKL@QotGR;Ogt(!4a2VXh`Q#oEArVo2AyIx2NfTjdPElnc zA@CwBE+8!?WOZ`CgR2VyH=wwH5Ey_**TD&n@b~s};}lf^pCJf;7f_2eP*a7C?3;)*;SbALi9b8Xn(K9p%4${%o3h|J1zv1O8 z>SAhOazX>j#n%t+jlhwN9T3cqeb_%3>2h+_)!WM%&8{KDJ63JDulhFa`LKn{9fw#x&-bz0O4dBI1X(qKP@p)BTao1^B_%6RaHGv zEq}|CDJ9KN2A<-6J{kt%zJ3A0PRa(7P(MEr4@noOv{aBoh>O06k6W;trg^ZAxtOX6 z(gKXEqN}N?>#8dx;j8Yh8v+kOxGB2=MyO~3gkbJYe(Ks@KGLFy5U7(83S16$)l=3+ zC_6~1I{Ls2)!-pIFimi$u6~e?YoLf1(p=Ll$W#NV>}%l$fr)D%bu@fD{hU-pObi{I z{4BlQrS-jp%$>Cj5g{&q`s#j`(*9n4;%Y|XK45Gw7j;w9Krwv-A9G)6gFtaHGYxNP z2|Y)VkYFPhBWF{TvWT;}uuo8su$e1%DaMYf%FY2UUV18U*8oc^OUV!^e?3V(Rb!Dr zA5mo_xJ6V%%2dnG$z0viSk%Ws&A?IG00s$=3XqmU`6IPN)x}g`a4E2Sl$M%@2+Bvw z#Y@=)h=z)}xsjDg5F9FQ}{LcHCL{LQr`#l@8o9^yJ7Uiun-M&M2l zL+ofmaA7AEEmdI;LtPV;iH@qSsT9mBSYKHRr5)hzBLd*m@G#X1Hult!P_xh#LZKub z4Sc=4j4aep5F;VUARtUYEkdM4MAT4TqQPdGfQu^Pz81<3ZYT|eTCfNB2vJit^KcXO zkkSoAB24t5x_FcUmx9?K-m|f;_4u3CTK+13pR9w8V5_5 zn3<^uYnbT)-BCe$d1;uc2m2U02L||>IEE+-`DytC8k+?pG{s&0oz+4DlywYDE!3rT zJWf(p6^Mjb z1_2UGq$RcCT81zcLnA$54K*uaKUaT!M;(|p+}zw3p{nZb{ryD%(pGM+BF^3hfC1*tC_`rmOvS@fKR8%f z+eO_$OTpedqa`N{^nvq!Y)oQuzr}Xwh2VbU)cxgZ>Z_x z;%F>|3{rN}azW}_7@4cMqf|ZJ4TQzi1J$6KC^chKHDJtD5SCgFLTXl0zU~s9YUWU2 ztu>59)DXJC`tC@L5LY9Fr;nxu+)_GFTTMs{A`M4~>$+HJS*od_y!E^df|Yf2L_9+f znue|-mfjMou7S>uAzogt!VWI3l71l;8m5|hU_^6CEeCyNVLu5q4?{04LuaHJ(%-_< zM;sL*Ze#>E)fX{$u+l`Rn+1e8s<|1NhUmFyYeJ-?4Pg$ZzD6iVD-)QHyEaVQR2eB2 zV&vhV07y*O9~r{1h@tp>OmagBEG5`rqWo> zqO?W)AVQAjo@!QFMt;)f7Lt0RlB%9kJ`PZ6X{dyzi-(1S4^T%HlR)f(U0p=g9Ka`) z5N8R0QJ8M9G0IdA;pyq33A4aLqyzQPH}KX#X#;ef%*BM&v;rJ0#WZw~N=3}V$y3A4Kwr`{zzPNNbagjSg@XIke4M>uCMbjuGDOT=!pSfQ=IbfuVc;BW zgmQ2&PjWU>-e`kX!wtThKju(E7 zI+vQA7?MkNLIzt(MD!_DjY}hsz2&Z@bq198caN98O0|8Kmuit8E5qMsM^WQl9Q->Q zDy?AqT=7YjkT~H^Ir>wXm79%m*gcUHaqK)aXb z1g~74Q@gC{!WDFcru4Z-hNgOYw~vQcdO&cCw2=vqYBU4UxfbNM9p%msN~30n$#V-; zZRwpxQLPQc(L}_aO3OPHbQ=qOvu2532OoDXg${iSN9dK4*^r;`gos*82QbaC&xSM_ zM*Jso`8{s-dcyNpOa>mj#tBQ*RYJ{kOc6!-k(N@U^?U^ia{(?x#G1a)3q!Q!v&L~P z>)G#hx8>Zo9aKp+>*TBrzgppWXx|M$3yNb)IR^-u{doG-@eD}SC>0{fRjukH{LkMCKW`F&h>EO@qpNJd-5V?*|PvLlxYizyGm=t8U6DDY# z^wc}K^pj*1@0F<5OT@Pm&!xdXPHRxg*KeIc{-bP`47o@5r{}++Sc(2bZl}07s$tcl z=n>#%HF~-cKdkhzBxjf(vQh-P6*kq(mR`zvD3YeiJl`DkYA9swZP}lZ7rl>+7vDh7 zY_iNp^r^+;q)veR>k(48_}#B`peh3*+H%K3Ldb<0v-2eeR8EvIx{v%Uvs}$VGUa$! zdgru^XYHq#RuM1sUU>0Rs0u?FSM2eEXq|r==tb3fo=%u^&45*RD6N+y? z%;tSEeoWuiwN6Wa!oh9|ux5o0qCbzpnm^ELOC7KI4yi$W3aKcgGH%UP6qlr(x1}J( z!WUJBMb-DHA77&$eZ&FZ{~B#VwbFbdu>{ecKq)_(HM{X+<&Emtz_A(B1AL!n_z>YF zMvCj$;z>&UvdeCm~eFRfN@!X8C%U3?nx1Y1HZY23)Y=MR9?TJkxqrqwOC2F7tO0;BcP$0y11zEYr`fweX z%cq2L=hShASd!@3hUL~^$Yp*^MbkxP8e&2g(lBwCbVRuGJ$0FgKdlO*a->hFafYb% z6d3Cq$9?L?^I&k7VKQdyWVwWZ`0K4d4RC^iRGo;YxP2fA%&(Mlnw`6Bj}3xI{XA(9 zo6ygh8vz}*c@KML3hhF4#&7+98T%vq)rbeHl&3cbEucoSV9UFZ5vbi&9eQNZL(2xoK<58(RSd^(Y1jTypfjP8eMyoB8)I2~Q; zjFt*JZ_oDiLkXSe$*4nEtFf^EEu;k)=k2V>XPQ_Z^5{mX+{82vUc|E<`%qgxEn1TB zEK$eqN=bIucR?sLIUB|PWUqoS3}MIe_H$c3o?IQ8E- zz6+=m7DUvcThlloV82`O+p&~BlG4ItK8HnM8W z8xRmNlk?}8f7*0FjKu^uHE?1~0v_@gdU5ktBEW7eKV9iWV38V8hjF4WIH;+>C_#_? zRgUlHu?CP;&Pq|^AhV$XkUi3&jY|fQ?LH20JQ17j3TEa!+ru8KY(n={}SF% zmy!a=w7KrQ)dY}*e~}SB88yWV5IfY(`fAG>5HP;O9fD<0G=_*rji)Q239pUH;GxSp z*V%)w0_-GTaN%S$lLoL@=Q`OtRWJ$*S5^v687|`nzKcFQd8Op>tf1QW~+?Wth49KYV znxi*fMh(D2BoDN;#<1Awz9c(;!bVr%OQjxY6RsEl$l@T}&c~2d5(CIGl1}3xqX&@L ze&X=i#%gG?B{%G3)c;qt1|H*o)%t(c`hV5>|0lJ!dY%%oeJES)q1eLA%>2?{L(kX? zsBAI+=N(*VmD>*tsy1EoPJQS4_p}>DGiNeh4!jSC%06zQDXIj<>w2uO%&}G%0^LW- zG!ez&{8I-o7#8=?ihK8hRvqM#J+DIGeWJTztNCopF6I`7vWt~zn~RY*8RpAV6fZEv zKa6fY%W;PbXS7=Ru+xUBdcOX|oW8u6A+!3}*uSr$kRf0>Sy6AG$n`eyUg?Nmo~6QD z{lj^>CUJzPf#;9V*IsvJSY_F%L9jT{bnFhJlvaQ(J@4I-jkl#mz?LR))!Zv*sb4}} z4*z4v&r&xy1)Ytabn0_>daF}PHm*-4D!SFG$X5?%1dE;nLNWfG*VML`4+5*EZx<6Y z)~`*@RR5NdPZtOocr`FvrpbDK|8Q}pCAI&;=B6Dmid){N2#!J2#s+~ze5ikdK&sh!8iD{$WJe4Gj_N-r==+T!4LT7 z+8doyHovwKBtwTryc@L(FNInx567D;e5Hj7^j3{~w;U84?0G$9%!#y;;BEhC&TaN; zHZd`Acek@gd?{>!)^c+)qpYP*<+AJx-3$rSgs0iuj%?-GyEgbB>N5@U#gU=j{)*Gx z*%mSLc>C~&FN_P_CiN1;MsB)y#~b{eah(gaID+3-XXLHBVRn!EAkOijdo9&L^6-%F ziBH)A&KfCLsCE-4eVpXZ;cQ>~6)%SzK4F&LH*8w_eNlORZhcVc;?|hT0o6q*YoE3o z=f+f*^nvTlQ>}4uYM$@do@m; zIn!^pyVGOja$xU`Bt>}(eeve-gYS&HADBMOJ(*1IJo7iz)?a2J?olo+aU{BLjPn>ET2BNlYkobYO(AN7@;)(e3x*zTQX-;ep$=w_1jWT z*m`8c2;0OoV^*OoqM0kcr)G0aFn3X2q7UL~mpg4{@d7^c^UOT!m#}i59De_cHys(eH{RMy8;-og zc%|ffc=%}eTmWE5#l%`uc*I!I)^C~V`GoYlA<2JQ62@4LLV9QH<~~$;jN27v%bFDC z1m!m$t|sL6IF4u+m$ICv_;K)uf<8Q=raiv%FgR(kamOlLA=t?HqrBvxiDnKG_EoXi zKkvxkg?&I$;gR8G&F&biKupAe@HgVu6CPY(Vt?lioa#S)S5&3FS zK47S2>Dv;+PI&L!H>GUKdi7frw@6t1-&NHA#k6&0_ueJZlRMZi7*Ke;T(h}U`b1qaxr9$S?!}1>;`C?aZ$?(eZqa9o)aNrXzf+|8ucR}XDI0o zIekgW&qxiYiz&_ z|A700X6xC%)+}f3TBgt~b1|>=$s=kkFHN0(wnFzKsK>bS6s81mmIx;9SA|o>NaD&L zM@3>DkJfMeUVqZRD?c#4p=NONATFC%f}Z`|NN7jm(Kq(J`LCX&OA&`2LpeFG#vNwD zcRF0Hf7ShbI^*oc7YNWVwb@9{J2DJ6?mmvZCrSbOi@#Gp;(dx%bjdlQ;fI+AHA8Gz z0z^rUpy3kZX1xXBWe&;m7rv<3TTiA97_!GS!EOKD6nuS>x z+cs=6%fhOX6kkRPNx?fVP@N48NE+Cl+*?}DX+U6RxmoDv16!YJ&b{kR7sT+?RFEtk zXeI`&PPRt=5f6MhVm0a#XgKuc{0A0kzuWPzyIX7;77u17X0$12Vo~i@Oycq4x$NAn8I3=W-)@nS zmp7EWdC9`E6Ebvjo%M4;@ox0G;!C~HWXH;M36$sBCV$Z~<1)+h+$rCqTes78P>hYE zl&s^3`c0#(m4PFQ!}X7%-fp)_i-{CJP|z%fZ3n-q(@QRRTSCpw{PFelDNlHu+iTg? z1qz>$>Kv4VJ-Yql4IE7QCTqazJ_8Dq`d8 z%2=CohS#UJD+)7tGafxf^D~!NhrbGlePQ-<90Fm_NFh-y(RYD;{04sd$~ZNaJX_ z|40s)7fDa|yWs^jJ^eM8!+!ZN`>#s3^{_S{CDr^uR!_{dEA-gb+7-62gc~fUcip7+ zT2Z;_sHiwn;*?R+Vx}IL<(tfCxa74r+m4Orw$+2_e;ZWZ?w#@OQIY?VrN1z3Pxs8Nb|4n1s}?BD$EI zJe8XPp@@9zLs3VDB51dw>SnoD-gIBKDIASEC4>iM&nq7?QB+lIBj^X%sCcR16oRZG8~A1@xERsQiibdtW7 zs8dz#bZQH51rdkAw|yxo+xjYNOs(RVCzc1Qrml~|;IT|4jql{z#>vx0ig@1Kq=K;t zIUoh_!6-e2bM=uSeB#mO4J)exKC8P9Y4Yd3N;BHgi@nAi!>kyzZVyNUG1O1itk=K#S&qKl*7FQ_(7U80ec`HD+}{@TRR^1e8b#YZadTg*s`uv z?)U=LtAKMxgh zy_)c8cXh2NCWfjZwo^j3$InUeg_{{s1Mla>q`MNlTII4uo)eWIzOP0Ws>9%B&u@`B zDejlPC}NM8%Br1L{8ooohW$I~{Ob5bys({ivxPa>sDHdVK7nJAogS4Qh%8mS? zi(h3e#=2mFdmlKD&e^@JqJBEq)3d@vl^wp|#9u#1`$$AQRPG=yy*xF*XqVNc!iO4b zL!t^nz#fzy)Q+n*MIVtl`>-w7-iZ8u9ol8>&s;kWr?N#3CAJ+JvbRra=H$0rQuOoc zb4wtnneW^*=hk^LUHzM!#*^77Bgwz#fQmAe&kbSiQ}N_A2M8`xdSS9%b#dQ62C%Shd=0D_h-ms^(&8-*}R&+@R$3cRYVvVW`R> z($Y8Ea<2AMhc2K%aJ61 zvq#FP?mnW}(|?rSkz9CWpxgDiTm2|cL^0_Yn`La2-?FM(h12n`_mmh&2p1^!A2H;} z#>1YQnh!+BT^9ZDL*Vs1xs_{^U7G?x?EZ($XL(6kb$0NoM^^FFqu;B#Sodag%*SM` zFE(fF-gwo3S&C_qG>=Du5aMrsG)a%|j|2V0Z=Z`_8CGw-q#SPAd0))DF<)-KdE522 z;+Q|;qEJ}SuQ~VLuNrT2f_SzYx)#d~KIpx+JxaR~(eDoJvAOQBbg22qd&b7Z=c4PE zZAe+;cBY2BtWD3kNS&YKLDjRFW;{MUKl2`SPFwM-ZTJ43BmN)(@_^U;gCZBZyduZ^ zWD_rKx(PwwQ3lEz`p9iaojM)8VK9@9H*R;781`5nz4Q3c|MJLjrWD1laxEuEUwTt$ zG~(x%X}0&_a=L3@dTWecO0(!6JuPTd&&oX1G}M_t$rhAA{h!dA2$$?F+p!g&j{cU` zgZj|j>!fhDxm@7KSa@h-#KANpfAcS+zi*bm#6-C>Tp6q+uW4+rRe{a&iXEsvP(X(w3jSy#bTIL?mzQm9fh#t^bTM@D81LQZTi|gW4+ze}6}l7`Ut{%fI!Qmz<)&6Fn&@AOX4P zH9_u^TvP{0=|4M6VAHl9>xsO$PGuOj){w-t6wsT`NB^UzjTP9xxkO zxU_H6r9yPk|8<+>2utK4KYv5mfaNf+@f+*g*g#@%$)+%`>4$nUKgmsyV{({?5Bv%Dyl>OALHG< zEPE`^svG>zM(9+^KF0R~jBoXvj&Y$OuN4+d#4L_@%dKTH+B}U2y=-B&r;*J3qUoql zQfjke=DkN9y;=IS+{zp_F=?wLQx%jlW~9olyZ7GuL4;989ZVq5VdVAIY=fiG+H(U~ zP$471^Og~~r-`_wmOOq*8-CI^Tds} zNt}Xa%B%dzSw#KTHU1xPnjv3OeYvolGyA~<1vlS;F9Xl1L)8YqP=aVa7akn*dy(fl zXM?PFb@N!z!nFPDT@OUCR)Ro{TJL0k&DGr{+Wm-wtmIF$!0!I}ZPV8aGP zgTwL~h|~+_5Rp}UD!WuJ72oIC0DtnP-yQSk968_**v28l^M~%^s*?S_Uhu{3O69Vg zh)kBVA?hR5wersrL%heNr#U#g~6=|8GK1#%3*JPu=qMg~|`7T&l0hd^{uNmD?*} zo$8pi5%LKJzpydC%vD>Y^mel%Cc3Nt#*10;gby`E1i)NmlG8l%=xS+=QI6KGI@nA4 zP}Rm2S;Uha!tzDQUVfua0_OuCUQL-k)RbO%pZo%K)X_a^17e);2i0j_yO+Fka=e=Z z-W+e#jgk3=^5RESGoW?BWgPXJ#s#G)RcQe}jc*)_9Ga>1e4Ls$L46 z;>1=ahQj`@(w2?r*1e@>^+J}&cb+%2N6r#|$o4JW^=w|?qux|+p}b3ZVZC;OX8bUG zT3u$w`vXy|OPStdkxq{syL2ok%VScyX2(e}%fJ7YmGifb zbi0tFrdqK-%i$R?mv~mETQm~JDvd>XKTrqTKmBdjlY7%)3OsT>G^h3ZX+R)U_dB~` z_IcnqbK$|R7tMK2wG5`Cai2o?dws>vscq#?ggu`Sk35Su@d!M6o7BJKdJvGh6;JtQ znD+e0;v4Hahx{q2Gla(X&oD>ht7xRy0**>i*P>O|VS;UIYFOyXPGNN~VsExFp%h;K zJ8|IbcTlc)KD#B$5~Al3u*9^_l0(@Q5xKQ?gLik|^C12d$)W_btt=~WH2dOgnd>>j zTJioD@rj43>S3nxj}J9XrKOpU!@i#&?32p2Z&$_j9`qC2Tp+EAez7&i!g@9gTQX_$ zBpmIZFSQYE?D0{-74iJQNP-S&Cd$$!ps>4tbcy^d$2Xu zkLlzqG_8O2VrRIzOyfd;Jjvhj7KEbXQZJ0@=Sk29SpW6pdL9^#@^>?K@P=EjYsCdDr`fy+8=xNOk^zZz- zA{zZ$=9n4*&?qvY!f@vl&T3u9mZq;Xn^BeKQ@1T{5BXAizvQp2!?hnaagS)2tS!qUB&XaHeX@v{ECIpiUqlFlH1 zhOie+HjJ0&4p1cLF}pr=5Vo`b1O0Mog>er73RNZN_fCo|Y=-fz4Y*BiqDDqQQ;m0Y z5%nYx_HTL(3+5vUKAT+8#R8X0+FY}Xp34fB0Rb4WWI98brOO|EGR8y+Shs&ce_Bru z|Lh)1WW|#9E^CK&1?$N=&tF+)!S&6YB6>^{y+uIC#9+@R^!8KU?1`WRT~}g{DZIn& z$&KA(y{{2-e7lxb6cY{Yf2s>9c3`8XH{+t!Z6yytc@5XQIh>rONPvxKcqi$Ca&8cK z*I`}3{XeH(!tVPY@)LlTxC^9TkgIMoC--Dyy9UHoZZ6yeTgu!RE$ZVz$v_a1LM@ht z?+aQ7y$6rSetZ=nvZ4bZiL=`shk9Vsyb5f+XjyBxQ4w~4E(V^lyYeS;nM;kO6i1*S z*bT;RkBIQKD%szDpo#0eTQ&jPCjTeGRc_wQt^23-%p7datpC29%KkMSY~{QnTfr@z zG+!6&Xb+31gi`Z>4H(T8vw2){3flHaaB)>@D1fagl7b+`0lX2-luJ)KUKYqYLF0g~ zoP#WAK6`sdjP|&1Nl675B@Uu47kpIK1fVXEl&m@%-4A+AJayv-@M`A;p2wup499{E zr**}bH3c!DpfmKtNo11I~jo{tXt3kSxDamQ@tzc16TL89@ zBL)Jtx8My-Dp>6D$vyhmMiG$eqf`Lr>EIls*g>Ojg1sCq()7-gMzScS5FnyD-Uv4) z!dGNM)Pw2NWw>q-rD32U`TSR_NTR2}NdMX8Gl*{u{kIx_-K!-a_@8Qa^tSgMT@4-J zSEklg<>To%I6%vY%2%5+INUJ20O}Aw)lO1&R)uo8SsQc@7K^_>$%`zC_cXvBl8 z)LtW;(OR|y_NHfudy@~_YUjJ@b{s?~$rD|0FN>2Iz;`p6kX`a=D?j?}TX!yQ9s~87 zdBSVzl_yNf_O-J0UQXn9`oKgZg`Av_S>bTT_J8`iKgIw%$me`V;)+lMu&rbCC&u7^ z_V3|(YZoduWUTU?LP@T2a1i}CzwQ26Y;^bI1quY{vJJ1VW8yT#?Epo2083@l$+NP3 z1^B2m9lB$`u2c=oWl<`}4Bk0^A^&gftLZ~hZc>EJWQ_-tX(3^KvY#P=wj$;FisMt< zb~r#j7JxN-{!TOleRQ#SNBuv0?@r&oCnZs~uUH>osS_URA0VWnqSC4x?T|O#`-Uus zxA2D#wsU{5i{}m=4IAK^CapB?d|(8wt=TzU>eyetnnRij7ZEs{O%$!g1$cXY zY?6y?sQKICl>Ftr@>`l^-HG8njk&m4(Y~*bZ){B|hu>J?kC?)BQbc=#VC5rh%uO8N zf_FBcIrP7FkzJ>L)muNxRqVd=Tt=Fi(}8^=sg>kG^S9Wm<5f}bsV)x|X2{;BA;Ya? z8`~iq$48e3#bW)LtK!5fVApGevB4KnjQFW_lZS&>l7^#~#Ei3end73!_FcE7!o`Bg zJ4KdJ_{L}*>;j#K-Ksu-(d6_Lp8g-(5(UWqOJ}$cZw&MaCj%q#vxfVt;6TItg)mFB#1>^YsWp7Z^aV!Y=(L-ecx&9L}YFVw5o zR$Aej3!8`7H_7NQyZW6;^L21I;mhp_>(x7qC(iBzHr`A%X!kJ!V?Ka$9w!*U8(*-~ z|CcwGAWCXPzFYe|6B;mA`FMBpPkN$8uQrh!?FFzEk(jYu_pmdOImCa7QjR>!02dv# zDg*9g8yGWbfjF4RkR7{MHV3S~J2T^p^Oh-y=)7*${-%nj4pk4m|5R(Zz8dg);xGBQ zpMBzfEA*#Af6;&<`k{xXo9DdGN^Wym(0PTY_%6p-fbb>Iohgm$1SQ2XR4EIq8+3AM zc!^mjzZ5aK+{CASS4glt#hAtdz>w|?{V)~+U3z1SPM}+zQ4u>8hmYf}6b7(r%)gR~7qr0oEUR)JbTYeiimh9g75j$yYMUmx-D4%J;hI}cYoERV zyB#(Nv{_*5YREHu7xWDP_cj_nr30))a+PcR=CPVPL!u_v`CJle@R`^%LeV9i&!hI8 zVthZIW=UB1S@O4-6snE1c2F-{PQ*gS|KJXqwE3P*tlMw?a&ux$HfHbyc_g-@b!3OL zejOM(t;i%d_G4IT04ygB`zO$I@KKix4Hrz(POml1W3m{RioQ1p=qanT2T%A$kPQ+{ zTwK!a*L+a;<*Z!V#qZ13Kcf;zHo`ju$)4exn#Qrjr20(h#sXr*oTDXg^QxW(eiOC}%Oaml2QDewXv>=~$&SO`EwX zKrvWVsCcwm+?*kyI=&y6uCV9+k}E<&T~Q~8=4KXXYnS-N+}-SKztkomOMMZi!fXbh z-#d1Ti10f$7{)gtM73rDq9+dUsUYe3YeH_zgPWqmI?{4w`w?g}-c=!#Droy9en0cD zp?SgkvY_nIpm^b<2q{gRJ`oTOfg$f7T%uKm0eYReOvR4dVcDP{O>n+uT}6+J8jY^n z8yEckKOFAT(!B~NC49m7QT+E>;`oej9YlJT|C*%Cz@XGU&I?94Qq1^XWil+oE@XB} zUk3*1+dJCh%_1dj&}63tcXY#V9kCf)jZWl{zY34ZFBPxXNY@*&#Hc~WwRUaOnKi6& zSqnB2URG*Nt=TK%unzUGqnfIwR(wzuB$Y6h!SZhZSw0K8=Kgv-$qMZi9E9hu0eg4f zVE*Lb6UaZLu9O$%XS_Xi;tvvtyqM&7fsD z;{H7|jWZyvPZJYwWy_&HbNTu#DMS-e&!N8X>>5tv!2vFEn!o=#z(vZzhm6Vh!%Z|| zfzg8Gad=IHl)uC8qwLniWk9{Z$K^ z7^?6u=fCxyKao5PwiCR5O?3_rNwhnK4g0Kp@}k+09p(Vd^3Yikf8y#m-BR);EQW^I zVi?g3(8bDtCZoQYO2I#Z#U4;}-J7netrX4Ii=26%O;5%{DuJk<1g%Aiqbo{@j8I=| z_)Fe~>{hCOUVbE0CYOl&pANudTm6|hL_0(o!vTSsN7U zL%MDu%r29AAgAuAF{TT> z_^`@@ll$}6iZpE*8_dw8CpDA1j4s|1Taj&x5ZLl6b|>J{TT*Ap1P8?RR;*)LbCePAZOLDFD|>%Fi9_OFQ&DT_wHD0P!uQHj{M~%hUhO@~ zM)#RxayUlKxB5AHqj&}+F1}0sWP|?4tEXd)fFH{3KL$@ex|s7a&nzJ%_GRF3U(T#J z#6#Q40JHVNo1YTzZorhS{L#G}S=VQm(0WMTDrpVBd--dewgR(4`crmlm>|ezY1|O=dVk<4)-SoJK zfgn1cI4MQU1S15A)Y_d2XdLTCFFI0}Br#23{v!^}6mq<9RY-$&eXr;}s`e{HId|J+ zA(hXW`pGEtO9*aJ;+PH!EYxiv*T?TR1YIq^o6pV3&km1-4206{e5rO@KwPrE+n$_#lJlN0Y{cGV9fvlZ{r04xE9$yY#YTG-owgPVd*!{Nb;bn0L#L z@*GVM`)(QgUY<*^%IUjn_!|nza65w|j01Rcm6*YQonSSvheDEX59~eQ$Y5$K^wNh& zVi_HxK#V_dcg)>vHWFP2@fpd;4y4&lVoooCS%^M6aNq_P_M*|H z8-3B`CpxsO0i4o*j!3ahL@QMt{t2ASlAsEq_VZ(Bp+4t6w0hn;$X#jhUy{*hJwX9U zxD9>ZWVZCaZ)bM~B4dYqO?{T^lfeCYw+F>@hdI^^0t`q z_QTr5jgc(9LB~UQDPVZ+e83=h&_E9ZtE7sxT?p_NVOc zrzd?Wm!k0n?jfw!X9VYc$4TQvp;B@=frB+f@wQ^l5_gBEVJ)Srs)Zru6T167@xSaQ z(cde`%>0(P&JPZM{^MAky23oez2aOZ`>Z;#8H}c%i|K;>~Q(hV(k!Kio}A%f9Z-8 z&+_AZ{`Q8EYnce?wjX1rYCH%+FV=X<0pio*qE6311 zmn_ck%rH8U0QC$qhqUqzsP-seR$Pp#lzRzG``Pg4 z1w5bFn4(*5sgruO4^eqC5M@wCdznQmLhG;F#G#BKy{GV{2JG$8^`!IcvkZ_jh*g6C z|Hmf#N}n!93Bo8tZ|M~) zZGvfOhh{Qu9=V5h%x5=2OW4|AqvZ>nkYue86HL+kZH>+ol>hq?m=v$*c0!_t;-A_O zDwu3Zdd(p_WJIf81x-2juixbWx)M%QbGC&vn;mWl8UJ3$^rY$Ud$h*wSG9uQsUEBw z!t!2(zhy*MCFeJBY}~U&=xi&5@%^^k>qipfV0{dZ2osIre_bI;>42C=iARux8(P_L zi%WZ04Z}U1bvq9)Z&)eLB`18#v2ou&EC0|`Dkr(nLK&lr?oV3>#)ISFgJwLflbt!n z3|(>omN!@-p&HkPwpS<9e-xyL!tc6~gNU$*I^`|i#zm78fk|1t6Oclmy6t2A%+n&T zC;clBB#0&_o=>pPU>UCo;Apa%+zC?%8$-UY4OhL{%Xwgoi451c#=cqI9z(>d^XIbc z;;V8hCVH-iNNs!y6YUFTHsTTzx2iIjU_+}Q5tv#k^D$B}ixvGFp))j_nsX9q`i=g78b;tP?|CK^#-1ZqYj~R%j z9G?&VlV}#{v=;RLFa+_G**X1i&O0d4zCNe>bb+@GP4U{1)LI`vq!h) zo)4R3IEnQ7GUIl7NK1VRXEHrP77Dgw3I=T)g%sG1)iY3oqQ9 zZ!OT;(GBSCn&Y~pB238Hwz_OBp`^d0e`z^oKx{FZ2o?(ISt;X=D3 z5wF%Cp4r;+3vG9+xxdf9jL00^T?lXg@E%>#!ZgnO_8AfH&14qu9c9&rC&)e&1dG@~ zUBdVOfZS9G2i5sUH>s=-lVABp@4RKAdp32mIzxIo8Etomp35S-{o7pmbWg-c9r^R1rEgTFW6wCPh?Jm{)3JFBMy>rC&+BXcv=DuHLm+z`O$j15Zd4gzF zmWfjtXZ*2}nQ}Rro}-EDQ*(K>V4IhORE;xnO^SbReila;z1^-ZWb2l6#dG4pmsjnV z*2LSN3ws8i4INyPfU1Y>aq{WJGx9WAWpN=HPISK#8wG9pY9!z&&5a<0-o8bGGQUaB z=AR_q0h22~CB>@{R2!k|zxcU^t57!)p7Bc3W3IdMUrPcegJ{0feDB%!#V_z4`BmRflsg8hcH8YC3f)V zgY@cqbSIUA6zmU17^M)U;}eA!4CTtzZof~5W^;SKdKE3hw|ODwFHhybn~rwb;E>z8 ziiVirA&OWAT;Tx=)5OClde@QlBMH+cezJ0b8@`3tTO^<{T`Bg->g}mRw1j42M-Mr8 zb6L|~GBgZ4zU3&&W#Q2N7hPii)JgQ|@Y%a7?Kj(1Zp|*PP}5)~5&S;K`77HGy&Uo7 z07s(v#q6NTXv#}!BeE6kryCl6WjN}6RN$l-%rdRL8JmGx9z!DpcK9ygsW=wH@8<`_ zGEX5KAe+;h8k1RL-y6@&yiAIsXICR8Gs~nNFS+4COeUyZ8L=?(y^g~buHNeFNKD2n zu_kwe;)9+5kI7?>Pb@jNdyEZEOetFzSXaKZY!yCpL{qy7zR;Aq^<38p-bt{pfSROn zSc#_mDwm-aE7$UV5v`Y6Wo7ZlAvWua#Nd>}l_h;$Vl7%X@uOMjsf^(b zDTrCTeFlBh`ildXI>LXOfG^7Sry#Y^%J$PE^NYa^Z?mfxUhmXA;hlH&qUF^oZ|RVn zqqBBspF@w4<}7%7ZqWR>0w;z@q|!uMti&9!@V9+4nv0-Fpr2AFzj2dTU7|BjzB%#W z5E|b8>*XCLKf*)QCN$j~;Q#Vz`pQ%)nt0 zDScQjyE^ojS4h7^iT%2sTdDoi@`?QCW|8Iw`Cn}+RMu<%3?(_75P^VN0C^a#7o=ZsHrpc8kvO78%Mse4l0%?j7k= z+!DpF3@0W}hT$0YH`YDE%vZaFH5{Fp;25G1=i%-TQ}_0suJa;;Ml_q{15-Wx2bUJizckk*C(;b9 z(of4VhUzqHD`a9fc|Kx`iBF(RlqaV-T4hgNWaH(NvUtu!Z2fb&dhl(k^>(Z5D}NHt z)20pdd~fYz)6<~!lB2qX0sj($e7jci*MVr~@SvP2KmR}MePvXYTNkdBl*9%B6{Vz+kd*FDK^g(2Tcn#U zASK<6bcd95iy|%Eps?wX?z=YXiEn(j#{GML9LE`Z4Bqvwxn?}`nRCq-@&ci4Q}b2; z=RX{6I36^Ha`Tayr1Uz$1hsZ*Uz2`S{Wrnb{Zz`NmseQQ2Elx&_2fzg-ZSEQ8hQF` z;D*GFPQHz=TWuE^mqz(kbVgnbVgW(76B*Rv?_3gp@RH&I=;$X?b@(#~zgV6Cib_ZJHRz?I)9KyAwI9erDAbbDj>g)>bk!Gm{BD4#N) zaAKPXl!2V0o@!DT8xyx=_w$=S1y8ui<>h#LklRORHNXAJG+qmkt90qe!vkGRp%r@ktrCo7pW?sux>x)nlaeE zbKvw=n%Zi5SQ}q*-ykf5}z zAbIy*KHFc5v#7KPBzIwAY{1$mkj}IplaA8#zaO8+Z%M%`JL-K~?%(?c*UB+Yvf)@i zQ6&QZPiG+96Mw-NCt_B!FVl3MsG)j5x0-L5)H-sVVx_ca_>x3!y!d*DN%hriT6>%C zV-#6cwS}L_hzO-9lhsP;xV?_b8XsA56P`bm(hltm!q=jI7Jxxy?98aRqYH`%^A4xz z*F$nzgrR;^6R0LhcypVgeee!4Jzm*e z>Uw`15Rei5+Nek@>HPEh`5Q6{ZCStwNeX>7Zzl3Dsow>=u14Pn;7?*{9d18y9haj$ z+&dG$|CI^NPz&Tva=BrjuEqL9n62{EkQmX=gnxMt&xXI!1(B4zQvP=R(AAi8m1`d zPhW^MC_$6l&s|Bmi5dOS`f_4hu9v{WEt^2zXg98Z13R9hcWIOleC*C_CKG{k#$q9U z(>;$7G7&K&`@Cjow1JrF%S~>}fp!x72jyh9e&^uVB^>Iq#4MV2qrIXVA8|ZtDzgI) z$-?tmMk6yM8)z3MG@iB{i)nXs0^PWcKoM2;8Z_jubPEyn%0XqA+3$~1#}sf zGB$#R?g6`!5+yW60*5x=4+x zr!sccEV3xRh2*f)hRzQ~&3nhff@uCg4zq{KtZC;$XU*=#g75-+m>&zL_pS)=mMJOa z39*^G|A={plIiLn@3$jH>f)HtRxm%jW&-g|^J!aSZELlMR9S$`bC6@PgMqJqMtiH{ zlcye#|9Je&M>%m>gGMm2tF=BFA1F$eSWA=GqlVyF&l=_dnDxR7JrT8a4hFOUB9v(D zJ4((vvM}3&D=tnCwFXl%dWrx+NfeaMhSB)QBbwg4&=!$MOP-PA_8{HGu)K7#Q|*QA z!=n|O!#QtrOEyZ6>F)2j<~jj(zk$>>9uks9tlpSKjVwZI#qg=Q6+A48)c2zI3!N~hV z@}8zwRg&khwmAlu&A7_;Z%v8l!AEnP3# z7Qyv=VY^P0-p0O4*hn`nOgP;Y@vGDa5u~J_mIvc@3oxoPC+nDwT|}Q1VBQ+$nXa{< zO7ph{eSDEdOgO9^gMIDU>wAcV2=2gecTu6d-TEiJ<-Iy-5*ED+YmO zMN-P5IC^{gMEuZz@#K6wfSjRJ(XVhnhYNL;QP}KKVBE4CeWP5{h6wJNx8!iVBOYQADUW7{5eS)iA}q zxIClKD9W&o%u;OkznE2yfL?@!!57RE(^u$3O_O#N8T!6piOPB7^B*Ai>*y${^evtU z;$xp*Dd5R+Q&aUj;O;24U_byvTU|qG1qmHIr4a^q-jrzd#nE;%kk7C`TOYpbk7hyO zM3w>d+4s*LA4<01K#iyztQH>UURi6HaO>*h65P9e8RGEm!kH98g?MngY(mp9jIj|n z?W9G3U3)=z|F}FAYiaTy+Cw&9)e;kl9SNsXb!Bn+fbx)+~SL)#Xsv_+p~#MZ7yza z{8h`mej`eHeT&LDrZ3j#8ZycPgN1gFftozm7nHujhE(6Onctc51tWaZji3&2(pRP| z`~GYo0p<9UN5qH~;POXH5+Z^V@-hSMlK>1HLVC1aZRyf3V~zl#d+w{)s1bnBp+V$~ z3ML@cfV6G5$9MUH)Y+G11Ru5SU##;6Iv=f>wzP@NsrF%_BB04y5TG-ZGlZXBpggS} zW`ds#3k^d3$w&}n1B_TT2*gP}E<)8A}!K~SR0rDym zu`ub0TM24#j8lPD^dJeLHRo|vZ>&z)B2v=5cmK8(sfregNP)Ov+`hF>DZXZXgjS+0 zR?w*SH+ftbv9AYySe)kc(J)@V{$MBZRgp1{F^GwZ2+lB4u^ey*I|F5aWXf=!RMdx+v?FP zh!7Y-HXMg+9S+I}!BiJ%vQP*nBHFOdyi;UojJ=L?wi}hE@S9$(ugkT-W281Cpb5qx zV)}O8RC6AXRaTT%(ttWq7g~Agi^KRIIF$VC*9w8?6hHtPZbi&&gwpXThkKgxVH^rV><#* zQqwSJ;RpUXF62Lry1;vYS1>`|imLRUm4#+?QB8!9O-Z}ber+9a z1YRad=n53fVlD1audeLmOxAG(V2&vQn>d}YGx9>&n0r3AGf4A@FW^eMQp6{YvhfZf zNy8>HK?MBxq25?MDa2aiax7}T`VS2LlreuFKp~(o+A$)l!}rD1e9~ut#FASUBAH_B zE3z6>f z6BFwQhsc1&sOrCvcFdf98L0+3Yf5oDz;stt$Z>AgRTR7CknRE=iz@eHnlEpG8cck~ zFYd`{(TcppWe_TJsJ1+NHuSB$i+AEy*tPC+5R@62OLUgBDBt2(TK;wU`_IDZEv^e# zbfmIt$@31iw}G-Ky=y^q9Qh8-m{{Vw z7Pay=pRICP0Tm_p9a+}i1$fm`h#I9%%9Yu&R&{oY=jEf$@GsAXO#gwsaZM#!MirVQ zhCCuKec@9*!dant8f!kFu)j)YK?>^s&<*ta6sxbL4`;?nBDeZ<(JDXwRh~2kJ0U#v z!#N8#p6YnhhIBUY={nR5)Cr+Vr%9XBe7T%5^Lbrb<9WyJZ`1()zXZq=`Jw#COX}60 zp320x6mXtdjkd}sOVHm>MW7Sjm7+upMWTAnXqHTwgyp1V#ni6(4SwY(@q@7T2kJ8U zq8slUv$m;bh}9~l*x+X6E#%T=hxex!U=^`ir#C2)T6$7?NH#m|&BI?I_?L#=fIh># zg>K?Gr8tC@Z%r2hC_6nA$2j}KtXxW~Wd=l$-3pDMkVID&U$+n)RM@o*!!Op=MGvG$ zW3qFdK|SsSW8ezNyPTPkz=dWEeGz#obt*H|7ipw5RFPDCrhOJ&VmH3I1;OWcu&R0 zn5^66^iS_e9Z0qdQ>|@Wt!u(ItkPU?EVeI-ssz>#e%8S@Dn{8F(sJjXTa?bZ#%3i( zuE{RC*<8J2;1>q|c#}##X5Vh10PX9Ci92fw%{KzkR=IL+dZFFcyU>3s_p|lBmwkjT z{BSf0SQHUb7*@HYO{81}3ya^s{-?$Wk#R$xQzcyWSnz(G)O{q^B%m;aYP4ZU@^(V$ zSnoCC0=yeN-5naV7;`c^)etn!$Ie=uoDrbj!w$wWGb!o1*Xk(?N-ukr7I=pqFYeZg zQ@rUy4PVZE-jZyZAT-`sMs1hb(?A+*c~JRH1pyHaS*qnxyMs>_qn|XIaUj!wNVgDi z`E80z+jcc89v-zUt2CMwm4|Xs)H3E#Yves?Rz>_r z>|YNOY2o9M(W+aXJ}%W~VIXg~ZHevruUtd`H{cPkW!+ChI&}03s;Y?LjGbWT3cVkg zN0=CAdV~0%1@tp8(a7|nLN2@JQVVX=+Y$E9<+KCU32)t(7ivL5TjpedQRZRKl_Lw!Y6BMl<6`2 zSDo({oOr08ipWR3+%oQqyw}}t?DqwA`Ao9{Q3O2a{!j8ka!S!Ya8D*YOGtBfO#@z> zuibHA?X)>(&aTW5UYUkyliL^fv)APjCMA|>y*1v4|O4nD;T>e60|Lh&O6l2`{3F>^;?Z zKTo#>W#@rwsb=ZTD@%W;>}A%$dSP%{n052t29k+r#E+t(`_Q@WNv5cn5gbYjHN2z; zE~*YWfO$3YK&Y0I!dXAu^raT^Yswi$Gw&=?s^^re7nQ?e@2^8aCyFDz&QNJ>mMkSd z#&sd?I4hL=I3Vxl5ZE=#7dJz)W(Vohwm};tg?7^a2M| zluACXGBc|S^epV>j`0Y)!E0VPnA5bY*wR-~_C@|@!hgP{@vFfO8ib7Jf)PG~OF#5u z8)$_B!lEjV1069`9}`y(k>`?<0@;2ZX?hct>VSTu*@dyay>|+S;GX2EW9@N{QRA;$8GM{C#WsJ>Ub>y`4LeL1eK<$JHC6vakih(BON*-s=EW zr*xouRF#u;P%N0yuCl&+C-rKM&< zedoRPul(@_O1B$a42Vz2S6C4VF>y;Dk=##w3zh>SCT$T*Qlt>cc)77H4o#xOqO&{> zjGR4qNn;9cs+-4$_ET0x+81Q0dkoRk{Ttjrqg0xOOF}7v#7pu9R1O2Tlr>-eIU2Go zP`IL9LNJ?*c;|*=O;VN#=9x!!)>8!b^sivE-i(N6vRY(jQCTyTR@K=rDv|!B%AdW3 z%oGtm+!MJupARmm;t`VRQptTn0NGRSja@1QTEPtoiZ`SZ5dzUv0}?}1k>=8Mx}fj$ zx>jkZI&UuhkWxO{-ce0kWa*D&uVpN>j3RR_2UbDsVc=*8fFS5OUxNNIDFyH2$mArN zFbYU(RJ%mx_uJ0m%0$x39zSY6x!kJ1esY%L{6lb$V2#{NGO|GDPIiz-|JDdixJGc2v3GGmGl2^47iPsqdfFEoVqEuhNJ;49NLygw z6p@>WviiOHC#ej?Z~Obf-+iR_AUm};cDrFna!0tIGk_$5j}wX##eC(=Rn)0bNMh_G zvfL=2$9)OP$ArIF`ZK6PAPU1HVq&j5dOsZBj6V`;_~2p>+PY^6 z$(o$q&KJ%31JP_Bmq&FWAI9xxXEkH0iyn^}Z06R&&yEzOFJ?bFi-^kMzyNg>|1aeQ zj{Eism%B{iD+Ps;+t&E8mju}U>;59#ys`_)mes*^keyf|TEY8$SNyjzif@6-BJa81 zpP$oZ-u%&bpeDFs&A8jWwfo*d7eb~R!>QasRNA9AX;$Oyw(BQ`TrZh8N=5UkCd+~< zBx0DWmg33vMZXp!(10EizMq~o%M>SX`;XH`R@QGRa~Y|ArZvY~v~v=fh>8bQco=>c zibC%9Nqs`?J9GgFXEHv4{z4UVw_ESKv`rBqnoa{nlLkMjS#mQnUMMpq#jyqAaU)l~ z4C@smb8aTDj23;q!h$)PUi1f>_BN-eJVqe#eHnplJD!SJ2TA}Wj$5hXB?2y z2En)cEds#3wZ`M>Z6qaCem+CA{zSwi-XKW&W)x|!FWxB9n&Bb&f{^syk4GugdqHo< zd#C4Fxtj0oVmGDB6Q6?C?z^~ov;tvK_W4=LFrSJK<(s2W7$relNn;gwCUo&6 zxI6l|VNU%hIwhW-lvtVdv8mF1MC!o#`1p&L_;@8T_BC0&RQ$>x|AreK%gs@ zrB2vHJrB7rcDYoz$A@9J}3hPKynk zg0+DY7drX@r9o6itRi}+?tf8W|dw<~-q zCqI}<1*DuG$ytmP7A)?Gyw@}+vG(#MHgc`HwHTu=8S02C-tu)f=ol|rQ%10n9son2 zkcAIr>O+Jm6lXr%L$v$dNU+OoH#+=hz&G&82GFL5Ias$C+!cG*R2>v@)i8GDQ^O>a zN?cT18Xmf|P1#j3*+hw8Jm;9@i*|03U2X!u~%T#r+KDAd%Iw^cIMJtr&&#F z?LGJ$h}3ia)$S@+yHgRr_JxKAdlsAA?(@RLD$FCiRJf%LY-*La5lTB57BTGz5w_8y z34wC&^c@cl4FR~Z7CV9)&kxt(Wt4#a1g>0(UnmC-Zb{L_QZFr(YThcW4zx+~XELB2 ztISvB?^V4#I29@h5^DGfOU_ZhB-l{~T6*k6GQ{SEaXSp|IPH>sh6|lj>x?*j$$HUF zK{sMG8g81^-M>fT069uS1s>+~#SJHq;PP?1v-5Z(M&#b0V!ZQBP-8;0qwt1;bKQm+ z0yB8=;N0AMAvozmyT>A+5t0UqQ?E0tQd9JHz8xV~7Ch-L!eWKV+aQp5{{d6ulmj-$ z(P}5$@B1AKw8Z)AE6elEDnavhH^ljTg!|Os8{yIqf1C##WKPz&WdZ8KcZ^O~BoUZ9 zz+$8(Bpw)Vge>98hxTymN+9-Vn6Z%j*y|ja3EDV=kbAY0C725GN6WoC5hIKOwAu4B zr#a;rt?A?em!|=xvm>;)jO2j2@+0jAc_E=WtF&N(vtR736)%JYz-^g@^qfLz@_HX} zgW05DA4Xswagt%xzsk|@%@~S<$dMn_HgxyF6z4b35XXKQ;yiHq6OTxva>GPE^`KZ} zC^+-XX(iDaeU|r)^HSsE%ucjB*BimKBS5gh==em02WjAbTUnO{U=>`i+2B3wpPO#% za-SIpVgAh|@KNKskiM?L*Di1jF(o%;edE!cZ_T2?WKy`LZ&Q*0yEZovwlT&@ zN&}c@Q?x7ZL#5nzm%b=9FQ{nw*}Z<9{VJ(!zx34;9l_h(6BCA!Z2LeNh@eWIPm-_P zXm4xXjcke#u@gBtQIWvVImkx?;(=@@y2cf_g`^3i@t?uJ0IgUAL0al z*~O>SDb@~#8gV)tXb<}J>Iq4h!@k~Ah)uF$cUUZSs~BTOkFXiso3fUzeVzjH$5}#O zSnB<*YW0Y-{&9;Br)$oLfHm#zM3;~382rnD$I0=3eH2N^8|8SBzr{y!jGtNmEQG%X z8mMj^tW1KKfM3S13(;fcBFaNYs6la$2^etdsDz#@oF|FAS8_nAQW>E)*C>6vHd0ce zRj>AqX{5mPY?FRZfxpHCvP2#(j<6S+@IQe+mGu9FKaV)sc2x1xkOWa~prr$a_V!VT z``roI;(*ZP?35CX2>BfEy~%Yr{gzC(mQlt9%}}A`5QO?K(>m$Y$kT*7fUwbA`q{2iV^==n-HvBV?=3+iWrDF?rFfhOA_OSyxVQ{v|E(S4zaq6VNrH@ z8Igm0zSBw ze*JFqYuNaF(^UQJDaRWL?=#*5Cjridz>)q>xClev>8$U%`EHp4_u=)ypa%b<0>Zfw zrr`0_&JLOg2!fVxfoQ*>iOmO{gzkH2Ne)yDVPN6JTREdhV zzuW9N_+s~UOZx2-<5Dhlh%U0fM6C$(Yx?e7a@0rFotZ3 zc@y9p(96jqAiMuz_dcBJ^ox4 zt3f_qoMDvi7AD(+@5@fx_rD7>3NB2Ar2JPOi(MXwS|{aLCxNBVj&=0AliSAFeMKrh z>C9ZBr8#kB&O8(_iB9Uq-AMs@<0LBM|Jw#Poi$Q_iej?UU*ULR`X^3+F4-DeDZAX17>E7qd>OeMc0|eRJ6*v1wkc)6Z ze!MG5rXPI0rX$6FU-KD7+kb%|u~Q^)BSE+}lG)@YrG-~dY`V_1+^|WV2R?b*^E1v+ z3gr65h(OS?fPsd$E9Cw*lJr|(vnJ+b^S{oIQTD%|U%r^{=Wo?mYoh~n541j72?O_v z%K{zC=^F!yh9*I)pDm3*y|)wu{7gV5!1nizDn$6>ss9fi{}&3GPQ4lJFT{cZ;Gc{Z zOOD`#e0~q&q8+{KeUKe>0(jJe{s$bT3ju9UKu2>{Z#g7(v;K-#I!&0(hs;W&?*_1t zp*52dKB6_IGd1M>+zwswPdkyc&1Y3Xu8wBktC8T8oDd3$mqX~~c%?Y^$%3dP@an`$ zZ=>tv-4z1Ed_Y0;7Q!N%%3rhy83fAh?Hz|=*<22*X5_+{`R{9yf7oyergn%1;CBl9 z`cw$wSxw59nKj$j?<(K%TC~Hs{@SDw25wry{Xg&pFy}o8$PJIKJ%v^6(-k;r_@ACa zG!RXN#xI+Vr`js#;@;-gpIs0#o@qvzcoc^3FRolZsJj_Wo^5^fK-gUYJsiG`XJ8xn z|JU32dAVy%Q~hl7-pI6LIv+^x@>-bJxz=i(1h^XYSaQ7l2@#B>@~1jr(Z^5j07!=m z&i(J(;&_mFfVMQR&t>X$=D>e*x+Zv#l8;W^Z5BOLU)zdICr_J1h6G;Kv|Ik`sJ|xXo0sF^70KtUBc@~TRlx{|KA)?f|&0= zV2ILZtDD^bAf{vZwhyhq1R`&DlYVRV$WG_U8nXGLYewkcfQbLE52!t7@1l-$vl;=e zVJ#?@f`cDCoS#4{J$y|`5odvQxH)Z@a{tDyA0M_H#PWtlzX7czZyfnS2KMPurULRb z0qq%ags;QBVh*T7@z9a}b8I2HmP>|PI%pkxA}WBT2nQdx^0qK?gp1>dTC*{}U#jGpJzT;g4C z_nc#H1^g~0hBoT_O&gH9;^1+NQ>Y>M!c&r9LJsgzixo~?p?|tmLCs6YruMrO9h#vi zndul?N1zY{0gX{R(nPQuB8~Fn{VB9%}^8s^TgjaSU_LaU!`CfZq`sPEiGu)jL z;Q8wfB!Ax;6as%)QZsFv1^+YL%JsX8s+`7m9Vq5QuY=iQ-xC!KP#7VDkQ1-LOCoCM z0!~N{Ek^5(+0>4ogG`a1rM73ydMz03C`?D;AhJ)kjPWs zj1k2x69d{Sd-IJkh^2?fKv%j`<=i>!DDI@;y8-oC1Sx1o@005RfgrO#rvlp2{3&V{ zS@zd{^?Pkx*O`B9S8p!_{O&F0s~^JED1HN~41e4QkAeTMdcO*^K=pJRXSM1KBXqr1 zx&1`Q4^{=GV(8Tm$6AiFxNI7Eo))TK`0extV#@&m;|UF_V5c<`#Q(W zKCOb|=^@@rVdj+M$l1Fnb6vYD`%wCyUe#ae0uA4W?YVW`sHieRyhlcq_%pRuAhQD;`rE%C)OFH9>@w>RfhF20AY_nOO$H0WIb( zRmv42s0R&Sz|LlrTnnWe?MrVa4!bFUkG6+f!`;Ww%+<56Gy=KwpLaoy6bllU+CPbl zFwxH1f!c>aw6ViRp;84`iOtuF1DX9znuf%*0tJ12Tjx~D(x|sd!6+R z?~DlX;HEMnr~-9fx@=}#j5*6Gr<#OkSsiiqmzZQtSt@a2zWZy+U{tF!@EpLTA&ssS zru7W)do5~W`q+OFNZ^0~`A=*Y$2|LuS3&r!32^u!ZT6SJ6uQ|eW0%`{|NWU{rbKV8 zae~wpk$=UHS9;#Z8%=anznKW{8f44H*~td8%<8$OAbQmj9ePvEKr|PZ^^`I8cmvs~ zX)VZ$scNgqGS6c3&1yFtq$aC%wPM|a%mTG3X%35#(DisjC)U#947RpEy#R5?7GDWB zl|9Bj292K{&GoI*n-5RRk2p?pml(7ylzHk_bl)Z2OAP){ZSS$$?dFIrDKc zsaqk?HAh)JqwD6T>s#l8{#(+9;>duKr5_xR485=du7PL~6DGQJ_4TOj1h>>!N^(C3 zOsCoLg}dtNhm&y05d+CVg8&vHATCG!9ZHV1!6cdDG*6w9vn`UM2~w|>={sCpojAOU zcO1_9^LaLl_JVJTSyHrh=@k#FgoO@P;>-@!U^Y7*^3KnTI_wf4*)K|)JZVlk%ExJR z5Zd2ft}s3$P3N=<8{BeOTkBO%4ZG|e*7n+dTetGlVmR9Z2aEJ|=CX%?HciT?ynG_Z zSVu1D;Ex);!B2voPIwTxvzyrW3cv@JvO5ePZ{B5Hq3=ySd9iPka~{mJU5d$EPrpTc zmE9{6b!%X613!iMC(=!Amf(l{!+gHd73xYM`Qxz50MiAHTd_( zwwa_J!d*@R^4kr&86^!rHt^=*7O3mSHRMY}`e?Vt!lqW1=!hg8v!6$p)aw{(XK>X; zAS?TTeXjFb#a>^oK?$T(=B!4dLFUmVeyI!*8IUNTWCTfNw%+mhJROV` zifUz20pF@Ol)4se1zy@U*2WD0^ko9*bGRlLaqSU^OKLn%TtY(m%9_}2FTcH#Qj@GZ=BxxC!fWziXCgyYJ%xb+ej_L<% z92tRV5w)+^pBvcQ`-h%x*6=l+pF8tkjNgGNKCeF-e(;dQ(Np(qy`NP!H>r0zo%Hqg zlPZC8(~C>DREcSBDVv7#fyRCi5ka`FVm!%r?9*7LohBR>bKPd#@j`1zJeXrOJD z?VsL_(V5()6z@4T4eHyT96UMsY!EsUQWK{1-Q#fNr%`Z93{@(zVBg79%-C!J!zs>d zPtHQfk7~1f&rx0uXX2-*()=7P{f4^TSTa?zy}n_hR_45xjg#?(z?zDJ@35t@?(A?q zndfbohfLRfw~PX{;vAQ>{y~QeR~~clu+hiA&o4lo%H}r6 zw*vFv?rOL53|^0LS|rZ~R2(XZPcK#uaVK6;tT?6t8UuL)BnMNEu`B-o8PNUOelR+} zUh>M?Gs)<1E~z~Ia{=PX$w}SMp^AgqBxYW&eG~T+AGoikgtSh}dA(e$7sr*n)u@Rx zBRG7rQDNgX!ZhOK;Mr8YjpFJs#Ky`%&Ude>Sa33hN%nU`mZnCJkxR+{wlXmw@uuQLD_Z_}nw?P3^BI7Y)H@<^$mo z((>MmfoROlT-uD-<^+rk%r=b*=3W6&-Y+(cd-bTkyPWR&o3)PVJt?GZ;B%>V9DmWZ z@_2b$U2k+oQ@G7C?U6=tVwL$i5c|k?oX}SSXY)}uRZi_5L6t6T_&6)o#HZY8{O3LE z=3bUGFq0ZQ2-BwRhbWh=8`#ODo$NitUr9V2@ zK%FrebR3ar1TuFJ_4&&vitQ-F+42JoDG==Ep90Kzr;cBaGa0RJh6ZFi2)rO|a(stW z{0k4kyYLLpTp13H>of<`uduEdp}WmTvja=8v^Dq3^Yw2jBe!0vi`@}4`!Gq8qW$CJ z_!vbz5UdQQu`CtvsWLqm*2S-1zYZGaxGL;-(CXssGl?L;o_sJgAbo?yio=SjN#6Yw z(wnkZeviv+x#&lD521w<2r(;{lBvv3-$}n^k%ky2HNFbrw3*F1EXH{B_Li5F$>GP3 z9gm~tTsx&pSf+f{b9orJ#ZK*p%x3Jjkrd;eERX43DoWlj%iTLs9h=FSRiFEDzM%%l z!-M%4qSV*j4c}*Nc~S$8&FoC<*{RhA&53z$AMH4AmQwkE1}6OupZ>hPd=nyRu>g7Yfct!I z%lH+t5a}E^jyUaF$eV2go5DSZf$38h?*W5QCJD&OevJXV-Y4DxNMj`98j2H_+qAClB z3F)Njj~JhN?IB?g&ewlhnOF1Y_9~=>Ecls_jHDwpCVMn%tGuRLbmk8;RS(~YEM~k; zMZJu*!~@Yf7y_MD*{_8){6|>f$sI$^eau#Yp!#IE{w~@TaIWi!!u2fn#C%h54B-zj zL<hc8uLNMi#X9Vt95)3xAuDay#lP4byCvlNs~^1C^o+iaASfH@=u}~wnD2t9^>DkvmTf2_BhWTQHlm7JlwX!<2_y`47H^;@xe8QYlC%IZ zCj0o-f_vV4183@F)&Js12q|H*aZa55O5 zy-ZQ~OU74?BME`ir-cLON3qGL36H`_`b zl()$H9vc(*eLdBMox>ZgR$*LwFzzJ|^A9!=S@?Te@5&!;4Lmden%HczAGJ1AB?+vi zcCL7&ul^bHQ2#7Z@^TM(MNDh}ZmG%AX0@#O#xxJ@Z@pCbw_Y~%d(>(22 z_d&jqd@WUybjvglKS<&6!wtlb!*?i*fB20JwDDJsMyb_YrMFlVE4>akOADUmD?{Fa zxT@zg`#NNA;vKWs07xE;K)e{fYNeSo2TAR1Grf&(2c%wA%2iIK-y5vk8gg1J_q@jQ zHfxxrXtu|OJjM0QUx5jGHmxhjiE;=#1pGl39~3h0m}9E zY@_fCDf2VkS=TCSwK5D5P1zbf2Q#k5i&IJyK7rGhevXqaL^Lj~e0M*p`+Q2|+e^YT z*q=VOz2`ndpitZ2c=%rHfn(NQ1#uvn*JZ6HD(CbBU4O&9_DWvIv-MI&^Kz%wLhh2r zC8p=oZr%bX&WU}z7aFh%X`B9GVaBQ3yMmW_%ag}l+kGb4R3O4647b!eD_s)@S8}OA zc#xh{kUXebo;y9>r&NuG7ez$e=>*ETNh2E^1wbdE@NL{{YfCsaB>{Ln z}m0g<5;v}+-;XSfk<3ici1=f(?OyPANK`Knw9T3 zg0DQe6z>W?-hIBQ=7s}1s?klk<1qqt!JFfY&>Pq~vbR0DdLozJs=%2U|a1_S$K z?g>7+(j7ajXGx@d8A=B{45Z)r6aDkE-@%{3G(a5>1T_N9#gMDjAo1O33wYT8SMMeM zgFxolcfdV&;D=0rvG4A5*96ZBfKrpzfHwr~>IjQ=udVO@8Buu&DO+%55ERsdrP(GM z_k-E}9MXurIF|XU3|<3~+ZeiE^i%u1jxtXsGq+Ovw!Z)@(p1nAZP$GlW;!0ftB}BT z(EKba)q{`cA=W!YJswuU?r01PqoikwgVKk*aZ{AXOFn3BH zy*#bhtJhpg9~3xStG8L^Wl)Ll&9ju$WBoeM+ciAaFsUca`4 zEsOt!C1{t_^!z-{^q(oQ&D>^!PF=jwUGo?T5l?-S#(E-uz*8Drv912q%PG|{Y)0VX z$HSCw**<%NXBC9`!kN^4{p+Kb8HG&iC5py!4xTn~Zf{6D4&`AmkoG*i`syYYg-P-C zToMnWz`O1ai$N~`g0cL>?)z0JB+T>*37cu?t<_ZZDm22$bbn_`c7Jtk-DXU@v6Sv- z_ggU&)s2umI7PPIe5Msab0P1NqOet<{Wes&R|}=oAi#3@?KwQNVou7TcPq@!m_9lxGpF~EIpPkkb{x0oh@znBZU)Nf(v@1_(F!+fNGAo1x#$ zKD{quv}xz@IO}zX^@k*}2REr@9$)6We){HFbA%3uy+Lpo5!rugVR55aQbIL<2RumE6zt7ZE61(k^Bo$v= z=YR#yIS|k`9b|&DsLCf3xM=|QT+jGYJK};u7AJg;@xqi}o{7OhQebVl69(dPTkE+2 zQM_Iu$YV>BWwC)nNKms!hsmPdc?A`l8(tdQcR9x2VM@RB#+Wuin0#Hc z^VQLdGYLJ^r?wUtho3jcHE}|{I#mMEmh1Bkw5Xfv{9LkqT&hItfKMM{Om;o#C=9|1 zR3{+rtU%qRC&}(OEWbDd*+u7fY4`kb+Eo4k=3QEXweRhP}9Q+s1nCd6_@>DZzK&&v zt!i$O{$_!hlX_`|C)@i5DQPC27b*!~2PP&|wrjfK9JL=eyk^&JL%OR;Auz@3AJ~5w z3O<4L`%*&>+N(y@ayzik(vK+F+Y6ny!!`=)=a*>@+H$GPzZq9xq(MqWdo^m~EGZoAnXsIMcZwk76HEuhW)*x;VHd-ZjV z@z+sgX?w312l+9tjY_Y^p6149CGu7|cW!&l0=4*m~p-}N=`%Z%i!>s6S@ zjdY-gnE^hK+l!dfxIuyY`+Zrr2Hm{oM zjH?@WS_Os3Vv{uqq!Lq>L2D5|AB!@YR=hW_bHu0x&&&Q zC^n(r46c5aHZKz_;J-H{0^~gRi|UtYE5$j!sNvgekUdbgc9tpY*>7Imfej#c+n&8vRA^t zSMZk2O{a*wo%D6%0vs-SSxqP#pMiG!y=D1OC%_8IoV%>gd0F(X#S}cX*)h%r)z2Q+pvp30Vl6^ZKU1yS-SaVhLRzyP*HpKj}6sZ^56lWB93SQ)0q1W69BL zhKU&@gB`FT$>?J_cZGvFo?UKL;n3ehKS7r-(dWYhwKe{9WaKvtxRu*U0qU(O>-*DZ z*NsiVN3c8mXeFSlVCmh3q9D_lhZD}|MwpY58u}ZO@4D_!M(^XBiG_s5ktFFoEc z)xJzm_4rBNxOYm)F?A&bs&qpWxfx=}Dx(h#-ipJZ%Bo38w29-Y-6ZhRQ<8BwOaG(Wv=-^p7g$Axe?*q+_e6QZKpEq`xmz6ryz23 zxPK0Db?)KsGa)*x)8d-udE`3&I|s4#R6HO+lWo$@Q`p3Qw5JzlHYK~=H&nu5&h0t2 zFEBPyv)$5a?%6^v9?GV;LE@tCLG!e=A-1)Waf0!1hL$?8rLv-uvBtmz?k7zbTjJfW z3ZJ%H+ePxpxivpOuCmhr<*i01?4gfOonj`~qi*eVU~cmi@<&&dO6%6HUv{zo&OvgU zV0vj=(VDsxZAcHdU*Wy%p4_^#TUO_|o$x5hE?G0>J1B5SL>b61^iYmhnoaecd|PMc zFGNsJhMn_)0xp%>n)7BS`n~a7GEG=Lhjz-9yraAFnnp6}lxGHk3D<{e?Honqzl;)nf)OX*r zH5rUSk`rlb`5)AMcRZEh|2M}u;m97@A%%=6+2bU8?>$aZC^HHX=OiK-8QCL+%Ff=W zh>{s)bE1f{vPV4EeYC#6=llEb`RDn^>(x1(`?{{r_1WWnpF5ws**@XKdy72)HUpihO- z*FEx^#X)V_`(<*Q>`tcddzRY$2Qx+~TZ!vZLl&2CDG&5Z^=#aTO3lsy)?Z~`VeiS45hnk~vZrs5xN|;#c(?MyM#K?m4OXa@WfbUg zT9ucU+ptvQp;Y6F^Kjm5lecxANXl9-L;6I;JJ_+J06Xt;2lll(YOQUXF`VO-j>%3}7B zTf_EgJ-G?5o$I|HSX%W`3M$oRPPlFil5ET^j1+rC9aUHt;PCn$-gWgnsBfwbtbLp1 zZ4e*XCu$J;LgAQcOixS40$xOtfWv~I{diA8!S|H6^yenMw$5tqS5Tv;$eO`EYNT({~LH=#>y(>|p)9D|Gr({IO7H!dJ&re9d(;2T&d z%Kg)C_BM3T@0!`(tXvsJcpVFDO;mgeTpJZS+To)kZ~e|HIqWQ?Zq1E8!dFmCGsj7l ztcZL_pXj9Ag>ujqwD|rr-F;(^IYGx(@kM4?`>+)+-d--l@!E=}r|(J&(+5--_IE%N zf^p&$)pZ5;>h8vy3l+|~*KQ9D^#ANqvy&yKdFFCg{Mn}hb`Oh*Z&1FJLEuUc#`biv zjA(JH@7(JRt-|Riqvuz@b??utO20_{z}9h+>X{1<v-xD=pdkPg**F%uIU!Ud+?i z1zY)PO;DCVIr0;=9OEt55SGQhXvGe{*P#~NpT4{;t7xW@WlE+71r!^o0J<{7M`48)3>UUbnUy8YgKw?^Lez6NT! zbaA`S(>WTk;q{u_hUVXO3X31^FRV^Pve_(0>Ag?a#G`;KOZQdN^hW3BaIWQ=U2f>s z!n-ZVgN^@SHb2qfiW{(U{su=JqtVA^-I`FYM}Ae#8)>A0nw6}x9(n)n1+WVc>-?&W zLZ~bAODvU2migQG1#eF*P0$%l1uZr_de^P^VQW;V(EZ_Hv<>a2XNd|upVTU&IX6J3 z$8~gbI=HcWq?f-04+03hy><#cf%OwhP1GX;e#i!`0nS>G-#b1`PnDxyHSH{#7$i~tcDA-^n!(B&0bm>d!Dt|%5HH}C4YRJk$ugc z`s|;B%oqWU!Qe4r|d%tX|$!*@3 z$-H?3IAvfqBp<}D_t=lrY;o>vhrFTsm4&gMP9Q3--+$(HCTPC_nK8#M=N;cwQ{odY z5wP?tO*I2pyyGOrn67m0n{RAgb=Ad(Cw0_q-T=6<%c_{6!{e%EOq9Jt)VBz19kXU=auyi4r? z-;ZM&9Jxmyn9%XGbtIp)k=GUW{c_H8=8b~jL*ZUe*A1`0hKnuDbHfdJD_+mT$u1{^ zfXvW{z`J;h{2MRxW$depn|8&wYr~zr5}%J}AiN}dg+IP#ze;C5d|hhXVkp~YVR_iA zI8^1HHmLMGSk@>%_4d2_PPpmmTkW64l4WRl70l$Gx6EX#f)+G-TF`2;FMILNTOPRL zV9B-P?GBkwDX>{&@cEq+lP3C?C4gNzXq9WTP|Li{13IKiA4o{P?pCO=8J%*^;9R`f zr2@3qv~Xu%lu16Lz_Hjcj~#jHuisZhgnv|lYO7$7c`lgWNx!wgwoxFHe(iGjx{&2k zg6^J=*38O#x0t52Ic1x%Z)smWpN&2dv90k_8ZW3_T1vXm`+>=RLrl*o<;h*(Oc(a_ z@=~uh)!)_j?_8hF&NR6-kiy7mHcV9-PIgdH-|gFfv-hrX-?Z)M(CF6M+au#%v3vj% zVcLW#PSMu<$=5`_sDU*z+s^^(UpCmX1z{}<<$fzCIQpHA%p6;Jj$CO+mgS^`(bTgtIN-A#ELDKQW4s|@|oUWj~d^~D0RU?)qV1Rjsk@hmEWxTAL{0nN+gDk=&poSZp;VFli6>tTqlCH)xRCgI_9*xd^E~;JpG-I7!WmUQR ztfoxyV*!iN*^6`jrv*8bCA_A#zr6KI>t^am&7ZHFumu&Tz-Mc1O??y6Ps|#J- zpjs;jS}A^RQpGn$-YaCGfy~~s0KzfMUVS9U48J1Af z!0PXs67x#a{+?FE5k@)biE8IF^CituMZo5zzTEjRGA_`h)-CA$E`1}>shNIi!7Ia7 z(rQ`x&WL-xVYi&Z$E^`tN>(*`x?f2zQ%x`5siRF-U%yyPh>LstGv%oH6QMGfZH&T< ztYk^&dehLvZ1aA@u|CeSTaCWyH->A_UoeC7BO!L*fKg71R#n`-9km!>Y3O?YZ&gaw z_fJ(ysRdM}@_*bINdP5ffBJ8~Iw7>;r9Xv}dXG^4OzXb5p>dY>0_c&t?zz#WTeERL z(9ouKo!_9!Pv?!E;$9Z$zIryaN6I_D-nr}7RT`P{J)gs;fK}MBuT@d}MhnMHhwmjk z^I@GPv=9#|)F{ovl1YXsA76r+r3-Rv+{>YqD(KxGN5| zR$j^=+W$Ucm!4W*Iub`cQ7r3T)4JsZ+$g6FnKf%2P@+s>TbVx8;AeyTwx9ZqS5~>w z)v9VPt?$|;Q#w&A5KVNpW0QJF4dUZ%rw$6mUmkns|7xB zTDOs31L{92Te1w#7W=0xW%I>b8bcq|Gt&O}@iG5;*|09luZ|*--o1R0`P?eI%+H&F ztFP}ly-)$FzqI(PXmMRNaLKj$$G+8zq@3RBp+FeSN^r zs^5O`wppaL|2v!7>J6e>KbFPQeYQ)x9OK3_o&{T0hI87}8Ad5S_gk&t0QD^1(+c71 ze91pu6?-*v^^)aI#6c;E7itedO5%iq;_1{K$90<)xDX-Lw zZO{`A2ECRi(jKe5gj7^~X&~s!vVm&ycH7ooWh1nc1R7Ib#xKu3arAbbph)}C5&0=~v3^2BRa7!kjk->L`%dkaKhz{7uK4NA zv?b`c>eBICnZu4OQB%$&9H12q#b0+hupeK^D_K0+>WlJRxj^^ z1hzQn1H8HWlGcE9<-FUs6TK4hGM{uRWWKAv-Q=Kw7nI+Qssvv4 z*n88m=dUhyH=6KTeED7Ty7#N*l|KlNTyMJ(>z5OnocXupb&*i=0uq+17|wYSNLcpI zx2t=}a=@@B%GUzJ9&X&?7`~Y{>!g0`SN0Zz*?}e3k6&wAiw{GUAP9Emzu(05W#QN`G~E$xSn$vS%i5jVG(R`*fj34w)4}^x{`!vTRvs&g z$(;qZXj3s!tMuBn^Z~}CylKbIB-UHV@zEGb$>~m}I&JTkBG0(XmbITpzV1!0R4MFg z>ZHzRYR9EryvN6*j66+F1p>wJkC)u06{YqPoKCBP+BZnKJqP&*?^uD3%eFqfPMNpX z#w1tL6V7y8&R7ash@>`YxEblRGp4S*`DNzpoNr50wyWe?e|}RHQ^1;Ex%^$u8}S)| zvjEIaN;~+X9m$31^$Tk3ppt!Iy$zJm20{KSW$8z}lRW5ZDN=uh-SVLmyT{S;f{u2Q zBl8KLGK_RMFgpQd_Tt6W6Ht=i3$@sC@}|pWvSn$(M=_ee2D6B;Wa-0U zLI=(+amIJ=F)#oJI=v%i@|ZcuN}x2(;l`R$H&f2AQIF`c!W~3I|ITx^2)VOv58EW) zeNLSRO=L&>5^is~OvoVgwy0V5V!FN?EWvL%u!(0S+WJl#BrB7w$OONs1pZAV`^~bB zd@g&*6-%rjDb>dh4PBlJ65(WuLUydT5U(DzvFEpC2IakZPC9*Px%94N#CXr1?|Afc z(AxYYx6ygv=f)_j%~qB#x7q5^CxS(d8VOY+BHMY-7MpVR5;yo`eQCC)dO2jE_PXP? zD=|(}pu|&D_r2*PDHTZDDs*M(u~4a>Vv<`E8G9zrKCm`@ z+g|f_8$2?hoeYX+UL=Y)B6xXLIXogdf*#C+M!W0ZJ0gEVsht4!x+mKpd_3Zusi9i^ zY(FPu{pE;CyW|?w^gnhAnN;3WIkcp$bqvjqW_QV*9j9MF$@8SdGd%NFCmmF1hg2QW!Y7f!3wQXRj>e z0z3b7+h4N%YloQL=b$q|84|9;PeG#9d~JPn#DWEgmV}jq%ANZXn(yaK6z6spnVdhg zGwQx?&hS5L-1jr(NR0)@$cYupa7L{}r=j2XtwNfHr~;OswKpV!cgrcTE(3X1$JJ`kJ_JRj8_yB7MnpqT=%VODC@3^#skn z0_WoD*sxvs4(Ijj`uWZG3UD#%7MJFCcHI3^Hn!Mj7%4t^G|qi+&kBu9jZfu`1?{tq zKQA{LJ+YX5RZsP#TzG6MPIDi!CpqrPQW91N_9PM56N$3m&9rbbb&lW42z3(y-o2YJ zZfhU|dr3n!yrI*QkM6j7b5`$Q;3ab<$`)}R%+EweK>-l(1X*u&D%ZoMr4t-XgVi@K z+n!o(Tl6D0xfsjRQCE`sLD)QmJ2osvpPO@Hh}WlH&NJXugVWHqI^WCVV^+=px~}NV z_z$qPdsRn=hNJ0UX8!f5!-13Sf9?(#sxrLktJATCEo9sWf2+_BY&}eTagX4df_FJdG!v zi~zwrjX%}cxHR_8OLvHi4^A(af-s!2`O}pLX=F@afRRYEmDu0$Svwl6Mp5P$5Z29^ zZY<9BYs}1=9*mit)kG?BO&yeCE(P2(;D!|)%(oX&o<(rScmh}KwC;j#$V$_;b>4NC zDTejE0u_)r4wpsvSH};s32a3Ao2KhZ_cb>x+x59}9)${wnUr984(>^>w=7pznu135 zZ(Yj1B@s=%EKPp~Bl9~Rx(v?h!pSilirP9@fB6*bKW6*E+F$^Yi^dlkd2u<)r3-gG z$~}A+q*ueb$2nv{{`;Uge*wD2sAJGn+`)|23$b@k=>gm=Aye+tnruh|ppHSWnSP!w z=wQ@@##i!7G@TFg-L8xVFJ{4kUVq2MC=TJ@sdpcE_4u{62hh(@gf^g|q)YwbU{sBu zv}LEXp=mWRrRDO}2mpkdABQ?iOw4(C1{K&r+m9WXIlOf?%^LzefkMAn8hpeCZ;uo- zW=AOezHpfAJq$i349vwc8$-io;H*L{WGP^Z2Z9?=7W6;8{GlTp9iaOi^rwV_NQlX|}NG;J87ZlHhajxZ7vPSHX+;G-;uUx%G2XP|Y{{ z7MQaIb2Ve7kAEY2ftPR%SS?5yLQ`=ELcmsbUxwD>Kngq^(*zJ$7qXbJ7|QE&017yt zFp-ALk8kzhaoQp4Re;w67?1UZ!QS6skREE7Z^Az8gr5<*bP^&hRMW|-j}cO()n~d0 zgOcjXZ$f3R1AI>ts8z%w1YifuH=hIJdk8|6A7kdd7ZOLLjZLYNQt# z63dS{4|*E}VV_FGw`-z#Lw>Zay4wi|M!xaLX~{nvY{UT*A@}756LQw9F89uHHN#4m zUvF!%oX$8cC;OBNOGNJRSg_*diC@q}`juetZ0PUcS@q<&02}bm*WPtG<)0ieqmm*0 zsfIfPv)UVe?s=-qP>Q{a&0lAsD8tzoNqO8h2h#=+7YqnsF0rr4l|GO?;i6%KhH{8w z&x0nS5!mQgP1jxU@6PDQ;4WOi2z1%+5$M|@J&rB?0FKN@wlwF;z*8n~s_kfuBRtTe z$)LMorN(*jHUNPtU(#+ia^hb;iIAgG;ff$2tt1NR(mYq?LL5M?AY<4;ws@QguWEK> zN{vV9N+5eA5$qBWxa8kPjK&*+Dbhu1IeNQ{Xz!dImjBAWdX%`EOvrM*>_qprDfjH_ z-k-AGYyZ#IB0wS?%%3~=NPks?WzhZ_KYtvfG!V5g5t!u2nxS+)+)M!P$q%z&SW*J5 zBMzRo_0Pft$Ot$!hM3O)&uFQVNV3+DO_7e)aAnE~6sfE*n;qYONdh*DVwFuH$0Shq zYgzhpQ-f|1*V|p^fOm5rH&xu#do;C@pHDI1D+Dbi-?zlTs#&i|9DN=FR5${mk44bm zc}}Z-j!U8uCc*e(LqiAY$wSw#evLKO~X$UCWkODyJ?c%_bfr_V^Cb% z8*UmotcQ8Z(zDGflDz9yve(4I}0AKf_-bp+;0In@)3izaIwsb;J zUX{xTk|Ujz%{0QL06Pdk3Vv%>PJkSh*J&&@TB#Bz)9%_Qo^gHf4JHD~{=>VC(>6hipY+ZowD{$6`BU3E;|D=pgOT!^`u8 zfXMcrYL6_Q0qEE(r!=Tjnvw>@y`9F4xxY0HUF4-*%Eb7Q{^-}Euxr!rX?WMRy0{dx ze={W@I?U|th$k=14Y?@>RPAUQcYFxy2W+|Z$hT8wfcbWL@xUSEw}TGWNi*s7UsMtX zH48)N!9p`m7dt;a0J?d^I#7%NnH&do2*NBy+P&uE7uRb5!0jk9vrUr>sCOO3=jXV% zi51%fyYAAZAiv+oe1nCzfZMa4$wL8MagU%Y1pb%XYiIHpe_$=(SpMo$<>g{X!Z5kl zHPq4Xxnfj_S3-6MBFrEuLsZ;RD!W$sWrF|+#+ttt(Ay%|P0h`*ltln7d>tGYcf#M6 zD;ac)-k!Oe4(5hYFCYdrEy7FvFH4_f`YLNbjNA3t(|M%-w06ip@3~2Ti7n-ad>fmJ z8-=+x&rN#QM@**IC>f1jkiraz%Q}|qg4{p2vsF3GheleiX=OYtwk%eZaP?d#zuW95 z{U}XVb;DipaOEZ<%xu}Jln=HH5Lb~;Kz~{n^ZI$NKw?Qq-z?Xa0rzfG8@dQe{{Mx; zmdOt@OB4@3)codumr=paaAh+JxbFa7;XmP@PIw03sX@0>Ad)CX8jAx^Y+BPNj+{l< za(#mOX7MwJiOAq)1YQQ1q5nnqj53I7G2H#15LXt+nQ9B_OWb((|AukpiwLfJtXqF= z;7oeX&+^Y&d7JX7>}g$B_+B zY)1dfh0rY3;Ka+?o1Rc9Eb@(kcDh9X01F{!5)oSgB;3lV`w{a$P&}{7T~Bz`xmP{* z%S(?S(K8@}14x~z%l{yT^~lH024&K=%s7ln0e86_pq!ln9tu=)hyDbJ^w_@df2EKD zGH}P`ATW3IV*uRRYVb&hZ1X&3b28eqpybF$J|1h&#du1<8{3y0%S{sI1fP~`4CdjgIBNlM9wg;3!DTNLlrQd3>b!()z1Nw*}s>JfNM{_6bqRbQ!G1q zTP65Bgr^opw0w^ocsa9lU<_XC(U1eE3O)s-H)CIddv**$NShj z@IJ06z8G?w^paS6#zwpkP(FUjR<8a)c+j_!StdDib^6;k6A@CP1URoPkDh=t580=zE@kS2}F zr2X%kJAm)JKHH=oXU_OC_T?ksC+jN%tmQj@k##TBaH=aONr)CK$qT3S8vx8DCc5{$ zKq+M7;-w?b2EAnsVW_>~y}#aEKX})rs%DXF(yOu!vrW4ME@UYBv1aBU62hMtnw<~B zm7IhbfmcMh(DBbZUz$wNIi_qy!0LEicQMiU9YjJ1;&wN!#}R^X&9E8f{h&bw1?KlC zH^{H;Oez_Q<0a5b=eldct93#zLroqQS5~ zyYZ^W@K(f=3GR0vMsb6;?@elbyvtQaS)xGu(nERt{BN|!%OOv}YefS1O1!^VHb~!k zG&|r~8$b2C7vSLTY)H=xf)Msl=`$pj2n;Qgh`#au^BOg>T$CTUJY{o6?j)~Z+hgcd zz?YS@kL1ZV!GPRkX6gMpz6^EcFrodiEH}#ZSMtr(q=^8SE6e*g;F6zkFa`ac&F?TC z6~Kwcz%O(7&4hsL&j^pN?RZY6z!3n@yxSl&Z#L1C2;emUsA*QAs+75K66&>s0e9g>#`9!)bX zjvG1xv{)_>aR-YJul~W4CyNI#p!+Ila6S4)ce;4ck>Gk#9%XTpZ32Mm$vW=Hrt=w^ zzgh5Ke`I+hULMTy_53%N1bfJ{W~Ie3@tgn@3IUD~AN2maWCmeLZ?L5A${o&Q=wPs9 zs6iGbfcgYWc6&zVn}H=o<>%zD%}GmNkiQssf`z+C>@pu5c510xM??VNv{74+T~NXs zye9&HJ0vGnc<^3eg0P!3Y2g>d7!m9x6aTl_CdcE~6U$ZJuf%qp3JEu%y)EzaRUVcM z;Jq(drfmZVUw~;Xb;8vrV0O*^b>)1~@wKN3Ifb|9%|NS$qAu_lXSH|X#$4?rh zkN#LxUiRiuaxmYH0@_$HEeOJ>byFC5c7^e5f!X=BD`sYeHu=tpS4_%(-bnDf6&y4p zCQp%tvG}-C+*CDJ6=_v|qgl0Z`?%gav%-O+sU3n$IUZ7JiS+F?7a{Epdvj4IF}(ZZ zVFcwlu8UyC;l6><-`5CW(qYrrDW;ny$;lpkl!z;TD#~&p^I9#Mj#h$U&(TrO8Q?ki z+JLtqtkFLC3GiF>4FZPr-b=$J=yc*kDYvVf)>MaF=9 zjf~HFJvR2XYv1pac3snM+;_LuqJZv|jMJ?x6{>f)IA;zlc(_|Ng_7q67C)jHj?{$MsTQWLM1Q{z1q!2 zm#7s{Zv+E>BSx|Ko?_eOr>Hu3AbgZ^lvHSN{y3&zAFJ~2p3AW!=-8Y>ccs(PXh zi;VKlaw_c8?Z~<;Q%(2AZ(rMu7!N2&AR`wkEFb3cWMhBX2crY%`sO)!{tKBitvRSY z#5PGYMi#qiaZ&IoCWH);Zr*1I{m3Z-RcJVRJZJb-=DTz5u#bl%4i&w{zMBQ$iynL8AlnM&(N7Hg4^Y9ebV9RB#ruxJbv`CrK+#KlF zJxthPwx`u~mrzzb9zFHsY1A&%hhbfBt(IEz|KsUQ=0};4qhtjG-^%3AL3>IE`?hqP zS4YO{ph@tS_?u|-Q0ytAMkbzk=gLkK0?Q1+Qb`hXWJji`d{SX!T6|&8C{JZN`ovg~(@uOH@HzV-a7bn4E3%c%m3d96qGl_$U zx(@x9$2!95i2aH4DvrZQJ5zB=xF+lXHhG1qe);5q=n$?GRgAbns!KY#*!Eu@T^+dR zk2l62+0tt}+{%mQzFkata9UPAnw@~wJ-?AB@x z+!XR)UP|>|md_Xgr@X)zv% zIM`ie0L3ct33y|>JFDurJ=V9Oo>cpA2cpH45~9H$hT+dejc)mr2xc>SUF+1u-X>=! z*<}rrO^g;XcguZ@fz309B|Ng%3bo4>W*ugWd@QL0PLkU@TuNZbw{Rx7Dp5DlN`jD2 zU!fiTG};UAh#Ok-aispM0+9SM2G~Jr>2;O$SN^JeZ%P>S@l7y3*iKHS#R1jCHSs>U zQT-iq=4v{W3WF)>1uk48{}>e^cC`_4LY!5$=# zNDgc|X2nu0>teYYSYo|e_Bnx2%Wx6mN+O^j1#E9#?uTLhv0pG_*fUn58RvhQkOMjR zhPZNM+y}_fffB|ca5r#ND7h+#ZzAcc$p(>wtku;9>BD;Ka3SS4<7vzf2+vnQaKazL zrw?U7_Gj!ec5_N$uVkOPL~h9~b;{HC-y3(T$7S}ZY`FfBVh@3(zSRpCMlly5sb)Ry zp)#ByQ7IKw80Cs!ETMUnMSsh{OpCjYL!n8~PXC2v$%V-76YV8G{Mc=GNHvWSyVjSw z*xmP>Ec()M(W$>I3%EO!3~1#Z*PpER&V0aHSYy9o*LJf?YTm3Dp)?R~hdu&po5(HRkKQwGWUNj{oc3}n~A|50j z(o21@pRI?*g_Zf|`%|k4Vod_qg{>ndi`EL+dNOVJMP;xxn7i1w*gi~y1HlLTwF=f6 zsZCl>89=0+{_gN$OJYPjPloC#aCR^JtIxAaVWnRzT^iQO@P;#y6%~@;1O>Qa>_v<@rT2d8 z4Oe$0zy39>5vIZDff0l35E8l;EZ;ysf$S+KJveFJsn-afH%gulV8EWiWafRb&NBny z#oueWcD#hugUy4lwzWsxQNuc83XQ~+6wZjv$stFP1ypi}g_8f%gkxioE{ClV))Xx#{eQV071YQo zQjv};F|EMUtzoF-=hGY+JIHa5A4HT26p=7AqNJ$W3gbIF2A~fVzmE-0SCCNcCIC< zo>OC=Hv(DcM7*MaKkT>OX#-INZ4I9$*(28`-bG5eS<5W5BUrgSL_Lh>@_>#bzBL^3 zkFX%c_0UK04tQ~N@igVdZb)~H!J>&N@)DRYs;H)d|JlQj^})Qwkh6Vw^wR;N+V`%= z2L!jr!3e)Gd)vU+O@tyKqf<2UIWKI4$Wzj!+yRHCw|mxPyiMAax-y}~3Y%)@%7$vp^m4r@&=L_A8} z!j@};<*vCmj*d84LhEr|bHNv%sG`$f{}0{X)Zi8|c0@ZQd|%{_G@GHY_7uiOH|dG& zj>wvn8KJOD$d35Rq=qBrk3yzoj8g$OJJHuS6S(=aA>Nv6eYKv#{CFY@v$p}_BfN;T zRZoSywBy@V6`ha&vCK2v7H%Eah1$L43`x@qei}jUmV0DJiHsb@P!|nR*iobqRm+W2 zo<<+^uBC=xH_RdT#K_AhdUJ!#Eur3yyP(Y>59G zEimz%^4%s7_4(s)Ur-EO)~O=w2?9P|ozXZRl+eboV#w~@^8N2@TN*Fb{}EIE0$j)i8wRgk98_yo4czTsOcBEbvMq z5e>nr7NiIyR9OZvQs98g63?7{ZnX2Z2_?5?luH+PrCFG%}U~3V&h;_KFj(g0WjZgHAEYwxRBuuFw zO7!fK^5uG7&7l1q{2V*=legO9gN6Qk~3!G%GOjxP?H&f@3 zr)ocvZyRi=0;U*u@`HltEYuLZlC(=t!~L+ekp$6-@O_*TI8|AEFC)$XcQNE`kMh54 z-iWJ0WtN8wn+;J9kTL3KJ%P=@WI!3OmpiSCnRp!BG!H}S zU7zb1ECN>Xyn_Toe}*pc0>6GVP6UwcE{+t(;{1-b>grRq4_^Op#8+FiA)X$u#Z^gE z4qdbbBQvsqdKX?T^T!Q$yEm7JJmZy&Ik%w;9mvnGoNypNxGnKz;!vWxhkUMQgt4ga z8vf|ox(mDBVym~yx7(UDW10m12^o8+u*rQ7gWO?fDvuGhLHBf%e@PF`f>yz2aAr(h zfM}!#x4-(_{r&Zg4E9Aa=_4gH+%xn}n*{BuL%T^)A^>~~Hkx;i!wmc>D3^f}qm$}M za>soxSaM(R?TtNq^dH6;7NYUlm#31W?3kD~XNRmmb1E)k_U2$cuFMcRDJ5N4_Y+wx zDK>|#KX1&z8XWDuUd$%~x0hQ3o%vaO4u&%lj>Zu)Iu+uqpD}aaqs!GhZx@H8E4u2+ zAN7w5in@R>Rrg_ovhwUXR zAa;r8EX}kkKT^kVe6;XC#Q44m-V9fY3&#;#LKn`$ILI0{&oXu5PNsvnYBSL90Z3)B z=`P%RR6*kvK`c;E$CDh42=|Xf`a_ToQYGKzFsDs|Z4$!7N}zV% ztnV_X6NJnQzD+ViTt+fMRJt1A(v1c-E`vg7IEVkjC+}fL=aJCibxQfg&SU@bM~(;!`{cVs=EoR$l?`4M*mz9Zx7y%3agjKySO2W7 zkH&^$y0IsF4DfPL*z!YIlw|49l7^**_{0!%1F#7iTy9iyY@5ev2Buw)L#CRS-ZceM zZW@T}_oq$Ine%-Kvbp(~-sdvNdt_C>NH7)H57^ggEZdKaV`x;!H!38A)c$edvu0r{ zBzn2??=HJr0ln>n8N9Yo->k2;GGs*fv1}t1gjf4>- zLE12*T-cqbCHL38PokuXwaKQRqe2nWu#Z~N{Muh{nwgLKEfdk~7`6`h*2jOcKKxHG zhh2bwgu8j#eO^e~Z6w51eE4W60Xo7S(G0A5Dcs(qf3Usc=#p}&;hEG@C>i&Skc`W6 zf?4IdGBat0zc$q4BU)q9N@HJnMAgjad-zoEd1|2BMBIP<9h;f(ftI;IRb*cMw}#MZ&SDc^U~|j8$w)|^#!H~ zm(YzKjN(oi5iJj3W!DEe2W={$1Wv77fJcx!V^&*`q;Dnv>2ats8l)fysu%hrZi-jO z)8NhVSqD){7OELur28({opkrvg@$2}t%JV^=<>D)!S=ODqHLRlxrHR9cD5yxI^=rtdef@QozpdUI4&S`q^(K zOu!bn)L>I2g2aM(#hx?jnIg;7-(J9OYZc8n*;ATeCCEFZG(DAs&axmV;-ZgdBw~GA zt{^LBZ1#mkx(0t!0IdEs991wm@L)VB@O>P;oGP@RQrsM_5EOh~2y>2n0-Q7mGs|U+ zx@Z+@buaz-P$W>1w~NUk8ej{^gkq7@rX=8vWF)W5`i?4>!d|&rkPl0N-3`DL*_)Bb zxK*zFYRqT_3Xp=r^X1TAW2*0lwJ`L|_hCugxsF~(I@sW6fJ?Tu>iUASU>V$B5u8(R zU}73>`^Mz+A*(82a<0=4V+j{7MWzk!AQ2+(M^Xa~S0t?PIpTJ(KQ`!z+I9BU9}Hk` zS->59-CXckF5BtRy8D^clI8)U(pMw6o!RAa=El|y#rX?OoPYNca}>*n@yDoPU;m)5 zd;ntSw{W064pEUt@mnn9f`UyaV;I}@$=<4(=0SfZ$RJG?Fvo$t=pV*fmcATC$z?BP ztzVxOdm<&rc(g?&g;V7=p&+1d*qQO$Y>+QmC&|$XEdC~ zDrZ>p1d#f-Cq})2#lvzU?^7#tpPLt3X3l8as0%!$5caKTFpKF`9nK3k_G}ZsI7?E& z8TysrT;?KjHC`i2Z@%ic;apz4xV?Cje!CLbwgFhkxLmY-XqWLb&JS~s{l~{P+PrOd z>jX#oW#45KLv{*?tsnj7@uiXS&s&^+6yv_Y1s8{PKS>RHc!*O-=i`azfMiUAG@Dm% zy7Yg4`CZW;B(@yfb?-wyIkOp-AC^RtBrl(>j(#N)y|5=6RmgE>y!wz>yI*P0O$N66 z3(E$(+ACT=J|J0Z1zo-_?ATBo z&1Rm`!bhxDd|MfAikt_SFpcZ(y;Xk*`?V04jB`UV@Ul;g#K}V*uoD(EHZTPFsHD;@ zrPxsPa42D8$@-C}r{wkJsuHw;g^bmyo(1esgt+|O&`DoV>g#sJgQKWhaSAQr)K^xp>5~LjakxW_4f`P9eNv~>F&@8lgOYBtH|k_svTc_ z4pE(wp_*urYLG>$3j1@|L>K-efLoNW1*-cZ@5kX{aqYO1xLxOvVDj2ofpxN=NAIT6 z4Hl3bYq~w9KdXFhMfH{Y#X|Rol7ua9s0~bpT0VI7wJ~bme{9V_usv)(_{!@eHkoZr zYoD9ger_1L#PwFha0i+-QPoSN6fbUF`IWN6Dv|BV&WG2-JlwfYp9abRgdYrAD!u*k zxO*yET>;?U3Sk?>?{d3k?5|$7dJAX0b)nE90ForgrKwqc=X;z>M7QPi=;KOroq}^lRY{~h2F$Vtomen*NA-@&sn<5kgcdLJ> z#rwte@2XcEfIOOFZ(-uF)n0ygU+^SVfz?#9))n|o{R7Jcjtx5JLZZ96$_z{ugE?O9 z86Lg0dcFTrx5A-4a^(cHTy_sW2j-vj%QaRx5+;16)wG&sB9%N$v8yg{zUxcy>Z8_t z5I4~-vR&!E=x6fVh?T2xty@Me`8kZI)K53wI+@0;(1YWc`jZLT8M z7G%t_h1zJx1;Iy<7oFy9WQxL8lDm+(96qe>qvTD=t03x|rL}zSpkzXjwZ}{+?$0*0 z@iLB_yI+KQWLmJ)*h2UM*hz7$3RdlXC&Ej@l|dFNqt4NT#60ET;p*(hA~`F-L7T-; znJx1aHB9T9p)PPY+ocyTgA#Wky}1zMQDtJKCXwdWQxp6WVa26-@97IE-ahzcAi$;{ zGx3A;{7PFsB%d4hreda45VCUKjo0Vbpgt#9!SA zd&Z9z*w;qO#@*ek>VUh^9k4o*Jd#8rD{_;6cG1b0#5%~&$W<_M>8Tnv|9$hT;OQ=G zgCImOVB7Z@!(QFtSO036M$DI^4!~;hK*);+~L)NXfA!HnhhB`m zhzoxl%@@2qy_Y`RF^FKevgoK9xv>Sf&-=1~*u{e?2p0*gjf9j)jpVKa7w36MbX6J6 znSF)krxYUOAOeseGIR&nfWPR9Zn}|h43PbrxJ4+?i^+_Gbeo70?L@N0c|#1%yHqIV z0oMf7cRjgwvIuL->$ihbwd+athr?U$>22hF$-78CH~8b_fw@8@BSc6fNDmRu#Iw8_ zWz^>?gL&Gl*rTO-6w3@)p%j)5S_VJQ;O)Y=Wid`CI^GK0mL6s`e*+Zu8nS@sdtxP; z4C_YMK{ldA>?}{C4NH_qj9o==R9vEMRO0VV-_gK8oS1 zFl7PdC=Uos_+!ptCvw;dbcv`s&;g+!Yz5A#m*G1O+$x8~3m1l8J>t+LMj(Ye?6(hp(ME4T6_qh?T)Y<4dh#_L zwfp`)?2hz%XEP$;OIeBidYq>=yAhtvIR+_zm@;gs+;oMl>ly8&k5hVCLj+=UXY5U zndG@yzNx~BATtO95$Z1zGki_P-Nrq4-XHR{J9|B`kCW7mG&HApfqUqTQ8Yn}-sGZO zH75CMBeLZny%KX4`x<+{6^r`-;40@z_v+e4SX4GD0X7z1v;CsQ^J z^8b-EVX~`x@Pb5{h{<4nr7(N=Z;c2Za}i-bG{>)LE^sSyrsCuE9C($7Q$bzBLCC3w z2eo#zJAR{+<&`buywt+8^-sDr%CJji7iGvnJhGXIdkM0E3Ao%>@m9bZU+Zof63oIS zAuc0CG>^QRAgR>*@G|9ZG+M5T=H#kuyVsUt6I$T<$%E3v@v4&NFVlKcn5Latu zB=F`Kme8=G!{p0g4mT&Qu6cWL2*j%KYFwrk;iyzmIG=JJ7YNcLwH; zgi<>W+Gr(w1VdjrYBcN;#2KvIG>iVZqz_4~drN4ur5Bo&?y&1+JOa_A5&Yrok9W(q zcs?AP4$E8JzZs2I6|RNh=Y{b!BT~x#h)u1N>b_`enKkwUNd`fS=o0gih^P$+S_Hvo zAiKP*uq}d@MlYydzoH1(;U;#|U*6OzoDGr7-w;X!1SSGvQb2A?n}^&HUxE+agWSx6 z=q$z=dmJQjgfe+JQjm1nqMuT=)!aP@U#c)d$uMSxc1;D9&H+#`;0l865c9zjiI>m3 zgVYspR3-7+cnNf*s<+e8Ehdn_oLF&O1!hYMD~~lY@(F@fUbP-r|C{ydfnm*uuK1(X z(6`H9o4SsK(h4(Aa35&Mr-arHDU!kSOyF2t!7u|%i8Kv@oX=5I;XENnWgP=>Yni0@ zGX3jDHBd8!%zd*BJ6l37j8wRa-=L+-^7&Q@S8*V)~xko|10a>vf;Z(o5v8Fr|Sfw$vu0PryB#zmAvPXM(7gxrK!5&w?R z%Y(dwE62%(+biJ(Eg+#gMAiYTkKIl_@RA5k6T?wbrlLAY z*R&EQL8E;EqK9MBAvikNdinh8aPa6_2t9U(jM?!s4@0I6T>s;4VR&UA)8>rP=k2mdrF%b0JXME*)M=tAB=8x_{A|oPbZ&_W2 zKM8ghp~+$eH!p|1hBUzOgb8*-v=Unoxab7zgMip^(Vx-1y4m3fwco6^mj6xeY~P|< z^`l68)l{V=PWRI;F?RG{T&U-Y3qiRAsTcN|S?%0sLok$L-4GXMESKE4A;=~U08V!! z%2AV2-sB1n6)dyI7t{HZ4qVN6c7+>UjS)ReEm>=o{P}lRr6`Z;|CvR#4$GQp^sgcB zAW1e#_LG|EP07E*gcfWkECY=(ct+N`eiW!xfWb*`AT}-y)&(D?00>{LW`1ef8B>LM zzl#4sr-~aCfmAjf$-vuF5QbRvaMuScfl{LvcmDS;kbt>Wfl1hQ6@-M=qa6OGrECdA zMP>gEMg)WP4IJdk#AIWi6!*t@`Uw{g%$OEQU^F;p59 zK{{b?KI;HEa=IvvivRJ#$Td74HwS>G7hwpe|JxnS$9RDZcc2I~D}sN5KinBW*L&^v zAAq9%y6}#%`KW%>Mr5_sqsv#?3Gn|Z3>qmGNQkg9vHq;BnYTg(Yqf&8WH~`Or;8nZ zrx(x_cRr>UL-I|Xuj1EpP4qgj^^K?C1|O0cF?dUI(ho56X#Dzf(UHBlZDB2FK9?t2 z2D6|w`6s_wQVdf_Qq~g>-{$=rjIyvO;M=hCPye7HinjrKTg1W?8;{O(g@0K?2!a3Z zF+d2#`0Z{eSHrOm%#XW-{oennNQ9@H-o^TPp9Sd4zPRYirjSe0)5P!Hh*=GA1P_c# zL{cHnE&*!GtG~sen?O0CFcNTmgf3hb3V4$57(u;?gKe$B$8rn57;8NJ1I?wI|00mv zENOuDnS{j{+<`Ze&Yla!yGk}v4YIQ7m-Ba1hyh0lt%LML_G3H2e;8bJr{XYAF|`=y z;18FUzBv)F0XtK;@)fiw&5IG?d1fIM+Ozf-BZGfp>YxTX5anNg?P&*IbNsdo5KXoz zf*I=-JQ5L`z~@l~1i*lI0zaU2x$I&~{7Vcp)4E$wW*|9HnNv=eu7o>=mRtrVmM|Iv z(8Vfg1R*RI=8n+W^$U~^arwvTfy~h3fE;)qVO>kZy6}f9;EVP#@idX1*IWsBbB42tK~WUCz2;IE~L@@U;TM0C&lg#VEh4-lD)Vv*MA2 z!ss|bI&6g`J;T8C(<3&VNd^!DQfKYUE@D6stQQLx-et|j&;B2ms=ESXh~ii9ftM@& zWk!RC>%<=tZ10l$G$v7M;s+^-j*Zkrr2Pkx7ote~jhZy{>1kGTd_Yzd3i*GYqZ=U# zFUvzx-zs9eEM?~YAM~`1#+=-nG;zW*9ZG!wQFcHAXhYP{vyM}Vw7s&jC5Sg-skJa- zKA?0lea3hTuqpsG6@srDH%IA?CptYIX{$h<&n;@lbdmd_(M5pA!^!zKV}jcOQbZ#* z6}~mt3H8+%t?c6LJpzqPAfiL^WY~eoQyJyLy8grJ2G#|g9lpf!eHLloe3xdqkyETS4Evk_D}&_gTF82 zDlnWXqDLjWJ6FH97tsg0SxASpW^D;A1J+53lEtHi516ZeLo7=Y!U3y7;JXGU z9c3d}CA^Rb6~*M%xWHkl2yemtlRKl9-a8x1w6y()?BlhkmCfZrk~RLVF%<<>C|?P{ z=MTmyFAV-eR0U~B`$+Y;3PxpK?3*Aml-kn(k%0P7G< zh2PGLr?a~J(oz87_0Q@AWX)7?CQV{j!*0UV3z!zKIGs>^ZPVgjAW1x%>nq?JeS{z# zXC~f`O=!Bv0qjU7^$yK37`51IrDJZ0D$a4j6d(t+)$fRce1ZBO?W%+`uHjs(G>!6M=+TFhlqMx8EqMCh7}G|qrR%({$B{|E=H~PR}BlU z^z^CPL2WFkzCDmACoL!1KmiIU3G`SB2!I)3b#F*6(GQhO05g4tZ4J-FIznjV3DBwc zJy`mGAQDIqp3`kR-MWFD{|5oN;~t^cCSyB0CdEMSaw?jW zaw^u^FEd&TJkWxcIq|g5^ds}5HR8Qv$bllUop5p9q}2qD{ii%@071e5 z4ORW2uh6G%fa_ZaHO&)sqya7c+HDpBytNyr_k>eJLJ#gUR1v$lUoZ9i;B~w+b&9SJ zcKk0pLE{=rKY6+iTmVjoxuZ(NregODMrI+Ob0ln6IWRi44w`=B7;gU<ra#u%_+J zXTNZ)+vBPDt0Y4q{Q_GK8l$f^Ge<{?R*3xU#Rz9RC^!lzPQ})GmNG!CaNBCk&Z6Rp zA0`ST5zT}-gB$3)62X z#JFDtyIS2b7$pojrXSNVg5P_i63R3`Y`PBs2y{ePHWEH^ocG>ODdYJs8mQtQ1Xk<3 z+=IFZa~jj!)6+YWSIlzgfiy3hPP>>33tx_S%>!A~?J|_3l850EN9{xJ97uXHko0Uy zKkK7k6YTsEyk@Axy;OwxKQbc7qTs6o_F|wyv|0n=xa`z7|1&ferb9~yb0l z2y)lkv_WD3uCNfYf3F~{6Lz6z=&#HA;0ph4pQ*B!8 z4Dc@kBT4#Dp#md>;up{uSquRt5c5e^a#9I;%Nnv2M;sA@O}qq^NSL=!nUxA&e~lNl zdLi^4{$2?|)Hs=>#B}9+w0?~cWPO0KwvI7eEd4zX-6hV{ii>)mvdvM1Eud5MWOUZ{q zVw3_)sSpts6D&~&s#?0^Fq;@S#IX>)oRkB;A@Bl#fobRfv=s!+5k}vw4+9^yMSq!i z`o|tG+MrhcKSR6fb?xCym4JQURN2?goq8ybnRL1SPBT)sVlA z1V{u%h+#6xR=|{OO@~a;ee$z0FvX~~zozJz26l1&dM-HJ3rOXkp{DH{ka#M=gFZ{cson>yu7ka2I+8+v?KO|1%qc$6O#3tc5~+7VbE0bUhw3 z^Hv5HQP1BcQ0&Xef{%KI#mdQDyx{KFIDz&{FeJfT!FIuvXg_1=_qmieLBMDGoZkR+ zqsg+S7)eEqh89Bpb*gG(VDcqYUNVqB9wL5XEjOI|Zs#z91p{Bsa(CXi=ZH{l<#OnT zb_rGp(y@uK42tSR2ZW%3KX&qYvlc_GRZXrXOh4(}N5pq~k3{u6OOdypmsw=>F0jW! z4Jxn7c=hCs?z{KKXs#T$pu8&g;djR*-fw^2^9g~J1)2y?!iiJspY+Il?_YYKAlIi? zBH|31$XD_{zR^!vX`1)nuZe}w6=oli;GA>BvtdUmSK<#gm<#lIg%oy2mTqL0Jl4-# zd~>uijaHH~1UeGo6V@AjIm2hJHQn5&Y7fxDZE$+l!PlSXrE z1525+tJN*`%al0@?zf-+kf8qX`9Z-=uFT*Oo7Cqi`Dr0``RSpbxKe^!W@FVlMn2E1 z&_Pwq?N(F`fmzVD0CUhD<$AyYZCUF!nNZ6aJS&hXC`qx#`1Eb=SJxf8<5FM6)7+6K z&_@{1?9Rg;kFJl@t9wwIPnM?K;bpufJ`;cI`iJ;ibY|bSH;i_fSyIB^Xa}oOgXv>Z-UL5sRnN7>#7nEG zWL==c!H>t_Iw7d>?MuboydJ7Mg`z(#=voJmwUUELs&Z>cC%10Ap;=JHTdz*`Ux?72uyw^^#t%bz^?_OA;u~)LzCYi+h zN~8iEKoRk})A!9A^dYJcB&2L6bbZmUXFGv5m!@lduWy@Tx15{&NF+pOWw~`3Fn;gJ z${xMCdzqr@%>6Y~e{rlB(tm_9zCG(A>AYw{xj|c28{%4w*1Jx*(C?Zy8f6ePUc9D*WoRo!BJWv{j z2xhlfYO@;2BiITkA&@5y#h5D}#4E>SwOZH{zdKS<7Y=&funGT zjDx$GTUQOR-LANZl#{s7xLEaDNSK05Rtgxjflzm&>_Qj!(e*lydIjZ8mR1M0DP2@T zi(7G8%<$2XZ2iBfQ=;>zQ^l59>9e31g^EEkd7nI73s}ipXIm>Sui;~#v)v*BO>kd+ z;e*uGf|Z4D`!L>w=IrZs=`lO#8FYwhf93dEe)pHq&oe=d~oO@zb7y z@AkVxEflUNWj|=Pbf_$@;b0S7*?>+HV4r5j?*052C_M`i5L^)4fKCW*+D-}60o|4` z`G&Ezj+OU_Q1X{NcWD``2BEUuG~e?ss_-QL$dr8*PEGxf|h08BNaD zkuM_+eHRgToXr}vTUE#-8#6Qraxv)J2@0>A zRck8cxveiA7cViH`()jo1voE;?z(MmO`NDR6+8XBD-o4#W3oC0X@PFcH&GSmTtfyB}m-Z=#W!Ml%eO|rd)~%2} zsa~(5?tg|0wuKkbaMw##0YrM+*Zp?Za+|>_@(xSEo~6JquNh58(BlZ0ELz>zJq}23 zHGx0)kdjo8`}NE8ixsHG?aoKp)V$)l-?ZOEN&fl{Z-QFUl!2G*N|(}E%IAn^b@VMT zdtDwtI3wKQSvJ>=pMc`F5|0RBL7(4?&PwF8+Xh{de1i5KZ9h1u=p{Tjq=hm7c%V(+ z8nwr(`YcIFzvp$_K&15o%!9xnpR#^0rpsqBn#7bcr(D$0g5a{@bVSaRJ(yM`Q9-7a zFI83mOixSZo-2j8CznXZKfHfg)o1lB7Ty(Lo~w9D~@Fp;Q}KgIzBF6pK+OYF$=w_SRjHbh}+pj zi22jqK)KTszU$YEG%^~`gV%rV)<7bqy-ef38YAlQILeE&+&?Qu?t6y~{q2-Tqu3%e z?=FPCO5wdY!FBjkqpgr#TFro$^6aYHTwNORVd^D)3KLijBoS-V6k#+bQlxizqU_AWMrKg zrD7uqVqq}iAt{aLd6mprmc&G64$-SeF$8l)<{P`x0z=`OWu*qv^Unlm5;NTeKpI%{$p#qtX+ zwDABffMxp5JUa7S|E53P^KEan)sEIpTn#uw5{B&O`g`4{a)yRQ-|Y(0{HYSt9PS;( zNIYnH2|>Qw*&PV8pkvN`Sugf={}bK>_1_b`>=JU0r}Agb9SLD1j@xWY@jyg)Bd43@CA3ku2d1030SEyh+dZDBnLKW`Kr0=la~zf&X`38XR=e_ zO%bY$nF|mfuP~iKA~*XrjVVT7ExpD0d^l(j0=HJG)7{HnBe3hG#Rf&Q12W&o`6c!n zQ=WZBL4i{SZ+t(C-e>j6IV{1MFQGq+q47-W=WkXgOa1Jp2-kwmdzfC=XySwFrVAya!*sr|K?+R2l*&~**l+b_~ zYy}Nn=vX!k&6UhaU9_-{stB=F`7Uma#xGsQpF>}IZ`^{Dw_mH%y2}NUf*YQGk;`aa zg}QUz_D+D|s$NX-XD}nL6m)6#Xxb@y{Fo?qmF7=!DYJ%AXxuK3s2tFLJw{1rvOjW) zn*F`>OXp*f`{(0EIbZToBiOG={gA?Xjncwv~ETD!Hy49;{X9yt4$ASv`h&e0E~2eKSx$?`Yph3 zD&`wC()~jP+EX*%?dN5jx~4V$vgA;Y(k?vW1$*m#kBzYT=E?fW)tg{rdnJ91Ac}MF zXW!p5b5tmd$RZ-IIU^zw&eQ@5+n^dvBs3**#9zTWDgB)*ZT=2<;=*eA?GY4d+Al=( z5_HIKe|;wL2pgR7dT_GJwIX_NYOLsvL9)}b!J=p1!XgB15-H&YNqQh-Fp^1w{_8@PY)%612MRq9>R}+ zI%A;oVnDyNqB_dg^R*~Bcqtck*?0}XfMCb~o7Pfh7lEP$AcNsh8Ynj0)&;kwetk2) z1l5U*p}B5LGF>gl7*L2sHU3)UP+8B0U$G6c#OJIVo7C%~E6+5FKuGQMczuzVb}qoe zjyjlJw0f_THRbR9o0`~3i&$1jO!17khZ!vW?tncQLM#%j^}O$-uMfWFgJp3rcHwni zm#!nt*|*NTpr#CZgIl36|BSLtk|h*-u5?N4|M5e2ixGJvo3cwJWo*btjEnX@EJSfKp6iyz!g z1RE4-WtdMGIk0@DuzIQEOD{BG?zA2S%ZHi~7a@ofI>F-&h4UzZm9qK9^asX(MBmvN z`!J_7}AOba>S6)AhSM%Tt+Lli3@lX6AS8ur`e1_w2MT081jZSb{Jx zdGdgyC&EJ7o6n3tPE`B~?M(!l5nyl0#`sbWNjY-EvE|#IxzW~z=lE2<%Z)6OFfV{ zmC(Cra8^ohT)7meyz{ZIXcn>dU2?=WeYt3R31FZt7SgAhI@6DNHzH+_j!_+?P)Odk z{hw~a;xaU-z~}jKiaq}O(W4LX6xvDXx6l(@=8naLXSKGivJ0LH&JY_d#-Wwg1hx09 zjdjtSV6D>&9f%qG*DO(&5t)h4$$s@eyl6JYVs`ArLTSI*#+#1wk*=!|#XIrZ1mbAL_hPtuO)E35Bk}2j^wVLYNul zaqs|Zz%|9!Ob{@s16~GUgRZ2(j@4)F3-S!C{4zlB>i>{uDpBtTp6yG6tzH|c+m?Kp z5P(b51DIZ%^&-f}L+1~@)Ov5nH)xHXGL^9Xss6O;%9=E-gFq!CV?vC$+S~@DB`NId zMH%B;!%zLscDeMgAB^NIUy^D{C%3+bK(}D_3al+ z@kl)!qls3!2q^DQCiP@CPNv@8_Hmx8Gy5#-ol7XEuL5>h9Pv%l1$(+Wd?4uqHmQl; zgCS?MS)DXZ7Z@v!6RzN=6PV&SzBqQg#R4&4tQ>p(XM~`~j;VJV{Gu;u8^&WjueR+p z*m;BDg3_CP#brSB=jIP?55>}AdMmdX26se63A^XDA@o0hnHhOBSn1=e9lPM*$*t@$tUW^XMj2gqm51lOFM z++{ziA;A`5yExyDY*0wxN#=_|B9L&@d0ntUXB>=BZ(h0u`0Id(Ag|VsU5bk%4v`_U z=Yc$S#tS*sjQ^S70@Ggro8?F|HXuuNG*RcSVvcqnV1>7fd=bZf&gO!2v28@Py6e0Y0pq>y5|ks!_WtEl*mTUewZ$~B2+E7$ z4?R`|IFJqGGcaIt+o~Y>pUyeT;WD=?@0V-5nm(hp?T7&d?_@qV!^H_q=8qB1fd_2+ zGJcE+>RoB!r9nTUczJGRj^l;M;{NGSP&uP zwV@dDkjGDggX152J$vlsb@RBuL^00kO`dd~391K0nn$*Go-REl;a>%>s;Zg_oa*z- z?CN^>r&Frc&&Jbp&2QlFy3IA^DQeM9dhU#ryp+%8{ZcoAkvA}Uh@zm(ur6(8n1X_o zUs8rI=9}wqERr9UjTamZJ43R3OkJkhcqSMJ!#@y0w;PLxdcmr(g-3&Qgx$zl;6h}> z1k>)YXxOdougMHy&_TDXA!@^HWfV{GUUVh0zk=a|Pu6P(DJhjvi94G__DH9@Sq6uA6K+O(= z|MW{p;jfdh<1kl4_`p{X8#j8xTsmVE25rETXR&VZ1UJKeAr|1~%yzK&QPh!K_ys1IQd1qNLTG$! zuoB9h+EV*W)SVZ47Yke)-%;i&?zznS=wMs8*&eT$Pe#FPIWxX8EBvPRiCFM6SgKkM zb9yQgTsRbC1&fBKBje~XvKFl1(`Y1jY1tZv3K9tlp`$gu6CEJw5s+o2 zk~~_}XC)<>^CCC{??%sd8*J`)Fdarb{K@A?9l8V z?Qf9I2T<7j(f@lNndJ2W=U$FdHK@j{<$KoZblP1H)KhkhRU>Q9P{b`OQps zA++Foc*Qa@wAxv+s@1kvU+(gYojt#IlAjf*&640LdV8rn2yR|AMZpRKT6oKGn-;_N z8T}lVsFEj|4w>&Bvd3Yaz14n%)M;3b4QE>P!c=v2Bg`wXbEKiWoh|?`ZG3xbGt6O5 zjz-Ii7jB>UU{+#(6`(Tp^E6WvzCpT!;d?*9WNsI^&aRtrGzBzIKD;up*O|=l1bK^P z<5F-bEYU$CPBZzX-i4x*k)E(_T=T2JymQKsO366N8Bu|-Rk%O>hl=404Cp=H0K z4$LpJDW|ilU05J_ge0(Dw*EMF@ExI=+2@6wdhly~$sF>!0dY^?LT(j|bX1-`aaMC# zSt3SaV{5MKdD<({H)t82CZX5_=s)_wdf-Njt=u$HKr7gI*RX^Ywv~v9y^Jwjjo?wu z%@Det>OI=4-zr&=BNCT=*CH>(QTYE;9A`!JSt_Ku)LMp@@PEB`{#u+K2HnLC3_Wsf z{G2gOTOcl{e_v3PvqqwGj)sHu6VeyHKr+n*Q(^NFB-VzWU5K5O8N&q2f+gc+OSwY9 z2ZhA*&u%W@z`BivIl+tSDCHpK0rwGcxj2(Voi${BW%$+Ki}*pBVbX{JGMrY=UN7u) zR>oSxMJg+GtOz^{ zG)3SF@~xE8tk3h4wqgHR=mg$Bq2m$@i~G9BtJu-cK}XAm$m=i~xVe0=X&|N46HYjJ zIUPD4wukUG>HYe(%bxfvGw?(cvhJLRN)g1_q`@V%Tbcbwmo5U>5Og033&fD!9q@pc zP9D$DisL7vvZtPR-a)*6Fr&7q$D&>M^-5ni&tJoMuW3GT_`QB}UkJ}PrZAvK^X}a0 zrW6yt_cP2gNF&*Sly2H z?*{>g6GU+HiibJ>JE$W;_>x$QVIfjKRWPu+@p2%PRto#C!`9(5v^G-A2TBtmv(?tW z>f!kL`Nyas?z0W*7kvn%Ls7wEo39CmU&PG~@@_2ix*zYvf-7bigI8}t8bxKZ1#`mI zY#-k5e@TpQlmL5*M`c0_#dQ{)mHxJH64Kck8gBvBDDFKDw}POv6RAX@BPKPH^{&s$ zhMRX@)poyX6;2d7VXAXFjVT`4W^?F&-}9^ucW-(FeecG>UejBnlKXSlsXB7c>;2(Q zll9McXscMg8VUcS0oh>QcPoAla{=g?Q_$JjM|WRQ zs&09GGwGG`ovlKi`5iHz`Rx%^Saj@+&~OSLnl`(AQD>0$Cu+TSe#s+kNM-G$p~7ND z{lsHg$m&xj?OnH?y9^uK=q%R*3>k;NG`z>qQtHbz{p?xuak9JvTrgcKHfZfEvZd{K zdSZWSckz@Wb6W7!2N5phtV$|eq@BXE+x9K#(M^mpLN`X?E0q_H@OZnA6eXkpBvate$M>`l8y&_MVDzwjrW|8fDe$s_!M#VgCrx_m~lD%QIkTFny9 zYt0-l&bpcp5O)t)F#9LdV7G9iI|Za!?J7}Ok$yK?`wyIcaodO!iysy zy`#9S6;o+9t4fSXo;i)<7Zwv=aogCuQ*^hKg?Mf3S>q*>oOz710JZf%jAn&P|F!rs zUHZ1p{_6Cg$J`c>@6*WdU4fJ1r2!v{me4r;h_K`RHBm=SL5Z(ShYmV#2!Cgp|7+u{ zO|tSlB9&`K8vg~WyRZMml7m%mg22~5F@@;xeR^~Xg_*w~{Wafga@u%-+r-oZ_mr!M zKMz*w5&oWRe?f88>A;(V@KzYJS4j+Sl7O`H(~p#{YeP~6K@R)H3SZ}L3M`k6#GD=u zh&jqP>E};;ZR(5vXgx4+cms0@;Q{YMxKN@DV0GgQZC5Vx94)d3VU%;p6oj$v;T6r{Osw!xq~mLAd- ze^d5pcWT(|krX;;{Vei|SlX|JoZJU57rwRb)VbIYhKhoiMuo>QvZ0`mR%} zPW$H8tCxzQE~Q(amnC#KOmx0$gwj3aO;T75t0CS0TtCC`$t!KD;aplg%a{euv~bV* zx$`M{sj2sm^NUB=8x5WsgRWI*)eEemWKIzb>Ja2@L3`8bVjvYcMV1!^e^f4i)r8&J zd{IvmMnVDMk4O#HkPfPLWiO8CB^z3b*)o%$w|AR%n4UW}Y|X4o2R)c5qb7n#8wiW2kOAZm6yA=>_k+iqy`@Gq!>+xp#?6J? z#)aG#9nYT%(AQd=ffN3#k1bjr_nv+@#qB0rkR(}?sAT5-SvY6qH#>SsCh!D_Pu{6^ zf{YoM?291X`!Uw+AwhS?xtOkavVq`}-j&$+0Q-4`^8jypdaXVY-rB*p=n8B*&8|s2KXa#cZlM$!3iMi^RCw==KTfwj(}wGP zx|KELbUH852 zDU-TQGc$R5IZb^`bM9<&xM~r)q1Ji4zNq*9_$)kvO2GK-+2rv)vz6v=$xxc}35&}D zhg(P0YXoFW@_}#K=_;vq!=1bvL=21Rax_&7{7jTp)r#gIy}s`0_}}?=GK{2uSuo8WN_eytm*30S9lRt zg3|O)@jUO#9%&(dT!Bpf=8N=tt9+b@)9cRE;WfqsXMSM?+F5J{D;wvNFJB^oM6Qp4 zxcQ{+SM%>5SA4eS@LyvqgD3j9FFjuJfqn2s{rJ&rB5xI`sCD;%uY!U?cLHx6IsMQh zUFv&BH|;j4YlD6U`roQ^(UyF zp~X0^pa<7%e+agt52J2QSuB1W=}u)AqN`g89r@PzV6~@XNm$Ii)|A_z5V0g-pG&+N zzPB93Xd?cWAMVy|Tz=!`4&!yfP;Lcc%dQ}4Jo!?ih+gV}ceQzZ`f3e5H7*Ajr()yl zBr2`^u|G17%$M`UXr$`Hw?x&}5DT*2)xE}y=Z7r5UiM)$WvwykK5}YZnNMVmH5NI- zvk2#Jwbj4C%19xe+ayY<%GI22;l5oT>b}ykZJq7cq`EZ4RVIguqA4}o!5>?CNceWX z)$EAE?RNsvlgyFWUh21Egts!NG>yGJ^nJ>$u$xdfZQhEcyDhm-BTT@|rM;MrqA}!2 zp+1*ACSmUiiq-3i%{JTrQ5Y4fgJKDJ`2efY;@c>t;8kit1GqbxD$qfQn3WzmtI@c`1T|v;gczcia52y9H%OYZL}E;SF8T? zMV??s<|tI{G2vTOTO_w(LL z&KYialxxf-WttRVQsFDR+xYTS;50lsKqt}&ry+1xQPWN&r!4}TM@pTYg9Bjcr4 z4NY}2BdcQ)Wo>m+J0CyI#xxFEd_w5u+Lz$i4puiuqVnw~w&M61#FoWWqJ3YE^n9oL zDHxe1QTqD9cFa<Ma8JbiZW)!>tv1K7GuQt zp?hv%mG1%frkbGB=hSP2z0{?sM23`2kKVGYHlwD+W)1iA{tUzmP&|+44QY&CLbcQdbHV(+ zw5UgEwX;D_@~&~l$$EM2(2P%Xv{J}tp<1<0zt8yY$&7qyv1!74kkB@Bbt(5s?x+Uswr>Zlz_mA=-lXQ+m!|To03Z=T!+jd1`B4k9aB(6U}CuSID-^J}hS8>%^ z9~$$gH1FFIt0s&2h>MqvML8IjzMBbk{|UeM;#z}MzyE`P>w6s)ZC$rp?E*No zQ;1~8_C9vFQ{Q=q$3x4yw=&}1mnuW3@kj8(@5rQ#jBhqtv7WM{g#kHZ*=tW+kJ|0# zTW+4XTKFd~C|z@!!1E#e^R&1BSqVdrp*voX*%f6{9ZV=0MY(0KR=Tu@R?Wam zQNk_C6QbR;@B24cD5$1(i+)&st$UTReM5S))hGNxC<2WaF(2tD@40~&3&OrmR`kW81=g0kx)taQXuzEl0o5Au3GGu_nz*%bYD{l z*N2_kJ2#%S4NVqE;mHK-(eROICo-R}0l48;hl3Z3>|?#nh>`K3PpA`w#R%crLYT{3 z4t{uB0*O&7iSZj2YwO#O76q_tI5v1lrHD7^58~}3`TfO;){CA8pR)vO$oSU!P@$Z7 z>0Kt?n`Fp7<7zl<)trmmxn@>O^3}tjr8q6(F_mD_@j3JD!Ly0}C$6*GZV~d%zs2V@ zy`(lACf=r!^M_iC#NyHJwCR2ty`4Kge{eV`_ucN?kKV+j-4t7v=pYg0lDaqa6pwl!OLcbix8gmC=8CIh zN_pfB_lUplibEhg{QFD5tbhG5{Jy#}8&x9d%$Csnm-m63^P|}FTx?1BqDj9pyx0vQ zjRz1ci10(cx^SZcrd9f)n)tROJ9){kaKDcxu7ZZ7xcjTh6JaIkuW@|*=cY50WmL~% z@Fyzgd%|f6e>RMtOf`70)9EJ>vOXC5x_cZarzdWM^~vB%zU!{AnaS2s;!iUd)(||V zQL&P|Ov2BF-|a%>o^(l8+16Lp-Cy#i(H1njrq+zpCi1C;)z7dFarfoBpCPWTw8u*W z8YA&#X&N~E(?#~2q^?uV!f5KKX2V9ZPDf7b7(^})c8Y0cYmIOR*dew_D?;D$ik{?OO3eQxyRH#)|TrpMSsq=>IEk+7mqGI_h z0YmXy_ar+~cxL-orMr^^TYZg{S=lV`KELr-ThD2}LlvFQZ5t}AdlA{Qj#H0d&yz|cr0bk>^?H2;!+j_Vf2d5TmTWA1z`B(s zsXH-BZPNHa{;N%VDWyrgaZAZWMlgl=ic5tf!HdMC12hqtRsb5)T<~ z@tBA1p$$XPdYmgSllJPu%qj1a;N ze~8#DH)7U4cylui4pq>xQ)&51*51>vO4ZU6zX>cb6Ir82yUQ+wa%Z z$?zz#-KF}<`GUxsSnJ*~ky_8SMVI~cv1GXim-AzoOEXQHW+f4NQj>&iY|4W_} z6gxHsS4@mi^h(?X@|+b(7)lrLDkB+>E2g zisq^D^?-ifmoAL;t*dBf!>GoRQ}>NJ*&ms$6dk7qFT2U_DR&<94y0{P;$|l4i4c%k zS=?=^Oz3@EOwm-k_7UUsBwpUlGHAE|PJbmqBdLN{t;@5I-G_Iztol+F9#+xFTZA<3 z3hjAB;=KKm9d`Xd-u+ipk9v+x-yk+|>%@U(f*7enh*9f{(>MVy@%Q^@O+D0JPw_s! zRsEEzrK2=86R_d)Wx)0P49oFeD<%7p#!cO>v*aofM>twKve|Fh>005Wkr?F*)n&nw zIB_Lo(?)N6+0mWH);ER&{nMTYtj9DTG^#7j%fwu+#iPMx=0CadYnb6V=p&xv-Qaa2V~IizemVbW)9!f%BTpqz>+4C*){Q=* zZqVfmi8k>h*7-O+w3(&TV(Ynkf+2f#yQX$lgMD@;|0;X13|HOCvFD?Aqc?qmdE6O3 zVqI-`bp9}~>yq$k=lh|!frNh72qB4u->$hg|6r$R^B*L3r)Jsx}iK#ZfN zc{pOTN~cy=llo`WagI);*22&-{qFnm#(~rx9e0mEA9uS=rd0$?fa-ORP)^g4H>nVo zp9p;%`@LpaAF{MZ;SOY$N4_QfaBuRL3CQ98S54n}3*NCt^If4)%Gyw}tG`_uId=>S9?p%gKH$U6RJ zu5G4vJok;z+nQsNPKK)b5rYnc=eOq_x{i~QtyJs|Jt%EDlcW9ml@Dr`@d>q$!JLst zYL6XG?oA0gZHnYCubp+cOR0O(IyWo}Xfw-4_o)0vAP1e5&Gbv}-8}`3xW`N1bn^Os zK052eR+nM9BJqK%&y~SG^3?5&I{uRoCnp@J_r$bHUm!sfMf};&$!zBR9l?Q5vs>?z zbs|{K#rru&uC)eMe&^2Bg5EgdNb&2$)9a^x zBn`S!$d+$ZS{~0n-(wAlPhA)q+LXBE@?&e5Wa<>ggT=bXJJZ$sws4?Eg_o}aZS~S< z>zf6~jRUr(^AF9qI%V%In-0!-Q!Sax)-7|e0 z9qP>GLdMpeS~k{Xuxd8(W?b!CYRp%LExH#!pD#IkpA6C0Vu!2IG9^Pu)c9Pmh%H8q$f$(pLxC1iDSTMx*G;2X$(y#WxWbZ zlNlzr>)dp{YiaL0&vD`Ax}iqjco;rjkR$EHTDa7t&3^Z}zF`@ta@@LoH0q(V{os~0P50^Gf9M`r4x?f1Q_Jq2eEUxEkh=n%yiym~*2%7--qqSM*F1ArWuuNq zCYo=x9+gb0FeyQtaol@1%{Xhmy`t@{5r4r^K|BtR?nl=HbKG3g55z^v%r`Na5b$eo zaxWO@m}Vk=@uy|mj|_C0S?TU@o9Zx*s;dUSBV>5D7_Atpltj|N(6=_$Kyry!S}s!k z6GDnZ!uV_l+vWJjqif)#zDG@U=<&DbYz}1Kt)|a$vK2kL)*W&e!{{BDi*NxB}&7 z`YxMCsa4`cxP0WUC0Z})0qU%MNUK}maq6c$Y<0k$OJ`8YdE*^?x5^-u^{%-{@pudQ zm)urvy-Mp5y2ti^Y^i@dIG2!Z{g7@KL+&DPCi_}Mz*{MTg_D-UBrJZ_sN5+KkD-$Y ztJ2x(+}lE;Y)bisV@&ID)Ni&Z%bO!%t?MVzY@CrS_c|Z<@2OsmsW{DNmdN@1Gp1GB zMj-3iN<&o>9+|O+-P8P4h7`xaeH0JvMKgzxX8qTlVduFsjFiP|?!>G|o^xkVkBGF5b5Q*OF5IyQBL822W z5+zZBAQD6fB2lA82~mUS5?~?;H9}sQ|)>;C`xV=0^=51Gny3k7va8|3a>^O=#DCAYQf+7GPE1EUJeB(yJ+ zJ=@aSJn>BxshDs_Jqv@Nm<@4e4StL(WYG)$JhVc!R>X5~qbtOMcDVy*^xv1r971P7r3>cF6gJuw$ zm6Ug-d%Uwg7`yf5al^;vf|go`z8~EWuNd%tFFuoP^HpUiBxL{@xF36o_D6wt)X$1> zi@X;mgU0zYKhzE%PH-1%pG35q3{g;T5l-z=cg3Qvj#5zvI#4E51fx`vkwGih={QFm zOBZ=THUk`aVuo+q4u@w+eeqai8A}WeuylReP2vAqp?N=6t^Wx*bwct|F$>jH;ylCl z5AvJ)s--2v5~8e=evCdJ2Td-Dd~2e6@l}4$eSu*)u@-eeahmb50$*&MS?ikwkpRYqr9Dxx-^Y!U$7D_-1^{P&KmRX@*hV;|a0Zw=eUkRR#|90>g@dn^tm_i@xH_ z`umm-xv=`;=Vmeb?zAn=1P-exoX*(=?X0_b-eD& zSBN^9Fn>aDFwHYcT_PWzp6=e+CU9rkaBBLFH%8UzI_k}xA6I{4mUe#&5_DKB7sj36 zzJ1niBJ#dR=b7~w4=%R4xr;Vo<>D&pS7OmpTVHs5$DRB-OL|&Akme1)49P$hs%^=d z)_CU$P+_>?@8xHNj)8_Jm|X~M-jkY)rQNKZ1IOkm{QDNSK(_?qLY-&AKW|b~#MQU* zHIVjp^Qrf15E~<|f7-DxEXqjW!!v0xaI#Leuncy~4vaR~|8x2V0H}$Z5WoND^`R8; z&h2MA9!@+Fk9@5TpOqu8SI}@e=wvJZS{?}EYj&Mf`Tco{L}LTfmfWu&cITDT6zv_^ z#z$9uV=iB*Dg3|NIXccr%UR7qX1m>51y`YF2d0s*x0qL7qTt!pj%`#*vaX%WH|RLa zM`~I5zes;h_Jp>&}@{fy}l_Qnd+Jx7+pLyx-ex!b;&)@UnQx!Ta-R>KiJFZTAQmc4rf% zF;QQW*%ei5=aOg~8d~4;sb|TCk!{lVEQ?DeM=cZP+ z%TL-0gLmN&s844Q4me5OFHNdUkMZ~#R|-|t;BNk{y=mgnwACT*ldir-2Toow@eJs_ zZ2;|yOB>wLcJ?=$(@VzXg{{pAe~Ms>g4q963n0!J>3dhYNj0+B%Hn%WcA9mdOQY&% zR_TBv?Z>Kw_G|$PxRZB01%VSC))P#gTb8ax-C^G@RCvjp$vZkG^Kp9PeWI`Zb4YM> zE0QmxK(3f0kcV79k)qKsV5K-zRQP(V0^x$ zSBeLI>OUrv8a|28a>hQ!1m*Z$X#{KG^-WOC4XY2Rq=gdSIOJstv`~nN%9>y zcs8o?7k;SVWBEIkV64UUxG;3G{zrl=Ix*I&!JnRrL;igbM{um)WXP0E`uOxzrLW9f z$7|DhW}7DuzB(%=6rKo4jm+}|ISoCvGby)uvt7!B9{)9Jnlsayt%MQ@RlV=J88!aH zEbSS=H{wrY#V+}F74m{Z8pQkLXWwlHxTw}+TvCO8f76Y`I3L~=G%Xv00N#Ev=8s6X zlgC<*&e85RzoLFRBPoq6Wx9fxRjh3fdqJ{2okk>&dW!GbW@ar2dNotu9+}9gp1snO zD+WJqsJSY?0r)>fY$FO1w7t`kvJ6o2d6Xv`@<3@T-fme-D+rLl>VROy3qm?_=>)?PNM; z?P9*7#^Cos_qB8`wQTvY!_3*B;hLZ!w6mwy63@leY8d~v=TTMS=}^^w1@-NzsAM5^g8MZ_I^ z`dNILBW7gQQI)Xx!}LamS}c>q4W#!SmfCk6gDiuyoiEr-ve9d&yX^U^$wW%tA{GU# z!g-CcH`sCt9n0vSe)><wkx2GCq4)@uUO7F}-JeQL4)v-G->*_qtkSn~De4!Y=bTMnDB!>%4BJy#OzcLlt04lwVif_GQ#H<7hi6i?3HbywxoBg}y-Dc|WLCB5i=i_`2%K zc8!*Bki(CmL?@$(OL|g*n25$E#{{tVA0?0(kak^;G9fFtGs|rH(lhlVP4(=Dv+qN; zgceKT@tK0jzovwINsngk9zr?uv1^)`wu{Tx3e6NcpLc|F@f6;=GfZ;@lpktk;FG@A zb_{2RW{GTZ$Oq|VOo@*5@E42K*KxVD_vVMS12EZ!5?;DpE*93aoRT9xE6hb2_ZGwx zew}w;=%D@G)cdX8H?Zu|)OJLy#Cu?0A7%@|caqrZ9r^DnboJh(0vl@fWpxpq)}+%7 z^{tL-*qmUnN!I@Wutzh%dGCB)B zC-rqT#4#3lH;dPYt%o8pP|Pms{1rR+YJEddJ5@GH)`=nQHpR=#UhiiPs-}l#9hoxf zPUxcW>6YQd>$q8|B)guJs+0th>xCKFC=cbd9D^#CZeecMemyJy%|P*9h2Za^>E-Cs zE}M~BgKwXkYSYPxd3v&OefOk4n!eGGMUT>A??sHHc&HgY@^rp0RvM|AcX9J+jpz-t zjc8P%-{GX`=H1z#Ek}z?jF0&+o|HE^S9S1uk|2W}>DBQWzS0YxWgZT^9}WZ9^{u7_ zY$uvrC9s!cN9H(qF!r?xAJRI0jlMMg6u+pXE1uq99@D{A={g@+^XNhHQDnVahodH; zA3Yc14k#3Z$@~7MhwDguQKsdZ+Z`%kJuNA2(^0-s=jmnUI+IAyFYxeq^dVG5nLs#4 z75e%~pn3O;O->55Oe&7zv_4kjWg2Sc*Q^$w8Jg>b^}fbLfBw!p0W4Lzu_J}W+QVH@x9X7h$nj)9vWAB8r%uhBhCKk6|fe(P^~ zuvHlH*!-u&pm5NO;H;OoUmmD>+4lKnDcmU!Y_jiro5H-AH}>JJf=NSARlCbB+Md4i z&MmR7OyJC1=PjNky%`Mac(8(RezV^1@w2u*OLXmXA?eQAc+rVaOqb{5l_yM8c6Te1 z9bzBb)YQ|Ye6ZWxd3XQUg2gZE08_zH+BuZD{*bw@;bJy=4)(@3s;zg0mZbM2&i@x{ zU0D-G9hKLvba*9^w$?QJxi_1zX@L=2oC><^Z;LNoQ5Iko%mbRA_c4h>-#ohF`8nfs=Q8;f+L=GU<{ z#j3|CT}{^OLUl!>oA1RTZ+!CYVkgmFND_~FDf`;s&x{s#KXsZuuZ_v!zW9GL}M%0OY2`6-%St{9HDY!6b3pv-I6nezyX@5MH;Ud7&RswV~pkUT( zu;tIV+Eh}=SNOA$2%oJ%gD^2SaN5%ZFyC&e$??{0nZ>gzs@sd7(VS63zrRiQH>>@W zvDb_&j!7!I!QVwk3aVTwB<_DQYB_9K+NW61fAEZ*YPBQC-}g`NC%FuF}LLqDQQKiNS(^K===7sFOMx>#ox*oR+CM5pm23!gCT9lJ|@ZM;A(}lg7=Vk zc~mFwo9CBeQNbZtwfDm&ECTR2)}%k)d%R8iS1kvBJE}&4V!mR}&mRTeMQ%T6c48KH z=qfnSd4yABJCGiB?!d37Y)QmS53#65>M@8EZ{MVxxf{z9ZOFK=ZIizEim(OQk zRyYKw^iW;ziJktnSwEPnN?0dab!m>8kHEI{La@xczEmkLEl@NJqHNOGBID*1Ier@h zM7@U5>46XizK^h`47VmhhwaHyjyPd$?N6)wf30BEQQnPpe@ZBy`*c4PVz}NOs>XKv zCrS0EZ@-nl2`914T?eokxp3E-QDW3uUf)qhY?#)uap_Kbq zmObki<8*HAecxB~@%)fmfdXY93N=R&y}$1>ektvXU>SZP9g-%N7!=%>^y%@n zc@5@U_OIz@kL*aP6CSF6C~WEcAieCH>-|E@-#+f?4yZ>z8_=tVXa<+qBKwE^`*sw5 zZkW{RkBUEe2P*W?Qd8|YwJs4?t*k98H)~NUA31`v%SyZyWp1BH?{^BAw0s5!zv-BB zHjg7{jAEZp){LFX46mZ1OA{+}niebFak|_6HaNH12VF!W#>9K);8lv}SrT6k_atZi zH&bQyHiCIuoV4jk`N`O==$FBf-k`Wt?}AWLzg^&|;EG6ICsuHjblbn}Ml=DBwo0|8 z5F0qZEN4%vqHz@Y%YD0F?8ia+Q&X~cJksL$I4C1IyU5KlcXO10GdN&-BQ>o>UeV`- zZcbxy|2z{0;%Ij)K zGN^7X9S8Tyz6^R)aDaTK$Mt90TDNQX* zrgEqqCZ6|x_>q^5eCRf+x)UpLH@#*fy>9zUcTn(PiBQAupi4iE7gh%v;MjM{?&r*E z<3FYskYncpx<=6R*llBMAV)~9(qR)aXSiaHj#2QNzw&B`7`3N%9I<`*rhhWL7;SdR zVO^V;3SbRSTSV2nLpePk#*lOx;ZNUuuYG5k7@q*vRRbKc}j;wa& zb*>89uBW0Q-?dl9YDp&!C_Aqie0x&%o90}b@<$`V0~HaH#YG`jlEj>NZzl1W;bT6x zix|~(5jY#TR|&2Hs*dQLb6%H|T2AO@5NVXe6O0jmVkvMqL>P4zNdHw>uO?h@JPD$BPW z-}u#=fb3vd1;;ij#X};IbOSg4yg1%_isuSSV=WOGtM8u;5eogiKF(qM6J1Z5?@G{E z=n$Y4Cn&~m11dDm1j{Xk7517n+6lc{{Mr^^yZXc)R6)6Ej{@_X^$m8@{;^k%z ztX+;Xa0&Gcuzb&Ib>OG@OZ-KcTr5&$r!2h!>B{|){#v%Yx_sfciI#QFb>1X>|0$uo z7rJJQ28z68fBG@<_dT(ABK6z5LlaI{-c%i!^?jJmz=hmKkx>WVR#`o37zE?ea3fT< zp2Ig}ogRzcp!Fxgjlb97wYiUQva2O{m<%c#%8(yaGQJWkzQ}Oc>n_=bT6~(fXxyxtOwuh~^ zytV5NyT*U?J-kx4?sz$KD}s3`6rQ5i`Z>{V7ueD zjY)afNhodCcw+Qk*wnt>@3bBmDNXa;i;ABZuN&4eTtHBOgnVgmeZcXx_aPD>Q>61K z5^lHZ5ujk%?9D!0VSOoE)wo3e?(p^Z$U#tqhA!n$FGn+7Q$NCHpDVeN*hCel{5A<3 zyRltMR7}GufDGTprVpNA8$>@d<4aq6>q}vK)5W`~GFms9P8u9;NOpexk>DcbzB6a` zK9uW%;-~E=vX%Mgo#~^0v#lBDyk$+c?(v*iA@UIo+d41+`%VL_?k4rX4ee(~XFkmh z9hZ{dcmKUTaXs#!1&hD&@G3s1*VASlt^HIdVm+k@NQ!rYBp#mq{mfZ^hxUTVtz-l{}gH2)w6K zQ|9^3n{eQH6JCGPSs(M`&!W}5QHg|rl;RDwVg6r{Cg|6oX7})E7|rc`Cgie zOGZJrqsVnI5xWdnzyJ}?Qs1FKzv0He=bRg1*-LAC1BR}3VR?hUH5=uQx!YUD-FI(- zW22?r3A;M)W5xT%%xl}J7w9FK@yRM+^z{mq!N$La{x-odQEUtKk zjrt8QIBC;G7(a7+d<8wMD}F?q(d+I?vvC2$9Zd$35wl>+p@J*)P6mZXhwK{1vGR;k zeiR?RSx(;Q1;%w+>6?P9fY{S~j_DF6B7}?T+tzd`Q?Qt3hdROz+++z4;vz#H5f+Hz zWMDtl-yS{0Lp$}zRus$%_HdAr!i|O;${zb`qhok(2NEbV!Ja9w#!P6pR-dMi2SDY} zF_E)*o%{ti)5Q3}pLQP>mnQRDqqz*(z}FCycyE0hICDdUDd2&@c+-CKi%T?ewYQB* z(Fc@qj6|bS#aO;#-z0<4%g(`HLE^-Y@^WpNLV)u&-d3I?yQb3p}T)gb` zKKb9;83)5cTJ6FKdVNFz74Ae`?ex~3GCF6+I-R5S3M{d>mBbJ-Q8_|c1S6$wOQzL) z_eS)CVe#-R$^tvO{Z?lnrQo~Dz-@zu6-JD$v{T4?mr|4$ zPwDCXpVLzvb`kRglvjVCuqDE6o+RG=3~Wjr6W$ApM(k;v>vBI&7Bce{@OpHg_ePPxc`(@NsBR?ERq1ri4jWVnPMPUJMKU@u>7nBAKQ z+l6!S!q1`lsVHtD_CAtPekK9B@QtSb<+IrOCny~Ho2F`~4w@CP4oM;?kpp@X5Igps z4u;(j04mTFkIDz2vXKrnFAs081Z{FE>4D~z*RU(j8ORD5{E7j0Zwqp%+B=MZ@~vpt zqHt?4VW(^};cwj`fq)`g48r*OEhbzTEFD(asoMPcEKTecFtobBL4aDgfZu2%+gVD8R#e9kS-4@n>Nl5hJ9aZA&NpcB(KqfZbDscXbrq zw@cYAF=^%y=fVQ~!y=sFwoIJL%5=wh#um4CFs zzqs}=0s=C9G{6edlw-Ge>moD44Wq`8rtUu|(d%F#LCmP36Zpn4}gNzL$0gtQWXZG z*)#_{o@vc4C(G*`KVMs5Y5^-=Wh4v%8J0?g@VZyKMNQ_Kk%6_zv2e%?xWlVp=C{5- z5-A>_?lnMM(g54_$DAA;P5-WS0ho#w?L`K$QS>i(?Bw%L!YiqjZ@{Jq9xp2uN*IHG z-lI-QU!s7qe*Ibh$5HGL)B^#uX#fpJh+8Yh4!fKW>kTjJwEoi>q9Q>Z6LC${6Wof1 z@<2!^1s4Gu%>}@t56E=)I+djxq>EBRpE)n-58mSGX4uw%jaSCnc;5xGZ9#e?M9;Jm ztoFOdghll^0ef!JE|A8ZhZVsepRtW&=v*+u+MWZ0ncjZ=zc4Y5%aU+W9r7AhT*e9Y zg<_x#m5kxy(YyJNU_2H*N|5AjIS^g1Xhia1ixjrF5X-VM>~sj2$3NdWQnLgFlyM{* zQUmSQ(-s6Y+LYiOR0I&x_$lZD>ja$%5`7ZB^Hqbst?2L_?l-|^7eOH`8vc^BWPp-_ zphoiNogQD@7NnhY;gH5=Q58gA)abC)ZY6-Gf(eN?f&=PoZ<#;hEyMq+1-S6n1RBBk z?)T}wfwv>?gK1~DJ9bXd-T>l;m(}fs!9ZC1tFP^)w-@9lpc|)bh-dH*K`F>$U6}uZ zT%i~ZSV|hHb^rtamZxQqlHF_mGzQ!kGpM|Y-h%eDql5Qp&ix3o>ClFaGY|fy^?#d5 zWGSC&&EXE+U6?n?1__RHjA5slO{^GDS;-kr4U>hXyi=vde3-%f?R9|ZN3~ut=)tb@ za=I3!;-*dk%K{AY-}b)wZvd-VPJVq{X zSqDcO0$9Ba3GT_fq(oL9TzhrSAI1Z7vRjyYcn=b{B%(v@-3ja<fIgUA#j z&~V7$Z|uT*>cbpM|6N;-|KcjE*A0F>;Za2rR>DC9bTL>&Ula6GvGqW#--?MIeYGNJ zBDf|p0WtJK_&)3@e55eLKfv~m-x3>X7PPJO3TW}XKt^pH7fS(Ya4U@D*INL#pjM zWbGHYQmo}9_0QM};5sgB<*z|=#k5+C9qL%a@V}lU_*fS6Vr>;5!@Ou;AU`8CRA^?B)`F3L|Q*cqI z3~c|m&0+ql7QQKa?7;VSLjl*YLm4v(?HICJO?A}IOegs|)lYR)_4@!NdCYA~XO>A) z$$4vHW(8{t!J+Bx#Qz07Q~ZiV?y6pYcwwI8*{2DVODYBAILz348+aDeSwnY2pdtBa zFpIC0L&#>_G$SFYIut{2-YbupHIALeNc)~5D9wfd__QfL?Jm$&MgSD8tH60GBbuRL zV9K;pJC;)xrv$?ix-%;`XlLD30}@HW$q_OWWR;(1MHbwb%N;pwm86Q0QAu5Txe(=)oyJD zLsX88fiD&R?>b)nD{?=zlfQxXXz1k?M9MWUIR#wW4tY2`kkuY6ya;x|$wQl;n7>Q; zltCAfO%-t{PdJEy5IBpxGw&6#(-o&veXL`x`OSo)dv20=gq;3qdfVrpMn)vz1!&uq zCN>x%FWJS$$oSd7>8vi`tqo>f%F|9+mq0KS$=B}I-gOi9xI*SCO{@7iS)F|FU~rzY zFU$h|9$u;6_66{pJ+X$(ucwa(k3{!yPboiI2HmfRpzTQ8*PdLoBL zqpj0E2B5Zn4+}1^{q287vq6GL&OeIjy0?qw<6B(Yql}t8RO?BAg1vv`HXw*nc7Os3 z@ue}>70?CN$y$m)w9A*e-wx=I4I1tIr4FWznC4ww*lcM=G1^0jPlIUquqg%OmK0M> zmu}P1Y3)5_$LN}Oq9o}mjlDHcZOIXRI%mXWf(Gj>$xE0+j z07^Q^1A4D%-RVCvHzRiId;3efRRnT%RgSLbZ*>=;3p}gjoi}tgcmESZZVDfVQv~&L z!c66BLka~>P!jeaY7vE;^+MHxKIHjP*?^i@{f1hmZ>BNFB(R#>vodUuYBT$lpAs?xXq;Cq` z3(z|48Hgi13U6IF{^9Yx%?+un{G)1>JRb2k3;N%)L*M%9vj>9B+5+2cSKZ?kfxFz} z4sVAEIH&#~uIv-x7j*)HazpJgw@ThA0Pfr0-Y7{0aRRyGd(T6csUN&lNTzoeU1JgZ zTo7#dpO)d*y*oR3w=;G}p6@U#DT-?U|Dzq|p)0pw3oh0#U`CUh>|azM?cf$AQsxq> z{|>vbcdH-)ICk^^DPy<6d1BG?12^XgvC|}wUQBZWQ-SnCk3l z7Vs}cwE_uTp}8bp_gF4JE+HJulVyOWz)6giguu5YzQKF75Y3Pb$X~d2&S=fbC+rOW z#^G@OJ@K}0iHh`2M=vJazKsM8TKw#l!sI^38fLE@Iz2uE@sSL89#qtZ%xPhue=5*U z>qzJ1!Z9XSl8-Ys*MjN|M4&x+3a%=;(LtSY;jWlCRQ2rz3}xdSP8#-zN%u{bgxpOG zTnXA=X)u`e#ixi)vOG2sXF=2((_8U305(HFkLE*WH7=lBr$Sn9Bb%#A5+Vo%p@67c zn1T(0z;JgE@n9_LS0?M_a4O~~1q!8a3c?4V>7A_-;w(&XaGag0p!8U^pc4#7&*=_V zM&o>7&972yyrT$%qhZN)LQUyHEfWnaDk=d3Wk;|muSeCDW!`_Wp>>V$!n#s2Ekh^) z03oYU%BA;O7 zDN!YjvWy&w+7#eQ(Zg0@*e2Ve2aKtBoJAJM3fg*-i`@Rl7Vapa=!7nIB&TwP7?TI- z4MXq@_R+f2t#LM0KSs{N;C;mkVR>Hqooz8I%;d+=)=&{RuJRVm?G)(IaST|RT>9;) zg)o?UFnS2lO88CdJ?wN4Gy%>&XG;s_P~(C=?JP<|m|jIMRDLw6Cq$LZZCQU zsm%rrP|CfdofO$F;~1-QCY+8UhZq60_Yw$KhrMv_oj`27FkvgT0T0)r(T-L*E_ojh z8SWx-(xo*I7ip=cIa16?jCubsY)P@D`L#^Ng=V z7&>c2-it6CyYhJ2?6HlIxFu#gL|824m>3BTf`c0?=+nof}S#W>pHK zz2>UBFYy^oxDvOs67WonjhrK1w?$eC$SVxmCA17Z_zc3EJz;27u6NSFsv^uC0EBJ6 z=3_#Er>u}z0{5=z+lDZbmGAP$(4ttwJwcC6>kN|0Hc$2e49&(RAnOr#JB!4zv890i zEp|8@{_Fj5z1w^B>SUVfV!zp$)jf}ms}HmM6*^o5r)=NVly*bHnWg|uFlV~H{MqO` z?%Z2mKdk@t!^*PRgJhRo{^~mtBBF^zdeoi%ksYF#AbUmNFu+SQK(AH(ovMUR6D81Q zc*u%{N?z|-9IDzSWEqw<`&N48Gz~e4Mi<=;i4ON9Ijoafm}UeopQu62ea-8I;0e3= z{Y?C%>I#7FqEHI?&4SsDVh?t$mM`*1mEW-7pP$hyr%@1g+KteV7;SsC=O!sn!eaVT za|=31%617r!>_=b0$^oK{1eKBoryk%(f4Kfw8tG zKsu&Aj&1YTTwujD?3`CzVYYVlnzr`PXdCR|6|mqkid|M^W7zN;DS0gr1#d_o1}VOO zOoC}1m>r$DPbdhHswYU0TS*gF-f#Rk5h-a?pbUk+&}e}fG?0k zIafg$avGbSChru_k=Lf*JIASU2V=009FPa7)uM_j*$x=Yt0?)>;io<{%5}>EG(Z*kI$echtQ>At69@O{aA=n$EMZ zt`&CFF?2{`jNp95`YZr!u*&&qBz zs|2O)w?hZeuNI+p!TO~UC&{h z$=&dAbfG1MPRx{LVCM`T-oFevz0;qj(^MEIw2ISISmh_#j(%M+$uIKOkK$0m-*^zR zP>bYygmZ5mH_>MWCtn+2xa$DLv5%-3i3`#$u!!*HMCa0`{yz-VL~tPL3*)F2(aha6 zAXmnW8-p#F59!IC2Ib(@P;H=RN|xTy`e5iZ2o~F`AAJF7S4lS##G)g4-x3xWnh32a zRY4a0Gy*mlB4gSCUPdhub`Utd_=IO^ZPw0Dr3EDX-{dPFLI-w4HZ6;L5BfhKF2f`d zXemV=v;Xh3N7cH~t)F3v#O}1pB(N>P_48~%Wj7oV74VTW=ob2t;IQHM_X%#?28&KL zv?azJj0rNqS(CS!WZ=NR)I(8&u;p!igf`_g1MC_M%SF{q!&n&7HNe#B{{L0%OzWYf z=$5Fk0bMcHm#;d#lYmwalWJ#Ni1cRk(=+gU^OJiQe* zyBEYyuA(d|%BP_*FL0fghw<79y_NO)`!rzs-?H8nXAfuNB{1Y+RK zN&Zw`O^iYe)53}buW#`1*29~1aH`zQER{Z`9Vb=Oru_4#8zPMi@L_JP~sYH$~ zR=F_~t%NyE)&F@i>SZoTB%J2`q~sj870oB%fpdf?Wf+`rfL|Gf$=&LGOs$L#KoC-M z%9M3C3LtqKCMVWZ5AJb}TF6Gh5YE(#d`qM{wU!Cz56gQMUlwCh=9aOY@8p|m4ZVp(rx?N`kV_B! zG!tQnE?RUf>b18J2toH*qZ z!t$}p41V<{*iRp(3T0>z$T10*c7zeXoGcEY;}@PQrv ztln&Jh~V(|q$qXm@pN%CAxirRSgp3f5qzy}(j2eZSjsqg?+Vq@88GoBTPX%r(@zx5 zF<6Vf+-R`kIRR1Qfg#r)#CZCJK=h6pEc`nXrOuZmaZ@J=mqBx=j(`5#sn7W{o%DD{ zaFFIe;pY|3H>!ZaC}ngm;Ol_QdIB??5YG$PP2+W|H1<$icJTLt-8E9XKy08EFL>+| z;;;dw%B2&gBWpO&DtTAb7YMSZwG4kDLsDx%w?>hf1Q1XJO-m-htCOP4tHYslV2W2L z)41GvG7@lU;2FL_wxvYlJ$ExHS5*i9wav6w1_DAj|E}ZfvA(Auww&9&@ZF00Jx3bH{ zdQ{sr?qzCTO~95+q@~~z+3u=8gKA~pC1>!q@(TpLleyYJGJfdOZ}dtG3{gsrjx}QT z>t(hHzr+1Tq-%(?`o}9Lvhts6$n0~@?f+hzjPNqY$>bQxG|5W?8Ti{W#LPhDjBdft zrG=JRM7%xN%qjW89dh|+l1C^ZDz>IIAJ42;ZTlsiaU^cp%1e6>#o)S!VcI zZCfdwcie9)iCzc5mB#uuzltqxk4&D94uiDdFPfoa{TPc!7>ZX!wtAYep-iI9H2DC;FN=jkeu)R z))oVt33oEKi%Q<&cSH;rq+K!*b&cCK&kg)cG7l@60QVr_(wTxKhKI|@L)^py115@@ z?2JOydfse!dwfUk<@C)3LP(Ilh+4~@Pp+3O-c4j)_g8GR8ih3SvGFvYPIAMwyi{*K zLnIpI1_t<*rxa)t9DbL3^bd?9&D;Ce#XcrAd@D*k`XZ3O0qd&o#a zbE`vhzpLdYKCCAX^u_yG!rnfW7=SeQF>)NUBst8~Xg{ioyjm0W*L3@@8N1bOonPB6 zv`lUf1#?q_0jRT)4b&{*_E-hZC3c;%_m75`Ru+BnD^!IZ_zQ--@M1_!fnWoHV2h-9 zw~^ql4;o+=;;JDPLz)goCr=MvShf`#7J`l4?} z-ajbG4%WGp?V=eEP?8=Un=-sV(Mp6WXRWHi2uF$vRNS1gg&4j{=<9_cJe2AlbHoc* zOkhS?z*_sS;g7Z!U9gCNFG}yCQ0Ce>KM}~fAFGS3r`$-!O+0kWKrw4q z|0JmuyxWo#6x!Q*3GnO=f<@<`qND@=NN-GiApI3a)S3`44Cr2x=-Jy8a?dMS#^wqw zEd+BsoSnC_eO#iz&4yX(YU0Qx;Dj^{(BuSWE$kYB14?2`fR>wI)Vrv|t25TdwnPdr zqY-h68R>*8FO3fMzmD+=M{b0s%WB(z*?kc)M2Z!E^o=`55^R$sr)MJPa6 zK9^;pR!K#^_KtfKA*Ca$H&BBYP6&2eLjNV59|hPPp>rb^_$V^41euFlzt$w8o{v*^ z=vI>I9WJjGv(mzXDo4PcOXF>M_+mgG8R2c6`^DqZ>-QbHoBII= z-U5PYXi=^fkaZ=0nbSDZKLn|g$G&bR@^J8+YI%l%yJ9Q!0vOblnXeUH(zZ1bsBnrbv^`w zb+^6^f&iqt30Oy>gjYQ|dS$Nm=sZe!?@h;>QOE}Q5?kBxpf~UdAl&+#!8Pe~fJy2F zZ?G0)j$AtA+-5aPKWM}+L<8oXhqx#dp)4H^$0uceH5<^H@pW9LA#LJvCegV{{6$yk z^FMPJL;@b^#`Zm-2CyLy#Ppbc3&o&Z$--EEytIa`bz}_fouzZs4lATJ^M@M3?!&^4k$AIN^85#fN%nChMKsNWdQE|A@xl zR?srvCOY9-x}kGXr&`X~=`ZWiMyh~Y-0y#Ug%qA0FkdjrnWH>k&{=BtW0Nv$=9~P0 z*XaVL5NzBN!p>TL(~VZhx0#Lwf8(zan&H~7H8pPN5CZO^#CkAi);{c{E)t@bQS|#%H+7 zo3;WgoOue0BA>V2ftOK6gYOM8vf_S{{cXllN9snS#5ZXEL8RrmrQqGMG*=noO7Kt= zV5qV5(wWy+5$`4@D4uEOTzbY;I8j-qmpy#o^S6lQdRT;zipwgw4uf7G=B@n=z&P;T=UU_9UA`B{FPOE) z0et5_3oLdZ?&gRL`A7_@h4xe4xJ?Hc3Hp#u#kJ&T%UCO;?U!DvU?Pa-$H2T>A3&g@ z9j&Z;7rA5BYPV)LA9Z}g@4$?R@SL1J#EK$e_s541%X61cZYm7?s}|t#^C?$MF_6SWi4=jy>;MB>Y8*e__PhUX1<3)T858WFTQ9Bn{}WRsVe>{xTJQC z-s{WzBz*W^Tt^WAxr=3C8)AXtJ77|((W-~aFuS|3J~Yt4pK+29TYe%atY9w}3*w6$UK79Dy0u$gjurfX>x-fIm?+JhgMoC z-?9WivWVjyIYOVLe1QF`s?CQ!x;A-%K?oo$Q?H$tU1N`_xow^-eQfBGgv7I2##P$5#Qjm^|wz<|oy2F`! zmrkQ6G)e@p|5uJk;5{JqE4(Qw=!g(u->gHHu&GwlurrZV z%5X0lQig)sQ}ET(*^x^lLapSGLeH7%0uyp0BdlhSRvnndui?GTcN(i4BMI44zN!sW z;fd8B+jtZGW&Kyk{eQY5VFVPvUb*KkauqR1Ren%7wX$%xY{LFVTUp8sP%m+!F%Ut{ zxca*_qS}JGScQuLnVe)JiJ|mPvfpyOD#3|oA9jy^symk6^g(X!Rh_+50dP*bE(ec$s2DvY!Y}g7fb_N&P@cwyJU0pp+ z)scrRu`F<%XfS^>f9vOnv%Sw!Lt4&xw;a#Zjd#h;sJ9=}EQkn$1lKi>wa~$~3@Z?R zDq9k*2+u#m)_4-skA+0PmVQA{GzFBD32yPT9mIa;$Dm*+S$};I37pGSuvmw(=p+rJ z))TF776UEZ6ZCISK9}YY{RCU60UrELie%G*)d=F0U2wupC`gm~ME!1RxOi7lXAh(y zc2sS%5%jv!2PN-nzN~%2oK;0@szg$ZPlb`_jA2CIQxU);yD-tckD$eUCA{hZkF>ZJ%W7Fj5UanKoqa=Xd&+znQ+Dose>ow^j zNp=Mc6LM6Pl=T%opyG5UGp zNdq~0BscgJ8c7kg!7CcBXq!e@1kv+TCf*_P^~OX##~!aDkWycF3RVD&EdhW?X_mS9 z{xx|p=ab@+JJ^tvL%R_P1fn)HBja(9(ZC;#0wr$<6oA=7CW4e2?4x8XxkgN}C4kwM zZoFKdI|_E~)%5-wkHoAUBV8wU``>nY_XV~{x-o_W`+YP|LbbBo5x$=_8$m*^=&v?# z-#PDGM-mJXM>{fRqB1g=xMByq*MW=&m!mQOH!`?I&RFIX*Xc|d#lB-}llW$LLtyf+ zfpuhEL{Er3Z56>^z5lAY#4SGXhGC{#_|^YI*IP$L`GswxGSuK845%O=ozhAQ(m9}l zN;je)9nw9tN;ybKBOnb!ONSzglq21!NQZQD_Ji;5UEli7Ie)m8x@Ml)d*6NC*S()o z?w}Ier|gm?^5ByA#DFU9R ze8c*WbSEu`U7m@Myy1%I?++z+_b?qNIJABm#OG=@i|=!+^W&N)gp5RFwFUs;E1`Dv zb0?qukO}*h`3)G86kfxwIUa#w`q&tNQPh(eno4o3;M1?bazAumIf)DkjAyE-Da^5c zuz>W-k8kC}@J@Q=<@~U9V{`xKdoQ4ip{(-Woc+jF=8wjwG;7yB#4HV#U3Rdl0Yz{z zAr+rd@elIrKl%Fl&u$-GM(f1MQUt^KZgDdfQ!R|=(aZ*iitMBeL!LS5;>sU>xB>Fp zn5-`0Y*A8lxC7X56+u1FO%9ADKycPx>QFCU0^aEXF!<|MQPPR>nbkQj);=A3JbFp< z(Fk_q)#%goBQdKdZM~n-ph(b$8EM`dbQKygfR7~G>WuxhMXlO8Uw4m6Xbr|h zdjzzO8t6i54&^IcSa8x#tppV9UgaXNY5<5J?+f(F5g^VyT$r7YYyyIZ-J}kMvX|Ow zXT7_IAlG9@c;AhhVAzcn+YrYWvSDGTqSy(3RSj12DW0#xDRy~awUn~Gk;@I!3_jfm z0RTRy3x0nIyw&PWaY)n=)p}KGK`TsB)msI(zJ@QL9{=OtVr0R)p}|UKDHY-K9YgQ| zWpKvgO$G!Mc4=?z+1$to$VI!v8?@P~UJ|=21WM_ph)--|!7+X^%zOp1`o%EwGX{eT z=6a3cO6y=n@-SjhP#B`lp5ujtvku7AgS3PM>@YU=#gl7<)L5LN&btLmloj={U20|$ ztPfRT+Q^^is((|XyYAnDRI{PesZ38`YWMlrg8M|$;w5gl_90z#r0S`7euU%6;==mQ zV*dKzlfkgDJuDx~)Od13V%6V$R*neGtqI~e?OrA4`Xl%JTMF)d$P5_Q^!(Wz3 z63xj*&%gKtwP#t2Fziq?V?+tmHLtOM^*?y`FZTFM~FYyP-0&4Mq$$HAYB2^i6#8U1Wb77mBwQ8Jz(( z1yJ7Ix7jBcGxb5ehVf8jv&NJr4FwGTM%BzhmoO_u^2`mx&-K7moDZORia^3@9LeTG z1~d35LsSLsnd9K$v+GPYuwgX`Q{1&$s>R5Bx7gGFiSbv^RRk0qXTth^y$jhU^Sck{ z?Mn_l9r!NEg`73tmdj%tYoY94{w44z&XQ*?ETMdOhEsX@rBKFSe+kQ_OJ27vM}pZ_ z+x@^!`Z$4L*X4;fCLc~IZMZlL3j^_YLD{(!}kDXRf|;q7ei=X0HZOz7^8 z7gFjFvRjwU_fb%|oYfKlBf#0Jdz-RaUqVqiNoYJgU__ngQjii~jQEyyncd9ov7u@O z2BSk2mB5R4RNFs9U!D2Y7;|5JXyY32BG9(JQ>dyV+dc{0$&MkC5gI|TY)i}!q{2w7 z;Ved;KYw}K_jATiorGNR(Hyi$vORF6x#L7Te{AQrI=e zBy8)I#C*Z~AKNv%ffgpDDCTZwNZTtG{K6g^yUofF^cY z3A>saB2r1#qO+jWRB6NKRsx!pgE~6FL^}y%dHr%Bw*z{HQ%AQ3bMzGkOLX(Pr00LN zpZu)oINXZ4ZBuXK9)LMM*_%#2T)sP4q^oi-#j53QMATf2b>-mWozI2SnGS8A;|70I zd9iY{sJ3XF7{@ig{Y`j@!(Z~dR7Soj$M{aU&G>@sLTeDQ47JyiI6V*pQVe|=)V=&j zgUY97B^QGT>&|0-Gl4g7{9~B+YV{9s^am2+opQiuRzNN!igG;1xOM3Mp%G3Gm6Au# zI{T|uqrow5J2+=jFHd;3vS;J`T+~=%-!lc_9cfbpjhMqoP~O49U((C|mQS>qe9Rs7n!ohvMyoH7T@t)va6wn=2%DrW6$pn!Tse@>dp#dBrN zW4qmgaVdO#MW}1j5*_=4f2okq!>A1B)gGP4b~f_c2?sR9KWRUh!_O8S+@(!s*_-d* z5h7wxnkBki{a|^=py*zM=wZQ%MM>rCQhlROfV5%P?8#?-fcvPS>5VU#JBl4r@CRBU5r3n%ap)7)%)FqmIVzD6fN)9({Mk%o|HEGln-pM+qMK5;8J~=3u#|X?o?j)1%?hWzLZ$d+plEv%v?$J2Hho zJFwJi8dCfIQQk+k)kd{%InD%2y5dG`$w_Z(_vkum{XHBn^UbP^@yi+W*JqkVC;C{Y zi5BVDS(t}TNiht5spZgWn0B^WdG|wLEn&;^-9g!RT$7psGOKXz$}u&441x1baW84Q(7wr|Mb=Du-LJtTQhm26rZ3^=CP!WdaBkdqhHKTeIhDr5 zD~a%;K&R&g5}&+)e=mn|g5r&~R4pEK(ll0{(Od)G!X!|Jid9C*RS*WVQmfb*U3qlZ zP~$~{9olV@^3p2XsDxi}PvXjfrR%xRQ=(Ee1lQaZ6~071G1XyW{wf!;-h8MXzTz3D z;{4S~` z!%e2Q{QL)cY`L{ZEAKx2z5T}(#f}zJXT3hFxiJC*$+lvD)8}awH>NqMjRFE|7Au-9 zdy?`J)32B09k#4DRh_*yT>5E9zMF1H7~v;rZ1_MVYdu)S=jtWP_xQB>zQ%eF#&HHrhHBWCp|totsj);%e85 zP^8`_dq&rF1RjArD7fJFK9k+JvcS1`1*M|y%^Uhl<5%qJj|nTbp4U}HIpnyA)+CU! zKsjTGENw9S8xWpwDR*q_Z>AtVnT?HAM=t_LzvrQ}B^5z?$I(S5-#^yIpG_39=~WZ7 zjNT%hlrWj|YxtE;xA-2fQ~M(KcFTd=q~EIT)bhvA#_ufobcX%~>s8%`uJ}tXB@t%k z;aT{dHb;|U1NIu*`P&&^=NV5jf7MSdo~-QVoobA))}!|tMdd=Cmra_6?@W~byc2MC zQrj!#A2{WGfLON@@byfmF8(lwWA-l2(DqCqijg+8t+vrAaO}IYVY(yLawpR4q~=uP z#LZG^Fk(%`G53{&bUsd6=q$A;+r6Qus0-Wno^NNL?yT4PPUwDZ4ziP18)?3lS{ENo zh|WY)^TG+pPLs8pyH#uWp5G4L+>o;^8S_PHN7}{2*U`&`7|lU=?L!tU@KT_NJ1Rvm zk)pot({KRSr76aJV-b3hOy!dx8 zPsRri&z9=*NS!UQK&H9erJ{bP9jQ|ntooGxkGx*o8r`TB26uycr6ApE^?m02^Yx5n zuctM1{MU=~)@o&5=`FSCJ+=7orW{nG>V>h`J2qpgbKC zO*Qh16|#+=v^PUcL#NPps_{TyrWO(OiYmUq?mG(~_j6#ztG1)>*voY5jNgK{KC&5F zOdi#9ZB+CA@~NF{4@ESswhJTJx|463M$w84O$w}0TMmD5q-!(_=>I#erA3B~9;ZLE zqCdfk5j7ysKJR}UFuQ%EZFEuR&b7mX4x480Y}(G`^_TEd604r1A~Dlb`OBQgf3!*W zy>P<%s%?%3;GISy@S=;aNB?xEm(d#-|329-=GohU(T!I%wjQ10#>v0?hkKObp6*=| z*lVYqB%3nQ-IX*tar8F!Fx^W|E*;bS*ixZZs$QFzeyEz5c3QO;#Aja)@|PFnEWYA6 z2LnEnBHgld#ARL*q}J72&z17LU9q%^ZndQQ3`0PUA*j-i%YHm#kIuMb5^>s1(Qr6WL&m@cC+u z@I{_sQ}-HwK3Ehvs`HsVnHU$?0DqiF&qP`RJm_0(Uv19jNcMo`y@0 zaJQ%#9O~q}a|^y4ui5HfS1DX@aufA5gz`Q?-Z>-_5!uS^pU?EGh*~)RkH|RZde0h* ziNnEMW9Q~K0n?u6K268fb4~V%d(A^7x*9u6gM4QO!+tw0W!qabqVXBy2g}|^L)z7S z%R^fxEk7H1M4p(rtHTU0WDVCfJ-JGO`5^o;$a>u9UUErJ+W2S`>VSl0#b<=7ekEEVk#-7XBt=^`O-&i& z%Mc1sD;!*H=EVBJV)R|dm`l`FBcq#)H;=LFmR$Yr#C(&iQp#@8;^fbRei^>I zl%|Xf{|9399)I{RhLI|t`r3C;UTaJ9VU&?|Bt4io`X1i0tv%`O24@-AH+?Q43hKIO zFUvO|_stCqCFcz>Tdp*$yoI8Y^}Heg#efSiLqNKjoG353l5AX=WE}Y1Srz<0QyGX2 zn_XYqD7ipGwkXu*Rk1-=Y=Q}T)%cpZNIQJd@3No^^tmvYPF8D>E6CVp_G;cgNvi3O z_p8Fi{J`&9Jt|~46|KoA^<4bavbu~;E8@pze9!7Uy4x;v_-sQ+=Sk6$j`oGN>02-4 z2#UA_PbV@A3bW&_7ryfYEf=5MZg~w zGrbkPmtEV^B+nyek~PNEuCnfz7gs<1%l_y~|Bq_tJU4DKk)7eU5@spOJPG<6!El9i zhHP~WpRVy<7>w!)yv9tEg;9<`a(CwYylsh20*L`GXplB6szI^Gpt#s#_k;1T&!;&F z%c4IJ+1^F>C%fJ<7x|i9MHWj5{?TC3oyqd_!FE%l6)7 z*wg@kMw(Oa+F)~{<=u>I{#Ah;lUm7BMt|_q4ggO` zxc5mmBG!0Bjd~jdr_3v>o9v~}7KrJGckZtr=t!28`S+#INHHEB>+RfLBw0U*b39X7 z_Q0OG=DZE)9pqn*7s$q*tkEqU62}a=d>bC_o7VWj-joiN38vr0WFiFroUFV4Je`{* zEDGDm;Mv^?x`LqM#X(hm{=XaY*(5QByjaA|$tc)(AxG9j@x-4iovkLumL; z_%!b^r@3|*hkNx+`_CcL{8m_}woYOu9zu8%0MVEIWM;R%F4av>nq-<)mKYYDspomi zm>SjSskqi@iwdOAx5f>aZ@Fj~p3WAY-B~8QT|6nd@Wj5{Z#?z$BRrbFoB+?ghy0I( zQBEb^1(8m&v3-aVMU1wb-j(Io-@O3fUl@4eV-Y~Q%KWwcSoZ@`UjJWv=mNSBko-29 zL1K)aW9xa2`q(9+`#`-i@MP1>UpNA1`cOcIwWfb780vo6J9_rTtdzKoi4EWg-;4^N3;I}ZF7BjBS z3JcI?a9SweGmeK5?~ZLxpBWTt_#@4yCPF2igr^7}5c8Zq@aLubI6}_hHEkFlt}it2 zMSZ+lt#ImSC}8&>g(+y7rupF;-v77&bo1b{VxBagnJ-f|Rcc;SU*u`?7!PGTT&Vt- z;YBZ=rD%WUxHg47k!HC4fOExa_(9h=QA&;0ulGD&ZDe%e)lFm*cXg+qD|U#QcUE?z zqWfP#av=I&Ib0D}#PEHbDf4=ALrEqOyDdOkew_t0v(fO2BQD<)l1c>@-Kb@`1dfy; zy24|qFKBsL+2a+r+0&twUB)B)Y~2Ta-Cy7FV`~wtdU_q)tlL#7OYf$v4*{(bq;Dv^ zhM;HW!8=88`Vkjd?P~o&0@7Ow!~u%VpR6ykJke2rUzXjN=eL#%nG|cBXy|v;CM6;* z^74vK%%J(D&57Mz|F--6dz1TSy2jyQz|y2eAjkdu)a>$xlfGsf8T*m%n-c0{w>zcY z)ta2X`$_DNH5-boGr5Xad&h;Wo?ux@o;3iz>He^fs_0I)Id-KOU@#~MG%BCzY`hWQSY65g|a$$3^S z%+cx&QhKXm<=Q$4RiTJ3SB>f6ZBX>`xseqWJAM4vsYM_hL^tw~rpf1y0fh^^+5NC% z(sgA~a+&c=Z}YF*$ln3+nFu>@kQ0ia)`t2UOh_K|2>`V;hHVj>hj<~=79x}qpm*;~ zIr3dwZh^%b)Qd)Rd3gQ0Q1Af6SJ*lUK_T@AP2Aubd5jNQo=Ktgl&f0FeRcW)qIl$} zgR<~{+B&xgmk8yF-U5tGS3Pm%wjoXoQ!LtWHW*;y&*OgAFgXbBFA`z|gP(45@I}O~ z*RBqjS}iR*KN`rCld85GylY!(&+|-s^8JZp0)S`%#ww--5OwXbXMY%1<=TF#RUeGvNK_1t9aL=~ZM(0;~p+q>NqWHoG z`$l%EKfU^yeadHf2{J5Um1^GxC3r6M=SEd*142?0Dw-O+XJm zJFgcRcpuQtt`pLt4gp_x#HvTZ2%hCadpnvnyAR|xW_zDW!4Op z^+5SecCh=5c3J9so)bJFHhF`H*AH%a;*z3SY&Crmg6{5pZgYbmxXz`lJ{rG)@#!?Ml`YNtNCLT=7r@<=_`HIYEB;N1hGb5Qa#;46VMuEYCOYnk7}LQjidxoFj8ZD+GLVcGUc=j2Uce}w{{`q)r#WOv&QPNc zxK#8W9!wok%wheKO5iL_|J?*a+3^LXKWks!wm4$s1*+_@FG8cKUI!vxuz}XDH zIbx|=v02nM(?%Y;N~t0SEoh2Zid^CzK*8%RW~2*sTkiiyywIz1D!(t|6tOf^Qgqrk z`F6+OdMcJlKG`axoE2$}c7x%C?3U(HiPvnjSzx4Jx~?x?2Zgd+t8@VNLJ**8?wSHw zOOpg!kV09=HSk;JRU@L8V05a71IBO6^uvU^J!do6+_ zOMS@P|Cx}S*VXKdypNP&Bs^wa-P;gnxOTQv5cpz@36kyWSG|o7;{sCORhCZPUv_&& z^&hXHnMUlMnCV8dL22dt()!h(#e3>oVPFvC&QT;iTJ@qG1)NFPS?U;fUdf1s1KREd zwERV37j;2Bcw0IE%4!IL`8s5w0RO*~@kC~)_#A28Z>>H#7homqX-f<|^}hn>;cO$c zzyHa7g^>5ohqN5~LK22!{zZJ$`iB`j59z0lhW&hCwZHt_TbY_}Jxh{UA9yI$yLRQj z#WFNnM=Hl875|?E7gZ#4Pi5Z-MZ_CfT~tl?k~!K$e`BPbcvtUA!4!@LFv$u7wPnQB zamo&X9v_gfc6jI~6hz!oH-io(FJ40X=f zUSaq?uKf;2wU<0DK>QI}LX&`5$xFUYskOWc(+y>sm4EKpPJ~}#12`=LIFS%ojmhA5 z{=k0r{uoZ<{h2|^-Ka;vZ`Bj*x_%QJ(X|z*yU-rtdQ%YMxn@EZ)+mJ3W9e7N@DYva z;tpDwG|ljMXr7@%7|>fI6-@q2S*>~V8;~;b61cyq3iomCT_}$iufn3G?3$De-O*GK zxpS7&`qReVj+2ZbP7m;g=yz>$hnB@2G#&2J+yR?)p@8(7!|x<~ivF-km!xeBaI|vM z+-o08R@ni^_9Xk0D6lXq^dSB>0lS~k_VNA2o9L5@ZRhm9&RIxPmIziXg}^KeH?!W+ z1R;_EeLfsw_5v!WF2Q*zuHCSi<{ug_ON^G&}pm21rn04 z+=m_6oSE*956I@?%lO7}Bz}sFXOee#WNjV-N>E%;Bv3!}@kKrMkZcW!RHVGBP7s{A zZR9#0@Ellcz?@8$?5F1UHDe|~+<-_T=*RH?6u1wBg%q|E?MN~wGAKW~4}#@SVZ-g} zSQxP?E!Uzb>Wu&Q3vp3EBOW0XGc27or70U9`{U0wE4d9liQr-}GT++_FT`x}z>&R) z-}-~k2;+WL62w^cgW10g`#)%e%3RDIYP*W9hGFJA2}K1Kc~OJ8%lCOZV?X)?G0BrX zTLH`xb3%8wZvrE?NKfZS96wxyQ_1RG-wm{b;`o4kYawGQ``3i%s$DFCSx@lgdC=Aj zg$|Pn{su8(Jg>t&UC69Q!%hp!r5sUvsEhJJRm}SvvMq+na-m;2=O#1vg4FN;iS4L8 zVW;Nx#ahl}TEJ%Uk78dZ2m*y5Nzn`g0CWLFRvs&`nt@+Ym~jI9&OYViT|jwn)cNn6 zn9MQ^^#}=$Y*y{5Wv>NM0#6&*S^?2Z$mC?bHF1A2Yw@nPnBa=4nf01M@z=K%NItR* zm_p#F{^OlCwptB7)C^{sB$?00*uLBch6tKh@kXE*e_>V~ zWz2SxFp+huA_<{It%jBP#P>!}2iJ-5k3JAzlmndEVP3!<}cs(&tUTzxrO=XbYsC0%LkQvy(1{zu8jAV z)Wt=;prr{$#RY~^*1WFDW%F?e=q65?bhB$CIC|=pvIzIa)x2woM)Mb*XHwwWs-$@8<1AmHyLpb7j&JdV@2!W8VX~QEFZfor?8JMIB zlqDe$B_TjAHEdm$2Z^(@qk{cJIv-)4UGLH?CIz`JJVo7b!i$+J6JVpa5nK_mky_8_G-wlFl z)v=OE?!V6Sgwi)8m( z6GF;s#5d&L`6Ye8K56>(sT`u}r4+o&hRpXCWWGdT3X2q4T@p>c?*^sKx!?%Lz^U=n z`y*w!W<#8yNzG{vbZScoDUM_1OHE6DH}Hx4tvs?ZCjmLyZ{1e4d?p?98H>E$gN(&`1ww+Nkx8inIfkM|efg6!(HZWM(=- z=|WcE%ym$QAUB}hn5YJ|zQ*v!KCzV`9Op$@++r~0G;fW>LsMQ9PJA*OqRl=ku*O5k zVBWGfZ1Qd1X=Tk0!9nnmjW_P#8<6>Mb$rF!LdCzp#k*w z9B4xB@J}j5o=IkXE;Xs8CR3mw=gz|mz#Iz*d3oJ14Q=h*tp==tnoIh#$H2QlV{`?n zyNaY^U5YYHUlX2SpHkV?5rT{XiZ;bM_hj4nv16gyG9O~~UzmhV%NOJV}}QVSazw>^^+pVQT{C?FTp#hXP8;iMUc&klzPY zh0GbCp%?FWlRz5!-#oTlWlZXnG@DoTQPZpeP4St3^HPT!aSO)G}Y^G#S6`@?#@Re7>)PiO=(WgfSjSi_d>8 zlq8PW2QUM(o(O?$O#Map`X?{5Z7F}SoS$M=+t+|`=0igv>j;9!JlIzV#+4h^m5~p8 zxvi6Y^hVMlJyS$wM&(!-!Q0O4f-e99VFVxnUw}a)Sf1*ilx9@HynQm4H7jcC7tZv zjZFy9s#QVq6ry!kDEMxeY?JIpYl6uLlZ755K|Ap}r^S~@(B7xyz{Uf182tz4hzs)V z9B*fV(n5|B7+9P*_fhs2|5>ca(v<4Y3pAI(vBs5U5_nEG|$b z)pik=p=jx9+rFEE#YP2cvs7P%6E{$3xD8@T+OxHtvLJq0v7-U9qUxL&)1#t3^^ys8AgVw(5L(t3CMW^1*LylWch&4nBW!td>jw{2U}^e ze+wy_gfXF22J-~)Pmsqs{=;i8<-q-QZKA5-w%F@!x3LS?pa3Xu7JyPgNeBwkHZ1=p zp;p^xr^tSS%%#FUy$1S~Z%bL-Jz96x{B)L@!N@x|gxOm{^(}@OBo;JVU$`C%^m8Mn zu@~W&@L7}RxEb_-Hp zlFU+|I&+^g0L1sdFfxo}bV39~N!YgXFx=MbWOT9!Cc9?>o-=7UWV^2iC23}mA zK=cv;@>q?(uO8I+S98xL^+^vLPb8-RyK(I;T#EYGx7-hpZrOza6(*s^Ht6t1n#Js* z%cTVt`B9D>zQ?2j0Wirrs9VUK0r= z%rS}!aXuOb?n=*mH(`1R*Okv=1MIcn=zlY@(6x~pAEqZKb~ROtex0T z=hBNoCvEo?m~dc0EK*XgAbCfAF#tOp(z1(?<%dPFt#Vlt@74a)p&>r#0@;qI*&unB z%WaY)$eh`c;uC&ULxX8PkIfDUeeOdT0#Y>=SR}zcOIWzetDFFJ34L4>$#q5rD2@cd z&RnPyYTaax@8U!->0u-wQAuBGsz^U-{p$@TEi`hF5y9Zw;@y;f(D1Vj$mBFDDGZsA z56!+sZlGIIGXj!gjH@*f3?uc6P)^Lm4=W-_Fdb=k^VR}^k%u2Y3vd>}(q^Jz_aP*8Qoz_Nz5-`hVXF}NCIlzrdxKUxLes$-yaLFIzQ5gCe zh|3Xw8vA)5NfZOYVISLNJVf8;_r(_FOtSMO&VtKK8qgbfE%SNXQ9zMZBU(>Et2U4> zW>Rk}A4H?i>`x*|@xckmGKGNeBlYYIgz*gV*CkO2S4$Jx(>HBwMO zxhHmuVEA8|=q^4;Cp=vQ19JxkcV=*5bn;qfvscw zo@QD2K2_b<;>&|oS&2fFH|eqN?9 zd)g2)xg)9da5&!`;}3ZNB`WMIT$6F`HOZYx%zZu=Msef61Z2;dLk8KSoS8!?IwYm6 zL9u#z7jjU5ZyRo&CgKj&!p)Co=`3J z8-76T*%@Cxu8Pw=kh!A*nd@T#Crpmd1lP*P5_$_679um4&dCfFO6OAB*lmnM5D_{D zx=~~-@)iT|5=B7VRlUC4os+#MJ#zxJ{k#fo-1H;HtpHbeTA=l~A1w9>`VxTBWKr~! zvMq_*#W_}Nes6xa35f!AxqzMk;rF*gXL=}wo)QqzK{eYEVK}sUs8PagXudi@gbJjbXqX#56f9><)xE*Y1aF9-B>I=7>ba}&f+DZVU9xy;^a=YVjB8~B zJT5WV1qppG3zniv%WZuR74SjpP=kct78CAP0Xk6O=^71ZXZN59W5ar|ksO~>8Yyt_ zDnRXA{@j{l)fZs(G;5pbwv?MCXVWLtFmy3j>*~1%0Um~Cm0*b5PfV-j5TKXJoId*y z<^p~!11QL5!PoLlW;6DJGd^o-R04P2dC=lD%#6|M{Q#h<$lTFP0sV`5B6Zz1#Baax-u1}BM=9zz; z&JZ1j&ZXWZi40mIE>C+X0VNLqTN43PkmoCIo{#MwA<_B5Z1E6|tG{b%%G_(o_{Ly0 z(LdWT&Cj)wL@Esp+hpitAb>A1tSS(Guf_*TjRB$7gG%JH42nD0es{~tzO0}ud_(Rx zkZ^O9%*Mg)>BXLbGUe$m4JRaT0w7Bz@MaW!MDb6xSB4kbg5duQ!wKerk{DdANX|J6 z9ZxHH#WfR-xdb{OK=2*HP_dwj2I_d_u1jOsGmkAx9m!{yn<1t}`R;-; zCLeQZp%S`;Q0EeF6V`U-nU+kx2p{n|1sa;?YxQAKgZZe_{sfg)E)6s=0-_J$v0%Fq zO2_*Rl@y*#h~W27r1T3D>O~4zC9?-Pm zH+)Mx->G)1JWXOA$A4S^Vt3Y0a+qXE0TVtZwRys5J}{j-EO;w z$!5zgnAz?%I)4`3J#uq;LFjWThJhZ{ML0wT_HNt?qq0S#lOM9F2dqXEC|aN;XIO83K=)n{?XK-I`CcG^Y*83P@p_QfeU0N zd(_WUl`qDbO*wxZ_5#%F`Sujbtr!T7k?mT0a;*(s;vC`77&QTfOG?fE|8#a69idzi zG$D0QzR@JORYIG9jMrB)@8wA|MwkQSx%2!`Fq| z&R1dX>WL?9qw*Nz{C~B;!2KChjEPe?n6{6rmb~sW4{7bGlLuSl+h5Kjm(YqpZQ(Le zRPvr5GOm)xxsQ6OxGmQwiau*;UK%cAJT+TEgZsW)0e@J;zkS$yZZOMx+1{{6O=4{z z8_pWy834YHj$gZ%qIFsPi;+RwoA>G!DO%FsJb6{cn6sfPI}&cp=t^VDb|wV*st!%o`lK%Qu0qt~IXd7F zD7mFlMmx&7*{>G2w;2o~ef5{$gs|NSaK9nob8wKS6aDmg1 z3*_ti>vC1Rd$Pb*{nm)U!+$b3Cb{!O>;RR_W-UW~+%^6E_Cq(AKis?&!Q4m+a28e2 zp>IN@(+jGWk<$LrmKR^2sQkFv4cHm6)w0uc4x8MpuFK8gWYX__hK_Q*6hMKM?pGcE zgc;ulR1BAjC=QyExrtvramPzKf1zIr}V3tcM%u4mFl8A46bn-N!yTFIG5h>LX?oSoYGzL0=w5U8@# zWw-Bdb!|3d9=R$^+g5%3tfLqS{6#2o|M-= kv}&?Qa8ZP1>Y`sbAwk{og&pYB;} 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 z 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 literal 0 HcmV?d00001 diff --git a/apps/emqx_s3/rebar.config b/apps/emqx_s3/rebar.config new file mode 100644 index 000000000..f8e4d4e42 --- /dev/null +++ b/apps/emqx_s3/rebar.config @@ -0,0 +1,6 @@ +{deps, [ + {emqx, {path, "../../apps/emqx"}}, + {erlcloud, {git, "https://github.com/savonarola/erlcloud", {tag, "3.6.7-emqx-1"}}} +]}. + +{project_plugins, [erlfmt]}. diff --git a/apps/emqx_s3/src/emqx_s3.app.src b/apps/emqx_s3/src/emqx_s3.app.src new file mode 100644 index 000000000..7864ffb29 --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3.app.src @@ -0,0 +1,14 @@ +{application, emqx_s3, [ + {description, "EMQX S3"}, + {vsn, "5.0.6"}, + {modules, []}, + {registered, [emqx_s3_sup]}, + {applications, [ + kernel, + stdlib, + gproc, + erlcloud, + ehttpc + ]}, + {mod, {emqx_s3_app, []}} +]}. diff --git a/apps/emqx_s3/src/emqx_s3.erl b/apps/emqx_s3/src/emqx_s3.erl new file mode 100644 index 000000000..6d2577dca --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3.erl @@ -0,0 +1,65 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3). + +-include_lib("emqx/include/types.hrl"). + +-export([ + start_profile/2, + stop_profile/1, + update_profile/2, + start_uploader/2, + with_client/2 +]). + +-export_type([ + profile_id/0, + profile_config/0 +]). + +-type profile_id() :: term(). + +%% TODO: define fields +-type profile_config() :: map(). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +-spec start_profile(profile_id(), profile_config()) -> ok_or_error(term()). +start_profile(ProfileId, ProfileConfig) -> + case emqx_s3_sup:start_profile(ProfileId, ProfileConfig) of + {ok, _} -> + ok; + {error, _} = Error -> + Error + end. + +-spec stop_profile(profile_id()) -> ok_or_error(term()). +stop_profile(ProfileId) -> + emqx_s3_sup:stop_profile(ProfileId). + +-spec update_profile(profile_id(), profile_config()) -> ok_or_error(term()). +update_profile(ProfileId, ProfileConfig) -> + emqx_s3_profile_conf:update_config(ProfileId, ProfileConfig). + +-spec start_uploader(profile_id(), emqx_s3_uploader:opts()) -> + supervisor:start_ret() | {error, profile_not_found}. +start_uploader(ProfileId, Opts) -> + emqx_s3_profile_uploader_sup:start_uploader(ProfileId, Opts). + +-spec with_client(profile_id(), fun((emqx_s3_client:client()) -> Result)) -> + {error, profile_not_found} | Result. +with_client(ProfileId, Fun) when is_function(Fun, 1) -> + case emqx_s3_profile_conf:checkout_config(ProfileId) of + {ok, ClientConfig, _UploadConfig} -> + try + Fun(emqx_s3_client:create(ClientConfig)) + after + emqx_s3_profile_conf:checkin_config(ProfileId) + end; + {error, _} = Error -> + Error + end. diff --git a/apps/emqx_s3/src/emqx_s3_app.erl b/apps/emqx_s3/src/emqx_s3_app.erl new file mode 100644 index 000000000..8d8b0f7b9 --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_app.erl @@ -0,0 +1,16 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_app). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_Type, _Args) -> + {ok, Sup} = emqx_s3_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok. diff --git a/apps/emqx_s3/src/emqx_s3_client.erl b/apps/emqx_s3/src/emqx_s3_client.erl new file mode 100644 index 000000000..01d677922 --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_client.erl @@ -0,0 +1,293 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_client). + +-include_lib("emqx/include/types.hrl"). +-include_lib("emqx/include/logger.hrl"). +-include_lib("erlcloud/include/erlcloud_aws.hrl"). + +-compile(nowarn_export_all). +-compile(export_all). + +-export([ + create/1, + + put_object/3, + + start_multipart/2, + upload_part/5, + complete_multipart/4, + abort_multipart/3, + list/2, + + format/1 +]). + +-export_type([client/0]). + +-type s3_bucket_acl() :: + private + | public_read + | public_read_write + | authenticated_read + | bucket_owner_read + | bucket_owner_full_control. + +-type headers() :: #{binary() => binary()}. + +-type key() :: string(). +-type part_number() :: non_neg_integer(). +-type upload_id() :: string(). +-type etag() :: string(). + +-type upload_options() :: list({acl, s3_bucket_acl()}). + +-opaque client() :: #{ + aws_config := aws_config(), + options := upload_options(), + bucket := string(), + headers := headers() +}. + +-type config() :: #{ + scheme := string(), + host := string(), + port := part_number(), + bucket := string(), + headers := headers(), + acl := s3_bucket_acl(), + access_key_id := string() | undefined, + secret_access_key := string() | undefined, + http_pool := ecpool:pool_name(), + request_timeout := timeout() +}. + +-type s3_options() :: list({string(), string()}). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +-spec create(config()) -> client(). +create(Config) -> + #{ + aws_config => aws_config(Config), + upload_options => upload_options(Config), + bucket => maps:get(bucket, Config), + headers => headers(Config) + }. + +-spec put_object(client(), key(), iodata()) -> ok_or_error(term()). +put_object( + #{bucket := Bucket, upload_options := Options, headers := Headers, aws_config := AwsConfig}, + Key, + Value +) -> + try erlcloud_s3:put_object(Bucket, Key, Value, Options, Headers, AwsConfig) of + Props when is_list(Props) -> + ok + catch + error:{aws_error, Reason} -> + ?SLOG(debug, #{msg => "put_object_fail", key => Key, reason => Reason}), + {error, Reason} + end. + +-spec start_multipart(client(), key()) -> ok_or_error(upload_id(), term()). +start_multipart( + #{bucket := Bucket, upload_options := Options, headers := Headers, aws_config := AwsConfig}, + Key +) -> + case erlcloud_s3:start_multipart(Bucket, Key, Options, Headers, AwsConfig) of + {ok, Props} -> + {ok, proplists:get_value(uploadId, Props)}; + {error, Reason} -> + ?SLOG(debug, #{msg => "start_multipart_fail", key => Key, reason => Reason}), + {error, Reason} + end. + +-spec upload_part(client(), key(), upload_id(), part_number(), iodata()) -> + ok_or_error(etag(), term()). +upload_part( + #{bucket := Bucket, headers := Headers, aws_config := AwsConfig}, + Key, + UploadId, + PartNumber, + Value +) -> + case erlcloud_s3:upload_part(Bucket, Key, UploadId, PartNumber, Value, Headers, AwsConfig) of + {ok, Props} -> + {ok, proplists:get_value(etag, Props)}; + {error, Reason} -> + ?SLOG(debug, #{msg => "upload_part_fail", key => Key, reason => Reason}), + {error, Reason} + end. + +-spec complete_multipart(client(), key(), upload_id(), [etag()]) -> ok_or_error(term()). +complete_multipart( + #{bucket := Bucket, headers := Headers, aws_config := AwsConfig}, Key, UploadId, ETags +) -> + case erlcloud_s3:complete_multipart(Bucket, Key, UploadId, ETags, Headers, AwsConfig) of + ok -> + ok; + {error, Reason} -> + ?SLOG(debug, #{msg => "complete_multipart_fail", key => Key, reason => Reason}), + {error, Reason} + end. + +-spec abort_multipart(client(), key(), upload_id()) -> ok_or_error(term()). +abort_multipart(#{bucket := Bucket, headers := Headers, aws_config := AwsConfig}, Key, UploadId) -> + case erlcloud_s3:abort_multipart(Bucket, Key, UploadId, [], Headers, AwsConfig) of + ok -> + ok; + {error, Reason} -> + ?SLOG(debug, #{msg => "abort_multipart_fail", key => Key, reason => Reason}), + {error, Reason} + end. + +-spec list(client(), s3_options()) -> ok_or_error(term()). +list(#{bucket := Bucket, aws_config := AwsConfig}, Options) -> + try + {ok, erlcloud_s3:list_objects(Bucket, Options, AwsConfig)} + catch + error:{aws_error, Reason} -> + ?SLOG(debug, #{msg => "list_objects_fail", bucket => Bucket, reason => Reason}), + {error, Reason} + end. + +-spec format(client()) -> term(). +format(#{aws_config := AwsConfig} = Client) -> + Client#{aws_config => AwsConfig#aws_config{secret_access_key = "***"}}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +upload_options(Config) -> + [ + {acl, maps:get(acl, Config)} + ]. + +headers(#{headers := Headers}) -> + maps:to_list(Headers). + +aws_config(#{ + scheme := Scheme, + host := Host, + port := Port, + headers := Headers, + access_key_id := AccessKeyId, + secret_access_key := SecretAccessKey, + http_pool := HttpPool, + request_timeout := Timeout +}) -> + #aws_config{ + s3_scheme = Scheme, + s3_host = Host, + s3_port = Port, + s3_bucket_access_method = path, + + access_key_id = AccessKeyId, + secret_access_key = SecretAccessKey, + + http_client = request_fun(Headers, HttpPool), + timeout = Timeout + }. + +-type http_headers() :: [{binary(), binary()}]. +-type http_pool() :: term(). + +-spec request_fun(http_headers(), http_pool()) -> erlcloud_httpc:request_fun(). +request_fun(CustomHeaders, HttpPool) -> + fun(Url, Method, Headers, Body, Timeout, _Config) -> + with_path_and_query_only(Url, fun(PathQuery) -> + JoinedHeaders = join_headers(Headers, CustomHeaders), + Request = make_request(Method, PathQuery, JoinedHeaders, Body), + ehttpc_request(HttpPool, Method, Request, Timeout) + end) + end. + +ehttpc_request(HttpPool, Method, Request, Timeout) -> + try ehttpc:request(HttpPool, Method, Request, Timeout) of + {ok, StatusCode, RespHeaders} -> + {ok, {{StatusCode, undefined}, string_headers(RespHeaders), undefined}}; + {ok, StatusCode, RespHeaders, RespBody} -> + {ok, {{StatusCode, undefined}, string_headers(RespHeaders), RespBody}}; + {error, Reason} -> + ?SLOG(error, #{ + msg => "s3_ehttpc_request_fail", + reason => Reason, + timeout => Timeout, + pool => HttpPool, + method => Method + }), + {error, Reason} + catch + error:badarg -> + ?SLOG(error, #{ + msg => "s3_ehttpc_request_fail", + reason => badarg, + timeout => Timeout, + pool => HttpPool, + method => Method + }), + {error, no_ehttpc_pool}; + error:Reason -> + ?SLOG(error, #{ + msg => "s3_ehttpc_request_fail", + reason => Reason, + timeout => Timeout, + pool => HttpPool, + method => Method + }), + {error, Reason} + end. + +-define(IS_BODY_EMPTY(Body), (Body =:= undefined orelse Body =:= <<>>)). +-define(NEEDS_BODY(Method), (Method =:= get orelse Method =:= head orelse Method =:= delete)). + +make_request(Method, PathQuery, Headers, Body) when + ?IS_BODY_EMPTY(Body) andalso ?NEEDS_BODY(Method) +-> + {PathQuery, Headers}; +make_request(_Method, PathQuery, Headers, Body) when ?IS_BODY_EMPTY(Body) -> + {PathQuery, [{<<"content-length">>, <<"0">>} | Headers], <<>>}; +make_request(_Method, PathQuery, Headers, Body) -> + {PathQuery, Headers, Body}. + +format_request({PathQuery, Headers, _Body}) -> {PathQuery, Headers, <<"...">>}. + +join_headers(Headers, CustomHeaders) -> + MapHeaders = lists:foldl( + fun({K, V}, MHeaders) -> + maps:put(to_binary(K), V, MHeaders) + end, + #{}, + Headers ++ maps:to_list(CustomHeaders) + ), + maps:to_list(MapHeaders). + +with_path_and_query_only(Url, Fun) -> + case string:split(Url, "//", leading) of + [_Scheme, UrlRem] -> + case string:split(UrlRem, "/", leading) of + [_HostPort, PathQuery] -> + Fun([$/ | PathQuery]); + _ -> + {error, {invalid_url, Url}} + end; + _ -> + {error, {invalid_url, Url}} + end. + +to_binary(Val) when is_list(Val) -> list_to_binary(Val); +to_binary(Val) when is_binary(Val) -> Val. + +string_headers(Hdrs) -> + [{string:to_lower(to_list_string(K)), to_list_string(V)} || {K, V} <- Hdrs]. + +to_list_string(Val) when is_binary(Val) -> + binary_to_list(Val); +to_list_string(Val) when is_list(Val) -> + Val. diff --git a/apps/emqx_s3/src/emqx_s3_profile_conf.erl b/apps/emqx_s3/src/emqx_s3_profile_conf.erl new file mode 100644 index 000000000..09e945edc --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_profile_conf.erl @@ -0,0 +1,390 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_profile_conf). + +-behaviour(gen_server). + +-include_lib("emqx/include/logger.hrl"). +-include_lib("emqx/include/types.hrl"). + +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +-export([ + start_link/2, + child_spec/2 +]). + +-export([ + checkout_config/1, + checkout_config/2, + checkin_config/1, + checkin_config/2, + + update_config/2, + update_config/3 +]). + +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). + +%% For test purposes +-export([ + client_config/2, + start_http_pool/2, + id/1 +]). + +-define(DEFAULT_CALL_TIMEOUT, 5000). + +-define(DEFAULT_HTTP_POOL_TIMEOUT, 60000). +-define(DEAFULT_HTTP_POOL_CLEANUP_INTERVAL, 60000). + +-spec child_spec(emqx_s3:profile_id(), emqx_s3:profile_config()) -> supervisor:child_spec(). +child_spec(ProfileId, ProfileConfig) -> + #{ + id => ProfileId, + start => {?MODULE, start_link, [ProfileId, ProfileConfig]}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [?MODULE] + }. + +-spec start_link(emqx_s3:profile_id(), emqx_s3:profile_config()) -> gen_server:start_ret(). +start_link(ProfileId, ProfileConfig) -> + gen_server:start_link(?MODULE, [ProfileId, ProfileConfig], []). + +-spec update_config(emqx_s3:profile_id(), emqx_s3:profile_config()) -> ok_or_error(term()). +update_config(ProfileId, ProfileConfig) -> + update_config(ProfileId, ProfileConfig, ?DEFAULT_CALL_TIMEOUT). + +-spec update_config(emqx_s3:profile_id(), emqx_s3:profile_config(), timeout()) -> + ok_or_error(term()). +update_config(ProfileId, ProfileConfig, Timeout) -> + case gproc:where({n, l, id(ProfileId)}) of + undefined -> + {error, profile_not_found}; + Pid -> + gen_server:call(Pid, {update_config, ProfileConfig}, Timeout) + end. + +-spec checkout_config(emqx_s3:profile_id()) -> + {ok, emqx_s3_client:config(), emqx_s3_uploader:config()} | {error, profile_not_found}. +checkout_config(ProfileId) -> + checkout_config(ProfileId, ?DEFAULT_CALL_TIMEOUT). + +-spec checkout_config(emqx_s3:profile_id(), timeout()) -> + {ok, emqx_s3_client:config(), emqx_s3_uploader:config()} | {error, profile_not_found}. +checkout_config(ProfileId, Timeout) -> + case gproc:where({n, l, id(ProfileId)}) of + undefined -> + {error, profile_not_found}; + Pid -> + gen_server:call(Pid, {checkout_config, self()}, Timeout) + end. + +-spec checkin_config(emqx_s3:profile_id()) -> ok | {error, profile_not_found}. +checkin_config(ProfileId) -> + checkin_config(ProfileId, ?DEFAULT_CALL_TIMEOUT). + +-spec checkin_config(emqx_s3:profile_id(), timeout()) -> ok | {error, profile_not_found}. +checkin_config(ProfileId, Timeout) -> + case gproc:where({n, l, id(ProfileId)}) of + undefined -> + {error, profile_not_found}; + Pid -> + gen_server:call(Pid, {checkin_config, self()}, Timeout) + end. + +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- + +init([ProfileId, ProfileConfig]) -> + _ = process_flag(trap_exit, true), + ok = cleanup_orphaned_pools(ProfileId), + case start_http_pool(ProfileId, ProfileConfig) of + {ok, PoolName} -> + true = gproc:reg({n, l, id(ProfileId)}, ignored), + HttpPoolCleanupInterval = http_pool_cleanup_interval(ProfileConfig), + {ok, #{ + profile_id => ProfileId, + profile_config => ProfileConfig, + client_config => client_config(ProfileConfig, PoolName), + uploader_config => uploader_config(ProfileConfig), + pool_name => PoolName, + pool_clients => emqx_s3_profile_http_pool_clients:create_table(), + %% We don't expose these options to users currently, but use in tests + http_pool_timeout => http_pool_timeout(ProfileConfig), + http_pool_cleanup_interval => HttpPoolCleanupInterval, + + outdated_pool_cleanup_tref => erlang:send_after( + HttpPoolCleanupInterval, self(), cleanup_outdated + ) + }}; + {error, Reason} -> + {stop, Reason} + end. + +handle_call( + {checkout_config, Pid}, + _From, + #{ + client_config := ClientConfig, + uploader_config := UploaderConfig + } = State +) -> + ok = register_client(Pid, State), + {reply, {ok, ClientConfig, UploaderConfig}, State}; +handle_call({checkin_config, Pid}, _From, State) -> + ok = unregister_client(Pid, State), + {reply, ok, State}; +handle_call( + {update_config, NewProfileConfig}, + _From, + #{profile_id := ProfileId} = State +) -> + case update_http_pool(ProfileId, NewProfileConfig, State) of + {ok, PoolName} -> + NewState = State#{ + profile_config => NewProfileConfig, + client_config => client_config(NewProfileConfig, PoolName), + uploader_config => uploader_config(NewProfileConfig), + http_pool_timeout => http_pool_timeout(NewProfileConfig), + http_pool_cleanup_interval => http_pool_cleanup_interval(NewProfileConfig), + pool_name => PoolName + }, + {reply, ok, NewState}; + {error, Reason} -> + {reply, {error, Reason}, State} + end; +handle_call(_Request, _From, State) -> + {reply, {error, not_implemented}, State}. + +handle_cast(_Request, State) -> + {noreply, State}. + +handle_info({'DOWN', _Ref, process, Pid, _Reason}, State) -> + ok = unregister_client(Pid, State), + {noreply, State}; +handle_info(cleanup_outdated, #{http_pool_cleanup_interval := HttpPoolCleanupInterval} = State0) -> + %% Maybe cleanup asynchoronously + ok = cleanup_outdated_pools(State0), + State1 = State0#{ + outdated_pool_cleanup_tref => erlang:send_after( + HttpPoolCleanupInterval, self(), cleanup_outdated + ) + }, + {noreply, State1}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, #{profile_id := ProfileId}) -> + lists:foreach( + fun(PoolName) -> + ok = stop_http_pool(ProfileId, PoolName) + end, + emqx_s3_profile_http_pools:all(ProfileId) + ). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +id(ProfileId) -> + {?MODULE, ProfileId}. + +client_config(ProfileConfig, PoolName) -> + HTTPOpts = maps:get(transport_options, ProfileConfig, #{}), + #{ + scheme => scheme(HTTPOpts), + host => maps:get(host, ProfileConfig), + port => maps:get(port, ProfileConfig), + headers => maps:get(headers, HTTPOpts, #{}), + acl => maps:get(acl, ProfileConfig), + bucket => maps:get(bucket, ProfileConfig), + access_key_id => maps:get(access_key_id, ProfileConfig, undefined), + secret_access_key => maps:get(secret_access_key, ProfileConfig, undefined), + request_timeout => maps:get(request_timeout, HTTPOpts, undefined), + http_pool => PoolName + }. + +uploader_config(#{max_part_size := MaxPartSize, min_part_size := MinPartSize} = _ProfileConfig) -> + #{ + min_part_size => MinPartSize, + max_part_size => MaxPartSize + }. + +scheme(#{ssl := #{enable := true}}) -> "https://"; +scheme(_TransportOpts) -> "http://". + +start_http_pool(ProfileId, ProfileConfig) -> + HttpConfig = http_config(ProfileConfig), + PoolName = pool_name(ProfileId), + case do_start_http_pool(PoolName, HttpConfig) of + ok -> + ok = emqx_s3_profile_http_pools:register(ProfileId, PoolName), + ok = ?tp(debug, "s3_start_http_pool", #{pool_name => PoolName, profile_id => ProfileId}), + {ok, PoolName}; + {error, _} = Error -> + Error + end. + +update_http_pool(ProfileId, ProfileConfig, #{pool_name := OldPoolName} = State) -> + HttpConfig = http_config(ProfileConfig), + OldHttpConfig = old_http_config(State), + case OldHttpConfig =:= HttpConfig of + true -> + {ok, OldPoolName}; + false -> + PoolName = pool_name(ProfileId), + case do_start_http_pool(PoolName, HttpConfig) of + ok -> + ok = set_old_pool_outdated(State), + ok = emqx_s3_profile_http_pools:register(ProfileId, PoolName), + {ok, PoolName}; + {error, _} = Error -> + Error + end + end. + +pool_name(ProfileId) -> + iolist_to_binary([ + <<"s3-http-">>, + ProfileId, + <<"-">>, + integer_to_binary(erlang:system_time(millisecond)), + <<"-">>, + integer_to_binary(erlang:unique_integer([positive])) + ]). + +old_http_config(#{profile_config := ProfileConfig}) -> http_config(ProfileConfig). + +set_old_pool_outdated(#{ + profile_id := ProfileId, pool_name := PoolName, http_pool_timeout := HttpPoolTimeout +}) -> + _ = emqx_s3_profile_http_pools:set_outdated(ProfileId, PoolName, HttpPoolTimeout), + ok. + +cleanup_orphaned_pools(ProfileId) -> + lists:foreach( + fun(PoolName) -> + ok = stop_http_pool(ProfileId, PoolName) + end, + emqx_s3_profile_http_pools:all(ProfileId) + ). + +register_client(Pid, #{profile_id := ProfileId, pool_clients := PoolClients, pool_name := PoolName}) -> + MRef = monitor(process, Pid), + ok = emqx_s3_profile_http_pool_clients:register(PoolClients, Pid, MRef, PoolName), + _ = emqx_s3_profile_http_pools:register_client(ProfileId, PoolName), + ok. + +unregister_client( + Pid, + #{ + profile_id := ProfileId, pool_clients := PoolClients, pool_name := PoolName + } +) -> + case emqx_s3_profile_http_pool_clients:unregister(PoolClients, Pid) of + undefined -> + ok; + {MRef, PoolName} -> + true = erlang:demonitor(MRef, [flush]), + _ = emqx_s3_profile_http_pools:unregister_client(ProfileId, PoolName), + ok; + {MRef, OutdatedPoolName} -> + true = erlang:demonitor(MRef, [flush]), + ClientNum = emqx_s3_profile_http_pools:unregister_client(ProfileId, OutdatedPoolName), + maybe_stop_outdated_pool(ProfileId, OutdatedPoolName, ClientNum) + end. + +maybe_stop_outdated_pool(ProfileId, OutdatedPoolName, 0) -> + ok = stop_http_pool(ProfileId, OutdatedPoolName); +maybe_stop_outdated_pool(_ProfileId, _OutdatedPoolName, _ClientNum) -> + ok. + +cleanup_outdated_pools(#{profile_id := ProfileId}) -> + lists:foreach( + fun(PoolName) -> + ok = stop_http_pool(ProfileId, PoolName) + end, + emqx_s3_profile_http_pools:outdated(ProfileId) + ). + +%%-------------------------------------------------------------------- +%% HTTP Pool implementation dependent functions +%%-------------------------------------------------------------------- + +http_config( + #{ + host := Host, + port := Port, + transport_options := #{ + pool_type := PoolType, + pool_size := PoolSize, + enable_pipelining := EnablePipelining, + connect_timeout := ConnectTimeout + } = HTTPOpts + } +) -> + {Transport, TransportOpts} = + case scheme(HTTPOpts) of + "http://" -> + {tcp, []}; + "https://" -> + SSLOpts = emqx_tls_lib:to_client_opts(maps:get(ssl, HTTPOpts)), + {tls, SSLOpts} + end, + NTransportOpts = emqx_misc:ipv6_probe(TransportOpts), + [ + {host, Host}, + {port, Port}, + {connect_timeout, ConnectTimeout}, + {keepalive, 30000}, + {pool_type, PoolType}, + {pool_size, PoolSize}, + {transport, Transport}, + {transport_opts, NTransportOpts}, + {enable_pipelining, EnablePipelining} + ]. + +http_pool_cleanup_interval(ProfileConfig) -> + maps:get( + http_pool_cleanup_interval, ProfileConfig, ?DEAFULT_HTTP_POOL_CLEANUP_INTERVAL + ). + +http_pool_timeout(ProfileConfig) -> + maps:get( + http_pool_timeout, ProfileConfig, ?DEFAULT_HTTP_POOL_TIMEOUT + ). + +stop_http_pool(ProfileId, PoolName) -> + case ehttpc_sup:stop_pool(PoolName) of + ok -> + ok; + {error, Reason} -> + ?SLOG(error, #{msg => "ehttpc_pool_stop_fail", pool_name => PoolName, reason => Reason}), + ok + end, + ok = emqx_s3_profile_http_pools:unregister(ProfileId, PoolName), + ok = ?tp(debug, "s3_stop_http_pool", #{pool_name => PoolName}). + +do_start_http_pool(PoolName, HttpConfig) -> + case ehttpc_sup:start_pool(PoolName, HttpConfig) of + {ok, _} -> + ok; + {error, _} = Error -> + Error + end. diff --git a/apps/emqx_s3/src/emqx_s3_profile_http_pool_clients.erl b/apps/emqx_s3/src/emqx_s3_profile_http_pool_clients.erl new file mode 100644 index 000000000..b4e640f7c --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_profile_http_pool_clients.erl @@ -0,0 +1,35 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_profile_http_pool_clients). + +-export([ + create_table/0, + + register/4, + unregister/2 +]). + +-define(TAB, ?MODULE). + +-spec create_table() -> ok. +create_table() -> + ets:new(?TAB, [ + private, + set + ]). + +-spec register(ets:tid(), pid(), reference(), emqx_s3_profile_http_pools:pool_name()) -> true. +register(Tab, Pid, MRef, PoolName) -> + true = ets:insert(Tab, {Pid, {MRef, PoolName}}), + ok. + +-spec unregister(ets:tid(), pid()) -> emqx_s3_profile_http_pools:pool_name() | undefined. +unregister(Tab, Pid) -> + case ets:take(Tab, Pid) of + [{Pid, {MRef, PoolName}}] -> + {MRef, PoolName}; + [] -> + undefined + end. diff --git a/apps/emqx_s3/src/emqx_s3_profile_http_pools.erl b/apps/emqx_s3/src/emqx_s3_profile_http_pools.erl new file mode 100644 index 000000000..e1b36c3be --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_profile_http_pools.erl @@ -0,0 +1,123 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_profile_http_pools). + +-include_lib("stdlib/include/ms_transform.hrl"). + +-export([ + create_table/0, + + register/2, + unregister/2, + + register_client/2, + unregister_client/2, + + set_outdated/3, + + outdated/1, + all/1 +]). + +-export_type([pool_name/0]). + +-define(TAB, ?MODULE). + +-type pool_name() :: ecpool:pool_name(). + +-type pool_key() :: {emqx_s3:profile_id(), pool_name()}. + +-record(pool, { + key :: pool_key(), + client_count = 0 :: integer(), + deadline = undefined :: undefined | integer(), + extra = #{} :: map() +}). + +-spec create_table() -> ok. +create_table() -> + _ = ets:new(?TAB, [ + named_table, + public, + ordered_set, + {keypos, #pool.key}, + {read_concurrency, true}, + {write_concurrency, true} + ]), + ok. + +-spec register(emqx_s3:profile_id(), pool_name()) -> + ok. +register(ProfileId, PoolName) -> + Key = key(ProfileId, PoolName), + true = ets:insert(?TAB, #pool{ + key = Key, + client_count = 0, + deadline = undefined, + extra = #{} + }), + ok. + +-spec unregister(emqx_s3:profile_id(), pool_name()) -> + ok. +unregister(ProfileId, PoolName) -> + Key = key(ProfileId, PoolName), + true = ets:delete(?TAB, Key), + ok. + +-spec register_client(emqx_s3:profile_id(), pool_name()) -> + integer(). +register_client(ProfileId, PoolName) -> + Key = key(ProfileId, PoolName), + ets:update_counter(?TAB, Key, {#pool.client_count, 1}). + +-spec unregister_client(emqx_s3:profile_id(), pool_name()) -> + integer(). +unregister_client(ProfileId, PoolName) -> + Key = key(ProfileId, PoolName), + try + ets:update_counter(?TAB, Key, {#pool.client_count, -1}) + catch + error:badarg -> + undefined + end. + +-spec set_outdated(emqx_s3:profile_id(), pool_name(), integer()) -> + ok. +set_outdated(ProfileId, PoolName, Timeout) -> + Key = key(ProfileId, PoolName), + Now = erlang:monotonic_time(millisecond), + ets:update_element(?TAB, Key, {#pool.deadline, Now + Timeout}). + +-spec outdated(emqx_s3:profile_id()) -> + [pool_name()]. +outdated(ProfileId) -> + Now = erlang:monotonic_time(millisecond), + MS = ets:fun2ms( + fun(#pool{key = {ProfileId_, PoolName}, deadline = Deadline_}) when + ProfileId_ =:= ProfileId andalso + Deadline_ =/= undefined andalso Deadline_ < Now + -> + PoolName + end + ), + ets:select(?TAB, MS). + +-spec all(emqx_s3:profile_id()) -> + [pool_name()]. +all(ProfileId) -> + MS = ets:fun2ms( + fun(#pool{key = {ProfileId_, PoolName}}) when ProfileId_ =:= ProfileId -> + PoolName + end + ), + ets:select(?TAB, MS). + +%%-------------------------------------------------------------------- +%% Helpers +%%-------------------------------------------------------------------- + +key(ProfileId, PoolName) -> + {ProfileId, PoolName}. diff --git a/apps/emqx_s3/src/emqx_s3_profile_sup.erl b/apps/emqx_s3/src/emqx_s3_profile_sup.erl new file mode 100644 index 000000000..c39fc9f4b --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_profile_sup.erl @@ -0,0 +1,48 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_profile_sup). + +-behaviour(supervisor). + +-include_lib("emqx/include/types.hrl"). + +-export([ + start_link/2, + child_spec/2 +]). + +-export([init/1]). + +-spec start_link(emqx_s3:profile_id(), emqx_s3:profile_config()) -> supervisor:start_ret(). +start_link(ProfileId, ProfileConfig) -> + supervisor:start_link(?MODULE, [ProfileId, ProfileConfig]). + +-spec child_spec(emqx_s3:profile_id(), emqx_s3:profile_config()) -> supervisor:child_spec(). +child_spec(ProfileId, ProfileConfig) -> + #{ + id => ProfileId, + start => {?MODULE, start_link, [ProfileId, ProfileConfig]}, + restart => permanent, + shutdown => 5000, + type => supervisor, + modules => [?MODULE] + }. + +%%-------------------------------------------------------------------- +%% supervisor callbacks +%%------------------------------------------------------------------- + +init([ProfileId, ProfileConfig]) -> + SupFlags = #{ + strategy => one_for_one, + intensity => 10, + period => 5 + }, + ChildSpecs = [ + %% Order matters + emqx_s3_profile_conf:child_spec(ProfileId, ProfileConfig), + emqx_s3_profile_uploader_sup:child_spec(ProfileId) + ], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_s3/src/emqx_s3_profile_uploader_sup.erl b/apps/emqx_s3/src/emqx_s3_profile_uploader_sup.erl new file mode 100644 index 000000000..1cd155a77 --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_profile_uploader_sup.erl @@ -0,0 +1,73 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_profile_uploader_sup). + +-behaviour(supervisor). + +-include_lib("emqx/include/types.hrl"). + +-export([ + start_link/1, + child_spec/1, + id/1, + start_uploader/2 +]). + +-export([init/1]). + +-export_type([id/0]). + +-type id() :: {?MODULE, emqx_s3:profile_id()}. + +-spec start_link(emqx_s3:profile_id()) -> supervisor:start_ret(). +start_link(ProfileId) -> + supervisor:start_link(?MODULE, [ProfileId]). + +-spec child_spec(emqx_s3:profile_id()) -> supervisor:child_spec(). +child_spec(ProfileId) -> + #{ + id => id(ProfileId), + start => {?MODULE, start_link, [ProfileId]}, + restart => permanent, + shutdown => 5000, + type => supervisor, + modules => [?MODULE] + }. + +-spec id(emqx_s3:profile_id()) -> id(). +id(ProfileId) -> + {?MODULE, ProfileId}. + +-spec start_uploader(emqx_s3:profile_id(), emqx_s3_uploader:opts()) -> + supervisor:start_ret() | {error, profile_not_found}. +start_uploader(ProfileId, Opts) -> + Id = id(ProfileId), + case gproc:where({n, l, Id}) of + undefined -> {error, profile_not_found}; + Pid -> supervisor:start_child(Pid, [Opts]) + end. + +%%-------------------------------------------------------------------- +%% supervisor callbacks +%%------------------------------------------------------------------- + +init([ProfileId]) -> + true = gproc:reg({n, l, id(ProfileId)}, ignored), + SupFlags = #{ + strategy => simple_one_for_one, + intensity => 10, + period => 5 + }, + ChildSpecs = [ + #{ + id => emqx_s3_uploader, + start => {emqx_s3_uploader, start_link, [ProfileId]}, + restart => temporary, + shutdown => 5000, + type => worker, + modules => [emqx_s3_uploader] + } + ], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_s3/src/emqx_s3_schema.erl b/apps/emqx_s3/src/emqx_s3_schema.erl new file mode 100644 index 000000000..ceb0d1dd4 --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_schema.erl @@ -0,0 +1,143 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_schema). + +-include_lib("typerefl/include/types.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-import(hoconsc, [mk/2, ref/1]). + +-export([roots/0, fields/1, namespace/0, tags/0]). + +-export([translate/1]). + +roots() -> + [s3]. + +namespace() -> "s3". + +tags() -> + [<<"S3">>]. + +fields(s3) -> + [ + {access_key_id, + mk( + string(), + #{ + desc => ?DESC("access_key_id"), + required => false + } + )}, + {secret_access_key, + mk( + string(), + #{ + desc => ?DESC("secret_access_key"), + required => false + } + )}, + {bucket, + mk( + string(), + #{ + desc => ?DESC("bucket"), + required => true + } + )}, + {host, + mk( + string(), + #{ + desc => ?DESC("host"), + required => true + } + )}, + {port, + mk( + pos_integer(), + #{ + desc => ?DESC("port"), + required => true + } + )}, + {min_part_size, + mk( + emqx_schema:bytesize(), + #{ + default => "5mb", + desc => ?DESC("min_part_size"), + required => true, + validator => fun part_size_validator/1 + } + )}, + {max_part_size, + mk( + emqx_schema:bytesize(), + #{ + default => "5gb", + desc => ?DESC("max_part_size"), + required => true, + validator => fun part_size_validator/1 + } + )}, + {acl, + mk( + hoconsc:enum([ + private, + public_read, + public_read_write, + authenticated_read, + bucket_owner_read, + bucket_owner_full_control + ]), + #{ + default => private, + desc => ?DESC("acl"), + required => true + } + )}, + {transport_options, + mk( + ref(transport_options), + #{ + desc => ?DESC("transport_options"), + required => false + } + )} + ]; +fields(transport_options) -> + props_without( + [base_url, max_retries, retry_interval, request], emqx_connector_http:fields(config) + ) ++ + props_with( + [headers, max_retries, request_timeout], emqx_connector_http:fields("request") + ). + +translate(Conf) -> + Options = #{atom_key => true}, + #{s3 := TranslatedConf} = hocon_tconf:check_plain( + emqx_s3_schema, #{<<"s3">> => Conf}, Options, [s3] + ), + TranslatedConf. + +%%-------------------------------------------------------------------- +%% Helpers +%%-------------------------------------------------------------------- + +props_with(Keys, Proplist) -> + lists:filter(fun({K, _}) -> lists:member(K, Keys) end, Proplist). + +props_without(Keys, Proplist) -> + lists:filter(fun({K, _}) -> not lists:member(K, Keys) end, Proplist). + +part_size_validator(PartSizeLimit) -> + case + PartSizeLimit >= 5 * 1024 * 1024 andalso + PartSizeLimit =< 5 * 1024 * 1024 * 1024 + of + true -> ok; + false -> {error, "must be at least 5mb and less than 5gb"} + end. diff --git a/apps/emqx_s3/src/emqx_s3_sup.erl b/apps/emqx_s3/src/emqx_s3_sup.erl new file mode 100644 index 000000000..0f6b0160b --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_sup.erl @@ -0,0 +1,47 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_sup). + +-behaviour(supervisor). + +-include_lib("emqx/include/types.hrl"). + +-export([ + start_link/0, + start_profile/2, + stop_profile/1 +]). + +-export([init/1]). + +-spec start_link() -> supervisor:start_ret(). +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +-spec start_profile(emqx_s3:profile_id(), emqx_s3:profile_config()) -> supervisor:startchild_ret(). +start_profile(ProfileId, ProfileConfig) -> + supervisor:start_child(?MODULE, emqx_s3_profile_sup:child_spec(ProfileId, ProfileConfig)). + +-spec stop_profile(emqx_s3:profile_id()) -> ok_or_error(term()). +stop_profile(ProfileId) -> + case supervisor:terminate_child(?MODULE, ProfileId) of + ok -> + supervisor:delete_child(?MODULE, ProfileId); + {error, Reason} -> + {error, Reason} + end. + +%%-------------------------------------------------------------------- +%% supervisor callbacks +%%------------------------------------------------------------------- + +init([]) -> + ok = emqx_s3_profile_http_pools:create_table(), + SupFlags = #{ + strategy => one_for_one, + intensity => 10, + period => 5 + }, + {ok, {SupFlags, []}}. diff --git a/apps/emqx_s3/src/emqx_s3_uploader.erl b/apps/emqx_s3/src/emqx_s3_uploader.erl new file mode 100644 index 000000000..4e3fe15f2 --- /dev/null +++ b/apps/emqx_s3/src/emqx_s3_uploader.erl @@ -0,0 +1,318 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_uploader). + +-include_lib("emqx/include/types.hrl"). + +-behaviour(gen_statem). + +-export([ + start_link/2, + + write/2, + complete/1, + abort/1 +]). + +-export([ + init/1, + callback_mode/0, + handle_event/4, + terminate/3, + code_change/4, + format_status/1, + format_status/2 +]). + +-export_type([opts/0, config/0]). + +-type opts() :: #{ + name := string() +}. + +-type config() :: #{ + min_part_size := pos_integer() +}. + +-type data() :: #{ + profile_id := emqx_s3:profile_id(), + client := emqx_s3_client:client(), + key := emqx_s3_client:key(), + buffer := iodata(), + buffer_size := non_neg_integer(), + min_part_size := pos_integer(), + max_part_size := pos_integer(), + upload_id := undefined | emqx_s3_client:upload_id(), + etags := [emqx_s3_client:etag()], + part_number := emqx_s3_client:part_number() +}. + +%% 5MB +-define(DEFAULT_MIN_PART_SIZE, 5242880). +%% 5GB +-define(DEFAULT_MAX_PART_SIZE, 5368709120). + +-spec start_link(emqx_s3:profile_id(), opts()) -> gen_statem:start_ret(). +start_link(ProfileId, #{key := Key} = Opts) when is_list(Key) -> + gen_statem:start_link(?MODULE, [ProfileId, Opts], []). + +-spec write(pid(), binary()) -> ok_or_error(term()). +write(Pid, WriteData) when is_binary(WriteData) -> + write(Pid, WriteData, infinity). + +-spec write(pid(), binary(), timeout()) -> ok_or_error(term()). +write(Pid, WriteData, Timeout) when is_binary(WriteData) -> + gen_statem:call(Pid, {write, wrap(WriteData)}, Timeout). + +-spec complete(pid()) -> ok_or_error(term()). +complete(Pid) -> + complete(Pid, infinity). + +-spec complete(pid(), timeout()) -> ok_or_error(term()). +complete(Pid, Timeout) -> + gen_statem:call(Pid, complete, Timeout). + +-spec abort(pid()) -> ok_or_error(term()). +abort(Pid) -> + abort(Pid, infinity). + +-spec abort(pid(), timeout()) -> ok_or_error(term()). +abort(Pid, Timeout) -> + gen_statem:call(Pid, abort, Timeout). + +%%-------------------------------------------------------------------- +%% gen_statem callbacks +%%-------------------------------------------------------------------- + +callback_mode() -> handle_event_function. + +init([ProfileId, #{key := Key}]) -> + process_flag(trap_exit, true), + {ok, ClientConfig, UploaderConfig} = emqx_s3_profile_conf:checkout_config(ProfileId), + Client = emqx_s3_client:create(ClientConfig), + {ok, upload_not_started, #{ + profile_id => ProfileId, + client => Client, + key => Key, + buffer => [], + buffer_size => 0, + min_part_size => maps:get(min_part_size, UploaderConfig, ?DEFAULT_MIN_PART_SIZE), + max_part_size => maps:get(max_part_size, UploaderConfig, ?DEFAULT_MAX_PART_SIZE), + upload_id => undefined, + etags => [], + part_number => 1 + }}. + +handle_event({call, From}, {write, WriteDataWrapped}, State, Data0) -> + WriteData = unwrap(WriteDataWrapped), + case is_valid_part(WriteData, Data0) of + true -> + handle_write(State, From, WriteData, Data0); + false -> + {keep_state_and_data, {reply, From, {error, {too_large, byte_size(WriteData)}}}} + end; +handle_event({call, From}, complete, upload_not_started, Data0) -> + case put_object(Data0) of + ok -> + {stop_and_reply, normal, {reply, From, ok}}; + {error, _} = Error -> + {stop_and_reply, Error, {reply, From, Error}, Data0} + end; +handle_event({call, From}, complete, upload_started, Data0) -> + case complete_upload(Data0) of + {ok, Data1} -> + {stop_and_reply, normal, {reply, From, ok}, Data1}; + {error, _} = Error -> + {stop_and_reply, Error, {reply, From, Error}, Data0} + end; +handle_event({call, From}, abort, upload_not_started, _Data) -> + {stop_and_reply, normal, {reply, From, ok}}; +handle_event({call, From}, abort, upload_started, Data0) -> + case abort_upload(Data0) of + ok -> + {stop_and_reply, normal, {reply, From, ok}}; + {error, _} = Error -> + {stop_and_reply, Error, {reply, From, Error}, Data0} + end. + +handle_write(upload_not_started, From, WriteData, Data0) -> + Data1 = append_buffer(Data0, WriteData), + case maybe_start_upload(Data1) of + not_started -> + {keep_state, Data1, {reply, From, ok}}; + {started, Data2} -> + case upload_part(Data2) of + {ok, Data3} -> + {next_state, upload_started, Data3, {reply, From, ok}}; + {error, _} = Error -> + {stop_and_reply, Error, {reply, From, Error}, Data2} + end; + {error, _} = Error -> + {stop_and_reply, Error, {reply, From, Error}, Data1} + end; +handle_write(upload_started, From, WriteData, Data0) -> + Data1 = append_buffer(Data0, WriteData), + case maybe_upload_part(Data1) of + {ok, Data2} -> + {keep_state, Data2, {reply, From, ok}}; + {error, _} = Error -> + {stop_and_reply, Error, {reply, From, Error}, Data1} + end. + +terminate(Reason, _State, #{client := Client, upload_id := UploadId, key := Key}) when + (UploadId =/= undefined) andalso (Reason =/= normal) +-> + emqx_s3_client:abort_multipart(Client, Key, UploadId); +terminate(_Reason, _State, _Data) -> + ok. + +code_change(_OldVsn, StateName, State, _Extra) -> + {ok, StateName, State}. + +format_status(#{data := #{client := Client} = Data} = Status) -> + Status#{ + data => Data#{ + client => emqx_s3_client:format(Client), + buffer => [<<"...">>] + } + }. + +format_status(_Opt, [PDict, State, #{client := Client} = Data]) -> + #{ + data => Data#{ + client => emqx_s3_client:format(Client), + buffer => [<<"...">>] + }, + state => State, + pdict => PDict + }. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +-spec maybe_start_upload(data()) -> not_started | {started, data()} | {error, term()}. +maybe_start_upload(#{buffer_size := BufferSize, min_part_size := MinPartSize} = Data) -> + case BufferSize >= MinPartSize of + true -> + start_upload(Data); + false -> + not_started + end. + +-spec start_upload(data()) -> {started, data()} | {error, term()}. +start_upload(#{client := Client, key := Key} = Data) -> + case emqx_s3_client:start_multipart(Client, Key) of + {ok, UploadId} -> + NewData = Data#{upload_id => UploadId}, + {started, NewData}; + {error, _} = Error -> + Error + end. + +-spec maybe_upload_part(data()) -> ok_or_error(data(), term()). +maybe_upload_part(#{buffer_size := BufferSize, min_part_size := MinPartSize} = Data) -> + case BufferSize >= MinPartSize of + true -> + upload_part(Data); + false -> + % ct:print("buffer size: ~p, max part size: ~p, no upload", [BufferSize, MinPartSize]), + {ok, Data} + end. + +-spec upload_part(data()) -> ok_or_error(data(), term()). +upload_part(#{buffer_size := 0} = Data) -> + {ok, Data}; +upload_part( + #{ + client := Client, + key := Key, + upload_id := UploadId, + buffer := Buffer, + part_number := PartNumber, + etags := ETags + } = Data +) -> + case emqx_s3_client:upload_part(Client, Key, UploadId, PartNumber, lists:reverse(Buffer)) of + {ok, ETag} -> + % ct:print("upload part ~p, etag: ~p", [PartNumber, ETag]), + NewData = Data#{ + buffer => [], + buffer_size => 0, + part_number => PartNumber + 1, + etags => [{PartNumber, ETag} | ETags] + }, + {ok, NewData}; + {error, _} = Error -> + % ct:print("upload part ~p failed: ~p", [PartNumber, Error]), + Error + end. + +-spec complete_upload(data()) -> ok_or_error(term()). +complete_upload( + #{ + client := Client, + key := Key, + upload_id := UploadId + } = Data0 +) -> + case upload_part(Data0) of + {ok, #{etags := ETags} = Data1} -> + case emqx_s3_client:complete_multipart(Client, Key, UploadId, lists:reverse(ETags)) of + ok -> + {ok, Data1}; + {error, _} = Error -> + Error + end; + {error, _} = Error -> + Error + end. + +-spec abort_upload(data()) -> ok_or_error(term()). +abort_upload( + #{ + client := Client, + key := Key, + upload_id := UploadId + } +) -> + case emqx_s3_client:abort_multipart(Client, Key, UploadId) of + ok -> + ok; + {error, _} = Error -> + Error + end. + +-spec put_object(data()) -> ok_or_error(term()). +put_object( + #{ + client := Client, + key := Key, + buffer := Buffer + } +) -> + case emqx_s3_client:put_object(Client, Key, lists:reverse(Buffer)) of + ok -> + ok; + {error, _} = Error -> + Error + end. + +-spec append_buffer(data(), binary()) -> data(). +append_buffer(#{buffer := Buffer, buffer_size := BufferSize} = Data, WriteData) -> + Data#{ + buffer => [WriteData | Buffer], + buffer_size => BufferSize + byte_size(WriteData) + }. + +-compile({inline, [wrap/1, unwrap/1]}). +wrap(Data) -> + fun() -> Data end. + +unwrap(WrappedData) -> + WrappedData(). + +is_valid_part(WriteData, #{max_part_size := MaxPartSize, buffer_size := BufferSize}) -> + BufferSize + byte_size(WriteData) =< MaxPartSize. diff --git a/apps/emqx_s3/test/certs/ca.crt b/apps/emqx_s3/test/certs/ca.crt new file mode 100644 index 000000000..8a9dafccd --- /dev/null +++ b/apps/emqx_s3/test/certs/ca.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE5DCCAswCCQCF3o0gIdaNDjANBgkqhkiG9w0BAQsFADA0MRIwEAYDVQQKDAlF +TVFYIFRlc3QxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0yMTEy +MzAwODQxMTFaFw00OTA1MTcwODQxMTFaMDQxEjAQBgNVBAoMCUVNUVggVGVzdDEe +MBwGA1UEAwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAqmqSrxyH16j63QhqGLT1UO8I+m6BM3HfnJQM8laQdtJ0 +WgHqCh0/OphH3S7v4SfF4fNJDEJWMWuuzJzU9cTqHPLzhvo3+ZHcMIENgtY2p2Cf +7AQjEqFViEDyv2ZWNEe76BJeShntdY5NZr4gIPar99YGG/Ln8YekspleV+DU38rE +EX9WzhgBr02NN9z4NzIxeB+jdvPnxcXs3WpUxzfnUjOQf/T1tManvSdRbFmKMbxl +A8NLYK3oAYm8EbljWUINUNN6loqYhbigKv8bvo5S4xvRqmX86XB7sc0SApngtNcg +O0EKn8z/KVPDskE+8lMfGMiU2e2Tzw6Rph57mQPOPtIp5hPiKRik7ST9n0p6piXW +zRLplJEzSjf40I1u+VHmpXlWI/Fs8b1UkDSMiMVJf0LyWb4ziBSZOY2LtZzWHbWj +LbNgxQcwSS29tKgUwfEFmFcm+iOM59cPfkl2IgqVLh5h4zmKJJbfQKSaYb5fcKRf +50b1qsN40VbR3Pk/0lJ0/WqgF6kZCExmT1qzD5HJES/5grjjKA4zIxmHOVU86xOF +ouWvtilVR4PGkzmkFvwK5yRhBUoGH/A9BurhqOc0QCGay1kqHQFA6se4JJS+9KOS +x8Rn1Nm6Pi7sd6Le3cKmHTlyl5a/ofKqTCX2Qh+v/7y62V1V1wnoh3ipRjdPTnMC +AwEAATANBgkqhkiG9w0BAQsFAAOCAgEARCqaocvlMFUQjtFtepO2vyG1krn11xJ0 +e7md26i+g8SxCCYqQ9IqGmQBg0Im8fyNDKRN/LZoj5+A4U4XkG1yya91ZIrPpWyF +KUiRAItchNj3g1kHmI2ckl1N//6Kpx3DPaS7qXZaN3LTExf6Ph+StE1FnS0wVF+s +tsNIf6EaQ+ZewW3pjdlLeAws3jvWKUkROc408Ngvx74zbbKo/zAC4tz8oH9ZcpsT +WD8enVVEeUQKI6ItcpZ9HgTI9TFWgfZ1vYwvkoRwNIeabYI62JKmLEo2vGfGwWKr +c+GjnJ/tlVI2DpPljfWOnQ037/7yyJI/zo65+HPRmGRD6MuW/BdPDYOvOZUTcQKh +kANi5THSbJJgZcG3jb1NLebaUQ1H0zgVjn0g3KhUV+NJQYk8RQ7rHtB+MySqTKlM +kRkRjfTfR0Ykxpks7Mjvsb6NcZENf08ZFPd45+e/ptsxpiKu4e4W4bV7NZDvNKf9 +0/aD3oGYNMiP7s+KJ1lRSAjnBuG21Yk8FpzG+yr8wvJhV8aFgNQ5wIH86SuUTmN0 +5bVzFEIcUejIwvGoQEctNHBlOwHrb7zmB6OwyZeMapdXBQ+9UDhYg8ehDqdDOdfn +wsBcnjD2MwNhlE1hjL+tZWLNwSHiD6xx3LvNoXZu2HK8Cp3SOrkE69cFghYMIZZb +T+fp6tNL6LE= +-----END CERTIFICATE----- diff --git a/apps/emqx_s3/test/emqx_s3_SUITE.erl b/apps/emqx_s3/test/emqx_s3_SUITE.erl new file mode 100644 index 000000000..287dcb597 --- /dev/null +++ b/apps/emqx_s3/test/emqx_s3_SUITE.erl @@ -0,0 +1,66 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +init_per_suite(Config) -> + {ok, _} = application:ensure_all_started(emqx_s3), + Config. + +end_per_suite(_Config) -> + ok = application:stop(emqx_s3). + +%%-------------------------------------------------------------------- +%% Test cases +%%-------------------------------------------------------------------- + +t_start_stop_update(_Config) -> + ProfileId = <<"test">>, + ProfileConfig = profile_config(), + + ?assertMatch( + ok, + emqx_s3:start_profile(ProfileId, ProfileConfig) + ), + + ?assertMatch( + {error, _}, + emqx_s3:start_profile(ProfileId, ProfileConfig) + ), + + ?assertEqual( + ok, + emqx_s3:update_profile(ProfileId, ProfileConfig) + ), + + ?assertMatch( + {error, _}, + emqx_s3:update_profile(<<"unknown">>, ProfileConfig) + ), + + ?assertEqual( + ok, + emqx_s3:stop_profile(ProfileId) + ), + + ?assertMatch( + {error, _}, + emqx_s3:stop_profile(ProfileId) + ). + +%%-------------------------------------------------------------------- +%% Helpers +%%-------------------------------------------------------------------- + +profile_config() -> + emqx_s3_test_helpers:base_config(tcp). diff --git a/apps/emqx_s3/test/emqx_s3_client_SUITE.erl b/apps/emqx_s3/test/emqx_s3_client_SUITE.erl new file mode 100644 index 000000000..3d0d7bb18 --- /dev/null +++ b/apps/emqx_s3/test/emqx_s3_client_SUITE.erl @@ -0,0 +1,104 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_client_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-define(PROFILE_ID, atom_to_binary(?MODULE)). + +all() -> + [ + {group, tcp}, + {group, tls} + ]. + +groups() -> + AllCases = emqx_common_test_helpers:all(?MODULE), + [ + {tcp, [], AllCases}, + {tls, [], AllCases} + ]. + +init_per_suite(Config) -> + {ok, _} = application:ensure_all_started(emqx_s3), + Config. + +end_per_suite(_Config) -> + ok = application:stop(emqx_s3). + +init_per_group(ConnType, Config) -> + [{conn_type, ConnType} | Config]. +end_per_group(_ConnType, _Config) -> + ok. + +init_per_testcase(_TestCase, Config0) -> + ConnType = ?config(conn_type, Config0), + + Bucket = emqx_s3_test_helpers:unique_bucket(), + TestAwsConfig = emqx_s3_test_helpers:aws_config(ConnType), + ok = erlcloud_s3:create_bucket(Bucket, TestAwsConfig), + Config1 = [ + {key, emqx_s3_test_helpers:unique_key()}, + {bucket, Bucket} + | Config0 + ], + {ok, PoolName} = emqx_s3_profile_conf:start_http_pool(?PROFILE_ID, profile_config(Config1)), + [{ehttpc_pool_name, PoolName} | Config1]. + +end_per_testcase(_TestCase, Config) -> + ok = ehttpc_sup:stop_pool(?config(ehttpc_pool_name, Config)). + +%%-------------------------------------------------------------------- +%% Test cases +%%-------------------------------------------------------------------- + +t_multipart_upload(Config) -> + Key = ?config(key, Config), + + Client = client(Config), + + {ok, UploadId} = emqx_s3_client:start_multipart(Client, Key), + + Data = data(6_000_000), + + {ok, Etag1} = emqx_s3_client:upload_part(Client, Key, UploadId, 1, Data), + {ok, Etag2} = emqx_s3_client:upload_part(Client, Key, UploadId, 2, Data), + + ok = emqx_s3_client:complete_multipart( + Client, Key, UploadId, [{1, Etag1}, {2, Etag2}] + ). + +t_simple_put(Config) -> + Key = ?config(key, Config), + + Client = client(Config), + + Data = data(6_000_000), + + ok = emqx_s3_client:put_object(Client, Key, Data). + +%%-------------------------------------------------------------------- +%% Helpers +%%-------------------------------------------------------------------- + +client(Config) -> + ClientConfig = emqx_s3_profile_conf:client_config( + profile_config(Config), ?config(ehttpc_pool_name, Config) + ), + emqx_s3_client:create(ClientConfig). + +profile_config(Config) -> + maps:put( + bucket, + ?config(bucket, Config), + emqx_s3_test_helpers:base_config(?config(conn_type, Config)) + ). + +data(Size) -> + iolist_to_binary([$a || _ <- lists:seq(1, Size)]). diff --git a/apps/emqx_s3/test/emqx_s3_profile_conf_SUITE.erl b/apps/emqx_s3/test/emqx_s3_profile_conf_SUITE.erl new file mode 100644 index 000000000..ce53525be --- /dev/null +++ b/apps/emqx_s3/test/emqx_s3_profile_conf_SUITE.erl @@ -0,0 +1,293 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_profile_conf_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +-define(assertWaitEvent(Code, EventMatch, Timeout), + ?assertMatch( + {_, {ok, EventMatch}}, + ?wait_async_action( + Code, + EventMatch, + Timeout + ) + ) +). + +all() -> emqx_common_test_helpers:all(?MODULE). + +suite() -> [{timetrap, {minutes, 1}}]. + +init_per_suite(Config) -> + {ok, _} = application:ensure_all_started(emqx_s3), + Config. + +end_per_suite(_Config) -> + ok = application:stop(emqx_s3). + +init_per_testcase(_TestCase, Config) -> + ok = snabbkaffe:start_trace(), + TestAwsConfig = emqx_s3_test_helpers:aws_config(tcp), + + Bucket = emqx_s3_test_helpers:unique_bucket(), + ok = erlcloud_s3:create_bucket(Bucket, TestAwsConfig), + + ProfileBaseConfig = emqx_s3_test_helpers:base_config(tcp), + ProfileConfig = ProfileBaseConfig#{bucket => Bucket}, + ok = emqx_s3:start_profile(profile_id(), ProfileConfig), + + [{profile_config, ProfileConfig} | Config]. + +end_per_testcase(_TestCase, _Config) -> + ok = snabbkaffe:stop(), + _ = emqx_s3:stop_profile(profile_id()). + +%%-------------------------------------------------------------------- +%% Test cases +%%-------------------------------------------------------------------- + +t_regular_outdated_pool_cleanup(Config) -> + _ = process_flag(trap_exit, true), + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + [OldPool] = emqx_s3_profile_http_pools:all(profile_id()), + + ProfileBaseConfig = ?config(profile_config, Config), + ProfileConfig = emqx_map_lib:deep_put( + [transport_options, pool_size], ProfileBaseConfig, 16 + ), + ok = emqx_s3:update_profile(profile_id(), ProfileConfig), + + ?assertEqual( + 2, + length(emqx_s3_profile_http_pools:all(profile_id())) + ), + + ?assertWaitEvent( + ok = emqx_s3_uploader:abort(Pid), + #{?snk_kind := "s3_stop_http_pool", pool_name := OldPool}, + 1000 + ), + + [NewPool] = emqx_s3_profile_http_pools:all(profile_id()), + + ?assertWaitEvent( + ok = emqx_s3:stop_profile(profile_id()), + #{?snk_kind := "s3_stop_http_pool", pool_name := NewPool}, + 1000 + ), + + ?assertEqual( + 0, + length(emqx_s3_profile_http_pools:all(profile_id())) + ). + +t_timeout_pool_cleanup(Config) -> + _ = process_flag(trap_exit, true), + + %% We restart the profile to set `http_pool_timeout` value suitable for test + ok = emqx_s3:stop_profile(profile_id()), + ProfileBaseConfig = ?config(profile_config, Config), + ProfileConfig = ProfileBaseConfig#{ + http_pool_timeout => 500, + http_pool_cleanup_interval => 100 + }, + ok = emqx_s3:start_profile(profile_id(), ProfileConfig), + + %% Start uploader + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + ok = emqx_s3_uploader:write(Pid, <<"data">>), + + [OldPool] = emqx_s3_profile_http_pools:all(profile_id()), + + NewProfileConfig = emqx_map_lib:deep_put( + [transport_options, pool_size], ProfileConfig, 16 + ), + + %% We update profile to create new pool and wait for the old one to be stopped by timeout + ?assertWaitEvent( + ok = emqx_s3:update_profile(profile_id(), NewProfileConfig), + #{?snk_kind := "s3_stop_http_pool", pool_name := OldPool}, + 1000 + ), + + %% The uploader now has no valid pool and should fail + ?assertMatch( + {error, _}, + emqx_s3_uploader:complete(Pid) + ). + +t_checkout_no_profile(_Config) -> + ?assertEqual( + {error, profile_not_found}, + emqx_s3_profile_conf:checkout_config(<<"no_such_profile">>) + ). + +t_httpc_pool_start_error(Config) -> + %% `ehhtpc_pool`s are lazy so it is difficult to trigger an error + %% passing some bad connection options. + %% So we emulate some unknown crash with `meck`. + meck:new(ehttpc_pool, [passthrough]), + meck:expect(ehttpc_pool, init, fun(_) -> meck:raise(error, badarg) end), + + ?assertMatch( + {error, _}, + emqx_s3:start_profile(<<"profile">>, ?config(profile_config, Config)) + ). + +t_httpc_pool_update_error(Config) -> + %% `ehhtpc_pool`s are lazy so it is difficult to trigger an error + %% passing some bad connection options. + %% So we emulate some unknown crash with `meck`. + meck:new(ehttpc_pool, [passthrough]), + meck:expect(ehttpc_pool, init, fun(_) -> meck:raise(error, badarg) end), + + ProfileBaseConfig = ?config(profile_config, Config), + NewProfileConfig = emqx_map_lib:deep_put( + [transport_options, pool_size], ProfileBaseConfig, 16 + ), + + ?assertMatch( + {error, _}, + emqx_s3:start_profile(<<"profile">>, NewProfileConfig) + ). + +t_orphaned_pools_cleanup(_Config) -> + ProfileId = profile_id(), + Pid = gproc:where({n, l, emqx_s3_profile_conf:id(ProfileId)}), + + %% We kill conf and wait for it to restart + %% and create a new pool + ?assertWaitEvent( + exit(Pid, kill), + #{?snk_kind := "s3_start_http_pool", profile_id := ProfileId}, + 1000 + ), + + %% We should still have only one pool + ?assertEqual( + 1, + length(emqx_s3_profile_http_pools:all(ProfileId)) + ). + +t_orphaned_pools_cleanup_non_graceful(_Config) -> + ProfileId = profile_id(), + Pid = gproc:where({n, l, emqx_s3_profile_conf:id(ProfileId)}), + + %% We stop pool, conf server should not fail when attempting to stop it once more + [PoolName] = emqx_s3_profile_http_pools:all(ProfileId), + ok = ehttpc_pool:stop_pool(PoolName), + + %% We kill conf and wait for it to restart + %% and create a new pool + ?assertWaitEvent( + exit(Pid, kill), + #{?snk_kind := "s3_start_http_pool", profile_id := ProfileId}, + 1000 + ), + + %% We should still have only one pool + ?assertEqual( + 1, + length(emqx_s3_profile_http_pools:all(ProfileId)) + ). + +t_checkout_client(Config) -> + ProfileId = profile_id(), + Key = emqx_s3_test_helpers:unique_key(), + Caller = self(), + Pid = spawn_link(fun() -> + emqx_s3:with_client( + ProfileId, + fun(Client) -> + receive + put_object -> + Caller ! {put_object, emqx_s3_client:put_object(Client, Key, <<"data">>)} + end, + receive + list_objects -> + Caller ! {list_objects, emqx_s3_client:list(Client, [])} + end + end + ), + Caller ! client_released, + receive + stop -> ok + end + end), + + %% Ask spawned process to put object + Pid ! put_object, + receive + {put_object, ok} -> ok + after 1000 -> + ct:fail("put_object fail") + end, + + %% Now change config for the profile + ProfileBaseConfig = ?config(profile_config, Config), + NewProfileConfig0 = ProfileBaseConfig#{bucket => <<"new_bucket">>}, + NewProfileConfig1 = emqx_map_lib:deep_put( + [transport_options, pool_size], NewProfileConfig0, 16 + ), + ok = emqx_s3:update_profile(profile_id(), NewProfileConfig1), + + %% We should have two pools now, because the old one is still in use + %% by the spawned process + ?assertEqual( + 2, + length(emqx_s3_profile_http_pools:all(ProfileId)) + ), + + %% Ask spawned process to list objects + Pid ! list_objects, + receive + {list_objects, Result} -> + {ok, OkResult} = Result, + Contents = proplists:get_value(contents, OkResult), + ?assertEqual(1, length(Contents)), + ?assertEqual(Key, proplists:get_value(key, hd(Contents))) + after 1000 -> + ct:fail("list_objects fail") + end, + + %% Wait till spawned process releases client + receive + client_released -> ok + after 1000 -> + ct:fail("client not released") + end, + + %% We should have only one pool now, because the old one is released + ?assertEqual( + 1, + length(emqx_s3_profile_http_pools:all(ProfileId)) + ). + +t_unknown_messages(_Config) -> + Pid = gproc:where({n, l, emqx_s3_profile_conf:id(profile_id())}), + + Pid ! unknown, + ok = gen_server:cast(Pid, unknown), + + ?assertEqual( + {error, not_implemented}, + gen_server:call(Pid, unknown) + ). + +%%-------------------------------------------------------------------- +%% Test helpers +%%-------------------------------------------------------------------- + +profile_id() -> + <<"test">>. diff --git a/apps/emqx_s3/test/emqx_s3_schema_SUITE.erl b/apps/emqx_s3/test/emqx_s3_schema_SUITE.erl new file mode 100644 index 000000000..bba1a5ba8 --- /dev/null +++ b/apps/emqx_s3/test/emqx_s3_schema_SUITE.erl @@ -0,0 +1,154 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_schema_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +%%-------------------------------------------------------------------- +%% Test cases +%%-------------------------------------------------------------------- + +t_minimal_config(_Config) -> + ?assertMatch( + #{ + bucket := "bucket", + host := "s3.us-east-1.endpoint.com", + port := 443, + acl := private, + min_part_size := 5242880, + transport_options := + #{ + connect_timeout := 15000, + enable_pipelining := 100, + pool_size := 8, + pool_type := random, + ssl := #{enable := false} + } + }, + emqx_s3_schema:translate(#{ + <<"bucket">> => <<"bucket">>, + <<"host">> => <<"s3.us-east-1.endpoint.com">>, + <<"port">> => 443 + }) + ). + +t_full_config(_Config) -> + ?assertMatch( + #{ + access_key_id := "access_key_id", + acl := public_read, + bucket := "bucket", + host := "s3.us-east-1.endpoint.com", + min_part_size := 10485760, + port := 443, + secret_access_key := "secret_access_key", + transport_options := + #{ + connect_timeout := 30000, + enable_pipelining := 200, + headers := #{<<"x-amz-acl">> := <<"public-read">>}, + max_retries := 3, + pool_size := 10, + pool_type := random, + request_timeout := 10000, + ssl := + #{ + cacertfile := <<"cacertfile.crt">>, + certfile := <<"server.crt">>, + ciphers := ["ECDHE-RSA-AES256-GCM-SHA384"], + depth := 10, + enable := true, + keyfile := <<"server.key">>, + reuse_sessions := true, + secure_renegotiate := true, + server_name_indication := "some-host", + verify := verify_peer, + versions := ['tlsv1.2'] + } + } + }, + emqx_s3_schema:translate(#{ + <<"access_key_id">> => <<"access_key_id">>, + <<"secret_access_key">> => <<"secret_access_key">>, + <<"bucket">> => <<"bucket">>, + <<"host">> => <<"s3.us-east-1.endpoint.com">>, + <<"port">> => 443, + <<"min_part_size">> => <<"10mb">>, + <<"acl">> => <<"public_read">>, + <<"transport_options">> => #{ + <<"connect_timeout">> => 30000, + <<"enable_pipelining">> => 200, + <<"pool_size">> => 10, + <<"pool_type">> => <<"random">>, + <<"ssl">> => #{ + <<"enable">> => true, + <<"keyfile">> => <<"server.key">>, + <<"certfile">> => <<"server.crt">>, + <<"cacertfile">> => <<"cacertfile.crt">>, + <<"server_name_indication">> => <<"some-host">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>] + }, + <<"request_timeout">> => <<"10s">>, + <<"max_retries">> => 3, + <<"headers">> => #{ + <<"x-amz-acl">> => <<"public-read">> + } + } + }) + ). + +t_invalid_limits(_Config) -> + ?assertException( + throw, + {emqx_s3_schema, [#{kind := validation_error, path := "s3.min_part_size"}]}, + emqx_s3_schema:translate(#{ + <<"bucket">> => <<"bucket">>, + <<"host">> => <<"s3.us-east-1.endpoint.com">>, + <<"port">> => 443, + <<"min_part_size">> => <<"1mb">> + }) + ), + + ?assertException( + throw, + {emqx_s3_schema, [#{kind := validation_error, path := "s3.min_part_size"}]}, + emqx_s3_schema:translate(#{ + <<"bucket">> => <<"bucket">>, + <<"host">> => <<"s3.us-east-1.endpoint.com">>, + <<"port">> => 443, + <<"min_part_size">> => <<"100000gb">> + }) + ), + + ?assertException( + throw, + {emqx_s3_schema, [#{kind := validation_error, path := "s3.max_part_size"}]}, + emqx_s3_schema:translate(#{ + <<"bucket">> => <<"bucket">>, + <<"host">> => <<"s3.us-east-1.endpoint.com">>, + <<"port">> => 443, + <<"max_part_size">> => <<"1mb">> + }) + ), + + ?assertException( + throw, + {emqx_s3_schema, [#{kind := validation_error, path := "s3.max_part_size"}]}, + emqx_s3_schema:translate(#{ + <<"bucket">> => <<"bucket">>, + <<"host">> => <<"s3.us-east-1.endpoint.com">>, + <<"port">> => 443, + <<"max_part_size">> => <<"100000gb">> + }) + ). diff --git a/apps/emqx_s3/test/emqx_s3_test_helpers.erl b/apps/emqx_s3/test/emqx_s3_test_helpers.erl new file mode 100644 index 000000000..c74e78a4d --- /dev/null +++ b/apps/emqx_s3/test/emqx_s3_test_helpers.erl @@ -0,0 +1,135 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_test_helpers). + +-compile(nowarn_export_all). +-compile(export_all). + +-define(ACCESS_KEY_ID, "minioadmin"). +-define(SECRET_ACCESS_KEY, "minioadmin"). + +-define(TOXIPROXY_HOST, "toxiproxy"). +-define(TOXIPROXY_PORT, 8474). + +-define(TCP_HOST, ?TOXIPROXY_HOST). +-define(TCP_PORT, 19000). +-define(TLS_HOST, ?TOXIPROXY_HOST). +-define(TLS_PORT, 19100). + +-include_lib("erlcloud/include/erlcloud_aws.hrl"). + +-export([ + aws_config/1, + base_raw_config/1, + base_config/1, + + unique_key/0, + unique_bucket/0, + + with_failure/3 +]). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +aws_config(tcp) -> + erlcloud_s3_new( + ?ACCESS_KEY_ID, + ?SECRET_ACCESS_KEY, + ?TCP_HOST, + ?TCP_PORT, + "http://" + ); +aws_config(tls) -> + erlcloud_s3_new( + ?ACCESS_KEY_ID, + ?SECRET_ACCESS_KEY, + ?TLS_HOST, + ?TLS_PORT, + "https://" + ). + +base_raw_config(tcp) -> + #{ + <<"bucket">> => <<"bucket">>, + <<"access_key_id">> => bin(?ACCESS_KEY_ID), + <<"secret_access_key">> => bin(?SECRET_ACCESS_KEY), + <<"host">> => ?TCP_HOST, + <<"port">> => ?TCP_PORT, + <<"max_part_size">> => 10 * 1024 * 1024, + <<"transport_options">> => + #{ + <<"request_timeout">> => 2000 + } + }; +base_raw_config(tls) -> + #{ + <<"bucket">> => <<"bucket">>, + <<"access_key_id">> => bin(?ACCESS_KEY_ID), + <<"secret_access_key">> => bin(?SECRET_ACCESS_KEY), + <<"host">> => ?TLS_HOST, + <<"port">> => ?TLS_PORT, + <<"max_part_size">> => 10 * 1024 * 1024, + <<"transport_options">> => + #{ + <<"request_timeout">> => 2000, + <<"ssl">> => #{ + <<"enable">> => true, + <<"cacertfile">> => bin(cert_path("ca.crt")), + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">> + } + } + }. + +base_config(ConnType) -> + emqx_s3_schema:translate(base_raw_config(ConnType)). + +unique_key() -> + "key-" ++ integer_to_list(erlang:system_time(millisecond)) ++ "-" ++ + integer_to_list(erlang:unique_integer([positive])). + +unique_bucket() -> + "bucket-" ++ integer_to_list(erlang:system_time(millisecond)) ++ "-" ++ + integer_to_list(erlang:unique_integer([positive])). + +with_failure(_ConnType, ehttpc_500, Fun) -> + try + meck:new(ehttpc, [passthrough, no_history]), + meck:expect(ehttpc, request, fun(_, _, _, _) -> {ok, 500, []} end), + Fun() + after + meck:unload(ehttpc) + end; +with_failure(ConnType, FailureType, Fun) -> + emqx_common_test_helpers:with_failure( + FailureType, + toxproxy_name(ConnType), + ?TOXIPROXY_HOST, + ?TOXIPROXY_PORT, + Fun + ). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +toxproxy_name(tcp) -> "minio_tcp"; +toxproxy_name(tls) -> "minio_tls". + +cert_path(FileName) -> + Dir = code:lib_dir(emqx_s3, test), + filename:join([Dir, <<"certs">>, FileName]). + +bin(String) when is_list(String) -> list_to_binary(String); +bin(Binary) when is_binary(Binary) -> Binary. + +erlcloud_s3_new(AccessKeyId, SecretAccessKey, Host, Port, Scheme) -> + AwsConfig = erlcloud_s3:new(AccessKeyId, SecretAccessKey, Host, Port), + AwsConfig#aws_config{ + s3_scheme = Scheme, + s3_bucket_access_method = path + }. diff --git a/apps/emqx_s3/test/emqx_s3_uploader_SUITE.erl b/apps/emqx_s3/test/emqx_s3_uploader_SUITE.erl new file mode 100644 index 000000000..ef1d916c6 --- /dev/null +++ b/apps/emqx_s3/test/emqx_s3_uploader_SUITE.erl @@ -0,0 +1,535 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_s3_uploader_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-define(assertProcessExited(Reason, Pid), + receive + {'DOWN', _, _, Pid, Reason} -> + % ct:print("uploader process exited with reason: ~p", [R]), + ok + after 3000 -> + ct:fail("uploader process did not exit") + end +). + +-define(assertObjectEqual(Value, AwsConfig, Bucket, Key), + ?assertEqual( + Value, + proplists:get_value( + content, + erlcloud_s3:get_object( + Bucket, + Key, + AwsConfig + ) + ) + ) +). + +all() -> + [ + {group, tcp}, + {group, tls} + ]. + +groups() -> + [ + {tcp, [ + {group, common_cases}, + {group, tcp_cases} + ]}, + {tls, [ + {group, common_cases}, + {group, tls_cases} + ]}, + {common_cases, [], [ + t_happy_path_simple_put, + t_happy_path_multi, + t_abort_multi, + t_abort_simple_put, + + {group, noconn_errors}, + {group, timeout_errors}, + {group, http_errors} + ]}, + + {tcp_cases, [ + t_config_switch, + t_config_switch_http_settings, + t_too_large, + t_no_profile + ]}, + + {tls_cases, [ + t_tls_error + ]}, + + {noconn_errors, [{group, transport_errors}]}, + {timeout_errors, [{group, transport_errors}]}, + {http_errors, [{group, transport_errors}]}, + + {transport_errors, [ + t_start_multipart_error, + t_upload_part_error, + t_complete_multipart_error, + t_abort_multipart_error, + t_put_object_error + ]} + ]. + +suite() -> [{timetrap, {minutes, 1}}]. + +init_per_suite(Config) -> + {ok, _} = application:ensure_all_started(emqx_s3), + Config. + +end_per_suite(_Config) -> + ok = application:stop(emqx_s3). + +init_per_group(Group, Config) when Group =:= tcp orelse Group =:= tls -> + [{conn_type, Group} | Config]; +init_per_group(noconn_errors, Config) -> + [{failure, down} | Config]; +init_per_group(timeout_errors, Config) -> + [{failure, timeout} | Config]; +init_per_group(http_errors, Config) -> + [{failure, ehttpc_500} | Config]; +init_per_group(_ConnType, Config) -> + Config. + +end_per_group(_ConnType, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + ok = snabbkaffe:start_trace(), + ConnType = ?config(conn_type, Config), + TestAwsConfig = emqx_s3_test_helpers:aws_config(ConnType), + + Bucket = emqx_s3_test_helpers:unique_bucket(), + ok = erlcloud_s3:create_bucket(Bucket, TestAwsConfig), + + ProfileBaseConfig = emqx_s3_test_helpers:base_config(ConnType), + ProfileConfig = ProfileBaseConfig#{bucket => Bucket}, + ok = emqx_s3:start_profile(profile_id(), ProfileConfig), + + [{bucket, Bucket}, {test_aws_config, TestAwsConfig}, {profile_config, ProfileConfig} | Config]. + +end_per_testcase(_TestCase, _Config) -> + ok = snabbkaffe:stop(), + _ = emqx_s3:stop_profile(profile_id()). + +%%-------------------------------------------------------------------- +%% Test cases +%%-------------------------------------------------------------------- + +t_happy_path_simple_put(Config) -> + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + Data = data($a, 1024, 10), + + lists:foreach( + fun(Chunk) -> + ?assertEqual( + ok, + emqx_s3_uploader:write(Pid, Chunk) + ) + end, + Data + ), + + ok = emqx_s3_uploader:complete(Pid), + + ?assertProcessExited( + normal, + Pid + ), + + ?assertObjectEqual( + iolist_to_binary(Data), + ?config(test_aws_config, Config), + ?config(bucket, Config), + Key + ). + +t_happy_path_multi(Config) -> + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + Data = data($a, 1024 * 1024, 10), + + lists:foreach( + fun(Chunk) -> + ?assertEqual( + ok, + emqx_s3_uploader:write(Pid, Chunk) + ) + end, + Data + ), + + ok = emqx_s3_uploader:complete(Pid), + + ?assertProcessExited( + normal, + Pid + ), + + ?assertObjectEqual( + iolist_to_binary(Data), + ?config(test_aws_config, Config), + ?config(bucket, Config), + Key + ). + +t_abort_multi(Config) -> + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + [Data] = data($a, 6 * 1024 * 1024, 1), + + ok = emqx_s3_uploader:write(Pid, Data), + + ?assertMatch( + [], + list_objects(Config) + ), + + ok = emqx_s3_uploader:abort(Pid), + + ?assertMatch( + [], + list_objects(Config) + ), + + ?assertProcessExited( + normal, + Pid + ). + +t_abort_simple_put(_Config) -> + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + [Data] = data($a, 10 * 1024, 1), + + ok = emqx_s3_uploader:write(Pid, Data), + + ok = emqx_s3_uploader:abort(Pid), + + ?assertProcessExited( + normal, + Pid + ). + +t_config_switch(Config) -> + Key = emqx_s3_test_helpers:unique_key(), + OldBucket = ?config(bucket, Config), + {ok, Pid0} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + [Data0, Data1] = data($a, 6 * 1024 * 1024, 2), + + ok = emqx_s3_uploader:write(Pid0, Data0), + + %% Switch to the new config, but without changing HTTP settings + ProfileConfig = ?config(profile_config, Config), + NewBucket = emqx_s3_test_helpers:unique_bucket(), + ok = erlcloud_s3:create_bucket(NewBucket, ?config(test_aws_config, Config)), + NewProfileConfig = ProfileConfig#{bucket => NewBucket}, + + ok = emqx_s3:update_profile(profile_id(), NewProfileConfig), + + %% Already started uploader should be OK and use previous config + ok = emqx_s3_uploader:write(Pid0, Data1), + ok = emqx_s3_uploader:complete(Pid0), + + ?assertObjectEqual( + iolist_to_binary([Data0, Data1]), + ?config(test_aws_config, Config), + OldBucket, + Key + ), + + %% Now check that new uploader uses new config + {ok, Pid1} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + ok = emqx_s3_uploader:write(Pid1, Data0), + ok = emqx_s3_uploader:complete(Pid1), + + ?assertObjectEqual( + iolist_to_binary(Data0), + ?config(test_aws_config, Config), + NewBucket, + Key + ). + +t_config_switch_http_settings(Config) -> + Key = emqx_s3_test_helpers:unique_key(), + OldBucket = ?config(bucket, Config), + {ok, Pid0} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + [Data0, Data1] = data($a, 6 * 1024 * 1024, 2), + + ok = emqx_s3_uploader:write(Pid0, Data0), + + %% Switch to the new config, completely changing HTTP settings (tcp -> tls) + NewBucket = emqx_s3_test_helpers:unique_bucket(), + NewTestAwsConfig = emqx_s3_test_helpers:aws_config(tls), + ok = erlcloud_s3:create_bucket(NewBucket, NewTestAwsConfig), + NewProfileConfig0 = emqx_s3_test_helpers:base_config(tls), + NewProfileConfig1 = NewProfileConfig0#{bucket => NewBucket}, + + ok = emqx_s3:update_profile(profile_id(), NewProfileConfig1), + + %% Already started uploader should be OK and use previous config + ok = emqx_s3_uploader:write(Pid0, Data1), + ok = emqx_s3_uploader:complete(Pid0), + + ?assertObjectEqual( + iolist_to_binary([Data0, Data1]), + ?config(test_aws_config, Config), + OldBucket, + Key + ), + + %% Now check that new uploader uses new config + {ok, Pid1} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + ok = emqx_s3_uploader:write(Pid1, Data0), + ok = emqx_s3_uploader:complete(Pid1), + + ?assertObjectEqual( + iolist_to_binary(Data0), + NewTestAwsConfig, + NewBucket, + Key + ). + +t_start_multipart_error(Config) -> + _ = process_flag(trap_exit, true), + + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + [Data] = data($a, 6 * 1024 * 1024, 1), + + emqx_s3_test_helpers:with_failure( + ?config(conn_type, Config), + ?config(failure, Config), + fun() -> + ?assertMatch( + {error, _}, + emqx_s3_uploader:write(Pid, Data) + ) + end + ), + + ?assertProcessExited( + {error, _}, + Pid + ). + +t_upload_part_error(Config) -> + _ = process_flag(trap_exit, true), + + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + [Data0, Data1] = data($a, 6 * 1024 * 1024, 2), + + ok = emqx_s3_uploader:write(Pid, Data0), + + emqx_s3_test_helpers:with_failure( + ?config(conn_type, Config), + ?config(failure, Config), + fun() -> + ?assertMatch( + {error, _}, + emqx_s3_uploader:write(Pid, Data1) + ) + end + ), + + ?assertProcessExited( + {error, _}, + Pid + ). + +t_abort_multipart_error(Config) -> + _ = process_flag(trap_exit, true), + + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + [Data] = data($a, 6 * 1024 * 1024, 1), + + ok = emqx_s3_uploader:write(Pid, Data), + + emqx_s3_test_helpers:with_failure( + ?config(conn_type, Config), + ?config(failure, Config), + fun() -> + ?assertMatch( + {error, _}, + emqx_s3_uploader:abort(Pid) + ) + end + ), + + ?assertProcessExited( + {error, _}, + Pid + ). + +t_complete_multipart_error(Config) -> + _ = process_flag(trap_exit, true), + + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + [Data] = data($a, 6 * 1024 * 1024, 1), + + ok = emqx_s3_uploader:write(Pid, Data), + + emqx_s3_test_helpers:with_failure( + ?config(conn_type, Config), + ?config(failure, Config), + fun() -> + ?assertMatch( + {error, _}, + emqx_s3_uploader:complete(Pid) + ) + end + ), + + ?assertProcessExited( + {error, _}, + Pid + ). + +t_put_object_error(Config) -> + _ = process_flag(trap_exit, true), + + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + %% Little data to avoid multipart upload + [Data] = data($a, 1024, 1), + + emqx_s3_test_helpers:with_failure( + ?config(conn_type, Config), + ?config(failure, Config), + fun() -> + ok = emqx_s3_uploader:write(Pid, Data), + ?assertMatch( + {error, _}, + emqx_s3_uploader:complete(Pid) + ) + end + ), + + ?assertProcessExited( + {error, _}, + Pid + ). + +t_too_large(Config) -> + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + [Data] = data($a, 1024, 1), + + [DataLarge] = data($a, 20 * 1024 * 1024, 1), + + ?assertMatch( + {error, {too_large, _}}, + emqx_s3_uploader:write(Pid, DataLarge) + ), + + ok = emqx_s3_uploader:write(Pid, Data), + ok = emqx_s3_uploader:complete(Pid), + + ?assertProcessExited( + normal, + Pid + ), + + ?assertObjectEqual( + iolist_to_binary(Data), + ?config(test_aws_config, Config), + ?config(bucket, Config), + Key + ). + +t_tls_error(Config) -> + _ = process_flag(trap_exit, true), + + ProfileBaseConfig = ?config(profile_config, Config), + ProfileConfig = emqx_map_lib:deep_put( + [transport_options, ssl, server_name_indication], ProfileBaseConfig, "invalid-hostname" + ), + ok = emqx_s3:update_profile(profile_id(), ProfileConfig), + Key = emqx_s3_test_helpers:unique_key(), + {ok, Pid} = emqx_s3:start_uploader(profile_id(), #{key => Key}), + + _ = erlang:monitor(process, Pid), + + [Data] = data($a, 6 * 1024 * 1024, 1), + + ?assertMatch( + {error, _}, + emqx_s3_uploader:write(Pid, Data) + ), + + ?assertProcessExited( + {error, _}, + Pid + ). + +t_no_profile(_Config) -> + Key = emqx_s3_test_helpers:unique_key(), + ?assertMatch( + {error, profile_not_found}, + emqx_s3:start_uploader(<<"no-profile">>, #{key => Key}) + ). + +%%-------------------------------------------------------------------- +%% Test helpers +%%-------------------------------------------------------------------- + +profile_id() -> + <<"test">>. + +data(Byte, ChunkSize, ChunkCount) -> + Chunk = iolist_to_binary([Byte || _ <- lists:seq(1, ChunkSize)]), + [Chunk || _ <- lists:seq(1, ChunkCount)]. + +list_objects(Config) -> + Props = erlcloud_s3:list_objects(?config(bucket, Config), [], ?config(test_aws_config, Config)), + proplists:get_value(contents, Props). diff --git a/rebar.config.erl b/rebar.config.erl index ea0016ca9..51a6946dc 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -401,7 +401,8 @@ relx_apps(ReleaseType, Edition) -> emqx_psk, emqx_slow_subs, emqx_plugins, - emqx_ft + emqx_ft, + emqx_s3 ] ++ [quicer || is_quicer_supported()] ++ [bcrypt || provide_bcrypt_release(ReleaseType)] ++ diff --git a/scripts/ct/run.sh b/scripts/ct/run.sh index 82823720d..9644ec8b9 100755 --- a/scripts/ct/run.sh +++ b/scripts/ct/run.sh @@ -91,6 +91,12 @@ if [ "${WHICH_APP}" = 'novalue' ]; then exit 1 fi +if [ ! -d "${WHICH_APP}" ]; then + echo "must provide an existing path for --app arg" + help + exit 1 +fi + if [[ "${WHICH_APP}" == lib-ee* && (-z "${PROFILE+x}" || "${PROFILE}" != emqx-enterprise) ]]; then echo 'You are trying to run an enterprise test case without the emqx-enterprise profile.' echo 'This will most likely not work.' @@ -172,10 +178,14 @@ for dep in ${CT_DEPS}; do ;; rocketmq) FILES+=( '.ci/docker-compose-file/docker-compose-rocketmq.yaml' ) - ;; + ;; cassandra) FILES+=( '.ci/docker-compose-file/docker-compose-cassandra.yaml' ) ;; + minio) + FILES+=( '.ci/docker-compose-file/docker-compose-minio-tcp.yaml' + '.ci/docker-compose-file/docker-compose-minio-tls.yaml' ) + ;; *) echo "unknown_ct_dependency $dep" exit 1