From 9a5c0fd75a4c20d3f4ee2b231a3cf8fd5bb437f6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 25 Sep 2001 18:47:49 +0000 Subject: [PATCH] made keylisting faster --- g10/ChangeLog | 10 +++ g10/getkey.c | 47 ----------- g10/keyring.c | 213 ++++++++++++++++++++++++++++++++++++++++++++------ g10/keyring.o | Bin 61904 -> 65256 bytes 4 files changed, 197 insertions(+), 73 deletions(-) diff --git a/g10/ChangeLog b/g10/ChangeLog index e33e175d6..8e6185d79 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,5 +1,15 @@ 2001-09-25 Werner Koch + * keyring.c (new_offset_item,release_offset_items) + (new_offset_hash_table, lookup_offset_hash_table) + (update_offset_hash_table, update_offset_hash_table_from_kb): New. + (keyring_search): Use a offset table to optimize search for + unknown keys. + (keyring_update_keyblock, keyring_insert_keyblock): Insert new + offsets. + * getkey.c (MAX_UNK_CACHE_ENTRIES): Removed the unknown keys + caching code. + * g10.c, options.h, import.c: Removed the entire allow-secret-key-import stuff because the validity is now controlled by other means. diff --git a/g10/getkey.c b/g10/getkey.c index d18d3440f..ad009d3d1 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -34,8 +34,6 @@ #include "trustdb.h" #include "i18n.h" -#define MAX_UNK_CACHE_ENTRIES 1000 /* we use a linked list - so I guess - * this is a reasonable limit */ #define MAX_PK_CACHE_ENTRIES 200 #define MAX_UID_CACHE_ENTRIES 200 @@ -73,12 +71,6 @@ typedef struct keyid_list { } *keyid_list_t; -#if MAX_UNK_CACHE_ENTRIES - static keyid_list_t unknown_keyids; - static int unk_cache_entries; /* number of entries in unknown keys cache */ - static int unk_cache_disabled; -#endif - #if MAX_PK_CACHE_ENTRIES typedef struct pk_cache_entry { struct pk_cache_entry *next; @@ -270,17 +262,6 @@ cache_user_id( KBNODE keyblock ) void getkey_disable_caches() { - #if MAX_UNK_CACHE_ENTRIES - { - keyid_list_t kl, kl2; - for( kl = unknown_keyids; kl; kl = kl2 ) { - kl2 = kl->next; - m_free(kl); - } - unknown_keyids = NULL; - unk_cache_disabled = 1; - } - #endif #if MAX_PK_CACHE_ENTRIES { pk_cache_entry_t ce, ce2; @@ -334,15 +315,6 @@ get_pubkey( PKT_public_key *pk, u32 *keyid ) int internal = 0; int rc = 0; - #if MAX_UNK_CACHE_ENTRIES - { /* let's see whether we checked the keyid already */ - keyid_list_t kl; - for( kl = unknown_keyids; kl; kl = kl->next ) - if( kl->keyid[0] == keyid[0] && kl->keyid[1] == keyid[1] ) - return G10ERR_NO_PUBKEY; /* already checked and not found */ - } - #endif - #if MAX_PK_CACHE_ENTRIES { /* Try to get it from the cache */ pk_cache_entry_t ce; @@ -385,25 +357,6 @@ get_pubkey( PKT_public_key *pk, u32 *keyid ) if( !rc ) goto leave; - #if MAX_UNK_CACHE_ENTRIES - /* not found: store it for future reference */ - if( unk_cache_disabled ) - ; - else if( ++unk_cache_entries > MAX_UNK_CACHE_ENTRIES ) { - unk_cache_disabled = 1; - if( opt.verbose > 1 ) - log_info(_("too many entries in unk cache - disabled\n")); - } - else { - keyid_list_t kl; - - kl = m_alloc( sizeof *kl ); - kl->keyid[0] = keyid[0]; - kl->keyid[1] = keyid[1]; - kl->next = unknown_keyids; - unknown_keyids = kl; - } - #endif rc = G10ERR_NO_PUBKEY; leave: diff --git a/g10/keyring.c b/g10/keyring.c index b0dabe740..5b5caca49 100644 --- a/g10/keyring.c +++ b/g10/keyring.c @@ -35,45 +35,153 @@ #include "main.h" /*for check_key_signature()*/ #include "i18n.h" +struct off_item { + struct off_item *next; + u32 kid[2]; + off_t off; +}; + +typedef struct off_item **OffsetHashTable; + + typedef struct keyring_name *KR_NAME; struct keyring_name { - struct keyring_name *next; - int secret; - DOTLOCK lockhd; - int is_locked; - char fname[1]; + struct keyring_name *next; + int secret; + OffsetHashTable offtbl; + int offtbl_ready; + DOTLOCK lockhd; + int is_locked; + char fname[1]; }; typedef struct keyring_name const * CONST_KR_NAME; static KR_NAME kr_names; static int active_handles; + struct keyring_handle { - int secret; /* this is for a secret keyring */ - struct { - CONST_KR_NAME kr; - IOBUF iobuf; - int eof; - int error; - } current; - struct { - CONST_KR_NAME kr; - off_t offset; - size_t pk_no; - size_t uid_no; - unsigned int n_packets; /*used for delete and update*/ - } found; - struct { - char *name; - char *pattern; - } word_match; + int secret; /* this is for a secret keyring */ + struct { + CONST_KR_NAME kr; + IOBUF iobuf; + int eof; + int error; + } current; + struct { + CONST_KR_NAME kr; + off_t offset; + size_t pk_no; + size_t uid_no; + unsigned int n_packets; /*used for delete and update*/ + } found; + struct { + char *name; + char *pattern; + } word_match; }; + + static int do_copy (int mode, const char *fname, KBNODE root, int secret, off_t start_offset, unsigned int n_packets ); + +static struct off_item * +new_offset_item (void) +{ + struct off_item *k; + + k = m_alloc_clear (sizeof *k); + return k; +} +static void +release_offset_items (struct off_item *k) +{ + struct off_item *k2; + + for (; k; k = k2) + { + k2 = k->next; + m_free (k); + } +} + + +static OffsetHashTable +new_offset_hash_table (void) +{ + struct off_item **tbl; + + tbl = m_alloc_clear (2048 * sizeof *tbl); + return tbl; +} + +static void +release_offset_hash_table (OffsetHashTable tbl) +{ + int i; + + if (!tbl) + return; + for (i=0; i < 2048; i++) + release_offset_items (tbl[i]); + m_free (tbl); +} + +static struct off_item * +lookup_offset_hash_table (OffsetHashTable tbl, u32 *kid) +{ + struct off_item *k; + + for (k = tbl[(kid[1] & 0x07ff)]; k; k = k->next) + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + return k; + return NULL; +} + +static void +update_offset_hash_table (OffsetHashTable tbl, u32 *kid, off_t off) +{ + struct off_item *k; + + for (k = tbl[(kid[1] & 0x07ff)]; k; k = k->next) + { + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + { + k->off = off; + return; + } + } + + k = new_offset_item (); + k->kid[0] = kid[0]; + k->kid[1] = kid[1]; + k->off = off; + k->next = tbl[(kid[1] & 0x07ff)]; + tbl[(kid[1] & 0x07ff)] = k; +} + +static void +update_offset_hash_table_from_kb (OffsetHashTable tbl, KBNODE node, off_t off) +{ + for (; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + u32 aki[2]; + keyid_from_pk (node->pkt->pkt.public_key, aki); + update_offset_hash_table (tbl, aki, off); + } + } +} + + + + /* * Register a filename for plain keyring files */ @@ -93,6 +201,8 @@ keyring_register_filename (const char *fname, int secret) kr = m_alloc (sizeof *kr + strlen (fname)); strcpy (kr->fname, fname); kr->secret = !!secret; + kr->offtbl = new_offset_hash_table (); + kr->offtbl_ready = 0; kr->lockhd = NULL; kr->is_locked = 0; kr->next = kr_names; @@ -369,9 +479,15 @@ keyring_update_keyblock (KEYRING_HANDLE hd, KBNODE kb) rc = do_copy (3, hd->found.kr->fname, kb, hd->secret, hd->found.offset, hd->found.n_packets ); if (!rc) { - /* better reset the found info */ - hd->found.kr = NULL; - hd->found.offset = 0; + if (hd->current.kr->offtbl) + { + /* we do not have the offset but as it is not use it does not + * matter*/ + update_offset_hash_table_from_kb (hd->current.kr->offtbl, kb, 0); + } + /* better reset the found info */ + hd->found.kr = NULL; + hd->found.offset = 0; } return rc; } @@ -405,6 +521,12 @@ keyring_insert_keyblock (KEYRING_HANDLE hd, KBNODE kb) /* do the insert */ rc = do_copy (1, fname, kb, hd->secret, 0, 0 ); + if (!rc && hd->current.kr->offtbl) + { + /* we do not have the offset but as it is not use it does not matter*/ + update_offset_hash_table_from_kb (hd->current.kr->offtbl, kb, 0); + } + return rc; } @@ -441,6 +563,8 @@ keyring_delete_keyblock (KEYRING_HANDLE hd) /* better reset the found info */ hd->found.kr = NULL; hd->found.offset = 0; + /* Delete is a rare operations, so we don't remove the keys + * from the offset table */ } return rc; } @@ -714,6 +838,8 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) PKT_user_id *uid = NULL; PKT_public_key *pk = NULL; PKT_secret_key *sk = NULL; + OffsetHashTable offtbl; + int offtbl_ready; /* figure out what information we need */ need_uid = need_words = need_keyid = need_fpr = any_skip = 0; @@ -755,6 +881,30 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) if (rc) return rc; + offtbl = hd->current.kr->offtbl; + offtbl_ready = hd->current.kr->offtbl_ready; + if (!offtbl) + ; + else if (!offtbl_ready) + need_keyid = 1; + else if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID) + { + struct off_item *oi; + + oi = lookup_offset_hash_table (offtbl, desc[0].u.kid); + if (!oi) + { /* We know that we don't have this key */ + hd->found.kr = NULL; + hd->current.eof = 1; + return -1; + } + /* We could now create a positive search status and return. + * However the problem is that another instance of gpg may + * have changed the keyring so that the offsets are not valid + * anymore - therefore we don't do it + */ + } + #if 0 if (need_words) { BUG(); @@ -807,6 +957,9 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) } if (need_keyid) keyid_from_pk (pk, aki); + + if (offtbl && !offtbl_ready) + update_offset_hash_table (offtbl, aki, main_offset); } else if (pkt.pkttype == PKT_USER_ID) { uid = pkt.pkt.user_id; @@ -824,6 +977,7 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) } if (need_keyid) keyid_from_sk (sk, aki); + } for (n=0; n < ndesc; n++) { @@ -894,10 +1048,17 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) hd->found.uid_no = uid? uid_no : 0; } else if (rc == -1) + { hd->current.eof = 1; + /* if we scanned the entire keyring, we are sure that + * all known key IDs are in our offtbl, mark that. */ + hd->current.kr->offtbl_ready = 1; + } else hd->current.error = rc; + + free_packet(&pkt); set_packet_list_mode(save_mode); return rc; diff --git a/g10/keyring.o b/g10/keyring.o index 66731335ee8d2dab6b73663e2df0cb9a4bcc3b9a..edce50322a2de8f7fee129f3f2e66ae0416499b1 100644 GIT binary patch delta 19241 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 Q9c1FPYzFCEyZGV%1M&{*^8f$< delta 16035 zcmaKz30zfG_xIO6XI};65u9;GMFpc0!8sN6VrFU%p;iu zKiJC>Eq(Ce)@?3}9X)Df@tml_UYZc{n-*@pn8!3_R9+K^%8ON1RV5qSv1z&0@P_zd z#!|R7eo1-$*y5&34$k?xaO)pMXE)Hrci>ue=GC*GmGrIWx)ps4$HJ}WMwRprCtMgy zagUZ4eN=cv6^+!}`$uuk+xy2l_gIacl~%9B5zlZ*mXwDY<|P59%s1u8%K)24FSolj zDw#{!ML!j1MHOeqE-9Z=^BF|aG0PcdcOCRP3X!%SrL#+h#TV_{`7t~Tk8?HCstWn( zi`l!lIVV|iFu&KblKgg5cy|=dNkv6UX2*{*k16UOQtVoV#h%Pm0c! z?Vysm z&gz)P%{HE^sw(QQ-61`Y9rLfxdx0NyOgW#&q^hZos&xgLzGJOsX!;?w>e$wsmXlNa zr}&l^kTk2pB#m40o$b6C*GgwaZO?SRj!UhXy{{%g$$td;J3J2ulD&40zrsa2d^S(IH`mG#20359>VQv7iU$JJbxz! zx|tp*owKjr6ZPsAXT=sJ6lcd5^++o}b``gq)2c~5r?`Hw-oV0MS8?WZ`k=+~3V@@X z-Hlr|8lCrVl-^{zsUhNjNkUZ7pH9ODSJv$c*HI(G9fE=0&KcdHb;I}IhM19~{g}AL z&pFE*v{##*cN@&B-xmerho_Z?ConS1Y}h1*mkxLRD9dU1uu|_iXBy2^DN9`PFmS&ySPOaws8=lAF=q`w5HN2ANUC_?t z<^vl%z~h(~Tuhk0g$s|Xd86~~D}`^SHNunVCE>}m0sKA+orwy> zqUET~l9=F1&!!g%?t9;YD| z$1RhKPo^o{Vj6~tHOv&Q&=hXG$NoTBENO$ZBw{e#OLaLzgdp8V^<4xnrIz5SlQ5bx zsnD3`BU5I}bK*j@!e=S$NmOF2FPjHH5Ya*<#N&ZOkd{R}_~D4wT29!)ObF5=lpy># zwVWWz3c5`A33RF(g7j1bucU4;zhw&gfGOw}!w*WK%e{2_jQw$P7F?APItV3+o;Oh^ z!KL(^slj=V{bpPjQG*w$mX}$OUXGZD>rMA?RFGbw4&w7Fbr*gOE$N0JZ3xj&QC_E! z!Y|V(;f*v_coR()euwgf-=q1$o9RK}_i4582dJ7Gf>cf~8blulG10=)E=XI6S5po_ z`UGxn2+}s%CA@?73GbvM!n^65@F!Fyyq5wPq#S}s83zua7MZk`Y;N9oqpYKv&cj_B zg*ci+5>g%!JLElAm(DxK?Qt%^>P$S_VsDWC@CXYb zL1o0S`Xyqf9;1Ph;CRGLJwfBe{u|8^K23{-&(O2NztcOyf1s7!5F}fDZkBN9K{VMh ziKwAY3D;46BiB`z#D12vH1au$7ygS9h0jw5;R}>1e33H39HPG|Gpx`h$`U3uRM=A2 z3rDH3!qIAyaEzKETua?09IF-x$En4_4b)>`3`nkhL-n*oB&cVFTd22%6VY#9n`dPR`NSzcVRsA7+h0>PW)WZ?{zD%@FR2zOE4 zg}bV&g?p-Ng|AYh4VwwsOWi6FeN>Tfrg}uUpL$HVzbX|Tpf(B*Qu~FkRsR(ptiBZ< zqRt2pRcAt?3{&TYhb!9-*M5YG6&|gc3Xf5V!Z)f8!Z)c5@EAGhd3jBuQALuxki&xHn5I%%GlUgXR z_l!L}ZVn@!$473jA%8?IFcC|Q6X#Wq zPq@^F!Qp~3^8+|=%Q4@Fv`OTyXKTTwv@)WFo{r#BzqLa2OvDgcqndJ#2tj&YwQ>=> zR=GC|wy%q5p%>I(@p(!4M~s&vM$;SWcJV1wbA{hh3&F!#$I(EomWX%N8sYbpe+PJ9 zZ4&zjY76*v(^@M{^KLf0O4(+2=U5KhHq6@)n?&0c+m3)sX&XDZu`fPKH31U_MkyUm*kZb7HOe$Ev1fEtK( zp!O|X&@UqjdLW{pUqux3kZK_Le}jJWMpcl$RhLP`k%*4_LG>2<&&oe;9990s;FyXy zZkW#EBtbfLKCNejkSDj9M!Zzbk%i!y!m#BRcV%`p`AQ{+Iep_>wvxd|tUn zGERHJ98VgfRWi{kw&%lL%&Th>?CmJVIt&htfRaRgt--j{P6n4!riB)zJ3Z!Al?j{6jfaR|=*I4z%KFIQ0bg_e<9;h`bRZ^rAa$r3T#x=nb5RUkava_=@= z%MsR#V$Zf-7anbWEIh{ACVYdnQ}{+}uVM6mkZ!X6D-q)@zZr9^zr;S#3U5qYWB>R( z$*OJU3g)9*cLepG~o+TQ$IcdTg=Jtz6AJutDRJ;4H|B4@JY0L`$u6s77;eDcxtP zk>W8Aldc}~PzrkN51FMAReiwnFT*94e_?pgYR6AU2vUjF!A0vG{YtzJ2zylssU-el#1N1&-Q(H%9B z`FM}n<#vy`L+>(NWX9*e4c~7%y~Ja;qZ5b3hn|2XiMDVedJSAk<))+G^_V+)ugCtH zu`8l;cU!)_BBFEmS}izJ1Z)ct{rQ;{bj`4T9kOynIb`8ECIsm_YliS)Yo73rR)O$O z)_ua2)-vH=td+vYt<}OOBG#DStPRF)I(nb=wm6-!-WUGesu0%pm%;(NQn;r5KjA35 zN;ul~J3Yp3Dt%bXZY>;ZcMORVZ+8@~YxfkcXJ0K`-_90pX!|$AM)u8OPq1$jZfg6t z#Afz9u{XEf6B+i5AhocUNJOIjkZ>#eQQ`TH|+GK_)52!RdM!1t5FPvf56Ygv`7Vctq5bkPs z6%KW`2Z++c9xU9;o*>-Yo*|rRFB2YUuM-|@`!~Sr?89OoZiinG25F=n;0*&INTcj_ z%ou-A#@NXcaiiT|c$}RjJi)$IIM<#be5<`c_%^#hc#3_Wa4)+^c$)pB@N_%$j3_hg zHNrFP4}|B~hlS_aKMCJypBA2Px6@oNuFTzbs_+84r|?3%uka%KW|#TQ7_t{iM1fr( ze2;yvaG||ic!~X#@V)jr;rr||;idK#@F6TH1@w_sfh+7!hCjA`1fMdz)!I=N!reBH zoj$SlAtKJ>a&Q}PCT%loXsWSqw>HDx)8kFx!5(h}-{kQI@D#&4to7iz9=`xCG`!P# z4*VE%h;~_}P@XjryRDVrR}5EJE5O{O<@^U>ld;Fzd?DXzIL1B<{?u@7`#^+$cr7`M zbm1;z=2qd5gb#D*D$hwLO=sZW7Rsl<*#ByXan^q_f%*uZ06=aW75H~NR{XxKRf zX}R%t*|GiO`D?bn7i_S3Z|{YC78CWM>3Fc4V3isKxWSE}*Q1Ilc^S55MK zR@ZU9y}n5(U+ zrtkyZ45FJDrOdABoX$>&J&uLX8-};pJY2ST><^di5yPdz_RocT>{jsSfa8MQ#YJ8o_S^oM z?|_}-M!^1+JyrN?`%d9+?E8d|*#5ELNBbGE|7`oa{BQPlv7fU28^iDRId6Ny!~SRc zSBA3@+xa=$d_#*2aGSGll)dUg#BO`ZE)aW_eLr|FS|t-L$7AJtk9mCjZg^Yxbs&{H zPjrn7aS9IH2h0|xKoV)avK~gWj)QBU6@+(7&1>xO!!*c0?@;U?O@t2EXA8N8V;5T6#>pRX;oKVK8| zD!&g}>GhJJwe~x(jou~pw)!*Sb`jiO|1S1q{WmxRt(A$E<96=@E;{N#UFHIpMLou^IRwuGu*4-;XBh zZr+T*Yhm4Ac#`%{$9dYnLfx+Y??I;N@NB}D+IqR9ouNyGXX-V=x9he35q*yKPrGyV z`{Hz$-XT0+|0cXpH?-X9;AXBn3x{+M;R1b?@V|AYaG`d;vM@*VC3>tx+^g>qUaE_Q z@7GTWKcL?eF45(}59?2a9bF+jNdF`}So??6Avz}fvNlM$+W%_e7Tre9evj(*!jI|h z!cXX-!cXdK;ivRO;gx!-@Y8yp@G8ATIJ8BesLXJN z=A+!l;7m*>PP)%O$w;1=2KKd!`<}f;FLrtnbh56lx`w= z=pBgY>+x2+_o?U17*#)dgz@a9EoV!18)r;Td}ur@Nwi*fOCXvCE~S^uQhk@lu_(k+ zk9n>?>M?JZYYe}vk(A!?I1$Vth&=tJ{k4dteM7|3UKY|#Tz@3ksFQ`?(u0LJMesY? zf2H!SzFB-W>j#BD&?|&L)XxZS(Vqx^q$>=Ysr#`$AQ3wwmf>Bxmduphy18(L?k&7W z4;0=TF{StEd&It97YqL+L^jGJV5 ztVw;3;l73+H+-w%R}JHihVY4DTn7-oHSE44dwDLdXzuUTXtUuo@+!EJcIueYi$z*WuMR9bIW4v&g*v75n%$}6^xQl1d$^IQ9aGag z^_-nMX;!W=rKLb;)6_dA-JUnOM{?)Rsc9WkQoCfZ@wV9$*q#e}$5gm>@50twrca!f z+ao!xQ)-u#)U;0BQoHr+N&T-II`aAn|0q#CG+&;?`DBT&b zeP~>#E?mqEv8~xY)9TdKX}F{F^6wfuckJq=Iy$fHy7RL16q=nsaaMkh8C)6G+suhos>PD;68Rw+YTL5Xx`M^={;s8cjc@FX260bMD%Q~0KhqYvYzw7o_a{Qf6rl+ z=9KOI-PylyzUovqd=`b+&%f>P#&;L3P+iu`FaXwt;#?ei1S-xKq56LZ?mF1!0kuC|6XbB_Gn zIxyoX(F!N}Xd7!;Gv|t<2EuNZ&u#Ie)aZmGd8`MP)mSPCEl2a=vz^S0bygiDo(XAglG2@&6k84W?NDUkzfg zH*$7XHmz~!2+H%QTQ9)0mFJ5*oR@QZUt$<<1$Fzo_Q@` zJsmoK52@h8dOzrg!9e^UZGJ|^U$8ZB<42pHC-JvEd~4wZd<){QP550s>x-cC+m+*9 zJa46ZeKuD)#IJ(RS2?~9u>UKN1DiR^e;pLs2;{H{pSpI^<9~TZ*(@#{>!>^{< z%>Ebg;k>TIhx3c#dU=848$z#y!5G8aC^gw&KYj57Vm!9?LLAZ@ZW0jhpXww zs_ADVbsk$59u^LqVfeK(+gK0p?qQvG_){3GZ^FO@%!2*`?vi(S`Z(wb*lV_U`W)zo zpyTFm{O^Yz4k3O(_*BnE)?b7}4yN`Nj8}fZricU_|7kTnyu*jnpR5)yFB`o4>v(%5 z)R1{#_@p1sAk}v;@!=DHIDSyI_>s^P5gA0&)}b?1qpQV_tES&3dV9CV;dkBP2ZWCc zVf{X*`H7Z&*C5$SoSXT6!0qymWaH-YX*K=pYWhj%l@l#*Zi-grm4ZK9SbmiqsrRm? zXIIlFSJU&W>5H}V?Qe~p)hAzE{#_Gi$*CUynl@qLRBV(JCQZ$qILrCqRLdIECfqVB zH@EE2sdXxFd+uGgO)i^%=2aEq)lVTizWOVSutZO`g|x7Mbz&Xy*RT$!87#5-{hMV| zGQUsd`1PLO4z@SP==1cmoDd>uY=zy6Wh&7sFP?`Gr`yI7EyqVi z>vFtUgH$18kjw@+@CzG`SjqAVT+diyQH_BQCrp9l2d1;$5kJOYnMU*;OQhS%5-s!} zmMBOIED%;Q{8^$v-61)BZ`Sd=>mWmHxWS7U&k^YJlPr-zCRPy6Ae$vJxCxT|b67_P zGdz91CqryU`ukZT{V~t}KTn?bWEIDw%j-m;|Jm`CC^zG`J-OMFTOir7f+hUwVu$1| zxe}5KlEFF(GR@PU@Z>&E7RDeRE%ziO`=7=(Zs8?xTr6Z)v`8%apCkIiz;Yl*pa$=H z`Z?C|#%m~N)SajnrVD@RoW-&ye)q!iDr|o&d*R>AKyp5Rc=D1bZJd5M-yv8_xvR!O zvR^(g##V0}p|O>7L>x;zXbek~{8pBI@I8R1ukiF&S!UvAku3XS7O?DxZ)G4k<6l`v z%lyd_W1`BF9dJZY{V}T`tswe;2m>;>o+UE4i5-wZ4(rI^PL^5tR>hN!C)ctZfE|e? z(r;#o^jjf0{Z7`A{u`F?JH--yXIa9p3KD)H;x`NI*aVUjwqpHiqV_D?V$=8ReLVX> z&pyhtkMr!ec=oxTeW7PB^z8dt4kS9tGL`5Iqza(_&$0mpxD3m@bq!G$PX;|X)RW^N zxqvsb4uAX`L$U^;3s^S8cF+K_HP#81t%xSFMAOgqWCcq$wr=R2FbIc8_U61z}SwApYr0LWjpeHnI-ZqXW5PDN0zAhuaI1T zlaL{^SK`Hv<##0JPeSq{P8h^fMQoF|*JM9ZYGY(aD*%M|<*HkK&Z11vM~a*HJj z_y!~w@Ez7sp^sRip!?a5mipO?Kaqg`XNN!80X1%d9gYV{7nbPTK`hY%V_D)slUU*b z(;)c)vsg#QB`h(WRi9IPE#K#hm8{_JrhwLDq+2 zxAOE5>(>!I%o0udqGvDjNDl$CA#QtPcLDK8b87kHD2f0-|_6_o_!}IKW7i?$gh%RD*FF-FXEgR zQ3F$nA8;j0jDa4W#4ICa5y50+1q=$~Me}HePbo``jjLc|zjBtX(FNSsZHRKX#+X&d z;iEcXdq+kp9sM6ft@vl%GvLfW>r8+%XD}=Y5+xqX5;}enY2=TdtVCw28wT$cuKjv! v)$BLblgm80(UTj}-T15U4EEDqA)8-Il+@ETZ0-Z8iFSDM^FB`9OJDpyF0k@N