From a59664415ac6617c8b0f8cf9bc79fbb7e7715c9f Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Thu, 20 Jan 2022 20:07:13 +0100 Subject: [PATCH] Add DPI and malware detection tests --- app/src/main/jni/core/blacklist.c | 13 ++-- app/src/main/jni/tests/CMakeLists.txt | 7 ++- app/src/main/jni/tests/blacklist.c | 66 +++++++++++++++++++- app/src/main/jni/tests/dpi.c | 70 ++++++++++++++++++++++ app/src/main/jni/tests/pcap/README.md | 27 +++++++++ app/src/main/jni/tests/pcap/metadata.pcap | Bin 0 -> 47561 bytes app/src/main/jni/tests/pcap_reader.c | 2 +- app/src/main/jni/tests/test_utils.c | 33 ++++++++++ app/src/main/jni/tests/test_utils.h | 7 ++- docs/testing.md | 23 +++++++ submodules/zdtun | 2 +- 11 files changed, 233 insertions(+), 17 deletions(-) create mode 100644 app/src/main/jni/tests/dpi.c create mode 100644 app/src/main/jni/tests/pcap/README.md create mode 100644 app/src/main/jni/tests/pcap/metadata.pcap create mode 100644 docs/testing.md diff --git a/app/src/main/jni/core/blacklist.c b/app/src/main/jni/core/blacklist.c index ea501af4..068bc56c 100644 --- a/app/src/main/jni/core/blacklist.c +++ b/app/src/main/jni/core/blacklist.c @@ -254,18 +254,13 @@ bool blacklist_match_ip(blacklist_t *bl, const zdtun_ip_t *ip, int ipver) { /* ******************************************************* */ bool blacklist_match_ipstr(blacklist_t *bl, const char *ip_str) { - ndpi_ip_addr_t addr; - int ipver = ndpi_parse_ip_string(ip_str, &addr); - zdtun_ip_t ip; + zdtun_ip_t parsed; - if(ipver == 4) - ip.ip4 = addr.ipv4; - else if(ipver == 6) - memcpy(&ip.ip6, &addr.ipv6, 16); - else + int ipver = zdtun_parse_ip(ip_str, &parsed); + if(ipver < 0) return false; - return blacklist_match_ip(bl, &ip, ipver); + return blacklist_match_ip(bl, &parsed, ipver); } /* ******************************************************* */ diff --git a/app/src/main/jni/tests/CMakeLists.txt b/app/src/main/jni/tests/CMakeLists.txt index 31c3b4cd..1e9d9fa5 100644 --- a/app/src/main/jni/tests/CMakeLists.txt +++ b/app/src/main/jni/tests/CMakeLists.txt @@ -14,10 +14,9 @@ include_directories(${ROOTDIR}/submodules/zdtun) include_directories(${ROOTDIR}/submodules/nDPI/src/include) include(CTest) -list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # Target to run tests and build them if necessary -add_custom_target(run_tests COMMAND ${CMAKE_CTEST_COMMAND}) +add_custom_target(run_tests COMMAND CTEST_OUTPUT_ON_FAILURE=1 ${CMAKE_CTEST_COMMAND}) # build_tests(target) macro(build_tests) @@ -30,5 +29,9 @@ endmacro() build_tests(pcap_reader) +add_test(NAME dpi_extract COMMAND ./dpi extract) +build_tests(dpi) + add_test(NAME blacklist_match COMMAND ./blacklist match) +add_test(NAME blacklist_detection COMMAND ./blacklist detection) build_tests(blacklist) diff --git a/app/src/main/jni/tests/blacklist.c b/app/src/main/jni/tests/blacklist.c index 12350249..03295002 100644 --- a/app/src/main/jni/tests/blacklist.c +++ b/app/src/main/jni/tests/blacklist.c @@ -51,9 +51,73 @@ static void test_match() { /* ******************************************************* */ +static void detection_cb(pcapdroid_t *pd) { + conn_and_tuple_t *conn; + + // IP blacklist + conn = assert_conn(pd, IPPROTO_ICMP, "1.1.1.1", 0, NULL); + assert1(conn->data->blacklisted_ip); + conn = assert_conn(pd, IPPROTO_TCP, "216.58.208.164", 80, NULL); + assert1(conn->data->blacklisted_ip); + conn = assert_conn(pd, IPPROTO_TCP, "2c9b:a9b9:83dd:d9d1::2003", 443, NULL); + assert1(conn->data->blacklisted_ip); + + // Host blacklist + conn = assert_conn(pd, IPPROTO_UDP, "8.8.8.8", 53, "www.google.it"); + assert0(conn->data->blacklisted_ip); + assert1(conn->data->blacklisted_domain); + conn = assert_conn(pd, IPPROTO_TCP, "146.112.255.155", 80, "www.internetbadguys.com"); + assert1(conn->data->blacklisted_domain); + conn = assert_conn(pd, IPPROTO_TCP, "3a5d:15fe:e3cb:9c5f::2003", 443, "www.google.it"); + assert0(conn->data->blacklisted_ip); + assert1(conn->data->blacklisted_domain); + + // Whitelist + conn = assert_conn(pd, IPPROTO_TCP, "149.202.95.241", 80, "f-droid.org"); + assert0(conn->data->blacklisted_domain); + assert0(conn->data->blacklisted_ip); + conn = assert_conn(pd, IPPROTO_TCP, "2ed5:9050:81e9:4b68:248:1893:25c8:1946", 443, "example.org"); + assert0(conn->data->blacklisted_domain); + assert0(conn->data->blacklisted_ip); +} + +static void test_detection() { + pcapdroid_t *pd = pd_init(PCAP_PATH "/metadata.pcap"); + + blacklist_t *bl = blacklist_init(); + assert(bl != NULL); + blacklist_t *wl = blacklist_init(); + assert(wl != NULL); + pd->malware_detection.enabled = true; + pd->malware_detection.bl = bl; + pd->malware_detection.whitelist = wl; + + // Load blacklist + blacklist_add_ipstr(bl, "1.1.1.1"); + blacklist_add_ipstr(bl, "216.58.208.164"); + blacklist_add_ipstr(bl, "2c9b:a9b9:83dd:d9d1::2003"); + blacklist_add_ipstr(bl, "149.202.95.241"); + blacklist_add_domain(bl, "google.it"); + blacklist_add_domain(bl, "www.internetbadguys.com"); + blacklist_add_domain(bl, "example.org"); + + // Load whitelist + blacklist_add_ipstr(wl, "149.202.95.241"); + blacklist_add_domain(wl, "example.org"); + + // Run + pd->cb.send_connections_dump = detection_cb; + pd_run(pd); + + pd_free(pd); +} + +/* ******************************************************* */ + int main(int argc, char **argv) { add_test("match", test_match); - run_test(argc, argv); + add_test("detection", test_detection); + run_test(argc, argv); return 0; } diff --git a/app/src/main/jni/tests/dpi.c b/app/src/main/jni/tests/dpi.c new file mode 100644 index 00000000..ce8e8276 --- /dev/null +++ b/app/src/main/jni/tests/dpi.c @@ -0,0 +1,70 @@ +/* + * This file is part of PCAPdroid. + * + * PCAPdroid is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PCAPdroid is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PCAPdroid. If not, see . + * + * Copyright 2022 - Emanuele Faranda + */ + +#include "test_utils.h" + +/* ******************************************************* */ + +// Called on send_connections_dump. pd->new_conns contains the dumped +// connections. Ensures that metadata is correctly extracted from +// network traffic. +static void extract_metadata_cb(pcapdroid_t *pd) { + conn_and_tuple_t *conn; + + // DNS request without reply + conn = assert_conn(pd, IPPROTO_UDP, "8.8.8.8", 53, "example.org"); + assert(conn->tuple.src_port == htons(48037)); + + // DNS (TCP) + assert_conn(pd, IPPROTO_TCP, "8.8.8.8", 53, "f-droid.org"); + + // Guess host name from previous DNS request + assert_conn(pd, IPPROTO_TCP, "149.202.95.241", 80, "f-droid.org"); + + // HTTP + conn = assert_conn(pd, IPPROTO_TCP, "216.58.208.164", 80, "www.google.com"); + assert(!strcmp(conn->data->url, "www.google.com/imghp?test=1&v2=2")); + conn = assert_conn(pd, IPPROTO_TCP, "385d:1ee:e3c9:9c5f::2004", 80, "www.google.com"); + assert(!strcmp(conn->data->url, "www.google.com/imghp?test=1&v2=2")); + + // TLS + conn = assert_conn(pd, IPPROTO_TCP, "142.250.180.131", 443, "google.it"); + assert(conn->data->l7proto == NDPI_PROTOCOL_TLS); + conn = assert_conn(pd, IPPROTO_TCP, "2ed5:9050:81e9:4b68:248:1893:25c8:1946", 443, "example.org"); + assert(conn->data->l7proto == NDPI_PROTOCOL_TLS); +} + +static void test_metadata_extraction() { + conn_and_tuple_t *conn; + pcapdroid_t *pd = pd_init(PCAP_PATH "/metadata.pcap"); + + pd->cb.send_connections_dump = extract_metadata_cb; + pd_run(pd); + + pd_free(pd); +} + +/* ******************************************************* */ + +int main(int argc, char **argv) { + add_test("extract", test_metadata_extraction); + + run_test(argc, argv); + return 0; +} diff --git a/app/src/main/jni/tests/pcap/README.md b/app/src/main/jni/tests/pcap/README.md new file mode 100644 index 00000000..e46a462b --- /dev/null +++ b/app/src/main/jni/tests/pcap/README.md @@ -0,0 +1,27 @@ +## metadata.pcap + +Contains HTTP, TLS and DNS connections for both IPv4 and IPv6, suitable to test DPI. + +Connections: + +``` +[UDP4] 192.168.1.10:48037 -> 8.8.8.8:53 [example.org] +[UDP4] 192.168.1.10:38793 -> 8.8.8.8:53 [www.google.com] +[TCP4] 192.168.1.10:36922 -> 216.58.208.164:80 [www.google.com] +[UDP4] 192.168.1.10:48772 -> 8.8.8.8:53 [www.google.com] +[TCP6] 2001:db8:1234::1:49936 -> 385d:1ee:e3c9:9c5f::2004:80 [www.google.com] +[UDP4] 192.168.1.10:51080 -> 8.8.8.8:53 [google.it] +[TCP6] 2001:db8:1234::1:44904 -> 2c9b:a9b9:83dd:d9d1::2003:443 [] +[TCP4] 192.168.1.10:51588 -> 142.250.180.131:443 [google.it] +[UDP4] 192.168.1.10:42218 -> 8.8.8.8:53 [www.google.it] +[TCP6] 2001:db8:1234::1:59424 -> 3a5d:15fe:e3cb:9c5f::2003:443 [www.google.it] +[ICMP4] 192.168.1.10:4 -> 1.1.1.1:0 [] +[UDP4] 192.168.1.10:47987 -> 8.8.8.8:53 [www.internetbadguys.com] +[TCP4] 192.168.1.10:46312 -> 146.112.255.155:80 [www.internetbadguys.com] +[UDP4] 192.168.1.10:51165 -> 8.8.8.8:53 [www.internetbadguys.com] +[UDP4] 192.168.1.10:52176 -> 8.8.8.8:53 [example.org] +[TCP6] 2001:db8:1234::1:45226 -> 2ed5:9050:81e9:4b68:248:1893:25c8:1946:443 [example.org] +[TCP4] 192.168.1.10:43453 -> 8.8.8.8:53 [f-droid.org] +[UDP4] 192.168.1.10:41011 -> 8.8.8.8:53 [f-droid.org] +[TCP4] 192.168.1.10:52782 -> 149.202.95.241:80 [f-droid.org] +``` diff --git a/app/src/main/jni/tests/pcap/metadata.pcap b/app/src/main/jni/tests/pcap/metadata.pcap new file mode 100644 index 0000000000000000000000000000000000000000..382484d30add8e146a4a214fd6c7bd772aa7234c GIT binary patch literal 47561 zcmeFa2UrwI*DyLn5yjOt=VnxbOb##%3`i6d5y@tIW*TN>5)%jltFEp&t$EEk=bYnW z&N)X6t76V!y{Eb-0`l(ne*53&{?Btqdzh}SI=QM&ovN<7aB#zXXPPU`_Hm^-!A;17 zzwiSMoM?UEH@cjiRH^S7cP{ohdbPC&Erv!*njKA}b*NWs+P-B@o*wYoxtJ!Tg{rn) zZ|sDTAZRqF8bkrE)~N^=tv(xWQL=wsfCiu1G*6nxnO_grOP=5BLj+)JcgKm=2_B%U z61LEVq7Hzqbc+Ssw2^?#?^%3v2EarF!^b5*Ki@rDtIY<4GOe0Kh#-eo4>_2Gyx9@* z++c-wHIQ4-oDk%3fv&WHfEitsY$(b}3FVfCBFGC%kaQ4qnh>b&{jx;iJ$Mk%jHq_1 zL^D9HDh=|^qt$IsYmn5z*~1y`;DdqM{9VsbH;5ds4z4r<+@h-zIYg<1Bxi&JIo-tP zn=`5*2gs~M4oSLGeVXeP@tH*xNT?s-T8$R5>g{CFVlYKGJ9d+z0UxR8rPS6U1X!cX5Co zFz@H@-&fZpAx}k+mpGpt?@)ug(G* zok)7GBghD%03gl>Xy~d0agu&I2{EKfh)tZ{-#_9Av13ds##XAc6}oUEVK7R#K6yL| z4~t7pO=5FdTrbZ!t-&b9!1`H~^|QeGy*$$lgq|6ZO=#em%%oSbgIFRCi{s@PA(Ih0 zNNmq;@8t=%KyZ=T%_@RQ!WE>ukiunG5Uv!2I|@WVyXHj^4D3$e45POAtg0uNaTR!Mb+#@{GofgcwUzm>7_Rb;339 zhsVRXfnt8Jm=}z7?2_u`8B?HB>Is7wV}g*PaG8Q&Mrk!hy;dd0bb4hTz+hl%ynu;k z6A};%dI;iTrWWb|xREF@vK29>Txx}?5aaaz)MO*mzb`}@g3t6QE?-1q|H$R zxry=75+Ro>iHwNLNJ>@Zq$EZ5RpbW;X6FV6YNeS*Lv9zmW1K1`THZgf(4bG~)+vsc z+&xAZ*{PsMa!@>1&dlOP#dXc*Df>qB&6kQ|jGek==XTB~ObIFd6SGCijOZ?jCL%vu z(<3X%lrGKCN5#ct>!Q=T_UIbiDKoLKP|z_sMb;%JLn;vErx;?4S>0smnffGLpBovg z6&VY&BMWe;x*#z&#gNHQ%#V))k zlAw+#Oqv`*j;aKohX;9ihI-4jG9%amhO8hg6n$e#BcV3Pv^qjUr;|4*gM_XCD#UEI z0c=c-v!J_UXAsgPuq!&I0(vBLrI8L02wWZ(swRv$CZlXkLhnd+SXjJTjb|%0N){Hc z!VF3SpusVn@)Kl4J*ZKO=?Fc*!t_cRp_k#9T!|^oWiW$Dg#kVm9n*lVNa%9HAk!;J zivg^T_^L4LwA5Iphz?vDbxb1Hq|B4h?W;m15YQL_X9l(ugown+rkb5>Hn+g?v#-Gj z&v3oGuS~1b>icqeBDs#G(_~XZ)9JN3kO=|<(2Yu?iU_M9h)_0_97?f*)F}hjxOuS! zo%H?LoCJQ5DLG!znV-;!-Ay8)hxuihG-SQ;^DoL*YUEl=b0{gwiHR5ccIy$X>z>tB z(@_-NooncpBoL;gF(cxA896bTN${M*;PN> z1Oj-(A104d0fDeKa z0;V8H6^eu)nII4kHz*KT=b|rQlpzpsAup0SXcJ%taf1L55BP*+potL_#N{vq!a$%{ z2!i7Ui9`$$s4OrD$_RPEki-C#z&RL^2sADbLN+r5P&+&kp~4~%7+1&#fdMC= zOi-%;$%_x1@*oorsg#R!BSI=ADG(uf0DKTQe2IW70bHPXK!CpZKui#j4!=OkJU|0h z0MtTZ(pb10P)jfmErK7w9q_~ufk=aefD?2r1Y-a*WC;1d1En+o11f?1 zaLYwz&qcL^A%veos3Q~)mWsgz@xpH;KafliXdn>if;2v|rXa8fAOobsLxh0ZIq(<> z8R;7t1rOMe$py;5l))OH98VYsR?UUNkQb~Gl>=J|0xW@W z3BfdkLJm~JV1_U_IEW8w0YQQiI2@>c3=yzIR!bxe01cKwDic@|g0TQxsCisK925+| z5c3QH7%j>OCAfT~2rg)e7YKN{$kajdzzTXs5(08V8NqUp)F25-1|A_X185%75j;0v zJXHV4JCsWO;{zQ?&Rj4LgceC2S-9mNaEmGfkN`N4C=j_kXtSUi!xK;${1Jd<0O$N* zAukYGA)W}>1l&lCBFGNqp*A2OzzxU{D!Tw056B<@8-PXzs0XxQfk^96_fg(pFcWSN zdL9J2fMJ$#NW?9#J>IZ z%)|9qmc$6IRdKO_P-TUn1O+2U(zADnrJcuAez<=T_*8!Ap)>^d@$vJ4?R(BJ-00<)jN} z5YvG}v-hFW%5dbgv%uRiYQYZzvdncs4Zp2F{bKZbFk(7R_h)!>8AS%YOe|w$tJG>S zx5S?z0}=RJY*SVeqL7$Nf>&dyNnrCa@e-BYZ35h@E%$1b#A0UP9GT4=S+ccqs@Cy32$yJxeDGJncOz>hA<1E>2u-$85t7rB=q z1rl$r6@VSn{-6d&ZJ7aOAq6=!@G!J8siEOzS$@Q*2=X46%VYB3K>`{Bff@PH4JN4? zhEEJDbD&71Lp%hj6Iwhj9d|sHic>C4cz% zK+gf@h)6K7a#?w}%0z^FbHt!tWF#1lSVj$t6sj|U2#hAOaFfdD=g)w)*{IdQz+H!D zlQrJY-@k<6&4K3YpK~dLm_bfeOyXj)kFzm~$iShQ`w?(p&(f`#h3XH2qDLS`aS&E^ zA`B8yJ*I?ErMxs+g`UU)QAza#o`c~fi2o8mT$z1}Q%ALj!blhmK*K@}#zGj|waJ#^ zdPbXU6U3x|8;G$OlmiGcm#-@b(SgQc5Xl4)in+Q1%v-6}K|_ISj3FgtxgrIj%vKo1 zJf5xqQb-tlaSWQP zA}G$a6i1|osKHE?8YW4Rm?b3CArSj9DxqyN(+U919A`nbG>TP3ma(J+^dsdm(6^w7 zWK2%Tw0hFc#3l_eqf%-JJE=JoAV=TgHXM%3J_o>3VkAKGbGckZ@PaC2$#TdN43RgR zYDzpo#iVM@8kTD1g$$#dfirXrmV(l87OqyR3dOiyiK`d}2)Hmo{mQHol(&eIhL{tg z1t|c(`OHEw7>yP>r8e2x>>{&<#b6?s2rf>EQC@^tWtz=_B_h4pEmp;>WgG!pc%%R+wg?f9%C)*Xf$_&j4(w|gXw@ep;`nn)e=BXa{|*+ zHE_9%;9zZnI1sYIZGYutdWhK=`J`o$C>&@kX_JxLEh15BM?3PeeZkAF#&4fN`t8#z z`|Tf4y#2+#RaU>ffyHlsUcKMGoAldpN5B2UD2v~I=z^o)ZZST4F>^33YJia(?ZpT) z3x1Y>pa#$D3!bDFLK-O6=Z|qPCNB^*>Uu&);6|oJxKODygq_KSzyyUss~79EN(jO0 z?WJllbZ96{MpPxB$)M^O7)GTkR8^CTD7F}>z5Xc;k`-H=1s%1a$bx}1aOmgN28(sd zgghQM%iJtjx-KwFgF>g1QF$HxVTvRgbr`8EC|f#tGnU_`v{y zr6!ax)XCFApUHu=?C@-W20pquivqG3mv3|1o#RwkjaA#W|5Czgds!3WDlLp&U)F&JPJ;*b15v!f|j`J0C~)&v+< z!=T!qfrvyoIbkMifEc3iQ11yJAM+0o5*mu(kRN4b@&K*L->A@Q^TFMeA|HtK2H_VD zu>~s$;75{!I1IVd;LKo@`$KtLW-^P5bzzbma5UB7-dvCgLf|d6kV{gh3kZM_ue`Vz zyk#l^AcG+*fo3g;wB-0GmxYs6+aZUpw(%q) zfknkuFbQT=qP1buU_L&WnI{bdG?ZDGpLM)QP2c$!S>rf<{$%h4NC_$NL|MHNP6*Ig zhObb`Y`$5)|!|t&9%>iZtb#_c)~vvq@D?aAfy=fBA)<#R z!Q<*cn0YLO#>*rn&~grTc(zIspGpzIrH6Ibno{9WbwCgupkXUFBovYf4LvLo9{i`m zFxRfKX9C@`DM730VI9%!e*z(gRThLj463-69u|$BR4z`ADJAr(MjS9fU5yoh->71P z!XU_KGO{7!DmTfD1`3@549pZ3p@Nkkp=?ta=A*}ROz=85Gn^vtWUG5cuH8)a674+&C=XBgRmhWYt-rs z*;<_uq)r(*-3nL`7Nyr}VER8BA|g;rNo^OSx@U$7Y9e3_2KYCGxsurzjt6H4`knz0 zL08&}Qn5ZsUx<}ZZ1xS}*pY~g9+%0WyM{2HnNVc-XrT?X3<*Gs##|Y7(71pNN{|PP z{6C{oVKX^WbOwuu;M??fHY_K#}f-tn) zL{1W2*}g2f6c}ae2^^;`rP=q=jV!)+h%r2nI1Bc7rEs)Q9RE%%MaiBv?AI z3QI!JV9>TI1l{$8(7KW6YXfCoAi@jc6$JCBH6s2n%wbX^Mmk2W0+Cc)CPRl18-(|O zSqOJfY%6pKz9TDkF#yB>d21i$u?F?@gbE^Rc~np@2r+Jn5#d36LKH&j0A@=O6f!tR zroe(5ZN46dwJd;_tYWrqhN^Ltw(P%A-$vb|2o;8+Ct6^HP0jCFmhd2#0vc--fyoG! z%B(U8odaeD_W)Z4o+`SBY0%MyZ;Pz(SRvD`8m^D1ut(q?F4E=s-g4Gb>@4lR2+#iQErLSYU$Ikn0A$ zI+$%Wunk(1UIy?TvRJSw5WDu2NWlki%v$kzWloUqLDf0B+(0*1OS&bnV~gCPs2YX@ zP9ZcGfv}*pj5VfdsbXj(Pt`JHewD+$7Au4G31HDCG)qXX_5!hJ!K<{e2`r%{Qm`zG zT>52M_eDpG7pW0}gbov#u!f5gX)aI%K?(_77zE^!#)eTngN;vfXJF+*irlaYoJp+| zR76bSks37}E#fxM(pWnVbB|-OIx`-K1fl?6h3acDUo{w)3UqC)R1S6w9S7wBuyJ`F z8k5NTlAdf|s^*xRby#p70+LjqK3oFYGCPDA4n+pQR1nqcqq@L0qMArnG7IOZ`OKE) zS>#CmtkUqn1^cf&elpMkLoGD-f&u^*2infRMsvB;L{wy9yqui6L1F$Z7&Oaab{mlc zgR#sm330GqFB#VBL7(Ig0C{wBIuiq^^ePHOkWt#quG0Rny3{;Z20<1=(v#jL1)%X* z2qdg2W(Bf1bVkKQeiex*ml8tu25pwnQko9yGa!N@lW-uCLG+Xb1oRH^H|h(?!G;43 zXh8r;b_qpy%pSrtlN5;sm?5X}(OkcE8l6%z3&yWRL08D*1L~3RU{FrZ24-1e zOOR?}WZNPFj`H*)^$W2P7(-`M(|i&bAH(3qJX(TvzNtzz0sf5re5I8L(gL$e7#wnR zXg@J+8{4o^!*2B^>1c`qmW*o93Q(%yM=g%IGq&|mB7VCNHOvoL8Xh!51SL>+f$}5M zgoq@vJ96HIWf-8-spZn{CZn-$U#jLX4JxgX3C^!tYZsF=!1RU65+66ugrL|Y1Rkpe zf#Iwk@kG2zY7M(PgoUf}(f=bzaHPyra8wA`JZV97uc&ja&_+^xm4Ixi$tJtDx_aX2 z7&-5oN63-iegM4eYU1s)NWXn*rFc8rYXkDzR}8J}w~w4vlh)jmMqU-yw|jb}!^)Zg z?xyLEe!JHOi{IYBx?-MM-wqu)u1M<~ldl{wpmVNXV2I{MDAFVJozkM>GP-n*Oc8a{ z#LFV1BD#V8EyGTFVrN;TR9?-v2)VA{sfFi~ZY}8>>y0oGCzrv*wMp+VG6SnYEdyJJ ztMCB>v`ko>!GyKI5D>#v1h~E?qq;BE7lYqRdDh?sqf|8IX;MQf#KMv3nUiJE664^N zzymW(AJjHeFeH$dyals{C4Iageozp34-0Vk4ON}-QrW~~pIf#J(Ax#XSdh=DgttEj7@P1G4JE)q5W2Hq?zzAp@+UiV<4!DI#g$d@l5 zd%WQhnY$Oum{hKnSZ0%9 zUXfv5LLd-uDy)+s7rI#D-bxKDh{-^43FtY^0LipCa_X9#Q9$z&<^?h`OCF1t!2rvM zN=e~HftQIJp^KHF*b@Bg0uv13vWjbQ#4;9&GeTHd&VuDBFybU}LrB|lPvIrkg+PA_ zi&AXtu}GGRi=_^URObd0MCObzWb=avHTut{P@olg;XdZ&c;L=MJRGgZlPVSPiwvcs zi4Jpda+4{^U5!~MYV&cpUy`aBAQY6a}`}#6u2J}r7$kgJVuvo1ElK&11QmqMM zzkr6_mknPzy$bkpxML}FEJDNTk;;$a7Ix{JkebHo%Er~`X+%;)4E&4AiD1joJ(o>b z{uVTLZqy-Nt%PF1t#R?cIG7uve_g=gS4-LV90%` zIY0=9Sl8S#vK-c>ndjAD3IP$GO@3EgH--wcvL#ZOs!@dFVmiGfL`5xsL~bj~s7)bf zSZr{us}ZS^0jLB5uRsgNY-?j-O&;mw;S!T&R!ilu?hKYFTIahQpV$@@N(*dLBL#l` zj!SQ?YvqjK{OJu=1Ep?RXkO1QO$PILW+QN2pz5IgZ=>x{MiMlaSSMc1`>oOh5AUogO&>7f*J)2B0*>g+I&JEg&PQ1 zR|fNekO>xL842?!0al-)if^4=fw;Ytn%zKDIs@j6K64~n-sEYRk%&_^5k*^UY^O2uf*T%+w1&` zP#D`h*=qf1o^I7y=fO(AcBmOv^aGc`G*`BNxP@T~Us`ekC|eB6Ov#0)Xo^SyVj*WR zp!c@TSHa{CP9g>VWe}$X`KrQU9hI1ljIl3)rh3Jxlx&g-$}fQxr*u+XasclqV`=lD z#f>)W5vGY96Id`TLu&xjl|+7VG1VaZTU*f(KkDVmvEV9J<9yC0ozE$iozKRjk@Gok zWo73xi|tACbElD4Md#DKS^9b8d=?fuI-iY4Tbxf_Lu=6Z|BNMz!6R88fVJv`MXtDL`+;LT&Veb&=F;KLImgwoI{oNxM?1q6`K= zUsU30KR7gF@2`>+;YOoIOwQFYq$;D>7uX@7_fTkZ#2kh^-~33PZz307FbFkd=P-aX z_(p46sG<L!eW` zJY!L>0v-=K3?5HhRKh@i$>yI*K;N*OLPOr6s5qHHRyl=q1;||J3x;&)51Co6a4alW z?HLuvp6}4N?jryp@6y`x~?x9a+Fi zzKYCEi3~k~d3cHfQWP?!)P^_;#gvdsZ+^wnibh^Y0YM3|T6hx}VzBTEW}cE{gT$L(N`BG3=lNn>p~pBXwu+D=$Or%s?;`oX)5qHm0CT#$O*3l!y-e{qqb(%8ggvR zL%&HbmsXnJ`&7c*1(=pKvkvBN(G5vaHxv@GvVlDYzZwExz?NDI3ua(ojF>cfCjN@F6xg;AmKeZ>s#$mEjZPMo1^lXqSZ7f*=b4GZ- z8Kwj*Z?oEX0+uN4BH&a261ouc=mDis7Oc(FqZ^As>vG9E^o1@`I|eCkGdoyWWsS<} zVZs^KNRaRQ-Y%+{nj9Ex2`N z#qhd`vSRf7?7)P~0z#Cdj2AGq(b3tOPQo-Re3L>>ih!!sB_-CkrBDU#l+ra3y|DzY z0Nk3}GHc)oGk;);8{BpnwxF&%%*zvABd251l$JyFHo8DRRdWiIN8W)xU^ zA~7sGJc$A;Wz|EROG2Dfxo-oA^_`?i+3(6@~S zh_spE*1qix^lf59<8wQRm3Ctg=;x6%POL!VXy958T)&%vS!t95*KgtgVp1QEYIeC; z-W?#;+e`N7XtRwyL(yI(rOlf6a{^GXB?Nq`+EfEU?pHnJ9wcN^LpI14?$;`=1~Tj@ zf*}7a^`L>c$*YoFWrxZARpyVkZ4LrdcYSlOR&h0(g8<}xwQ2ATVE?FgcM?Rv2_S%g zoam}VK*r%<#M=BB4g^Hq?bEzpH3Yy;BtQVQA4%;>`;j1|3uPYGJ#5C@a4r;grBDyM z+0XsVLU5ouU`}4!eii^t)W=EG+v)lJqqF)_x0vg}y0oM*%naB91MN}r4ZAUw+(~2} z+DW8jFMBacM|+eYYG z5le?o2um^O7VS(@ve$-X>A#32#u>1@$cKG(Dq>OZ{P!dlzXS22e^?od;a|j((jKrp zQVwyzvK#58#=d`#myCOe7x^wL7FW6-$?NVYzD;;Ke)I3vIeaUX@&! zpydNdx1bQYFsT1LJOUh!Hq-mH_OiJJ=8e&ul}3P0b3#Db)dMXeffhIdRm<5ms|E^g zKq3HLC!*1mKqLc$J%;%ptYm7Pirq*FJFPu3bz0@gyuT#^CoCjtec-9 z#J?zLG!r~US0%*R$48NfgB=j3Oy#p{R*e|Au}4h$I8IG1K8~w1Ab}4C&4B|EMZh-N zE)QK5x@On8E30xh7&Dh|A9C~RSp*INT&r-}+5Y?C8R!nV8^40uon75XcVk+{r-djT zxf_UO??RVq+|cc%z_NSMA*4VgucJeqcG9Q#MN^PJzueZ6giLO>mUgerA$!OM*b=Z4 zB%rI3FhehXM@~2~20=y8opv4_dhO%Qp8$Fc+HB2lC<&R|Y;E-N*fIniZMKGJ9tV(r zJOtT(h@|;s1)5_3dR?d+@6(!5bps&&g&-q{-!NBNM^Fg5D%K6>;O&PI#KOZ>LWIrM z?f|W*MF)tl5X43(>}&N4sGs< z7KNfw8penw5GHse5l#9x+u4^g6Dx|^3d*Zv__EH z7%nbcUNjDkMvGPp1KPK~e6Zgf=lA1B%JGfK zH*5GdrZuE(0<{0veUMpoU1c-WN>xB`33!4kFsVWns7s&H9=VBbDG-B2W;J?w$DfdC zA&A*GTxloVXyjE9qT8NJ5YHXp-p}Pn5X+#_nIX1D5F5F;&<23X=efA7{%dimR=TOr zi#<%E(Rs<)#XCOSyj~X8>*B3gkNdwaIQSn8wLZXMfm%tW{?dW5|JJHL%+kB1N6xb6 z%ZgrjHag<`^kvTbkyi&zIv5TX;q2nt0MI)*HFj~S+o}HH>$AH4+5BsdNi~N=;yrWw z_4({}aNHsPi6KwQOi4WkOPKpyzj}rzo*quVE zR5N13$kwMjeD2Q~uqSRBKfbWm za^KfG8(m70#6|Bo8f8kpvwGIB!84xh@|x7Lw&TU%r`Pywe@jH`=)j3Jay1S2F;jySVFwym6e2u+M!)9-Jar5Y+;aHuX zI&SjshAF^k1tqn~%9+R3KtP##jPLhpy_5P13mnPF&JQ@|*KQU{{=tInnJ8n+a zL>%^u+*}?#ZN`N|e=ItGU9;R+R~s^XtM5wj++%N#jsAQ4(-|Y8|N3~^|6Hl-=YBoC z@}%OaPi|(G%35At*Q;Rw_g1DbwZos+Crtm-CAsOwV+CdR`nT|E)pPnhiKNC^r{uSL zd&GM6@87;S=*)skr^DjY?wXFDPaOMp%$h^_&DRebW8Ai4?922cvF*!FL~fmaR*7x@ zUh+$(-_gj6t}PFhem#}6e^bK=J|_>g<+XwR12bE;u2rK&`7v&J>yhi<7ALQ9nK!F- z(Z!&1;;(&t-dvh?ZWt9hTUIDMHoVikr4egJ z}Fk9J8tyKQ;t^?i(cS7M8vaa%;{+qK?WH>?4F*_=V=B&M8C z^S;U4TE_al>-ByOYt%$fpC=cG?vebJRcGJ#r1`(!&MjNb@BMmDVEnV5K8+uZ=+VN9 z|M!DMiv9a8jd?9tb8ORy*3Wm!nuXu_e60A|iz|^*_W>=3pKi`A;q3m~L-*;(`*~ki zXrda86n1Z>-n)JI-MbT48b!lv?*41+D3>u4+KgE6^E+SHLxgGn-E$ii()hzI7Ir)T z^ckzw!MbynVD5i0bDwYWTHS#)r8YiyX~%Q?iyi*VVCPCcypd1Xb|bC%+i@;mm)AR7 zqtW!UC&K@Zz5Qs%<0mbX=561fS-56gm|)1hw@sJ(JT2X~a9d31oREnZ6EAr;4nA1t zGXv|+^#43}L|Cn9`%^rpF|v%ec1087dD;2#j}t#WFYCQm{pPQg`#SdQ#PytZrN9`%c$B-Xn*VUCgg>q}^-l4LwF{1krf2+TNlD*;_a} z_7?YMko`{+CpT3+$KK-7qch|Bmu}cUdy;(dUvX(0YDcl|Un~^-we@-dXCpKG(~YAW zF6hr~A6)*J{&CFbuA^og-*|t?^EJANFEv(oKGT|>F>~(4U20QI;0NQY(Gzz(6D!6( zS&&>>JZ)4@n4{(u`bg68NhQL-hn+fST%Oge`L0iC+CIl|*1__-7aIF7UHk6*(RW8r zG5X!;r{6u|%(cABx=(t|@e@fws?SS)&8+?D=&}}Hj^>m$3M-UMI5Pap{%oHKp&6M? z+io1XH?;l7Lhrn0ojWr8WGfOq2=VM=D=)U1GvA|~@M7;w@6oay%uGhTEiR$;3g0~m z@5)%Gd-|>2wTSHfABz^AJQ)6YeuL%GEpDvz z{mt2R$4_h3iw2C%2_I6YQ+rXZd`#4p6AsXtEWW1%gVjHV19UtVDii5KkfBw z-e73&eG?-;h(GtL&FVb1?rqg9em(V8Zp~T0sy7$7W`yBfNHvOsVt@)4ZZ(bUe7k0Dx@fXprU80>H zV1-0^=iA|D5oQZhL<{81#H@1pCaqX(N}FUXvGgUGlNj>-ohqir*PJq~Yss zG>b~Bf4k6Sz~ygmi0<1n*M)!e>N>9Z8PV(G;#g0vX4C4{ri3;Zii=OtawU-$M{n$P z&prIAH1kiv^l?R+YYorl#lG(E*_bDY)D6(jY%`UWX(~Q9cH;DTO;>l>)nVoC+e>0< zw2bP*I(_)ViU;NToYA8W`sXkAUH3=cZuWt{ho8&#Y`RDLn0sNes`mF!4PDMGeb?lA z9me+LhCg>m_j2#`Ew}U0qa#b5F3&BWGn?FQ?D9{h~U*by`WQulw`E z+W)O%U(WL1Ps?c4dC%g5(Ql%cUm4xw zCR20WiCep7`TcdhamubuuWts;9I;~Z)@PePHH(`u=0mU0cJ6`=zSwH2=jsyI;3yNNz@l2R~BcgoYSGTg~n;B&{ zLc`W=xqN=rrN*k8KEDXW`r||NH9HjFe_2C(@Kknod-{;*?sG@G)snT+$)ox=c#7}* z<>P3k=khhRo^9mKy}aS4pXUg3mHhpKCcc~;=YHhQf%n-yE{*2=6z%nJgtp1Zw&UAf zyYsc_1u1KT_%bs~;L^PLw!fz~iHUp?yS>g0m1!NHJEebIu?!b`w_>k2@@aAY?&&*g==PqbH;p#H>xwy!^lQ`#kp4qD$nAu?WAkU?X#=bn&^>Envy1z`X8h$^ut(!o7m>m@?YWrGvp1)fbm$Bfu8=Ubm zZUs|X)tA=Dz4i2q2@gt^e?Kt)L{VIeb@Agooqjpt z^4qxXLi*D4bH=4kReWD6_mX#>65!g4&tFpe{_2HM*NfU+cpJRt<{z(dKW^WLD^_jz zI^wu@=nNkQ<8|)llb1?ntq**q{Mcl=^Zn!{s~bG1z3p+-Jz4w7!{wxM*x%r^xuPBxt6wPZm2mWL;jI@f9TtMYD(I(rt3TGzxiU@3m%Y~cA#uaa+M z4iy(IZJ;vM8v3{3#kwhOH#6jOFP;{kzH^H;U-5gNO%pp@7`keD#H4RGcg5}B8uiF& zX!+D*?WavhU-0}=hxR8Q98L-djwyRJApG~Ixkn}rKhrz*dh0(+Yn3hQaA-*8@O1}U z=5x0s?(EkkC~E)uiy!v{J`b7O=$drkk?0jEgC{K|Q1JF)wjJ|QPn zWB!&F3!A=5$q-$5agSTWly!91TlZ!|SMQ(+c6=lHyeR(T){7x$yNrxwF5MUqbh!DD z70m22`9I&UMmEI~yqNYixXHFYaqE`rXC3e3G-yJX;FX?-{W31DZ!sv{`KK+e1Lj|-T|DPc z$l9FfA__`|^FKmUYi`y4C!F|F{)cr*p@?q+wM7k{2Yhpnyf>z9`M_1#ao3Y-biCWp5OhPitMj+m75!$v-fL|8 z$o0^=ai`ur_)Yj_U0si|9yialn&AGX@24wM8lO?t=zQ9DQ^3)Eb-(pIg#UV^&GHi~ z6OWwllQsfs~GzhqkC6ZOmH4N&wXJx;jHJiZr+(u z67R91X6uk;rBSuE`1CMIefL~&PX2!T`3>6XIv1R_bm@O~*t0tQl?SAQ24vjrs!&A} zMO#|?Z+bamZb{3y13>}T4wp1sR;J0kbK<89eLwYX^K4kh*0i&4``mbTl>2SWjDbc3G>@AK~?k&pKpxz=9B2H9*)7D#T zd+bVs#T?{SF%m`Vf2k%J+e&rpEy~we#um*sIQABg2Tkj|Z0gmNC8LczpP!c=xcrDY zc2oQM{Vxk1@$=JntUKDP)t3*(UzTK=^aF%jG6Ms}X0VKu0y49Vag39*UOyI?p>#Vo6K2%7 zgryd|oK)~IbqbNNY{mBzYj(eS=c=!Tdv06M^5!q?-^)I_1eEW%<2`oejBD=?e)+3G z#=Wo;X0$Jj<2-t<87|o$gw1>TRP=dfzsrL8JzE?LS+>xJj_~gR z{FXyvD&Z%#pCa*-D@^S0AF<&7*)~!oC4}%Z4v)Q|9p~r1?sw(YvmY~iDdTpu__fwy z&bH9)4LqXW2QEL~<7%r3rPKP}X?QaVk-&KFN}CHJKvyLae&2qYBq6(kl0I2T`24xD z2wDv@wcb$R{bbu_I;U;%bav}`2irYQ-Vy)2U~N#NYri2Xg++`*DY5MnkBn%d=0+di zcm7jRlgvA5<2w{g?osESk8{({b&nriyr2J}aTm`^ew}8`=-6&){sTmAGld)NPiGo= zRje(}TP}e_2DnQ$mEVZ{x;i)WP+yHyJ+a&ECzvN1GX6Fx9h{COVI-6@9~9*kb+dr{yZ&{pX$T zm24my_S`V{fopWj(~Fk;ex&gw?|LV;j#xf)_2gk=gwY{IeLsE=;cso0?>xVMzfT>X zncCpVtxlh6^jYn{uGjLe-i_OjUfH+{Ui>b6aAdpRdawBQXhzol#WS}Qw2M-o{e5TF z^s~LDD`%}QP8l)2{drdY^5eg@dOJ!;=oigZoc{Qr++~OR_H{J|w|e_$bG+LZ}&F5>2`goSJdPkOrHz-l-C<{k9+K^+qjO;?{!_1YZ>Ufe`7rM zym7bAk z8G54itB((s+#Y{v_oO2kUzYFA`_(O|dEJP96Bp;NjM|$hInXp}(3ytKiW%cxwEmkp z;MA=Eeei4Tt?|wW-(PaNHD{P?$cd)0^uodgZJ|HI z?X`#Tar`|d_h;z?a&^C?I-TutTl>Q4^Apb<8%NDQ*(Z%V;Ox`>nvE9@1}~=9+D5n=U&)A#a(klJVFo$^HX94xJ~cw@hgUcVIfsJVaNtn$C& zq=mhfX52_^(e=yOryc5KMn0N*Zd0f!hQ0-uXriWv-e3@mb zmJ`NN2!Z@)bG4*3yOKV6;ea`J&ep8cTc3AsaRfe_9Hgw@FG9LeGS z3LKh1X1|d#+iK-74k1MlFS@u}Ay#5RaH%cHg0=z+zX1y-fan%B%*uiSScpLoqmO&g z(D`8KstD2P^QB@0QP!D81i7EWkmkQsuJNSHekA_LHxgy5WG55-PUOF*j zO}Vr7${f*%C3}V++%far2*svr^9RPCSU)c1=^^KP&krXbe(Akn=jRFU7Edp2(EQol zb^TU1bNw^C%e!l76}&j|^j;dpIan80Y4-*ze$^ z;?WCT&Nj|W|GEo|8_&OdOM zMBQL&$o)3I-W>e=bnubdTQhnrx>HcPv3TpMX={c*Jy26L``KTA++L#@r$708Y|UGX zMuo527az0jL*$ju>v|cV)}P(9d&l%PwSN2AFn-9#dz_~`X3yIAF=G4VS3Q}?F1mqT zRAam6N!#65*>+!dMz$*sw%BgFHlvrvc_WD50b(dD1wmIOOJiLsL6#{EJCn8x zmKKmu$EzbIsb9ue|47zLAl2@8gHY#Vmi`uPZ){{Ui13)`h_vg*U|V z7v66ljs1Sn&G1*R+HEKMxUaf)=;E?+k8k7KCMGFPZ(idgJ3Q>+&A5E=#DZ{M1s2fC3|f*ajrMyMjfSA`Z+ld; z^6AnblI&*{$d+5kPO~+=Rt=gPt&-FQm;NBh|6GCm(LnxK)E5P>w@N)C?OwY!h^{99@qbL$ zSgTkQDY3ewR!i3mlCDox(p3-WT34B_8UXQsPnSBaTDoSEbX6D?ycq-X8b``&wUw?& zkQeFDn$+9<6jc+n%5KpUSj6P}xw-rkbP(_#eeB+Puq;GGB+IJ@>_!59 z;s{vYXi-6OFKyz2oMfs<}gU}K%AlUx!Qwzma zFabHs(CY_>+SsiJLSE#fh8Xmt}FH z!FSU4LC4-Reo} z3BE46Dp)&Uac)=H+N;F^??Rk{F4K(VB*J31AaIpnfX}W(}Ig!Kw3pl2CPdj3<3(y|vA9dEtc{#=WKe5WghZS(&)I4Vm}@8rN?;jZJKE}c-4QTxlJ zk|7UPa&uDFAq`vss$o$uc~wDm;yt-3OWg2zgaOQ@4Li(^?BnnhN?tG%;q; zc1^!DFtmBx(QmR}dJNetsc}Kkc+d8^Q3Iwu8Tvl#T++trL0k|&e6dm+8{2@p052bb1~?em5cQMH@HXxF4zTdY&Q5T=&GQLn&FR~ z*#B4;*2>0*RRfeRwqzhK{_V>4KdT1+V*~v^Z8}Kk0T2ZFfzWI94*G9|&V}(NCG>4U z|G&^H)z@?o`eoJ+h3<4N;J*<%Q(+PM&JX{;&?^bO4+uTwsLPMM9}xOKcjtpUkWD>= zeipZ~2>n_kBy@yj-Ju%)GL}W=aFD_GdJ78{zV639*4VcH4KE9=wf07Bgarp)*2~># zQs@}bRcW}K8e)L4pHs-MXyEAt9}imMS$H+d36+Ebv_GIU`qZuWbNvPl8#Qjyv{~~O zEnBt5yyJR?9}Zw!bT2vY3ctf zj8eeZG24c*GLNA%5s%^?HXhfX1RkAHNyM8FN~6y|r0|bq@tnHnmoS<<)u&AKXPvo&+u=kM^muH&_4KAB5&5WN)h&NfE`8S&{YZh*wcMU>|qYr3)|h=-hsqUiV1~`s>Tis?yF04 zN5XFPCyncdw$A3((4x(@G=_bcC)$EZjl=eF`h6kzUt@>RXdQqG@C+dYL4(&1E0C(X z?`bupO6m^1a0N(p|G6E~1>$qlV_MSn=eAc;SDq!~N#rZ3>x=G9Lnu%@2??R|fLd0_ zm4pzA$O?8Z|AwF<=(MCwk4M*Acpsp{E2%36_324MZh&^}xQNZ@ji3YMnh3HAAV&fw zbXB5RaN#ORGou2{F#x>`2EOkFQ$8^L~=I{3~kMgUg-4lj02U3F% z6xX43a)URA;i?GHE%X4`YOuS{oF75lqPIYt*$zRpFb&(iag9iAdtjQgdLNJ*EZLwsOq@~6(MP8l-OhS{VEo7Q>u<06stI;v0XeV4I+8k1h z@lcKeg8^;4ZG`ReU}G*Z#tp&Lu&JtqJgE|80F1CxFl5q#*2EZ`j0&oRy{|L|I06L_ zP(*>s6edj$Ax9dL@dLd)L%pNBMx}O7iowusPFPY}WI}utMrX3w8T=?VJ32KQ%S5{} zVqhC=c1$82BTphXpVgAjXKD4>?9^mxpGhtk6|nqf$&GMOh-WAX7PawBv5EtGAK$lckqsAFuwq~y2;XPD2N#B2xJR>)5t1W6v2 zX?uW9LbL4bj-kU}ZI7sfMzA(K8K1)!Q0XNk-8fccrQec^!xc(#Af9|+&PJWYT1Dop zte_x{SqM~KP!F7+2E^#?vXeXYLIpy{o>&o@Wk_x^ZB4YCYY_-p(MD=S9|T)3%j}d@=?E7qZRP`8;3y3n z6Sm#;Nm}iR(AiN?g}qof1um=PliXC_4um}>s~jydhql;3;C2=VTM43v_LM2O3{1g; zi*JRGT2^mpS*z)$u9MwV`wE@PtkLzBJwvf(mb0Z(xj*D0HnSaq_`UkJx*yqA&vtC9 zmpvm}|0cBbxiABcIz9vqwbdAGcL)c2n`;Yo_%Ws+RCxqC;~Mr%w;lj((@(X6w`H@h zJzPGgoujm@QZiFO)jxE6svQKv2vTMuV}gcQj4{zczZFaegJZJoY{x_5m&wIDXU_EiF%>Er+^ zpHM-!V&7)ilW7l%61++E1FTkE+7MeSI@y+?%5K$z?T~Xg?VLRN(KD3XS<#c+lHYu! zjkQIEzR2N$nQC(v6{_G?)mLIl0|w_f7Gmi50BG;P!yxyCh68+z1Pjn|&Ap$37Z#s| z!I6M)lo~XjCIi?p))oBp=)@ETItGj#2EK_3)4>+p3iJ#CFktywlS+5w3-WCMd5%0K%7$P_Ru;K>fAOsm&J5d)mt3|qq@Xlj5; ziUhVI1=6uBRiPOgoGl1aApL6Bl4{%P8)RGUU%9Pb{T8*=exED1)kgrL9wIpCs_0j{ z$PeJCt@blIw$-cOTH5L^V#l^R6qMGHFrtlNfnhmpK#TE!GYuS;3&%o|8r6lx8+{G1 zvpuD~EIk3oHCj{&M~EQi^#tV5z?lwk08l;-RtbCFnm0;j*^XwgJ5<8#ks~J&U;%D8 zY{8BWbl?IAQ->-N5D|gSas)h3MRa&Jpf?j14O{QZu{0Htr-Xxp7?c{&3BAzC!qG^U zN`d9WVfSFWRHJ3J0&r9@*MVecB-GG0{13sB%`lu_ zLl28WF0PC$Pc<5pL0MWn>If!pCKH1g2WICGGnuGeG&k_n-nlRcBR9`Q-qOkH>okgF zx&Q1}xlXTdi|TZ{W0mW4>tCvEB~akX{b%1QAwvAT-O*-u3XH8lR)_Y(Hm zO6=~jPwtnfV~1SPtz}@>T7Is{T!k?yQtkQ3L*ZyVj`NXi8h80+bS?;|L-?YQ_xGQR zw)(*E5>KFZ1?jYs0}CY<>%#DP?BRY0@#$x)WgB@t^S7|^U|FRdh!TsG07bSxm#TFj zBAXf85uqlwg+*;E@?kmaot-OP&VZ>S>0Ibr7}IC625 z3Jye`bE&>zQ1i<4eVZjK#nacFT3zyXs5txSrjLHr(rynwZ}#rZgPl6sO|{x4t9(-* zn+sanEyw6xS?0IblK;qLkpp#P;3rw3EV&zz*b|p9_kv`A!|C2|-q{v=EJt`N*5-;`?r6LD9 zMKNQ*0`}dBjtom5W(+NQ9(gH+2VG__`)2z+33cVc?%KJVuLf&5lr1djoa?df{E}zJ zc7=VDjLfX3?6YmLU9nKNXtBLH)65b8BJh|=eiP9OK!Jq6fOqY#J)GaL*2(sc4$uj_IkL; zq^cv$HOnSJ?`NL2(VXs9qu%QupsWKR3s2_7i$m6ife!6XJ}v7D+0HGLDKoUDj=l!- zeU?2N8hvx)$^?FoAkw~Tj!Vv&{s~nXnkMSw>a! zn1k3kYRhfFwFTo6*Nn?X{z+vPEF5FIizgra^iyEUB;AMMw=N`R91HB4Y@gt*b2<$} zHLV?_VaqIPOT3Dqn(HtkGNCvckmU@p(6%6sP#p&xs+0vnb<0#{!B7qBm%n+u@^)c% zWLQ%`=cQ4$5&Qj+y{>MMVmwGpd9?pxPOk3=MH(< z^J72xv`YLk|Z- z9JxQ|$p#xl2V@~4uLI;cz=Rh|vnU4^3eZf>k5bTl6wtGw?q3AWlGX$uMj)xf5MBEvkg{LNX``QX+?6D4LNIpWrZ5KO{xQ;^2g{XFSq9z z>7WBXl5pstJ1JBjrIppYeX8Qmd#|r}0y^;@@qyYVfHnjpm)x8|AxABc^$yH> zV4PGN=-Q3?5HToDBO;6wAXd>WFInwL5e)z_mW>#hC`V-FPhj>w_Wi)bw4(1{4%spbMo?Rz%J5=kB>6#}JFsQw$uk0ctNahu`f8|c zj!B2NS;4Ya^DCSI3cbVwpsOEry$zc1Vr9W)bP4F&S$F!!}zwiH5} z6?%jOS$yrQVnnBrdjMJeEFE1+G#^#bYzXN11$ZO_l}|wFA5Nmv3%Ow z+LWDP^0Xw)QjOa-H+ zx5pAssZ*2uX1a|nF>1=3m#!`{Yl+k}$r(1|sDwRmG+@#;6mW;IQUrejo)MQg*CpUi zpl_mV79J80hE&X^(*CS!D$HGRSD$QL%l54mZzZaO!<`PptzpW*aKkhwJ1+{;JjAOu zZVMF9^SV~L!0(I~U%Mzk#38Utr~&ayf|#c=Wu%DkrnRn_K{TZiA9e8new_conns.cur_items; i++) { + conn_and_tuple_t *conn = &pd->new_conns.items[i]; + + if((conn->tuple.ipproto == ipproto) && + (conn->tuple.dst_port == dst_port) && + (conn->tuple.ipver == ipver) && + (!zdtun_cmp_ip(ipver, &conn->tuple.dst_ip, &ip)) && + ((info == NULL) || ((conn->data->info != NULL) && !strcmp(info, conn->data->info)))) { + found = conn; + break; + } + } + + assert(found); + return found; +} diff --git a/app/src/main/jni/tests/test_utils.h b/app/src/main/jni/tests/test_utils.h index 85708ffd..3487fdae 100644 --- a/app/src/main/jni/tests/test_utils.h +++ b/app/src/main/jni/tests/test_utils.h @@ -26,13 +26,14 @@ #define assert0(x) assert((x) == 0) #define assert1(x) assert((x) == 1) +#define PCAP_PATH "../pcap" + void add_test(const char *name, void (*test_cb)()); void run_test(int argc, char **argv); pcapdroid_t* pd_init(const char *ifname); +static inline void pd_free(pcapdroid_t *pd) { free(pd); } -static inline void pd_free(pcapdroid_t *pd) { - free(pd); -} +conn_and_tuple_t* assert_conn(pcapdroid_t *pd, int ipproto, const char *dst_ip, uint16_t dst_port, const char *info); #endif diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..fc20aeb3 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,23 @@ +Tests in PCAPdroid can be split in the following categories: + +- [Java tests](https://github.com/emanuele-f/PCAPdroid/tree/dev/app/src/test/java): + they can be run via `./gradlew test`. They use the + [robolectric framework](https://github.com/robolectric/robolectric) + to mock the Android API, allowing them to be run locally (without an emulator) + +- [Native tests](https://github.com/emanuele-f/PCAPdroid/tree/dev/app/src/main/jni/tests): + they can be run with `./run_tests.sh` on a linux host. They are built + with the [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) + to detect memory issues and leaks + +The tests are executed on every push via the +[Github workflows](https://github.com/emanuele-f/PCAPdroid/tree/dev/.github/workflows) + +Apart from automatic tests, the following manual tests should be performed +before every release: + +- Test on devices matching the `minSdkVersion` (currently Android SDK 21) +- Test on devices matching the `targetSdkVersion` (currently Android SDK 31) +- Rotate the device, put activity in background, clear from recent activities +- Java memory consumption tests via the [Memory Profiler](https://developer.android.com/studio/profile/memory-profiler) +- Manual malware detection test against `internetbadguys.com` and `0.0.0.1` diff --git a/submodules/zdtun b/submodules/zdtun index e536c593..240ae645 160000 --- a/submodules/zdtun +++ b/submodules/zdtun @@ -1 +1 @@ -Subproject commit e536c593d1fe49c67617911b18bda23a13138e5d +Subproject commit 240ae6459c9ef55c90ab8fd4fea1e60d8b5ad8b2