[or-cvs] r11280: added new tests for proposal 114 (in puppetor/trunk: . doc lib src/de/uniba/wiai/lspi/puppetor src/de/uniba/wiai/lspi/puppetor/diststorage src/de/uniba/wiai/lspi/puppetor/examples src/de/uniba/wiai/lspi/puppetor/impl)
kloesing at seul.org
kloesing at seul.org
Sun Aug 26 21:18:53 UTC 2007
Author: kloesing
Date: 2007-08-26 17:18:53 -0400 (Sun, 26 Aug 2007)
New Revision: 11280
Added:
puppetor/trunk/lib/
puppetor/trunk/lib/bcpg-jdk16-137.jar
puppetor/trunk/lib/bcprov-jdk16-137.jar
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/AdvertisingAndAccessingV2HiddenServiceOverPrivateTorNetwork.java
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/DistributedStorage.java
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/HidServDirectoryTest.java
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/HidServRoutingTable.java
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/RendezvousServiceDescriptor.java
Removed:
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/examples/DistributedStorage.java
Modified:
puppetor/trunk/LICENSE
puppetor/trunk/doc/howto.pdf
puppetor/trunk/doc/howto.tex
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/RouterNode.java
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java
puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java
Log:
added new tests for proposal 114
Modified: puppetor/trunk/LICENSE
===================================================================
--- puppetor/trunk/LICENSE 2007-08-26 21:03:54 UTC (rev 11279)
+++ puppetor/trunk/LICENSE 2007-08-26 21:18:53 UTC (rev 11280)
@@ -64,3 +64,27 @@
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+===============================================================================
+The Bouncy Castle Crypto Package is distributed under this license:
+
+Copyright (c) 2000-2006 The Legion Of The Bouncy Castle
+(http://www.bouncycastle.org)
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
Modified: puppetor/trunk/doc/howto.pdf
===================================================================
--- puppetor/trunk/doc/howto.pdf 2007-08-26 21:03:54 UTC (rev 11279)
+++ puppetor/trunk/doc/howto.pdf 2007-08-26 21:18:53 UTC (rev 11280)
@@ -3,100 +3,88 @@
5 0 obj
<</Length 6 0 R/Filter /FlateDecode>>
stream
-xÅ\I·
-Îyâñny/¥×" ®>%Îf9KÙɤrHå idÉ¥ñlK²_Àn¶4£¸\eõt7Iëý¾9¨EýWÿ}üõÅý¿ÃÓï.òÝ>ü±_=¿øæB×?T}ûðÉ%H£=\~y¡ëë&èżwøÃå×ÿ<~~RKt R<¾:Eiáø"_9ðêlÜAûãºu´ÇËSpÖ·§³^,Äã·t>%ø×åggoR8ÆÁ).¯p_Î -qÏOù
äÑ>QÞøD$©ÅÛ-F9Ü£K¥¥È)0H>¾YÆhë8'rÊØãS¦äWyÑ»á.JlIÆ=þ3Eo -Ï3IL¥ òÑFòECL©Êk¼x5£åUv` &k:j~Q\Þ£%
-ßUKåí1ô»U~pÐvë«üp0³Å½»f_¸Ö(Ù`ÁR©Ð5íUÒëLG®ù,Ò)$®a,í/_j$å1±=ð®Î`µCY*²$TT´`ÇÚJ³;$T}ã³B6bóå..IçìqÌH²èkøêlÈzQO²ÌU9zD2:¥HVòm¾4Jźf8x=DGkZô6MßX¤äo3
-=ñ2 ×ê¯Fk\v5ÆG$XgÛè5QwÑÙEÙ-9Ftc^DÐxqШ&7bã²Ñ>bÈJlLp¾mØD8~BôÕpêbôs7¨$KE+:>ËL4.É_íÝdðãÇeEgbݯMAg³-[g&ð2rrºçlgn
%E?%í =öèÙc[M&ÚuÓUá£~õ³Î°ø+Iù9¯É´3IMyì ühB£0©Øéâ¤ÉSöè óL³~tÎÚ©
-ÊKÔ5ã¡íYÁ@t5GPѸÐY×ÀQ´ßß¡ø §²Dnk:×
Lv$L·'7*ÐtÓª,ò&, ®ÑOÑî
nü=åÚ]Ò<¡,]Ö-¡`puè¦
tù=eBI_uÎÛñ½CÓz¨Ör»b<('樦íK,#Æ®&»©ÌÖ´zÑÅÙ¦ß
!²X0ÜFW4¹¦+è¥~>áË·èÊøxÁ«hOÃâTÅg}M½í¢+:³ûæسËÌ£ì
nÖÝÃÐãµu|s+w¾3vQú¢YpêE%<HO÷üDHN½Í¼Ñ9âM\'X)¹¨yxµYs^zpü84¦«5/Ie^uøæ°úõÄýÕàêÃzñW7:-QùÑZ
-odÔº^òM1tæßLfg]j-yxûEQr± )y£Í 5åmD=è»_IoÜÄÒØÞ-Ò@"0#%(¨cc®©°K¹$
-«÷ÊÉ»9qÔm"C¨
SÞíÍZÐ6˵ÙaBQñ9"|-1°Måd5LlevU@6Éè½
¾uú:-ð¥¦j?äØ
-°q=ì(,5z±^xÕHÓÿI7âæô2G`júJÒBÕ1¦wVc8Ûöý=glálÂϪÄ-í§ÀzÎ#<- 1jéÉêÒ^d
ñSï¥çßg5dã6ÒÒMÄhËOà÷PuÞs.èOpÕ¶N@Î3Û©/áÎ\±ì¹,
|ÒiRoNÑP 6RA²DëVä
-yü¹æf"Øv#gfNßìD¬P^þ¶è¥G¨zdÍi _w£¥Äx×HröÐô`!\6KÐ6W2G+ã4¦Í
-~ ¾_]MÞ@wHÓð1=§W¡¯Éìdä±<ôWqK'È£¥gÃÁ)#éú³k*V»§ÉïÕrIx'²zCäm
-ª8-)¬`z~À^k)¸
-ç/ÝOæ°a´§"ë®oä<cmªÌàú¢wnÅ< ãï{¹è÷Q@~Á÷IÎÎ&ºõ¤³3àM|ò^V4iEísγ»mi¤RÆä@cuL~X]¿ÇÛ5u.Ú
-(ÉÍëæèe_Ó\ÿðs
=~ÓÍèÕ̯%Ò
-Uù^uíeݵojk+S[æíQ©à£Ï
ò±´GÈ®øà#¡·)g< hW+07²Ù`É #k_lB÷ê¦Öçw9·é-è:9Ìvj&ïy«àS[fo% )ðR qÔÃ+8·Òë´ÕpÅÝ $uËV&R²MF'Sç¿®ª2»6\,<¤§TÐk¤[oG'VKD.<<3G@òLË-Q3åMëgò¤!zQl'T×g@+²{·É
-ªÖ-ám!$×µ!|ËÓÒ0@<ÛÙݦäËdü»¢jU%½ruÚ`ÞÙP8àÂI¡ÌÅ(_jZ/úQw³òAyçõqW¥{D%¿?ÑIU~^ Y#Åz$¶
-oô&´Y`v(ªè»ÂÿϸcE3r°aD>jí&Åê?ãìÔìköË?Þ¼LÂlÆ?SRæ^¤- ¾¼·ê|èÛ?@{E¡:^wÖ½û®Ec0Þib»ÉÂnåeÛé#|¿xaÍõ¯
-a-Ý*fm£n5'j¶©¨J]¦gíÛ7jS8·Mé·yz>5ÜI`Úãìÿ(#²¹ªðÓ7¿xÞ ®¡NKºénq6 mõ¾ë(¹E,3="W$éÒ¯×ÃÈw
ü?ÔaJ-oÅÔs c÷ñªÃÐÐáÐÚ÷òÞk ÈæN5<K
-´yÓQVâN9½÷;ê¿_°NËãIù$/k|¹C=!Ò5/W
-¶ÔmÓÃ7§pHõvÛmj.ÿ_÷QîV¶&ùqCwðûäûñ-G9xE³âf*/ûÙUH«>Ò¡¶,ê®ME98['ÃÓ{6å³FYïé[ÛÝl>&õm¯öH?OVRnÓ
½Cü&àµñtVQ@ù´6¹AèYñC¿fË̦ÕyÚÚ)ôÿàT«©¯úæj£ÂÞt>ðh©jÝQf´D&%NxuÓàer;Î<|oRiÔåêQË2íüèþLQÀΡ׮êÙíé¹ëzO0ø`chÎX#zðén¼n°b4À¼^(ɬwéjæàÈBvSXÂnå~D«±[ÁiÆ>s·Ý(áêû¬I5¯«#5Kâ|Apv¨lð(nzCN/¯óÌOºkÇ/c#áD«ã3!\_ ï¦Êêkö?XÃ05ÐÆý_çëÌD[6§¿'&ûèÕ|¢2ë[Kz5)·?Öªw 2«Þ¥-ÅüEÍóÜö§ßEù&é
-J=$zÈ·$1Ï«~ï%ÊÇNë$¿¦&Å=¾¢1çªyêëÂGÆ'FÛ&uã]>«j6K9Ã)COóLU14ä¹l0 ^»íê»}tóÒÏOS²¬SMªè«4èÓ\ówÀ]Pâìé7btµÊ-MF"m*ó-:Çwu§³fªËÚÅû¢í`Nå0÷ú¶£Kø½ÊJÓѦh&ÐàEúîñÊéBXè§
-Ä ±[iæEãÜáMK¿Ña¥¨^uk{Úéä+&©Ó³0Â=^º§GÅT}Î=°µñ?mÊÆÂêqÕ5Cnòïv9³±bZo£iMmcé7kÅ{vº¼=ÈïãBòém1NPs}Â=ó¸Ø^_o=rLåVi}ߺsViàº!_itn¨h¬aÑS¹ù;ÿ7©n=ò½bã>ëÃ6\½,ß ÄÜmÔìþé;r;F(>Uo~ñªOþDa[ä7k8PF®øl·%ðºy6wþµ6B3ÍwÇn-Pïb«>"cÕG*¬õÎçBÍ6x¢"ÓµlHÄÛ ÏÖbW=)ó'nÙuÅÁeß
-õ§Ï$3²?§Ãô#Cvek3ð4*8 ;-w@?84]Fí"Æ¥þÜ KïR¸ëZ'ÐÂ̦âxtW+\ÚãÙö©ýþMèÝ|qñìêäendstream
+xÅ\IGæ<øG¼ïzÚN`#à@p4²äÐhF¶%Ùâ×óeYÝÕÒȶ £î®ª¬\¿ÌÊ~_Ä"þWþ}üââþßüáé·âðþ{zñõ
L/Ê?_>½ÄKñ ý¢¤5Ë//ò`yP^.êà]´;\¾¸ø×ñóX:ãëZÔÞ_¦++#®ÎÊ.AKw|B7æxyòvRéãíé,£ÃñºÐ.FýïË?]ZþpVc8\^aßÎZh¼/¼^ÉãÃr1Fß]l°¸U¦=cMiÇG¤ò0bÀ·Mº+LÅâµÊä@¨ôñïm¿_ÑSfAÔ(#
+cÇuÜ({Õ6¼fÚ,6h±uâAtBeü㤥5ÞßùxüE_^j Ò?;á9þP
>âH!ß;Í×mN¢åþߤ<HÐa"}8Ë° $K1fâ,øòh¡S.IbqÆkëAY|T!Òþé.ÒuÚªã
+¨&¨ãµ^ ëÀ=º,§´ùx3&91öÑÞLY¡Ìñi§ä×iÁÙá.$¶De´9~Sº.ßg":vbä#ÐàDCL)ʵ¼/^Ïhy]§X¢µ$k:ªþ¢ë÷è£Ôü±¹Î}»[ä§Ò,Ú¸"?Ì£Õál°w[íK?õæø+-e YûíZùóÌx
+K¤uMkÉIõíñòÇJx8&pZµü
ò~WÌðS´{¦ß'Ï&´°õ.iÓ.Ý®[L15ApµpÓLºÄ|OéSÒצNçíÄøÞ£Ci=¨5ß.ÛCáaMST+³%u1â/Suqà;3Ä.Ö,»Ñ0WuÎëç~ù]/¸
+fqä±f3 ÏÚ}LRôº¦èÝ7'`Ï&3Ù3Ý,¡ÇkÏêZøfWî¾34Qº¬YJpDõ¬NsO÷üDHNf½M¼Ô£sÄM¬ã
+À\Ô}{µZ³g^zpü8ÓÕ¤2o:¼9¬~=q%¸:¿^¼ÆâKn´Ìµn&Á¦/ù6:¦ùO'³±.VÔ<¼ý"+¹A,Õº*y¥MÁÒ6ô¿+_qo\ÅRÙÞ,ÒèH`KQ×¹¦Ì.a#7¬2Þ Ëïæ+¬£%©üì <g#/Ô«B¤vDï5ôÓ×x¬Acªýc+$+ÄO¹3~iÖ¨¢ ÛHK|2q£-'>i·ªÓSA«¶utÙN}I¿ù«ÓÙ+¶=ç¥À'(õö`E@%»"oÈãÇÈ5w4Á¶¸9ÃddüîÅêyíËßf½t:¾E6FHÞÔäëÎr´¸oà©ãÓ=TcÐr° .%èY£eqÓÖÀf¿Fßů®&¯ ÛÇiøɾ§WÁÏdHïdä±&é¯â2
+çN°æ
+cBSõï®m
+¨ºÚ=L~¯Kü{Õ["lkPÅÐäý8½/Þ+ªýÌ«Ô÷1¢uâAºÇYÁôü {%ã*(Ï_LaCIGEÖ³X_ñyÆÚT>gÁõeî:%ÝyÆß+{ÙàöQ at zÁ©IÎÎ*Øõ¤³3àM|rW4iEéRγ»3mq¤RÆä@cuL2~X\¿Cm«ºOhçÎusô²VÍ4WÀn®°Ç¯½ùñµDj¡*Ý+®=o£¹öMm`
+byj+4Ò¼Ý2*Õ\0ã¹P:¶òÑuE=s:ÓyÆ
+ DÚRAÜLÉf98'¯}uºW6µ>¿K9¼ï@×Ñ"Ø©|àAFªOm}½¸Às%ÄRSNïDXÁ¹^ -ó¬ÞPK2AÖÌae"9,¨Xy2uþëªj&³i3sÁÙÂ}pJ½»õztb$GäÌÃwæ0H>3)bÙ±%j梳¼iý4¤C/ªíêr0óñ¸R ³çpëÜ êÚØÅ¿+¤Ú°ä!¤çöési íìnSòídü§ jUã%½|uÚ`ÚÙP8è
L
+¿ÈÔ´(_p£î&åÓqæ×Ç]
îüáD'UÞ»ydËØ*¼ÑTjÑû°CPÕDÞþ?ÆÛÐÈ¡ÈGݤØCýg}Õ~¹ñÇÈf¬fágJÊÜd`y½]õng¨BRX4'f§fì.âûÇ{«Î&°íù~¯H3TÇËÎwßÓU²hã&6¸Þ¼l;}ïgovXs=ëLXK³YÛ¨'D$toU'jÕzÚ6z¦¢*u¥]LkÞ(5^¤pvÒoóôtj¸ÀÔÇÉÿQ²¹ª¼îOßöäøåÄón8±
+Bä(uÓÝbMdÚ2ë}ßQrXjzD:¯qÒ¹_/ï?Ëø!|¬Ã0H^k½ëk]Uõºê³òé²Ú!óVúè:ûÕªCÑÐáÐê÷²+å½k+û!J¹©âÝ5íèfÒ@$l'>ÌÄûyÍ çMx«Ò'÷ÞòNÆsúnµ4ÈÝ1x
+¼CH=ì{ÂÖ¿ËúØÍË·K©E[¯¼ä®%ý@ÕRoì{óh#(r3i?¢ôï//¾¸øú ©SF¥ÏÞÏf ø[{lú TN
+ô!ý§.î?øËáÕ7¯\ÜÿçA^Üÿ#ýߧÿÿ<øÝág¿pøb÷{÷6ð/ì%5õD?±§@ðÇ/T<Ð'dBÊô
*À°ÅdöxÅéðÜg½ù®B¾W¢I¢Ì&Þ´ÿ/ôå6%ÐK::ê3Ç
+PÿtÓ§ÓRªgÒÙrî$ÄѸü"Ü%ïºEÙð>&S¬ ä/Ûè>Ïm{úMöX4¸ß$}R©DùÈæyݮҽ(|þØ ¹Ntkj¼÷ÝëW´2rPݧ¾Îì±d<ð°mrèAVÞ¥³ª:ài%03¬Pôùt³Ó@U©SßJ2
+
+àY/ËÝvõm.pºûÒÏOc4]§ªTá4èÓÜ>$åï»
+ÄÙÑo4+öÕÕÊæöúòJR[5*¦góÙZ½Ü<W Ê!Ó½K¦*Ý(F1À
+]ú¶`róaåH3Ç-Aí
+ÇÐ㻺iÂÀ¬êvíêû¢í §²È½>§íÈ~f¯v¥įDêªh*ÐÝ;é7zWJüB?UÀ±Ýr3ÏïùÖoúÃEõºYÛÓFg¿ê$5ÚSF¸Çq÷ô(ªKùBµnmýª?H+&
ã,ªkúÔäßìrfcÙ´
+ßFÓÚÆÒn÷ìd)x»^ ¿CóÑÅwÅ8FIÊõ ÷ÌoöÀÕíõUîøFèác
+·rëûÖwÕ®òJ¦Ê.zj"7!}çÿ9ÕG¾mÜ%}Ø«Wù;ºªÝÒÑ?ýpGjǨíOÕ»¿xÕ&KDBãû%ÅW|6Á+cÜ"Ó¹Pµ>QjZK6ÄbÊígk±Ä é·äºÂà²os
úÓgA²3²=-§Ãô#Cfek3ð4*¸;<vWMÓ¯f#6nÞ2wÖ~phºØ5$ Æ¥üÜK
+ïS¸ëR'Ì̦âxtW+\êãÙt±þþoY|qñ_fgé endobj
6 0 obj
-4887
+4886
endobj
25 0 obj
<</Length 26 0 R/Filter /FlateDecode>>
stream
-xí\[o·~ò#ôxNaw2oI¢IÆuEÓ˲e׶¤Ø÷×w×!whÇвٳäÃo¾¹¬~>^q¼â¿üßǯî?tÇoâÝcqüçzõâèç#ÿgÍO}
-9K°ÒêãÓ§G"?/_à[Ý"áWGÿÜî½Z¼q»«ýY¼Aí^ïOT\Íîr/íͪwOö'pKêÕî~ÙK·»ÊÝ»½TUr
-r]ýîLdL wÒÞå)¥ »søÞV·{f^å©ôíçõê¦^å1FzXÌ$`Ãaw½?r1^²d4/Jµ{º×V«5_í%qÆn<¼fWN/v¥cÈ+µ}¼GUãe[>y'µ
-_u¹f1AHØ(S» ËÊîîáqÙÅÂMºxáÇñäG Çv$cVQÖnà÷çûuqz]qÿ:ýþHz³c at _NÏA?áaEñeédkiAF
-Ìî·»gåAï¤Lx"dèÊS]ÉÅAöC~Oû±*ªvÑðÊgQÖ:"ÌQSP½"Y6MÂ7Jó÷ Ôº8&ëguÎöàM7OZ9§1Û÷)ú>0Bpk¯YâéàD0ú*,®DIÊ?àlB;_áaK )otEÖ^G,UÍI°x¥chÇ]Õ
-n6Q\Wá]×_Ödý£¡°Q0<
öä#g\¯³
-;±AbÄdÃmúÞPÊ×öffB1X@PØëÍ=Y¡hÕ6ÕDß®7
-bÄ%)ßíOVø}UT¶¿$Û6-\ÖàÐ.wTê8lK*âYn2mIØv%
-Û]áy±ð¼
-Í»\0 t0Tz!5xæ`¨]ðúÖN§©mãUÎ=ÐJ¾ÝÃ(p!¸z¡áe]Ô{ÓgE)WCF-ìÝ°D}wTKT·MÙE°(QÕøE.ÉÛÉô¸l
¿Uye|Q¯.8àU:-t5ÝBóô¶R(´;¯½zÃ/k¬x&¨úÔ¿Ã2°{AÔ@v´g¥ÀÕPp'?kb8åD~Yôw'ÀrAº'mÙ`:ÀPcvßá Á{
1Ñ$y=ú"¡D:W$3o7ßÀ}Þv´Ï{¨ Çú%ã`2@[''t²½h¼DÀ&Þlr Á>·«Çß¿¨Ö»áçÙSI÷Õí¼ú?ðßæ#.Hßd¬Õ/©' <ZlwfKIÐFâdÅîÛý]`þÎ5kÂñÿ"[{Yo¶«Ê Ãa
k<#B
-<"%O¹æ'0gdq'
-§íBBµváL±ªÞ$À±ïê¯ÑEÀ˸%Ã'E\qvf9à¦Ð-4\PoÂä·5®l÷ÚÙ¾Ä4 ÏóâA:êF ¬HïMä!ÞDa¡!
-ñ/"ò#ïäª5¤¸9+CQµv¡-Ä ë2 ã&
-Mî¡áæúELTÄpeNL](>ÆôR]J<ÙÏd Lt¡¸½MvU[¢¸:l5ªÃ#®nJZ»1ì½èÐJs
-ý}LLUuÍ«¨êJxwÜC£µ oÔ<ÝG_d:fÚ;$úº£å¬è ÕXËTzî´0ôFëßï=ìR(²ú!Ò[H3ÔÆWçMcÝçj:K9S jļR GȺ(ÒÒSOì`cÊnA-f|&1Êë}J,k<".°=½ÊÛ°ù§}U!γEu8[ÜðÌ8Ñܳwr`2HxÓ%®°uî*7!pÚ95xÒ/GLÙì!+ZótÇ'BcYî|28 Ë®ËUJ¢DxU$Û¿ÈN7rûfèmªD |¬ô¢0P¹É×,¼¢PT80.LÖX¢îë&-ñé}=`tvq¾ÿk#¡]Y44¢hîÑ£ÂmHwÒá
-jc ¥=;»ñ²C8«nFOCçMùÎ3@¹ ¶Û\ËÀØÛã
-¾
-R1zZ/ÑõÏ)içhclRè{o^Ô+ ÕÁ
uD;nmÑ¢ |§EÒÏ45ÖÛÔÓc.õ;è(vIkiôPÁ´òZ
-@í<{;PÞ¢ÖnãàÆR}j&+@]¸³öå¡òʹn±I¥(-feìrÒÕ3(bÒÛôI:31Q1f6A,1ä)%'¨Ò§ú±uGö
-aQ×oSgÚ°zÃÏS^û>¼ª/\Ϧù®Ëж_Ï=Ø
ó,£©¶&ÍE¥p8ðRa¶YÐ4éeMwïeVæõLO8íbJmÞiev
-D¹ç-A¢ºYD¿ýiù¬²ìÑáäF§´O37åܹ68¶ÈÏö Ý`/iâW0^Uê>UÛ'ÆeK#9)p0©ìb&w5rs3-Gè|D6ÿÛpü¬<w÷VN·¼EX×Ø}ÿ{0%ú öáÌPH|B©
-¡È|ÏÿÜPôfP/<k¾?ã0ëÜð#=h7³$þìú~AÝdVËs¼YÄòFǦ¹Wï£$ÍÂÁ»÷Ìî{°P)cötRûØ
-¡c!VÕô´À\¶ûMkÊg·«lJæKS4!CQRéH!¦ü u}+vø0õ c@Q#ùn»Çs·Ã¬Ï ¢JNµ¥ÕU=ÛøÈÀ1/ 5Zy-|6'\*Ê&>²R6ÓT1{HýAÁú)N.>ýÈ ÕY=íY£qyÛCß:%Uø:ÎÚ3b{KêóÙ[ÀDj
-i|íêgó
-õ6Þ
PümxþfW~ÓûËViïD»^qÆ«Ý+éI3±J LÖÌ÷#¢C.&9®ÁJ2ÍìÙpdMý-¶®=ç58Ö<|ù_¤Y%§%´ÉimjcÃÌnÑÉìÀöÝõrîâü£W*¸|66ÔÆÈ Æ:E_©!aÖÆ|L¶2MHiì[f²ò'סô LÞt£àD~
-Z¬¹sÏòR&éíbåÖ~æìð±A;ts¢ÝÓ´¥Uðgb½oP-3l5Hù]3Õdâ4~úÅ/Nä÷ãVLLC-Bö¥\°ÑfZ)ÄÚ¾<ýæ;æ
-kãç&¶m
-íK½>«Ù4e½Ô)\r^óÑ«ëC×8Ô±Áõg`7sF#2añë¬Ãæ²mûÄeª+2ÝSLN Ûy^nGõùìçµ·},@úøR&÷¥sVC×ûyã_³-Y`fA³
-Î\ջܾ³2TÇ/ÇPHö«úHøôU1 Øfo?Ýö©h+xí\[o·~ò#ôxNaw2oI¶ ÆuEÓ˲e7¶¤Ø÷×w×!whÇÐd±gÉ%3ß|sYýr¼.âxÅùÿ_Ýè/^Çÿ.~9ñãü¿Ç/¿>
9K°ÒêãÓ§Gi´8Æ/pÇnðÃË£îN÷^-Þ¸ÝÕþÄ,Þ v¯ö'*E®fw¹öfÕ»'û¸%õjwoöÒ-Æ®r÷n/ÕbFÃ¥\W¿û&²
+&»GéïòÒÝ9üo«Û=N³K¯òTAúöózuS¯ò#=¬f°á°»ÞH¹/BY2N%Ú=ÝëE«ÕÖEÃÈÏ/÷Æ8c7KÞ ³+§»Ò1äÚ>^ã˪ñ²-¼ÚL
¯ºÜK³ $lÄ©]ÐeNew÷ð¸ìbá&]¼ðãxò£c;K1«(k7ðûóýº8½®¸Ë~$½Y1 /§ç ðÉ°"Áø²t²µ´ £f÷ÖÛݳò ÎwR&<2MItå)Ì®äâ û!¿§ýXÕOI»hxå³(
+kæ¨)¨^D,&á¥Çù{MPj]Eõ³:g{ð¦'Óíû}J@ N!¸µ×,ñÈtp"
+
+}W"$åp6¡¯ð°%ÐÇÍ7:"k¯#ªæ$X¼ËÒ±N´ã®j7(®«ð®ë/ê²þÁÑPØ(B{ò3®×ÙEIDNÎØ 1b²á6}o(eMVNÀkû
+33¡,L (ìõæ¬ÏP´jj¢oWÏÄÎ1âÉïö'+ü¾**Û7ɶ
++\®^hbxdYõÁÞ´@ÆEÀ¦`Q
+æàQ{7,QßÕR"¢åmSäfvQ&,JT5¾@QÁKòv2=î_Á+Æ'íÎk¯ÞðÁ
#'"+^£ ª>õ/à°ì^u&Ý íY)p5ÜGàÏáN9¥_=ÃÝ °\îàD[6ئ0ÔÝwxBð^aL4I+"`^¾H(ÎÉÌÛfæMÁ7p·íÇóÞ j'豦¢~Á8ÐÖÉ l/+â@ª&ºÿ¯Vñ9ÊÅvQ,Í],ç: ÖwèÞP꺺óÍ!/ñID6ÜüDaÙ Rò`ynq¾£o¨¸Ð©k&éÒáÅƵ NçìÉt9~Öc,\-SÀ£5 @Nú
KâuÐq@Â&÷PpsýÆ"&ÀJ*bOB¸2'¦.czÃE©.%Cìg2L&ºPÜÞ¦»ª-QÜÎ?F¶ÀCÕáW7%-ÎËÝö
^tè%ƹþ>&¦ªºæUTu%¼
;î¡ÑZÐ7ê+Éàì6NÊV´·ÒJbï[åvFö0êò@örá涹n©d1NØè¿T,´Gÿ.¬á]¢¨Ø¨Ã ÂEú]Uç*Wµ»Î溶î9`RaWUü"æ¤:HEs½¦9æGI&ðqw3[Lïî+ë's*-n¹Ê÷¬+ÝGÏ£ÍÍ$JxÝ
Ò-¬iÄëà®$6ÅMM3'G$È+2¬gd½Ü¿(ðw«¤I.6eÓÅ£ Q_×½k|®«kçYMÑY´
â²Õ%hø¤ç3å&ÉdóÏ9W¦ïØã¹L?VËÙdIÍlNù>ïUk8wì£}>ZY(W|+bZpDÒâå`z³TWØp㱩Þb<
øô¾0:»8ßÿµÐ.,Q4wÃèQá6¤;éð5I±ÐÒÝxÙ!IU7 at H£§!Íó¦|ç
+ \PÛm®e`lÉíñßIIC©Î=-Âhúç´s416)ô½7/êêàFÂ:¢·¶hÑF¾Ó"NégëíAjÆé1úDt뤵4z¨`Zy-É v½(oÑMk·qpc©>5 .ÜYûòPyå\·Ø¤ÒF³2v9éj1émú$Ïƨ3E DKòLËÍTéSýغÀ#{
°¨ë·©3mX
+½aÇç)¯}^Õ®gÓ|WÊå@h[ɯçìÂyÑT[æ¢R8x©0[Ë,hô2¦»÷2+óz¦§Iv1¥6KïÅ´Ï2;¢Üs+ ìÈR¦µ©Ú©sßÈ
+i9ì4¶?ëCáøÜVçS3¢t»?îÑ÷|Шñóéï¥\QmVì<¬Îþ¦ÐÆãl4-%e\èM7i?4
+E?W%ÒªÎni¾²=Í5K¸ù^©¬(2×Û:¤ÄëÁùos<ª°Mà=ûcüÀªØ(ô;N³/Ýö}!ûR®HØÊh3-bíßLþFósM
µñsDÛ¶ö¥^ÉÕlL2Â^ê.9¯ùhÕõ¡ÎkêØàú3°Î9£°øuÖasÙ¶}K
+â2OÕÆî)&'Ðíż /B·£úFÂ|öóÚÛ> ýÌ|)ûÒ9«!Ëëý¼Öñ˯Ù,0³ Ù
+çD®jÂ]nßYMªãc($
+Hûeý$|ú@ªPl³·nûÃÈT4ßc¥&Åú±óu0üø¼Å0OHmØí©§oU>ôßîÁòTüÆà+O7ϧ3[S·w9³·Ý]#°Tà°yºÜ}ÜéB| endobj
26 0 obj
4713
@@ -104,48 +92,48 @@
32 0 obj
<</Length 33 0 R/Filter /FlateDecode>>
stream
-xí\[oGrÎ3a áÀ/á ÄQß/ò°v´°v½fà8(É$RHYûïSÕ×êsHí:AÀ5ì¾U×å«K_vb;ÿ½=yøg¿{ùá$µîäîíéõÉ/'²ü!Ê×»¯Ï¡
-»¸D§Ù¿8å{iÍâÂο(xñöä?N·?3KÂûÓËôè£5§{ëóÞú¾·ÞôÖW½õÛzÕ[_æG/b8µÈÈÏö~!O,ì¾÷f^vRR1ÀÇo[§WtþÚéÎÄôÃô>ô°vzE7Âtú×iEöôø]ëtÛ¶No¸Ý9Ïûã5·æ'©Ñ+þ?Ïÿp¢_À;çÀ+ùÐ1æô×ô¥Ò
-"Ý_·FæL
°hvgRåþ«sÇËuÐj¥XùYo%[yW>Ðöß> ¤¾ê_õÖ
}|ØÙò;gì·oVåÎYøj =!42?$ú\²ò·ôÖ[v£¯æ©g
-~e5mébÕ:lôCÛÒ»^ÅÈûÛƤ<v±N¢vz?
-?ËʧþxÑFzKgbd
Õ$ËpLȸÿzrþÿ¯0ÿ¯*Ì÷ÜF?NìoX·X÷Sϻ¼i
-ÞuëNV÷ºuÂþYê46N¡)ïºT«Åäiô´7§ö
->>¢$-V®
¶'8ZG>äû&-ÂD8\Ü@dÂgjË£
-ºÒ[ËPRÚô(ÈÊ_^<Ìð®ÍÚçÚ¶AæïôxVNÆü @¨øÄÒãu¼ÇÇÀÅ Ú°X¨&V7ÙxÆ
Z{ÕÚ¦*H/¯¨!=æ\ª@ ËÞ [éÅ[Ù9±r¹@Ã[ûTþs¹¯°Ñ.^ú¹yZ»à
Ó/Úä8qE(ªÉý§Îcyõ¿Mì¦
帧£%ü²´Ö2LÅê¸ë°]% 4V¾Ù+" T§Ýu'mÙ-MhÂÎ×}#}øNýõíL3СS¿
-T]9RGg5ØÈxdÊcR1ò
-dÒN/_dSä#ß½p e^QT ¸$\Ù2§«.w¾¡TÁAH!!ÀëÌ]Öâf¼{»Ähª¼çþBøB|z¥]^êAXW"]2!u at G©´e¤'Tf?À^Í$ý}c-ÎÁ£àQWüÝþÌÙ¦ürݦjktEg£¼#çQ)erf$À²Z(ïý'¤Q
-2ƪöi-ûð¦MÁa-w x=G¿ «ú6èëQV#7åLìíÿJ at lúô®ÉÿØ[Ƚü65 ¨#ëT½qTp Ø$nx` Ǹ,P\ÒÚËàåpÂD®(ìÅÞ-¼åoJ´~î¦÷²Çy¿lqÞöýcºg塼ôBU`P¦`XU»±ð±C`ÛB´¦|Àå®æ.ÎÁã5àh;`ø<CN-¬¢©QUd½IkÀøÊ«®GΨ*ö4UpÕÐ&aÁ}ѧ¿k¼ßYÉcàe¥éµK4Ðû4ÏyE xE4¸uîiBFVÑ
-3î^®ÆIDW\å-y!Ñ.Ü0wr´M$¸C at -%´¢TäÂRäü¶<¨X|ÙãSýidþæõ<ívY}±æue6¦^ñ®jô»
-[Åê1(j
#àÊ4è¡0ÑMe¿xÊ`«0s!Jtæ4¥Q)àXóçÐì³ø(=(fãÔ5² ;Zû¬ÄúÛAüú*˨MLtÕcì³¼~9=¥E¹ëíËr© {ÙJÂÖ
-êÎ$*0)*©ÉçùØAççenxó¬.+x'*þÛ@ÅÒºBaó·Ï{ë[@ÖðÝ®R1\"8\ïöP;6éÿ-§«âëîúytõ±î_àÙúø`2ÖñÒMFsð@%ÜÉä¡_å<°cP©Ã¾ÅO¯ÿü¯°±èFAS
-Ð-K79Ù©ÅÄÍ¢3õROykÔ`RGnøø\ô«wî>ü½xj)ÈPÙØÛJ|¦!Ki=0d¼,á¨àØ´FE|´iL½Bª8öµ#9¾ í
-h¿o¡i0ÎBoÆuìÔ
-ËÃACö`Ø!õ=ÔRmåV½'à®ÚûÙìÉBg%æhØËu×d©]{z5CH¸¿îUÇÅ®9q8W2
-1;Þªî:eM8/Բùº&,Z6w«@1Ia7a#ìò]+áªyÍ\ñ{¯ÆÀoYnSâ[![7sqC#ù8Ö[¼;à¶kmn¤u^Ì,õääQqJ1iÔtÛ¼\ªÀX§!éµ²MªTgâD>×Î
-À"ð*|à[Æ=¿%²
-;2`p'£ÒRÓȸ"=6¹Bf'jóä/_G°öðvLQãÅÑK[ö@EqÑKõBrÑåT.8Åq>.¸[Ìa¬EB_¶· oÒý!Oç)-8_Þ¥¼ÅßK½CïϤÔ8øîcj<ç1é±r]6Õ]×&g~,wÂ8µß(ÈÊ9\õ[k>°y¯yù'óläî+kÓjöÖj[ë:ÿº¸Ksý~-W#/-ÂÌ;k¬?äx¡hµÖ #T¯q-Ee`tlÓw?O®QËFh¡ÝÈ*¤ÔÿÞP."g~YbaÚóN©R+îªSââ/Ñ;´ôú-ÇicB'Gê¸X>ßÐr¶äÍ`_pFæ÷F²¢F§÷d@±°¼'àp7W<ZsFÀ£cª¦©ÍU ©lwXPÓŦõ)U¦ye ¥øèçÆ;Õîîç7o%Zä2';çâPÐ@Ó
-Ûc
a¹ÀS¸î×À]6òÒG¬äòsH«î÷^¨TiBw£¼Àý|3v-Ç´©Æu¯öËjª(GLô-µ%cÖqR2àoù<k]úW¸i¥¢#=ȶûhþÚ§§«èïI§>ÒÞ ïó# Ïò!ÁäÓ5.Úåæ3)Ï~D_éhßÐÙP£·_Z-iKHmþ±-ù§Û¨ëéDåæK;ðÞçÇ_¨÷¦P:´ñL+xí\Yo]GrÎ3a áÂ/á
+Ä£Þy;±²hì±?Äy HIVD²Dɪ^«ûÔ¹ÔÇ}NoÕµ|µôýe'¹øOù{ñæäáüîåû±ûüûòä>Ø?ov_?
TØÅ%:åÌîéÜ[î¤5;/ü¢àÅÿ8ýÝþÌ,1ïO/Ó£Ö~ìÏ{ë»ÞzÛ[_õÖ÷lëuo}½eâÔJ> k \ìý"B4Z#YØ{|ïͼ줤b߶N¯èüµÓ%éé}é%aíônéôG®Óìéñ»ÖéCo|Ö:]q»'s>í7ܤF¯púÿ|úÏ'ÊùÅø+ó·Ñ»¤Æ[dOÛH¬Æ{Â)Ìç]aÞ6
Iï¦u'«{Ý:áÿ$õNE§Ðw]ªÕbò4zÚCÅÓG{
+Q+0áôÇ}Jö
<ÃÑÝ¢¢I¯OÅ:iaC÷Z/Zy²wH
+½ØøÍ^ÛÅ*ÊwÂËá»:Lb³H%4¨ôÏçéÎL:^Zú(£]9fKÛYDÉÅËëHÐËiµ=ýy/áÑ|jÊ-éA.Já£F
ÂD at 4L¢¬¢_ö×Àø*úÅ)
+Lªà [Ï÷ÀXJ}úgøNÚEk
+UÉFj-a#X]OWÌëA/Ápi!Ñ_Ãð1±F]Úû|j5£=oݯÓ
+æ¾!m
+Ê9ëU°r-,°=<ÁÑ:ò!·Ü«´ápq·" /2Õ,4Gt¥%¶¡¤´éQ:;¿¼xámµÏÿ¬mÌßéqQNÆü @¨øÄÒãu¼ÇÇÀÅ Ú°X¨&V7ÙxÆ
Z{ÕÚ¦*H/¯¨!=æ\ª@ ËÞ"[éÅ[Ù9±r¹@Ã[ûTþs¹¯±Ñ.^ú¹yZ»à
Ó/Úä8qE(ªÉý§Îcyõ¿Mì¦
帧£%ü²´Ö2LÅê¸ë°]% 4V¾Ý+" T§ÝM'mÙ-MhÂÎ7}#}øNýõf8(¡C§~?*©0»s¤Î>k°ñÈ<Ǥ&båȤ&_¾È¦ÈG¾{á@
+˼¦¨@qI¸²)dNW]6î¼¢TÁAH!!ÀëÌ]Öâf¼{»Ähª¼çþBøB|z¥]^êAXW"]2!u at G©´e¤'Tf?À^Í$ýmc-ÎÁ£àQWüÝþÌÙ¦ürÓ¦jktEg£¼#çQ)erf$À²Z(ï}
Ò ¨ÅGcUû´ÊýÎxÓÁ¦`0À;¼ÎÉ
Ö£_ÉÕO}LôõÖ(«
ò&övÌÄ% 6}úM×äÿÒ[Ƚü65 ¨#ëT½qTp Ø$nx` Ǹ,P\ÒÚËàåpÂD®(ìÅÞ+¼áW%ÚAC?wÓ{Ùã¼_¶8ïOûþÁ?Ì1ÝɳrÍP^úK¡*E
+0L(S0¬ÌªÝXøXÅ!0m!Ú
+Ó@>àÍrWsMçàñpO´
+°A|Á¡A§@+ºAÂúþ6è
+"U{~Ç,ÏocR1.ßÞæõµÿ,2fCS[ fµo
Õ¬*i¸
ÓUñMwÀý<ºúX÷ÖÍ/ðl}|0@ëxé&£9x IîdòЯsXÈ1¨DKÉÔaÁGßâ§×þWXXt#Á )h+?5sC£`I¼Îkä¦æ%4
gÆ¥Æ÷¨®«wfëX3Æ:Å%ôTÖ*æÒ.ÎvÃÊY
AÍؾ_LxaÒ-$Y}×Ú
_¿ûÕ¸æßåZ(ãñqY¤6ñpKÔÔ(ôM¬=Ì)÷3¸ÈëéÉüNÆöÎÀsþ°Ö¯ì·¤óø6uÝ°Ö:VH¤³ÒYÏéÆjÍ®0f+£eÐA VÚ´äjµâ#E¥&X÷îÑ#
+µ+OoÚ$¯êZ}K½wÑ)"F6Æ!âQz?¬µ$ÎRÉØ5t3d*zô+2F66¶iÈCZÙ"/K8*¸6-Á!Æ/¨6©WHgÁÞ#°v$Ç7¡ý²¡í÷-4
+ÆYèÍð!³:±PayØ!hÈ;¤¾Zª|rÒª÷ÜU{_Ì\!tVbM¹\wMÚµ§WÃY°89ûë^u\ìs%£³±ãê SÖñB-k1Ü«k¢esG±
+v£È¨6BÁ®(߶®×ìùÈÅèñ¿÷züå6%¾²ex3gø84Oc½Å»n»ÖéFZçÅÌROùANE§FM·Í«É¥
+u^+Û¨zIu&NäsíÑ+dpTÀÔ¸üü¤Zð~^¶§ò¦£÷«TWuL³©b¥;w
+§m(_^ájäÅì)q9qtò¸öìt{pÎ-®õÖd?%shCòG¹í^7ló2à}6úÏ!£ÒUùò«î"UD+sÅ]u
+R<qRü%zÇQÿÀqÚÐÉ:.Ïâw"´-¹ì+ÎèÁüãàÞHVÔHðâH"÷<îæGkÎxtLµàÒ4µ9ó¯
+4
+ðjºØ´>¥Ê4¯,¡ý\±Ãx§ÚÝÃýâæD\f#ðäópç\
+hºa{¬0,p
+÷ÀýZx ËF^ú\~iÕýÞ+*ÍRèq·sø±oÆ®å#Õx£îÕsYRMå~uÔÅÙ-è°Q¶X=mÄ¥ÕïS%b.[´Ýø-¸Ñ?%ïkkÌ÷J½\E=L®@xd¦ò»ìas×®Ë~¥9z¹ë=Þ,(½¹ºg¬«À!lò9_¿G`#OlÑx·QCqò£§'ßü²öQé§!Ï@QÀÿk{ÝI"þ½xsòõãÿmwûîÃó?îäÉÃoñ?_÷
+üyüO»¿9yôx÷ýæ¯Pʸ³XakÃð+¨L£ðwÒ¯Pê}À¸%4$w`<ØÜty wBßIx~ÛJËï6%
+Z¼þn·âïòE`Ö\*xI¹]@<0BúÙËÖø<;>8òe3?ÁJ_2 endobj
33 0 obj
5168
@@ -153,206 +141,207 @@
39 0 obj
<</Length 40 0 R/Filter /FlateDecode>>
stream
-xÕ\Y·Îó"?bà§Ù@Ûâ}$ðCtØoYëAyWZ+We[òßSE²É"»zfv%;X½n6YçWûÅFLr#ð¿òïÙG7¿òWGit#7Ô«^Éò(wonÂ*lârfsúøHû¥ÖSØxá'ã?ýc{y|b¦÷ÛGéÒGk¶?µÑ_Úè³6ú²þÐF§6ú}}ÝF·Ñ¯Ûååøâtù¤]>=öÑÌO¹pPIµývÛFm½NMNl¿Å¥ÅËoÛõ_êÍÿ<ýè+mG_3
äæDIjsz´ýüXOJ
-
.Brje'gÓÂc1j L°Â aµ
-àõôÆô³-îÔÈ4-í¤µ:Éx|û
-Twg[Ú%sÕncõ
Óü+ÒLÊ)øè7§þ§¬ß·ýõøDMJÈ $Xô+4þPø»q^¨Jà5r{'U0ÓÓúxèQûùòøaóéFM2ø2»¦Ì®lÜ>¬û×~
-ø]hºÌüFaOñV7Eà'¿R
-2Ò×R?>6ÑÂðìG\t¤d¦xZXÂ4ò;²wF;²;$¨ oÏ´´\V:\ààÉ:_×Ñ4è8!`ÇD¬ç'] Ë
ÙÀö7Ç( Ê t
-UÒ0&°B¸3l¿<>qÁ²Ê<yf&¬Q)Û:õÑzÖ(A-Ú:ð'*ÄÉy»9EeÏ,Ë"#R÷°ýÌ)ÐPYC;³éèM³l4ó®o7³øt´
£æíæ_Y{>ZÞbMG÷µÑûìñ³öX[g£¶´Í±JÙÔªÉÝL=ë¨}ϳc`}°! ÇQN£Ò÷<l¼¿,7XVr*ÛWdÙ[ü]y1XãP!¥ö¸MzÞV×Û ôqa $áÆag´
-Üv¡¿BxøtMP¦ÄLµ5'½ÜM/>ªÖú4óCZm»kÐQ
-,C|oðQ¹½WâÚÁ w¡PÙ®i»»2²'©v~iÀ°æàü|¾Åªó0¥¼óëf&öl`wY¥,<°°%O
àÌ_Þ¸Öì·½E´ö¦·ßÀQ&û¯íÀ¬=ÖöçFabÕÛÞp"Ü´HÍO8èa*×;ºd®c@·6ëv^¦5QÛ8é*,±D#P6Ïv¹Ì} UzERuxE!=±)ä÷Åe©¢íqK^ÔoeaÔ µã,Dzb{'wq5H}`wïBîR¬É¶-2i`)Èîį~ì#ò(ÔZ@oÖÓgøÔõh²$Jyõàk´Ð²R@vYÞyçÄõÀÀ¬6E]HFi}0
³gÂèà[jÍ-·pÏ(½·
-i°ÄÃ)
-¶Ï ÏÂkBUxù!¿_5dÉ%1&3Í<Oµ$ÕvÒ1È(+®®ì»!FIv4'ÈãË#3ëa54=§Ø`ñ¬*VÙ
-|HR¹|:$3ÇL³ lå$"¬¨ ûWz;§>dKÏïÉ# 5`Ͻ¤¹çÕ¯°Ós)¦>OÆX/ø>ûf커¼¤(T0P@Ý,Ò«IYAÆaFÝ#@öW"l¤ñ÷HbäqEä >F-f3WYÅx5B»ÌbkuT5vÆuÚÎA2ÊI%¡rD_!²&ú$%>ìÝcöÈK1ÜfrÖÈÃDê_Æ<*/ßäÒL1Ø×þà&;¢OOKµ&ÚÖv,}_Éç-ÂE-
°èðÒHÝ>:b u6nX1$²3Ьµ¾ÙuÐsÈP#,R%!"ÀI ³zðòncÊU|M-x9aõÐÑÏzn\HXn?-êaËü~µêzÊãl´f}
-2Þ.Zû¬]>dï%9»vÞÝILçºNß«4¶Î¬±<IbMø|À¶¥@ÒR©*b-}¼Ýú4¿ÈW°áÚ°="ÞZÐñdn©Ù?ÆÀ,
-|}ݬ¹4®6ûÐU¯Î*Á¾ÒÖ¢í½k<EidbO¯_9áÆþDífµ}¾f9Ù|ÿظ£'qKÍöè\êB§Æ{ÉèB§:?jLB§KU$~§ÎfûÄ¢J}bżÿV}büȼ¦âÇ>«Yù³«^£ÂÜÖû¾ÚSvp+Ø©kINPÞz
-Ï-`U¤v}-÷B¨-¤íòáùMF
-¥ÉNÀêÁH_ýÐ×بÒ'Oú£ÙìÚÓ®
-Î2k
-2*¸¦½Î+i/*[©c7°É®6ª2éÈ:÷¬Ò3Å´5Rå¾×½Çù~êòÂÕ.\åÙt¹oï]NmÒÁõú5I®ù¡06ThÐVtx'èCI&Ó·²h ð~Xî]öõ¥×[S\ØaÖÈ÷Ö³mD,ÔEï"
:d¹Tå¥AñÖ
j9àTa¡Ïwd-÷@»VÑ")³À©óÔ+xо¸æ4^sìçYö¡ÁÚ®5ý]·+I£"ù¹k1ê@#²ÛÇV×BrLÅG£äd¯L=¥v¹ÁµâÙ¢
y&¸ö¬1/Æ8/ª¯£µÍ'÷:é+
-»
-Ƭ;Âô÷ÒÂEªärg¯zÂ7צµ_{±"SB%3Ëvr^?xQ°3»wB'û_)w¶/tOóÔI1ìé,Ç_sC°)c$°´*Þ,ñØÔ-<½n~¦7»s°xÐâäGsGd$~¬©ê;¨¢§sâa4]õüèìj¸O^¦6iTÿe>¨ÿÀC1ûÐM_÷4@]Þ±ËYïñ\éJe<uÖ.÷ö\÷a_ëj/+k_TáÒ>$zà S{¡Oô%v2O×ú:f<ô?´GDß>ð åâ+xÕ\[µÎó?b§ÙÛÖý±1`
+ÆK¨TÈÙµ½¾aÀÊÏ9Z:R]R)ʸéVKçúúÅFLr#ð¿ò÷ÙÓ£_ùÍÅ«#±ùþ\½8éMùëìéææ)ܤÂ&NÑ)g6§òÓr#µÂÆ?)zôíåñbÞo¥KÙþÔFi£ÏÚèË6úcÚè÷môu}ÞF¿nãÓåíòɱDf~ªÌ
Jªí·Û6jëè0èÔä´ÙÊö»X\zQ¼üö¸]ÿ¹ÞüÏÓϾÒvô5SBnNA¡6§ç@Û{ÇzRR(¤péS+;9£Qäd «µlàô8+×1ÎK5B c¼FnÏò¤
+fzRo=j?_?¬s>½Ò¨I_f÷ÑÙÛu_óÚ`ñÀO¿Mßh"¬ã Þê¦üáWj²AFúzBêÇÇf2Z¸=ÅåéIG:Hfz§
%L#¿#{'a´#»C09ðöL»ñ¶ËJÜü#F²Î×õg4
+:N@Ø1ëyÅI$Èra6°ýÍ1
+òf]C´ ¬îÛ/OÜ$Dð¬2O£ kATÊv
E}´õ'JP¶|ã
+qrÞnNdQÙ³&Dë²ÄàÔ=l?sJ+ùýbqYÅ@ªh{Ü õ[Y5h-á8˱&¡¬Ø~»É¸È¤¾°»w!wE)ÖäN[+ìU &¢+ÞD%×JË.[svÄü'íÍ
+á y¨ý¾¸£+B¯ú( æ Õ¹´Dż£ È õtJCØäükâWÇ@Vö±yÂj-4Ç7
+ëé3 WB¤êz\ÙÖÎÀW¯<Ë ¡Bð5nJ8IY) 4»¬áïżsâzH+ýô³^¼}
+ ÉÛN8Ïg¼Þ[
4X"ãRËÜçÚgáµ@¡*¼Äüèßį²ÈÊäÎæP
+§ÚÂê;énW§öÝ$;S
+äñÇÆåÌÊõi1S+ÖÎá«<>$©\>RcÎYAøKB7Ì÷IVTÐÓÂ+½ÌS¼¥ç÷dаç^Ò,ËóêWØÇéιdNSƨ/ø>gìŤ¥(T5P@Ý,Ò«éYAÆanÝ#@öW"l¤øHbäqEä >F-f3WYÅx5B»ÌbkuT5vÆuÚÎA2ÊI%¡rD_!²¦út%>+%±èðÒHÝ>:âhqÃ(ÈÎXrB³^Öúf×9Bc|+CµHy$?g9ÂÝnLY²¯à½4tÜÉm2>OHMùÃo²«â$gìöª6Øií/ä³éÄ.ÎL G/{ïR_ÄF«³qÆ^K%-l<óC3vq#vIÐK®a©Rî
XhQîEÈ|Í'ó_BØeâK¨¢FvÁBcøí&áâ«ýcë+^D«ss§Jì¨pÖ+êL9ÈMгVð½å¿ÌÖøθ¶¸+Ý4f3È(©àÓÜHÊrà6ßþKU}ÛíeÔuÁk9WÿÓF+3=ä$FÚ¤!:©C«Á·º7æ@ÃÔÕDLÀJPºR~2\ú¹Ö¨áç+dÐ.(ÔÃùýjÕõÇÙhÍúd¼]´v·]>dï%9»vÞÝILçºß«´¸Î¬±<IbMø|À¶¥@ÒR©*b-}¼Ýú$¿ÈW°áÚº="ÞZÐñdn©í?ÆÀ,
+|}ݬ¹4®6ûÐU¯Î*Á¾ÒÖ¢¾k<EidbO×_9ëÆþDífµ¾f9Ù|ÿظ£;qKÍöè\êB§Æ{ÉèB§:?jºÖEú|ìÀZ*èlö±c,ªÔ1VÌûoÕ1ƯÌ{h*~xìnÍÊwd]õæ°Þ÷Õî²[øcO]sròÖS|¾+
+É03¾:"0õÚågÖg&"æàڳƼ㼨¾Ö66á뤯T(\ì*³îÓßK©ËV¼êYß\Ö~îmÆL ÌD.ÛaÊyýàEÂÎìÞ},dì¥,zÜÙ¾Ð=ÍclR$Å°ç´ÍAd]Â>¤ÀÒv¾xG°hÄ#z(%'þ®yÔ
+{>¢éñ[3Û(ÿ1úÃë/(Á çк®AM ZðÔ"9vïË|p\O-^§×ÍÏÔófã¡%,´8ùÑÜ«GªúæªèéÄxÍC$F=¿$:»îS§©MÕ£$¥êá?ðPÌ>tÓ×=
+PW
wìr§Å{<WºRÙOµË}¥=!×}bâ×zÚË
+ÄÚ·U¸´8ÈÔ!g¨Æ}ÌÓµ¾£ÿç"íÑ·<hù 8À¸(%ÑÍ^¼Èã̲·=DÃúä¦ÓÊ(}ΫDÿ}§×c |oöwõ2óÃ!Ó¹yº}w£ûX@ÿmå¹T%ò·tò%²Ég=DD=Á±6;à,96ö¬'Húð+²¦³ãkÙGÜíµN³5\Q¾ùv±_áFéb¯£÷º|9ßû762Jß®÷ëùQã{&TõÞïûmôë6JH¿8ÃnxÐAú>wLÎþ{
+ߨn¯_²{%øå ¹á.KÝ-ïà«~Slõ_ê×¾0F¾6úëóièoÇÄV°Ñ,×û4%Ûý(]S·9»(f~}ûGÿÒ3endstream
endobj
40 0 obj
-4823
+4829
endobj
44 0 obj
<</Length 45 0 R/Filter /FlateDecode>>
stream
-xÅÙn·µÏBþÂEtßñp']ôÁÄMâUA"°%[v%Ë«d»Eÿ½çCCGòU"ú\®gßæÍjÄjÄÿ¦¿/w®?t«£w;º«ïÊèxçÍþ1N³W·öaô«0+^í?ÛÓ|¡ÔàWntøËw_®7z~tn÷qº`ôîizDЧ}KÐ º;¼ è{~C^µûÆá͵F´."P
-¹{x@À3¾)üîtýÓÞ"öû/»epRV½à«2Ýô=
-¯¥¡Ã?÷ÿ±#ÇÁûQ¬6B®ö{óö*:ÛµÅûMÞ'è}~OÐ;å2»_·;Äáþl.ïjÜ%à¯4|X°µGÀ1?ýþ¨,Úoqѯü-¸ÇÏ,ÃoÊN÷;ÅE?t¿]ý²&èßÊ©uý¡0\¨6vµbð*øD½ké5i£@ÔC6§¥(èÚ}¿£V6ÎMP 2R
-VI¤·R~ÐÚ°Ï×Ò&ÀÎÖbÐÞº,a÷°-¸÷ÇõFÂÞ
-ø7âup"¡©Ö=¡'.«;N¬ÎTÌÓ.ShµB2pÜw}ÞªVñv;ø´Çê¯
-/±õ$øï*ÅAãua±Ùª®H!=î\fã ª½(ìªJRêÑrÎ;AFС¥;lÏá¯e¦ÕÞí>C \HI<SúUÒñAæ "bÚ擄OäCi½+pOøRitÆNohËI0¼íKÜSv¥òìÊÇxÈj0l&ÛþxÈp÷|£KçÙ°ªF+Ù1´7¿§Ô'Qí´àú¨l³
|2zد7=ãÌ[ ïjYôTç2óª+P#À¤ iè`Ú¼lýÉctÒð®µ+m%bA©Õþ÷;ûýyW
-À±C/aÂèr#øSJk
-ܨáà$¸È8XëaܸÁ©ÂâE@ã
-)"Z-xÑ
-þcd£¥ËDG(ëã8ÞÊApÍë5èP㣲µÇ
Mº¿Ü·´ý$ÇÂ(3i_ÔÁIfÄNfê)b2è\$è¾¾ëMa¸v6<ZðE45Dv(¨-©¡ª=ÙIoèQg-vZáÊ$FؾZ¨ÉFa%7öô3:°í¸ÄHkð|ÆD·]@{&=ãØ7cTë®Âx/',X@Í ~>lÀÄʶQ:³2²(^D¥CNÂò]9»¢I´7jÙÁ;6Ø:ñòtI°ýÌ$ßѪd]ÍIÌÉ8:?ÕÍ'¶ÂH>ä²ûM÷g$yU#UMGÚ|á5²or Ë^`ì¥aû7Í</Î0mVX+óS°¿]qFW[ºW¶G41Îgä¢]_³
$02³)1>33[M·*{¸?Ôä²ÁÇP@¹ß®G³NbØ©$oÿç&Z?(²M÷ëÓIü+MÃv' ZʸìƯRtROJ½^~¹+òi
-æ¬ê¥½å×¼Ú*ãé¢k«T$Lr&sÕ}QÝÈ;¹ªq-§6 O¸Òi£¿éù£04ÇäSï¯7êS.h\zÇÆÉY².ÆY-ïL¹
-
-áà{nÑ»Eñ®ü>Òcb`´YÙöLãÃéòr÷Q¡Ç=' dSòÌá÷w}4%Ê>ëhqAÜ/¹¦$`£Y2k¢ÿB1ûÓJFòÉX:þó
-ÄÁHÝÏIp¢¤º*~ff5NèAc
-;סÉÍ:¶t¨ZpûÐÅ``3P_é¨Î"Ùíô-¢jñÀ¢x_/ß_91ó*ðgß
-y5RLÉƸ|ÐdãRù06|8ÁºÂ)WWûNyntþdPûS ¡Õ±9WÛ)¼@]÷jí~¿®§æ0²ÕÁM©»_$å=¥ä¾LþB#üûıD÷lßíÛVëi.«ì±*àAwßþfh8ÌvÀe©¢l
Ç%ÿ¾û»½2àû²héyÑq[«¾êU>v1ð²lõg@¼¢'£Îs_Åõäx^®òSöjw*2ͪoÛFèÇïz·;÷°F,r%Y¯·<ù ÿN»»"ÿÍY¦ûæ>ÖÜáþlĶÿª§eúóc×
*4u¸æ_aÙàxEç#ÿ>ÎxAAÐkSìçlYä«1±`À+ðg|SøÝéú§½Eì÷wË*6ḬzÁWe »é{^KC7îÿ}G÷£Xm\í5ö)çíU"t¶k÷½OÐûý wÊev¿nwÃýÙ\Þ+Ô¸KÀÑðaÁÖÄü@@öû£²h¿ÆEÿâoÉÀ=~f~Svº×Ü).ú¾{üí²èç5AÿRHë
áBµ±«WÁ'êÝ\K7¨q4H¢æ²É88-D90@ïÔî;ø}µ²q&lâj°J"½òÖM|¾f0^p¶öfÔe±»øj-ü Ô #+hÏaWñ°O{¬þªð[Oÿ®R4^»úèºÒÃ(àþÀeF9N¢ÚÂn ªÄ ¥-ç¼cd=háñÉÊY
+¹ÃèÀö¬ùXfZíÝî3ÊÁÄ3¥¯X%0`n+¯¶@VÈÑR¡"ÈËÏ×ÀJ kÀ>ÖZTY#l¢¤GÑ4ûrÌ;¸q£XÞAðØ@¡´ÎåxÜÉ(:;ä6ýÛ',IÂBûGEâ
+#ÚU°ì%¯ÒýDg4|BÃ
+î¥Gc]ôô @EÿãGÎM`Ô^ÜA/ZaÁ?ð`l´t(ðeý`Ç[y"®B+#ùd,ÿùâÁÏ`¤îç$¸GQÒ]?3³§JtÈ 1EC
ëÐäæD
+[N:T-¸}èb0°Ì¨¯tTgìvz-±h)´Ò íÿ)ÜyÇÐTÔÀ^Â."BDØʸiôÜ&Ñg·Öà4À·ÒâªÉSÀðXeTXè³H¼&¸{s@ò+¥ã[M©;Óöÿ´ö@iéÅäjOáxW±Ð¤yà&PF3TÌE¸HLÌä ¥ûyÆ$çD¥HÂ@`ÅÀÊfÁ²ÖXl/R\,º¹~Ú½çÏ!³)Õ×YîTÏ£®eï¼Ìíé-0µX¢jôxô`-÷ñ^HãÚ²àóÛY®²ÉXæåK¹e eÎ6ñb¡
(M
->çËG¾Q¡@*xÏaóÚõHU!5ñ·XÍf©ü¾{}ÀÓQYiÎ(¡RRbB;VùÒJ©iÀ1æî}ÏÂ2péIGë5£ö8
-Õ¦ªÓÝÎnÀÞ%0Ïôm¬ÑÊð´;Z!
-ÇzÓ¦ÓYNWDkb}ɤè¹$Â`º-%¨»ávNµxkHüù©*0%Üd?{¹ÙBüIǺóN=a#xxu²"eýO'ÆQ®kc»ùÚµö>!¯9eË´óK¾ z c;ÈÏêé2§RgÂüµÔZ Çy¬é³éå/fE³GØ,FlØ3«Mv<ñ°{[:4¿,xu9çìO %`m«{7.Y_PfP£2QOõì`®H)ð`´wy¨ÝõmÖ/|oÃÒê.¯mçyûÛãnvHÐo[£Íæö»Ò WJS\eÔsìrÎøìÆZ=ï5Ö6ç·Á{÷ü¯
-&+¬^0±¦k¢úî²áº-ÚúÌå½#SsE·Ä,Qnéж#fb;\7Ï&£<=.¥ÖW
¨ _VfÝ´%énµxÕxôBèFóRõ¬2acÒ\ÕÓ £Noj¥0ÿ¯2 jWå]¬JùCùI9&ÒTMjc<álñ<:åFÍÂ¥Ï"7urLv+ÓB½ÚO¡h7êËP{Kϳ/6#ÝYCæ2'UÜ¡]-«±Ïû?¨õøCwnÿvð×íiMëñ³B="àY²S«lVÖ/øIêÖåí;âû:U<å¢Þ{ÒíýVůüûB¡I¹0Hs©6Û"ó
-°äÚäuWWYâ#¹b½aa©
:}tR Õë«Îa·Shtu$Ü5Z÷¯ô;õVnCYu«¥8Ä`ÇHrjJ7MÕ tZüx)aÊîyÏ«+÷г@÷¬i êyt¶Ñ1+õÁS¤@½¯ë4]o$ÓV
- U-ñ,ãnö;!«5`ZlO6=¸[»¸3²f^E÷®ÿASÿwi.ûòè6
-÷*å¹·¿ó`çÍJÉAǯóà7¿O0¿ÀÏø÷àåÎ;;×ïü°zÿöìéÎõVbçú·ø¿[÷¿?wn¯þ´³wgõ`˵°]*<ø }
-ÈìÐJù?l
-UþØÎÃý2aùlòñÀØ^?Qå>=e¯¥`½}¡ÍSØJN夻eÆ)/·L,suGÎL§ür;gzåg*0÷9&9·ãèI¯jf0ÒÇ6ÌZ²iÔJ01þÝ»åèç;»%ôïÕuÈQCsf2#·_J3E¾¾H3],¸ÊÎÁUò÷\
--¬a)íQÀ²Í÷ÈìcýôS¬³P
-S7FðfYz,JB¿n> ÛOn½oéçv?C[h8í§ÇbÁ¿Zº´×áú|*wEÑá?Îý÷Ã0Ý|Ï:õ ÝÿLiÏ;W®ÓÜëùEk¥Ø{ÀÁþï
ìZDp³/¯s[Áå±RíCÝ_ ô0kýÏ÷j1T,ÞÐ4ëÖç\¹Ý*²]_¯^p¹äÈw¾cÊzTMM
-ÏQf)`Îeå«ÜÒmhÛpÝâ7³ð¥Åg Ì_¬@xi¥â®eüø¥4VÍ,÷,4W²ùFKxäïmÑaÍíGRÌsX¦½¤Ë>ÛEðSsû¥Lr7&²®ÙýW©_V2_ÂAÉÕ¿°(Âlò¥Y°ó?7;endstream
+±h)´Ò íïnżch*j`/aN!"leÜ4znè³[ë
+pLMà[iqÕä)`x¬2*,ôYÈ$^ܽ9 EùÒñ¦Ô
+ÌéaÛZ{ ´ôbrµ§p¼«XhÒC<p(£*æ"\$&fòÒý¼?cs"ÏRB$a °b`å
+³`Yk,6EÁ).Ý\?íÞógÙÅÀêë¬NwHªçQW2w^æötZ,Q5Mz<z°Äûx/$qMmBYðùí,WÙd¬óò¥Ü²VaPÁ2gx±PÂÏB&óå#ߨP ¼ç°ymÌz¤ªø[¬f³T~ß½>à騬4gP)©1¡«|i¥T4às÷¾Îga¸ô¸£õQD
+{
jGSUÇénCCg7`ïgú6VhexÚ
c½iÓé,§« K¢5±¾dRtÊÎ\a0ÝÔÝp;§Z¼5$þüT
+n²½ÜìH!þ¤c]ÊÈy+Q}wYpÝm}æòÞ©¹¢[Jb(·thÛÍ3±ÇG®gQRë+ÄÎBTÐ/+³îÚtA·Z ¼j<úO¡It£y©zV°1i®jÅÊiÑE§7µRß«fÚUy«R~D&áP~R4UÚO8c<ÎÇb¹Ñg³p©À³ÈMÝÊ´P¯öS(Ú¢ú2ÔÞÒ3ÅìË+g²{ÞóêÊ=ô,ÐÆ=ë@ZzmôãeÌJ}Fð)Pïë:Í@çÛ'ÉÅ´Uh@$>(Þò_¥+Û3CɱQr·ÁÂ8ååie®îÈá_ÎsçL¯üLæ>Ç$gó¶b=éµUÍFúØYK0A &Æ¿zסÝà|§â°uסÄâþµºyò jHc.ÓLfäÖñKi¦È×i¦W9Ðù ¸Jþº«áñ H,(MËtÐ#zÊíÏP§ÆßäÒê «K³rñté[ý wÚÆá·WPGg$uTÄfRW5¬#¥=
+X¶ù}cÌ¢~ucbÐJaêÆÞ,1KEIè×ÍçsûÉ÷-}âÜîghm§ýôX,ØãWKö:|XOå®(ú3üǹqór¦ïY§³ûß)máyçÊu{=¿Hp4{Sc8Øÿ»½cSnöåun+¸¼Õ1Vª}¨ûfÿÙâ^-Å;ºfÝú+·[EV±KbáëÕ.ùÎw,SYSª©Iá9Ê,̹¬|»Sz°
+m endobj
45 0 obj
-4200
+4201
endobj
49 0 obj
<</Length 50 0 R/Filter /FlateDecode>>
stream
xí\[o\·~
-ô/,ú´dÞ/)úЦÄéÅV¸¶ä ÙR"Kµûë;ÃÃËgWk;i~ðñr8óqnõ+ÎÄãô÷ÉË£/¸Õóë£H]Õ7åéüèÇ#þÁÓèÕ_áéW+^?;i¼PùãI ¿<úa}o³Õ,xîÜú6>º`ôúi¥¾ªÔ×úm¥>%ÔçÃyG+Õzý²ÿ,>¦É:,>âdÁ3äúOy(;Oû]ÄyÿS©ídéñ¼eûx óÞ«ÔPé޺ϾÝ8Æ]°®½rQ
-øNMÄGëòÕ£M%ÿ±<þûøë#!WÇ÷?û!) ªe¦ú¼Rö2T"(ÓJ=Ô]è
-·´[߯ij²Ïë~$Ç3íÙÕå«7ø¶|ô9
-g&¸Åï¢ Êã58v'jðñ~{V©×ñóɦ%2î¨<Ü ^¾x LcT4óÕV
-æUð«ãS0(Ç-gÚ£ÌúÅFÀ£áqa¥<Ó
+ô/,ú´dÞ/)úЦAÖéÅV¸¶dËeK,Õî¯ï/C]/iá9<äpæãÜ8ëW+ÎÄãôïÉ£/î»ÕÙõ_ýþ½:qÀ*ýsòbõ§c$ý*°`¥Õ«ã§GÓ×b%b~å¸cè/~X½Ùj<wn}]0zý¤R_VêëJý¶R
+ÇêÙpÞáÑJfµ^¿è?i2Ç¥Î&8Yð̹þÃfk¤dÞÊÎ~qÞÿTêe;Yz|^Dz}<y¿®ÔPé޺ϾÝ8Æ]°®½rQ
+øNMÄëòÕÃM%ÿ¾<þûø¯GB®ïöCR at TËL=õ¬Rô2T"(ÓJ=Ô]è
+·´[ß«Äó²Ïë~$Ç3íÙÕå«7ø¶|ô9
+g&¸Åï¢ Êã58v'jðñ^{^©×ñóɦ%2î¨<Ü ^¾¸/LcT4óÕV
+æUð«ãS0(Ç-gÚ£ÌúÙFÀ£áqa¥<Ó
!
-&¥æ%"ädWrÆã&ãZY *¦µu~tUfÂ3j-÷ORWiîÈgÎÖæ|¹Å¥3(\_Ô×ÓA*%
O,¹5`r*qÚ»4Q
-¡[ÿ®pL¸K,áô¸w¦2ïSzE
CXL+ÎE.ENßéãÔ ,æ÷ïBh3×~DÖ?Å¡'î6ï`gj2:´2CôH-ä£/QfhypVÁêÂ3á½ÈÂ8¼¸ÞVÍ vvLÐAãÞ¤bÖë«Ðnáì0îmüxÌ.Æp"¸lgGÉì=*ÈülXÍëïðIp©×ÿÜl-ÀJHµþCdÖçU´u¥ûaüZXSX߯6Ò0ã´ º¤¨µak6b>q °R
-¤ß×û3§Ô oq¢¸]§Ö'sõ#rRÈûÑôuC,Ó&»ÕC-U×'unâ¨+ø¼¤0ºoÈhÄ¡GpDVLW=}þÛ¸·:ýM ·²mb ²Yý¶)³-'8EÉA:dÄ'JPg½êÀ5Áæo*Ϭ÷ó¸åÃøqÈ*ä)ÃHÉ8Ö7¹PLÂÖÕ2ÚíûiÓtì!N¤¡O
-1ü#'Ù[4ü1jbwÀ ï;8W_Dk&¨±&Óâ¢0Ú2yyLí¸ôÜ-¥<ÍYôÃe¯IfóvãÁJîwoÛ&ëÔ\fÛø³që7}äÖDndáW%Bi¼RÐÊó-q¿5ÅèSý>£JÇÂ[z«¿>%HW*OX>ÇuàüB<Ó{¶8ýyã ÓIÜ}lFÄÅ §ÔΪµji¾Üiëó5Y}Ì÷æ¤x6Ì`íX7ô2r*ÎÚ²ëd-ê+>ZZ>£©Øãþ´÷u.<@©4</.§r¤yÕpó¤·}1Û8v°wHTêÃúø×Ê)ÞÌØÁy/"9é}n;Î.$íxô*RÄU§£}ê¼gûÔÙ¾úødD§Y¶z*ÕÏÂÜ"ñî:Á&cìbįÓÞd¬g3<p~6÷§¡J^§Õ¦-x÷AÐ÷§Ó
-³4ô}¬by§¬¡
§iM'´¹Ûò-B«ü¬o_Ç]8ñèFðþ\
-|èX¬ªRWuÖ?Qç«¢c4"ĺqÇ@Y»úf3\2ßÃ:Ý]$ppxÂÕKz'ík6lï~CuÖ\z3ËÁö1¤+Nñ8ñÀv´(NWçû¤òj±S*l1aèd¤ÒpZqÒtDHª¿VûÒ2ø¤¿ÇAä÷ûv"¬j&d'η%¾5Øo½GBjÞØÄOjµ\f½Êîë|Ñ}^ø9Î)Î^¾Ú{^\q÷~bÏÜAó±×±ëËÉMsû5>èØ¥f|éZpQ®º^ú^åËåw9ßx¦
ÞÙ âØ5£ ÄÓåXï¸bG(µ©VÒ;·Ïè½@ÀIRãÜw-6SH]S8vpCz©É'½U5Bi;7ÃpÔÅ·Å)|$w ¼ã½éD¾PVûm«WkO
-Ó:Ò
¬Îå¶M×Ã6ÿ¤A/_¥8Ý}ÿݵÐ1Ø1| ÷`
-rÔ:
-ñýf9Äúø¬ô-l0r´¶;Øjßíg)D
-íÕ¼yõ^$¡¸éNJò69ô·8¦F,ÑÅÔG.ÄÔ) :ÀªNÓbÝ´'cËÍý¯}ªë(¿è$Í};ÑýO6Y£4
-L¿© ÌÂY|:§»S3ÆÌõI¼ªôÞ$~ñ@u%ÄÓ"ñ BXû}ܨ˹¼ô3¢Ô¿Ç{±§rÅ°áP;)lÏ~ÏÖ7ÉïÙD_lWbÓ òÃö%êúËO^û¶òGCNZÖHÜ4ô$nwå=¯kòB9}
-û ù°àã8PhüªnþJ}=ì]øLj;ø Y©l'Ø XWû¸±(ëX ÅûbñÝìâÃw_ú
-y¯½ÃÉXrÏ[Ã%(RÎR½EÛ$ÖÄ=ë9²nÝ»ª»H_q³Ùg±ð¢« +ʶ`a g
-|)7¬-¦mcaãr1tÅÕ-®-]DÉh9ó±QÄû£O @krRæú{Ô¥@´«c¤÷QtÜÍ#4]µyµnÇ"uOhÇ
-è»FúîTiLHSõ ªïTâû~ßÐ8òÈy£Ï!ÜIûÓ&,?[wP#æÊà$~.3|7wA¬p®R·WD
-VùEyÈA½pXrÕÎÌ
-Óí¹È_¨EÙÿ¡ïÜÉýüJ½»2
-49xD%ÿÍÂ1÷þþæS²!endstream
+&¥æ%"ädWrÆã&ãZY *¦µu~tUfÂ3j-÷ORWiîÈçÎÖæ|±Å¥3(\_Ô×ÓA*%
O,¹5`r*qÚ»4Q
+¡[ÿ¦pL¸K,áô¸w¦2ïSzE
CXL+ÎE.ENßéãÔ ,æ÷ïBh3×~DÖ?Å¡'î6ï`gj2:´2CôH+ä£/QfhypVÁêÂ3á½ÈÂ8¼¸ÞVÍ vvLÐAãÞ¤bÖë«Ðnáì0îmücxÌ.Æp"¸lgGÉì>,Èü6,Áæ
õwø$¸Ôën¶`%¤Zÿ.Á 2DëYm]©âþ¢@¿ÄÖ7eàË4Ì8-.)êamØ
+Od£!¢`\G¬)å÷uÅþLå)5Æ[(nשõIÅ\ýò~4}ÝË´ÉnµÁÀ.X^qs3o©³(¡Ä/MÐ;¿ëJ%øQ?C?àïÃä´8¿'t
âä×j
+WbóýØÐ@L¦ý#èY¥ÞTêU¥~·#Mþ¿<^}GçÚ-¢ÏS
+^,H«¸É±8©ó3nºËw}ç9@ðhϬÑÄõ=yÎt<8&[+0z`çÏ©3yjPI*08+ÔÎßnÐã$ÄÉ^x1]K0'¨o´Jɼ¦ôp^VÛPÏi=ü+O<úùä2ÙtvIZóê;C\ù¢rëÉûªèäJhðù¨ãÕE>n«#°K{í£cd+GöÄ«Ù>@¡êú¤ÎMu?qÔF÷
+-øSô(ÎèÀéê ¡Ï»#÷V§¿)áVv°MDÖ!«ß0e¶å§(9A,ñ¤Rê¬W8R°F"ØüMåáÞÁaW£ül1Î+7ÆÆû¤À÷kYS>Õï3ªt,¼¥'±ú+àSt¥òåç¸_gzϧÞ8ÈtwQ q1È£¦Ã)µ§³j³2Í;m}s¾&«ù¾ÂÔÏ¡ ëÞBFîQÅy@[v²E}ÅGKKÃçô {|Óö¾Î
(çÅåT43¯n÷6 /ÆÓq;cÇö òýJ}Pÿ\y ÅÃ;8ïÅP$'½ÏcÇÒåPÄÒ°BåAx¿êt´¯@÷l_:ÛWlèá4ËV¯U¥úYx[$Þ]'XÀdc]ìøuÚlõtÇnÒÂOóþ8TÉë´Ú+É^-¤ ?áäNöãäçWEç*5|BÈOiI~~nçà&[oÞ7yqp n2Ü>nRT¶yTv5DÈ/Ïí
©iò
û2XîWÿ 7ÿSCÚ[Þô¼Ý=kW¥Øª?j
udm%fóJeV+»Dº·Ì8aÄ"¶ë/£2v`mY@,%~»Q)ày¬«¤G1Hf´Ô¤¹6´ô^»Dä:?Ïx7ÈIaï>úþtºa¾U,ï5ô±ð4é6w[@c%ÚLì6äDzR)^ôh¬|ªÕV8rCXè¥&±{[µ7×IͶ¶Êf=Þ(Ã"){ßÌ©¼aJ*ªðúøx*XR9¼þ_cw°±h³[pT|eñ×ÈYïífÆÇ¢ù¸äyÙî¨Î¹-2lêÂãn¤©@âzi]ÚÚzû¬
+A(¡å+wn¤IxE룵ڸX Ën¥i/l'è¥ÖÛJá]&¹z=i=Øåf$°&ºK3Τo_UUyS{îdoW+V¦.Nø¬{¯2&õâéòÝ0<õ!æLéCÂÚU¸þàxâÜÑ*q%NÖJCåªø¢|Ç$^à.wÑÄ«©{)±YºRÿ
+ìK Å*?ëÂÁÛ×qNF<º¼?bG:«*£TàUµæOÔù²è
+§±nÜ1PkÖÃÙÌ÷°Nw põ£Þaû
+Û»ßP7ÞÌr°=béS<N<°-ÓÕù>©¼`lÁ
+aL:)
tV4ÝQ+ê¯Õ¾´>éïqùýÅ>¢«§ Ùóm¯'C
+vÁGïЦ7öñZ-Y¯²û:_t?¯JüçLOg/ß
+í¿½G¯®¸{?±gî ùØëØõå䦹ýtìR3¾ÏÎtM-¸(×CC]/}M/óåò»Ëo<ÓBïìqìÑâér¬w\±#Ú
ÔD«éÅÛgôÞ à$©qî;F+¨°ªà´X7íÉØe£År3dÿkê:Ê/:$ GsßNtÿMÖÀ(MÓoGê!³pÓÎÝ©ÇcæúÉ$^UzoH¿¸¯ºFâix!L¬ý¶ endobj
50 0 obj
-3580
+3581
endobj
54 0 obj
<</Length 55 0 R/Filter /FlateDecode>>
stream
-xí\mU·îçU~Ä_zo=¿û´êBHBK VU[U»Ø%Àòªÿ½3ö±=¶çÜ»»ì¦T!ñõëxf<óÌøü´±ñÏüïã{7¿s«'¯÷BíJ¬þK?îý´'æÿsëÕgÐAÕ4LVZ½:øaOÌí
öõ+7ºAÂ/öþº>ØìöÂ(³~ºP4£^oöbRvý~³/9
-¯Ö}3x?MjýbÒ*½~û?ßÓÒîJùA+·~
-ÅaÔÊ®ßlÔ µtÇ»õ«PÒ(ò3v10¼ó¤÷³0¸çv&UâÏËÏqôIÃ+LdøSÞBÑ9#¤ôD·FÖ9F=ÞFÒ¨ÑæÍåî¸
-=ù=íþ¾t'k»Sã(æéqMdw¸<çôú]<ÅQ 2=Ý´/üúq´ßɨ<Ìÿ÷?ìIà#à8ÔóâäM=ÄBÑåßðw78£Óü8Ó)×tyR£ÃUìKo8¡Õ¾q!Ð9µ#*:TëÔF-=è&Hµ§
I¹ÌêhH)ë«
N*-$_4pR-^RÕB5PN§(Âzó}5ÕIÖgG úb·/u§Ôô¬½öd#Í`¤½]àN£é<ϹO#Z_dýâeÍWgíø"f4ü~
-Rî@¥&<tÃj¹~ÆíLyéÀ¶ó*±§¬</1+ÙCQ£SI
-ßϵ%@D Ôç¼ùôÔ@Á½É¯îíüæ¯ëû ¨ÃäqÇ¡ò¢øεïJíi©}Uj¢·°¨?~°ÉÊõïaëR8÷~iÐë¨nÜPlÆÛ~QjKñ1;.?ØRº°Û«#lEC¬ô¨ß°Û¹:év©ÓÒS§KåßÖ¹×
-:Uª|ÏRàEêe©|%Ëä;$¬OÅSÔ 7B ×ûß6¥ÁïrÙÊ#ÕºüËC޷ĵ÷Ù¶G5u5Á|Ñ)BQÌüè
-åÇ«äÇ
¶Ç¥ía7[³
-køI;º÷ È&_rùǯr¯j(B°ÔI4#
N¢1-ÍpzoËöXù!'K\êVNýf(·WqÙz
-¿FdÃZpP°c+_y7òåBQ;Ã-ÒÚ³âj³/»ý¹°Ñ÷»ð| ?
-Òôþ5¡o¯-x±d.Ìú'¬rÛ$`C åéîRæxⶳæJGÒ³WÄosï¥À3Ôp§x`o[÷ËBí׬Ç{ÂÖ>aÇ}5{f!y8)©íË0rO¹.ü_÷Åxlëöu§TdH¾ì»OÖMpÑ- @fåXºhk°XÁ:x¼ÀãM_ì:[Ùÿ'Ôû÷ùM©|+ xFeBP'\§ã¤ç0ør
-hlåä !4¾_Ú|^d¶ßÚl·â9qì-æ³@Ôz÷JåÝx´ xCf_Ñe¥Nסa4påív7¹8³*Lsovë׿Iŵ-¿çÞÌÂ
M+Í^";?(´o#T.®þ(o)FY--6è/ÔuwlÓ:¡A'{y9TÁ2S«`}aC¢²S!¸Am Jm]ªkõ¾³Ì®
-äÈÎ\¦êeIqé{÷ý¹È`½y× 1¦+(Ïù Þ@õ|G =(~Wb
->û:´î׸L~@CÕAÆhEã}×¹Õ)Á å:W\×Y%ì£Ö¤3ãU7Ñ+³ßy¬C¡±C?Uå<ÕÅí$Ë~y7>¼¡ÂÃe<¡.øÞ¸'ÍïÍ;§'ÈÓO
-½Á _¨Rv®-²æhMÞÑÂ3¹°ËCRÛ`@]ÓÉ3lÌðÁWkò?ÄL*±ò&CV/Ç)ïo?t-f¬Júâ§Ey\·ô1¤iY«1k;åz¢aJRÛòL]äd6UM¹Âìzï7GPâ~ê
-ѳóM¾V¾áÄ$åå¼VÖ0!§4^2¨<´wm¨%CH±4å6{[жÉ`!Á²ûÕmxç`ïÛ½VJ:|ä-Äà¾Ó7ýyGzSë/sÛåýMjF¿þýMäÎÑý}ÇîïÛ²¿ïÙ»ó]ÀÁÆWsëãMHð>@J 5Ö5ÚyÔ°Gõ«øQì÷î¬9?OÑ_Äè`S¤qTºmG Á(r)2°¯,õÁv%A¢6ÄßðÀefxû½(Áß±Á(%ßÌó1ÙnÃâ&é¢V_3ªÃùE;ò·JÛ+V_=Hnç9ÔÂ#AÌýtâ¢n
_×$Þüþ¬°¸¯kßÕ`MØÝû»]öw=¾?uÝjUÎi2^ d¼[Íq¢Ê$ÚZ,h)¸ûã¿rÑ"Ø_LÉZd2³Fãn Qb9Tª.ÒAHÆè<g#h1Ƕ~#ÚIQ¡h|Íå£Â¸BÉFuu+cvZ¿vaUÉQ±vs }{ÑX½`ÊhǺx^ÀÿfÝv9ÓLJÝæܵʾMp¥³² ¡ö/çØ F~(tUαÛlí6-l×b``¬Ü4X/ÐÄÁ®Õ-©BëQT9ÀÄÛær°³ç±ç⤲ñXñ¶K«0ú+/íÁ;5 9Èêú:ò«Ã¼©*Ã<ôz1Ù*l\z"uY½5ã<NÍ/iFîµWü@ìôÁâ°lHìæ
-
-,ðÍâG<iUQwǧM`.õ¸ÚdÖÝOdî¿Óùã7Æ-سI; ¨Âê«0Æ{ÓÖãÏÃU-Î2K>«ò¿,ÇdaÌÛN¤ñC%ÒDÒÛN¤±SbÛR(æ
ü}yö½PÂóÕÿ³ïu²oç*Jãâ ´GTú¤ÊýÖ°á»J!¿Re´³ò¼
Môò
bÛ¡¡¦!K_H½|P5å»ÖÑnúWç³Åç$ØE«;å,&Ï¿ (o>{ïYÇ¥Ë ÷Ý×eçèZú`ÿ(y|C:¡òáêGS¡Nc5
6ùM¾wļMÀöeø|-ÿ³æǧëW:)uÕW
%W@".qV¹¾Ýû75ßendstream
+xí\[o7Þg!?âÀ/{Î j7ïìöÁqÄ3ãÄÊ3l)¶KrlË,ö¿oÙ$dõ9,Íöi³y-V«¾*ö¯«q«ÿÌ¿OOönÿèVÏÞì«oàï³½_÷D¨°¬¾<J¬¦a²ÒêÕÁÏ{±µX íëWnt'{YlöÇA{aY?ßx4£^oöÅ ¥íúÃf_r^7ûfð~Ôúd
Ò*½~Û¿ÜÓÒæJùA+·~è]¿Ý¨Ak7éÔwë×áIJ£Èklb {çIë¡s=91íL*Ä_×±÷IÝ+L¤û3ìÞBÑ1#¤ôDFæ9F=ÞEÒ¨ÑåÅçæ¸=ù=mþ¡4's«Sã(æáqNdu8=çôú}ÜÅQ 2<]Ô/üúi´_ɨ<ÿ?Ø;6À5GÀ%±«Æ'Ä?Íz+¢Ó/ïð½Ñi|髺Û<©Ñá,ö¥7ìÐj_È8ÏèÏÛͪyj#¥påûTUa·ÂK©úÝÆÏ6Âbª6ë#´
yéÉ(ÐáÓÖM
+mdfd\ö°Ù÷
+aÖwpt=h+5Z2}BkëaRTÊÙ9u¤"CâWd¯yÿ»ÿT5Ùík÷'¼îög=ý®ÝÖ^ôÁÛH9&5 ºTC6@z'Øô³f ᬪÔNì´Z»Ã¡fÜaïEÚb ÒÇÀØ°Þ!cL9¼/ÌwqÚ4©ÐÓ(n2XzVxyˬN²N°Zè´ÒBùeA'Õâ%U-¤QewÂ!¬7¯ØWCf}v¢/'Ap*¨ñJgJÍxA/ÁÜaO7ÒÆiAÚÐÓÎq0ó{}9ÔBÿ"sèÏ8u/k¸:kÇHÑòþ9¤ÜK1L¸+éÕrý$WËòÔmçYâS:²ò<Ŭd±G1NQ&-4 RLÞ7jK©9yûGé©k1_<Ø;øÝ_ÖAPÉã:Ã#Èâ;¾/¥g¥ôu)ý½
eEÝøéMxT®ÿ.åàs
+]¿ÎÚêú
+M¿sݯKéay|ÊöËwö±<]ØìõÆÁN¶"Ã!zÔoÙå<tK»Ôhi©Ñ/¥ð¯ëÜê*~`)p»zU
+_FÉò ùÎ%É"óSq5áÐ^Âñþ×M©ðû\a¶2bOµ.Äòá-ñCéC¶îQM]dÍ`0_vð(f~ôòãéuòcÏBMÝãR÷°Ã£5ü¤ÂÌdÀ/ùùǯs«ª+B°ÔH4=
F£1+h·4lÙ®®$Ç;·T´[J«-ÝÑ»¼YÄÓìòRJÉvgBé«^¾-¥/Ø
+*Oó.>æ´3ø»\øUÇy?:ÂtV©ÕÅÇ¿Uí!n½ëc§4vë=Û,m2¶k6Y¦ý½))}ÏÖ%úø.«æËþþLûJ¤|V
+Ï9¢W:>íÔ:RjD¡7èG®X$æV£¶=´ºÓ¼0k æ1!ò½É¥=3ÆÇÝtiôÉMèy)=oÕs(ý©<¶·òü2è'Ú×v¡[9 (¨¥''CD¨]Õ¨ûÙ÷À÷>z»àÎcÂåÞ墯âä-ñ»oD0Ò
+P¦©Á!µtª¦
+Ô ¥¬ïqOÁ!ÁC¸2Ìn¦d*t£#
ÔÿGÊy¹g´ÃXyðHÈg[S3·¾»ê쮯%8nýÙìfÇd²ï%pÌG+.,
'cD°ýÔø©,¶uú)Ic©éèqåÊWèáX?Fåéç´ÔD:zÙ éø¶n°ÈBkò/ìF¨rpG¸(°1BÎ^ûƦ`2 ýFE+Ø1ǯ¼Àùj!¨¡+Ø1C
+÷ö®
qx¿,~Çz¼§lé3¶ß׳g¦çPÓâÒ®³#÷ñ¯ù9~ïQlÇƸfßåxJEäË.Q!¹ûdÞÝd&8)ÛÒm@[úÅ
+ÖÁãM¤oúz×^ÜÉþ?¡ÞǼÎïKáÓ\H@ÄsÚ):å·$½`ÈÁkx8@Pa+§ Oð°¡ñÃRà«òHø[©ûc)}Ì6û[y|4'ð ½Â|(ZïA)¼·sÈì[:Ôè«24®3³Ýî&GV
iÌÂnýúwéqmË{ã<Y8ð°ªÒp¤ÙKBdq
ömÄÊÅÙßå-Å(`2ªú|ÂDijX(»QÍùÚï0º#O:âM&SÄÿO°U+O:÷l>i¢iUòM9%pÐRÖTѬ ©#dJ¥|ãÓÀcݲK£G|¡wþQ¸&ð²¶Ã(¯«\Q3Ø>o24ZÅèÆAÀX6²y¶XKʬUê^Ø6I¥õt¾fLrB+ÈZWû¿N½Ða¬°[b9³¬X±+"¼µÆ#;ÖúM¾õkIà£*·8)P|`bPâl¡}ôóí#«bU»DÑ:1èeW,Ch²Õ92%ÕTKÚLÞÈÃóìj°R±²_ep2¦np`sú¥³*)jG1d
+'ný
+ôÃzÇ-°ÊèǺù¬\ìÀã2ß\}Õ¤M¡ä È º^/¼ÿÏ*USY0Ômµ
+Æ=«Õ¬r¨#£·=HG^8e«ä¨q°§
+0©3'Ò/YH§µH1q×v'*ïתä@rDÎ8|&kÕ
+iHlùýI®£¿²ÖMï¨`ÁüYNÅïè/Ç% ¤`iM|8ÑG¼§m
+ è<m^<YGÑqÒæ"ùèH>]çÎ4öVD[%(+à
R2ïNòB
mZ'Tèd/O*XPFbj¬/lHTvJ17ȱD)í³KCqÞwbVsÙUáÙËTÝ,).}ïÞ°¯ÌÖw
+a±bøâ
+T×wвá½SðôÙÛ¡Ut¿ÆeòJìª2F+Ï»ÎNaD)×¹âºÎ*a¯¶ µ¯º^¼ç±"&v
ÄýTotrðTo´,ëåÝøp
+7ñºà{ã4ïvN
+N«6z)'ÞP¥ì\X1³H¨.é%ÜKjw©±·Ó ;XsczteÍѼ¢
kra=W
+¤¶Áº§kØá·ÖäTb!äMº¬n!:Sîß~ì2[ÌX%%&"õÅOr¹né*cHÓ²4WcÖvÊõ0Dä´å»ÈÉlª(r
Ùußo ÄõÔ¢gç||ÃIÊËy®¬3;aCNi¼bPyhÏÚPJ.Ç.Ò»ìi}@ë&
ËV§á½½ö~])9èðxçWÒà{¿R£øÁ§'{_Þß»}ÿ»ÕÛ×çÇ{·ÿ´{·¿Å¾|t~îµú½{÷W?,~O¡&`ú4p)øø=bpßëæþª#E½¨õ7¹îòú&5£ßüú&rçèú~d×÷CYßOl
ÇÝþ.Dà`aNLÁ«9õé&$x %ëí<êAXË£úUü(6swKVÍ'Áè/âÖIt°)JÒ8ªK¶#`¹MBØWú`» Q
+âoxà²M³E¼}á\àïØ`Ä'ßó)ÙoÃâ&é¢V_3ªÃùE;òwJÝkV_=HnçÔÂ-AÌýt£â¢n
_7$Þüú¬°¸®_Õ`MØÝë»[ÖwݾÿîÕªÓd¼@Èx·&ãDIµ%µXÐSpöÇå¢1D°½µ6Édf6ÇÝ&A¢År¨T¤ÑyÁ GÐclý.F´¢BÑx?ËG
q
êêTÆì´ÔíÂ&ªbíæ.û×¢±xÁ'Ðu1ñ¼#ÿ̺íj¦
+ºÍ¹}=à>Jge?@;)Bé/°<ý=V¢ÐU¹ÀJî²¥Û´X¨°]9±rÓ`]¼@»Vg ¦FFQå+°+4°dÀ7kñ¤UEݯ6¹ÔãjsUz¬´î~$sþÍ¿1nÁ%LÚAT
14®Ø¶î'|®PþA$^êî!ÉÖòj+ê.T×Xï0¹)¿¬¼ß-ßñÂAl9qr%é
+N1ç?iQÆnúH´= öÓþ÷:ÁÿWs@Bkoغ| ¯ItÀ[ëMRDrú güÞÑÛ© endobj
55 0 obj
-4873
+4876
endobj
59 0 obj
<</Length 60 0 R/Filter /FlateDecode>>
stream
-xí\ÝsU·÷t¦ÿÂÍÛ½®¢ïô$d¦MZp§i'¶Ø&þﻫs¤]éèÚæ#08ì9Ò®VûñÛ./VR¨Ä?óß'{_Þ«Çç{ºR«ïëÓÓ½{jþ¿^}½t\%¼övµÿhOÍß+cD\úÉÞÏëóÍÖeë£ü³ëD½ êQ_UõQzNÔW=·üxJügÍ7ôü×é1Èôßý¿ï)½Úÿaoÿ/?¯®cÎuJÔWCñ_·ÔGD=~pB÷ødAÈlX¿ âëLÔJsÏ+ñÕX'?¸Þ¢Â¥°¶ü}}UeåQñgHQè8·\Ä:ÃÑÛ¸-_ÞS®±:+bjµÕR-íÈÀì!v*
-QÚð5ojX>¬ð°MgÑ0p¾K b!çhhäoú(ѧí§C`ÐèIú@t8DñéÈôϪm±ñpêpQ1¹ç_¹aR×mÜjùä×A8È«<(_1Î9ÎJßZã´Ó*òlÌM
-ó³Í¹48WsyÄüD-BR<uýJÒ=èR¬°ç5×·)vʦçT /òò1}ø¦#zõÉ´g+Ö&2" LrL§ºìÈÎ|qNA<¤'c¤×ÍÈõ'÷Ô6r÷
Çu¯ª`×ú×î
-ëQ_sËÝ
ù£AEî0gCm:©
-ÜDÑãÔÆyX62Ì·[ Õ×g,üh$Îhò`ÁÍÛáæ./7î¶Ö"úF}¼ç]äø^Þ/¸¥Ò§°¬Ra;Éõíd3ÓOÅç}#èê"¶©,o¿}9Î`
÷v £Ë¼E^¦I¹P*²0Eëi©¾hmc¥ÏÚ¨íÅ@dIίä.G1ß
éæoÙÚ/z´Üm{Èk·KWë%gÜÆúú.q[Ö¸U
-ö:
þu¡Á®uÀ¨#«u#ê´Í
-/ @AóµÁá!*4 èD(CBÉ$>\êH%
-¤°¬W6
*ºÂÒX«©¾t^8Hf-³B!®Úª0%ÎlBL^kñ÷íÞÄWéÔ.kk¢>*°D+5o±ÓD'-¯Uà\.Íå¿4}ù Óá<LHf[ý¢Ï¸7æå¼2¨iÞ{°µ?A%j ÈmÐÀ˶¸¡i2vt946²£fFÊ
Éôt2XÂT at 5êÆ>@àRK¿S¨i®]Oîm³ %r©=HiüdóÕdÆm\½-BûÐëp··~áZZµÊ½ XsÌ«ëe¡W>DC XçÑÊ&¤Y0ª´c÷GÅ8{}2)EË)¶`èIñ´èa*X)µVгz2ðÂï³å#VÜ¢]&ýEµDÞ
-"0ö j.)«5°S&Èl<ÒÄ:¾5´¶Qï$F
-¼ýlPÏy*ÀývÊðÈ{Ú\9ÙZ
-kxD-,vTÇÕm]Ù)°JRD¡ËT]ôÖ~9ÖDw³òBrd]A²ã
ÞaçÑX±}¿ªÄªÅÃHϱ,§Ð7ÞåªGs®Zy¨ZÝÑb±AB±ï@
-¬vÆÜ2ý
Mº77fE¡Rí4;Ú¶
-¯¶N
-GeF¹ãb
tæ=o`ªÀÃÄ9.&éZ÷6>h1hÙð6ö>å(¤©¯a»o>gú0öÈðHÅañªùf
-xò-ËI¹.i¼îÕêâk<^ï¾&}>U[bk4c¦ø]÷Ãd²èØ*aǵ1¿òiÌÀÞ"Ì.¸°ÜµZ@ä^ôdÝxdÇ·å!Sx¤2-ÿi9¹·ÑîÇÃÚeR¼Ð"%$-K7ÞÌiÃeKWà'ÿ¦
-¨¼Ô¼CÚeð3÷m½ßÔqc-Þ+`ª×¡¾ÄSW¢""ÞÞ¢ëûu;îâ{¨t Q|Cý¨÷qÈ>¤Tº
-WCÅ«wP«Þ¤Â6n·W¶vf?XðͽÃÏUQöò#ÆÆËÇ91}äR:e©²J³£Ìa¹¡×hËQt÷ZïÝâ³TØNxüº¯þÜÝ×|îKR}\÷5H÷%mýß}[÷5»ûÚOÒ}Iªë¾ö龤ê¾ßn/¹,Îè»3ÙÞ×)N=^ëe½:3òok
IɳÞàÒ#»ÉJwGëÁ+A×»ï|9áý½·7ñ×f¼ón÷æùãÛ:/v
-¢¿PF&®©sÝüÛ¼Ô¹.j4ÜÌs3O©wx®»çz<¡ëýîîK{7÷µVêÏÓ}õgî¾þÓs_飺¯[÷wªw÷%}T÷½ß7»vss(ÈÞ[¼`bǧCíñ ZºÆ?=L4×X¾Þ2sÄc¤
©a67+xí\[o·~
+ô/¼Sø0¼_Ò''q¶iØ*ú-ɶ`K²-[þ}g¸KÎË#É8QÜ%g8×o<~±B$þÿ=8Ùûò^X=>ß«¿ÀßÇ{/öTþ`5ÿsp²úz>ÒqDòÚÛÕþ£½i¶Z)cD\ñ½Öç)ÊÖGù1$g×/iôFhô%
+}F£Ç4zN£¯zjùñ>øÏ=oèùÏÓcé¿ûÛSzµÿýÞþ~Z\GmêF_
+Ù~ÝVÑèÙðz¼ÇBÆdÃú
+¾ÎZiÎây|5ê·`ÐIáRX[þ¾>Ê*²òÈFñWHQè8µÅºÂÑjGTË÷k¬Î¥Zmµ0pæöïMB'epEc¢°Ößl´ÞèõÍÖÁ¨P`µË0Z`Èß
+«I(þò%0l¬R¡Z
+,]?Ù(a£vs£½0Ñ ±(¡µÇguy"tD¯O7Ú ç}¶Ne«Í$çkN<%¥ØÁà<Ø2â¤IÚT¡ätdÎÚ"
Ï
+xéî^ÃlÃ9ËÒHj+$Ù{pB`ØÏ(Ò(&5¶ÁcËõèTJ*OÈ>¾.Ê}3\¹4"ßÑgC¾]]Ëêewið ,<`Z´x^R¸¯¬ë,öÁ³:ë¸8È8e~y« I%
+Qw®ØU{¹ß¦ÑhôýFÿJá~
ü¸¿øïVmüæ1³Hë
+þXó/dïï×IûÝ`ô3ßK¼ÃiÖÇïêJw;ò¤É['Ý<0n=ßA°0Ö[ÅbÑiõay8(O¨
2Ρäñ¥ÔSd`Îþ$·;¢HQÂ÷ëÃ:x¶Q¬QõàÀnF¡X0ÄÎ@¥!J¾ðM
+Ûö± càÌÎw D,ä
+üM%ú´ýtè=éCB>þYµ-6N.*&wóüë 7L⺪¶/¼zIÉòãã¬ô5NVgcnRgMlÍ¥Á¹Ë#æwÔ"$ÅS×/ÄÝ.Å Ûx^s}b§ljpMò2,+ÓiÉÙ1¢çYL{¶bm"cù)Ò Â$·Àyù§À.IïÌwáÄCzB9FzÍÈ\rOm#wÏQy\ùª2vÍÁéÞй^Ðèkn¹»0ô"¨Èælè M§ µ(úÀÐcÚ8OËfÑA9ãÖi2¡úú
+ÄpBL,H°u;ÜÜ%à%òFmk-¢oÄ·ÀKyÝEïùý¢X*}
+d
+Ó$+ZÔ¯¤Éf¦)4Ïz#èê"¦T·߾òEg°Â{»ÑeÝÂ/¤\I"õ´_´¶±Òg4ÚíÅ@dIίä.g1Ý
éæoÙÞ/hôh©m{È{·KWë9gÔÆúúnq[ö¸U
+tÂGÿ²`×:`£"«u£Ñ9i^@0æ?/kÃCTÀi@Ð8QI+*ÈÎ R¾d³&è]ômæ ¸ÁÈ`Y×k×è¿g$4Gå
+4'2î¸7xM°àrkF¿wlO.&°
µ» /å2RA³u5 ³Ôè"¡VzÛõ¤<D>åØØ{
+ÕÇ9?ÁrüÔ@Ý,ÙàYñ97ÜQ&@ðñ\ÁSCM_ºRbÅ0j0Vs$¹K
+?µndDt¡oTbα1ÌLr*Ç öSýÞÎƶ£ÀCAcpßD=ns4^+À¹\Ëiúò¦Ãu̶z¢Ï¸7æå¼3¨iÞ{°µ?A
+%j ËmÐÀ˶¹¡i2vt946²£fFÊ
ñôt2XÂT at 5êÆ>@àRK¿SF\»ÜÛfKäR{*7àÒøÉg3ªÉÛ¸z[ö¡-ànoýµ´j
{A±æW×ËB ®&|@±+¤CÙ{õÝt±×'_}15çaE8õLqwᶮ®
+EvÖ
+e7ÛðÄZµá÷Gà<1íÐX³³±qÏ\QÚØÕkÐðÎéØ°l£pÊ2)
+}
ô8
+/T¿Îe6Êt-f+¤¹6Ëòbtp}ªìª=ìý2Én¿?"CçÉYé-®ÐôÑIÏ8eO=T`±£:®ªuQ\d§XÂ*I
.SPuÑ[ûå|XÝÍÊÉuÉzGcÆô~UU%bYN¡o¼ËUæ\µóPµº£Åb0cß?#Xì¹eù?"
+Û%[t [onÌB¥Úiv´m^m:dËrÇÅéÌ{ÞÁ&:T+s\:Mܵïm|Ðbвámì}Ê7QHS3_Ãvß|Îä1a4ìáÃâUse
+xò-ËI¹.i¼îÅââ{<^ï¾&}>U[bk4c&ø]÷Ãd²èØ*aǵ1¿òÍiÌ6ÀÞ"Ì.¨°ÔµZ@¤^ädÝxdÇ·å!x¤2-ÿi;¹·ÑîÇÃÚeR¼Ð"%$-K7ÞÌiÃeKW ÿÓTÞ+êu¨ï%ÑÔuPÑ ÞÞ"ëûUwñ=T:(¾¡þFïã,à;|H®tËî:ÂWï> T½I
*(n·W²v&?XÐͽà ÏUQöò#ÆÆËÇ91}äR:e®²J³£Ìa¹¡×HËÒ èîµÞ»Åg©°%ðø3u_ý¹»¯ù$ݸú¸îk~îKÒú¿û¶îk>w÷µ¤ûW×}íïÒ}IZÕ}¿Ý8!^rYÑwg²¼¯Sz¼×ËzufäßÖ
+g-½Á¥Gvî4öW5¯wßùrÂû{o)=oâ')¯ÍXón÷æõãÛ:/v
+¢¿ SF&®c©sÝüÛ¼Ô¹.J4ÜÌs3M©wx®»çz<¡ëýæîK{7÷µVêÏÓ}õgî¾þÓs_b飺¯[÷wªs÷%}T÷½ß7»vss(ÈÞ[¼`bǧCíñ Z¸Æ?=L4×X¾Þ2sÄc¤
a67 endobj
60 0 obj
3536
@@ -360,234 +349,205 @@
64 0 obj
<</Length 65 0 R/Filter /FlateDecode>>
stream
-xí\I·¾Aò>½gh:Ít¬ØpbEv¤ñɱ4Z ÑHÑê_*v,Õo=Ù`è >®Å¯v?8ñßüÿç7þzÏ=¾ºKÄÑ×ùëÙoùq®}ôù 4Pæ(ÁJ«NÝs}¡ý`ýÝ áç7¾_ýe}lïCPÿ=ù4UCø4Á9h|ò*ß^öÂ(³:_ÓÒÈÕÓüu¶>z´«µ4 «Wëc%ÅàO5hmX=YèÆzu5Ç0¸ÕËõ±ñ"¤¥W«ËXSJ£VÖzÐj´rõbjîýùùZBgìê
-ºv>ÐßI§?ÃPÕhV¯óÈïØÁ0jeçÉ» cÙ¨1&¥ÞD`ñg DxóÃ(äê~nó
-ÎvĪD×QÕ×¥ô>L. ¥$aiçò¯cü
-BÎ
²ÞÄ&ã <Ù¿¡L~&Jµô~Á
Õ5âEZ?Î&|ÌFè´LnÂô Å B©TÖôj4.>½ÖÈK_Ò³RJ©\±²c9ë<-LÆÃBõy-ãè)Â+è&dZ^äÝÚv´)¿?ß
-ü!W
-ò^*5ÿDûL?yntÍO2HYØáÜô ¤ ½@`AzÃ4+lcP>¤õLCųUaïÒûyùù´ôy=ùÁ²ÞsYÈlqN:å P
-²F¨¨ãzßLBdL`ÑURBj5ÀQÞM©¦ ü)È<Îsï¤y&QIJtz°ÀÇBNÓ\
-4^6+µùFIØ_CEÁÅH¥vØIÕl@5ÃS"@}W&ÚÒ 'Zv¸ÈâRVF,K{Ñ´Àþ>K¿rt×#PûÎO¿_ÝäÐèT¯JéÛNÔa)dÏàÓ£´@2¢³h«¿ÃÊ@x ÁÝR¡ë×9Pl]¿¸}Vê~YJOËç¶_¾³÷åsèzÀfkZ&اXè%ÕWìrîÆFº¥]j´´ÄÔèY)üa[}BJ
ïX
-<Ï]½,
ç(ñÀ²Åª"óÓEÏ}ÛK%W?¬K
¿Uzî8õùý^YÖëRÚ«
Ì]¶5QÌX37ãG£{ßl
-M°-KaÑáƪoÖ2QUD`¢0Î ÇÀ&0Rhí ¯Ã)fô.æ-Ô^TV¸j ³;ff>ÑÌ¡q ©y¿pñÓèFWi(B][00!ÒçÊ3\Z&h~ÉÉR2Ò1Î$Hh¶7Hq"¬¨nÅÃ-Xà©(̵%KºàÇÝ`±Røxld{à²þjð¾À «dçd-to/e ÷É-íÜ.n
}Ö¶\ÿeÁælÌÇþÔÞì£,(³S¢}ÈLÑ(FK·2:ëªÐ-¾Htp¥iL/(¢Ñ\h.{}X%¥Ð]¬æYi\ÞÃq2ûqÐ,À(±þ£&ØLä¨ë&)©&ül
-¬{^P¼ÿ3¡J%æ<4k£¡cGÝÄ
-xÀOÁA± ï¢+øs·çØé>~2Ú£²l¨s0£Ñlfui0²§yIjÐéZº½î
-h©
-»¥wþ4*lu9×u\R:äó¼$wH.ëªÍÓfX÷#)´Ç¥îYI$Ü9yY*TafÌIz½.ÙI¤ÇËF`!ß°§²iæ¢rÁÁsÉ,·K2¿4'ÎJg1]j¢ÈÀ#ÿ`×½å/$W®Ï¬Ô2,9xL¹|Ûaã¾áf=fÓðý¾MGq· Òä6N0!D
ÛÊI4ÇÈÇ`wpò
- ùÄFbu<HøðÉtWIÈÆeTjpÖVy at b5h<Ô4!2£b <Ï)ªÎ Yb!8;6Êi)AÓ÷k/
- ú½hí&ëÆúú¢-)<úÑ@ àùbCý/¬JBKó<j»¤[ìñ¢SG+k9¢Gp·ÃðÃ~Áí^.é|[@å@Ùz5§ª@G¼Ødõ~¡{Äò÷.nz«ÐUh$b[âPozÓ&ã-6ÂÀ¯3sç#ø`TæÂ~Ë=ñhý¡&aIÚàÜÁÁôjÉÌî½<ÈÚ£-Xr»o2ÑõzÉéLPÕ.¤Ïïs.ZIJ*!ø8z<ü*KµZÃc$¹ºXÙîQÍìüHµUþ)q>]<,íÀ®Q-E Fövq¨½SìÇ82ØP$ÝÙ
F~Â5JÈÖË!sCl¡ßlØ·Öf
-el4r00TÀpìºI.4òúöGhg¼hÛvB0ØH(,·æûÞÏF®ÔË9hDkÃzµ,Ðó
-hÒÊæ|w`,S¡ô Ë9ÒÓιXö3ÈARr
-ò¬Ô%§(Û÷àâ}ÎËåÝ«®Gº@uÉÁ°´
-gd)Ve`÷Û#4¼-6Oý(Ä+ýD¸A1y´ª
-/³[ÛÏôpGPÛz»»U¿ÌÝ)/çÆY "÷©L¿ªÜãVT\hhðñ^¶¥½[¤sZP.¼Ì?îÛõéQÖ"¯»âíóå
-¾ÈÀä[5Tb}¨¹À0ê#¨½0GýË6t$ï´âÄÅ¢ÖÕÔ&ÅåÐ"Z(ác~¬»rð¶N¦¤
-4þ/rø?jX?p!½Ä;
-\DëgÏYw¢
zgº:±5¿dbíq°ÅêbýcÔK§÷8à>L!À9U^$ú̺=ÕªA§Å³
z®¦æ´þ@£¤²RÞÑdÏ©&¦®½î9®ØUÎâξbE½!F«
-S3DbdòµÞ© §FáL|ÎáW¸%EÌ2Ú£òIâ¬$Rû,é>ã|uö¿Í
|÷M¾R]gn¦TOH¤FÄ¢zËßÇJ
õë
-ôÞIêkk'ø²Ï-IrxÝÁ!ÂIîIµdIWNU1á©8ºäpÉgÖÕ!
plQL*7)oë0=K±dÒGÃAú÷¤X»£ð,~IÐê/ñDcT,õ¯AÅêèKtBÔhI1DºJï~àc<Q½ÛÊxÅ`ü£º+õ±DÌ[¶îÓRúø6ëx]dSe;Wr14þLÒU%#òõÍÅ÷²~ç£î«íÅK¶Â]®9Ñ}Å^¾7¹\Ô«>w üØÔyRJzYJ¿+}Ö°QGç:ßѾ8êøêR.8ÝViz綧FxJ2ôc-cé¿YØ\°¥]n6o®1ú1ì.)]NôþzOððs ý~AÍ®.l¢¦7¢*2$T,Qaó=»pÒó²-Ý´¥Y®#ߤ^~¹m/nqWßçu~S
-p·ë[c³½ò|Á5:kIÚºHýíÏäO{"·ï7bÑÁBí»¥.!yñc©{¯Þgý8{W(òoËïßå/Â;ÓÎâáæWÀHÿ_åF_ÐASáÍ
-©ñmIg|ñTÎá_}>çâïc5©øü-ïUÓ)Ðo`!¨éG<$4-+б_Üøüû¸Ùendstream
+xí\Y·~ä/,ü4cx;ÍtY±ã$ìHë';0Ö«Õí®dË¿>Uì&Y$«çXl0A-ÏâW7ÉÆAøgþ÷ìòÖﻣÇ×·Æ£¿ÃßÇ·~¼%b
£ù³Ë£ÏN 2GaVZ}tòèÖÔZ íëÜè ?\Þúvõ§õ±¼Aý÷äÐPT
+áÓç ñÉC¨|g}<Ú£Ìêb=NK#WOó×ùúXRêÑ®®ÖÒ&¬^S>Õ µubõd- 3êÕi>ÖÃàV/ÖÇRÆ:^^ÆRµz´ÖV£«çSsïèÏk m±«kèRØAø@'þC
+rT£Y½Îs"¿cè'ïe£Æ@z1tU^Å%á1Î#£«¹ÍW8#Ø«]G%Vÿ*¥`r!+ÒÑsæ´©7Lx¹Æ6f9HáCÚQ¿È4¤Q<[ö.½_OK/±'?8CÖû|.y-.HGð³J¡BÖu°Uu\ïIiB+2Ü;iI±,,ðã±Ó4+ªW¥ôm'ê°H²gðéQZ ÑY@´ËÕ_ae C<à^©Ðõë(¶®ßVÜ>+u¿(¥§åóíïì]ùº°Ù˵-lES,ôê+v9÷b#ÝÒ.5ZZbjô¬~·Ê>¢C¥ÂX
+\æ®^ÂIx`ÙbUùé¢ç>í¥«ïÖ¥Â_*=wzÊü~¿À,ëu)íÕBæÛÃÃ(f¬ñ£Ñ½o¶&X^ =¦ºç¥î鶩ߧ#+¼Â~ÈFn"¸Ü¶¼Y3UC K´Y¥Ä¾gU>\Ðü.¸ß¡uh)ÚéÎÐÒ7i¶´H³} ¥ß¼Àú[ù|QUõvµ²Sá!ðQ£.µzHeP%¸F[Qen*ÒlTfû ÊH`ùQü@ëfºÐÞZ¤Ù>Ð"Íö= ÀÚäìæ~U4d§Sòeù|ÚVh?Ï3ªñ`³|Ãâãó1yÿIêØêáMÆßÁÄϲ@ÂÔÍfHx¶Y2ª±]½åmdUèI¦K
ã/×jbz
+KaÑáƪoÖ2QUD`¢0Î ÇÀ&0Rhí ¯Ã)fôSÌì¤SQ
+éFÚ-[WÇ)çg$Ö!iöI.,EÑâByÿ(t0§óøEdȬ#`JÕIÉf<É¡SQ
NÓjKs}ZdBõèîùý1lH¡s¾nk¾ZK7 Lÿ.¡"GLÇ1¥ÚqKrkN ª@û®á]çØÆ
¸Å2¨@£³g¥wkpuSfqoæ¬ÏB$;æwDØ/r$¡¨0$³!±wèª1¡ÜÓdN=ñqk kÏ3ñ Y¸TGEÂHkÙ³Ö²\@d£~ßáÌ ¢{À³bX:ñ^Ñʳ£Ï`¬{´°M$ÜÞ®Ê<Ï.+áSK9,²#jÏ*I+\5ÐÙ3A3ÀèæиԼ_¸øit£«4¡.Â-ÉéóeFF.Î -
+4¿àd)ég$4Û¤8VT7Ìâa+}0KõÁûYQ¨Èä£~Âl\Râ[úmºw|ú ö£x*
+smÉ®¸ÅñG7XF¬>Ÿ ¬?¼/0HÇ*Ù¹¤Y¢']#Á[æKeÂ}2 at eevUÒ¸A?-¨.ò8lì¯W`Ú[`ávliY¼Áý"×\8KÀîN&Ôkª*o#ïÆï~¡5k£»6ÅdmtèQ
+JòÂèX ÉE.ÄZ
§ÚªC~ÑÍiú`·údC4mäÓ¤U³Ðv° å{S:Dðög'IÞúb6ßhß py²Ð^}SRR E^6DZæ¶Øbï[Ú2b¤ýd!_ÇÒ®MCµçò¨¨ú6Ñ ¹|Wå$M¥\â¢W+ÐSV<w%â¶ìs:'E½È,Ê
+ºOªSciì#¨Êï%SµÑè7`Ìz[âÊ
AjÓkN¡ªpD¥Q¶7xÍ{D¤Íå4};¾ÁDcMpíG#v¨QÚª?¿Uo2®¸±¬¸ßÚI&ÌKµO ¬ÎX¶_ at L':¿~ÈIè LóljmG¬QßAUÖ¡*·¤Ù5s<r²LMH©âx©ý,Ç°<½ºãkôP×lgÜ·îþEÇ`\nó++Ugs a¯¥Göçeéñ¢XÈ7ì©l¹¨\pðb²"ËíÌ"à/Í ¢³ÒYLW¥(2ðÈ?ØuoùÉë3+µKSb.ßvظo¸YÙ4|¿oÓQÜ- (4¹¥LÈQáö¢rÒ"Mãäã1ò1Ø|¥HD>±
X>|2ÝU²qµUXM%5
+B̨9EÕ9 !K,g'ÐFS9-%húníå @D¿]ÓdÝA__@!HæþG@飪]ª» -¨= ¾¸±
+(Õ ³¸ÃÜ·¥=¼\Æ8Ý/i6%9ª(ð)5Ó®õc°\Ø"ÞzµaêÊxV!%G1(|ã3_l¨ÿÃ
UIhiGmtK=^t*ñòaår-Gôîvx~Ø/¸=ÐË%Ïp¨ (;Sïc±æTè¬ÞÑÏ0tXþÞÅÍ+]7É
F^ÿÌãhmómÛNHf
åÖ|ß»ÙÈz9ÇhmX¯Zs^MZÙoRñìbec*d9GzÚ9Ë~9HJNAºä%ñrû\¼²Ïy¹¼Ûs½Ññh@¨.¹2¶!ãì,Ūì¾q{²%Ñæéó¢ÄBx¥w"h1&VµáErvkûîèj[ow·ê¹;ååcð|Á8+Aäâ>UéW{ÜÁ
+m6Þ˶´÷btNÊ
ùgÒ}{°²>=ÊZDóUs×0@¼}¾¡Á|b«J¬5F}µ÷æ¨Ùä@ã²XÔºÚ¤¸:CäA%|ÌÏssCÞÖÉTóªáæÒÿUðG
+ë.¤xGh½÷ñì9ëN´PïLW'¶æL¬]0¶X]¬ZbàséôÜ)äX#§ÊDY·§Z5è´x¶ðPÏÕÔ|ÖïiTTVÃ;ì3ÑÄÔµWÑ3Ç»ÊYÜÙ@¬h 3ÒhµaJÓpã@ÌÌ@¾Ñ;ôÔHÒ1ÉÏ9ü·¤yCF{T>IDj_S%Ýg!s¼¯Àþ×¹ã¾É7SªðÌÍê ÔXToùûX©°~]Þ;I}íqí_ö£%I®Ï²;8D1É=©,éÊ©J1&<5GW.ù̺:D£#Iå&åm¦¢g)Lúh8SÿtkwÅ/ úAâ%h
þ
+¨X}iQQNÚ-©#HXWéÝ÷|çSªw[yo²Tw¥>yËÖ}ZJ_±ßa¯«,bªlRbçJ.1"¦&ÃÒR#Iº®dD¾¾¹øÞCvÀï~PÑ}ÝR°½ øp¥ëCNt_³ïM.%õªÏH$?4uR¢¡^ÒoÊg5lÔÑE¦Î7´/:¾ºN·UÞ¹íiÑ endobj
65 0 obj
-4702
+4704
endobj
69 0 obj
<</Length 70 0 R/Filter /FlateDecode>>
stream
-xí\Yo]·~ô/ܼI
Åp_
ØiZ7£4("-/eɼ$íï×!®ØN~È áÌÇoæðêÙ3±áø/ÿ÷Þî¸ÍÃغ¿Õ§Ç;ÏvDþ{o>9ÒoVZ½9x°#ra4³~ã¸c^<Ùùa×ïík<wnÇGÞ½¶·og^
]7í [«l?îµækó¿þº³/a6ÏÅf_ÈÍÁ,|³u}Ùf¸ßZO[ëóÖz»µ·Öißûõtlçðh%³FìÞk}OÆ%ãa9Y->Þh}I£öøí¸ÍØ÷¼µ.ôI{qô 2lB®Èuß;
-Û=nuÐó颧uÐ áÌr½û'- ì°,¶uþ¸;@± äîèfö
H<i0:â´>bò¼µ²ÖzØZ vZëeg ÏîÜÛ_=±q>Óý
-^eÔÚùÀèt½k
-[ÿÏGÑëQ½Sé¤ìO%9ø^*¹<8¨ª4f÷[?®S%gAªû}ß¾+ö.£¦þLõ¾ï¾Û 9çD¬µ.Ì´SeIºm up5~ÜÞ9øã(H0ç/ϧ{xºm¿ZëÙ´ÃöxgD;åYk|1;mSJAcêiʧÅd~_yC5_´ó<sÞÐÕΩ³ó6±Mò¬d·å_Mg æ&(»Õ:M·w£
ðj¯Zã½Y~1óqÓMQ=îÖQ»+£é¨Ëééµî´4Îî7å
-꫶~ÝZ¿n$À|Ñ óéÂ)ããÁ¢/>~UMòeküª¢2B¿©Úù:gâéË ¡1úî¥4Þ¤kÖÇ[u¦¯â ¿Oÿ¬Z?9ËÔnly5Â{ôE§F$NÿÑåñK3H >ë¬ê¿?c Ç/jêUóÑaº<\gMI<«¥f\üÒ1)Ó/{ûI®¸Ç.ö£«àÌiiâ¡LJÍ-º 0(©·\Z_ÔñÏ÷ÓÚè@æ¤oA
-Ådty´w4ÌÄ5¥W»?7éÒòA~m¤é%ÌtEÖÁÅñJZKJ ÐJC¤36ï-v|ئ|´'
-D o«tNç9Q
- §íJåÈqÇT0¤'þñ*_ìE"î²j\аd_+Ï´-V·,Óæ¦òfñ3Óp%ÕN«®Öi®WÁ^úºÍ>WÁ'À|Û`N9¡§=rLO!$'`säÕìû°¹Îóiâr]d8ë;,C8YøÃî°aìþ_ÐÌÝi%üÿ¤ÓL¬-ÈbÈE¨CvàÜgл٠gBaòh½p È48³+
-ÈX°«° iɶh:àýfY´E{:H¨,d*@2-ér£&÷ÓHÆ Ù%pèKYyf-0Nº@YÕ0u×ÍW^1o¤ÙÊ0ãaTzo
į2øRÂÃR}CcLÈ
-nñ9¤ä /lH²±ÓÔèc¥Êb.dçy7Ðî¬õAk%ÛÜ)2:ä°ïæCtaX¸ðºÔ©!2¹²åh
-óJòñ³a^rî#ªÌ^ZRB1®33lÂꬮBj1 *7R^ÜI*.y~-Åj
"¾
Vé_Ô×mHÛ[«XÑZÇ®DòèãgÑØòQrTmÑúµQÛY[jë
\-lÏ:ñèÙ}ü®hz :="èYW~Ýï
-òÁ~z
-£ø£QCXǽ@¨MQKs§ì{:)åÏÂ×êÖ¼V¢ÄoÄiâTV-®ÙTJLÓ^_`ù4âD¸%"(J6EB F»+EkédöÇ8'êÝà¤VJÿäXghq5Ò?,)Lµ@ßWæç°ÔýìCº±inápLkD³r`®RÐ9;g^$iMmª¶ÜǶ %-¶ynü¸e\
-¬p-VÝÐ,Æ»P}Z1ìÝüVô¼ÌôñNãÏÝA¬¤Ì²9*ÁÖJâ7gÖn'Å/à·m}d
t N¾4/`ö}p®®É@;$Mg3§<×BÄ ô5B
-¯¡c'ÑñhÔº³ÜÝ?îV u8*ÃdÛè$»
´-Òçm\SµS^BÙaY0;{ãô@>e̸°6u¤I³+æ¾ÆZ|û¡<+!Ù"ál×.¿Ì»0EhEçãv*ìlw8 'd\nqÇ¿I´êçÙF!Æü¶÷d'¸!Â~ÿî-õ¢Þz¢¦s[H×ì2
-yݶFì3Óê4çùBs³°À»ðjCÄ[L*-.øeDÉùWª·-?KL<£Mf:³éë=rù`
-ͱþîÀQÛÕºÇÒ¥oüïbÇhÙÕÊèÒèx¥ÀhîUÇb0Îß$b}ý¬?³m'íãÑIýL7?íy1Ú¹9²×Èþ¤t[¹E½gHù¾ «,_ÙÆ0
ÃóGF¼HåZD]©MH5s1¿Ñ!C"ma÷Y©pZÚýVÊà5$¯ÉÉeÝË.;¾ñq¿]ª%?®&?'!?UzÚú.~L~2^>®pÏfÒÌîy,(´rÁ5ZrU1¡ý|¬ñ`ñ8ü0éRí`ßýÅæçÚÔ·¦ÐC°^õÊ!sëÁ+GpýÂk*G« +{=bà¢m"ÿlø2ǣƿb³øùvì°ÐÞ8ï[>Ýg&Ú+6¡Öä¯2ÕÆ-Ê¡Õ+¯Öx¼¹ÙWvYÌôñOC'qó`çø÷_ Èendstream
+xí[Yo]·~ú#nÞtæzH:èë-nÝxSZIaȺ¶$ØlÉKþùÎð!¯ä MÐCr³~CÞ¼[I¡Vÿä¿ßì\âWG;ruþ;Úy·£ÒUþëðÍêÏû0IUqÒ]í¿ÜW«rVLaå¥>¼Ùùq7¬÷¬Az¿+ÓÐGgw¯÷"¸ë,Q5QZùÛJþ×þ_vö4ì¤Zí)½Úß+ñçYwëN;Ò¢¿
+Ùß®¶Gβµ[4[½a>õîÝç¢WC#²¤ÜûrAAæâ¹Îª>Øú#Ðeü¢¦V5×(×ôá6kJ+ ʬ.ð,c¬
+ä×^8h>¯÷´ÐÒH] ÄT!
·Ú¥ TBk+'L%6Ñ`1Ú
+«¹P?Ôõï×FXëld{ò¯
:º¼:x,Æhá£BK"OÌîÏ$ÝÌ>*å oÑH8Ƽ½^'môi½ÑHWÆ(Ðhï¦|¶4ñ¶<^kU"LU:oó¨Svµ LäWÈÇɶµFå+½H$}VXÙñ³ ÂjÐhÕÈI36´7G4K@?°4Yí´êú¨ns£
+6ûK{o³Ç&fyJnÎ9¤ÝI/Ä+4VÂYmx£ =x
+¦©R#+ZÈÕÊMMfmZ0oc¦ªxmÍù(=í@ÛªiÛQ¾WEk®ý¹¥SθÜ$ºäl0ËCVð¦ÚôB+ϧsb_i]~ë³\¼
+ã9Í|>¼b+KØeÖã7%Ïl´3[Ï`©ôaxa
U+ pJu¾Û`çÄ{È*(%ð³ñQ`6Ø_;·§ÇXªc³kS®H9ðs ?,ëlÂÏkü¡^-À(:©ð²N§ú¬»âµðÞ8Ý10ÞëئÎ=Cê$Áwù
+þ¤tʵ&²¶sÈ}?_ãGpþtKààÚÎwÅÎ\H J,î¥+%ãÀáËÑuÐ^~kEÞs§
ü΢áEM¶½³ð
+þu²®`ÇO8,Kº¯:¿ÖÏM]2^àÚ+k1=©1/ÄdjÀ÷oO?
+fD ÜêÁ@H±Ì<Ìsÿ0$¡aó }ÿPÇê+276u4
+ÐO5}ñgytõff6(Þ+Çî/
¸«Á3&¿d3WÂ!µÂÈOMè%¯ PQ£µÐ¥KÙ³y5öY endobj
70 0 obj
-4060
+3109
endobj
76 0 obj
<</Length 77 0 R/Filter /FlateDecode>>
stream
-x[Yo7Þçýó8³È´yy r87»K@}°%G"Kò!ÿ~«xU±=#{-«m|ĵôd>=OlV:h§&ïö°ÓZínòÕÄ-äjßù^KPj²¯2,]R*4
-@ÑxrÎe^;[^ÄÕ4W:S×
-®°NÂöì_³ü¶;ËZZmAaªIwIÏÑ¡!àª=°Gû÷¤äLéâo°^i1XN£$³bf`¸ßáw:ÁÙ[
½&ù¾)F?vàÎÒ£GsB¯zÒÓæäÈåp^éã ôYñúÚ®QQµ ÑRð :Õ3FÉÓ
-w2É EP.k«Y¹«²:°ïTÔãôv±"0z}ÝnæaÊƽóÖ b²L
-'k=é;<ÿÎmÀðZ±*Á0bÆã|æ"8°4¶ÚÍÖÅ÷wíözÏe4àZ©{*¼^ò`î! Ò!l(\íØ:H;ÞÌ\OÁÌðÈK56hGlë3 FÞuª¤Ò¹ÉÊqS¸3Ä%QÇ+öÛÛ½å> X-k%s4ñc.â%=TÚö§ÃÝÌí`RìÚ÷Äè¼#ø[u"1>yz?ÌâñÕO¿JÚzªßáÁ&m'|´?8ðd`ÐÝøØÅLÒÁN¤xv¶¬ç<ÁèYGÀ4za0faË(cvd¯Ç*Iøª¯ú`©³èjJ³ËQf xÙ&1ÇÇ4&Ñk::y7H¤khEYvÁ<Ù:àѵU
E!%öYÓy±¤»àoywëF én66áÕÒ³ä©fR.zÞ×`RÎÄXz}B5È%|ÞÉ9;Ãf u2Ƽ¤ÀÔg-KÐbGr^:i©eîQ¸Êí-S-M3 l
-¬ÖèÓxsab,K&Cw¥B0¾<zbåºÒ%l$À8¢¢íÁö LÒè'ðCåCÁÀ *S)¤#SJ&qõ2ÎÖ(j¯y`õÁªz-ez±K¹Qµ-(U*^Q?.òÿ·k¨^"\βëpB½5é¸øuY±
-%ZKeõuâOÝ(¯[¤7ÓÖ#µbAJwø°¤×Ëå¸ÞXDl%Ø-ÿëZ´
Çü«"]*IêÄÌl=Òf§áXz>4ÕD?Y.sÁ«j/zªªR×Ìüäâ]*ê-ý
-5ÄÊVávºnw¡Ë¾¦Àbêöi©ÎeâîTtHý]}aak«}/9ÿ!ZcÎ>®É¬èÈNøîm~^ƤrÎcÊ»`VìwßcæXØàjX¥ÊM±9s6L¹Ã¶¦VYB=æ¶Ã§E^ù»^Æ5ÊZÕK1³å»Nbû¸¿rÐÁtÉ
-A\-ù]Õf7Í:=³öÔ#(Òàì3°blôkAô"[9´$I-ëV=\}×qV¯¼êå°|±aº:Ä»ùmiÆéB¶Ë¦äXJ`Ã*5¼-Ãã¤ÖËæîfÞ¤9Â|yä
-¬1õÚ-/qU¦³7WËNÕ,çSoX[Í<d=b©jñòóÞDè¾W±6 QØ´¨þ{[gáKßú"B¢ÅrÏJµ\ÌÃ'!¦ËýqØáC£ïiôÃ0Ôº¡Pkñ+µ¨Ëx$"òLJ ãCáãZG+nTüÓ÷ý,ÃrÎC%gæâíÂç
<´`õaÁ%Ç]¶«Ü¯ý¬ eVÌ_ø/«#frÓ>lfº=ú.Râql÷vÔõ¹
r¦Ã]£^ê1^èné{gÊ&¢qíòrÙò_2pÊpà³dU±:å,8§úÍL#~ÎÛì3Ñ|üÁinI>ÍrzÒ¦:uïÙËÑóå£áZ:]K
Õ0)+ Nt½b?"
-Æ4ãïiôJÜÛHYD5Ô>ÒÅÀëÒ®õ¹
-ÓÂͲyãLãC9×þ¾µèhCkÿyaÆó±E3üQ׫ñ[¶ÂýyÒo_`w|]ZLË3FÖó6½ÄÅ®Õ4ú¿ÕØ®L^)h-@ Ö&õ¾'Bê:yt¶ùiór+1_¥ÒÏþ ùçaÙ-ÌÅ(þªð«Çÿ½}óêîÙæá¯[¹yøþï«¿?¿Ùþmóèñö§õ_ÆEöÛÐýÂNf«P¥]þ¡ÙdE*äR&wDZû+
-ùÑP3åÚLF(lÝSìDê¾°ñÖøÜ(dJ¥Ô1`$úw#R¶ÀFM#v<½On¶±t
-§Ô-gÓ_ì±AräØØ(&
- +xµ[ÙnÇÍ3a ¿pßroà;ê}ÉÅr¬,Þ@ÀFDR¢
+D¶ô÷9ÕËtõLIyAàhзêêSU§ªovb;Aÿ+ÿ_<úÆï.:»â¿Ë7'2uØίvOÑIê46NíN_äÑr'µÂÎ?)³;½:ùnG)e¼Ù?9(>î> VFîô$sûëQîߢ5ÊØýO£jrÎï¡ô?_4tfß½´Öû«&Æ2¥.&£B0¾|zIÛLXHxc\b^1î_`HZi¿:*ïö7´¢¢2ûï?Ðp©d W?Ù&³Àio³DóÏ]LÖi<To©×ÍmNÁ[ÝÉ\·ù¿ÓÑÉØ]¢SÎÐÉ(ô×1ìNÿsrú×ïö_síor2Á
+³Í¾
+ª·Rcr¬£nz8SÔp´S1jÔXÓY¢Üý~}Pv²18¦ b/¡xú9Ji±!@èé8iyeé¸Æ¡]QOã44Qæ!+TlPeítÖmNZðXW<J×\"a©ô,¤ rIhÉ;ÒH'û ÒjËƳ¾d~6N>ÚN³Fó
Bõ&²Ø³î)Íd
:©¾!geüäg?ë±PÞyãyk-r"È2t+à´ä¬%3ÖRQáØìã}L at IÄ\+Ç.·Kb¶!lîö3CùlÖ.¬ÏÁNz*D\ký¦gDCÎÑ+Û"ÀjýáÑòÙkÚ±ÔkË¥Ñçí3+^_zÆ~Çyq|plöÉ÷zÒ
+tÖoRõ|Eä'NDì>ZªQf§®lP¹Îf§ÚÙûé¶~
+&ô§H2 (â
±YË
+ðUÈÂÄßѨ¤P¦öàjM+PWÚ¤.jPu«tAowÀ%YMt¶Þ.ÁHþ¹Çe¯+o$XqK~LAX+¦µ¶fÛõ«%ucøßÂÑ~^
+§LÔ8÷Ìù
+<_ÐÖ®ôSx)1ýíó³üéáÁ¿[Ù|Yól½5&ù!E¹;nPù ÈX
+ÚI;}ë#í]ÿÖÉ´¤{¨§AX", u%îðnïgV4f?å$&7Îág¨{(k!Á^öJ3§ÍÆ?¥P5Áz_àgiÆâ¯ÊÎðL¡~H\íL7z J¿CêØ& ½ÓËÛ7æ¸ÚØJïs÷ÏR1Â;Ã9Êeq¡Ù½,ÑÌIæùª~ilúÁºT\c<Mîa/^~P¨úìqúQX=è]K2Ï+¬FMña+^U+¶!i.ý
Í£÷[Bæ¼êqXzÆýo§µew¡à¹ÒK â¹0¶W+[oæ¨p»> -ÜÕË£e'üªEÛÜUzÇCIÉ«²½»â±2%vÛeY®§Þ]6|.SÖ;<íSµÿ[ï"tg~Òda$
*üygÌ£E6SOä[-]÷lTKã(}R©p;ܯRv(jÝ´ôéUk}×ZßSW-ÕºhÏï̺'!"¯4eæ>20
+iI
+Ïq0sVx9s?>Ç(ä}Ev3¼pÉyÄ(ÁËôyf]üÇRY1¿Cõ¿ÌN"ÉY.{Æc²Ü¶Éųé6#çya;Ù=÷µäBGÙ83çS©jiVf+Ø3÷`ÌÉ/Á°¼ä°ñRF¯ØjÆRÕç˯K#Ë[xEd}ÎÛÂsùáøòR(æ}*¶(çI®¨û NZn\*·*éZ"
+VÃfè¬@èzÿ\JöOÁÙçPë»ÖzJì,8ûÛé¬N?'D#~!èM,¢#L+´¨æï`ú]ûj×R
+¡nì§ÏÛæÑß4Æ#Å/áá&ÂÕÊoÐëtuI&¸1SfíFZdmÖåú¦çPòÆ
֨ؾ0ùP®÷ ¾9ùÀ2~
+´ÂcþA?çIaûêÿÜ~ç@F+ܵí³ßjÉ#y±pÆwV£~ìe6â]tü:nĬwsß4¹T
+Óí>(|Þ}D±A«aIbIû·¼¦}=4Kf?4vþPS98æÓÀè¥
+f¤¼Vÿf)õöNRÕ¯Åøóa"¸nî·úC 2)6¯Ñ
+CPC>è1OÒ©¥3KÞ96Â(d>LYü^øýX¢êvã9SSûOOÕ@nKõúη¬dûÖy»z½°|bѹ«1/ÔGç{îâå.iÂÜÎ)`½~ÓãR:O)æ{ºÎÁhK0âM¬YYû
+ô¨Gtô¯/û-ÂÚ_.Øc[_?ðÄÆÔ6ÈÁ§âÒR/bEÑHCá³z]Á^uWUaT¿öÙ3ÕKôÎ2_Mö©`~t&õEÚ¼
+Ü+zTmÏÿ¤ÃùÊû²<ùì
+·®gAY=O³RÀ* QͺxÅþû
;éM³ÔEq®Üv÷r7»¡o!Ýoa7åjÔwåF¦zhí«0'Nç(Ñ,{#è=¯\6º°õèÎZx.\=z'DÀÄ3C¸èðNoh/_лá}d×ðµå\kÿì³
Ê'íuYw¥6º¦]~lä:ZÄßîÆcÅNÉ0®óñÜñð«8ñlàí)Ò²ÞѿƸ¬Ç¶¨¿®SÏö7ÌÂÚ~KHê5WqýMh)EÆT*9ùø*é«ÕL_Ï+R}4·ñý&_I.wfbl+^vÊ7t- ÔÃíÔïÙ¸òýtÆNÓc0¬ëù,@Óâm_Ún¢yHvÑ60ÁEÓSaÖèlê+:U6sAr¥bÁ;àJ
Áä+Õ¬@] ZõDéïjV×ÖðO6éÙ<k|Ù>Kk~d7èô© qÞ¹ïÒXÁí²3¢ânÈäuÚúÚ²nçÔgÜN&ox=©¸Hã#]µ%VòL¶¶Y¶Ph¿ÉiËÉEºò»É®ZÛxvVãCO°§`Ú(¸ù ö,Ï
Qð:ºÄôñà3[.(ç,»×)`|}òü¯&endstream
endobj
77 0 obj
-4168
+3702
endobj
81 0 obj
<</Length 82 0 R/Filter /FlateDecode>>
stream
-xÅZKs·Îy~Åܼ{ Fã[d+2ãı,:9¤rl1®NäWªòëý5fðå¬äÍîP¥*qØzúkôÓoGhÔòoþùúa¸þ2w?:ÒøY}ú×ðv ù=¯Ýb¹1©ä·ãíæõ£òqôlñöaøÛî÷{£Øowßîµ
-Ö¦@»»ýS1¹ÝO{R6:mwßg¢1wßì¯ØxÅw©1Ýo÷WAûtÏÊ6
§qf÷uåÓÀ¸ûÙ©@f÷¶ìËß
-Xv/ÿyeÑÚwÜ_·E¶Äë~pÖdghÆ¥ÝH¿ýÃðüvx Ý
-oGö6)'ûGb-ÅâüÒ1úXóÿÚÊ÷à´.îþ]}²¸¨ÌÈ9(*µé6Ý·7N9ïCó¥:C1©Wg¡Tå1Ǥ¯Q:ˮ˨nt ÎOöNi§=õ#×|W:´{Uu<íæE7ÃÄaçB
-¡¢·OìÖ(}ÙtuHt9PÇÍþ
-÷:·vƽf`L-ì«]K¯ÍµíMKͨ$¨Vá§u(Ò$3³s6-ôk)Ì+¥éÓ³Æ5JUhÝu[Âq
eT{&© g Ék·ûGÖ\>ª;R,ÌãñH³ptUîJp1HºÏF®M¥½,ª²ÊÊ5¥ ´JÙU)gÙ¡QµâèÔïFЧw\Y8äQ#Nɽ'º¢<ù«¸zúã>"þD¶»[¡Ú¨áð(ÂòWä¸ÀÑüyÒDF
-+Âû£9f¥Ûã°saãã&qiÞzß-8&}®»@z)µzé[y^ý½;yÖë¶Äñ(´T«-ÝmA£u[-gÞøo qÚ¶0¤|[Ð|Ù.Û§ÀaµÜ
-ÇiÈÑݺ- ¥%Z*Dãµàéâû¶øPsXéÙB\:¬F
-lï°uUuÏGN Î_+á,wõ.ßZÆõ<oÊX¬\ûür3o¬M7¯¯"åÒqcd0Ô%±GÖµ`_쪢÷÷X³Wn-FØYè±µcY
-R+×ùªÅ ;Á¢1xtÒäój+eϽ¬q._®K2^µÀt=yuÃ¥nZÐyÀYñ·r®õ]Ý
-øjwÙPÀ{mTôYbð"Qu)Ý)uË·Wú"×!ä¯{ó®J9벡Y'Ü\k|Þ[^Ïýü±ÜÅOÑ2f¿Ü¬÷Ã^âCÌ_õÛg8$kûíÁ1é·×ã×zäììs½¹ì0¥@KÙ·½[´ }Èär]g
-Aù©!C¥
-ÂæÒ¢á 9$øÖ£¿EÄAp(=ÚbåEb\Å
-(L³ªºóÙú¥f6U
-¨f§/Ð>ÅÔ3mU-]QLô¹mÕL£UÎ84Ë2o;« ÁÝ´:§*ï¹.ÚBpKSR¿\¥rZS¡sS±9Ü-¥¾±îêðmÇ[ðÕ(?WP^[২:*¥¬*³W+&CÙÕãÛwÆWnçÏQíãWoj"TBYS&©V(õ}eSn#ÆlÎx%+ÕØã*³HíýR_7O0Pêûê®Úv¼±´7rã%vcËLñùÃå U
-ñ
-Å0x&©PÊ®F9uÚ4ÕiS;¥EO2T6EÜIßÆ£1Á+«H[v¯;
-¥ ¡ó0xx¤Ko¿_3Ñr©Ì¬ÍGæOh&ÃóçO4/§m&&ü<
-sÊ<óúl4ì;íÊýÿ®Oµq%¼9Þ¸M£:Â^5ðrøOuQendstream
+xÅZKs·ÎyÅÜ<{ ¯Ü"[±8eÑÉ!iKvèD~Vù×çkÌà1»³´«¡Ê%jþúùõÞZÑ å¿ùçÝýîúó0¼þa§ñçõîíòaþqw?<»Å"rCRÉÏÃí«Ý´c£òqðÖ(2Ãíýîßã÷FYÇo÷Zæh|½¿r*&"7þ´'ÅÑi¿ÏBc¿Þ_Yãv¤,1Ü_1ì½U´)ggƯê9í7C
+8~SÌø~U/ÿnpd÷ð÷WF}wú][YtKvý¤¡sH<C3.ÿ+*ýçö/»ç·»;¯R°Ñ
+¿ÀvßîÈÇ \ÄÃ}$Â#Òðfgñ´& .(Ô]UòÍîå>ÄÊXyTñÿT
+þ¦äÈ8¬«ä×ê¿X+ãÂø13qôvülåÖ1Äjc
+ª¤F(©Dk
+ºîºÀ0®Jfa
ÏáEë9ùe`;_ÛÊ÷ô.ÿ1ÝBDdÄòbJGmº
ÍÚßíSÎûÐ"0YhzI5µ1*ÇkfÀ²ë&shÀöNi§=õµ ˼ë
+7g¸!/«U§ÝvófQµuE:TIEÌhµ"©ë®l`m8´ÁÍþ
+©Ç9·æÊÞð¾Á¡0j±Õêå]¶½¦ÅÔ>cUx °´Edæã§jT¦h;£VI3¡×$Õu×FeJ*ez&5 ÉÎÐ×nü2Û*Á,T%Ï(bäñëJ1bÖ-îç¿:ÔW"¨ýrÇxȧÒ^TIYÅ.zEaMÂ6")»ªäq¦±UL±6ÞÎ$]ÕmÒ
+ô-¶çs®XÅÁä#Ì<»Ù]ßümøñû¾Þ]ÿk Ýõ'ò¿g}7
+Ø=¿^F9:©òµãô óéá¢åñV¤µ¥S8<¸ÈSàð$.pô*ÿ½i*£ Õx½kNà@Ðbéö8X[å\Xàø°iÜC´gﻧ´.ð^bHR¾;í[çx^³»sGk'ëîúÛC²ñ$´n{hymÐ:@
+åí>Zë:º6g<ÜA»@«×Kg¾¬5rê¹ÎDgQºüöQñwaF¡ª7JmBùPo<Ö%À YKTæ@棩fÑh²JH^ÍMÀ"U|&%1·
3«Z% Ñ2á´s
>à\)sYsfB¢í¯#ÓÉr¦ri¶å2È·|Y¹¬®¬ºJ4ÜC[¬×QüÊ©²ÂYÒVYh¸"¹r9§J3ZàPQEÆc4.òÛÝA/Ýge÷må6y@Â^ç6MKdlß2º8îáý³z¸ÛVñ)1²ÔÍq
+>·Äù~òl^òpkÀaPºNã¥ôuU¨éö8@°]
+ GÚ;$+º¨ïD2GÛGàÌÑÌwJdúã+ü,ÃIeÆcxjæХѳÇ. +$Ç:ÄÃrĸÊæ¶t¹Üé©mt8Þ«Ë3íâovØë3=[y JÝLßV ~Eb+Aùi@'^kP°´F&-Åyd*
+0Lò^°[uv1A2°D©r at 6FWBþzÌ-'Õ'Phë$ã{²a:²ÑVUjq$1Qwl£IA70&
+g\D6Bpùûþd#ê\Ù«¾¦=
âL¨¿;&qÅ×âo7@¹ÐÁÝßS"7ßUñ¨ezbùåúVÁv¡ÚªrõiE"]Ѧî2T¼ÚðlIjã@;|EÒã+·MR8ßiZÔ'Ö]¾íÎ|µnÏü§Q¾ùòP§CUåÊѤéPvõø6;;ã+¯òæ;íSR¯\j:4IYU®Hêë®ßvg¾ÜãJ[^¬ÜÎiOoú¬ùNϤ>«îêmw¶ #%ä»\+ ¹²ÌoÐ9\ÞBnÒÇc£ÉèMÝÕ$5ë`V/PòÔü<Éuª©®ÞK6\@bE}ïP!1)%ÚÈÇÄ8ñö+ð{®¼õWë×UÏ»ò:m¿äÊ+ÉuâAÈ©2S«Ãþä¾GJù*z rM¨¶ÝIæÌï'qn8<æ¤Sø
+î<|ç_¿¶Z#xÊ@Ý®OzçºzäÅîÿÆd<endstream
endobj
82 0 obj
-2238
+2207
endobj
86 0 obj
<</Length 87 0 R/Filter /FlateDecode>>
stream
-xíYÉr7Íy¾bnÝ-·HVleµl&9¤rHd[qE²ã½*_np°9LÌÔ)å*zzðzæu¯éµü?/oºãǾ¿zÛ%´7ý×åÛÝëÎèqt²ä ÆöQEúåóÎãA
Ð[o
~yÓý<|¹-ÒÖÉ{èy2oñóîøüÛþÝ÷ϺãzÓ?ÿNòÇùýþ³î켿`[&¨µDÊíbÉ©è1Øþ#ûàθT´½Çþ¦"HN¹Ø_w`Å9ÄDÖ%0Rfä÷îÉüí*@yvPVÿEñͺ-~Dº8|½W~ß$Àúá{ÙACÁáðhqäÖÁ²
" eý(±<1¼háSÆÌ-ËãP6Þ/Î åqÕL%v0ÓÂÍ
Õ1¦d±343=WÜ@:J7ãìúÈöêÜa[än:eÙéUÀy
-r'JÊhrq¸ä| ÜvIº\ÏÙÀh#±ÅåS6ð)o71@ûº¤¼ÝÄÿßno7§o$INÁo$w*»[Þzv&2òùé¶þksåäi¸ÒµÜ-°hQ¸èþ;¢`vendstream
+xíYKs7Îy~ÅÜ=XVwë[lp6É!CbÀ¡
+C+z-ÿ/¯»ã¾¿zÓéþÿ{Õ½ê -èÇ?×ýÉí£é×O»ÍnèQEØ[o ýúºûuøz
wfx¶ÒÊ=W«#«B°ÃÛðZ«Íð: -
+OVGNQ 4?|¹:2ƨàÂpE
+AcVÇEQýÄz@±FÃVIVyÀá
+TÚËß#Óþnu
+µvö˺R£Èºù.g6èÿ*»þfÆXæk1T«í/WØ"ï÷«#ÍBMÀò×ÏUÍïÕÅEÏoëoº³uwÑ9=Û¿ïÀ¯¼í-º <õ×UBÆ)ûça at qNO,)»äÏîÑÙÃ:ÅMú|U"]AîDÅèJùx§|/6Z¡õÃâ0ÁÑð`uäÖÁâ$+µA9h<P$°Ñ
+Ü à-{>ÁD~ÛçÒ4¸4ÀÚ¹0¶ÎàÈ#+5B5¯j¾¬iU·?i?Ònÿ@Rií³5k+,.5¸
+SãÒ")tÒÒÃ 8°ìù*øic:!ÁF`Ñi;ü<Ù)w
+Æ\arrüq¦ AäÚgýÙÔë*ÑÈ`ãuà9Àäzªó²jÔU¨#H§ÞH#éØYOÜ ckLÛDéPÛû;I¢4ÉÀÓÍKv8K^âW=mÎ=âAßñ1^Ô³ÿ¼Ðóîøüûþï×otÇ¿ôÐßÿ<8å?çwû/º³óþâPì ½c7ÃPbû{ê>Xý- ðc&(êè==\»*¸ÎJ.6o]Æ\<lQpi\^ý\ÀIvZ¹Q¨
+ÚÏ[Ó¶Òáw
+1F-D#EvZÎr@SuU)»]u^SErBÇ>bNíç/Döj
+µ´W½`
+[`õS5#s
+×ÜÂWýþïõÂ^\ÑÛÀÅTHણ&YkѵÙ\xɾJ$ç¤AËÁO`Tv5ÛENw»ée»Áè
-3UÇqâ
+ &DZª@ĦïDB5nS%OkUK2õ¯gVL g¬Á$n04UE6⨼ ¯åf,ö`F±H«0$®¼5ÄÜ8¡ßh-iÈò¨%nÀ
+±¯ªvj'mAn»/ÿXs*#ÍLÍ
åòÏ1¹ü«kb£ m¡0ïö-¢-Ó áVLçþFq5}éIìQf#yÏ×7lÔ¯I³<ñ¡&IdnGY
ûK¯ÈÈ·N§MdUhV\@|_åë´ÙÍ3÷åã.±G}Çqã8!ÄMmE]iÄÄK !UroÙÑÊbA>qH²%<F`F%XìT Øg¹ÛPÅ-÷:Ý=[Ë7MϹyÞ >j4w+mÛF±4:¾CðÍ+LÐÝ·ÏEÑ rW³fSãs&GWѺ(¿ÎHÑÛ¼VÉÓåTKq£<QiH´èò;e1¡êããæ¤XÕTt©t
M¤§ò« ºñ±eQ~{TÝRª:ÏcBËGI.? ,Êï3r`USÑ-¦ZУc-ýo+¿Óà 5>îÍHÊQUMŵjÁÌ>77ÒOFõÌ'
è÷F¾ïô7î©jûfÉó9»?¯×ÂAÐyäÒ(ÐÆÅá»"2m¦¸ÝµÑ°ÆõcVð1¿´QÀö§òKýÿKçJF«¹¯l1ÓffÁsFðu54^{ÈÐÓj2BGn®ÉÄ`oªi><¶ÂwøoÎij@n±@1p(±¡î_M«9
+endstream
endobj
87 0 obj
-1745
+1707
endobj
91 0 obj
<</Length 92 0 R/Filter /FlateDecode>>
stream
-xÕ\I³·Î¿7h¬¹E¶"+vÙ~vªÊ!m9I.¯©Ê¯Ï×,³Pä#gXU&_À ÷=øa/
ÚKþ×¾|³{ô¹ß¿úi {µÿ$û×îêÿýèýã{LPvEtÚýý·;ÕWÑ
-·÷R
-ö÷ov=üþ¨gÔáG)¼1ѫëã!*e¿0ÁJsø1µ¶tøæxGÚ
-t
-Á~{¼3¤`Ih%µé×ÔV¾ÎëoXF ,¨Ãá-V4cãá'|Ò~×X²zø¯Ç;-´®ZýeÉ{SFu5ôûÑÛüü·s;©&ý<ñ±Æãû/oYpQ×-»/³ÿ^þüÈ×yÉðeöwyàpHkoz¼=j+¬ÛºÇXÿvÿÝûÝg`ö«Ý{r&
-øγöÈcã{½2°X=~¶{ôìûüåÝ£?ïÕîÑÇü¿Ç/>Ädzö¿Ù=y¶ÿk©a!'¦^øóüµÀ`÷ÿ?Ý)vï¬r ÁþM¤Ý¿Þie´0qb(Ï<+C¾Û}quqAhÂÓ¸Sßeás°D#3·øëùwf°öYøK
2ÁÑáÅñÎ )LMJÉy
-$Ã;'dbäYä¹( ¿º¡ÉçV7תô¢Æ³ÿ§üõ¨ôÃJ»:»£mg4óé¤dWÖjXhßés¾¨^KÎH L¬É!ñBæ
ìÛ3ê19?:BCtM®,à£|²´Òûxø"Ó¸M}ì'CĵQ 0`o¤"ØÒ9HÆ~tr°eãÙñçZ;Çã2E
-ä«]qa/ÌeÛý Ò»Za¦¯Q`ª\ Ø(ÎA2Aó¬Ø(°Ë·Ví1;U ¬²«trðVDú ã<¨£
-søº`ÁTTVøbÞmÅçk ¢à
H1떬Gi =a3
ÈÓìYr$:ù-ÐûÐ"ð
¿:"JB×bh)§'öáyvÏH-í,ñ~Wß7l»ïZLjóö9c«G,íßq ±Áþ±¼¤V·9A÷³òûSÌ
@%ÅÐL°
-ðÒÀ'kðzÍX" ÷GVUVØ¯Ò Îi\0yÂù9ZBØ,ÿ{¶hî´)M^r$RQæ/¤¨`=¾:j/j¸:ý2OÏ4¨Â«6È@(ìm öÛY ! eÛnLâÙý2ÄJf Vq,ûz÷mãa`,d--VÉÃ\
-hä»sð(èû@-öXhV-LµHÚj$7ëµW©©öÂÐLµ·C¼ÌöªøÕ±J(;+~©ïÈn!¸ßÏø^Ý7O¢%Ì°R05ݵ÷ËT
-¡å Tt3Ê{¾÷
-2'mad¸¾ÈinWO±DS*5Q^&ÜÁ~èZÚZ´Rà1Ø0ÔÖ*Aæ1pÍaLöjHõ©´Úxä-ÂXzo#wºlé礰óÃoSéñ¡8²ð@ël¡±&©
-Åum¡aÆå-ÿiç×Eú2E%9
-@°MQGTU)£È-ÂB#4Ö,#¦N)ôÚxinñ-^Ò¶* +QÁ¼Q¹Q¤I5Èk#äNauv äCÊ0wBxVñüÍpËBT bSÆX¦×¸`j8¯0.6y)r¶@ ¶ï~O3¹J1PË1& ñ¸0H6@ºRFQtjBdOøa¹ÊJt¹ç.¦d<·ÈY-«Ö«ÆXSõv¿ëÆXã:NYåêx:¬2,òe#ñÕcÔL&Äùµl.lZ-!¬æ6ü2J ¬:þ[÷¨Öq¹Aã¤È¦½÷2«]U
-¹æDdÏÁ7ÆÔj±6¾ZêÔjѲí<WpLj
-Y}Ç)Í×/¤öªdqëªôÒhÆÜTz!y±Á³cd-̳n¢äR!µ&WU¬ll.ÁppÏä¸8u¶nÇt>°6ò°"©Ð:om_.ÞÊY¼+âͶtKÇ}çµt²(:ÉΡÈv\%Ûʧnr»J¸ìL:ò\Q¸
-¥á
-99 ZâððÆmBMÇrZáqnCÈI~£ký³h#Àj¶þ3Ñî©1×Þ=Á¹øv÷ãúGÒôùõ,6A˶ÞÍÈÔÈöÍ¡v²É(ªÚ-Ì/¿µvP{îç[@µgÉà¸28íù\¿kFBæîήަêà¼PÖ¼IñÛ
ä>5áK5óÊV!øÙFÚ¾»µ""'ëÎ7}´Úpg
-U}´eTß6;pncÕH[ ×å4 e)ùõ`7&å¹åoÅ]Z,!\2ZóCJZÚíå´ScÁ'Õï>#Ó©^¸:¶±{%ç$¶ùø
-E[Ð@{#Tô
-
-Ö®
k|ÀÚ¨jÊ¿öê+V.5µd]Ñ
-þÑøªéRsìë¦KU¸lÏzë¦Ë2ò&8ÃG1½C²I·ÙKu«d²{w¢®nʳ-[Ò£ÛdÇ0¾Î·;ê
-g»#½9ñ¤"íyõ½»î
-±zï³÷9Lrþs^)ÆàûK ¡T4bè2{ïîQrî-äÒë½×Ïæe\ÝU,ý9϶Úyø¦ÄÈ(âá%lrÎK-ªÖ¬xÿ58çò;
-º½Ìí;ê¼¢ÿ«ËïxË:þ\~W_ôÕ]~7¾°Îðí|GMì7.¹ünþ"=iDXäâk=´~Àå .*È/¦¹úu0Ó¢ùg»ÿ p'endstream
+xÕ\Iܶιoî>àaÍ-²[£h;UIåÈ*\òªüú|$pk5{TbÑ+ms:+©úrÆ_ýòI²Dùu)µ
+sßÎHû¸B¥ÔXî@y«nÆx/¸Aæb,׫\°¶
+K4uaàRU¤¢¤0/îxåR¤ZNRR¡"Ò5\~Ô&V
+ÉkiSz£JtAwÝ*UZ{àͽ8G/{óg¹ +xÿ»©øPÕDA`YKþÎXLâ¶þÎ0÷âä
+½ûù+:ȸ¨òß@ñ}LE÷æ$#\'U$¯Rüu
ÈÌN[÷º¶¹v8/uÂJDÃslÚ6çþ[sîM®¸áÂÀÀQJ;p·2ð²*óâ Yªí»\ñnICg"2X7oÞõ°³Â¼ìÜ·Iç·k0¡*SC2ÆJ¥FÞ»ïPªK:Qk&`sBPGkH»&d>õ¼-*Û¶ü-©Úåé,ok:4·Èè¨ìlæöÔªxeNÖ¥79sKb;¯Ì)¯ÊÆ3
Pôðµ=eÈ
º@ {JnPÌPçùx¿iÖõ [vl§êwkº4pø!]WÙWU?Ð?FTb(¯D½ÆÛö¿%ÈË^Þ(£
gõÄ_ÿG Heä5yU÷0
+Æ¥xU¨Vîa¢¤
+çhûqyÔ²¤ôõ2$¤c"2;TëyU)«$Â(g HvÌíÏÉ+l?¤÷}8#ry7aÜ¥½=.3ÙpNÜ4=\;ñÝ6=7~t*7§Óq7,Êe'
5HÔB&âoHüÖ]¯ú(ì@ÜATþråi2Éò!¹êÉÕXò{È°Ñ
+/ÆXËFåö0BÎͼu{
+kbk¬ÝCBÖaÕXB0
+ÍÓÆÌ´ñBô¦!诸dB?Yo»¹²ûYääÉ:xÂó°+zÒ>bW^XΧ«ÖOûpH]]¡ÜÛè0R¢ûË1TAáÖ8î¦Á±¶#ÆÔÖ¤»yµ«Tµju&¾VÅÅF
+cä"þ6ZÒKÍàs^ק9í®J0!Ý Ä#âìVW%^YÕt3í¤xrEcôxd{
+$&¶}ë鸳; ã¬&Ü;-Õ®j}Úo¢¤ÐcTØ^-uTí2÷PL©ØãE
ÆÛ¦ç)Æz«
+9ÔÖ1fH^e,ª Nʦlsy×=¬ZaÕñ«|h.¦«Àoyò.n
+]lqû¤«1µà·&>#56§Ê¬í+Ù+»8:ìB·O½Ñ©§#íì,NTuyã<×uÒJ¢=´Ö³Ú³NpìRK+ÈX«ZL²Þ½Û4DÚamýàSw!
+h>Þn
+Å4D`d,>ØÔ]/þ¿Úsm»5õéÅ=ú«¢Þ7«ºýügkÙ®!=}^ùw±ñ~b\Mÿ-jù¬ÿö}ñc+¥÷P(ñPÖp½¿a>
+Ðlü®ÑuKàÉ?ó«Û%ÊÐ~,!yUßX8Þº_W´K (ÀQ9Ío4îÓ-Ió ,7GjÓWR· Byl%I,Á[²£Ûc¸ZçO«ÐùÀ ºpÞw×~àTã>{±À¤¿äë6mèc{I$F´ubçâ´luwL^Ô_3H%xÙþúf3ÙðrÊ×U¢¥îÈéïn).þIÝ
/3¶£7tä<üL)Þ»zb¬Ôät7©TÎnQýÊ }tÞÐóàg&rúÓÝHZ«Èé/E).nQÊ =«lèÈyø36¤´~¿0
þòÌéÑ]^2H)::þL¦Cñu[ü'ò`~º¤¸ØG¹èà3ÆHQØ®éöȹH\}¶ºâplïéީܱ§ÓÎÀ« _@¯áÉP!¦hì75OB"IÍí78à+È(èáZ7<eæý_]AÆ(ëø?pY}aâé. 8)vb¤ìر{Kרþ '¦>Vf«³_påYK4Fx;úò³ÖßæÇ-n*X_g8¹sv4Ó&úÃw?dendstream
endobj
92 0 obj
-3425
+3403
endobj
-96 0 obj
-<</Length 97 0 R/Filter /FlateDecode>>
-stream
-xµWKo7¾/ô/Ì-3 at FE=O$hÑ`õÆÛí¾þ}ÉF¢vÇz|0)ßÇ¿ZÁ ù§ü>Ü쾫Ýr:ÀðS>î¾ì ü¡ËíáåÐ
-I%o¼öïwPîÊÇ!è }¸Ùý>~7ÍNÅþ±ÿ¡S$Ñ¥HyILðãõÊF§íxfPÆXíÇÛiFô?MZkïêãÕ©-!rãø÷ÄùE4Òøý4dU¤Óæåu5yQ]»]ßÎP(¡6Ȳ_¡&,J!¹%àU|lâEÖÚñÐNUÜ¢ò!U:½ Ç#Eíf«Ðbaôsý¥V¶é,K3cPvd633üö¡Ñö¢Ñ²å/À¶<ôT3¡YÌCÑ÷Û-R2«¨-çÄfò8@í
-èô-QyÈc°Û6n"_yQëu1>5r XU=ývEÌôfZvÉ´k1Krÿ
-öÉÑVëÔ/lþò8¾ÒNûî°B#¨µJ+>,ºZâPÀÅ1Ìd[L
-« 滯÷»_éç?Æ·ùendstream
-endobj
-97 0 obj
-1160
-endobj
4 0 obj
<</Type/Page/MediaBox [0 0 595 842]
/Rotate 0/Parent 3 0 R
@@ -728,16 +688,6 @@
/Contents 91 0 R
>>
endobj
-95 0 obj
-<</Type/Page/MediaBox [0 0 595 842]
-/Rotate 0/Parent 3 0 R
-/Resources<</ProcSet[/PDF /Text]
-/ExtGState 98 0 R
-/Font 99 0 R
->>
-/Contents 96 0 R
->>
-endobj
3 0 obj
<< /Type /Pages /Kids [
4 0 R
@@ -754,8 +704,7 @@
80 0 R
85 0 R
90 0 R
-95 0 R
-] /Count 15
+] /Count 14
>>
endobj
1 0 obj
@@ -916,134 +865,161 @@
15 0 R/R72
72 0 R>>
endobj
-98 0 obj
-<</R7
-7 0 R>>
-endobj
-99 0 obj
-<</R15
-15 0 R/R35
-35 0 R/R28
-28 0 R>>
-endobj
-100 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 101 0 R>>stream
-xYXTgÖ¾ãÈÜ«±2Þ-yì&3¿Ô ªïpóÃ:V°é
êìx"={t~k»Ñc ¤+´{uM`#êz
-c¦7
WUëµyZì-ͺÓuûæÝ
-Ç Úv]òãH&b.NwNܾ#ææo³ÍgNeÔT
-T{hB!VëÂ*µÄDËJ
ÏÄF¼}Gig¬ÊØOä£GÔÃ
-¶jÝÊèÏ G^¼ñ·¶xÃ+4é/µÀ%hR1æHúU^9Ñ¿cp-?³VvtLfS
-4ÚRë$Å`Ðk*TZ
-Ä0åt(8ïâÉA]4ÀAu£ÀáÐ^#ãKéÌt&CÁí|l¤ÕdSÙå2þ ]íÏÉi}5Õþàu¾BpÏÁ9TÖ9&¥¹42w3?çCw;ÆuÌfK![®TBR*·ÉÁ»iã¡1`±=]±'¡Ñôï1y(E²Fß2ùoÜ
£#hî3Hto"~× Ó%ªÐe8àõf4ÖÄÛµ
WY¿¸JnÙHo ùè>[wø`Ýa`.pï
Ó<¼¡²#hÁÀ¬ëßæô%ø?~X$ýwí¶-*Z&}|þ%z.e+¶É:}_iË.í]5ÔÀaKÎ,*4~AF4¯-Bg³À`tU´A
ÒDnðýΰxñU?E{M´>®Mt?ûþ ÌßÄý¿{ëL2=
-dñPbC¢"B÷á=RÅ7.Þú⫶©ëMdéaf4õvIºxWñÃY$1ã|óÑGûôÅVÌ%)c!,®©+6T6{7¬_8mýPÓ³¯ã«¯Û¢m×ÕÂh=îÛiu~Ô:áMhw5:ÛWtè¹E£,-A½uãé÷#¿ÁoÉðß´WÒ¨ÿØÝpÉ3#ègªP)*,Y¾'AS`èfg mʯo;mF
-»ºüÐæ*qÇ.?ÇGÎÃs ì^öÃ}úq~À4Y*ØËGsPÄÒÑX 5ëJ°© («ÎÓ¯Ïg|/EJók;ðâü;6Òýhî5'=ìÞÙ:Ðè8&: ÂãSYUA}}`ë<%® ô*êFcÙ¯ºoüãtò*ì^Ö¼] ºöHô4ïJãéÏ(Íe§ÂÓm}q,§ ¢v0waDd¨&`G¸,5)2Ä9lKH«ªG.\mo¯5íjÊ,ôô¹2]IL\aRI^yvEiR½gô6
»çÖà¦fü¢El3yUm%îõ_FÚ
^æhǸ-½-¸ñYlç÷5Ò[áAWô§ÇBa£iîÐßå½åôdÿ1=Êz Ê^4ªÖ` OC4ÜÄO+·n¨üÅo6Ò{ÈgMÊñ0Ùèë1{ºÏ¯c8U¡&#DHa[:Òò3µÚòrN§]Yù>·ý#Q`úÀ_>?´3©@æ_ïãÃH¯®Í_Rxr`cuëÔ-sR Ó¦ä°0
Y.ûúeº929E©òªud}¼¬õ- ©ÕÕ*>O`^ç.#$BÝß,æ·¢Lö·Fw:ls÷a4èÉoüPgéËWc|=2R5¡#ÄÔMÒ «9Ï¿«¶Æ¡HôE4T@I§«0÷Qw%~gQÄ{x¤ìi"û¸òÚ¸Æ|%·ñÿåwë½ÏKáBY´ô¢%%Ð~»}áZËÀ£Õ«ÕçcÅN`®Þ¬Í|â*RÒRøÆÃfH=ð(ñ+ÛØsÖóûí'¶%ÅÂÍü;fkҧݨ<åý³laÔ|`r!§XæÑ-äY§>d૧ïëå޲崴wÇÒexscmÙÔpúçð÷[×éߺ¢z§c#+úOñi³S éÖ\þªúÔ.¡µ¤}#8bF3¢/ Ubä ô·Íøm´"\¢4dú×Ѹ¦KµÎq'Â×ÑA^ÞKàA)³Dìi°¢Þ/¬¯¼XHló#´ÿÅC\|« zizUízí1]%1¼j/Uf¶È¤?dð»ÙúpcH`ThX¨)Ì\[e¬9Âç7±ÖúxuØ9ôÙ¹õ¤öÑ3ÞÅÌ+=ÁI»ÇÎ\Cïð,N«-oºFäÐ,jªB9÷Pñã;f²HÓåX}qeD]òÔF%È!!IôaG ¤Uÿ¯çÐJäA¢]2¾&ZRTOT©´/(ö/Âö%÷üÊy¾'FQBQõù×¹j¡`³H¹<aVWÁªéëúSÉgqaB1®R´Àõ®bѳTb)7òÍ¢r>AÌOEwØÜҽ絯'°U'DÍüHµR)çØâî¨^QH$^gWÝeD>4kÕ¿.NÎB¢0
-ÇÈ6+A^
÷ÒÖäÑu(¬Môâ¼íA%¬Fzȸt:/çìá6Q8C°*BȨ¼R ¡\'DNHJMÃCq[>þ]ùo ¶¨vÂÍõ+ùNûøêжµk§!WÕjOéPu:á*o¯£Â-ÁE#©Ic%¿)@8çÚDõÊ0Þ×´¼"X³QAl)w3¨ó3ùAÙ1ö8Ýv,2¤ûW×[à¡õZáúCP *@ÖøgÛÌ}ª-[K/´£óÛìLU!.3³±æ_EÎOgÑäsôð
-SûÀð#È¡÷h²"!â åÎã#¶èÒÿ³Ķº¡¥ØIvAdÎ'F¬kÙÛ»»½´lÏÚ÷«ZXE¼VÐ$vçîÝ+«ÍMÅíB¶ÂÔA]ÙZ¯Kb7ªs!91]±r¶íìRµT纮×,UÇÃvpÖ
-H¼ZM»ãÌÚ:;ÚyÍÕ«ÔÛü£ZgÖV}hSW
-`Ò§B1êÎ|^ç#oÙ#/'IIÙ*ì·wjëLdoÑì´D«¬RîàôO«à®¾×øßÚÿ÷ÆóJ¸ëïòz|åÿì-[nàw(_Gå·l¤Ñ\¾7.þ?ª¢µÚ¢vFº#²¸mÓ^-£F=Áٮαñ2=.a;MpªÍ/i9þqN%즰J¯jwýpoµc°Ëö@/'aoYÑ`$í¡¤³»ø4e!NÙ/â_âØ×"¬(Ô"I°ÜB¿hþ¤i)T©8£;ÝmñT>Q¡Î"2¶ó$Twgq]uîÂ'À>¦
¶XÖéíy¹F¯Éý¿¸ú
-d¤x#çÎ[}>¹'³3
-ÑL>Ó½×eù£å¯/eÑÜ"´Â$FîüötÄ~WßØа°ÒÐ&CQ^±:VióW¬%%TLZ¦23ÿÖ¨'gñÏXoüeAôä¤ÝÆi,I.®÷GÖh5 qæíFóP_Ô¿Pb=Ô+'-ÆÒUÜFÜ- IÝgçãÞc«Ó¸ïeyÀdeè³eÿðtOßô,\£Þë#êkÿ)J¥BêòXÙ÷#Oà)½-n¿Lv²$a)IÂâV«Õ$,ùp³L¡Ðh@ÙjÁõë>Ñêñ*;%Y9yBì+(äxRÊĨ9²'qÙjúex!ºóBÖeÿÙµñª1çmÄü²g6³PH+OP&'«8ü׿ÍKO$[m'ÏMÉ/ÊÈËÓ7F:lM¢[(çwEYø*ý+ß/¶~7³Ä
-a^ûøEé³á}1©uÎÞ÷.Gcð¥qß·õ2¯Áuûàˬ'¬ªýN~Â)8çs£7®äVA-·æÀ
-¢§åËw]äy7fó>RÐ/Ĩ½c-פj"ÔiªIÓ@2#í˳¡Û'¹Ù¼x¼(xëÜÒS¾²Æt³ v3M¡àm-E"ÔçÁg7W\{Û(@ø*^Ü0¢>3Yh|±¬ê1SdýøRcuÁØmÌI«x¦V.^æWP!ýnÀ,n#£{®-5>d\èXPBn'ý=Øþw@FÄfÞÍ`Ú¯.¸d#ý¡ÍdQùT iÞÜva:9BÌOíN^nï>¥¥Ì#ø-uYñ)*aÔǧ;ý¬PÕX%A*¾UWâQ·ïÀ![Öl1£{6Ò à¶²èO1µÅ5Þ×Ch¢!(Ô÷âÂ,UfÈFº,:esôðÙÎÆT½`IÔx>HBK-ÊÖé³2¸üæc7àTmÍ-ñÖyÂ&Fú±ÔnÞa[I#[s4î8ÉJ«*+ãªü|Ó¶Ú_M¼5óì;$hñJm,8÷j2Áé$åBºøa·ì!½<.»o#íD¶è[I£,Ô¯épeÂÏÄíð-»N+cÛá+Ò>¯Õ?½_r--åöåÔ#£âR<sÿ®>d
- A&L6yÝæXWÎÝ%Ò
-æ2¸ÿ7£P÷3m'+8Üï6¶BàÙØf8Bûj76×Ú]Ø Á¯Ô¥ cleføòjñö
¼l¸|Ö|í÷v'¯ìpÆ6taÌçÌËùÿmyª6+SaÒNzÒ¨ Òý9̦¬ñØËE6º63÷ ý[C>Ê¥fû<½Ûrª²1=¤óT§D at 4DÄ¥ÿf0#¥Aþ*pö$ê?~ÿ7ÚGa%½¥V©(r9,§(ï íkóÝ
%ëü×y|£í/¥»TñÕ\²:,üaGV)U%,bäÚ%Ë}yàÜH©åRñɱ9;J¤,~s¦ID"ö>ÿè®_',zi3©#¹÷Qûþßý±Éw×ÚUÛ¼é\póÚÌP`Æbf¦þͽÿäSG\/,ñèÇ7Û>=X\¼*ZX7)´á5)ªD2õ&1¦ûï7ÇkJQp¾»ÐæÞàzvnèÕÃÛ«×C¯Þõ¿¸È?»
+95 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 96 0 R>>stream
+xYXT׶>ÃÀ£±2+ú}Ùÿþ?ÈðýÀ/XÿÍ:G:MÚ0¨ï 3¬%[úú×OؤÚ.·½b§³ûl°ýàìÁþfH!Ú{hÜÐÿϽÎEÊË*Mv}ø»ÃÏØ2"û
+ÝHÇ_D«¨~V`F3ÐÐûèË*1å³ùx ^Ìl³:»AÆï¦÷@Ñ×\Yâ«àsÁOÏUÒ×ur_Yg%í«¿ËEñe~)dÙ-f~¹ATßá æGt¬bÓÕÙ +PÚ¯0.nÒ_½°iÚÔ9vÄlyM|¬ð
++-é÷Eÿ³v7Ýá£Í¢ò;b~ºÏV?ª-ò(u&
+âÀY%{0kJâ!viÔéÉoa-#2OvÕÍPÎépÍru<§6XGnxÊ¡ÈíÙ¦'%(¢7.Y é±eæ0Õ±%Q1ÑIÁ.m¾Gη:UÉ]ÓS`æÚ¢MÖÈâüªKÈû´Y ',6Ò.rXFNب=ª«j=BHÉá ¢¸ròÂù|§uNüWn"ÐìfÐzî9¨)±4À°³[èjFzõG[ÑsÒ¶Êúý":NÉ«Pãæ*ÑÛhÓ#1Âüë¬JILxYLmµ¡t÷ÇóZÞÅ'a
+À¾4¸õÉËÛªµR¡æB&®Höfã´ýÈͼÕv<÷¼Æ·¬oqÊ]4¡³5ÝF§,xj#åQoÁ"
+Ý ËnçÐ+Ùr¥S¹->M;ìíñ4ì=Ñ<½f~dÉì$9(vid©øa³±÷ðæ=è(:6í9D÷'ã·2]"¨)½QÖ
+¼ÑÆx»¶*ëPÉméh!zÀÖ=\w«ç¦a+ÜwñLOoCk,ÑZ00,3Ú>#+¡ôÖÇ·ï¡7á\\ÿ6s°aüÕLî./}*nìÇþAy´ 9C£Ñ´ASð<ô_ÃØî2»·[J>KÑGí_kO
+-Ç:ún [·ö.U*[7ÆVÅY(!Eä~À9/ý³ìñ§¢iïÉVÒ'µIçßcñëxà·s¹uI¦GA,NC`\htdRb`30Þ»O²øÚåÛ~Þ6}£ ©SºkM¿c!ËîÑ~$$fÜ1¯Þÿ¾Ò¹de\D0Q%15uÅÊfglÎazNÐM|ýåf[´íºZØí°Ð}í ÎoZ'< íFç»â<£tEs%¨¢¾xúíè¯ðßdøÅoÊÄK iÔJlÂ^¸dÍHú]aM*eQEwïKÐ4¾ÕHiöëÓÎQÃ.?´µJÜ1¬ËEàQð| »:cx@?É!«Be{yÐXn
+JtW4V&@Í,cª$¡(Úªóì«ý_!áSEÒüʼ¸øâ®ô EI
+»ÿY¶4:N¥Ip&Ô¸ÓTVUP_Tç¶Àw["'½,h,ûU÷N^
=*кÓw*TX×¹\k<f#ý E ùìtxZQ£/®åTÔîæ
+&G
z3:m mUóøÒ¥ëÍ Í²¦=Me¾^#W¦Ë!/L.É+Ï®(M®÷qUx¸sî
+îúh`&.Yò®«É»j,ig\+Ñ°/=ACíqÎÜd<|ø\.údà5Àlró+ÕªehRë¨$Øz¨¸ÎØÐrÂä¾6_ö¬+¼µ±Ë¶li8ûÓÃ)xûkA»èߺ¢Àz§£+zÏÀS±;)³Ó éÖ]ý¼úÌ¡´¤}%8QjF³¢.¡ÁUbä Ô7Ìø
+´*B¢<lþ×Ѹÿãä¦+µW.p§"6ÐÁÞ>Ëàa)é³DìÙnaE}_X_{±ØæÇh1ÿ ¸ø(WAôÒô2ÛõÚºJbx»³½\Ù"~Áïeë#¡AÑaáa¦psm±¾§
#ÎorõÉêðèãIg¼-W{ÓvO»Þ1áY.YVüÌ£í]áHÓhÓ8¿uÁuSI8Ä3§
+u|Á0Y¾òhòåxFú¸&mwZ1<ØOfÑ
3CæeÞÈ=¡º iÒä¥ê%#»Õågs Õëý/øÞ'ìõù·Èâéò[3
+²î¦üC3ÚÓÝë»/æ;øgl.ÿ:Wiø©Hûá
+óÛÖã$|%ÑzÍ_U\Táâ?¿æ7Uu7»m¤±üì|Õçêh<ê×·¾méÊöcú²ç?7 at 5UÇÒsa2ò@íu\·gÁøÈ.Hx>:öã
++!#7#¯ÇÖàÑu(¼Môâ¢íC%¬Fzȸr6/çüÑ6QØC* BI«¼Z(ì¡\'$ILNMÃÃq?[>þCùo ¶¨vÃ>Í>õKùN_øàúð¶õg¯%WÕjÏèPu:á*ïãBLHoÓM³0(K`ñ±ïwƽ«Ð:ø<éKãáÁdD?x$¥Þ+5Fû'º:\q¼ÿìêåkù2mIÄ_C·Áy²¾«Ú[ãLzïiàM6ÖDI~²nØvç»L×ýî³eGÌ
B¬£I¾£!\t±Â4¦8òIêô¤É8×v4ªWhö¾¦ä!ÍêHît7C:/)SkÓmÇ#Cz0yy}7<°Që+\
+TÈÿd¨Oà¡ËÊ,ú5Ù>ÃÍrá+»BÐwC¦[å;DÄ `w¬`ÿEâëª tÿͯê¾0Á1ÅQ ±æÁû&ë¶ëÛ¡®VÙHäèîglg{;0ÆsE_ÈNF¬§×F(äÊr¦I
+nñmöjÙR²
+ÙKâ«kËÕé
+Ûµ²ÆÃùõÀ>å5UæCʶ|³j±jUð|ÿMàʼý$âüÅCûÚ˹44E_èï²Õ£ñÈm·Ñì,N+Ñl>Ó½ÝÕý¥]÷·=2ÆËhþZe#þ.{6ò _\XxxiX¡(/ØHV«FÊÀ´
«VËA
+*&-Sû3ÔëöÏXoüy½AôÝi1ºÓX]|)¬ÑZ@+¥R¡uyìÛѧð4ÀÞçùàEx+(èî=ØG~îuén =Eß,TA¸~üñ{³èÈÈõ¡wBØIzpë®i©Ëûað2Éçh2âΧvH2åsÜ[9LºþèKZ«ãªýRÕ Qqh>
+ÌÃýoÏ~gË
+«exoS"íxZxmÂs/ÙöxOÒ\ůg«ãk|cw)F!fÔPQýÉùe=MÏHÏ{;Vºìo,«©æÐÆ×ÿÛ¤Õ^!¥fdêýýUÖmÈrÆ3ôΣyÏm¤BûÑìÓIjnM;9îyóL±uf©î3ìäÌ/6ÓúAôdõÚpWYð6U&U©NTMv1ÒN¹<J¹[ÍKÇà¡KB\Ýæñ5¦M°i
+3D&ºN~¼P¿ÝZuã
+£+ÿÄ
+Á|tDÎ|ütqiýD°ÔkMB\\lùÂèIðãBÇ¢ rË8éíÃöÈÒ"6óîkÔ~}Ñé÷µh6ʧKHñvàvÝÉ1b~ÚàxWwÒ3Y¼÷>6â·Ôe%¤¨Víô·BU¯c©ø^V]GßC6·|·ÍbïÛH"Û΢×f?ÅÔ6·?_m¢
+4b Pÿ˳Ti"é²1ËÀ¶g;Sõj)f¤&Èðar´Ô¢l>+Ë/i>ñªí9q%>:/ØÂH&Ú1Ò=ÒË'|;)dëÇÑ'=yCiUee|U@¢_ÚvûKc·f}¤-^
+²M7N'üLpúI¹.~DÅíûF ¯G+ØH;-ú¤QÐt´²MáoâvzKPåNSÉîܳ®ïÏÂðÌâpôgl@Ì¢ÇHüõ?È}Ò©ß`ñRÍÛb8ô¾Æ®à³â¸vøÏõOÕf®Ì6ÃVb·Äû;n'}ð¯Ã7+sªÑ÷Y5ZõHè ÛüHöPvWüÝæÅ4¶NIÐDµS&#½{#+Éww÷}¸»iWóP+B! ¨N*¯¨(Ù}fCÃDüÚf,â°äÞüÑÐuZÿèzÑEÄ|ÿͤÀÈ`¥÷u¡²`ÒëKrÏdgÝ M<%ò9X0 À2xËåÐ!8Ç¥áý9'2*îøéÊMG#QPõ¨#6Òëh&¿Ý¾[¹Jsµ]¹zaÓÂu^Ûý8ÓNöæÃÇà"óhê©É£Þv§sisæò#ªâéýµ!öëGþlÙÈêëÛ?5Éz·q±Æ¼ûQîlm|{ðûAµ±íÁÅó7Ͷ_øáíÓõ¾lïFIÓ£;üH%+!¯¥Äè
+Hã¤7¶¯wÒí ãäÔ#£S¼ðø®~¤
+$ A&L<uÃÖ87ÎÃ%Êæ3xàWcå¹Ö¶Óð.+¶3³üÇN{9x{B{
+.ßï÷:oüÑìä¥îÂØ£.ù¾ÙQ9ÿÿ3/ÕVe*lSÚIïP5AÚ¿Ù´u;㸨FwÃ`&à^s²sÈ÷/Q¹ÜÌâ endstream
endobj
-101 0 obj
-7650
+96 0 obj
+7536
endobj
-102 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 103 0 R>>stream
-xW TWÖ®¢é®·BÕ¸ï .1FÜ%*nÙ}ûÒ Í¾
-("´n\"ÍhÔ,ÆD'ë¨3Ñh¢£·Èc&ÿ+Ð'sþÿü§çÐÕ·ß»Ëw¿û]±µaXåÜÌñüºüï(i+½f#
-V-Ûè;Ñyvp°ó
-ùÎ+ü#üãüýº/wÙº=Ò?ÜyIx(Ã0cfnæ·Õ{x@ÄæÈÀíï-
-Ö. ñ]=ÁeòëoLyó-שӦ3ÌPf3Æx2îÌrÆÇd¼ùÌ{Ìf%³YżÉeV3of 3Ϭa2nÌTÆéËôcú3¯03È82NÌ«Ì f,ÍcË$3?°Ø¾6i6Oë'll#ï(o««ªÜ.;ÍçOöÐö(¶{Å®±ç¬WíçÚgÿ¨×^&ͽÞ}Ä>;ûöíÓ÷©§Ãoå:¬èbV³ÒúiBú.ÝÎ]fz,Éé|溰ìðì,Ø|¬FµÇp ¾Zoå¶?çÁ×Ѥ:O\4ª&Ã8Mðì¦ã9r['-J¨rèPU²3³¨@×#«öâáGßÛÍïz´Q;îüÔäK
-Wkø
-öÒ§ûnä¹Ú~áSÄ,ÔÿÙs²3¥ÍìA´Çyh§6c¡$RãõúäL1=fëüIÀ!ãÉÁ8ÐxF©×ëuzuvvRóÚýq»MK]'\þJâáNxÂ×~£pr#òF¦Qý±Æf¬µ²8{ãßð5Å¡9æiç!ÉÃi×îÞÿqêEÒ«PÃë÷^cü=׫dHVϬÅÜcÓÌ5î~n¤!7<>ö¨$ÌúÅÓ §vøÍæõôªm¤ ½BªÇ0MzH&×)ÃÈ-CóÖÊ°ê-s±A¯ËRÓâ ßØ»§¦¹¼µ%À2×
(ü /¹L~íð
-¯®A
õFr;X>ÞbÅã,GGfQt¬ÂZA«"c6Í$sä ÐrFu¿ÈwÚû´ªwSµãDO¬@N-úÇvýÛ¿d]¾~ïJ&íg¯cÄÓüë
-
ãÛNC9-¹xê/ÛBÔ
-uyìLJ>I|TYÌ=eU¦ºØ&Æ7*6Jôó+ô¦aï½ã\äk TÇÅÄn-oßuëÀcú4ñ¾þ2iõæÍË)N¼pÊ)Øãííç}ìÁ/0þ
ÔK%T1!Ci bVvJzfvXŦ|Ú!d,C±ÏöÆLu}¦9#§-ÿDy/®x&fTësâìH5á¸H/7 ¢ZÜSudóG°Naßt:¾Í½Kí»?0eÑ¢âeÅpÿô.Pïns
- xäÖª_P¬ÜÐÿ
eG¾?7D.ÛOªûé/ÿ/ýµtñgÅYÕì/ô®^Èhn%ÃÑK«Òö=:ßr4rC®G~u¹õôe±]»[¼>¯¥4ÊNMA-µýÃåQ`DBhÙÒèçèïßØrÈÜØ"¹de¥3/áì"H{áBÎHã¸n2ã_
-JK§2onÖú¥3ßÍÚ}JÄûñølS¹Q>«»×¬bÎtÎþÙÒ8rkÜ9Éâ·è¨èð£ó_£TÑ$UiQ"[pó§Q]5<ýð ü®R·È8éÎU~]àZHlhßÑxk|Ú:3ÑÓpoÇ@mª-ÎÊ:øÂVÐÁ
-z-÷æ#C¼]¦«¶)¶~_é`Uú®¸bÑ\XG9ÿ¢Åz#G^'ol
-ä£/~ó¡¥µZ½ 4?m*SÔÒ Q½8* 'ìiaklÐû¾æ£? ÓøÓNYÈl=÷t¦(¤@iºi¸¾´¸°à®\ïeYë!fB¸Ý¨ÓïIåõ\§ùJ7Êùw¦ù-ö¥0/IØ
-Ý[`ïù$¶#×ÎãoÍ+_ã`õ(þÿ¨iÙk'ÏP³}æeÛÿÕ{YÉ=yŲ¬òûø~¶pUUÕm«N¥îÓT¸ð=ì¹6&ÈW½ÓG8püä.ý®þÖÃMê`µ©A06O¬¡±ÕÛ1i¦Vö×Fé7Á¸ËSnÂ5´*+hUæ@D-ÜD=è²ÒÔäíÎd¬üѪ
-ÒM¤ËC0í2ºÉµîdýúVU¨n
QïÚMÓþÅçÇåÊmG[Ì_s$#ºujóã»ÞVÁÎ<cï õĹ÷ØlWà§8W¸GÚ'=Éfé¡m¥Â/QÎeøp0¤T écÇIÆLÉÁ"}Ù&ADi´)|w|`2|
-N=üÊXC×s e®T}L¥@RCB¢£·¦F-ÎmÏ3¯õ]·«Ü|5jB¢SÓ(Æ:ÅIS_¤éð!±ßø¥ýmp Q`Þ¤ã ù¬!Z»ßGm-,o}|Kh¯64T;éÑJºüLûîGÿH&Õwê?9ßñWÜÎ5±æ9}Í¡ø=
-ªÖ¦î_k--_@>ðwÁ#$$kEݶÀm:]²>Z¥ËÈtÈâãò¡Z|UªåU¿YÑ'¬c¥ÙªÝú¼ðX$$äAç2¥? *ÂÊÄóÒÎ~xõ4céb"
äß#½}qùgTÐS£&âR
°G=U£utWú¼¥±êäJ;¡$Y¯ÌTq]üû)^á>Åk+Ó
-Ùlà!5NMZ©rK©.2ä%eÍ·h²aÂÛn0ôÞ´¸ìã@u{éáæmõB¶Ä®ò+öÄîbtzÞè³#H£~áóGh··=`ÎÁðIî¹½ÕXÚ}Jy¿9».Ü?+>¶òõM{Ìg7~2ô%3g²ù@Ê1úÑyìeÒ3z¼µQDAøGYf¸
-
<ön?yæ!Ì5f{îdm;91^òêlî
E`+97 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 98 0 R>>stream
+xW TWÖ®¢é®¤b¬Æw&¨cÅEâÒ "ìû*[÷¥AU¡ºQIhF£f1&:I4Ñ£&þ&:z<f2¯ f<Éóÿç?u8®¾ýîöÝï~e¬e9W÷^Ó_ÿ/
+g¥W¤
+ _µèöPl¿:äã!hÞqú FÁ²n+¼]CwÅ
øïttrà8}ölÇùÁ~áÛ´!îÚÈ~ÁÚHú!ÈÑ3t[_dÜTÇùAAkä_D8®ñðöóísî¼+*Ò/ÜÑ=Ô×/<a§ù!ÛB}wùm_î±#2 ê¥Ñ1AÚå±Á>ë§8OmÆÌ×ßp5{ÃbV1f³qc3ãOf ó³YË,cÖ1¯3õÌ
+Æqg0
+ÌJÆÅØ1!ÌKÌËÀe1öó
+3@ËÂX3)Ì÷ìr¶ÑÊÁJoõTñâuë«Ê8åÕUêçÀmä:øu¼ÔoJ¿fù6×ú÷¿nfeûÓ+ø8êá<4B|u:ÞÂEäÁß
Cí±Fu8+§hTÍpágØO
+'sä¶ OÚ$Ie×dcbQ,nAV!ÄsÂ>ï+W÷oz´R¹»î|Õä37
+WgøҧϷpÈ\ïºt¤&bHþlÀÙI9Ò°Vö(Úâb´QH;°P
+i z}J»kÉ4àÉHÀÉ'Îåâ0f<§ËÒëõ:½:''9ÂymKüþ£e§¾!\ÁZâ¶ð#¯þ0
+Çãô&ä¢Ý¯V¯¥2ÔÏ+&¶ú)E[
Ô¡öL'.3G¡dؽ)8]n?Ä!"1Õ
+ØïSð^U«ºöø}ø+"ðfU¦){þ¡yÚso¤¦[©¢¡_JbúfæÉÇrؾ%8ûI©ñÇûâ÷ï7ímë\Ðû+Ë°³PA[.ùò³ÎÖbuce}þ^CNåd>º<öÀòªú¸fÆ':.Zô©÷-ò¢iïüeQP±9@·üAS·½&!"Ó=Öð-Á8çæû×î¬n/½ëÃÊ;[!+WcL9
+UP[QRÆcÿa&\>y.ß¾
+n7ÛLõYâ-|ñYòú;VS,Ùû=zé"TP(±]ÇCìÑO1áS
4@'T11SébvNjFVNhåö:Ád"¡£pPTSº!ËÇÓÿ3D{¨|*fVësãH5á¸XȨ0 ²Z<PubÇû°ÎààRtè3ÅìSû´¬-^^²ª:øîB
+*
+
ÄÜäB(¾òêÔÏOèÿB~ã6.Ûö£êÅyúëÿü~eͽ¼ÄYp^5û3õ5À¬@íc,dzjUz'o'§~ç
+ÒÄü&òó«íg¯]ÚõÜÒà UðIe7vVªj©ó'#Ï#ºBÛÎ&_ +¼
&ü\Z\Rt,\.¶CxB¤ì©´>gwvf¶N§&ËÈ8¥¤áþ.¹W
+pù7p½ÂUVÕîÃÄÙÞ¸¬çÓb IÔpõ©a'Y+¥2/(Ë}î[`ïù&Öã6-Ln-®X`½{´7ÿìÓÔFÍý¯lû¿p/*¹'O°DU¾+ÑM 0
+ɽP»Éaú?ùø<yìè*q¤µG×mABïÛ*Øo¬âí¤þ¸ð[]
+ü
+÷H״ߤGIzhbÛ©ðK¥s9~.ÜÌ$©$
+HÆÄI at R0Ss1ñ¡H_v
+QS¾?¡0>K
§Ë~n¬¥·å.T}Ì@Rcbv¥E+Ýq¤Ëñ&]O0Ú{$LôÒ¼oµ¥¨¢ ñm!>Úí´Gkq ÎþúûGïÿ@¦5w>¼+qry®|éÊu¦Â÷&Q`¬Ä÷
+_ßA>>¦ c}·ËúOïìRJ»cnVÍFÎblÑÆÚ¥Ú¶_K-²u¶rmíæßE1Ø
endstream
endobj
-103 0 obj
-4070
+98 0 obj
+3974
endobj
-104 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 105 0 R>>stream
-xµV{PSW¿×@îUQ[ÝÛjËÞÛ£VTt«XmpD°¢´¨¼%òB*Ï@ò% ¯<xðQ*(VQQ¶µ¶Çmݾ·îZÛµÝÐãL÷ÄGÛÝÙþ³3;drgÎ9ßw~ïwiÊe
-EÓ´Ø?8Ô{¹óß|dz´Ã}ã÷"À±'£\ÁMn.Gܧ·ÏFéO¢³Ð'(MoܲË_&ÏV$%$*=ø/ôð^½zoªD´'Vê«L¤Æ*ÉCÇvÙ$2{oJG¨sGºG¨$]¢ÈÄ?(í/K«
G°,^¢Rõ¯T&Q$¤+TûRb³R/Yæ½|ý*úµzÚHm§6Q;¨0*z
-¢)?j'BÍ fQOR³©9'5zÜb( gÒJúý)kD¢+.Oº$¸»¦»ö}9Ϻ±^ìöÇ©)Ó^Vý½1ùÓ[.ØQݱÁJ;âJj+Ô m©&Kîÿm®:N¶EÇJK8mZÖÎÄê6òTcí 7Åç°·ñ-a¾¿z~l¬.<Ç~cåNP®ù}ÛDå79[éÎ Ô9!rD£dqïa»-öijñï¾yÑ´;_£Ù<NÆQÜ^jÉtáôÀ¥®ã'@/Ënï Ø!Û%g>/RÉ-ÈÿA¡Â1cÊå`ù6ì6Å+ÇOà¹ÿXDhú-FoáLÈû3sü-Á±6vî2ØèKWÿÕÞ!Ò1ÚÌLé`¿xûí
=ûº
Þ¾`rçW_¢É<V]} Ád66Õ¯ÇïËUó©Íµ"ïM¯Ù[] ÈÍB<l;¢0i"ßL×Ù·CÑ,4ýîÙZ.níâÃÛvÂEmɯ.°
-+«ëØ»ùÜ
-ø¶ÙýíÅÞYmö3¯Ì¸ýr!Kl;FÜ~CLÞ6¡©®
-Þör
?¾º.b©ó>T¾Õ±ÒL@Ù"ô¡c
-gc~aª OiM©Ø,°yG$ö$
-ݲÃEçòjK[3͹µ*±1Ý+6ïîùFÅA¯ÓkËK Ø<ÐdØÉMµQ¯·5òÆJ0Ô7ôDC#°ß¹9Ü]P%$tÉ+6Lð«Q¶Ë6ø¢+¤x}a%[F³ðv"ÞgìÈÇJßGîM"´Ö1CvìTbWÂËJÙÌqE×g/{¿ ÚÁøKcãà«F2ë¾S<lsFôeòsÜçÚ2eiªyjKVK¹Ç<á¤Ìªb¢Ö_r¸ªFàð{óÐÛÅgû{]pøqÀ¬Ú°~µæäYý
ÁN
-N@ér/b :[Òú¼E@éÃv{sC0H¨vµ3
Cr8¸\t¶.îFé}Dú"t9Ð@eM¦«£'ô6=i,S©á 4Ö¨;X-£¹ÓÉBrH&ëCëßÖ&GS±×\¶¼ø¥ôå
ùoDEçÅ8S¨¤Ðq2p
-pÚNzYI=ÔAC¥©º¶Þv¨¿¥«#±=¡=¡QE,¹sWÁ´¤F»^lCz7áD´ô˧D'cÿ#~| vkN¼úFXSÐÿ-þÐ"zÂa6´øÕß*B¯pc½ñÒ,Uܢ꬯©4VðeezâÖ-BÉ`q°¸½ùÒ¶´y?wYÄ,»AÞØ/2[[}wGEV ])oygxpØÏlXºroèGá×tíOF§u§[Î_z¸ Ñã¨nÑÄÀï;"¹&^É'wOWÉ}ny xâK¯ïFß<Ú"T½v:³ØOkï"
-c_4{á+99 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 100 0 R>>stream
+xµVyPgïv`ºUÔD·LÜnؤ\5^Äc5*`"`DIðB@ÆaaÖaf£3 ×\\3J8<P<ð>X5&Ëæ>L5Ù|Ã~Ve{<²ÙÍ?[µÕÕ]ÝUïûÞû~ï÷{¿&IÂÈØø°9þ·É¾Iߤ¾ß+ ì4ú£ñ(÷Y´|ù! Éå«6DJeùòqº"dJäÔ°æ
gä[S$!±)tQvÿÈ
+Y+Ý!RäÏ ÏÊ
+÷¯È
+åä*QÚ£ÔÒlR!ÄJÓDr AÏ
K¤"¹8W¡Ü¢Î>sö¥sçÄÕÄÄrb-±XG$ÑÄ[DKDë8b1x@ÏÁÄóüé+Ú ®?ê^a¬îä
S\(áÔ'.dv®¡ÈkÝý|Åh%s$«ªþâÊÕº®í\wÏ°Ànÿm,*5ÖTí¬·Ù-n»ÆþvÚöB
+Ý^ÃC¶"jqüþmUÅÜÎÂ| ¤Áýraó7ámzùÝx4¾òæs«ÛÙÄõð:ÄA ä;ªàkCU-}¿y¾o´CoC+çð¶U¹þ&+ò!uSK²T@·bļÑÆfîîjáܵð.ÐwP ¼öæM³ü§,O9}síäÞÃ(ÿ°+n(N^Ú{ñ=ör)II¯X~qýä¡{Ð{$"/ Pï!Ó¢jæ(U²ì&uS»½ÅÍbÇ1êr¥gJó¿rWU5
}t^jéæ|Ôcmó(¾ØóTÛè/yÊ´(jnÒ²¥GN²è#
+ú98es8<X·e6åôî<ÎÒû¼ÞÆú.àIB!®*ÐKEé*ú9äó' 8ç/]Ør{xêÐ^dg ¼Äå°ëÒÍvíü¡rW9_X1²!Aaæ'ZiO1Ðyyù&¿ÇúTæ~QÂCׯÏ×Æ_GÁóËù;uùô
S2±NU¡
+¯1¨øàfs?/`þ`d´~ðp`ÏcxG3¿¬ÿQ²c¿JödD9ÑGóíóNòÐu:8äøCNg=[ºÓ;j·+ÐÛ¥ÌEUwDÌ9muÕ+»kW9¯ÚêÕ[6s¥e&#ÒÁR9tQO¥üô+\§g7«[Ý.û{*çðN²wÙx{\ÞgàáCñPêH÷XË$ÛæKcb0"¢sY÷k¹'àS8vÂ{¶óó·pÆÉø³¢íjDjÏÃ?roÃús
+W·û[zVáx½&A¬%Ú85£N'þ×Ñ?î¢@/ÙeÞøbÐ"F),Û¡[WTPRÏëƱÂÖÆ[Ðtrúܧ5û&ïátÔ×Á.WK]GíÁTÆ:®ô
+ýñe³æn_Åá$¬
+Ìõþ2lñËm´ÓânD¢ÚADò¢{ß·qÊlpãPl¥èfÏàû/güp¾ïÎ&®òcy]@ZsHÃÑ<Gá)8oÂkÐ$¼%v\4;nqûl(p?ÔÑ6=°za!OÛB]XòHþ<+0æ (´
¢Éh`ÓýÌYwÚ¸è>Ô`Ò($[rR_ó9âËÙÏo¼sD¹¾«U ÔOðSµú^v~u»·ì½Jox3ýAV4®g ³Sí`w UY§nin¨óÿë Æa
+ý_Cg~þÇ9¥E¯21}ºØUÜRÔïÒ·:ïqÊ»-£ßë!³Ij|£xÇÖ§MT¹|ï^ßëmßAé0É4b=
DZ¦ÔA+
YxS [¸Ë^y±¦Úfý6Ò
¡xº,m2øÍZØÚ{ endstream
endobj
-105 0 obj
-2508
+100 0 obj
+2570
endobj
-106 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 107 0 R>>stream
+101 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 102 0 R>>stream
xVyTSW@ÞS,m_ÅѾdf\kEC«¥XZDÔº ¨´¾çáÕ/#öñ>ìýÀ§Eì#V¿f⿱¾lÓ)ÃSESg?¢Ü¡üyÚB¥9
=r¯çÔè+¡ôå
@@ -1069,11 +1045,11 @@
{5YÏ é£ÍÈ,BU(ëÃæ8Æ/Ïî^cÇM6¤¨ãøjÆ9ÕåËOõÖ*¦M±6ÍU3í1ú/S\!
endstream
endobj
-107 0 obj
+102 0 obj
2471
endobj
-108 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 109 0 R>>stream
+103 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 104 0 R>>stream
x
VXTÕ>Ã0çPF at mÌÄG*^³¾®/@ !äé(3³Î *¢"£òP b©yíVZ~Y}½LËËÍ»mú¼û@jfÝûíoæ̳Î^{õÿÿZ
ÆÕ
Q(ÜÔ9AáãäþÒ
4ØEzR $t°«ÐµXîJpwmì1ÓÍýq^_×Q*3BMMJÎL5|Oá;nâÄ ¾¯$R㢣}çD(ý³ÒwaRtÁ9Æ÷+}Èo¬ò]`XeHM7Äô:f2¤úÎI1¤&23øÄ褹ÉÔUÆ3Mqifů^æçþÃaæ1ÓWéÌXf>3YÈ,bf1Ï1ÃÅL0ÊgF2!L83 b0sç&Ä<ICf8&¹©§Øë2Ò¥C9GyÄu ë)ÕDÕ×ì&ön$·»Êá÷èSìæçÖþØòÇ~qv¿ü¸æ®bÝbÆã®ÒÌÃD)¸RÑܵ[)%vjrv,HÜDèþÑ;ïMsîdàÓõìá¨ÂÛ°ßÌ%Ê{ èÚñq:Ø3dêjf¿Ð_A#5Éï¥x
²¤NE2X».Ù_Énouz"KW$²^êÃEÛ#± N[öXyq8T\mûG#}²TÏxÔ ûyñAÿQ¡ÂS´êKÈpdJ®JÏ-9ËyÜUÜÌä5I}ð ÂþJìFi2×XÒaÿZcÆîºzGËUÂÎ'3_!áÏ÷ÏàüÛ^äÊ×ÂúL«%;_²,#x¢ô?ò6à µ9dJ÷Bo2]Pý @£!ÊñK`¤W¹f?2üüWOqã¯Â5÷QXXAR¨7u>t÷ÀNÀóyÞ¿JÜÜ¥ÐÍV6þ`l}ddl|-¢m½öx7c_%Xà³ì6{%LÒ´#ïéckcc«öþ,ß)~êNy$ÁÙèõ,¿ìðc'þÛYFùàQH£82-Ïj¥L´øVÝ}ûòßÖ:ò³«µYyÉYÍÇÔ¦8÷×Ö4íq!ýÈÐÒGë?EtÊ$ê¡ 7(®È®\=6iv}4]A[Ia1F8X²¼Ý}Lëæ:¡Ò½OS»;²{ÜÜ=æ¿[ôý
endstream
endobj
-109 0 obj
+104 0 obj
2974
endobj
-110 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 111 0 R>>stream
+105 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 106 0 R>>stream
xcd`ab`dddwö
44 endstream
endobj
-111 0 obj
+106 0 obj
237
endobj
-112 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 113 0 R>>stream
+107 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 108 0 R>>stream
xYXT׶>ÃÀcWðPï9¤Xb=&cA±¢ H:ÌÐÛÌ:Òa:E]±bÔ$c}1ÑÅhcbî:¹÷½·Afrã-|óÏÞ{õ¯õÿk#¢,-(H$Yìæî>}á×qÂh0ÆBø8Ç
ûþpµBÅh°eókaÐÓSbÈyÅæÅ¡»c¥þ¾~¿í0}Þ¼w}¤þ;¼Bܼ"ü|½"È ÈamèØ©NAAko;¬ñ ÷Fùì4î½84xwdÔÁ-t§4¢¨e+bCv¸-
ݹqåâÝ>- Ûµj©Ôwµs¸ß#ü×.pw
@@ -1156,11 +1132,11 @@
<@7x°~ðú+´?
endstream
endobj
-113 0 obj
+108 0 obj
6149
endobj
-114 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 115 0 R>>stream
+109 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 110 0 R>>stream
xWTSWº>áHÎQµÞ3Þp¨CÕj¡/Tª¥Úµ<ä ïWx¿ðÂÂ#b¨UEKm©vlít°h{.ãmµ3ãc»Õ»TÚÕÛéÜ»Ö]µVöÞÿ¿¿ÿûþÿ;"j%ìý¶l÷¾,³ýFd{ÂÎöï4àÈ©ÛS;ìÁ9Ö'êCºEhÛäµ¢E¢Moú¥¤*Òââ3]Üý<\ññyÎe½\ì²%23^*Ì$ÿÈ\R¢¤.ëe2íÂíÒiz¶4Fì"OÍʦ»lI¦'SåªHNIn}}cz\F|fÖì¤Yd®<Ê멾µÚF½B½Nm¤Þ 6Q+©W©g¨eTåOí ^£vRL¡B¨@jµ
Ú@½Im¥ü(GÊZ@¹Prj)¹<ÅP
"G\tÑn»Ýgôô>ÏIsÕ~x±ø4óSÆ\fìæ®{xÞùôüõóßs¨v|ÉqØñ¾£ÓJ'ëÀïDQÓwv+ߧðíSÈðgÚ¶
}Áõ¾7Ô`©a-L¼J
@@ -1191,34 +1167,34 @@
b©g,ó&çóóæ<×é0פuplup¤¨ÿqfù7
endstream
endobj
-115 0 obj
+110 0 obj
3328
endobj
-116 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 117 0 R>>stream
+111 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 112 0 R>>stream
x5P[HSa
+68«x³Á&No¨æîC\¾ÉÄé¶NÇ;x»7nõjf[·sÅV#o· ~!^ORZ -,J6RAøN÷Ó·©Ø bËòö98Ãh¡
rJÿ²¾¢ï, ÄPçORA%kò¶zoa]ãÍæ<Xi\"¼÷-EC¡Ð³qøFÉX6waEVNféóæ®'=£®²ñ§o!½6y<ïäåSEJìÅf¯×ç¦âùß^×èkD!ek/ÃÄÊ/äA$ÆТbñ.K
/ØTw§®(
öGAØG;56óuCèQ*ÊX]YÕÄ !öS0
éùÌiÌà¸ìò°Çìë¬í±µ³c/¾Á{:rÏîkVº}¶¶&èõ5ú`}£v³ÿ_EÉXê'D°@3(W!à8ñvw[«§Å?6ÊdF=;Ö£f-F¹ÿÖòÀåV¿,Þ+ç©\q¯oâ~*ÑÕ»~¢"k§º(!~!ªýLÜPÃ,t3Û endstream
endobj
-117 0 obj
+112 0 obj
656
endobj
-118 0 obj
-<</Subtype/Type1C/Filter/FlateDecode/Length 119 0 R>>stream
+113 0 obj
+<</Subtype/Type1C/Filter/FlateDecode/Length 114 0 R>>stream
x5_Hq IÑßé¹ÑITÂút±È{:Pò18QJ[o;É3D:|0ªþ׶×c<6³qÁòUVyË{®ÞÛÜrz )Ê×báÐÈüôÜ;øDióbª¨Ôi?oíêêæ{n
/RÃ>ôF ¹ò2'[kÌ»fQ-b· rÝ#häqîGi!D¬N
a«ý£Aô]}UDJÃÕóeþi³:âó 9RÛgcÙ¥GEH½[DEIµ6>þ¾%4Ó".âYE§¬½7úBCî ÍG
'æüc|1ãÒ=klêJåJÚ[ÛI®kôÜÝÿ/àQ*/\Úz4Å
AtRŸ`"°ÓÕpz¼ømãÌ"zÂ
²)»E>éõñpÎrÉÛlxÿ©ú+#TmHÄn"É Qu\4û >aa' Õ;Bw
GD endstream
endobj
-119 0 obj
+114 0 obj
655
endobj
15 0 obj
-<</BaseFont/ICLHMS+CMR10/FontDescriptor 14 0 R/Type/Font
-/FirstChar 11/LastChar 122/Widths[ 583 556 0 833 833
+<</BaseFont/MOMLGJ+CMR10/FontDescriptor 14 0 R/Type/Font
+/FirstChar 11/LastChar 122/Widths[ 583 556 0 833 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 500 0 0 0 0 278 389 389 0 0 278 333 278 500
500 500 500 500 500 500 500 500 500 500 278 0 0 0 0 472
@@ -1226,30 +1202,30 @@
681 778 736 556 722 750 750 1028 0 750 611 0 500 0 0 0
0 500 556 444 556 444 306 500 556 278 306 528 278 833 556 500
556 528 392 394 389 556 528 722 528 528 444]
-/Encoding 120 0 R/Subtype/Type1>>
+/Encoding 115 0 R/Subtype/Type1>>
endobj
-120 0 obj
+115 0 obj
<</Type/Encoding/BaseEncoding/WinAnsiEncoding/Differences[
11/ff/fi
-14/ffi/ffl
+14/ffi
34/quotedblright
39/quoteright
92/quotedblleft]>>
endobj
13 0 obj
-<</BaseFont/UZYZOC+CMBX12/FontDescriptor 12 0 R/Type/Font
+<</BaseFont/KSKQOQ+CMBX12/FontDescriptor 12 0 R/Type/Font
/FirstChar 45/LastChar 120/Widths[ 375 0 0
563 563 563 563 563 563 563 563 563 563 313 0 0 0 0 0
-0 850 800 0 0 738 0 0 0 419 0 881 0 0 0 0
+0 850 0 0 0 738 0 0 0 419 0 881 0 0 0 0
0 0 0 0 782 0 0 1162 0 0 0 0 0 0 0 0
0 547 625 500 625 513 344 563 625 313 0 594 313 938 625 563
625 0 459 444 438 625 594 813 594]
/Encoding/WinAnsiEncoding/Subtype/Type1>>
endobj
11 0 obj
-<</BaseFont/ITYNZM+CMR12/FontDescriptor 10 0 R/Type/Font
+<</BaseFont/AKZNKH+CMR12/FontDescriptor 10 0 R/Type/Font
/FirstChar 44/LastChar 120/Widths[ 272 0 272 0
-490 490 490 0 0 0 0 490 0 0 0 0 0 0 0 0
+490 0 490 0 0 0 490 490 0 0 0 0 0 0 0 0
762 734 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 490 0 0 0 435 0 490 0 272 0 517 272 816 544 490
@@ -1264,9 +1240,9 @@
628 0 0 511 668 693 0 0 0 0 0 0 0 0 0 0
0 459 511 0 511 406 0 0 0 250 0 0 250 772 0 459
511 0 354 359 354 511 485]
-/Encoding 121 0 R/Subtype/Type1>>
+/Encoding 116 0 R/Subtype/Type1>>
endobj
-121 0 obj
+116 0 obj
<</Type/Encoding/BaseEncoding/WinAnsiEncoding/Differences[
39/quoteright]>>
endobj
@@ -1280,18 +1256,18 @@
0 0 863 0 800 0 0 1189 0 0 0 0 0 0 0 0
0 559 0 511 639 527 0 0 639 319 0 607 319 958 639 575
639 0 474 454 447 639 0 831 607]
-/Encoding 122 0 R/Subtype/Type1>>
+/Encoding 117 0 R/Subtype/Type1>>
endobj
-122 0 obj
+117 0 obj
<</Type/Encoding/BaseEncoding/WinAnsiEncoding/Differences[
12/fi]>>
endobj
35 0 obj
<</BaseFont/WNYVFJ+CMSY10/FontDescriptor 34 0 R/Type/Font
/FirstChar 15/LastChar 15/Widths[ 500]
-/Encoding 123 0 R/Subtype/Type1>>
+/Encoding 118 0 R/Subtype/Type1>>
endobj
-123 0 obj
+118 0 obj
<</Type/Encoding/BaseEncoding/WinAnsiEncoding/Differences[
15/bullet]>>
endobj
@@ -1315,9 +1291,9 @@
723 0 0 0 767 0 0 0 0 0 0 0 0 0 0 0
0 531 590 472 590 472 325 531 590 295 0 561 295 885 590 531
590 0 414 419 413 590 561 767 561 561]
-/Encoding 124 0 R/Subtype/Type1>>
+/Encoding 119 0 R/Subtype/Type1>>
endobj
-124 0 obj
+119 0 obj
<</Type/Encoding/BaseEncoding/WinAnsiEncoding/Differences[
12/fi]>>
endobj
@@ -1332,34 +1308,34 @@
/Encoding/WinAnsiEncoding/Subtype/Type1>>
endobj
14 0 obj
-<</Type/FontDescriptor/FontName/ICLHMS+CMR10/FontBBox[-40 -250 1009 750]/Flags 4
+<</Type/FontDescriptor/FontName/MOMLGJ+CMR10/FontBBox[-40 -250 1009 750]/Flags 4
/Ascent 750
/CapHeight 750
/Descent -250
/ItalicAngle 0
/StemV 151
/MissingWidth 333
-/CharSet(/colon/L/slash/A/y/ffl/n/ff/c/B/z/zero/o/d/Y/N/one/C/p/e/Z/quoteright/O/D/quotedblleft/two/q/f/parenleft/P/E/three/r/g/parenright/question/Q/F/four/s/h/R/five/G/quotedblright/t/i/S/H/fi/six/u/seven/j/comma/T/I/v/k/hyphen/U/J/eight/w/l/a/period/V/K/nine/x/m/b/ffi/W)/FontFile3 100 0 R>>
+/CharSet(/colon/L/slash/A/y/n/ff/c/B/z/zero/o/d/Y/N/one/C/p/e/Z/quoteright/O/D/quotedblleft/two/q/f/parenleft/P/E/three/r/g/parenright/question/Q/F/four/s/h/R/five/G/quotedblright/t/i/S/H/fi/six/u/seven/j/comma/T/I/v/k/hyphen/U/J/eight/w/l/a/period/V/K/nine/x/m/b/ffi/W)/FontFile3 95 0 R>>
endobj
12 0 obj
-<</Type/FontDescriptor/FontName/UZYZOC+CMBX12/FontBBox[0 -201 1139 700]/Flags 4
+<</Type/FontDescriptor/FontName/KSKQOQ+CMBX12/FontBBox[0 -201 1139 700]/Flags 4
/Ascent 700
/CapHeight 700
/Descent -201
/ItalicAngle 0
/StemV 170
/MissingWidth 375
-/CharSet(/colon/A/n/c/B/zero/o/one/d/p/two/e/three/f/E/r/g/four/s/h/five/t/i/six/u/seven/T/I/v/k/hyphen/eight/w/l/a/nine/K/x/m/b/W)/FontFile3 102 0 R>>
+/CharSet(/colon/A/n/c/zero/o/one/d/p/two/e/three/f/E/r/g/four/s/h/five/t/i/six/u/seven/T/I/v/k/hyphen/eight/w/l/a/nine/K/x/m/b/W)/FontFile3 97 0 R>>
endobj
10 0 obj
-<</Type/FontDescriptor/FontName/ITYNZM+CMR12/FontBBox[0 -205 793 714]/Flags 4
+<</Type/FontDescriptor/FontName/AKZNKH+CMR12/FontBBox[0 -205 793 714]/Flags 4
/Ascent 714
/CapHeight 714
/Descent -205
/ItalicAngle 0
/StemV 118
/MissingWidth 326
-/CharSet(/A/n/zero/o/one/e/two/r/g/s/at/t/i/u/seven/comma/k/l/a/period/x/m)/FontFile3 104 0 R>>
+/CharSet(/A/n/zero/o/e/two/r/g/s/at/t/i/six/u/seven/comma/k/l/a/period/x/m)/FontFile3 99 0 R>>
endobj
8 0 obj
<</Type/FontDescriptor/FontName/GRQREI+CMR17/FontBBox[0 -195 744 707]/Flags 4
@@ -1369,7 +1345,7 @@
/ItalicAngle 0
/StemV 111
/MissingWidth 301
-/CharSet(/A/o/d/p/e/quoteright/P/r/s/G/t/i/S/u/T/v/hyphen/U/J/l/a/m/b)/FontFile3 106 0 R>>
+/CharSet(/A/o/d/p/e/quoteright/P/r/s/G/t/i/S/u/T/v/hyphen/U/J/l/a/m/b)/FontFile3 101 0 R>>
endobj
71 0 obj
<</Type/FontDescriptor/FontName/FVSMAJ+CMBX10/FontBBox[0 -194 1164 705]/Flags 4
@@ -1379,7 +1355,7 @@
/ItalicAngle 0
/StemV 174
/MissingWidth 383
-/CharSet(/A/n/c/o/d/N/p/e/r/s/h/R/G/t/i/fi/u/T/exclam/I/k/w/l/a/x/colon/m/W)/FontFile3 108 0 R>>
+/CharSet(/A/n/c/o/d/N/p/e/r/s/h/R/G/t/i/fi/u/T/exclam/I/k/w/l/a/x/colon/m/W)/FontFile3 103 0 R>>
endobj
34 0 obj
<</Type/FontDescriptor/FontName/WNYVFJ+CMSY10/FontBBox[0 0 443 444]/Flags 4
@@ -1388,7 +1364,7 @@
/Descent 0
/ItalicAngle 0
/StemV 66
-/CharSet(/bullet)/FontFile3 110 0 R>>
+/CharSet(/bullet)/FontFile3 105 0 R>>
endobj
27 0 obj
<</Type/FontDescriptor/FontName/BNGLQK+CMTT10/FontBBox[-4 -229 537 694]/Flags 5
@@ -1400,7 +1376,7 @@
/AvgWidth 525
/MaxWidth 525
/MissingWidth 525
-/CharSet(/colon/L/A/underscore/y/n/c/semicolon/M/zero/B/braceleft/o/d/Y/N/one/C/p/e/O/two/D/equal/braceright/q/f/parenleft/P/E/three/r/g/parenright/Q/F/four/s/five/h/asterisk/R/G/t/i/S/H/six/u/j/comma/T/exclam/seven/I/v/k/hyphen/U/quotedbl/eight/w/l/a/period/V/nine/x/m/b/slash/W)/FontFile3 112 0 R>>
+/CharSet(/colon/L/A/underscore/y/n/c/semicolon/M/zero/B/braceleft/o/d/Y/N/one/C/p/e/O/two/D/equal/braceright/q/f/parenleft/P/E/three/r/g/parenright/Q/F/four/s/five/h/asterisk/R/G/t/i/S/H/six/u/j/comma/T/exclam/seven/I/v/k/hyphen/U/quotedbl/eight/w/l/a/period/V/nine/x/m/b/slash/W)/FontFile3 107 0 R>>
endobj
20 0 obj
<</Type/FontDescriptor/FontName/IRVDMF+CMR8/FontBBox[0 -205 857 704]/Flags 4
@@ -1410,7 +1386,7 @@
/ItalicAngle 0
/StemV 128
/MissingWidth 354
-/CharSet(/y/n/c/o/d/p/e/O/f/P/E/r/g/question/s/h/t/i/H/fi/u/comma/T/v/k/hyphen/w/l/a/period/x/m/b)/FontFile3 114 0 R>>
+/CharSet(/y/n/c/o/d/p/e/O/f/P/E/r/g/question/s/h/t/i/H/fi/u/comma/T/v/k/hyphen/w/l/a/period/x/m/b)/FontFile3 109 0 R>>
endobj
18 0 obj
<</Type/FontDescriptor/FontName/WTBOIB+CMR6/FontBBox[0 -21 564 675]/Flags 4
@@ -1420,7 +1396,7 @@
/ItalicAngle 0
/StemV 84
/MissingWidth 416
-/CharSet(/one/two/three/four)/FontFile3 116 0 R>>
+/CharSet(/one/two/three/four)/FontFile3 111 0 R>>
endobj
16 0 obj
<</Type/FontDescriptor/FontName/WTBOIB+CMR7/FontBBox[0 -20 529 674]/Flags 4
@@ -1430,143 +1406,138 @@
/ItalicAngle 0
/StemV 79
/MissingWidth 384
-/CharSet(/one/two/three/four)/FontFile3 118 0 R>>
+/CharSet(/one/two/three/four)/FontFile3 113 0 R>>
endobj
2 0 obj
<</Producer(ESP Ghostscript 815.04)
-/CreationDate(D:20070811165352)
-/ModDate(D:20070811165352)>>endobj
+/CreationDate(D:20070826231432)
+/ModDate(D:20070826231432)>>endobj
xref
-0 125
+0 120
0000000000 65535 f
-0000061343 00000 n
-0000101592 00000 n
-0000061185 00000 n
-0000058685 00000 n
+0000058425 00000 n
+0000098418 00000 n
+0000058274 00000 n
+0000055936 00000 n
0000000015 00000 n
-0000004972 00000 n
-0000061391 00000 n
-0000099596 00000 n
-0000096006 00000 n
-0000099321 00000 n
-0000095657 00000 n
-0000098988 00000 n
-0000095270 00000 n
-0000098511 00000 n
-0000094606 00000 n
-0000101367 00000 n
-0000098346 00000 n
-0000101142 00000 n
-0000098181 00000 n
-0000100845 00000 n
-0000097668 00000 n
-0000061432 00000 n
-0000061462 00000 n
-0000058853 00000 n
-0000004992 00000 n
-0000009777 00000 n
-0000100336 00000 n
-0000097186 00000 n
-0000061558 00000 n
-0000061588 00000 n
-0000059015 00000 n
-0000009798 00000 n
-0000015038 00000 n
-0000100143 00000 n
-0000096950 00000 n
-0000061642 00000 n
-0000061672 00000 n
-0000059185 00000 n
-0000015059 00000 n
-0000019954 00000 n
-0000061770 00000 n
-0000061800 00000 n
-0000059355 00000 n
-0000019975 00000 n
-0000024247 00000 n
-0000061843 00000 n
-0000061873 00000 n
-0000059525 00000 n
-0000024268 00000 n
-0000027920 00000 n
-0000061938 00000 n
-0000061968 00000 n
-0000059687 00000 n
-0000027941 00000 n
-0000032886 00000 n
-0000062033 00000 n
-0000062063 00000 n
-0000059857 00000 n
-0000032907 00000 n
-0000036515 00000 n
-0000062106 00000 n
-0000062136 00000 n
-0000060019 00000 n
-0000036536 00000 n
-0000041310 00000 n
-0000062201 00000 n
-0000062231 00000 n
-0000060181 00000 n
-0000041331 00000 n
-0000045463 00000 n
-0000099865 00000 n
-0000096446 00000 n
-0000062285 00000 n
-0000062315 00000 n
-0000060343 00000 n
-0000045484 00000 n
-0000049724 00000 n
-0000062380 00000 n
-0000062410 00000 n
-0000060513 00000 n
-0000049745 00000 n
-0000052055 00000 n
-0000062508 00000 n
-0000062538 00000 n
-0000060683 00000 n
-0000052076 00000 n
-0000053893 00000 n
-0000062581 00000 n
-0000062611 00000 n
-0000060853 00000 n
-0000053914 00000 n
-0000057411 00000 n
-0000062654 00000 n
-0000062684 00000 n
-0000061023 00000 n
+0000004971 00000 n
+0000058473 00000 n
+0000096422 00000 n
+0000092841 00000 n
+0000096148 00000 n
+0000092492 00000 n
+0000095818 00000 n
+0000092107 00000 n
+0000095346 00000 n
+0000091449 00000 n
+0000098193 00000 n
+0000095181 00000 n
+0000097968 00000 n
+0000095016 00000 n
+0000097671 00000 n
+0000094503 00000 n
+0000058514 00000 n
+0000058544 00000 n
+0000056104 00000 n
+0000004991 00000 n
+0000009776 00000 n
+0000097162 00000 n
+0000094021 00000 n
+0000058640 00000 n
+0000058670 00000 n
+0000056266 00000 n
+0000009797 00000 n
+0000015037 00000 n
+0000096969 00000 n
+0000093785 00000 n
+0000058724 00000 n
+0000058754 00000 n
+0000056436 00000 n
+0000015058 00000 n
+0000019959 00000 n
+0000058852 00000 n
+0000058882 00000 n
+0000056606 00000 n
+0000019980 00000 n
+0000024253 00000 n
+0000058925 00000 n
+0000058955 00000 n
+0000056776 00000 n
+0000024274 00000 n
+0000027927 00000 n
+0000059020 00000 n
+0000059050 00000 n
+0000056938 00000 n
+0000027948 00000 n
+0000032896 00000 n
+0000059115 00000 n
+0000059145 00000 n
+0000057108 00000 n
+0000032917 00000 n
+0000036525 00000 n
+0000059188 00000 n
+0000059218 00000 n
+0000057270 00000 n
+0000036546 00000 n
+0000041322 00000 n
+0000059283 00000 n
+0000059313 00000 n
0000057432 00000 n
-0000058664 00000 n
-0000062727 00000 n
-0000062757 00000 n
-0000062811 00000 n
-0000070549 00000 n
-0000070571 00000 n
-0000074729 00000 n
-0000074751 00000 n
-0000077347 00000 n
-0000077369 00000 n
-0000079928 00000 n
-0000079950 00000 n
-0000083012 00000 n
-0000083034 00000 n
-0000083359 00000 n
-0000083380 00000 n
-0000089617 00000 n
-0000089639 00000 n
-0000093055 00000 n
-0000093077 00000 n
-0000093821 00000 n
-0000093842 00000 n
-0000094585 00000 n
-0000095124 00000 n
-0000096353 00000 n
-0000096865 00000 n
-0000097097 00000 n
-0000098096 00000 n
+0000041343 00000 n
+0000044524 00000 n
+0000096691 00000 n
+0000093281 00000 n
+0000059367 00000 n
+0000059397 00000 n
+0000057594 00000 n
+0000044545 00000 n
+0000048319 00000 n
+0000059462 00000 n
+0000059492 00000 n
+0000057764 00000 n
+0000048340 00000 n
+0000050619 00000 n
+0000059590 00000 n
+0000059620 00000 n
+0000057934 00000 n
+0000050640 00000 n
+0000052419 00000 n
+0000059663 00000 n
+0000059693 00000 n
+0000058104 00000 n
+0000052440 00000 n
+0000055915 00000 n
+0000059736 00000 n
+0000059766 00000 n
+0000059809 00000 n
+0000067431 00000 n
+0000067452 00000 n
+0000071512 00000 n
+0000071533 00000 n
+0000074190 00000 n
+0000074212 00000 n
+0000076771 00000 n
+0000076793 00000 n
+0000079855 00000 n
+0000079877 00000 n
+0000080202 00000 n
+0000080223 00000 n
+0000086460 00000 n
+0000086482 00000 n
+0000089898 00000 n
+0000089920 00000 n
+0000090664 00000 n
+0000090685 00000 n
+0000091428 00000 n
+0000091965 00000 n
+0000093188 00000 n
+0000093700 00000 n
+0000093932 00000 n
+0000094931 00000 n
trailer
-<< /Size 125 /Root 1 0 R /Info 2 0 R
-/ID [(£ÛÎ_>¢ù¯\(MBο^þç)(£ÛÎ_>¢ù¯\(MBο^þç)]
+<< /Size 120 /Root 1 0 R /Info 2 0 R
+/ID [(ÔòyÔ\reI;¥!æ»)(ÔòyÔ\reI;¥!æ»)]
>>
startxref
-101703
+98529
%%EOF
Modified: puppetor/trunk/doc/howto.tex
===================================================================
--- puppetor/trunk/doc/howto.tex 2007-08-26 21:03:54 UTC (rev 11279)
+++ puppetor/trunk/doc/howto.tex 2007-08-26 21:18:53 UTC (rev 11280)
@@ -171,7 +171,7 @@
\begin{verbatim}
ClientApplication client = network.createClient("client",
- "www.google.de", 80, 7002);
+ "www.google.com", 80, 7002);
\end{verbatim}
Before starting the request we want to register for events coming from this
@@ -539,38 +539,15 @@
Handling event: APPLICATION_REQUESTS_PERFORMED
\end{verbatim}
-\section{Example 5: Beta test of distributed storage for hidden service
+\section{Tests of distributed storage for hidden service
descriptors}
-\textbf{WARNING: This example does not work with an unmodified Tor!}
+The purpose of the tests in package
+\texttt{de.uniba.wiai.lspi.puppetor.diststorage} is the validation
+of the distributed storage for hidden service descriptors as described
+in proposal 114.
+\textbf{WARNING: These examples do not work with an unmodified Tor!}
-The purpose of this example is the automatic validation of the distributed
-storage for hidden service descriptors as described in proposal 114.
-
-When running, the example starts a network of local Tor processes, consisting
-of 2 directory nodes and 9 periodically changing onion routers, some of them
-(not the initial nodes, but those nodes replacing them) with attached hidden
-services. Each hour, one node is stopped and a new node is started, so that the
-population size stays the same.
-
-The automatic validation performs four measurements:
-
-\begin{enumerate}
-\item Are the online statuses of hidden service directories propagated to all
-running nodes successfully, and how long does propagation take?
-\item The same measurement with offline statuses.
-\item Are hidden service descriptors stored on the correct, responsible hidden
-service directories, and how long does propagation take?
-\item Are hidden service requests successful within a given timeout?
-\end{enumerate}
-
-The results of these measurements are written as comma-separated values to the
-four files \texttt{online-propagation}, \texttt{offline-propagation},
-\texttt{descriptor-propagation}, and \texttt{hidden-service-requests} in the
-working directory of the test run. Successful measurements are written as the
-number of seconds that the measurement took, failures are encoded as
-\texttt{-1}, and aborted measurements are dropped.
-
\section{Architecture}
Though the examples show how to use the simulator, they do not provide insights
Added: puppetor/trunk/lib/bcpg-jdk16-137.jar
===================================================================
(Binary files differ)
Property changes on: puppetor/trunk/lib/bcpg-jdk16-137.jar
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: puppetor/trunk/lib/bcprov-jdk16-137.jar
===================================================================
(Binary files differ)
Property changes on: puppetor/trunk/lib/bcprov-jdk16-137.jar
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Modified: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/RouterNode.java
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/RouterNode.java 2007-08-26 21:03:54 UTC (rev 11279)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/RouterNode.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -54,14 +54,15 @@
public abstract String determineFingerprint() throws TorProcessException;
/**
- * Determines the base32-encoded fingerprint of this node.
+ * Returns the base32-encoded fingerprint of this node.
*
* @return The base32-encoded fingerprint of this node.
- * @throws TorProcessException
- * Thrown if the fingerprint cannot be determined.
+ * @throws IllegalStateException
+ * Thrown if node is neither in state
+ * <code>NodeState.RUNNING</code> or
+ * <code>NodeState.SHUT_DOWN</code>.
*/
- public abstract String determineFingerprintBase32()
- throws TorProcessException;
+ public abstract String getFingerprintBase32();
/**
* Returns the dir port of this node.
Added: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/AdvertisingAndAccessingV2HiddenServiceOverPrivateTorNetwork.java
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/AdvertisingAndAccessingV2HiddenServiceOverPrivateTorNetwork.java (rev 0)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/AdvertisingAndAccessingV2HiddenServiceOverPrivateTorNetwork.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -0,0 +1,182 @@
+package de.uniba.wiai.lspi.puppetor.diststorage;
+
+import java.util.Random;
+
+import de.uniba.wiai.lspi.puppetor.ClientApplication;
+import de.uniba.wiai.lspi.puppetor.DirectoryNode;
+import de.uniba.wiai.lspi.puppetor.Event;
+import de.uniba.wiai.lspi.puppetor.EventListener;
+import de.uniba.wiai.lspi.puppetor.EventManager;
+import de.uniba.wiai.lspi.puppetor.EventType;
+import de.uniba.wiai.lspi.puppetor.Network;
+import de.uniba.wiai.lspi.puppetor.NetworkFactory;
+import de.uniba.wiai.lspi.puppetor.RouterNode;
+import de.uniba.wiai.lspi.puppetor.ServerApplication;
+import de.uniba.wiai.lspi.puppetor.TorProcessException;
+
+/**
+ * Example for advertising and accessing a hidden service over a private Tor
+ * network.
+ *
+ * @author kloesing
+ */
+public class AdvertisingAndAccessingV2HiddenServiceOverPrivateTorNetwork {
+
+ /**
+ * Sets up and runs the test.
+ *
+ * @param args
+ * Command-line arguments (ignored).
+ * @throws TorProcessException
+ * Thrown if there is a problem with the JVM-external Tor
+ * processes that we cannot handle.
+ */
+ public static void main(String[] args) throws TorProcessException {
+
+ // create a network to initialize a test case
+ Network network = NetworkFactory.createNetwork("example4");
+
+ // create two directory nodes with parameters (router name, control
+ // port, SOCKS port, OR port, dir port)
+ DirectoryNode dir1 = network.createDirectory("dir1", 7001, 7002, 7003,
+ 7004);
+ DirectoryNode dir2 = network.createDirectory("dir2", 7011, 7012, 7013,
+ 7014);
+
+ // create three router nodes with parameters (router name, control port,
+ // SOCKS port, OR port, dir mirror port)
+ RouterNode router1 = network.createRouter("router1", 7021, 7022, 7023,
+ 7024);
+ RouterNode router2 = network.createRouter("router2", 7031, 7032, 7033,
+ 7034);
+ RouterNode router3 = network.createRouter("router3", 7041, 7042, 7043,
+ 7044);
+ RouterNode router4 = network.createRouter("router4", 7051, 7052, 7053,
+ 7054);
+
+ // configure all nodes hidden service directories
+ dir1.addConfiguration("HidServDirectoryV2 1");
+ dir2.addConfiguration("HidServDirectoryV2 1");
+ dir1.addConfiguration("MinUptimeHidServDirectoryV2 0 minutes");
+ dir2.addConfiguration("MinUptimeHidServDirectoryV2 0 minutes");
+ router1.addConfiguration("HidServDirectoryV2 1");
+ router2.addConfiguration("HidServDirectoryV2 1");
+ router3.addConfiguration("HidServDirectoryV2 1");
+ router4.addConfiguration("HidServDirectoryV2 1");
+
+ // add hidden service
+ router1.addHiddenService("hidServ", 7025, 80);
+
+ // re-configure routers 1 and 3 for V2 compatibility
+ router1.addConfiguration("PublishHidServDescriptors 0");
+ router1.addConfiguration("PublishV2HidServDescriptors 1");
+ router1.addConfiguration("FetchHidServDescriptors 0");
+ router1.addConfiguration("FetchV2HidServDescriptors 1");
+ router2.addConfiguration("FetchHidServDescriptors 0");
+ router2.addConfiguration("FetchV2HidServDescriptors 1");
+ router3.addConfiguration("FetchHidServDescriptors 0");
+ router3.addConfiguration("FetchV2HidServDescriptors 1");
+ router4.addConfiguration("FetchHidServDescriptors 0");
+ router4.addConfiguration("FetchV2HidServDescriptors 1");
+
+ // configure nodes of this network to be part of a private network
+ network.configureAsPrivateNetwork();
+
+ // write configuration of proxy node
+ network.writeConfigurations();
+
+ // start proxy node and wait until it has opened a circuit with a
+ // timeout of 15 seconds
+ if (!network.startNodes(15000)) {
+
+ // failed to start the proxy
+ System.out.println("Failed to start nodes!");
+ return;
+ }
+ System.out.println("Successfully started nodes!");
+
+ // hup until proxy has built circuits (10 retries, 10 seconds timeout
+ // each)
+ if (!network.hupUntilUp(20, 6000)) {
+
+ // failed to build circuits
+ System.out.println("Failed to build circuits!");
+ return;
+ }
+ System.out.println("Successfully built circuits!");
+
+ // obtain reference to event manager to be able to respond to events
+ EventManager manager = network.getEventManager();
+
+ manager.registerEventTypePattern(
+ "Hidden service routing table has changed",
+ EventType.NODE_ROUTING_TABLE_CHANGED);
+ manager.registerEventTypePattern("Sending publish request for "
+ + "v2 descriptor for "
+ + "service '.*' with descriptor ID '.*' with validity of .* "
+ + "seconds to hidden service directory '.*' on port .*",
+ EventType.BOB_SENDING_PUBLISH_DESC);
+ manager.registerEventTypePattern("Successfully stored service "
+ + "descriptor with desc ID " + "'.*' and len .*",
+ EventType.HSDIR_DESC_STORED);
+ manager.registerEventTypePattern("Sending fetch request for v2 "
+ + "descriptor for "
+ + "service '.*' with descriptor ID '.*' to hidden "
+ + "service directory '.*' on port .*",
+ EventType.ALICE_SENDING_FETCH_DESC);
+ manager
+ .registerEventTypePattern(
+ "Successfully stored service descriptor with "
+ + "desc ID '.*'", EventType.HSDIR_DESC_STORED);
+
+ manager.addEventListener(new EventListener() {
+ public void handleEvent(Event event) {
+ System.out.println(event);
+ }
+ });
+
+ // wait for 30 minutes that the proxy has published its first RSD
+ if (!manager.waitForAnyOccurence(router1,
+ EventType.BOB_SENDING_PUBLISH_DESC, 30L * 60L * 1000L)) {
+ // failed to publish an RSD
+ System.out.println("Failed to publish an RSD!");
+ return;
+ }
+ System.out.println("All RSDs published!");
+
+ // determine onion address for hidden service
+ String onionAddress = router1.getOnionAddress("hidServ", 2);
+
+ // create server application
+ ServerApplication server = network.createServer("server", 7025);
+
+ // start server
+ server.listen();
+ System.out.println("Started server with onion address '" + onionAddress
+ + "'");
+
+ Random rnd = new Random();
+
+ for (int i = 0; i < 10; i++) {
+
+ int socksPort = 7022 + rnd.nextInt(4) * 10;
+ System.out.println("Socks Port = " + socksPort);
+
+ // create client application
+ ClientApplication client = network.createClient("client",
+ onionAddress, 80, socksPort);
+
+ // perform at most five request with a timeout of 45 seconds each
+ client.performRequest(5, 45000, true);
+
+ try {
+ Thread.sleep(60000);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // shut down nodes
+ network.shutdownNodes();
+
+ }
+}
Copied: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/DistributedStorage.java (from rev 11212, puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/examples/DistributedStorage.java)
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/DistributedStorage.java (rev 0)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/DistributedStorage.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -0,0 +1,1458 @@
+/*
+ *
+ * WARNING! This long-running test case sometimes fails for yet unknown
+ * reasons, possibly because of the test case implementation! Don't rely
+ * on it!
+ *
+ */
+
+package de.uniba.wiai.lspi.puppetor.diststorage;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import de.uniba.wiai.lspi.puppetor.ClientApplication;
+import de.uniba.wiai.lspi.puppetor.DirectoryNode;
+import de.uniba.wiai.lspi.puppetor.Event;
+import de.uniba.wiai.lspi.puppetor.EventListener;
+import de.uniba.wiai.lspi.puppetor.EventManager;
+import de.uniba.wiai.lspi.puppetor.EventSource;
+import de.uniba.wiai.lspi.puppetor.EventType;
+import de.uniba.wiai.lspi.puppetor.Network;
+import de.uniba.wiai.lspi.puppetor.NetworkFactory;
+import de.uniba.wiai.lspi.puppetor.ProxyNode;
+import de.uniba.wiai.lspi.puppetor.RouterNode;
+import de.uniba.wiai.lspi.puppetor.TorProcessException;
+
+/* TODO Document this class properly */
+
+/**
+ * <p>
+ * Automatic validation of the distributed storage for hidden service
+ * descriptors as described in proposal 114. <b>WARNING: This example does not
+ * work with an unmodified Tor!</b>
+ * </p>
+ *
+ * <p>
+ * When running, the example starts a network of local Tor processes, consisting
+ * of 2 directory nodes and 9 periodically changing onion routers, some of them
+ * (not the initial nodes, but those nodes replacing them) with attached hidden
+ * services.
+ * </p>
+ *
+ * <p>
+ * The automatic validation performs four measurements:
+ * </p>
+ *
+ * <ol>
+ * <li>Are the online statuses of hidden service directories propagated to all
+ * running nodes successfully, and how long does propagation take?</li>
+ * <li>The same measurement with offline statuses.</li>
+ * <li>Are hidden service descriptors stored on the correct, responsible hidden
+ * service directories, and how long does propagation take?</li>
+ * <li>Are hidden service requests successful within a given timeout?</li>
+ * </ol>
+ *
+ * <p>
+ * The results of these measurements are written as comma-separated values to
+ * the four files <b>online-propagation</b>, <b>offline-propagation</b>,
+ * <b>descriptor-propagation</b>, and <b>hidden-service-requests</b> in the
+ * working directory of the test run. Successful measurements are written as the
+ * number of seconds that the measurement took, failures are encoded as <b>-1</b>,
+ * and aborted measurements are dropped.
+ * </p>
+ *
+ * @author kloesing
+ */
+public class DistributedStorage {
+
+ /**
+ * Observes a hidden service directory from starting it to shutting it down.
+ * Waits for 24 hours after starting the node, so that it will be accepted
+ * as hidden service directory by the directory authorities. Afterwards,
+ * initiates an online propagation measurement for every node that is in the
+ * network or that enters the network while the observed hidden service
+ * directory is online. After going offline, initiates an offline
+ * propagation measurement for every node that has previously accepted this
+ * hidden service directory.
+ */
+ private static class HiddenServiceDirectoryObserver extends Thread
+ implements EventListener {
+
+ /**
+ * Is the hidden service directory online for at least 24 hours, so that
+ * it will be listed by the directory authorities?
+ */
+ private boolean twentyFourHoursUp;
+
+ /**
+ * The Tor process behind the observed hidden service directory.
+ */
+ private RouterNode hsdNode;
+
+ /**
+ * Set of nodes that have reported to accept this hidden service
+ * directory.
+ */
+ private Set<EventSource> nodesThatKnowMe = new HashSet<EventSource>();
+
+ /**
+ * Creates a new observer instance, but does not start it yet.
+ *
+ * @param hsdNode
+ * The Tor process behind the observed hidden service
+ * directory.
+ * @param nodeID
+ * The node ID of the observed hidden service directory.
+ */
+ private HiddenServiceDirectoryObserver(RouterNode hsdNode) {
+
+ // remember the args
+ this.hsdNode = hsdNode;
+
+ // listen for events coming from my HSDir
+ manager.addEventListener(hsdNode, this);
+
+ // listen for starting/stopping nodes
+ manager.addEventListener(this);
+ }
+
+ @Override
+ public synchronized void run() {
+
+ // not yet accepted
+ this.twentyFourHoursUp = false;
+
+ // wait for 24 hours until hsdir is accepted by DAs
+ long startingTime = System.currentTimeMillis();
+ // for testing; change to 24 * 60 * 60 * 1000
+ long endOfWaiting = startingTime + 30 * 60 * 1000;
+ long now;
+ while ((now = System.currentTimeMillis()) < endOfWaiting) {
+ try {
+ wait(endOfWaiting - now);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // accepted
+ this.twentyFourHoursUp = true;
+
+ // add to set of running hidden service directories
+ globalRoutingTable.addHiddenServiceDirectory(this.hsdNode);
+
+ // measure how long the current nodes need to get aware of this
+ // hidden service directory
+ for (RouterNode router : runningRouters) {
+ new OnlinePropagationMeasurement(router, this.hsdNode).start();
+ }
+ }
+
+ public synchronized void handleEvent(Event event) {
+
+ if (!this.twentyFourHoursUp) {
+ // when the represented node is not running for at least 24
+ // hours, others would not consider it as hidden service
+ // directory and we cannot perform any useful measurements
+ return;
+ }
+
+ if (event.getType() == EventType.NODE_STARTED) {
+ // when another node is started, initiate measurement of the
+ // propagation time that this hiddden service directory is
+ // online
+ new OnlinePropagationMeasurement(event.getSource(),
+ this.hsdNode).start();
+
+ } else if (event.getSource() == this.hsdNode
+ && event.getType() == EventType.NODE_STOPPED) {
+ // when the represented node is stopped, initiate measurement of
+ // the propagation time that this hidden service directory is
+ // offline (any online propagation measurements that are still
+ // running will realize by themselves that they cannot succeed
+ // and will abort)
+ for (EventSource node : nodesThatKnowMe) {
+ if (node != this.hsdNode) {
+ new OfflinePropagationMeasurement(node, this.hsdNode)
+ .start();
+ }
+ }
+
+ // remove from set of running hidden service directories
+ globalRoutingTable.removeHiddenServiceDirectory(this.hsdNode);
+
+ // stop listening for events
+ manager.removeEventListener(this);
+
+ // just in case that there is another event in the queue, don't
+ // handle it any more
+ this.twentyFourHoursUp = false;
+
+ } else if (event.getType() == EventType.NODE_ROUTING_TABLE_CHANGED
+ && event.getMessage().contains(
+ this.hsdNode.getFingerprintBase32())) {
+ // when some node reports to have changed its routing table and
+ // now includes the node ID of the represented node, remember
+ // that node for later offline propagation measurement
+ nodesThatKnowMe.add(event.getSource());
+
+ } else if (event.getType() == EventType.NODE_STOPPED
+ && event.getSource() != this.hsdNode) {
+ // when some other node is stopped, remove it from the list of
+ // nodes that know that the represented hidden service directory
+ // is online
+ nodesThatKnowMe.remove(event.getSource());
+ }
+ }
+ }
+
+ /**
+ * The states in which a measurement can be.
+ */
+ private static enum MeasurementState {
+
+ /**
+ * The measurement was started and is currently running.
+ */
+ STARTED,
+
+ /**
+ * The measurement has succeeded with a positive result.
+ */
+ SUCCEEDED,
+
+ /**
+ * The measurement has failed within the given maximum time.
+ */
+ FAILED,
+
+ /**
+ * The measurement was aborted, because it has become impossible to
+ * succeed.
+ */
+ ABORTED
+ }
+
+ /**
+ * Verifies that the online status of a given hidden service directory is
+ * propagated to a given running node, and measures how long that
+ * propagation takes. If the propagation does not succeed within one hour,
+ * it is considered as failed.
+ */
+ private static class OnlinePropagationMeasurement extends Thread implements
+ EventListener {
+
+ /**
+ * The state of this measurement.
+ */
+ private MeasurementState measurementState;
+
+ /**
+ * The hidden service directory of which the online status should be
+ * propagated.
+ */
+ private RouterNode hsdNode;
+
+ /**
+ * The node to which the online status should be propagated.
+ */
+ private EventSource node;
+
+ /**
+ * Creates a new measurement, but does not start it yet.
+ *
+ * @param node
+ * The node to which the online status should be propagated.
+ * @param hsdNode
+ * The hidden service directory of which the online status
+ * should be propagated.
+ */
+ private OnlinePropagationMeasurement(EventSource node,
+ RouterNode hsdNode) {
+
+ // remember the args
+ this.hsdNode = hsdNode;
+ this.node = node;
+
+ // listen for events coming from the node and the hidden service
+ // directory
+ manager.addEventListener(node, this);
+ manager.addEventListener(hsdNode, this);
+
+ }
+
+ @Override
+ public synchronized void run() {
+
+ // start the measurement in state STARTED
+ this.measurementState = MeasurementState.STARTED;
+
+ // wait for a maximum of one hour for the measurement to succeed
+ long startingTime = System.currentTimeMillis();
+ long endOfWaiting = startingTime + 60 * 60 * 1000;
+ long now;
+ while (this.measurementState == MeasurementState.STARTED
+ && (now = System.currentTimeMillis()) < endOfWaiting) {
+ try {
+ wait(endOfWaiting - now);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // if the node did not learn about the hidden service directory
+ // within one hour, fail the measurement
+ if (this.measurementState == MeasurementState.STARTED) {
+ measurementState = MeasurementState.FAILED;
+ }
+
+ // unregister event listener
+ manager.removeEventListener(this);
+
+ // print out measurement result
+ long duration = System.currentTimeMillis() - startingTime;
+ System.out.println(new Date() + ": Online propagation for HSDir "
+ + this.hsdNode.getName() + " to node "
+ + this.node.getName() + " took " + (duration / 1000)
+ + " seconds and ended in state "
+ + measurementState.toString());
+ if (this.measurementState == MeasurementState.SUCCEEDED) {
+ resultWriter.writeOnlinePropagation(duration / 1000);
+ } else if (this.measurementState == MeasurementState.FAILED) {
+ resultWriter.writeOnlinePropagation(-1);
+ }
+ }
+
+ public synchronized void handleEvent(Event event) {
+
+ // only accept events when this measurement is running
+ if (measurementState != MeasurementState.STARTED) {
+ return;
+ }
+
+ if (event.getSource() == this.node
+ && event.getType() == EventType.NODE_ROUTING_TABLE_CHANGED
+ && event.getMessage().contains(
+ this.hsdNode.getFingerprintBase32())) {
+ // when the node has added the node ID of the hidden service
+ // directory, succeed the measurement
+ this.measurementState = MeasurementState.SUCCEEDED;
+ notify();
+
+ } else if (event.getType() == EventType.NODE_STOPPED) {
+ // when either the node or the hidden service directory were
+ // stopped within the one hour period, the measurement cannot be
+ // succeeded anymore and is therefore aborted
+ this.measurementState = MeasurementState.ABORTED;
+ notify();
+ }
+ }
+ }
+
+ /**
+ * Verifies that the offline status of a given hidden service directory is
+ * propagated to a given running node, and measures how long that
+ * propagation takes. If the propagation does not succeed within one hour,
+ * it is considered as failed.
+ */
+ private static class OfflinePropagationMeasurement extends Thread implements
+ EventListener {
+
+ /**
+ * The state of this measurement.
+ */
+ private MeasurementState measurementState;
+
+ /**
+ * The hidden service directory of which the offline status should be
+ * propagated.
+ */
+ private RouterNode hsdNode;
+
+ /**
+ * The node to which the offline status should be propagated.
+ */
+ private EventSource node;
+
+ /**
+ * Creates a new measurement, but does not start it yet.
+ *
+ * @param node
+ * The node to which the offline status should be propagated.
+ * @param hsdNode
+ * The hidden service directory of which the offline status
+ * should be propagated.
+ */
+ private OfflinePropagationMeasurement(EventSource node,
+ RouterNode hsdNode) {
+
+ // remember the args
+ this.node = node;
+ this.hsdNode = hsdNode;
+
+ // register for events from the node that should observe the offline
+ // status
+ manager.addEventListener(node, this);
+ }
+
+ @Override
+ public synchronized void run() {
+
+ // start the measurement in state STARTED
+ this.measurementState = MeasurementState.STARTED;
+
+ // wait for a maximum of 90 minutes for the measurement to succeed
+ long startingTime = System.currentTimeMillis();
+ long endOfWaiting = startingTime + 90 * 60 * 1000;
+ long now;
+ while (this.measurementState == MeasurementState.STARTED
+ && (now = System.currentTimeMillis()) < endOfWaiting) {
+ try {
+ wait(endOfWaiting - now);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // if the node did not learn about the offline status of the hidden
+ // service directory, fail the measurement
+ if (this.measurementState == MeasurementState.STARTED) {
+ measurementState = MeasurementState.FAILED;
+ }
+
+ // unregister event listener
+ manager.removeEventListener(this);
+
+ // print out measurement result
+ long duration = System.currentTimeMillis() - startingTime;
+ System.out.println(new Date() + ": Offline propagation for HSDir "
+ + this.hsdNode.getName() + " to node "
+ + this.node.getName() + " took " + (duration / 1000)
+ + " seconds and ended in state "
+ + measurementState.toString());
+ if (this.measurementState == MeasurementState.SUCCEEDED) {
+ resultWriter.writeOfflinePropagation(duration / 1000);
+ } else if (this.measurementState == MeasurementState.FAILED) {
+ resultWriter.writeOfflinePropagation(-1);
+ }
+ }
+
+ public synchronized void handleEvent(Event event) {
+
+ // only accept events when this measurement is running
+ if (measurementState != MeasurementState.STARTED) {
+ return;
+ }
+
+ if (event.getType() == EventType.NODE_ROUTING_TABLE_CHANGED
+ && !event.getMessage().contains(
+ this.hsdNode.getFingerprintBase32())) {
+ // when the node has removed the node ID of the hidden service
+ // directory, succeed the measurement
+ this.measurementState = MeasurementState.SUCCEEDED;
+ notify();
+
+ } else if (event.getType() == EventType.NODE_STOPPED) {
+ // when the node was stopped within the waiting period, the
+ // measurement cannot be
+ // succeeded anymore and is therefore aborted
+ this.measurementState = MeasurementState.ABORTED;
+ notify();
+ }
+ }
+ }
+
+ /**
+ * The routing table containing the global state of all hidden service
+ * directories in the network.
+ */
+ private static HidServRoutingTable globalRoutingTable = new HidServRoutingTable();
+
+ /**
+ * Observes all publications of descriptors by hidden service providers.
+ * Whenever there is a novel descriptor, the observer launches measurements
+ * of the ropagation of the new descriptor to the responsible hidden service
+ * directories.
+ */
+ private static class DescriptorObserverStarter implements EventListener {
+
+ /**
+ * Set of all descriptor IDs that have been observed so far.
+ */
+ private Set<String> knownDescIDs = new HashSet<String>();
+
+ /**
+ * Creates a new observer that starts listening for events.
+ */
+ private DescriptorObserverStarter() {
+ manager.addEventListener(this);
+ }
+
+ public void handleEvent(Event event) {
+ // listen for new desc-ids
+ if (event.getType() == EventType.BOB_SENDING_PUBLISH_DESC) {
+ // a descriptor was published by any hidden service provider;
+ // check if this descriptor ID is novel
+
+ // parse desc id from event message
+ String message = event.getMessage();
+ String prefixDescID = "with descriptor ID '";
+ String descID = message.substring(message.indexOf(prefixDescID)
+ + prefixDescID.length());
+ descID = descID.substring(0, 32);
+
+ if (!knownDescIDs.contains(descID)) {
+ String prefixSecondsValid = "with validity of ";
+ String secondsValidPlusRest = message.substring(message
+ .indexOf(prefixSecondsValid)
+ + prefixSecondsValid.length());
+ String[] splitted = secondsValidPlusRest.split(" ");
+ long secondsValid = Long.parseLong(splitted[0]);
+
+ if (secondsValid > 30 * 60) {
+ new DescriptorObserver(descID, secondsValid).start();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Observes a descriptor from its first publication to 30 minutes before its
+ * validity ends. Initiates a descriptor propagation measurement for every
+ * hidden service directory that either is or becomes responsible for the
+ * descriptor ID. (This does not include checks that a descriptor remains
+ * stored on the responsible hidden service directories.)
+ */
+ private static class DescriptorObserver extends Thread {
+
+ /**
+ * The descriptor ID of the observed descriptor.
+ */
+ private String descID;
+
+ /**
+ * Time in millis when the validity of the observed descriptor amounts
+ * 30 minutes.
+ */
+ private long endOfWaiting;
+
+ /**
+ * The set of hidden service directories that we think is responsible
+ * for storing the observed descriptor.
+ */
+ private Set<EventSource> hsDirsDeemedResponsible;
+
+ /**
+ * Creates a new observer instance, but does not start it yet.
+ *
+ * @param descID
+ * The descriptor ID of the observed descriptor.
+ * @param validityInSeconds
+ * The validity of the descriptor in seconds.
+ */
+ private DescriptorObserver(String descID, long validityInSeconds) {
+
+ // remember the args
+ this.descID = descID;
+
+ // determine when the validity of the observed descriptor amounts 30
+ // minutes
+ this.endOfWaiting = System.currentTimeMillis() + validityInSeconds
+ * 1000 - 30 * 60 * 1000;
+ }
+
+ @Override
+ public void run() {
+
+ if (System.currentTimeMillis() < this.endOfWaiting) {
+ // when the descriptor is not valid for at least 30 minutes, we
+ // don't need to perform any measurements at all, because it is
+ // quite unlikely that they would succeed
+ return;
+ }
+
+ // start measurements for the currently responsible hidden service
+ // directories
+ hsDirsDeemedResponsible = globalRoutingTable
+ .getResponsibleHSDirs(this.descID);
+ for (EventSource responsibleDir : hsDirsDeemedResponsible) {
+ new DescriptorPropagationMeasurement(responsibleDir,
+ this.descID).start();
+ }
+
+ // wait until the validity of the descriptor is 30 minutes or less
+ long now;
+ while ((now = System.currentTimeMillis()) < this.endOfWaiting) {
+
+ // wait for changes in the global routing table
+ globalRoutingTable.waitForRoutingTableChange(this.endOfWaiting
+ - now);
+
+ // check if the descriptor validity is still sufficient to
+ // initiate new measurements
+ if (now < this.endOfWaiting) {
+
+ // check if the change affects us
+ Set<EventSource> newResponsibleHSDirs = globalRoutingTable
+ .getResponsibleHSDirs(this.descID);
+ Set<EventSource> newcomers = new HashSet<EventSource>(
+ newResponsibleHSDirs);
+ newcomers.removeAll(this.hsDirsDeemedResponsible);
+ if (newcomers.size() > 0) {
+
+ // initiate a new measurement for every new responsible
+ // hidden service directory
+ for (EventSource responsibleDir : newcomers) {
+ new DescriptorPropagationMeasurement(
+ responsibleDir, this.descID).start();
+ }
+ this.hsDirsDeemedResponsible = newResponsibleHSDirs;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Verifies that a descriptor is propagated to a given hidden service
+ * directory, and measures how long that propagation takes. If the
+ * propagation does not succeed within 90 minutes, it is considered as
+ * failed, unless a change in the routing table makes the hidden service
+ * directory irresponsible for the given descriptor.
+ */
+ private static class DescriptorPropagationMeasurement extends Thread
+ implements EventListener {
+
+ /**
+ * The state of this measurement.
+ */
+ private MeasurementState measurementState;
+
+ /**
+ * The descriptor ID of the observed descriptor.
+ */
+ private String descid;
+
+ /**
+ * The hidden service directory that is deemed responsible to store the
+ * given descriptor.
+ */
+ private EventSource responsibleHSDir;
+
+ /**
+ * Creates a new measurement instance, but does not start it yet.
+ *
+ * @param responsibleHSDir
+ * The hidden service directory that is deemed responsible to
+ * store the given descriptor.
+ * @param descid
+ * The descriptor ID of the observed descriptor.
+ */
+ private DescriptorPropagationMeasurement(EventSource responsibleHSDir,
+ String descid) {
+
+ // remember the args
+ this.responsibleHSDir = responsibleHSDir;
+ this.descid = descid;
+
+ // register for events from the hidden service directory
+ manager.addEventListener(responsibleHSDir, this);
+ }
+
+ @Override
+ public synchronized void run() {
+
+ // start the measurement in state STARTED
+ this.measurementState = MeasurementState.STARTED;
+
+ // wait for a maximum of 90 minutes for the measurement to succeed
+ long startingTime = System.currentTimeMillis();
+ long endOfWaiting = startingTime + 90 * 60 * 1000;
+ long now;
+ while (this.measurementState == MeasurementState.STARTED
+ && (now = System.currentTimeMillis()) < endOfWaiting) {
+
+ // wait for a change in the routing table, that might make the
+ // hidden service directory irresponsible for the given
+ // descriptor, or be interrupted by an incoming event
+ globalRoutingTable
+ .waitForRoutingTableChange(endOfWaiting - now);
+
+ if (this.measurementState == MeasurementState.STARTED
+ && !globalRoutingTable
+ .getResponsibleHSDirs(this.descid).contains(
+ this.responsibleHSDir)) {
+ // routing table has changed, so that the hidden service
+ // directory is not responsible for the given descriptor any
+ // more
+ this.measurementState = MeasurementState.ABORTED;
+ }
+ }
+
+ // if the node did not learn about the hidden service directory
+ // within the given time, fail the measurement
+ if (this.measurementState == MeasurementState.STARTED) {
+ measurementState = MeasurementState.FAILED;
+ }
+
+ // unregister event listener
+ manager.removeEventListener(this);
+
+ // print out measurement result
+ long duration = System.currentTimeMillis() - startingTime;
+ System.out.println(new Date()
+ + ": Descriptor propagation for desc ID " + this.descid
+ + " took " + (duration / 1000)
+ + " seconds to responsible HS directory "
+ + this.responsibleHSDir.getName() + " and ended in state "
+ + measurementState.toString());
+ if (this.measurementState == MeasurementState.SUCCEEDED) {
+ resultWriter.writeDescriptorPropagation(duration / 1000);
+ } else if (this.measurementState == MeasurementState.FAILED) {
+ resultWriter.writeDescriptorPropagation(-1);
+ }
+ }
+
+ public synchronized void handleEvent(Event event) {
+
+ if (event.getType() == EventType.HSDIR_DESC_STORED
+ && event.getMessage().contains(this.descid)) {
+ // when the hidden service directory stores the given descriptor
+ // for the first time, the measurement succeeds
+ this.measurementState = MeasurementState.SUCCEEDED;
+ interrupt();
+ }
+ }
+ }
+
+ /**
+ * Observes a hidden service for the first 20 minutes after starting it
+ * before adding it to the list of available hidden services. When the node
+ * is stopped (within or after the 20 minutes), the hidden service will be
+ * removed from the list of available hidden services.
+ */
+ private static class HiddenServiceObserver extends Thread implements
+ EventListener {
+
+ /**
+ * The onion address by which this hidden service can be accessed.
+ */
+ private String onionAddress;
+
+ /**
+ * The node that provides the hidden service.
+ */
+ private EventSource providingNode;
+
+ /**
+ * Creates a new observer instance, but does not start it yet.
+ *
+ * @param providingNode
+ * The node that provides the hidden service.
+ * @param onionAddress
+ * The onion address by which this hidden service can be
+ * accessed.
+ */
+ private HiddenServiceObserver(EventSource providingNode,
+ String onionAddress) {
+
+ // remember the args
+ this.onionAddress = onionAddress;
+ this.providingNode = providingNode;
+
+ // listen for events coming from the providing node
+ manager.addEventListener(providingNode, this);
+ }
+
+ /**
+ * Is the observed hidden service still available, or was the providing
+ * node stopped?
+ */
+ private boolean stopped = false;
+
+ @Override
+ public void run() {
+
+ // wait for 20 minutes until the hidden service can be considered
+ // available
+ long startingTime = System.currentTimeMillis();
+ long endOfWaiting = startingTime + 20 * 60 * 1000;
+ long now;
+ while (!this.stopped
+ && (now = System.currentTimeMillis()) < endOfWaiting) {
+ try {
+ Thread.sleep(endOfWaiting - now);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // add it to the list of available hidden services
+ availableHiddenServices.put(this.onionAddress, this.providingNode);
+ }
+
+ public void handleEvent(Event event) {
+ if (event.getType() == EventType.NODE_STOPPED) {
+ // when the providing node is stopped, the hidden service will
+ // not be available any more
+ this.stopped = true;
+ availableHiddenServices.remove(this.onionAddress);
+ }
+ }
+ }
+
+ /**
+ * A list of all hidden services that are available for testing.
+ */
+ private static Map<String, EventSource> availableHiddenServices = new HashMap<String, EventSource>();
+
+ /**
+ * Periodically (every 5 to 10 minutes) picks a hidden service that is
+ * available and running for at least 20 minutes and performs a request to
+ * it using an arbitrary node as proxy.
+ */
+ private static class HiddenServiceRequestStarter extends Thread {
+
+ @Override
+ public void run() {
+ // first wait until HSDirs are up and descriptors distributed --
+ // stable state
+ // Thread.sleep(24 * 60 * 60 * 1000 + 90 * 60 * 1000);
+ try {
+ Thread.sleep(30 * 60 * 1000 + 30 * 60 * 1000);
+ } catch (InterruptedException e) {
+ }
+
+ Random rnd = new Random();
+ while (true) {
+ if (availableHiddenServices.size() > 0) {
+
+ // pick an available hidden service at random
+ String onionAddressToTry = new ArrayList<String>(
+ availableHiddenServices.keySet()).get(rnd
+ .nextInt(availableHiddenServices.size()));
+ EventSource providingNode = availableHiddenServices
+ .get(onionAddressToTry);
+
+ // pick an arbitrary running node as proxy
+ RouterNode useAsProxy = runningRouters.get(rnd
+ .nextInt(runningRouters.size()));
+
+ // start measurement
+ new HiddenServiceRequestMeasurement(onionAddressToTry,
+ providingNode, useAsProxy).start();
+ }
+
+ // wait for a random time between 5 to 10 minutes
+ try {
+ Thread.sleep(5 * 60 * 1000 + rnd.nextInt(5 * 60 * 1000));
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Verifies that a hidden service can be accessed, and measures how long
+ * performing a request takes. If the request does not succeed within one
+ * minute, it is considered as failed, unless either the node providing the
+ * hidden service, or the node that is used as proxy fails.
+ */
+ private static class HiddenServiceRequestMeasurement extends Thread
+ implements EventListener {
+
+ /**
+ * The onion address to which the request shall be performed.
+ */
+ private String onionAddress;
+
+ /**
+ * The node that provides the hidden service.
+ */
+ private EventSource providingNode;
+
+ /**
+ * The node that is used as proxy for the request.
+ */
+ private EventSource useAsProxy;
+
+ /**
+ * The client application that performs the requests.
+ */
+ private ClientApplication clientApp;
+
+ /**
+ * The state of this measurement.
+ */
+ private MeasurementState measurementState;
+
+ /**
+ * The time when the request was sent from the client.
+ */
+ private long requestSentFromClient;
+
+ /**
+ * The time when a reply was received at the client.
+ */
+ private long replyReceivedAtClient;
+
+ /**
+ * Creates a new measurement instance, but does not start it yet.
+ *
+ * @param onionAddress
+ * The onion address to which the request shall be performed.
+ * @param providingNode
+ * The node that provides the hidden service.
+ * @param useAsProxy
+ * The node that is used as proxy for the request.
+ */
+ private HiddenServiceRequestMeasurement(String onionAddress,
+ EventSource providingNode, ProxyNode useAsProxy) {
+
+ // remember the args
+ this.onionAddress = onionAddress;
+ this.providingNode = providingNode;
+ this.useAsProxy = useAsProxy;
+
+ // register for events from the two involved nodes
+ manager.addEventListener(providingNode, this);
+ manager.addEventListener(useAsProxy, this);
+
+ // determine socks port of proxy
+ int socksPort = useAsProxy.getSocksPort();
+
+ // create client application and register for events originating
+ // from it
+ this.clientApp = network.createClient("client", onionAddress, 80,
+ socksPort);
+ manager.addEventListener(this.clientApp, this);
+ }
+
+ @Override
+ public synchronized void run() {
+
+ // start the measurement in state STARTED
+ this.measurementState = MeasurementState.STARTED;
+
+ // perform the request
+ this.clientApp.performRequest(1, 60 * 1000, true);
+
+ // wait for a minute for the request to be performed
+ long startingTime = System.currentTimeMillis();
+ long endOfWaiting = startingTime + 60 * 1000;
+ long now;
+ while (this.measurementState == MeasurementState.STARTED
+ && (now = System.currentTimeMillis()) < endOfWaiting) {
+ try {
+ System.out.println(new Date() + ": Waiting...");
+ wait(endOfWaiting - now);
+ } catch (InterruptedException e) {
+ }
+ }
+ System.out.println(new Date() + ": Finished waiting!");
+
+ // if the client application did not receive a reply within the
+ // given time, fail the measurement
+ if (this.measurementState == MeasurementState.STARTED) {
+ measurementState = MeasurementState.FAILED;
+ }
+
+ // unregister event listener
+ manager.removeEventListener(this);
+
+ // print out measurement result
+ System.out
+ .println(System.currentTimeMillis()
+ + ": Hidden service request for onion address "
+ + this.onionAddress
+ + " running on node "
+ + this.providingNode.getName()
+ + " using node "
+ + this.useAsProxy.getName()
+ + " as proxy took "
+ + (replyReceivedAtClient > 0
+ && requestSentFromClient > 0 ? ((replyReceivedAtClient - requestSentFromClient) / 1000)
+ : -1) + " seconds and ended in state "
+ + measurementState.toString());
+
+ if (this.measurementState == MeasurementState.SUCCEEDED) {
+ resultWriter
+ .writeHiddenServiceRequest((replyReceivedAtClient - requestSentFromClient) / 1000);
+ } else if (this.measurementState == MeasurementState.FAILED) {
+ resultWriter.writeHiddenServiceRequest(-1);
+ }
+ }
+
+ public void handleEvent(Event event) {
+ // only accept events when this measurement is running
+ if (measurementState != MeasurementState.STARTED) {
+ return;
+ }
+
+ if ((event.getSource() == this.useAsProxy || event.getSource() == this.providingNode)
+ && event.getType() == EventType.NODE_STOPPED) {
+ // when either the node providing the hidden service, or the
+ // node that is used as proxy fails, abort measurement
+ System.out.println("node has stopped: " + event);
+ this.measurementState = MeasurementState.ABORTED;
+ notify();
+
+ } else if (event.getSource() == this.clientApp
+ && event.getType() == EventType.CLIENT_SENDING_REQUEST) {
+ // when the client application reports sending the request, note
+ // the time
+ this.requestSentFromClient = event.getOccurrenceTime();
+
+ } else if (event.getSource() == this.clientApp
+ && event.getType() == EventType.CLIENT_REPLY_RECEIVED) {
+ // when the client application reports receiving a reply,
+ // succeed the measurement
+ this.replyReceivedAtClient = event.getOccurrenceTime();
+ this.measurementState = MeasurementState.SUCCEEDED;
+ notify();
+
+ } else if (event.getSource() == this.clientApp
+ && event.getType() == EventType.CLIENT_GAVE_UP_REQUEST) {
+ // when the client application reports giving up the request,
+ // fail the measurement
+ System.out.println("client gave up request" + event);
+ this.measurementState = MeasurementState.FAILED;
+ notify();
+ }
+ }
+ }
+
+ /**
+ * Writes the results of the measurements to files.
+ */
+ private static class ResultWriter {
+
+ /**
+ * Performs the actual writing to file.
+ *
+ * @param fileName
+ * File name to write the measurement result to.
+ * @param timeInSeconds
+ * Value to be written.
+ */
+ private synchronized void writeToFile(String fileName,
+ long timeInSeconds) {
+ try {
+ File file = new File(network.getWorkingDirectory()
+ .getAbsolutePath()
+ + File.separator + fileName);
+ if (!file.exists()) {
+ FileWriter fw = new FileWriter(file);
+ fw.append("" + timeInSeconds);
+ fw.close();
+ } else {
+ FileWriter fw = new FileWriter(file, true);
+ fw.append("," + timeInSeconds);
+ fw.close();
+ }
+ } catch (IOException e) {
+ System.err.println(new Date()
+ + ": Could not write measurement result to file.");
+ }
+ }
+
+ /**
+ * Writes the result of an online propagation measurement to file.
+ *
+ * @param timeInSeconds
+ * Measurement result.
+ * @throws IOException
+ * Thrown if the file could not be written for some reason.
+ */
+ private synchronized void writeOnlinePropagation(long timeInSeconds) {
+ this.writeToFile("online-propagation", timeInSeconds);
+ }
+
+ /**
+ * Writes the result of an offline propagation measurement to file.
+ *
+ * @param timeInSeconds
+ * Measurement result.
+ * @throws IOException
+ * Thrown if the file could not be written for some reason.
+ */
+ private synchronized void writeOfflinePropagation(long timeInSeconds) {
+ this.writeToFile("offline-propagation", timeInSeconds);
+ }
+
+ /**
+ * Writes the result of a descriptor propagation measurement to file.
+ *
+ * @param timeInSeconds
+ * Measurement result.
+ * @throws IOException
+ * Thrown if the file could not be written for some reason.
+ */
+ private synchronized void writeDescriptorPropagation(long timeInSeconds) {
+ this.writeToFile("descriptor-propagation", timeInSeconds);
+ }
+
+ /**
+ * Writes the result of a hidden service request measurement to file.
+ *
+ * @param timeInSeconds
+ * Measurement result.
+ * @throws IOException
+ * Thrown if the file could not be written for some reason.
+ */
+ private synchronized void writeHiddenServiceRequest(long timeInSeconds) {
+ this.writeToFile("hidden-service-requests", timeInSeconds);
+ }
+ }
+
+ /**
+ * Writes the results of the measurements to files.
+ */
+ private static ResultWriter resultWriter = new ResultWriter();
+
+ /**
+ * The network configuration.
+ */
+ private static Network network;
+
+ /**
+ * The event manager of this application.
+ */
+ private static EventManager manager;
+
+ /**
+ * List of all directory nodes.
+ */
+ private static List<DirectoryNode> runningDirs;
+
+ /**
+ * List of all router nodes.
+ */
+ private static List<RouterNode> runningRouters;
+
+ /**
+ * Sets up and runs the test.
+ *
+ * @param args
+ * Optionally, a base port number can be passed so that the
+ * started Tor processes use ports starting from that number (up
+ * to the next few hundreds).
+ * @throws TorProcessException
+ * Thrown if there is a problem with the JVM-external Tor
+ * processes that we cannot handle.
+ */
+ public static void main(String[] args) {
+ try {
+
+ System.out.println("WARNING! This long-running test case "
+ + "sometimes fails for yet unknown reasons, "
+ + "possibly because of the test case "
+ + "implementation! Don't rely on it!");
+
+ int portStart = 7000;
+
+ if (args.length == 1) {
+ try {
+ portStart = Integer.parseInt(args[0]);
+ } catch (NumberFormatException e) {
+ System.out.println("Usage: java "
+ + DistributedStorage.class.getCanonicalName()
+ + " [basePort]");
+ System.exit(1);
+ }
+ }
+
+ // create a network to initialize a test case
+ network = NetworkFactory.createNetwork("distributed-storage");
+
+ System.out.println(new Date() + ": Starting test run "
+ + network.getWorkingDirectory().getName());
+
+ // obtain reference to event manager to be able to respond to events
+ manager = network.getEventManager();
+
+ // add event type patterns for events that only occur in a modified
+ // Tor
+ manager.registerEventTypePattern(
+ "Hidden service routing table has changed",
+ EventType.NODE_ROUTING_TABLE_CHANGED);
+ manager
+ .registerEventTypePattern(
+ "Sending publish request for "
+ + "v2 descriptor for "
+ + "service '.*' with descriptor ID '.*' with validity of .* "
+ + "seconds to hidden service directory '.*' on port .*",
+ EventType.BOB_SENDING_PUBLISH_DESC);
+ manager.registerEventTypePattern("Successfully stored service "
+ + "descriptor with desc ID " + "'.*' and len .*",
+ EventType.HSDIR_DESC_STORED);
+ manager.registerEventTypePattern("Sending fetch request for v2 "
+ + "descriptor for "
+ + "service '.*' with descriptor ID '.*' to hidden "
+ + "service directory '.*' on port .*",
+ EventType.ALICE_SENDING_FETCH_DESC);
+ manager.registerEventTypePattern(
+ "Successfully stored service descriptor with "
+ + "desc ID '.*'", EventType.HSDIR_DESC_STORED);
+
+ // create two directory nodes with parameters (router name, control
+ // port, SOCKS port, OR port, dir port)
+ runningDirs = new ArrayList<DirectoryNode>();
+ DirectoryNode dir1 = network.createDirectory("dir1", portStart + 1,
+ portStart + 2, portStart + 3, portStart + 4);
+ DirectoryNode dir2 = network.createDirectory("dir2",
+ portStart + 11, portStart + 12, portStart + 13,
+ portStart + 14);
+ dir1.addConfiguration("MinUptimeHidServDirectoryV2 0 minutes");
+ dir2.addConfiguration("MinUptimeHidServDirectoryV2 0 minutes");
+ runningDirs.add(dir1);
+ runningDirs.add(dir2);
+
+ runningRouters = new ArrayList<RouterNode>();
+
+ // create 9 router nodes with parameters (router name, control port,
+ // SOCKS port, OR port, dir mirror port)
+ int routerCounter = 1;
+ for (; routerCounter < 10; routerCounter++) {
+ RouterNode router = network.createRouter("router0"
+ + routerCounter, portStart + routerCounter * 10 + 11,
+ portStart + routerCounter * 10 + 12, portStart
+ + routerCounter * 10 + 13, portStart
+ + routerCounter * 10 + 14);
+ router.addConfiguration("HidServDirectoryV2 1");
+ router.addConfiguration("FetchHidServDescriptors 0");
+ router.addConfiguration("FetchV2HidServDescriptors 1");
+ runningRouters.add(router);
+ }
+
+ // configure nodes of this network to be part of a private network
+ network.configureAsPrivateNetwork();
+
+ // write configuration of proxy node
+ network.writeConfigurations();
+ System.out.println(new Date()
+ + ": Successfully written configurations!");
+
+ // start proxy node and wait until it has opened a circuit with a
+ // timeout of 10 seconds
+ if (!network.startNodes(10000)) {
+
+ // failed to start the proxy
+ System.out.println(new Date() + ": Failed to start nodes!");
+ System.exit(1);
+ }
+ System.out.println(new Date() + ": Successfully started nodes!");
+
+ // start observers for all initial routers which might become hidden
+ // service directories after some time (if not stopped before)
+ for (RouterNode router : runningRouters) {
+ new HiddenServiceDirectoryObserver(router).start();
+ }
+
+ // start measurement of descriptor propagation
+ new DescriptorObserverStarter();
+
+ // start thread that will periodically try to access a hidden
+ // service
+ new HiddenServiceRequestStarter().start();
+
+ // hup until proxy has built circuits (6 retries, 10 seconds timeout
+ // each)
+ if (!network.hupUntilUp(6, 10000)) {
+
+ // failed to build circuits
+ System.out.println(new Date() + ": Failed to build circuits!");
+ System.exit(1);
+ }
+ System.out.println(new Date() + ": Successfully built circuits!");
+
+ int HOURS_TO_WAIT = 30;
+
+ // let it run for HOURS_TO_WAIT minutes...
+ System.out.println(new Date() + ": Waiting for " + HOURS_TO_WAIT
+ + " hours, changing node population every hour...");
+
+ long hiddenServiceStableTime = System.currentTimeMillis() + 30 * 60 * 1000;
+
+ Random rnd = new Random();
+ long waitingTime = 0;
+
+ for (int i = 0; i < HOURS_TO_WAIT - 1; i++) {
+ // wait for 15 to 30 minutes
+ waitingTime = 15 * 60 * 1000 + rnd.nextInt(15 * 60 * 1000);
+ System.out.print("Waiting for " + waitingTime / 1000
+ + " seconds");
+ long startOfWaiting = System.currentTimeMillis();
+ while (System.currentTimeMillis() < startOfWaiting
+ + waitingTime) {
+ System.out.print(".");
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ System.out.println();
+
+ // shut down one node
+ int candidate = rnd.nextInt(runningRouters.size());
+ RouterNode removedRouter = runningRouters.remove(candidate);
+ System.out.print(new Date() + ": Shutting down router "
+ + removedRouter.getNodeName() + "... ");
+ removedRouter.shutdown();
+ System.out.println("succeeded");
+
+ // wait for the rest of the half hour
+ waitingTime = 30 * 60 * 1000 - waitingTime;
+ System.out.print("Waiting for " + waitingTime / 1000
+ + " seconds");
+ startOfWaiting = System.currentTimeMillis();
+ while (System.currentTimeMillis() < startOfWaiting
+ + waitingTime) {
+ System.out.print(".");
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ System.out.println();
+
+ // wait for 15 to 30 minutes
+ waitingTime = 15 * 60 * 1000 + rnd.nextInt(15 * 60 * 1000);
+ System.out.print("Waiting for " + waitingTime / 1000
+ + " seconds");
+ startOfWaiting = System.currentTimeMillis();
+ while (System.currentTimeMillis() < startOfWaiting
+ + waitingTime) {
+ System.out.print(".");
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ System.out.println();
+
+ // start another node
+ boolean startSuccessful = false;
+ do {
+ RouterNode newRouter = network.createRouter("router"
+ + routerCounter, portStart + routerCounter * 10
+ + 11, portStart + routerCounter * 10 + 12,
+ portStart + routerCounter * 10 + 13, portStart
+ + routerCounter * 10 + 14);
+
+ newRouter.addConfiguration("HidServDirectoryV2 1");
+ newRouter.addConfiguration("FetchHidServDescriptors 0");
+ newRouter.addConfiguration("FetchV2HidServDescriptors 1");
+
+ // if the system is running for at least 24 hours, it can be
+ // considered stable, so that hidden services can be started
+ long now = System.currentTimeMillis();
+ if (now >= hiddenServiceStableTime) {
+
+ // add hidden service to the configuration of the new
+ // router
+ newRouter.addHiddenService("hidServ" + routerCounter,
+ portStart + routerCounter * 10 + 15, 80);
+
+ // let the new router only publish v2 descriptors
+ newRouter
+ .addConfiguration("PublishHidServDescriptors 0");
+ newRouter
+ .addConfiguration("PublishV2HidServDescriptors 1");
+ }
+
+ // re-configure nodes of this network to be part of a
+ // private network
+ network.configureAsPrivateNetwork();
+
+ newRouter.writeConfiguration();
+ System.out
+ .print(new Date()
+ + ": Starting router "
+ + newRouter.getNodeName()
+ + (now >= hiddenServiceStableTime ? " with hidden service"
+ : "") + "... ");
+ if (newRouter.startNode(15000)) {
+ System.out.println("succeeded");
+
+ runningRouters.add(newRouter);
+
+ startSuccessful = true;
+
+ // if a hidden service was started, start an observer
+ // for it
+ if (now >= hiddenServiceStableTime) {
+ new HiddenServiceObserver(newRouter, newRouter
+ .getOnionAddress("hidServ" + routerCounter,
+ 2)).start();
+ }
+
+ // start observer for the new hidden service directory
+ new HiddenServiceDirectoryObserver(newRouter).start();
+
+ } else {
+ System.out.println("failed");
+
+ // trying to start another router node...
+ // TODO limit this restarting of nodes to a certain
+ // number to get aware of permanent errors
+ }
+
+ routerCounter++;
+ } while (!startSuccessful);
+
+ // wait for the rest of the half hour
+ waitingTime = 30 * 60 * 1000 - waitingTime;
+ System.out.print("Waiting for " + waitingTime / 1000
+ + " seconds");
+ startOfWaiting = System.currentTimeMillis();
+ while (System.currentTimeMillis() < startOfWaiting
+ + waitingTime) {
+ System.out.print(".");
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ System.out.println();
+
+ System.out.println(new Date() + ": Waiting for another "
+ + (HOURS_TO_WAIT - i - 1) + " hour"
+ + (i == HOURS_TO_WAIT - 2 ? "" : "s") + " ...");
+ }
+
+ // waiting for the last hour
+ try {
+ Thread.sleep(1L * 60L * 60L * 1000L);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+
+ // shut down nodes
+ network.shutdownNodes();
+
+ System.out.println(new Date() + ": Exiting...");
+ System.exit(1);
+
+ } catch (TorProcessException e) {
+ System.out.println(e);
+ e.printStackTrace();
+ System.exit(1);
+ } catch (Throwable t) {
+ System.out.println(t);
+ t.printStackTrace();
+ System.exit(1);
+ }
+ }
+}
Added: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/HidServDirectoryTest.java
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/HidServDirectoryTest.java (rev 0)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/HidServDirectoryTest.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -0,0 +1,431 @@
+package de.uniba.wiai.lspi.puppetor.diststorage;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.Socket;
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import de.uniba.wiai.lspi.puppetor.DirectoryNode;
+import de.uniba.wiai.lspi.puppetor.EventSource;
+import de.uniba.wiai.lspi.puppetor.Network;
+import de.uniba.wiai.lspi.puppetor.NetworkFactory;
+import de.uniba.wiai.lspi.puppetor.RouterNode;
+import de.uniba.wiai.lspi.puppetor.TorProcessException;
+
+/* TODO Document this class properly */
+
+/**
+ * <p>
+ * Test application of the distributed storage for hidden service descriptors as
+ * described in proposal 114. <b>WARNING: This example does not work with an
+ * unmodified Tor!</b>
+ * </p>
+ *
+ * <p>
+ * When running, the example starts a network of local Tor processes, consisting
+ * of 2 directory nodes and 4 onion routers, and lets it stabilize for one
+ * minutes. Afterwards, it performs the following test operations:
+ * </p>
+ *
+ * <ol>
+ * <li>Post a number of freshly generated v2 rendezvous service descriptors via
+ * HTTP POST to the responsible (and one irresponsible) hidden service directory
+ * and observe if the operation succeeds.</li>
+ * <li>Fetch the previously posted descriptors (and one freshly generated one)
+ * via HTTP GET from the responsible (and one irresponsible) hidden service
+ * directory and validate the reply.</li>
+ * <li>Request the replicas of descriptors in all intervals between two hidden
+ * service directories from all such and display the replies.</li>
+ * </ol>
+ *
+ * <p>
+ * Note: This test application requires crypto from BouncyCastle; you need to
+ * include the jar "bcprov-jdk16-137.jar" into your classpath and download and
+ * install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction
+ * Policy Files 6 from the Sun homepage. (Installation directory under Ubuntu
+ * Linux is /usr/lib/jvm/java-6-sun-1.6.0.00/jre/lib/security/)
+ *
+ * @author kloesing
+ */
+public class HidServDirectoryTest {
+
+ /**
+ * Helper method: Sends a publish request containing the given <b>descString</b>
+ * via HTTP POST to the given <b>dirPort</b>.
+ *
+ * @param descString
+ * The ASCII-encoded descriptor to be published.
+ * @param dirPort
+ * The dir port to send the request to.
+ * @return The HTTP reply.
+ */
+ private static boolean sendDescriptor(String descString, int dirPort) {
+
+ try {
+
+ // create new socket using Tor as proxy
+ Socket s = new Socket("localhost", dirPort);
+
+ // open output stream to write request
+ PrintStream out = new PrintStream(s.getOutputStream());
+
+ out
+ .print("POST /tor/rendezvous2/publish HTTP/1.0\r\nContent-Length: ");
+ out.print(descString.length());
+ out.print("\r\nHost: 127.0.0.1:");
+ out.print(dirPort);
+ out.print("\r\n\r\n");
+ out.print(descString);
+ out.print("\r\n");
+
+ // open input stream to read reply
+ BufferedReader in = new BufferedReader(new InputStreamReader(s
+ .getInputStream()));
+
+ // read reply
+ String inputLine = null;
+ StringBuilder result = new StringBuilder();
+ while ((inputLine = in.readLine()) != null) {
+ // System.out.println(inputLine);
+ result.append(inputLine + "\n");
+ }
+
+ // clean up socket
+ in.close();
+ out.close();
+ s.close();
+
+ if (result.toString().startsWith("HTTP/1.0 200 ")) {
+ return true;
+ }
+ return false;
+
+ } catch (IOException e) {
+ System.out.println("Sending failed!");
+ return false;
+ }
+ }
+
+ /**
+ * Helper method: Sends a fetch request for the given <b>descId</b> via
+ * HTTP GET to the given <b>dirPort</b>.
+ *
+ * @param descId
+ * The descriptor ID to be fetched.
+ * @param dirPort
+ * The dir port to send the request to.
+ * @return The HTTP reply.
+ */
+ private static String fetchDescriptor(String descId, int dirPort) {
+
+ try {
+
+ // create new socket using Tor as proxy
+ Socket s = new Socket("localhost", dirPort);
+
+ // open output stream to write request
+ PrintStream out = new PrintStream(s.getOutputStream());
+
+ out.print("GET /tor/rendezvous2/");
+ out.print(descId);
+ out.print(" HTTP/1.0\r\n\r\n");
+
+ // open input stream to read reply
+ BufferedReader in = new BufferedReader(new InputStreamReader(s
+ .getInputStream()));
+
+ // read reply
+ String inputLine = null;
+ StringBuilder result = new StringBuilder();
+ while ((inputLine = in.readLine()) != null) {
+ // System.out.println(inputLine);
+ result.append(inputLine + "\n");
+ }
+
+ // clean up socket
+ in.close();
+ out.close();
+ s.close();
+
+ return result.toString();
+
+ } catch (IOException e) {
+ System.out.println("Sending failed!");
+ return null;
+ }
+ }
+
+ /**
+ * Sets up and runs the test.
+ *
+ * @param args
+ * Command-line arguments (ignored).
+ * @throws TorProcessException
+ * Thrown if there is a problem with the JVM-external Tor
+ * processes that we cannot handle.
+ */
+ public static void main(String[] args) throws TorProcessException {
+
+ int numberOfDescs = 10;
+
+ if (args.length == 1) {
+ try {
+ numberOfDescs = Integer.parseInt(args[0]);
+ } catch (NumberFormatException e) {
+ System.out.println("Usage: java "
+ + HidServDirectoryTest.class.getCanonicalName()
+ + " [numberOfDescs]");
+ System.exit(1);
+ }
+ }
+
+ if (numberOfDescs < 0 || numberOfDescs > 1000) {
+ System.out.println("Bad value for numberOfDescs: " + numberOfDescs
+ + "! Setting to 10.");
+ numberOfDescs = 10;
+ }
+
+ Security.addProvider(new BouncyCastleProvider());
+
+ // create a private tor network with hs dirs, let it stabilize
+ // create a network to initialize a test case
+ Network network = NetworkFactory.createNetwork("example4");
+
+ // create two directory nodes with parameters (router name, control
+ // port, SOCKS port, OR port, dir port)
+ DirectoryNode dir1 = network.createDirectory("dir1", 7001, 7002, 7003,
+ 7004);
+ DirectoryNode dir2 = network.createDirectory("dir2", 7011, 7012, 7013,
+ 7014);
+
+ // create three router nodes with parameters (router name, control port,
+ // SOCKS port, OR port, dir mirror port)
+ RouterNode router1 = network.createRouter("router1", 7021, 7022, 7023,
+ 7024);
+ RouterNode router2 = network.createRouter("router2", 7031, 7032, 7033,
+ 7034);
+ RouterNode router3 = network.createRouter("router3", 7041, 7042, 7043,
+ 7044);
+ RouterNode router4 = network.createRouter("router4", 7051, 7052, 7053,
+ 7054);
+
+ Set<RouterNode> allRouters = new HashSet<RouterNode>();
+ allRouters.add(dir1);
+ allRouters.add(dir2);
+ allRouters.add(router1);
+ allRouters.add(router2);
+ allRouters.add(router3);
+ allRouters.add(router4);
+
+ // configure all nodes hidden service directories
+ dir1.addConfiguration("HidServDirectoryV2 1");
+ dir2.addConfiguration("HidServDirectoryV2 1");
+ dir1.addConfiguration("MinUptimeHidServDirectoryV2 0 minutes");
+ dir2.addConfiguration("MinUptimeHidServDirectoryV2 0 minutes");
+ router1.addConfiguration("HidServDirectoryV2 1");
+ router2.addConfiguration("HidServDirectoryV2 1");
+ router3.addConfiguration("HidServDirectoryV2 1");
+ router4.addConfiguration("HidServDirectoryV2 1");
+
+ // configure nodes of this network to be part of a private network
+ network.configureAsPrivateNetwork();
+
+ // write configuration of proxy node
+ network.writeConfigurations();
+
+ // start proxy node and wait until it has opened a circuit with a
+ // timeout of 15 seconds
+ if (!network.startNodes(15000)) {
+
+ // failed to start the proxy
+ System.out.println("Failed to start nodes!");
+ return;
+ }
+ System.out.println("Successfully started nodes!");
+
+ // hup until proxy has built circuits (10 retries, 10 seconds timeout
+ // each)
+ if (!network.hupUntilUp(20, 6000)) {
+
+ // failed to build circuits
+ System.out.println("Failed to build circuits!");
+ return;
+ }
+ System.out.println("Successfully built circuits!");
+
+ // wait for 30 minutes for stabilization
+ try {
+ Thread.sleep(30 * 60 * 1000);
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
+ }
+
+ // add all routers to the global routing table
+ HidServRoutingTable routingTable = new HidServRoutingTable();
+ for (RouterNode router : allRouters) {
+ routingTable.addHiddenServiceDirectory(router);
+ }
+ System.out.println(routingTable);
+
+ Random rnd = new Random();
+
+ List<RendezvousServiceDescriptor> allDescs = new ArrayList<RendezvousServiceDescriptor>();
+
+ // post descs (correct and false ones) to all responsible dirs
+
+ for (int i = 0; i < numberOfDescs; i++) {
+
+ RendezvousServiceDescriptor desc = RendezvousServiceDescriptor
+ .prepare();
+
+ // try to parse and verify it
+ RendezvousServiceDescriptor parsed = RendezvousServiceDescriptor
+ .parseAndVerify(desc.getDescriptorString());
+ System.out.println("Can generated descriptor with ID "
+ + parsed.getDescriptorID() + " be parsed and verified? "
+ + (parsed != null));
+
+ allDescs.add(desc);
+
+ // post to all responsible and ONE irresponsible node
+ Set<EventSource> responsibleNodes = routingTable
+ .getResponsibleHSDirs(desc.getDescriptorID());
+
+ for (EventSource node : responsibleNodes) {
+
+ System.out.print("Posting descriptor to node " + node.getName()
+ + "... ");
+ boolean result = sendDescriptor(desc.getDescriptorString(),
+ ((RouterNode) node).getDirPort());
+ System.out.println(result ? "successful" : "failed");
+
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // post to one false node
+ List<EventSource> notResponsible = new ArrayList<EventSource>(
+ allRouters);
+ notResponsible.removeAll(responsibleNodes);
+ EventSource node = notResponsible.get(rnd.nextInt(notResponsible
+ .size()));
+ System.out.print("Posting descriptor to IRRESPONSIBLE node "
+ + node.getName() + "... ");
+ boolean result = sendDescriptor(desc.getDescriptorString(),
+ ((RouterNode) node).getDirPort());
+ System.out.println(result ? "successful" : "failed");
+
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+
+ }
+
+ // fetch descs (existing and non-existing ones) from each resp dir
+ RendezvousServiceDescriptor freshDesc = RendezvousServiceDescriptor
+ .prepare();
+ allDescs.add(freshDesc);
+ for (RendezvousServiceDescriptor desc : allDescs) {
+
+ Set<EventSource> responsibleNodes = routingTable
+ .getResponsibleHSDirs(desc.getDescriptorID());
+
+ for (EventSource node : responsibleNodes) {
+
+ System.out.print("Fetching descriptor from node "
+ + node.getName() + "... ");
+ String result = fetchDescriptor(desc.getDescriptorID(),
+ ((RouterNode) node).getDirPort());
+
+ RendezvousServiceDescriptor received = RendezvousServiceDescriptor
+ .parseAndVerify(result);
+ System.out
+ .println((received != null) ? "successful" : "failed");
+
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // fetch from one false node
+ List<EventSource> notResponsible = new ArrayList<EventSource>(
+ allRouters);
+ notResponsible.removeAll(responsibleNodes);
+ EventSource node = notResponsible.get(rnd.nextInt(notResponsible
+ .size()));
+ System.out.print("Fetching descriptor from IRRESPONSIBLE node "
+ + node.getName() + "... ");
+ String result = fetchDescriptor(desc.getDescriptorID(),
+ ((RouterNode) node).getDirPort());
+
+ RendezvousServiceDescriptor received = RendezvousServiceDescriptor
+ .parseAndVerify(result);
+ System.out.println((received != null) ? "successful" : "failed");
+
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+
+ }
+ allDescs.remove(freshDesc);
+
+ // request all replicas from all nodes
+ List<RouterNode> allNodes = routingTable.getAllHSDirs();
+ for (int i = 0; i < allNodes.size(); i++) {
+ RouterNode from = allNodes.get(i);
+ RouterNode to = allNodes.get(i < allNodes.size() - 1 ? i + 1 : 0);
+
+ for (RouterNode router : allRouters) {
+ String request = from.getFingerprintBase32() + "-"
+ + to.getFingerprintBase32();
+ System.out.print("Fetching replicas for interval " + request
+ + " from node " + router.getName() + "... ");
+ String replicas = fetchDescriptor(request, router.getDirPort());
+ int numReplicas = 0;
+ int numParsed = 0;
+ StringBuilder sb = new StringBuilder();
+ while (replicas.contains("rendezvous-service-descriptor ")) {
+ replicas = replicas.substring(replicas
+ .indexOf("rendezvous-service-descriptor "));
+ String desc2;
+ if (replicas.indexOf("rendezvous-service-descriptor ", 16) > 0) {
+ desc2 = replicas.substring(0, replicas.indexOf(
+ "rendezvous-service-descriptor ", 16));
+ replicas = replicas.substring(desc2.length());
+ numReplicas++;
+ } else {
+ desc2 = replicas;
+ replicas = "";
+ numReplicas++;
+ }
+ RendezvousServiceDescriptor received = RendezvousServiceDescriptor
+ .parseAndVerify(desc2);
+ if (received != null) {
+ sb.append(received.getDescriptorID() + " ");
+ numParsed++;
+ }
+ }
+ System.out.println(numReplicas + " replicas, " + numParsed
+ + " successfully parsed: " + sb.toString());
+ }
+ }
+
+ // shut down nodes
+ network.shutdownNodes();
+
+ }
+}
Added: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/HidServRoutingTable.java
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/HidServRoutingTable.java (rev 0)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/HidServRoutingTable.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -0,0 +1,195 @@
+package de.uniba.wiai.lspi.puppetor.diststorage;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import de.uniba.wiai.lspi.puppetor.EventSource;
+import de.uniba.wiai.lspi.puppetor.RouterNode;
+
+/* TODO Document this class properly */
+
+/**
+ * Manages a routing table of hidden service directories that can be used to
+ * maintain a global view of a Tor network under test and to synchronize on
+ * changes on it.
+ */
+public class HidServRoutingTable {
+
+ /** Compares two base32-encoded IDs to each other. */
+ private static Comparator<String> comparator = new Comparator<String>() {
+
+ private int[] base32Lookup = { 0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D,
+ 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
+ 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF };
+
+ private byte[] base32toHex(String base32) {
+
+ int i, index, lookup, offset, digit;
+
+ byte[] bytes = new byte[base32.length() * 5 / 8];
+
+ for (i = 0, index = 0, offset = 0; i < base32.length(); i++) {
+ lookup = base32.charAt(i) - '0';
+
+ /* Skip chars outside the lookup table */
+ if (lookup < 0 || lookup >= base32Lookup.length)
+ continue;
+
+ digit = base32Lookup[lookup];
+
+ /* If this digit is not in the table, ignore it */
+ if (digit == 0xFF)
+ continue;
+
+ if (index <= 3) {
+ index = (index + 5) % 8;
+ if (index == 0) {
+ bytes[offset] |= digit;
+ offset++;
+ if (offset >= bytes.length)
+ break;
+ } else
+ bytes[offset] |= digit << (8 - index);
+ } else {
+ index = (index + 5) % 8;
+ bytes[offset] |= (digit >>> index);
+ offset++;
+
+ if (offset >= bytes.length)
+ break;
+ bytes[offset] |= digit << (8 - index);
+ }
+ }
+ return bytes;
+ }
+
+ public int compare(String arg0, String arg1) {
+ byte[] bytes0 = base32toHex(arg0);
+ byte[] bytes1 = base32toHex(arg1);
+ for (int i = 0; i < bytes0.length && i < bytes1.length; i++) {
+ if ((bytes0[i] + 256) % 256 < (bytes1[i] + 256) % 256) {
+ return -1;
+ } else if ((bytes0[i] + 256) % 256 > (bytes1[i] + 256) % 256) {
+ return 1;
+ }
+ }
+ return 0;
+ }
+ };
+
+ /**
+ * Sorted map of hidden service directory IDs and the corresponding router
+ * node.
+ */
+ private SortedMap<String, RouterNode> hsDirs = new TreeMap<String, RouterNode>(
+ comparator);
+
+ /**
+ * Adds a hidden service directory to the routing table. The node needs to
+ * be started before being added!
+ *
+ * @param hsdNode
+ * The node to be added as hidden service directory.
+ */
+ public synchronized void addHiddenServiceDirectory(RouterNode hsdNode) {
+ this.hsDirs.put(hsdNode.getFingerprintBase32(), hsdNode);
+ notifyAll();
+ }
+
+ public synchronized String toString() {
+ StringBuilder sb = new StringBuilder(this.getClass().getSimpleName()
+ + ": hsdirs={ ");
+ for (String nodeID : this.hsDirs.keySet()) {
+ sb.append(this.hsDirs.get(nodeID).getName() + "("
+ + nodeID.substring(0, 4) + "),");
+ }
+ String result = sb.toString();
+ result = result.substring(0, result.length() - 1) + " }";
+ return result;
+ }
+
+ /**
+ * Removes a hidden service directory from the routing table.
+ *
+ * @param hsdNode
+ * The hidden service directory to be removed.
+ */
+ public synchronized void removeHiddenServiceDirectory(RouterNode hsdNode) {
+ this.hsDirs.remove(hsdNode.getFingerprintBase32());
+ notifyAll();
+ }
+
+ /**
+ * Returns the set of responsible hidden service directories for the given
+ * descriptor ID.
+ *
+ * @param descID
+ * The descriptor ID for which the responsible hidden service
+ * directories shall be determined.
+ * @return The set of responsible hidden service directories.
+ */
+ public synchronized Set<EventSource> getResponsibleHSDirs(String descID) {
+
+ Set<EventSource> result = new HashSet<EventSource>();
+ if (this.hsDirs.size() < 3) {
+ return result;
+ }
+
+ for (EventSource s : this.hsDirs.tailMap(descID).values()) {
+ if (result.size() < 3) {
+ result.add(s);
+ } else {
+ break;
+ }
+ }
+
+ for (EventSource s : this.hsDirs.values()) {
+ if (result.size() < 3) {
+ result.add(s);
+ } else {
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a list of all hidden service directories in the routing table,
+ * ordered by their node IDs.
+ *
+ * @return List of all hidden service directories.
+ */
+ public synchronized List<RouterNode> getAllHSDirs() {
+ List<RouterNode> result = new ArrayList<RouterNode>();
+ for (RouterNode router : this.hsDirs.values()) {
+ result.add(router);
+ }
+ return result;
+ }
+
+ /**
+ * Waits for the given number of millis until either a node is added or
+ * removed.
+ *
+ * @param timeToWait
+ * The number of millis to wait.
+ */
+ public synchronized void waitForRoutingTableChange(long timeToWait) {
+ try {
+ wait(timeToWait);
+ } catch (InterruptedException e) {
+ }
+ }
+}
Added: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/RendezvousServiceDescriptor.java
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/RendezvousServiceDescriptor.java (rev 0)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/diststorage/RendezvousServiceDescriptor.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -0,0 +1,904 @@
+package de.uniba.wiai.lspi.puppetor.diststorage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
+import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSAEngine;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.jce.provider.JCERSAPublicKey;
+import org.bouncycastle.openssl.PEMReader;
+import org.bouncycastle.util.encoders.Base64;
+
+/* TODO Document this class properly */
+
+/**
+ * Data structure of a v2 rendezvous service descriptor that further contains
+ * methods to encode, parse, and verify ASCII-encoded representations.
+ *
+ * The cryptographic routines have been adopted from the Onion Coffee project.
+ *
+ * @author kloesing
+ */
+public class RendezvousServiceDescriptor {
+
+ /** Descriptor ID consisting of 32 base32 chars. */
+ private String descriptorID;
+
+ /** Version number. */
+ private int version;
+
+ /** Permanent key. */
+ private String publicKeyString;
+
+ /** Secret ID part. */
+ private String secretIdPart;
+
+ /** Publication time. */
+ private String publicationTime;
+
+ /** Protocol versions. */
+ private int protocolVersions;
+
+ /** Introduction points. */
+ private String introPointsString;
+
+ /** Signature. */
+ private String signatureString;
+
+ /** ASCII-encoded representation; */
+ private String descriptorString;
+
+ @Override
+ public String toString() {
+ return "descriptorID=\"" + descriptorID + "\", " + "version=" + version
+ + ", " + "publicKeyString=\"" + publicKeyString + "\", "
+ + "secretIdPart=\"" + secretIdPart + "\", "
+ + "publicationTime=\"" + publicationTime + "\", "
+ + "protocolVersions=" + protocolVersions + ", "
+ + "introPointsString=\"" + introPointsString + "\", "
+ + "signatureString=\"" + signatureString + "\"";
+ }
+
+ /**
+ * performed on server side, as encrypted ipos and secret-id-part we take
+ * random values to make things less complicated; the hsdirs don't care.
+ */
+ public static RendezvousServiceDescriptor prepare() {
+ try {
+ RendezvousServiceDescriptor rsd = new RendezvousServiceDescriptor();
+
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA",
+ "BC");
+ generator.initialize(1024, new SecureRandom());
+ KeyPair pair = generator.generateKeyPair();
+ RSAPublicKey pubKey = (RSAPublicKey) pair.getPublic();
+ RSAPrivateKey privKey = (RSAPrivateKey) pair.getPrivate();
+ RSAPublicKeyStructure key = new RSAPublicKeyStructure(pubKey
+ .getModulus(), pubKey.getPublicExponent());
+ rsd.publicKeyString = getPEMStringFromRSAPublicKey(key);
+ RSAPrivateKeyStructure privateKey = new RSAPrivateKeyStructure(
+ privKey.getModulus(), pubKey.getPublicExponent(), privKey
+ .getPrivateExponent(), null, null, null, null, null);
+
+ Random rnd = new Random();
+ byte[] secretIdPartBytes = new byte[20];
+ rnd.nextBytes(secretIdPartBytes);
+ rsd.secretIdPart = binaryToBase32(secretIdPartBytes);
+
+ rsd.publicationTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+ .format(new Date());
+
+ rsd.version = 2;
+
+ rsd.protocolVersions = 5;
+
+ rsd.introPointsString = aesSample;
+
+ byte[] hash = getHash(getPKCS1EncodingFromRSAPublicKey(key));
+ byte[] digestInput = new byte[10 + secretIdPartBytes.length];
+ int i, j;
+ for (i = 0, j = 0; j < 10; i++, j++)
+ digestInput[i] = hash[j];
+ for (j = 0; j < secretIdPartBytes.length; i++, j++)
+ digestInput[i] = secretIdPartBytes[j];
+ byte[] descIdBin = getHash(digestInput);
+ rsd.descriptorID = toBase32(descIdBin);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("rendezvous-service-descriptor ");
+ sb.append(rsd.descriptorID);
+ sb.append("\nversion ");
+ sb.append(rsd.version);
+ sb.append("\npermanent-key\n");
+ sb.append(rsd.publicKeyString);
+ sb.append("secret-id-part ");
+ sb.append(rsd.secretIdPart);
+ sb.append("\npublication-time ");
+ sb.append(rsd.publicationTime);
+ sb.append("\nprotocol-versions ");
+ sb.append(rsd.protocolVersions);
+ sb.append("\nintroduction-points\n");
+ sb.append(rsd.introPointsString);
+ sb.append("signature\n");
+ String descWithoutSignature = sb.toString();
+
+ byte[] signature = signData(descWithoutSignature.getBytes(),
+ new RSAKeyParameters(true, privKey.getModulus(), privKey
+ .getPrivateExponent()));
+
+ String signatureString = toBase64(signature);
+
+ sb.append("-----BEGIN SIGNATURE-----\n");
+ while (signatureString.length() > 0) {
+ if (signatureString.length() > 64) {
+ sb.append(signatureString.substring(0, 64));
+ sb.append("\n");
+ signatureString = signatureString.substring(64);
+ } else {
+ sb.append(signatureString);
+ sb.append("\n");
+ signatureString = "";
+ }
+ }
+
+ sb.append("-----END SIGNATURE-----\n\n");
+ rsd.descriptorString = sb.toString();
+
+ return rsd;
+
+ } catch (Exception e) {
+ return null;
+ }
+
+ }
+
+ /** performed on client side */
+ public static RendezvousServiceDescriptor parseAndVerify(String str) {
+ RendezvousServiceDescriptor rsd = new RendezvousServiceDescriptor();
+ {
+ String rsdTag = "rendezvous-service-descriptor ";
+ if (!str.contains(rsdTag)) {
+ // System.out
+ // .println("String does not contain \"" + rsdTag + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(rsdTag));
+ rsd.descriptorString = str;
+ str = str.substring(rsdTag.length());
+ if (str.length() < 32) {
+ return null;
+ }
+ rsd.descriptorID = str.substring(0, 32);
+ str = str.substring(32);
+ }
+ {
+ String versionTag = "version ";
+ if (!str.contains(versionTag)) {
+ // System.out.println("String does not contain \"" + versionTag
+ // + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(versionTag) + versionTag.length());
+ if (!str.contains("\n")) {
+ return null;
+ }
+ rsd.version = Integer.parseInt(str.substring(0, str.indexOf("\n")));
+ str = str.substring(str.indexOf("\n") + 1);
+ }
+ {
+ String permanentKeyTag = "permanent-key\n";
+ if (!str.contains(permanentKeyTag)) {
+ // System.out.println("String does not contain \""
+ // + permanentKeyTag + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(permanentKeyTag)
+ + permanentKeyTag.length());
+ String publicKeyBegin = "-----BEGIN RSA PUBLIC KEY-----";
+ if (!str.contains(publicKeyBegin)) {
+ // System.out.println("String does not contain \""
+ // + publicKeyBegin + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(publicKeyBegin));
+ String publicKeyEnd = "-----END RSA PUBLIC KEY-----";
+ if (!str.contains(publicKeyEnd)) {
+ // System.out.println("String does not contain \"" +
+ // publicKeyEnd
+ // + "\"");
+ return null;
+ }
+ rsd.publicKeyString = str.substring(0, str.indexOf(publicKeyEnd)
+ + publicKeyEnd.length());
+ str = str.substring(str.indexOf(publicKeyEnd)
+ + publicKeyEnd.length());
+ }
+ {
+ String secretIdPartTag = "secret-id-part ";
+ if (!str.contains(secretIdPartTag)) {
+ // System.out.println("String does not contain \""
+ // + secretIdPartTag + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(secretIdPartTag)
+ + secretIdPartTag.length());
+ if (str.length() < 32) {
+ return null;
+ }
+ rsd.secretIdPart = str.substring(0, 32);
+ str = str.substring(32);
+ }
+ {
+ String publicationTimeTag = "publication-time ";
+ if (!str.contains(publicationTimeTag)) {
+ // System.out.println("String does not contain \""
+ // + publicationTimeTag + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(publicationTimeTag)
+ + publicationTimeTag.length());
+ if (!str.contains("\n")) {
+ // System.out.println("String does not contain \"\\n\"");
+ return null;
+ }
+ rsd.publicationTime = str.substring(0, str.indexOf("\n"));
+ str = str.substring(str.indexOf("\n") + 1);
+ }
+ {
+ String protocolVersionsTag = "protocol-versions ";
+ if (!str.contains(protocolVersionsTag)) {
+ // System.out.println("String does not contain \""
+ // + protocolVersionsTag + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(protocolVersionsTag)
+ + protocolVersionsTag.length());
+ if (!str.contains("\n")) {
+ return null;
+ }
+ rsd.protocolVersions = Integer.parseInt(str.substring(0, str
+ .indexOf("\n")));
+ str = str.substring(str.indexOf("\n") + 1);
+ }
+ {
+ String introPointsTag = "introduction-points\n";
+ if (!str.contains(introPointsTag)) {
+ // System.out.println("String does not contain \""
+ // + introPointsTag + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(introPointsTag)
+ + introPointsTag.length());
+ String introPointsBegin = "-----BEGIN AES ENCRYPTED MESSAGE-----";
+ if (!str.contains(introPointsBegin)) {
+ // System.out.println("String does not contain \""
+ // + introPointsBegin + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(introPointsBegin));
+ String introPointsEnd = "-----END AES ENCRYPTED MESSAGE-----";
+ if (!str.contains(introPointsEnd)) {
+ // System.out.println("String does not contain \""
+ // + introPointsEnd + "\"");
+ return null;
+ }
+ rsd.introPointsString = str.substring(0, str
+ .indexOf(introPointsEnd)
+ + introPointsEnd.length());
+ str = str.substring(str.indexOf(introPointsEnd)
+ + introPointsEnd.length());
+ }
+ {
+ String signatureTag = "signature\n";
+ if (!str.contains(signatureTag)) {
+ // System.out.println("String does not contain \"" +
+ // signatureTag
+ // + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(signatureTag)
+ + signatureTag.length());
+ String signatureBegin = "-----BEGIN SIGNATURE-----\n";
+ if (!str.contains(signatureBegin)) {
+ // System.out.println("String does not contain \""
+ // + signatureBegin + "\"");
+ return null;
+ }
+ str = str.substring(str.indexOf(signatureBegin)
+ + signatureBegin.length());
+ String signatureEnd = "\n-----END SIGNATURE-----\n";
+ if (!str.contains(signatureEnd)) {
+ // System.out.println("String does not contain \"" +
+ // signatureEnd
+ // + "\"");
+ return null;
+ }
+ rsd.signatureString = str.substring(0, str.indexOf(signatureEnd));
+ str = str.substring(str.indexOf(signatureEnd)
+ + signatureEnd.length());
+ }
+
+ // signature okay?
+ JCERSAPublicKey rsaKey = getRSAPublicKeyFromPEMString(rsd.publicKeyString);
+ RSAPublicKeyStructure pubKey = getRSAPublicKeyStructureFromJCERSAPublicKey(rsaKey);
+ String descWithoutSignature = rsd.descriptorString;
+ descWithoutSignature = descWithoutSignature.substring(0,
+ descWithoutSignature.indexOf("-----BEGIN SIGNATURE-----"));
+ // System.out.println("Desc without signature: '" + descWithoutSignature
+ // + "'");
+ // byte[] hash = Encryption.getHash(descWithoutSignature.getBytes());
+ byte[] sig = parseBase64(rsd.signatureString);
+ // System.out.println("Checking signature for hash: ");
+ // for (byte b : hash)
+ // System.out.print(b + " ");
+ boolean signatureOkay = verifySignature(sig, pubKey,
+ descWithoutSignature.getBytes());
+ if (!signatureOkay) {
+ // System.out.println("Signature not okay!");
+ return null;
+ }
+
+ // descriptor id okay?
+ byte[] hash = getHash(getPKCS1EncodingFromRSAPublicKey(pubKey));
+ byte[] secretIdPart = base32toBinary(rsd.secretIdPart);
+ byte[] digestInput = new byte[10 + secretIdPart.length];
+ int i, j;
+ for (i = 0, j = 0; j < 10; i++, j++)
+ digestInput[i] = hash[j];
+ for (j = 0; j < secretIdPart.length; i++, j++)
+ digestInput[i] = secretIdPart[j];
+ byte[] descIdBin = getHash(digestInput);
+ String descIdString = toBase32(descIdBin);
+ // System.out.println("parsed desc id=" + desc.descriptorID
+ // + ", calculated desc id=" + descIdString);
+ if (!descIdString.equals(rsd.getDescriptorID())) {
+ // System.out.println("Desc ID not okay!");
+ return null;
+ }
+
+ return rsd;
+ }
+
+ private static String binaryToBase32(byte[] bytes) {
+ int i = 0, index = 0, digit = 0;
+ int currByte, nextByte;
+ StringBuffer base32 = new StringBuffer((bytes.length + 7) * 8 / 5);
+ String base32Chars = "abcdefghijklmnopqrstuvwxyz234567";
+ while (i < bytes.length) {
+ currByte = (bytes[i] >= 0) ? bytes[i] : (bytes[i] + 256);
+ if (index > 3) {
+ if ((i + 1) < bytes.length) {
+ nextByte = (bytes[i + 1] >= 0) ? bytes[i + 1]
+ : (bytes[i + 1] + 256);
+ } else {
+ nextByte = 0;
+ }
+ digit = currByte & (0xFF >> index);
+ index = (index + 5) % 8;
+ digit <<= index;
+ digit |= nextByte >> (8 - index);
+ i++;
+ } else {
+ digit = (currByte >> (8 - (index + 5))) & 0x1F;
+ index = (index + 5) % 8;
+ if (index == 0) {
+ i++;
+ }
+ }
+ base32.append(base32Chars.charAt(digit));
+ }
+ return base32.toString();
+ }
+
+ private static byte[] base32toBinary(String base32) {
+
+ int i, index, lookup, offset, digit;
+
+ byte[] bytes = new byte[base32.length() * 5 / 8];
+
+ for (i = 0, index = 0, offset = 0; i < base32.length(); i++) {
+ lookup = base32.charAt(i) - '0';
+
+ /* Skip chars outside the lookup table */
+ if (lookup < 0 || lookup >= base32Lookup.length)
+ continue;
+
+ digit = base32Lookup[lookup];
+
+ /* If this digit is not in the table, ignore it */
+ if (digit == 0xFF)
+ continue;
+
+ if (index <= 3) {
+ index = (index + 5) % 8;
+ if (index == 0) {
+ bytes[offset] |= digit;
+ offset++;
+ if (offset >= bytes.length)
+ break;
+ } else
+ bytes[offset] |= digit << (8 - index);
+ } else {
+ index = (index + 5) % 8;
+ bytes[offset] |= (digit >>> index);
+ offset++;
+
+ if (offset >= bytes.length)
+ break;
+ bytes[offset] |= digit << (8 - index);
+ }
+ }
+ return bytes;
+ }
+
+ static int[] base32Lookup = { 0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
+ 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
+ 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01,
+ 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
+ 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+ private static String aesSample = "-----BEGIN AES ENCRYPTED MESSAGE-----\n"
+ + "99caooDjcURlHhbPVFGnpjH/Wq7D3PrSgvI1hGsO0mITGVOEmI4SNeLSLcLu9+As\n"
+ + "kYfwWczVmLIfJD3cf9ed2fhJKZmuMVuRov33vK3QzhZa7Jo0L+9l3j/88vR91hbl\n"
+ + "GkFIVlTT5sYhXKdOn1VqmWcKHuWmGqnD/HTtA5Yu8a+SNp5wvUrCpOlDB5MKrxMI\n"
+ + "7NiJEpimSySgLDPV2DBjG+oLz0Nh5kNoTGfOnWC8HVruy2dFw0DmYOyFsjJYuucO\n"
+ + "7PcJYL9pSAGxH42bCYvlMTWy40RFXK2CtTBjIni4+uz6L75dW4tAChjLrSQH7E4x\n"
+ + "gV4U/9LBFnEt57g3kEmDKnc68XPO4+Jy340+kuSZJLcUL0k5sv9gpGLSPC5D4OfN\n"
+ + "voIx0tG9ov46QtOtY03SP75ekeM4zvhBj1wQDk3YKvYKTGrXO3naDon8WZbFe3ri\n"
+ + "Hb4MEkfei4mY0ox4YBWGgMr5URW6XZrbAwlyT1+kcTAb4z8JDAa96MLW46zjAOAp\n"
+ + "PKvwd7aeqpnrfUYpNL1nVCRg844ORdzRySMCGmmZ3b++hmGtz2oF0Bd/DiksuRz8\n"
+ + "XgoZX2pSiG1tlSJ1R/Vi9JbXdA4akl3SrPILmTIgt6OI6nj996eXEBtZu+1+sdre\n"
+ + "COGtkdaqp2rqirMceL1TkJdYxCRy6H9Sb5KeoVw7cCjcjOJM4PXBPnpBIUF5ZUQc\n"
+ + "MLFimQEaEcIDOgA6nbN+8VSZCUiMX5R9VCD1tRTcrKMGfoCkpMWzGf12HbgMz/Wn\n"
+ + "1rqi9zTZqkv/gq7bXmL6YoCI9ZMdFU45uFEfwX7BoQ2GYANqUVjE14ehyTEFJmja\n"
+ + "T92IdlXaH45mTZeG3ilkfd1JHB5EAB3+HzM3rhWyOshaKhP4sGRZxusZ8xJIspCz\n"
+ + "7x7Vb/XjmOSm+hmM9F3jJxpsUW48jDWqwyrZz7h+1kboUpYChmMyGvtPozRyOmOG\n"
+ + "zdl3+8arz/hx0i2s/u3IyVSagvBALeCydrZdhZF1VTf9FosE6Q9fwXKAlM4PUf9r\n"
+ + "mgsu1ckXfNZPbENlVYFQ6MOvKeiI1PbgjlWMZD/POGrNeVoNVTPsQ5A32CpVZvM9\n"
+ + "C0PAwTg9hqH2kQbDwvCkpOdH7CXsxC/+MU75El1jDnzT0cij/nXtWiZ6j16QxmZW\n"
+ + "POS602E0uKAdtq8SE2oBZGNXwg/3bCf7f+zIm9nD0gR6owY9mJ5sVg4ArDRoj4n4\n"
+ + "60R7BOtBpF2m4ioW/pn0KyqajIuKChn9SbDPB243/eV0BFO3hCvnCIEleANY/u3z\n"
+ + "D/1WRerbfFLA40rB3Q7dEDukmBUcgV5g6+7SQbV+AbkHLDBtql9K+K2jhd3KcxAX\n"
+ + "O28vxMnIB3RAPF55c7cCPYZYq8N4tIVAQLPwkRCQgfDwnBnrHF5n6WTtGEDjn3F0\n"
+ + "GaJZhHsnyWXnVUE+zpBC8mHfLp6301dPREc5YvozE1V1vVDdrooEFKGc2Lh6HSbf\n"
+ + "IwSNoxiEsNh/mqSZ0LulDnJXWjudwTZjJVfaOe1u6n80xYMkgkGsEM5MWNrMaMob\n"
+ + "mKLuolptyffHER59pJMSOos9wf+JRlx7P2GerdU1sD/CKx2zzzNzyr4dW/GTBL3V\n"
+ + "o/Riz/rEbKBUxp3yaBfcJ14tGLkW4M7Yx9Js9t5nX1tOefkVCNUBwBBnFyA4hkzF\n"
+ + "21pXxEN6fCRFKIDv9jwmwrRrM/Ydaj1XG7nhimlWEi2kzpuOhWGQ3Kkl+h/4PDDg\n"
+ + "eVnAbO2hmwiLZ2b/mmyezenPhkvgH/V3WchQHZGZGwiLL3XKxC8I7dRRF1XdXJsG\n"
+ + "WqLK49x75wolLcLx73nKY64O2v6J+09dyXIAKmsPL59jdKBSia6t/lbJ07CM1DCA\n"
+ + "BBRSCF7GmIENJ8oYDYYsXEKBMUlYJO8QEj80udFTFsenhxmiCOBBHOJ8AMKCQGKn\n"
+ + "qtQr/NCkVtGuPvgVOPAslJ4tDTcYO1RJgIEwBPmAwIZ9P5bB6E05lq+6VYz7aOk0\n"
+ + "A8ziP+xWs0IxoG8uinPPimnR1T/6HkU23iK07OTfHYLNw9sKpROuFjKdb5fi0xZh\n"
+ + "92vxAv439riwvBR6D3tKuCJX+L3nEUQ4DY0ZCmrefpU5tj9IZz7e3OeNPNBX4bdw\n"
+ + "ZZB4XRL1fHc8al84jHTYlqcnm/+mpTlzOICYDIpwQMaQA4yVpEa4Jj8cLES0Yma8\n"
+ + "rI+aY6rEGINBhiUGFyZXLVaGaR3b4vlcZzBeTEmKZfpHhxB+MmUhPE4IpDSsxlng\n"
+ + "OpLf11SyLU0A1DR7U4fUDAbfTKgMaoPHZhdFn2v/Fnl1yD9XsH4qF9ku/7HdoTIV\n"
+ + "rsgAER+Ln/8L1if4Ydkh71B81Yb17/Mzavwi+Y0YxQE+zHexWNatDnCROgBpV6Qd\n"
+ + "KMdOrS/kE/OcElPRCgbmOsZBP87Hb2zDFXt6tMXBz9iRWe59XuFJGyf1lW4d7ZoH\n"
+ + "+1vLJ1z8ctVyoznBKLXlQXOx8F8Q8MxOCfHx+elu5QXq5aSAt/v2fNqaLqm5lTqh\n"
+ + "-----END AES ENCRYPTED MESSAGE-----\n";
+
+ /**
+ * parses a base64-String. <br>
+ * <b>Q</b>: Why doesn't provide Java us with such a functionality? <br>
+ * <b>A</b>: Because it sucks. <br>
+ * <b>A</b>: RTFM e.g.
+ *
+ * @see sun.misc.BASE64Decoder <br>
+ *
+ * @param s
+ * a string that contains a base64 encoded array
+ * @return the decoded array
+ */
+ public static byte[] parseBase64(String s) {
+ byte[] b = new byte[s.length() + 5]; // size is a upper approximation
+ // of expected length needed
+ int fill = 0;
+ char[] c = s.toCharArray();
+
+ // main loop
+ int temp = 0; // stores the reconstructed bitfield
+ int temp_fill = 0;
+ String base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ for (int i = 0; i < c.length; ++i) { // loop over the complete string
+ int v = base64.indexOf(c[i]);
+ if (v >= 0) { // base64 char found
+ temp = temp << 6;
+ temp = temp | v;
+ ++temp_fill;
+ if (temp_fill >= 4) { // filled up 24bit
+ System.arraycopy(intToNByteArray(temp, 3), 0, b, fill, 3);
+ fill += 3;
+ temp_fill = 0;
+ temp = 0;
+ }
+ ;
+ } else {
+ // the string to be parsed does obviously contain other
+ // characters, too.
+ }
+ ;
+ }
+ ;
+ if (temp_fill > 0) { // end of string, 24 bit buffer still filled?
+ if (temp_fill == 1)
+ temp = temp << 18;
+ if (temp_fill == 2)
+ temp = temp << 12;
+ if (temp_fill == 3)
+ temp = temp << 6;
+ System.arraycopy(intToNByteArray(temp, 3), 0, b, fill, 3);
+
+ if (temp_fill == 1)
+ fill += 2;
+ if (temp_fill == 2)
+ fill += 2;
+ if (temp_fill == 3)
+ fill += 2;
+ }
+ ;
+
+ // copy from temp array to return-array
+ byte[] ret = new byte[fill];
+ for (int i = 0; i < fill; ++i)
+ ret[i] = b[i];
+ return ret;
+ }
+
+ /**
+ * do a base32-enconding from a binary field
+ */
+ public static String toBase32(byte[] data) {
+ String base32 = "abcdefghijklmnopqrstuvwxyz234567";
+
+ StringBuffer sb = new StringBuffer();
+ int b32 = 0;
+ int b32_filled = 0;
+ for (int pos = 0; pos < data.length; ++pos)
+ for (int bitmask = 128; bitmask > 0; bitmask /= 2) {
+ b32 = (b32 << 1);
+ if (((int) data[pos] & bitmask) != 0)
+ b32 = b32 | 1;
+ ++b32_filled;
+ if (b32_filled == 5) {
+ sb.append(base32.charAt(b32)); // transform to
+ // base32-encoding
+ b32 = 0;
+ b32_filled = 0;
+ }
+ }
+ // check if bits were left unencoded
+ if (b32_filled != 0)
+ System.out
+ .println("Common.toBase32: received array with unsupported number of bits "
+ + toHexString(data));
+ // return result
+ return sb.toString();
+ }
+
+ static String[] hexChars = { "00", "01", "02", "03", "04", "05", "06",
+ "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", "10", "11",
+ "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c",
+ "1d", "1e", "1f", "20", "21", "22", "23", "24", "25", "26", "27",
+ "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", "30", "31", "32",
+ "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d",
+ "3e", "3f", "40", "41", "42", "43", "44", "45", "46", "47", "48",
+ "49", "4a", "4b", "4c", "4d", "4e", "4f", "50", "51", "52", "53",
+ "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e",
+ "5f", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
+ "6a", "6b", "6c", "6d", "6e", "6f", "70", "71", "72", "73", "74",
+ "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f",
+ "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a",
+ "8b", "8c", "8d", "8e", "8f", "90", "91", "92", "93", "94", "95",
+ "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", "a0",
+ "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab",
+ "ac", "ad", "ae", "af", "b0", "b1", "b2", "b3", "b4", "b5", "b6",
+ "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", "c0", "c1",
+ "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc",
+ "cd", "ce", "cf", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
+ "d8", "d9", "da", "db", "dc", "dd", "de", "df", "e0", "e1", "e2",
+ "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed",
+ "ee", "ef", "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8",
+ "f9", "fa", "fb", "fc", "fd", "fe", "ff" };
+
+ /**
+ * Converts a byte array to hex string
+ */
+ public static String toHexString(byte[] block, int column_width) {
+ StringBuffer buf = new StringBuffer(4 * (block.length + 2));
+ for (int i = 0; i < block.length; i++) {
+ if (i > 0) {
+ buf.append(":");
+ if (i % (column_width / 3) == 0)
+ buf.append("\n");
+ }
+ ;
+ buf.append(hexChars[block[i] & 0xff]);
+ }
+ return buf.toString();
+ }
+
+ public static String toHexString(byte[] block) {
+ return toHexString(block, block.length * 3 + 1);
+ }
+
+ /**
+ * Convert int to the array of bytes
+ *
+ * @param myInt
+ * integer to convert
+ * @param n
+ * size of the byte array
+ * @return byte array of size n
+ *
+ */
+ public static byte[] intToNByteArray(int myInt, int n) {
+
+ byte[] myBytes = new byte[n];
+
+ for (int i = 0; i < n; ++i) {
+ myBytes[i] = (byte) ((myInt >> ((n - i - 1) * 8)) & 0xff);
+ }
+ return myBytes;
+ }
+
+ /** creates an base64-string out of a byte[] */
+ public static String toBase64(byte[] data) {
+ return new String(org.bouncycastle.util.encoders.Base64.encode(data));
+ }
+
+ /**
+ * compares two arrays.
+ *
+ * @return true, if the two arrays are equal
+ */
+ public static boolean arraysEqual(byte[] one, byte[] two) {
+ if ((one == null) && (two == null))
+ return true;
+ if ((one != null) && (two == null)) {
+ // System.err.println("first array contains data, second doesn't");
+ return false;
+ }
+ if ((one == null) && (two != null)) {
+ // System.err.println("seconds array contains data, first doesn't");
+ return false;
+ }
+ if (one.length != two.length) {
+ // System.err.println("Different size: "+one.length+" !=
+ // "+two.length);
+ return false;
+ }
+ for (int i = 0; i < one.length; ++i)
+ if (one[i] != two[i]) {
+ // System.err.println("byte "+i+" of "+one.length+" differs:
+ // "+one[i]+" != "+two[i]);
+ return false;
+ }
+ ;
+ return true;
+ }
+
+ /**
+ * returns the hash of the input
+ *
+ *
+ */
+ public static byte[] getHash(byte[] input) {
+
+ SHA1Digest sha1 = new SHA1Digest();
+ sha1.reset();
+ sha1.update(input, 0, input.length);
+
+ byte[] hash = new byte[sha1.getDigestSize()];
+ sha1.doFinal(hash, 0);
+ return hash;
+
+ }
+
+ /**
+ * checks signature of PKCS1-padded SHA1 hash of the input
+ *
+ * @param signature
+ * signature to check
+ * @param signingKey
+ * public key from signing
+ * @param input
+ * byte array, signature is made over
+ *
+ * @return true, if the signature is correct
+ *
+ */
+ public static boolean verifySignature(byte[] signature,
+ RSAPublicKeyStructure signingKey, byte[] input) {
+
+ byte[] hash = getHash(input);
+
+ try {
+ RSAKeyParameters myRSAKeyParameters = new RSAKeyParameters(false,
+ signingKey.getModulus(), signingKey.getPublicExponent());
+
+ PKCS1Encoding pkcs_alg = new PKCS1Encoding(new RSAEngine());
+ pkcs_alg.init(false, myRSAKeyParameters);
+
+ byte[] decrypted_signature = pkcs_alg.processBlock(signature, 0,
+ signature.length);
+
+ return arraysEqual(hash, decrypted_signature);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return false;
+
+ }
+
+ /**
+ * sign some data using a private kjey and PKCS#1 v1.5 padding
+ *
+ * @param data
+ * the data to be signed
+ * @param signingKey
+ * the key to sign the data
+ * @return a signature
+ */
+ public static byte[] signData(byte[] data, RSAKeyParameters signingKey) {
+ try {
+ byte[] hash = getHash(data);
+ PKCS1Encoding pkcs1 = new PKCS1Encoding(new RSAEngine());
+ pkcs1.init(true, signingKey);
+ return pkcs1.processBlock(hash, 0, hash.length);
+ } catch (InvalidCipherTextException e) {
+ System.out.println("Common.signData(): " + e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /** used to encode a signature in PEM */
+ public static String binarySignatureToPEM(byte[] signature) {
+ String sigB64 = toBase64(signature);
+ StringBuffer sig = new StringBuffer();
+
+ sig.append("-----BEGIN SIGNATURE-----\n");
+ while (sigB64.length() > 64) {
+ sig.append(sigB64.substring(0, 64) + "\n");
+ sigB64 = sigB64.substring(64);
+ }
+ sig.append(sigB64 + "\n");
+ sig.append("-----END SIGNATURE-----\n");
+ return sig.toString();
+ }
+
+ /**
+ * copy from one format to another
+ */
+ public static RSAPublicKeyStructure getRSAPublicKeyStructureFromJCERSAPublicKey(
+ JCERSAPublicKey jpub) {
+ return new RSAPublicKeyStructure(jpub.getModulus(), jpub
+ .getPublicExponent());
+ }
+
+ /**
+ * converts a JCERSAPublicKey into PKCS1-encoding
+ *
+ * @param rsaPublicKey
+ * @see JCERSAPublicKey
+ * @return PKCS1-encoded RSA PUBLIC KEY
+ */
+ public static byte[] getPKCS1EncodingFromRSAPublicKey(
+ RSAPublicKeyStructure pubKeyStruct) {
+ try {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ASN1OutputStream aOut = new ASN1OutputStream(bOut);
+ aOut.writeObject(pubKeyStruct.toASN1Object());
+ return bOut.toByteArray();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * converts a JCERSAPublicKey into PEM/PKCS1-encoding
+ *
+ * @param rsaPublicKey
+ * @see RSAPublicKeyStructure
+ * @return PEM-encoded RSA PUBLIC KEY
+ */
+ public static String getPEMStringFromRSAPublicKey(
+ RSAPublicKeyStructure rsaPublicKey) {
+
+ // mrk: this was awful to program. Remeber: There are two entirely
+ // different
+ // standard formats for rsa public keys. Bouncy castle does only support
+ // the
+ // one we can't use for TOR directories.
+
+ StringBuffer tmpDirSigningKey = new StringBuffer();
+
+ try {
+
+ tmpDirSigningKey.append("-----BEGIN RSA PUBLIC KEY-----\n");
+
+ byte[] base64Encoding = Base64
+ .encode(getPKCS1EncodingFromRSAPublicKey(rsaPublicKey));
+ for (int i = 0; i < base64Encoding.length; i++) {
+ tmpDirSigningKey.append((char) base64Encoding[i]);
+ if (((i + 1) % 64) == 0)
+ tmpDirSigningKey.append("\n");
+ }
+ tmpDirSigningKey.append("\n");
+
+ tmpDirSigningKey.append("-----END RSA PUBLIC KEY-----\n");
+ } catch (Exception e) {
+ return null;
+ }
+
+ return tmpDirSigningKey.toString();
+ }
+
+ /**
+ * makes RSA public key from base64 encoded string
+ *
+ * @param s
+ * string that contais the key
+ * @return
+ * @see JCERSAPublicKey
+ */
+ public static JCERSAPublicKey getRSAPublicKeyFromPEMString(String s) {
+
+ PEMReader reader = new PEMReader(new StringReader(s));
+ JCERSAPublicKey theKey;
+
+ try {
+ Object o = reader.readObject();
+ if (!(o instanceof JCERSAPublicKey))
+ throw new IOException(
+ "no public key found for signing key in string '" + s
+ + "'");
+ theKey = (JCERSAPublicKey) o;
+ } catch (IOException e) {
+ System.out.println("Caught exception:" + e.getMessage());
+ theKey = null;
+ }
+
+ return theKey;
+ }
+
+ /**
+ * Returns the ASCII-encoded descriptor string.
+ *
+ * @return The ASCII-encoded descriptor string.
+ */
+ public String getDescriptorString() {
+ return this.descriptorString;
+ }
+
+ /**
+ * Returns the descriptor ID consisting of 32 base32 chars.
+ *
+ * @return The descriptor ID.
+ */
+ public String getDescriptorID() {
+ return this.descriptorID;
+ }
+}
Deleted: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/examples/DistributedStorage.java
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/examples/DistributedStorage.java 2007-08-26 21:03:54 UTC (rev 11279)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/examples/DistributedStorage.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -1,1522 +0,0 @@
-package de.uniba.wiai.lspi.puppetor.examples;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import de.uniba.wiai.lspi.puppetor.ClientApplication;
-import de.uniba.wiai.lspi.puppetor.DirectoryNode;
-import de.uniba.wiai.lspi.puppetor.Event;
-import de.uniba.wiai.lspi.puppetor.EventListener;
-import de.uniba.wiai.lspi.puppetor.EventManager;
-import de.uniba.wiai.lspi.puppetor.EventSource;
-import de.uniba.wiai.lspi.puppetor.EventType;
-import de.uniba.wiai.lspi.puppetor.Network;
-import de.uniba.wiai.lspi.puppetor.NetworkFactory;
-import de.uniba.wiai.lspi.puppetor.ProxyNode;
-import de.uniba.wiai.lspi.puppetor.RouterNode;
-import de.uniba.wiai.lspi.puppetor.TorProcessException;
-
-/**
- * <p>
- * Automatic validation of the distributed storage for hidden service
- * descriptors as described in proposal 114. <b>WARNING: This example does not
- * work with an unmodified Tor!</b>
- * </p>
- *
- * <p>
- * When running, the example starts a network of local Tor processes, consisting
- * of 2 directory nodes and 9 periodically changing onion routers, some of them
- * (not the initial nodes, but those nodes replacing them) with attached hidden
- * services.
- * </p>
- *
- * <p>
- * The automatic validation performs four measurements:
- * </p>
- *
- * <ol>
- * <li>Are the online statuses of hidden service directories propagated to all
- * running nodes successfully, and how long does propagation take?</li>
- * <li>The same measurement with offline statuses.</li>
- * <li>Are hidden service descriptors stored on the correct, responsible hidden
- * service directories, and how long does propagation take?</li>
- * <li>Are hidden service requests successful within a given timeout?</li>
- * </ol>
- *
- * @author kloesing
- */
-public class DistributedStorage {
-
- /**
- * Observes a hidden service directory from starting it to shutting it down.
- * Waits for 24 hours after starting the node, so that it will be accepted
- * as hidden service directory by the directory authorities. Afterwards,
- * initiates an online propagation measurement for every node that is in the
- * network or that enters the network while the observed hidden service
- * directory is online. After going offline, initiates an offline
- * propagation measurement for every node that has previously accepted this
- * hidden service directory.
- */
- private static class HiddenServiceDirectoryObserver extends Thread
- implements EventListener {
-
- /**
- * Is the hidden service directory online for at least 24 hours, so that
- * it will be listed by the directory authorities?
- */
- private boolean twentyFourHoursUp;
-
- /**
- * The Tor process behind the observed hidden service directory.
- */
- private EventSource hsdNode;
-
- /**
- * The node ID of the observed hidden service directory.
- */
- private String nodeID;
-
- /**
- * Set of nodes that have reported to accept this hidden service
- * directory.
- */
- private Set<EventSource> nodesThatKnowMe = new HashSet<EventSource>();
-
- /**
- * Creates a new observer instance, but does not start it yet.
- *
- * @param hsdNode
- * The Tor process behind the observed hidden service
- * directory.
- * @param nodeID
- * The node ID of the observed hidden service directory.
- */
- private HiddenServiceDirectoryObserver(EventSource hsdNode,
- String nodeID) {
-
- // remember the args
- this.nodeID = nodeID;
- this.hsdNode = hsdNode;
-
- // listen for events coming from my HSDir
- manager.addEventListener(hsdNode, this);
-
- // listen for starting/stopping nodes
- manager.addEventListener(this);
- }
-
- @Override
- public synchronized void run() {
-
- // not yet accepted
- this.twentyFourHoursUp = false;
-
- // wait for 24 hours until hsdir is accepted by DAs
- long startingTime = System.currentTimeMillis();
- // for testing; change to 24 * 60 * 60 * 1000
- long endOfWaiting = startingTime + 30 * 60 * 1000;
- long now;
- while ((now = System.currentTimeMillis()) < endOfWaiting) {
- try {
- wait(endOfWaiting - now);
- } catch (InterruptedException e) {
- }
- }
-
- // accepted
- this.twentyFourHoursUp = true;
-
- // add to set of running hidden service directories
- globalRoutingTable.addHiddenServiceDirectory(this.nodeID,
- this.hsdNode);
-
- // measure how long the current nodes need to get aware of this
- // hidden service directory
- for (RouterNode router : runningRouters) {
- new OnlinePropagationMeasurement(router, this.hsdNode,
- this.nodeID).start();
- }
- }
-
- public synchronized void handleEvent(Event event) {
-
- if (!this.twentyFourHoursUp) {
- // when the represented node is not running for at least 24
- // hours, others would not consider it as hidden service
- // directory and we cannot perform any useful measurements
- return;
- }
-
- if (event.getType() == EventType.NODE_STARTED) {
- // when another node is started, initiate measurement of the
- // propagation time that this hiddden service directory is
- // online
- new OnlinePropagationMeasurement(event.getSource(),
- this.hsdNode, this.nodeID).start();
-
- } else if (event.getSource() == this.hsdNode
- && event.getType() == EventType.NODE_STOPPED) {
- // when the represented node is stopped, initiate measurement of
- // the propagation time that this hidden service directory is
- // offline (any online propagation measurements that are still
- // running will realize by themselves that they cannot succeed
- // and will abort)
- for (EventSource node : nodesThatKnowMe) {
- if (node != this.hsdNode) {
- new OfflinePropagationMeasurement(node, this.hsdNode,
- this.nodeID).start();
- }
- }
-
- // remove from set of running hidden service directories
- globalRoutingTable.removeHiddenServiceDirectory(this.nodeID);
-
- // stop listening for events
- manager.removeEventListener(this);
-
- // just in case that there is another event in the queue, don't
- // handle it any more
- this.twentyFourHoursUp = false;
-
- } else if (event.getType() == EventType.NODE_ROUTING_TABLE_CHANGED
- && event.getMessage().contains(this.nodeID)) {
- // when some node reports to have changed its routing table and
- // now includes the node ID of the represented node, remember
- // that node for later offline propagation measurement
- nodesThatKnowMe.add(event.getSource());
-
- } else if (event.getType() == EventType.NODE_STOPPED
- && event.getSource() != this.hsdNode) {
- // when some other node is stopped, remove it from the list of
- // nodes that know that the represented hidden service directory
- // is online
- nodesThatKnowMe.remove(event.getSource());
- }
- }
- }
-
- /**
- * The states in which a measurement can be.
- */
- private static enum MeasurementState {
-
- /**
- * The measurement was started and is currently running.
- */
- STARTED,
-
- /**
- * The measurement has succeeded with a positive result.
- */
- SUCCEEDED,
-
- /**
- * The measurement has failed within the given maximum time.
- */
- FAILED,
-
- /**
- * The measurement was aborted, because it has become impossible to
- * succeed.
- */
- ABORTED
- }
-
- /**
- * Verifies that the online status of a given hidden service directory is
- * propagated to a given running node, and measures how long that
- * propagation takes. If the propagation does not succeed within one hour,
- * it is considered as failed.
- */
- private static class OnlinePropagationMeasurement extends Thread implements
- EventListener {
-
- /**
- * The state of this measurement.
- */
- private MeasurementState measurementState;
-
- /**
- * The node ID of the hidden service directory.
- */
- private String hsdNodeID;
-
- /**
- * The hidden service directory of which the online status should be
- * propagated.
- */
- private EventSource hsdNode;
-
- /**
- * The node to which the online status should be propagated.
- */
- private EventSource node;
-
- /**
- * Creates a new measurement, but does not start it yet.
- *
- * @param node
- * The node to which the online status should be propagated.
- * @param hsdNode
- * The hidden service directory of which the online status
- * should be propagated.
- * @param hsdNodeID
- * The node ID of the hidden service directory.
- */
- private OnlinePropagationMeasurement(EventSource node,
- EventSource hsdNode, String hsdNodeID) {
-
- // remember the args
- this.hsdNodeID = hsdNodeID;
- this.hsdNode = hsdNode;
- this.node = node;
-
- // listen for events coming from the node and the hidden service
- // directory
- manager.addEventListener(node, this);
- manager.addEventListener(hsdNode, this);
-
- }
-
- @Override
- public synchronized void run() {
-
- // start the measurement in state STARTED
- this.measurementState = MeasurementState.STARTED;
-
- // wait for a maximum of one hour for the measurement to succeed
- long startingTime = System.currentTimeMillis();
- long endOfWaiting = startingTime + 60 * 60 * 1000;
- long now;
- while (this.measurementState == MeasurementState.STARTED
- && (now = System.currentTimeMillis()) < endOfWaiting) {
- try {
- wait(endOfWaiting - now);
- } catch (InterruptedException e) {
- }
- }
-
- // if the node did not learn about the hidden service directory
- // within one hour, fail the measurement
- if (this.measurementState == MeasurementState.STARTED) {
- measurementState = MeasurementState.FAILED;
- }
-
- // unregister event listener
- manager.removeEventListener(this);
-
- // print out measurement result
- long duration = System.currentTimeMillis() - startingTime;
- System.out.println(new Date() + ": Online propagation for HSDir "
- + this.hsdNode.getName() + " to node "
- + this.node.getName() + " took " + (duration / 1000)
- + " seconds and ended in state "
- + measurementState.toString());
- if (this.measurementState == MeasurementState.SUCCEEDED) {
- resultWriter.writeOnlinePropagation(duration / 1000);
- } else if (this.measurementState == MeasurementState.FAILED) {
- resultWriter.writeOnlinePropagation(-1);
- }
- }
-
- public synchronized void handleEvent(Event event) {
-
- // only accept events when this measurement is running
- if (measurementState != MeasurementState.STARTED) {
- return;
- }
-
- if (event.getSource() == this.node
- && event.getType() == EventType.NODE_ROUTING_TABLE_CHANGED
- && event.getMessage().contains(this.hsdNodeID)) {
- // when the node has added the node ID of the hidden service
- // directory, succeed the measurement
- this.measurementState = MeasurementState.SUCCEEDED;
- notify();
-
- } else if (event.getType() == EventType.NODE_STOPPED) {
- // when either the node or the hidden service directory were
- // stopped within the one hour period, the measurement cannot be
- // succeeded anymore and is therefore aborted
- this.measurementState = MeasurementState.ABORTED;
- notify();
- }
- }
- }
-
- /**
- * Verifies that the offline status of a given hidden service directory is
- * propagated to a given running node, and measures how long that
- * propagation takes. If the propagation does not succeed within one hour,
- * it is considered as failed.
- */
- private static class OfflinePropagationMeasurement extends Thread implements
- EventListener {
-
- /**
- * The state of this measurement.
- */
- private MeasurementState measurementState;
-
- /**
- * The node ID of the hidden service directory.
- */
- private String hsdNodeID;
-
- /**
- * The hidden service directory of which the offline status should be
- * propagated.
- */
- private EventSource hsdNode;
-
- /**
- * The node to which the offline status should be propagated.
- */
- private EventSource node;
-
- /**
- * Creates a new measurement, but does not start it yet.
- *
- * @param node
- * The node to which the offline status should be propagated.
- * @param hsdNode
- * The hidden service directory of which the offline status
- * should be propagated.
- * @param hsdNodeID
- * The node ID of the hidden service directory.
- */
- private OfflinePropagationMeasurement(EventSource node,
- EventSource hsdNode, String hsdNodeID) {
-
- // remember the args
- this.hsdNodeID = hsdNodeID;
- this.node = node;
- this.hsdNode = hsdNode;
-
- // register for events from the node that should observe the offline
- // status
- manager.addEventListener(node, this);
- }
-
- @Override
- public synchronized void run() {
-
- // start the measurement in state STARTED
- this.measurementState = MeasurementState.STARTED;
-
- // wait for a maximum of 90 minutes for the measurement to succeed
- long startingTime = System.currentTimeMillis();
- long endOfWaiting = startingTime + 90 * 60 * 1000;
- long now;
- while (this.measurementState == MeasurementState.STARTED
- && (now = System.currentTimeMillis()) < endOfWaiting) {
- try {
- wait(endOfWaiting - now);
- } catch (InterruptedException e) {
- }
- }
-
- // if the node did not learn about the offline status of the hidden
- // service directory, fail the measurement
- if (this.measurementState == MeasurementState.STARTED) {
- measurementState = MeasurementState.FAILED;
- }
-
- // unregister event listener
- manager.removeEventListener(this);
-
- // print out measurement result
- long duration = System.currentTimeMillis() - startingTime;
- System.out.println(new Date() + ": Offline propagation for HSDir "
- + this.hsdNode.getName() + " to node "
- + this.node.getName() + " took " + (duration / 1000)
- + " seconds and ended in state "
- + measurementState.toString());
- if (this.measurementState == MeasurementState.SUCCEEDED) {
- resultWriter.writeOfflinePropagation(duration / 1000);
- } else if (this.measurementState == MeasurementState.FAILED) {
- resultWriter.writeOfflinePropagation(-1);
- }
- }
-
- public synchronized void handleEvent(Event event) {
-
- // only accept events when this measurement is running
- if (measurementState != MeasurementState.STARTED) {
- return;
- }
-
- if (event.getType() == EventType.NODE_ROUTING_TABLE_CHANGED
- && !event.getMessage().contains(this.hsdNodeID)) {
- // when the node has removed the node ID of the hidden service
- // directory, succeed the measurement
- this.measurementState = MeasurementState.SUCCEEDED;
- notify();
-
- } else if (event.getType() == EventType.NODE_STOPPED) {
- // when the node was stopped within the waiting period, the
- // measurement cannot be
- // succeeded anymore and is therefore aborted
- this.measurementState = MeasurementState.ABORTED;
- notify();
- }
- }
- }
-
- /**
- * Manages the global routing table of all hidden service directories. This
- * global view differs from the local views of the nodes, because of the
- * propagation latency which may take some tens of minutes.
- */
- private static class GlobalRoutingTable {
-
- private static Comparator<String> comparator = new Comparator<String>() {
-
- int[] base32Lookup = { 0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
- 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
- 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
- 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF,
- 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11,
- 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF,
- 0xFF, 0xFF, 0xFF };
-
- private String base32toHex(String base32) {
-
- int i, index, lookup, offset, digit;
-
- byte[] bytes = new byte[base32.length() * 5 / 8];
-
- for (i = 0, index = 0, offset = 0; i < base32.length(); i++) {
- lookup = base32.charAt(i) - '0';
-
- /* Skip chars outside the lookup table */
- if (lookup < 0 || lookup >= base32Lookup.length)
- continue;
-
- digit = base32Lookup[lookup];
-
- /* If this digit is not in the table, ignore it */
- if (digit == 0xFF)
- continue;
-
- if (index <= 3) {
- index = (index + 5) % 8;
- if (index == 0) {
- bytes[offset] |= digit;
- offset++;
- if (offset >= bytes.length)
- break;
- } else
- bytes[offset] |= digit << (8 - index);
- } else {
- index = (index + 5) % 8;
- bytes[offset] |= (digit >>> index);
- offset++;
-
- if (offset >= bytes.length)
- break;
- bytes[offset] |= digit << (8 - index);
- }
- }
- StringBuilder result = new StringBuilder();
- for (byte b : bytes) {
- result.append(Integer
- .toHexString(new Integer(b).intValue()));
- }
- return result.toString();
- }
-
- public int compare(String arg0, String arg1) {
- return base32toHex(arg0).compareTo(base32toHex(arg1));
- }
- };
-
- private SortedMap<String, EventSource> hsDirs = new TreeMap<String, EventSource>(
- comparator);
-
- private synchronized void addHiddenServiceDirectory(String nodeID,
- EventSource hsdNode) {
- this.hsDirs.put(nodeID, hsdNode);
- notifyAll();
- }
-
- private synchronized void removeHiddenServiceDirectory(String nodeID) {
- this.hsDirs.remove(nodeID);
- notifyAll();
- }
-
- /**
- * Returns the set of responsible hidden service directories for the
- * given descriptor ID.
- *
- * @param descID
- * The descriptor ID for which the responsible hidden service
- * directories shall be determined.
- * @return The set of responsible hidden service directories.
- */
- private synchronized Set<EventSource> getResponsibleHSDirs(String descID) {
-
- Set<EventSource> result = new HashSet<EventSource>();
- if (this.hsDirs.size() < 3) {
- return result;
- }
-
- for (EventSource s : this.hsDirs.tailMap(descID).values()) {
- if (result.size() < 3) {
- result.add(s);
- } else {
- break;
- }
- }
-
- for (EventSource s : this.hsDirs.values()) {
- if (result.size() < 3) {
- result.add(s);
- } else {
- break;
- }
- }
-
- return result;
- }
-
- /**
- * Waits for the given number of millis until either a node is added or
- * removed.
- *
- * @param timeToWait
- * The number of millis to wait.
- */
- public synchronized void waitForRoutingTableChange(long timeToWait) {
- try {
- wait(timeToWait);
- } catch (InterruptedException e) {
- }
- }
- }
-
- /**
- * The routing table containing the global state of all hidden service
- * directories in the network.
- */
- private static GlobalRoutingTable globalRoutingTable = new GlobalRoutingTable();
-
- /**
- * Observes all publications of descriptors by hidden service providers.
- * Whenever there is a novel descriptor, the observer launches measurements
- * of the ropagation of the new descriptor to the responsible hidden service
- * directories.
- */
- private static class DescriptorObserverStarter implements EventListener {
-
- /**
- * Set of all descriptor IDs that have been observed so far.
- */
- private Set<String> knownDescIDs = new HashSet<String>();
-
- /**
- * Creates a new observer that starts listening for events.
- */
- private DescriptorObserverStarter() {
- manager.addEventListener(this);
- }
-
- public void handleEvent(Event event) {
- // listen for new desc-ids
- if (event.getType() == EventType.BOB_SENDING_PUBLISH_DESC) {
- // a descriptor was published by any hidden service provider;
- // check if this descriptor ID is novel
-
- // parse desc id from event message
- String message = event.getMessage();
- String prefixDescID = "with descriptor ID '";
- String descID = message.substring(message.indexOf(prefixDescID)
- + prefixDescID.length());
- descID = descID.substring(0, 32);
-
- if (!knownDescIDs.contains(descID)) {
- String prefixSecondsValid = "with validity of ";
- String secondsValidPlusRest = message.substring(message
- .indexOf(prefixSecondsValid)
- + prefixSecondsValid.length());
- String[] splitted = secondsValidPlusRest.split(" ");
- long secondsValid = Long.parseLong(splitted[0]);
-
- if (secondsValid > 30 * 60) {
- new DescriptorObserver(descID, secondsValid).start();
- }
- }
- }
- }
- }
-
- /**
- * Observes a descriptor from its first publication to 30 minutes before its
- * validity ends. Initiates a descriptor propagation measurement for every
- * hidden service directory that either is or becomes responsible for the
- * descriptor ID. (This does not include checks that a descriptor remains
- * stored on the responsible hidden service directories.)
- */
- private static class DescriptorObserver extends Thread {
-
- /**
- * The descriptor ID of the observed descriptor.
- */
- private String descID;
-
- /**
- * Time in millis when the validity of the observed descriptor amounts
- * 30 minutes.
- */
- private long endOfWaiting;
-
- /**
- * The set of hidden service directories that we think is responsible
- * for storing the observed descriptor.
- */
- private Set<EventSource> hsDirsDeemedResponsible;
-
- /**
- * Creates a new observer instance, but does not start it yet.
- *
- * @param descID
- * The descriptor ID of the observed descriptor.
- * @param validityInSeconds
- * The validity of the descriptor in seconds.
- */
- private DescriptorObserver(String descID, long validityInSeconds) {
-
- // remember the args
- this.descID = descID;
-
- // determine when the validity of the observed descriptor amounts 30
- // minutes
- this.endOfWaiting = System.currentTimeMillis() + validityInSeconds
- * 1000 - 30 * 60 * 1000;
- }
-
- @Override
- public void run() {
-
- if (System.currentTimeMillis() < this.endOfWaiting) {
- // when the descriptor is not valid for at least 30 minutes, we
- // don't need to perform any measurements at all, because it is
- // quite unlikely that they would succeed
- return;
- }
-
- // start measurements for the currently responsible hidden service
- // directories
- hsDirsDeemedResponsible = globalRoutingTable
- .getResponsibleHSDirs(this.descID);
- for (EventSource responsibleDir : hsDirsDeemedResponsible) {
- new DescriptorPropagationMeasurement(responsibleDir,
- this.descID).start();
- }
-
- // wait until the validity of the descriptor is 30 minutes or less
- long now;
- while ((now = System.currentTimeMillis()) < this.endOfWaiting) {
-
- // wait for changes in the global routing table
- globalRoutingTable.waitForRoutingTableChange(this.endOfWaiting
- - now);
-
- // check if the descriptor validity is still sufficient to
- // initiate new measurements
- if (now < this.endOfWaiting) {
-
- // check if the change affects us
- Set<EventSource> newResponsibleHSDirs = globalRoutingTable
- .getResponsibleHSDirs(this.descID);
- Set<EventSource> newcomers = new HashSet<EventSource>(
- newResponsibleHSDirs);
- newcomers.removeAll(this.hsDirsDeemedResponsible);
- if (newcomers.size() > 0) {
-
- // initiate a new measurement for every new responsible
- // hidden service directory
- for (EventSource responsibleDir : newcomers) {
- new DescriptorPropagationMeasurement(
- responsibleDir, this.descID).start();
- }
- this.hsDirsDeemedResponsible = newResponsibleHSDirs;
- }
- }
- }
- }
- }
-
- /**
- * Verifies that a descriptor is propagated to a given hidden service
- * directory, and measures how long that propagation takes. If the
- * propagation does not succeed within 90 minutes, it is considered as
- * failed, unless a change in the routing table makes the hidden service
- * directory irresponsible for the given descriptor.
- */
- private static class DescriptorPropagationMeasurement extends Thread
- implements EventListener {
-
- /**
- * The state of this measurement.
- */
- private MeasurementState measurementState;
-
- /**
- * The descriptor ID of the observed descriptor.
- */
- private String descid;
-
- /**
- * The hidden service directory that is deemed responsible to store the
- * given descriptor.
- */
- private EventSource responsibleHSDir;
-
- /**
- * Creates a new measurement instance, but does not start it yet.
- *
- * @param responsibleHSDir
- * The hidden service directory that is deemed responsible to
- * store the given descriptor.
- * @param descid
- * The descriptor ID of the observed descriptor.
- */
- private DescriptorPropagationMeasurement(EventSource responsibleHSDir,
- String descid) {
-
- // remember the args
- this.responsibleHSDir = responsibleHSDir;
- this.descid = descid;
-
- // register for events from the hidden service directory
- manager.addEventListener(responsibleHSDir, this);
- }
-
- @Override
- public synchronized void run() {
-
- // start the measurement in state STARTED
- this.measurementState = MeasurementState.STARTED;
-
- // wait for a maximum of 90 minutes for the measurement to succeed
- long startingTime = System.currentTimeMillis();
- long endOfWaiting = startingTime + 90 * 60 * 1000;
- long now;
- while (this.measurementState == MeasurementState.STARTED
- && (now = System.currentTimeMillis()) < endOfWaiting) {
-
- // wait for a change in the routing table, that might make the
- // hidden service directory irresponsible for the given
- // descriptor, or be interrupted by an incoming event
- globalRoutingTable
- .waitForRoutingTableChange(endOfWaiting - now);
-
- if (this.measurementState == MeasurementState.STARTED
- && !globalRoutingTable
- .getResponsibleHSDirs(this.descid).contains(
- this.responsibleHSDir)) {
- // routing table has changed, so that the hidden service
- // directory is not responsible for the given descriptor any
- // more
- this.measurementState = MeasurementState.ABORTED;
- }
- }
-
- // if the node did not learn about the hidden service directory
- // within the given time, fail the measurement
- if (this.measurementState == MeasurementState.STARTED) {
- measurementState = MeasurementState.FAILED;
- }
-
- // unregister event listener
- manager.removeEventListener(this);
-
- // print out measurement result
- long duration = System.currentTimeMillis() - startingTime;
- System.out.println(new Date()
- + ": Descriptor propagation for desc ID " + this.descid
- + " took " + (duration / 1000)
- + " seconds to responsible HS directory "
- + this.responsibleHSDir.getName() + " and ended in state "
- + measurementState.toString());
- if (this.measurementState == MeasurementState.SUCCEEDED) {
- resultWriter.writeDescriptorPropagation(duration / 1000);
- } else if (this.measurementState == MeasurementState.FAILED) {
- resultWriter.writeDescriptorPropagation(-1);
- }
- }
-
- public synchronized void handleEvent(Event event) {
-
- if (event.getType() == EventType.HSDIR_DESC_STORED
- && event.getMessage().contains(this.descid)) {
- // when the hidden service directory stores the given descriptor
- // for the first time, the measurement succeeds
- this.measurementState = MeasurementState.SUCCEEDED;
- interrupt();
- }
- }
- }
-
- /**
- * Observes a hidden service for the first 20 minutes after starting it
- * before adding it to the list of available hidden services. When the node
- * is stopped (within or after the 20 minutes), the hidden service will be
- * removed from the list of available hidden services.
- */
- private static class HiddenServiceObserver extends Thread implements
- EventListener {
-
- /**
- * The onion address by which this hidden service can be accessed.
- */
- private String onionAddress;
-
- /**
- * The node that provides the hidden service.
- */
- private EventSource providingNode;
-
- /**
- * Creates a new observer instance, but does not start it yet.
- *
- * @param providingNode
- * The node that provides the hidden service.
- * @param onionAddress
- * The onion address by which this hidden service can be
- * accessed.
- */
- private HiddenServiceObserver(EventSource providingNode,
- String onionAddress) {
-
- // remember the args
- this.onionAddress = onionAddress;
- this.providingNode = providingNode;
-
- // listen for events coming from the providing node
- manager.addEventListener(providingNode, this);
- }
-
- /**
- * Is the observed hidden service still available, or was the providing
- * node stopped?
- */
- private boolean stopped = false;
-
- @Override
- public void run() {
-
- // wait for 20 minutes until the hidden service can be considered
- // available
- long startingTime = System.currentTimeMillis();
- long endOfWaiting = startingTime + 20 * 60 * 1000;
- long now;
- while (!this.stopped
- && (now = System.currentTimeMillis()) < endOfWaiting) {
- try {
- Thread.sleep(endOfWaiting - now);
- } catch (InterruptedException e) {
- }
- }
-
- // add it to the list of available hidden services
- availableHiddenServices.put(this.onionAddress, this.providingNode);
- }
-
- public void handleEvent(Event event) {
- if (event.getType() == EventType.NODE_STOPPED) {
- // when the providing node is stopped, the hidden service will
- // not be available any more
- this.stopped = true;
- availableHiddenServices.remove(this.onionAddress);
- }
- }
- }
-
- /**
- * A list of all hidden services that are available for testing.
- */
- private static Map<String, EventSource> availableHiddenServices = new HashMap<String, EventSource>();
-
- /**
- * Periodically (every 5 to 10 minutes) picks a hidden service that is
- * available and running for at least 20 minutes and performs a request to
- * it using an arbitrary node as proxy.
- */
- private static class HiddenServiceRequestStarter extends Thread {
-
- @Override
- public void run() {
- // first wait until HSDirs are up and descriptors distributed --
- // stable state
- // Thread.sleep(24 * 60 * 60 * 1000 + 90 * 60 * 1000);
- try {
- Thread.sleep(30 * 60 * 1000 + 30 * 60 * 1000);
- } catch (InterruptedException e) {
- }
-
- Random rnd = new Random();
- while (true) {
- if (availableHiddenServices.size() > 0) {
-
- // pick an available hidden service at random
- String onionAddressToTry = new ArrayList<String>(
- availableHiddenServices.keySet()).get(rnd
- .nextInt(availableHiddenServices.size()));
- EventSource providingNode = availableHiddenServices
- .get(onionAddressToTry);
-
- // pick an arbitrary running node as proxy
- RouterNode useAsProxy = runningRouters.get(rnd
- .nextInt(runningRouters.size()));
-
- // start measurement
- new HiddenServiceRequestMeasurement(onionAddressToTry,
- providingNode, useAsProxy).start();
- }
-
- // wait for a random time between 5 to 10 minutes
- try {
- Thread.sleep(5 * 60 * 1000 + rnd.nextInt(5 * 60 * 1000));
- } catch (InterruptedException e) {
- }
- }
- }
- }
-
- /**
- * Verifies that a hidden service can be accessed, and measures how long
- * performing a request takes. If the request does not succeed within one
- * minute, it is considered as failed, unless either the node providing the
- * hidden service, or the node that is used as proxy fails.
- */
- private static class HiddenServiceRequestMeasurement extends Thread
- implements EventListener {
-
- /**
- * The onion address to which the request shall be performed.
- */
- private String onionAddress;
-
- /**
- * The node that provides the hidden service.
- */
- private EventSource providingNode;
-
- /**
- * The node that is used as proxy for the request.
- */
- private EventSource useAsProxy;
-
- /**
- * The client application that performs the requests.
- */
- private ClientApplication clientApp;
-
- /**
- * The state of this measurement.
- */
- private MeasurementState measurementState;
-
- /**
- * The time when the request was sent from the client.
- */
- private long requestSentFromClient;
-
- /**
- * The time when a reply was received at the client.
- */
- private long replyReceivedAtClient;
-
- /**
- * Creates a new measurement instance, but does not start it yet.
- *
- * @param onionAddress
- * The onion address to which the request shall be performed.
- * @param providingNode
- * The node that provides the hidden service.
- * @param useAsProxy
- * The node that is used as proxy for the request.
- */
- private HiddenServiceRequestMeasurement(String onionAddress,
- EventSource providingNode, ProxyNode useAsProxy) {
-
- // remember the args
- this.onionAddress = onionAddress;
- this.providingNode = providingNode;
- this.useAsProxy = useAsProxy;
-
- // register for events from the two involved nodes
- manager.addEventListener(providingNode, this);
- manager.addEventListener(useAsProxy, this);
-
- // determine socks port of proxy
- int socksPort = useAsProxy.getSocksPort();
-
- // create client application and register for events originating
- // from it
- this.clientApp = network.createClient("client", onionAddress, 80,
- socksPort);
- manager.addEventListener(this.clientApp, this);
- }
-
- @Override
- public synchronized void run() {
-
- // start the measurement in state STARTED
- this.measurementState = MeasurementState.STARTED;
-
- // perform the request
- this.clientApp.performRequest(1, 60 * 1000, true);
-
- // wait for a minute for the request to be performed
- long startingTime = System.currentTimeMillis();
- long endOfWaiting = startingTime + 60 * 1000;
- long now;
- while (this.measurementState == MeasurementState.STARTED
- && (now = System.currentTimeMillis()) < endOfWaiting) {
- try {
- wait(endOfWaiting - now);
- } catch (InterruptedException e) {
- }
- }
-
- // if the client application did not receive a reply within the
- // given time, fail the measurement
- if (this.measurementState == MeasurementState.STARTED) {
- measurementState = MeasurementState.FAILED;
- }
-
- // unregister event listener
- manager.removeEventListener(this);
-
- // print out measurement result
- System.out
- .println(System.currentTimeMillis()
- + ": Hidden service request for onion address "
- + this.onionAddress
- + " running on node "
- + this.providingNode.getName()
- + " using node "
- + this.useAsProxy.getName()
- + " as proxy took "
- + (replyReceivedAtClient > 0
- && requestSentFromClient > 0 ? ((replyReceivedAtClient - requestSentFromClient) / 1000)
- : -1) + " seconds and ended in state "
- + measurementState.toString());
-
- if (this.measurementState == MeasurementState.SUCCEEDED) {
- resultWriter
- .writeHiddenServiceRequest((replyReceivedAtClient - requestSentFromClient) / 1000);
- } else if (this.measurementState == MeasurementState.FAILED) {
- resultWriter.writeHiddenServiceRequest(-1);
- }
- }
-
- public void handleEvent(Event event) {
- // only accept events when this measurement is running
- if (measurementState != MeasurementState.STARTED) {
- return;
- }
-
- if ((event.getSource() == this.useAsProxy || event.getSource() == this.providingNode)
- && event.getType() == EventType.NODE_STOPPED) {
- // when either the node providing the hidden service, or the
- // node that is used as proxy fails, abort measurement
- this.measurementState = MeasurementState.ABORTED;
- notify();
-
- } else if (event.getSource() == this.clientApp
- && event.getType() == EventType.CLIENT_SENDING_REQUEST) {
- // when the client application reports sending the request, note
- // the time
- this.requestSentFromClient = event.getOccurrenceTime();
-
- } else if (event.getSource() == this.clientApp
- && event.getType() == EventType.CLIENT_REPLY_RECEIVED) {
- // when the client application reports receiving a reply,
- // succeed the measurement
- this.replyReceivedAtClient = event.getOccurrenceTime();
- this.measurementState = MeasurementState.SUCCEEDED;
- notify();
-
- } else if (event.getSource() == this.clientApp
- && event.getType() == EventType.CLIENT_GAVE_UP_REQUEST) {
- // when the client application reports giving up the request,
- // fail the measurement
- this.measurementState = MeasurementState.FAILED;
- notify();
- }
- }
- }
-
- /**
- * Writes the results of the measurements to files.
- */
- private static class ResultWriter {
-
- /**
- * Performs the actual writing to file.
- *
- * @param fileName
- * File name to write the measurement result to.
- * @param timeInSeconds
- * Value to be written.
- */
- private synchronized void writeToFile(String fileName,
- long timeInSeconds) {
- try {
- File file = new File(network.getWorkingDirectory()
- .getAbsolutePath()
- + File.separator + fileName);
- if (!file.exists()) {
- FileWriter fw = new FileWriter(file);
- fw.append("" + timeInSeconds);
- fw.close();
- } else {
- FileWriter fw = new FileWriter(file, true);
- fw.append("," + timeInSeconds);
- fw.close();
- }
- } catch (IOException e) {
- System.err.println(new Date()
- + ": Could not write measurement result to file.");
- }
- }
-
- /**
- * Writes the result of an online propagation measurement to file.
- *
- * @param timeInSeconds
- * Measurement result.
- * @throws IOException
- * Thrown if the file could not be written for some reason.
- */
- private synchronized void writeOnlinePropagation(long timeInSeconds) {
- this.writeToFile("online-propagation", timeInSeconds);
- }
-
- /**
- * Writes the result of an offline propagation measurement to file.
- *
- * @param timeInSeconds
- * Measurement result.
- * @throws IOException
- * Thrown if the file could not be written for some reason.
- */
- private synchronized void writeOfflinePropagation(long timeInSeconds) {
- this.writeToFile("offline-propagation", timeInSeconds);
- }
-
- /**
- * Writes the result of a descriptor propagation measurement to file.
- *
- * @param timeInSeconds
- * Measurement result.
- * @throws IOException
- * Thrown if the file could not be written for some reason.
- */
- private synchronized void writeDescriptorPropagation(long timeInSeconds) {
- this.writeToFile("descriptor-propagation", timeInSeconds);
- }
-
- /**
- * Writes the result of a hidden service request measurement to file.
- *
- * @param timeInSeconds
- * Measurement result.
- * @throws IOException
- * Thrown if the file could not be written for some reason.
- */
- private synchronized void writeHiddenServiceRequest(long timeInSeconds) {
- this.writeToFile("hidden-service-requests", timeInSeconds);
- }
- }
-
- /**
- * Writes the results of the measurements to files.
- */
- private static ResultWriter resultWriter = new ResultWriter();
-
- /**
- * The network configuration.
- */
- private static Network network;
-
- /**
- * The event manager of this application.
- */
- private static EventManager manager;
-
- /**
- * List of all directory nodes.
- */
- private static List<DirectoryNode> runningDirs;
-
- /**
- * List of all router nodes.
- */
- private static List<RouterNode> runningRouters;
-
- /**
- * Sets up and runs the test.
- *
- * @param args
- * Optionally, a base port number can be passed so that the
- * started Tor processes use ports starting from that number (up
- * to the next few hundreds).
- * @throws TorProcessException
- * Thrown if there is a problem with the JVM-external Tor
- * processes that we cannot handle.
- */
- public static void main(String[] args) throws TorProcessException {
-
- int portStart = 7000;
-
- if (args.length == 1) {
- try {
- portStart = Integer.parseInt(args[0]);
- } catch (NumberFormatException e) {
- System.out.println("Usage: java "
- + DistributedStorage.class.getCanonicalName()
- + " [basePort]");
- System.exit(1);
- }
- }
-
- // create a network to initialize a test case
- network = NetworkFactory.createNetwork("distributed-storage");
-
- System.out.println(new Date() + ": Starting test run "
- + network.getWorkingDirectory().getName());
-
- // obtain reference to event manager to be able to respond to events
- manager = network.getEventManager();
-
- // add event type patterns for events that only occur in a modified Tor
- manager.registerEventTypePattern(
- "Hidden service routing table has changed",
- EventType.NODE_ROUTING_TABLE_CHANGED);
- manager.registerEventTypePattern("Sending publish request for "
- + "v2 descriptor for "
- + "service '.*' with descriptor ID '.*' with validity of .* "
- + "seconds to hidden service directory '.*' on port .*",
- EventType.BOB_SENDING_PUBLISH_DESC);
- manager.registerEventTypePattern("Successfully stored service "
- + "descriptor with desc ID " + "'.*' and len .*",
- EventType.HSDIR_DESC_STORED);
- manager.registerEventTypePattern("Sending fetch request for v2 "
- + "descriptor for "
- + "service '.*' with descriptor ID '.*' to hidden "
- + "service directory '.*' on port .*",
- EventType.ALICE_SENDING_FETCH_DESC);
- manager
- .registerEventTypePattern(
- "Successfully stored service descriptor with "
- + "desc ID '.*'", EventType.HSDIR_DESC_STORED);
-
- // create two directory nodes with parameters (router name, control
- // port, SOCKS port, OR port, dir port)
- runningDirs = new ArrayList<DirectoryNode>();
- DirectoryNode dir1 = network.createDirectory("dir1", portStart + 1,
- portStart + 2, portStart + 3, portStart + 4);
- DirectoryNode dir2 = network.createDirectory("dir2", portStart + 11,
- portStart + 12, portStart + 13, portStart + 14);
- runningDirs.add(dir1);
- runningDirs.add(dir2);
-
- runningRouters = new ArrayList<RouterNode>();
-
- // create 9 router nodes with parameters (router name, control port,
- // SOCKS port, OR port, dir mirror port)
- int routerCounter = 1;
- for (; routerCounter < 10; routerCounter++) {
- RouterNode router = network.createRouter("router0" + routerCounter,
- portStart + routerCounter * 10 + 11, portStart
- + routerCounter * 10 + 12, portStart
- + routerCounter * 10 + 13, portStart
- + routerCounter * 10 + 14);
- router.addConfiguration("HidServDirectoryV2 1");
- router.addConfiguration("FetchHidServDescriptors 0");
- router.addConfiguration("FetchV2HidServDescriptors 1");
- runningRouters.add(router);
- }
-
- // configure nodes of this network to be part of a private network
- network.configureAsPrivateNetwork();
-
- // write configuration of proxy node
- network.writeConfigurations();
- System.out.println(new Date()
- + ": Successfully written configurations!");
-
- // start proxy node and wait until it has opened a circuit with a
- // timeout of 10 seconds
- if (!network.startNodes(10000)) {
-
- // failed to start the proxy
- System.out.println(new Date() + ": Failed to start nodes!");
- System.exit(1);
- }
- System.out.println(new Date() + ": Successfully started nodes!");
-
- // start observers for all initial routers which might become hidden
- // service directories after some time (if not stopped before)
- for (RouterNode router : runningRouters) {
- new HiddenServiceDirectoryObserver(router, router
- .determineFingerprintBase32()).start();
- }
-
- // start measurement of descriptor propagation
- new DescriptorObserverStarter();
-
- // start thread that will periodically try to access a hidden service
- new HiddenServiceRequestStarter().start();
-
- // hup until proxy has built circuits (6 retries, 10 seconds timeout
- // each)
- if (!network.hupUntilUp(6, 10000)) {
-
- // failed to build circuits
- System.out.println(new Date() + ": Failed to build circuits!");
- System.exit(1);
- }
- System.out.println(new Date() + ": Successfully built circuits!");
-
- int HOURS_TO_WAIT = 30;
-
- // let it run for HOURS_TO_WAIT minutes...
- System.out.println(new Date() + ": Waiting for " + HOURS_TO_WAIT
- + " hours, changing node population every hour...");
-
- // TODO change after testing...
- long hiddenServiceStableTime = System.currentTimeMillis() + 30 * 60 * 1000;
-
- Random rnd = new Random();
- long waitingTime = 0;
-
- for (int i = 0; i < HOURS_TO_WAIT - 1; i++) {
- // wait for 15 to 30 minutes
- waitingTime = 15 * 60 * 1000 + rnd.nextInt(15 * 60 * 1000);
- try {
- Thread.sleep(waitingTime);
- } catch (InterruptedException e) {
- // do nothing
- }
-
- // shut down one node
- int candidate = rnd.nextInt(runningRouters.size());
- RouterNode removedRouter = runningRouters.remove(candidate);
- System.out.println(new Date() + ": Shutting down router "
- + removedRouter.getNodeName() + "...");
- removedRouter.shutdown();
-
- // wait for the rest of the half hour
- waitingTime = 30 * 60 * 1000 - waitingTime;
- try {
- Thread.sleep(waitingTime);
- } catch (InterruptedException e) {
- // do nothing
- }
-
- // wait for 15 to 30 minutes
- waitingTime = 15 * 60 * 1000 + rnd.nextInt(15 * 60 * 1000);
- try {
- Thread.sleep(waitingTime);
- } catch (InterruptedException e) {
- // do nothing
- }
-
- // start another node
- RouterNode newRouter = network.createRouter("router"
- + routerCounter, portStart + routerCounter * 10 + 11,
- portStart + routerCounter * 10 + 12, portStart
- + routerCounter * 10 + 13, portStart
- + routerCounter * 10 + 14);
-
- newRouter.addConfiguration("HidServDirectoryV2 1");
- newRouter.addConfiguration("FetchHidServDescriptors 0");
- newRouter.addConfiguration("FetchV2HidServDescriptors 1");
-
- // if the system is running for at least 24 hours, it can be
- // considered stable, so that hidden services can be started
- long now = System.currentTimeMillis();
- if (now >= hiddenServiceStableTime) {
-
- // add hidden service to the configuration of the new router
- newRouter.addHiddenService("hidServ" + routerCounter, portStart
- + routerCounter * 10 + 15, 80);
-
- // let the new router only publish v2 descriptors
- newRouter.addConfiguration("PublishHidServDescriptors 0");
- newRouter.addConfiguration("PublishV2HidServDescriptors 1");
- }
-
- // re-configure nodes of this network to be part of a private
- // network
- network.configureAsPrivateNetwork();
-
- // manager.addEventListener(newRouter, new
- runningRouters.add(newRouter);
- newRouter.writeConfiguration();
- System.out.println(new Date()
- + ": Starting router "
- + newRouter.getNodeName()
- + (now >= hiddenServiceStableTime ? " with hidden service"
- : "") + "...");
- newRouter.startNode(5000);
-
- // if a hidden service was started, start an observer for it
- if (now >= hiddenServiceStableTime) {
- new HiddenServiceObserver(newRouter, newRouter.getOnionAddress(
- "hidServ" + routerCounter, 2)).start();
- }
-
- // start observer for the new hidden service directory
- new HiddenServiceDirectoryObserver(newRouter, newRouter
- .determineFingerprintBase32()).start();
-
- routerCounter++;
-
- // wait for the rest of the half hour
- waitingTime = 30 * 60 * 1000 - waitingTime;
- try {
- Thread.sleep(waitingTime);
- } catch (InterruptedException e) {
- // do nothing
- }
-
- System.out.println(new Date() + ": Waiting for another "
- + (HOURS_TO_WAIT - i - 1) + " hour"
- + (i == HOURS_TO_WAIT - 2 ? "" : "s") + " ...");
- }
-
- // waiting for the last hour
- try {
- Thread.sleep(1L * 60L * 60L * 1000L);
- } catch (InterruptedException e) {
- // do nothing
- }
-
- // shut down nodes
- network.shutdownNodes();
-
- System.out.println(new Date() + ": Exiting...");
- System.exit(1);
- }
-}
Modified: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java 2007-08-26 21:03:54 UTC (rev 11279)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -307,7 +307,7 @@
}
}
- // wait for all fingerprints have been determined
+ // wait for all fingerprints to be determined
for (FingerprintThread fingerprintThread : fingerprintThreads) {
// join fingerprint determination one after the other
Modified: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java 2007-08-26 21:03:54 UTC (rev 11279)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -648,9 +648,6 @@
// be sure that Tor is ready, especially if computer is very busy and
// many nodes are created
- // TODO don't wait, because we could miss log messages while waiting
- // TODO should we better only parse the process output instead of
- // parsing log messages from the controller?!
try {
Thread.sleep(1000);
} catch (InterruptedException e2) {
Modified: puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java
===================================================================
--- puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java 2007-08-26 21:03:54 UTC (rev 11279)
+++ puppetor/trunk/src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java 2007-08-26 21:18:53 UTC (rev 11280)
@@ -11,6 +11,7 @@
import java.util.Set;
import java.util.logging.Level;
+import de.uniba.wiai.lspi.puppetor.NodeState;
import de.uniba.wiai.lspi.puppetor.RouterNode;
import de.uniba.wiai.lspi.puppetor.TorProcessException;
@@ -29,12 +30,23 @@
protected int dirPort;
/**
+ * Temporary config file that is used to determine the fingerprint of this
+ * node.
+ */
+ protected File tempConfigFile;
+
+ /**
* The fingerprint of this node that is determined as hash value of its
* onion key.
*/
protected String fingerprint;
/**
+ * The base32-encoded ID of this node.
+ */
+ protected String fingerprintBase32;
+
+ /**
* The IP address of local nodes (typically <code>localhost</code> or
* <code>127.0.0.1</code>).
*/
@@ -125,48 +137,35 @@
// bypass testing if we are reachable
this.configuration.add("AssumeReachable 1");
+ // create file reference for temporary config file
+ this.tempConfigFile = new File(this.workingDir.getAbsolutePath()
+ + File.separator + "torrc.tmp");
+
// log exiting
this.logger.exiting(this.getClass().getName(), "RouterNodeImpl");
}
- public synchronized String determineFingerprintBase32()
- throws TorProcessException {
- String fingerprint = determineFingerprint();
- fingerprint = fingerprint.substring(fingerprint.indexOf(' ') + 1);
- byte[] bytes = new byte[20];
- int j = 0;
- for (String part : fingerprint.split(" ")) {
- bytes[j++] = (byte) Integer.parseInt(part.substring(0, 2), 16);
- bytes[j++] = (byte) Integer.parseInt(part.substring(2), 16);
+ public synchronized String getFingerprintBase32() {
+
+ // log entering
+ this.logger.entering(this.getClass().getName(), "getFingerprintBase32");
+
+ // check state
+ if (this.nodeState != NodeState.RUNNING
+ && this.nodeState != NodeState.SHUT_DOWN) {
+ String reason = "Node is neither in state NodeState.RUNNING "
+ + "nor in NodeState.SHUT_DOWN, but in state "
+ + this.nodeState + "!";
+ IllegalStateException e = new IllegalStateException(reason);
+ this.logger.throwing(this.getClass().getName(),
+ "getFingerprintBase32", e);
+ throw e;
}
- int i = 0, index = 0, digit = 0;
- int currByte, nextByte;
- StringBuffer base32 = new StringBuffer((bytes.length + 7) * 8 / 5);
- String base32Chars = "abcdefghijklmnopqrstuvwxyz234567";
- while (i < bytes.length) {
- currByte = (bytes[i] >= 0) ? bytes[i] : (bytes[i] + 256);
- if (index > 3) {
- if ((i + 1) < bytes.length) {
- nextByte = (bytes[i + 1] >= 0) ? bytes[i + 1]
- : (bytes[i + 1] + 256);
- } else {
- nextByte = 0;
- }
- digit = currByte & (0xFF >> index);
- index = (index + 5) % 8;
- digit <<= index;
- digit |= nextByte >> (8 - index);
- i++;
- } else {
- digit = (currByte >> (8 - (index + 5))) & 0x1F;
- index = (index + 5) % 8;
- if (index == 0) {
- i++;
- }
- }
- base32.append(base32Chars.charAt(digit));
- }
- return base32.toString();
+
+ // log exiting
+ this.logger.exiting(this.getClass().getName(), "getFingerprintBase32",
+ this.fingerprintBase32);
+ return fingerprintBase32;
}
public synchronized String determineFingerprint()
@@ -197,7 +196,8 @@
+ " 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000";
copyOfConfig.add(fakeDirServerString);
try {
- BufferedWriter bw = new BufferedWriter(new FileWriter(configFile));
+ BufferedWriter bw = new BufferedWriter(new FileWriter(
+ this.tempConfigFile));
for (String c : copyOfConfig) {
bw.write(c + "\n");
}
@@ -211,8 +211,9 @@
}
// start process with option --list-fingerprint
+ // TODO make this more configurable
ProcessBuilder processBuilder = new ProcessBuilder(torExecutable
- .getPath(), "--list-fingerprint", "-f", "torrc");
+ .getPath(), "--list-fingerprint", "-f", "torrc.tmp");
processBuilder.directory(this.workingDir);
processBuilder.redirectErrorStream(true);
Process tmpProcess = null;
@@ -261,6 +262,38 @@
}
// read fingerprint from file
+ this.readFingerprintFromFile();
+
+ // log exiting and return fingerprint
+ this.logger.exiting(this.getClass().getName(), "determineFingerprint",
+ this.fingerprint);
+ return this.fingerprint;
+ }
+
+ /**
+ * Reads the fingerprint from disk. This requires that the fingerprint file
+ * has been written before by Tor. Otherwise, a TorProcessException is
+ * thrown.
+ *
+ * @throws TorProcessException
+ * Thrown if no fingerprint file has been written by Tor before.
+ */
+ private void readFingerprintFromFile() throws TorProcessException {
+
+ // log entering
+ this.logger.entering(this.getClass().getName(),
+ "readFingerprintFromFile");
+
+ // check if the fingerprint has been read before
+ if (this.fingerprint != null) {
+
+ // log exiting and return fingerprint
+ this.logger.exiting(this.getClass().getName(),
+ "readFingerprintFromFile");
+ return;
+ }
+
+ // read fingerprint from file
File fingerprintFile = new File(this.workingDir.getAbsolutePath()
+ File.separator + "fingerprint");
try {
@@ -276,10 +309,50 @@
throw ex;
}
- // log exiting and return fingerprint
- this.logger.exiting(this.getClass().getName(), "determineFingerprint",
- this.fingerprint);
- return this.fingerprint;
+ // parse fingerprint string to base32 encoded ID
+
+ // convert fingerprint string to base32 encoding
+ // TODO move to separate util class, document it
+ String fp = this.fingerprint;
+ fp = fp.substring(fp.indexOf(' ') + 1);
+ byte[] bytes = new byte[20];
+ int j = 0;
+ for (String part : fp.split(" ")) {
+ bytes[j++] = (byte) Integer.parseInt(part.substring(0, 2), 16);
+ bytes[j++] = (byte) Integer.parseInt(part.substring(2), 16);
+ }
+ int i = 0, index = 0, digit = 0;
+ int currByte, nextByte;
+ StringBuffer base32 = new StringBuffer((bytes.length + 7) * 8 / 5);
+ String base32Chars = "abcdefghijklmnopqrstuvwxyz234567";
+ while (i < bytes.length) {
+ currByte = (bytes[i] >= 0) ? bytes[i] : (bytes[i] + 256);
+ if (index > 3) {
+ if ((i + 1) < bytes.length) {
+ nextByte = (bytes[i + 1] >= 0) ? bytes[i + 1]
+ : (bytes[i + 1] + 256);
+ } else {
+ nextByte = 0;
+ }
+ digit = currByte & (0xFF >> index);
+ index = (index + 5) % 8;
+ digit <<= index;
+ digit |= nextByte >> (8 - index);
+ i++;
+ } else {
+ digit = (currByte >> (8 - (index + 5))) & 0x1F;
+ index = (index + 5) % 8;
+ if (index == 0) {
+ i++;
+ }
+ }
+ base32.append(base32Chars.charAt(digit));
+ }
+ this.fingerprintBase32 = base32.toString();
+
+ // log exiting
+ this.logger.exiting(this.getClass().getName(),
+ "readFingerprintFromFile");
}
@Override
@@ -300,4 +373,21 @@
public int getDirPort() {
return this.dirPort;
}
+
+ public boolean startNode(long maximumTimeToWaitInMillis)
+ throws TorProcessException {
+
+ // log entering
+ this.logger.entering(this.getClass().getName(), "startNode");
+
+ // before starting the node, determine its fingerprint
+ this.determineFingerprint();
+
+ // perform the same as a proxy node does
+ boolean result = super.startNode(maximumTimeToWaitInMillis);
+
+ // log exiting and return result
+ this.logger.exiting(this.getClass().getName(), "startNode", result);
+ return result;
+ }
}
More information about the tor-commits
mailing list