From 643b91b616a667d0b570279f565f4747be893d8a Mon Sep 17 00:00:00 2001 From: Gene Morgan Date: Tue, 19 Jul 2016 18:43:15 -0700 Subject: [PATCH] Source release v3.1.0 --- CHANGELOG.md | 26 + README.pdf | Bin 537874 -> 584227 bytes cdm/cdm.gyp | 18 +- cdm/core_unittests.gypi | 4 +- cdm/include/cdm.h | 207 +- cdm/include/cdm_version.h | 2 +- cdm/oemcrypto_unittests.gypi | 2 + cdm/src/cdm.cpp | 655 ++- cdm/test/cdm_test.cpp | 813 +++- cdm/test/cdm_test_main.cpp | 6 +- cdm/test/test_host.cpp | 1 + core/include/cdm_engine.h | 122 +- core/include/cdm_session.h | 75 +- core/include/certificate_provisioning.h | 3 +- core/include/crypto_key.h | 5 + core/include/crypto_session.h | 34 +- core/include/device_files.h | 39 +- core/include/file_store.h | 47 +- core/include/initialization_data.h | 40 +- core/include/license_key_status.h | 139 + core/include/max_res_engine.h | 105 - core/include/oemcrypto_adapter.h | 1 + core/include/policy_engine.h | 29 +- core/include/string_conversions.h | 3 +- core/include/wv_cdm_constants.h | 11 + core/include/wv_cdm_types.h | 127 +- core/src/cdm_engine.cpp | 417 +- core/src/cdm_session.cpp | 280 +- core/src/certificate_provisioning.cpp | 6 +- core/src/crypto_session.cpp | 358 +- core/src/device_files.cpp | 298 +- core/src/device_files.proto | 11 + core/src/initialization_data.cpp | 454 +- core/src/license.cpp | 32 +- core/src/license_key_status.cpp | 285 ++ core/src/license_protocol.proto | 81 +- core/src/max_res_engine.cpp | 173 - core/src/oemcrypto_adapter_static.cpp | 4 + core/src/oemcrypto_adapter_static_v11.cpp | 67 + core/src/policy_engine.cpp | 126 +- core/src/string_conversions.cpp | 52 +- core/test/base64_test.cpp | 53 +- core/test/cdm_engine_test.cpp | 42 +- core/test/cdm_session_unittest.cpp | 35 +- core/test/device_files_unittest.cpp | 516 +-- core/test/file_store_unittest.cpp | 249 +- core/test/generic_crypto_unittest.cpp | 416 ++ core/test/http_socket.cpp | 13 +- core/test/initialization_data_unittest.cpp | 608 ++- core/test/license_keys_unittest.cpp | 888 ++++ core/test/license_request.cpp | 26 - core/test/license_request.h | 1 - core/test/max_res_engine_unittest.cpp | 323 -- .../policy_engine_constraints_unittest.cpp | 430 ++ core/test/policy_engine_unittest.cpp | 230 +- core/test/test_printers.cpp | 307 +- oemcrypto/include/OEMCryptoCENC.h | 154 +- oemcrypto/test/oec_device_features.cpp | 144 + ...oemcrypto_test.h => oec_device_features.h} | 8 +- oemcrypto/test/oec_session_util.cpp | 802 ++++ oemcrypto/test/oec_session_util.h | 207 + oemcrypto/test/oec_test_data.h | 273 ++ oemcrypto/test/oemcrypto_test.cpp | 4128 +++++++---------- oemcrypto/test/oemcrypto_test_android.cpp | 10 +- oemcrypto/test/oemcrypto_test_main.cpp | 2 +- third_party/gyp/MSVSNew.py | 2 +- third_party/gyp/MSVSSettings.py | 108 +- third_party/gyp/MSVSSettings_test.py | 4 +- third_party/gyp/MSVSUtil.py | 35 +- third_party/gyp/MSVSVersion.py | 102 +- third_party/gyp/__init__.py | 29 +- third_party/gyp/common.py | 106 +- third_party/gyp/flock_tool.py | 7 +- third_party/gyp/generator/analyzer.py | 741 +++ third_party/gyp/generator/android.py | 1069 ----- third_party/gyp/generator/cmake.py | 423 +- .../gyp/generator/dump_dependency_json.py | 20 +- third_party/gyp/generator/eclipse.py | 134 +- third_party/gyp/generator/gypd.py | 7 + third_party/gyp/generator/make.py | 153 +- third_party/gyp/generator/msvs.py | 362 +- third_party/gyp/generator/ninja.py | 580 ++- third_party/gyp/generator/ninja_test.py | 21 +- third_party/gyp/generator/xcode.py | 125 +- third_party/gyp/input.py | 754 +-- third_party/gyp/input_test.py | 14 +- third_party/gyp/mac_tool.py | 284 +- third_party/gyp/msvs_emulation.py | 208 +- third_party/gyp/simple_copy.py | 46 + third_party/gyp/win_tool.py | 56 +- third_party/gyp/xcode_emulation.py | 557 ++- third_party/gyp/xcode_ninja.py | 289 ++ third_party/gyp/xcodeproj_file.py | 174 +- third_party/jsmn/LICENSE | 20 + third_party/jsmn/README.md | 167 + third_party/jsmn/example/jsondump.c | 126 + third_party/jsmn/example/simple.c | 76 + third_party/jsmn/jsmn.c | 311 ++ third_party/jsmn/jsmn.h | 76 + third_party/jsmn/library.json | 16 + third_party/jsmn/test/test.h | 27 + third_party/jsmn/test/tests.c | 378 ++ third_party/jsmn/test/testutil.h | 94 + third_party/protobuf/gtest/libtool | 2 +- third_party/protobuf/libtool | 2 +- third_party/stringencoders/src/modp_b64.cpp | 269 ++ third_party/stringencoders/src/modp_b64.h | 234 + .../stringencoders/src/modp_b64_data.h | 480 ++ 108 files changed, 16537 insertions(+), 7174 deletions(-) create mode 100644 core/include/license_key_status.h delete mode 100644 core/include/max_res_engine.h create mode 100644 core/src/license_key_status.cpp delete mode 100644 core/src/max_res_engine.cpp create mode 100644 core/src/oemcrypto_adapter_static_v11.cpp create mode 100644 core/test/generic_crypto_unittest.cpp create mode 100644 core/test/license_keys_unittest.cpp delete mode 100644 core/test/max_res_engine_unittest.cpp create mode 100644 core/test/policy_engine_constraints_unittest.cpp create mode 100644 oemcrypto/test/oec_device_features.cpp rename oemcrypto/test/{oemcrypto_test.h => oec_device_features.h} (91%) create mode 100644 oemcrypto/test/oec_session_util.cpp create mode 100644 oemcrypto/test/oec_session_util.h create mode 100644 oemcrypto/test/oec_test_data.h create mode 100644 third_party/gyp/generator/analyzer.py delete mode 100644 third_party/gyp/generator/android.py create mode 100644 third_party/gyp/simple_copy.py create mode 100644 third_party/gyp/xcode_ninja.py create mode 100644 third_party/jsmn/LICENSE create mode 100644 third_party/jsmn/README.md create mode 100644 third_party/jsmn/example/jsondump.c create mode 100644 third_party/jsmn/example/simple.c create mode 100644 third_party/jsmn/jsmn.c create mode 100644 third_party/jsmn/jsmn.h create mode 100644 third_party/jsmn/library.json create mode 100644 third_party/jsmn/test/test.h create mode 100644 third_party/jsmn/test/tests.c create mode 100644 third_party/jsmn/test/testutil.h create mode 100644 third_party/stringencoders/src/modp_b64.cpp create mode 100644 third_party/stringencoders/src/modp_b64.h create mode 100644 third_party/stringencoders/src/modp_b64_data.h diff --git a/CHANGELOG.md b/CHANGELOG.md index bb4d4137..97050e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +## 3.1.0 (2016-07-18) + +Features: + - Updates to conform to EME June 10, 2016 Specification + (http://www.w3.org/TR/2016/WD-encrypted-media-20160610/) + - Add per-origin storage of all persistent data. + - Use EME Direct Individualization to provision devices. + - Add IEventListener::onDirectIndividualizationRequest() callback. + - A "license-release" message is no longer fired on calls to load(). + - Add CDM entry points for generic crypto operations (Cdm::genericEncrypt(), + Cdm::genericDecrypt(), Cdm::genericSign(), Cdm::genericVerify()). + - Add support for CENC 3.0 and decryption of encrypted HLS content. + - Add support for querying allowed usage for a key + (Cdm::getKeyAllowedUsages()). + - Upgrade to OEMCrypto v11. + - Numerous unit test additions and improvements. + - Add jsmn to third\_party/. + +Bugfixes: + - Remove IEventListener::onMessageUrl() callback. + - Don't check/validate crypto mode when Decrypt is called with unencrypted + data. + - Ensure keys are loaded before sending OnKeyStatusChange notifications. + This avoids errors due to prematurely checking key statuses. + - Correctly handle a bad RSA key. + ## 3.0.5 (2015-12-16) Features: diff --git a/README.pdf b/README.pdf index fbd9ebc47c612820500e5a5f5622903c1011d0d4..065d8629d3f48276f4b7636df7e2e94526b06ac8 100644 GIT binary patch delta 529326 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μrDyuQ1`mJB5Sb`lfGqecX$)H&3LX&(pw^2( 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%0D~l`q-~&D!OV~dVD=s-7+`>_e^zazP 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%0DhXX(HIWXMFh?<)7Jr{x`zSVH8EA_S& z@afvlJ&l>9#UH!{QsS)aFe>&FFm?BbSvzpoHt^o+VMmk+%EA6m9O={nW;>5l_=5Q< zLXihTGg>Ft_)YEW%Dh z=nV*G8-031J=Y)S-dCt|7_MsMiGH5E)^|w?8)!W0Z&o{_Yb^ zeAVZhui`OpT2-L9&!Y*6#yNYUF9G~S4}3EA8hTQn6@mhRQbJYNLLg}PvArjs?Aw(P zm;+4%887--OBNh>@v_u6 ze^dVCetx=GDl?sUFUeWX%>Q{GrbPFT)wuI)Fl%O_0flD7T(v>R&Hh4AMP2bPUHEX@ zy=+?2ICM4v)KBQJhKFaEK0DixXiAZcY_$Xc&OS>pPt{G9IkEJXlBUoFfe-*4q4?1g z(F;G&X&{5>U=>%UNByUt8{AV!P6tt$oSiif?J&$ukL=ra^ZTbv#?VC`%74Cro15~Uvt(Z0~|=0Z4kNdM}e5AZ;8Z2iNlcTd6t`8i}fTN5)V*O`3Rt|-u~9F5|vea zsw&f5NKMQvsYabT$T6ca<%_RITRvmb&op0b7^oJKV8+Jjp=wrzBGw4;^Ye7Xru6ii zD3~}3hbS&19!!x$+KK8(02mFeobsa=(gJdX(rq6oS9l~u3E-DZe z|6QcJkNT07dqfsBXIkJaR>+hd@8r))%`EmVMen+8S-($89gE=%CS=)Ks|& z1(Sf0-9ku1(9f6(X$EDw1cQTnVxKtF#F&Vtl{qnyNbwP_J6d7Zc2|G=9O4FG}M*%sW z27Zt52i$&`$}$r62fHIu`JzQFT^A@Z>3z|+wV#jw|8rS(dp=^f4pmFrswc@`l;!o3{vxy&uwHwf; z&jF#C=V5W77CC4{nAChPZ689Y*P}u%kSemsEPixFVcAhc7$zm|>2%Kc>u@m%V{HKU z^bx_Y7(bA%olYH@@v|%!+t^UA0jAxLhwJ}L5-_Q~cUJTdFGi*iP^KI<4;Gcj{j!1^ zdjZIF!&Y$wB40gLY?~)(qUiC9{<2L$90|N9#y=PGVH#vWC-%R`AlQ7%6+p?O_Zvlp z;k*wrl58Sc%Z~u_^nSQi!V}okGsD2LV)v!nw@KjHR$0d}682*ZS|r4C_2hMWQoJzv zP)(no$aMC?abJRj;`CM+m~a`eqc{$&21G)iRm;PboIKp#+5g_o}_Lgk4 zz_=8hdOIcdKHL47Rh`ADP2VnUv}WGA?&fC!v=$NH&o6?LFFJn@V3b4=OA*z#=Fd7J zmHc5(=!Ew)t^4gD_mxPB2dMM?#upAtNZ+7v(gTcecMJ1+tFzzguHO-?ZOsgtrZEX` zqT}(xUjObWE|jJn^?#D2M+J3d`RYp39dHX00L?I*Z@=p!OGTplXkB zOB6J8EJei})$&g|LlE{-@ZCUt|&8z~0wK3p3ezWncR z%;Kh}0ACNRvv3W5QYy*|4kG`1LlnGNhf@&#dleAP9C!if*(2DPctL@3>|dZm_y6ZV z{Iyn>^mtlA# z5g=3pzYI*s)K*jpL*GN4DibvBmSMosZE-a9I1}PDJDh(F#Or#flV^ft8YsKq#*^L7 zq5hnyEg=7jgx$U;C}sBy;}X~JqVu0F8<@b?CUcyRr(B@&-0h#>PbrG8&f7e5Ze=H~ zSM>|q4eP3||8-Czz_sgm;o$V$iaQNqF{=V11-{-cq|bF#TyRfnu+(w9rud2Z(ZS8~ z91!}@^1G5t!ppL{orQR?mT+m^0`=|6q!JvwM-G^h+yfN8GJ{w@5EJuq`uY}gdA6q$ zYJ{LYD#9t6R2Qnc@rXx(gBZ`r5}6i*U5^B#AtxpdxxI~eH`oQ1A|VJ`PEOS01dqRG zP69*5cz^ud{wC^xU@PVY+tl-T!4%*dFG#4C34SV9h9*Qa6O3T`1-{W>`~}jz{ss2q z?*b{z_|UWw?1Dz&7oBnh;TOOV^y27;trorLhjp4juhb3riE7XW+@Mai4&G@xk_E0ZFYIUtd4?F=XZI=1f>Qk;)?l*5buU7=FjRZ(pMQ#y*)*{N2Dg_Xsbx6lX zb4kH|T3%7@0XcQN_66s@X**do9j|6SR5IiMAFj2^FHly&Zk3?Z2Z|R_T5oN18?BaS zwB5c|nO9GmM=dwn=)%syf|w7#3(Tuu82dmgUL^>+vxxP-y4`QjDV#tvLK@<$x(Pzo zLQN;iT5(%Bz&|$zUHA|<0^Ci_PMaBc&PKr6QG_D8`8QM-Y?u#Wk{dHculd4E()x@(Nl&^Q?{k-gV)< z;KWWS$0kjr^J1w3n+-gHn)FYZ9JA@w5nu#Rw|uTc__S)gju6_ak9$FS)sOn(b1Luo z{ASO{0Vco6Lb(e}b>_mZ!)=95i@mx2eVK6##VH((Es+eoA#qnFztXn)-^~qu%x#pi zaz*%UR|&t& zruk>lgi@ahZTTKxB7dkk=R@;81lse3D;b~-^KNFO9}FS$!SB=oD9O-pK;R^f!f7{rzx`^o%Udlnf8x}hHg?&7RKx^Uk z6lZ4wx=xGXtMKH;t+cKnhD(Z%(4J8B^;02rWaUizdX4^*XINrOIuT+QUX6$^%{M%M ze(A+D)}RqBFyYpZe>+N?mg2I?VYfhO-L^ZnDx9RL09ZxPU=&`G0Kp7S!USX3wjs$g z`k1HZ5w!~%scAiak$5)$Olg~jV_EHx#h&c(^s#^VAMe2r-1!7au7euZ!RKXc14_uo z6vt$uIJY^HgrB5(V)&|r)<=T&9S5ZicP+Hv zEIikr3K~|tp|l4KT*7_Ci;1OfD~Mi{)Br(Xg0z5(46PSl$*naP4hdoU)8!JU6#Acy zPhkcGSbpaWdhRXJlejpP0JqcB#Clw+e4n9vg?dm zsDq_eu9c;QpTI#QyvLyR7zIEs71#0v5q}1xs_QU{@WFVY!-Kjta)#VeW%)(eFXq-z-a6!CgPw3ITkq*oRu>&Th9NKP3Pf(gfN(UkI?dWeEU@f6wJ z2wB&G_h}PE#dqlviyMWH$y81G$OTIL0)nwcfqsX#U-UK~5v)@c$kdC^nwa@wS8WrH zh|8JCURfePBi<-@iB!8)-(;x@GdQ#4>EBI3D)1OqlH)++>#GhJa3Tm{85LsF%D5XA zzeK&G7SlN=NY#^B>SNKc=4nU+6AZtTjKs0w`+>DDZ=chI1sY4^AjXfu*JLahyP0q7 zfc3CXaEiPv>c>^J8J^eZkqo*6)-E!gYrDKHEuA#NZF>`01x|24P!*$fb`E zA$~M5Pr7NH?R|PRd6bdYO z_kxT)FzZt^Tk`02lK<2`=B#s(a0fVDmYLk}{lVTV3Juwnj$wJrZS{+cR(@H^**T6C z;y6p$;`YM)^3G#2Tv>HD5|OA!Po?U2NrTLoWn zx}NCg`u7q~IB?_WDUo~1XvPBREF5C6F<%{85N}M6^z;M{Y=)vqtN{vk)!;A`GVPb zwC(A|Am!M~l5=(l&3|fQf z*K`Q$@~1ZH@%NpCYf+!G|DbkCauDE|TGl6hqCnBpGrw#i70LMP0?0{C$iD7&MwwbsX$uZV*VJGXh}y}%-*QKSY)lgoXCHZg zKOy0l7&2}0%uh1HQwRw4ux`mkx}^|aV+^FvJ&qNM5)~_$twH|GRr>+I_D1ktm~H2n z({?Jij|y4TLiv^zxLmhHUS{0Vp*G};q>RnhXIR&XPufMrUpb7Pz(oxDuzz{~!s_|O zrqEA86}jf@wE2E8^@C1N^;=bmgpYCA7CpY(yCfh({Ezay1bc6+ep;H!cHy1U(9Xm_ zwe{vNxsNPZ^z`Tik?9447_@yH)?|{YaYU*FC!3kKKygP&;8B{`3HQs%&|W`glqEGD zjD^DLu$bh^(&!Hi^2ohqIW%wZj~)zMo)^$Wi=~8huHyv+i%-q-VjozwtK;JYqw!Z< z<{h7+D#@`1+MWbsmPltu7ngk4w|MJsVOl^B6IV8xqp>HZd}U3UQ$IdnE(*ou)cY7c zuNv2S4fq0|0cDEB(~lDg1glZJx^#3%w&vog1q(}3(YrmXrjBdyrfn(XMMv!D8&6?M zFMP|o5>E4)wWz3Yhg|Fk^ao_3Mo~)y*3=CX9K$cU(~DxHScSw7L{Ax#t98U0bzV$! zm6Dds24~w~qKQer%JHW14#sUQ-sWOIsDr zOhhjKG>g2D5|(=`u>wjhzdb7W3PXCCx_Kb&NykS@-*W{Va$Uvs%w$XGkGY@M7~OUX z`|45xZ35rgo*|FbCa!{0R*|DDoNPazetXA>y?Y?>lg02+ei3JTr!b5s=<}bmeh^p0 z`Y zn)WkdYIY_=YBgo7i8z6B$jDR35E0z#De*)0k78zpy`(ZF6jl?A$H00M_Oj-5Rjbk$D_C+@a07Rh<+ghkXAfXyEo& zDw4=xZ}vX9AP5e?T&Bx-J%vfi51+iwG4{6{7s+~SrAH0R6uX`JBcWC`XKY;RFhj^N z<95=$eO)XTZJ0S`>YmlKNA()3Wy_R7Z2gB^RSlsRpY1?EmeRxFoL&%aA)VADum-0< zV-5yC*BqFQHb}MEb!=vz{?68qoFe&rd2F}RvG&*UyWB-Tn0yK*l9**X-dQNPLXqHG z?q9sA`!L`a^G?Ny?e0gNZAV=flfNcjbfzBlUC9$y@Xr46dbnbpCVBq;`na@eS(te^ zw!evsoPTi%wWjZ(R?rPiv#c0Ez7sb>?~-Ux%_X1Zl2U{R^9;?FM^l?l5m#rnsmsF( zIy}xgBRwfJytw-#Q_L~}yfIKyb^UCD*%Omz@_3fvfF%y)EOwM`MxuPdW+lJ0h^^_p!WI(nycTQ!{YW2O>I@(;8 zVql}np>KEZEAPCZ#cP+qiB9P#VS0SauaLgtY9OC6=+=FCR8RBU*C@_|E20d_z5b_l zG!8a>iPC~2tj24&rMduIslBv8N7K&Lf`Z`#la*I_Eb(^&AJlGKUP+g?D{(<&=17rw zmiR9M4OQYoamXZZbNr7M?!I5=maXtf)GnSQ-P0m(9R4m8Z9yAH<8u^m~qS`?K)h>-I0d>p9F9%xwI8WICgcPP@#?AtRp7=K8ko_Z+a>eRrHwbN$j)w zmxTn+E+emuK+mBROgV0Kal@c9V2r_lRh+=W*C2J_H?Xx*Al5#bA&nXOD(o7R`C6#P zHY%&asU^B5xfBAh#_1_+=)gc;a;m{%TM6miy7Smv>B!=^JXPjsjdSt%4sP}ZTOntq z#dK0j!BBO!2o)yJTFok7g@;!>^-@pg{@U=i#}_XvxS(oJcsw2ozR!9KOMRRc>?A)x zNc3^>!E!RS-C20He(9O(EB^dTZCL)|=iU`+z;N41*nk|U|FCj307(!fLgi(qj`>U1 zbq5#eKg5x?7(R>cUy!ZEPvmR%Zh>q)!OKbuN=xM3?qfaY>My^ z^2D!@MI^eo%sNv*x(x~1HvGbsw*DHL;RtD#|L)-C#(s!!=)N8DHETE3T^x97~RK?---1nPd}xyIOnz5Y`7}4 zj<)6j!=bHwo0};|%jTmaYN}i@8JlP z5(@=&S%37$i=Gk(;iYum-+BJ9d=MXj-@I&azc*TDJ~mC;JA2j-U4#mv@CQ$5-UZEC{S7 zV3X$kEEC2Yz)LR`E?d{?7D*&E{7r(D%aRypWKI+7Qcpp$p8N9ws1v!t+}g7I%a`=0 z^!=@+0WWhYdF4uFN}CvgX%4xSEkW?!TQwcToTO_FhPK7vBUy4EM&?ni<9FzkF*=@_ zX&N{-x+1!2YPu!EbtyQr;#lmsv*Nv5K*fe7)`v5pLGH|+GNdhIG-5p?GhvfAArW5y z301Qn6G1vpH`%_%;@3B*@A*%Cn0_PMlnJUhnmEWL<7tB&0wQ+!1g2BR8^A~IE5Xo# zK5<7M5f|>b#QyTTiR#KatNd~_RHZg*{Hc^+#c%2(nyR_-{hp*PO9n?;WtPOqKx6y2 zhGQzO2u8gO3ut5=cY%7GU)@*5LT0gd#|(NR1_>h}E1WQ@+5$Q_o4Gy(*ke|ojyI?AUW~Y$gwy)}IUc+|W%c>S}-QGB!!RvXAr&LgYX8iP; zjUxEC?CTOWJV*Nc8LM=AXt5VSntl-Ud;kpF8ZOB;h$l}i|LCtS^|oL+^QStZW&po` z@_KDS_l4Z|EJf3B&tT>w{{Ytk8p12>Ga)+kxp9@Y4*kN;MfK$JGS|0@;jk;TTHUnX zm}a6&43T@eqMvc}m^T3$R*MTOJ^SLZE2(=XB#9fXVDaSYuR1kfXF|#wI=rg&`Op*tqE1V} zBeWl%J(Q8ttX6d{h}dbTQ?DhOIv1N$f8xs@glN)yZDeBw&0Em)jv<)P0(JpTq&L<~ z8^HcrHi^m>9)=a(DnA97=J9aW)p9SbRgy>1uWJ<0T=KHv-NDm7aJo3s!X7XE>tG%~ z%|m+#4@~`Qs>D0r!KhdDs>QH~UqyF+ zy@Ug;Q&gbQQk&&1VhX;^x%GsH@w&4(vWQ(22Q0nAzr;W@vI1su15de1LVeBfvs;&# zF~o~a3J)1;em`f75iEOz90oEy0yGGv(JTMDV)Z8*e}l=ne}CoD-HIRVht+PGPXC0m zPO|ssUCdDdiO2{Hf{%vhW{aa$n00mDqe5c!F>mD;}__QLLdqxWTueD`2{T=L0IBb9+d zvB9@PgkIAR&hvXsEPQrM;{T{QH4zY#`{UvxH8;^PPiz|McB#*e8l| zO0>UKM#}#@eXM8*^b_b67YM5-2qyMq6t0fYE0{=&j$DUyoUC+@dlh)DHxpZk(#$kG zJP=ZXKaOux->SPkLFd~JP9tViX$bxz`)!;`%1~z%Rw@St!lCTz9VrU z*8U%!&U$54MpHzI8_8?Q9pp6Z5rmz#q3#beqz6LL5r^FYf26Bm3S~x`ra^kNyNVJd z+@W=r>2Kwi(3mmzgw*wTh)nv*4rx8SiFh*b3b3K^N*-T2@_yTl4(pu5S&*!ElwyJa z_ten$(WxT`qjxWqLiMu8#|2awXqx}jV9XA2-=zqGYRL42JMT}MhNN!J=%~lyM&yFD zAFVHODQ;z(2Jn{gNA%#yK(X{00|a3LpSMbYUdRwx^)Jx>9tJ-+5}1K3m&*=sDfrF`ffm;7{`_Iz|1OaK=Cgfdi}B&O z{bq$et&tlBK=lf8>)H)*Vq5$JP4ov11uw_eEPInc^`l!n1}{Tp$BOo zi(V2gvoCXEK-*En&5DinQq#diEoY6->es0G9P59v^2L#S2U4$EvgqWs%9e*duro)9 z(IyZv=d7>|KAwZL*XNu)i0Acu^2^JeEq$(iGzyGy64y+1kz`+u)kisZ-cyg~A4497`|6j`?x;X|{ z^KgfPkZXaCo8Ll+Jb4%}7)^$;jP=-t(!!(QV}Q!)+6_QR{Y z54FH(?z7*2#1A16`s3b^EcIx(&~2el7$HUUC%^v&8-N>lM^d=`-HiF53SqiCz7MWH z!0+=7T2L1CKRT#?@XiHG&+|-cBpMX>Jv&g<+Vg*KeZkXwFY@2Yu5g3Z?X`IgE#5>L zkSmVV+b9QnOgDF_!<1D}CSkJb$02EK(Ifbn9WvAW2xLZLxSvl{xSDD9^=v&f9FS%n zw9d$~>^NQjy*np()$VTwkyB`Y+gx2CN(ySwKw$ed9z*8jMCAqK?Kbwt%8FEv{E75O;U1`Vrh^IcFd} zTBfD6m8!NSN6ML>neK`0@r%`9*Y|b{(K8)^+HK49svon=eb)FwDuxu0Cm2mAiB=Cc zz=dMIm|LL@iPvaTd^aN{3HWO0^u&SRJkHyqhW1;2=@c`Q>|6VbdB35phB;N|Q3u+) zPQpo4aKcrWi9(C)r#dftY9Y^WPHX;Njqvxd4K|zQ%X(80YFar-3z?&ywJKL8?Emxq zjc#)`H7f-%)Bur~b*3+m4EWDBdP#)^?Fm<|{t~!_z_DhXSjC^Jx$qYTd_c%zHqnr}Kfb*KxJPCP5HV(K=3cn+BqYeC%N=m_pW~^JB z7}d%SSIK(kgJ2x&Qzu}QGC0x6^1!w}zgSjh1(W}Wx3`Rn z-73inwQ>CSaBN?>N_f9SuBM<;QWPu4Q#vg@5WX#cM+fu#8M$$> zc=|!m>*E|LLlh^u?Q~%;t62w|{Xjj6LP#%RB)5`f9DI8VW;s6z1fnJj_sY(yD?Gav z0*NEAxIedq&$jR1-{g5?MJ)X5#a81SoEnhV!J_1Afm%K;TOoF)pi}d38KWl(Frs5e zAeg+x`a2FQjG}RA*`$lqQ8hVLXoC{km@`Ws9_&tI4@n)4rmfCDC_q8{9u*(ZhnsDs z%zfq9{&AVH<}!RFjnEE%Mvrq~=Ble0 zbIj{KFXcFRo^+LLA`wQ`3sz6DX0t&{jDp#tW)`>&?!ZOD|32vPf*7rY+3{lymUdsQ z@~?a&TQN?QKNlou5%%uALJ??}VHC;7ys;$F*>_Oa{;`jU(Al?L^L(`a@6%QtlSVfU z>0kS^&_|F;k)~^F<5B`|Z6h#o5eKt>(DZ7XX3uE?$CzBfu&l6w; z|KEBl9-8#1XB;rkH;~7DTa>tI*I?JV6&ij=dVgT+t_zEms)LifL>Sysy~!5#fxz{TGF z5#93w{?P+>EzM%Yci)JcCz*ymJcbKB6JZ0s2pp*(&uRBsy`C*Q>beRToL=1g#w4)* z0``cFCULu+Q>&Lx3HQwlFD~96_(tG^bV=5!1uuL_VzCQeY$o093p$?uCI0W_7ck(( zdhPpRUfV&>EBimNX>>V2HQjd`_GE;3uA;{UA7XUiW$U!-XqrLlqIR#+PQ;X%BZhXiq)TGzYX=~2)y z3wc`3Q*Z+8~<-Q zw6Ap~xSV&2*-g@Lh>2)5_^O`4ChU;WT>$8a!T$+*fHqxCpN@9ozw&0**C;& z*|jT#3V)2qSvgXm+GqI@!@=g-jEnO;unm1;mO0le}{ zmegHpzw#b09JbL}zcD2yY!xkVQFgZf#g*$BjvrN!K<=r2GQh#}S%C7(85mfCjBAa< zS~kN=NEUq0YW&vyvw$F@9oVC3%O~k4WPsOcanQ2OHsS8`P?Jo+@vyWdrY7`go>xvyOxHBTy=+7>+?zb)K7kD| zi@TT!R-)Ln?RjolIFQ_Uc3vkrQo^M=b{L)Noq>~Da4h=3NXVPnMyOBkyEre;>mE}S^ zJ@M{8Zqj`~-kO#Kn(!S4S5Nak-}b|mr3TIm2VNLE1<-31g4x?*CZ6*E@pG|8fTY{8N*cSVpVo%uXKoa(w*-|| zt{fsTJ@#x^4vZ>-@sVbwuYBkc1Oqk#?;@_@nyoEdUuALW66fA{`c18jTa|P522b!w z4%4+__t~rwzQ%z1l6t`1nJW^%^zKsGG5lK8TE$`9n zYM=yc{(1y?nEzR4^MO_y!rwt=ShfF6nNgSf-m_7s_O5Lc4GhYlw6BJOzZTd7$y>GC zqUpS*3db%_Q=yY7G1YgcqPtso>-yPV_rxKVRvwBDSFg|X^#M`yFxHjN zY-S!5B{z*;dr0NfSrY`^9~v(uPL*az8h{RQ$V-RM^I$zlzOE0Wnel8)&s3#YE&(I1 z-Tx=>uXaoY-jS4#z;njXtw_^K1FLG@^BkS?-A2G1a&n9y2nZgL z<EB^P{Bv6p?%P?g)#W5&XX}mA~U`A`b*+t-d)il_JTg?Ix|zyI&_+3UOY&J6p=nQ ztN}%(c>d{P+`p!KywRV{b7n~hmPP?{_4ih7YkJDuuYX>Ti9hhK$yvJ_G%4jplERqC zJ&E@vNvfmrR4}w=`nFAY*h1my+ zI@_5=gfwP_OAd%p2>1SZcJM}h+9FNJ;g-5nNaNe)PnIikB0%saJqY^*pIY47u{5lio;#$MEc8fs1>+1__R3X6f3w6@(bUv>7~u-iodP8q37h(0 z0)C%+zjgG51g1x2JEIXy$oB;EhW7PBW7wn!X;uSDnE3`Ij+ZqBHu}nc7dDAe3paOm z%~T1Ryc&!X1c>hjEisqyTT|`L;Ts-536X6zrsWDV3B~N5TYeN;<&s}>{bSp43+oax3Z^Aw*+L`xX8~=>%tC8QCW) zB~s8x`xMJEg~)%qoTh<{&}ItTBQ_id661V9@|5^@_@AHj_5zQx-Mqcl@?bEm)(45-iXbGOu!V-? zs!LaX&hhOr%*zcDpF2bd12MX)sY|snZ$6tTvn^fomOD74p4AQ|9N{nV!6E<@PWt^s z7`LdAUVh7f9%;U^Y6z(n82S|`-P{GiyM`jU{O!BOFAHc**^RQVpB+?$yw4S&?Z3?L zux<0!z>XOoSp`ZNsj6UZ?4Gz)bqcJEWoOdg##V%5biGM?wO?z}hdC_QPr^Yk{=GKx zeQUFu3%-<~k0~RAUr|{}~cR8Q-=nxFsvd=4_;UK04QmvB7_i)*8n>TYe= zn*kp~Ilq)Y^%}m|6arklMjcLymRK;$T@Rms32FLCnQlsM%@J*TM+rTZdG>V>e+T+Gav5>h`}!>va>-pN?+R87&{`fE(1ys{j>8MZ#U&PzOWcK9Zy zlF7`ULYK_qaiXZQr}>}zpA#lx7|OB_^zouoyPcOfFARxq1fGOE!PKCp%p=%)Dm1pf;IN@CK*3t=BYc$y zZ;p--N2ct{ecLoX2-H;v2YwSE zGCwpAX0ZHu0yx*cFNmm0xD~zm>%Rb-NNogYjKJ4GBm5h$;3W(;Ir+Q$9?7S@ttQ{7 zWHG8${dvPi!t+6ktv&E$#FvOh;p_Rya~@Sk!inh^l>wFe)<2yYxDkm`M1zC|ZWOEa z?rUGXJ%7)0WqpzI7v|}bA^I3VpEo8u98Yq_x7-9r z$l;6k$@4IA>mgW4_LpCs^QUfrAZ7fXIds*rx?``b4z9v)K<87^@+jKwDDff-lxv-@ zGvmEaLC(wCKF?j2L-7y@M^qYhlu0CBWS@r5VyE@#g*nrBmalE{u36}FLWNmZXMX*3*I%AuPerF96^In3#CxU2FTG3-LZ*On+g~b7QDsqQgodY2^ z%#5)6^9_l#k|cN?V`D59$N37O{cKNoRP#)U8k|JPX7$zw&No}?lfh>{4d<@aQ-;D$ zU4a!ibsPW>g&s2TCLtk7J4#W>65!|Kb6RPtOn*)o%MtPI|Mu!XxZhPQxBo7e3yFlI z7(X2pB-rd9NnJ>I{9#DrLU~~8{_k0a>)>(UK`)TCpr=!n*WMv;V^c7kcyK(Ef7BID z?+$Oohu2a4a4257^{4YL36fL}N*Ivd4v=ucTY!j+Haj;5Zx}o=r+b|&UhXEVb?(HA zz#)G-D4AP-KA&&HlV%+}(EPt+&i~u7uI+iFGcaHu_^)AD-`D=ZmY0{|Ex$*A7m%d! zq`{f#PKjaX^}vMq6NB}C)kxCn-VA@K?zmfKhx-2aApgH4@&9jkVdi{<%_hwLO}pqs zp-yW9BQZ8#ETgpJrgbi(;3Tg`Qv!k@K0aD;aV$?S4{LL0EWhlOK#d88mbkS5Sac9x zjkN+1GbRSoV(r2N1X|7SBOKsN}!ksd{YfAL5Vb6C-WgFN~ zXk;{utUM65-vvB1yDm(=lvKlD=mxqEG)KPBmklW7OS-p3Q(wivFW5e=>)>V+<_oJO8;UZtAH}ciDNjm)! z$E0ahN&%N>UcQjGgr_&)?6zwDuHsUX^og{QX(WXbl!( z(Xd3m7Q-rLo?CPx`GIe{+d#8#_h!*NvL@M3!=V@b({0n3Pa)~^{>fj3l=nKfy>?GY zPO^%9lsz{pb&UX{W_4$3RN+pj1lgM0VFn`#)QX*~z&4YhvHX@N0Lpf(muIPSjthI{ z$4;IC=Pu0l_fpgM^;H+BqJ1%_os*~xX`W@%SMz++B>*EWB(~|bwHv-j21woXRw@fcZn3W1!5aBdMlX~2;@am7#4cykG-dnlfU|I2fHlYr&OQ%ES$;5_^ ztprYXAOU3j4|=Xb?oB$&;rz^vB|EPdNpLI+FI56UXqxskQk&lM=SR_Hj?;g_dG5zL zzI>2l)vKNA=!|y_3v9%4OB{4lzG3JV?P1&o^>*5Q<a${eKWSByz<<3lqP z{yX-YcJ!mau|}DhHDC9%^1UvF6H@Z&zDB_;*a3J&n}Q=@V5D!)E<4NkwUdM~aVZW_ ztn~M|w$X+0g;IAl?~BypU!+f@d6Pq&1<6ZHw$Gz^iDx$uj16vn1W03S+w2EYmQ@VW zJU*s3bfC~dHhod3L5~$nFQTX)$a^Q+RYuTmCRjd48-x5O6^Up)H|6U3Z;c}rUl&bt zk1~K|3{j(_xBw*SLK~QAc2%K)$ot`QJLB6ZN?OOt)9+~qK}b-CrS9kAsLfv1?%%{R zu$1vh@&%&GKKr-A7}GhZw3TOZ=;jD(1~rex8H@y`)htD?Y#3cD-mO)pq;$S=LpJSH z{p*B8iSSdVr#5@`L(V2jq<8h4rEv`>@=tQW1((W7<%!?Bdu$ri>(MQr33iR;Xz%fm zEZs`U)_x~KJ3@uFBG=TUU`e2-eC?X;lc8MBP%lL=?|3?#K0f(YA8AT05buvFYFIhz zH3HHv93_*WIcmOQL>pgZ%85=51dwh{QKgsh&lx}Uxm16I50b$}0$mO}qDZ31t=gAB z?jb430pCT4pX>Bj*lTHwQ0iiu1v*qxzJ{iZO8Jm~`n+F)lcO7~SR^sU#TqdUYBc(A4RPoJh>0hY& zw_HnkgFElBT{XF#R`0MkG!p@f2+kb9*_q@rRoDku0poyDN2mNu2lgw99(m+bcZ6v2JAie%B8V7 zYpaS%*F4o;xiz}EJ}q??asYNxiq%VHZzKaYe>#*hZqgx?b&t#KF`*#f_H@}R$Zwd3 zn^=SWMMJrN1{%!kiF26fH7?C1uu5{$@^+89R2J`$2k*cUzN?>X$LlS2o;drqmG!WSO5ZHJ^5kS9) zj4`KN&P9Hvh3oDbds!RfkvB3wV$+7rr!nAF(qgPQ62&{})?w zHsYS~6-FHHj@-rWWt?Xpn5=<3kDo*#U-8IO_+vivC*wc!-;r?t)*+1x-DB9BT`(mr zbs#Ns7U@{Qp`VyAZ0g~-!Ue0U=@9HTy)l7JrSivvHsik)5cjznVeOU@;bq00CJzzB z>w-ga&D>l=Xa0fkXghuf!t=lckjD(7+wWPg8yV?e9gnWDtxV*Z-3gDNPz3E9Lrwgj z*qOg;o>D9MST7JFrtI(Jq?LK;SSA`A-ed+>>UK@T#qg(c-D04^=>}~=`}ln>YX7k9 zs@Flcedz=@T_<{-;gTY+i}0dTW6{phXZEn&L^+MIO;q%s1J$R&8DrpUhXAG>v>! zQ30nX9eHmKW8Cn|+I>vdHRV4KgNYD_`Lc$JSHC7LtsQ;i_{H3#biVE}o}(U05_l@u znfK;)pzpz4dKE|ZV9q9lf>Mh;kUm(Q%+tS3;$D#ZvqyFA-|xYzmWXeTm+C)uGUoE@ z73+NXx!eNaG1OP9bk;aU+SIexP*yB0#P94l#cd0pu!jGveADzoM^;PZrT9bwC-XxqX)Yb7AS=S9^7TH(L8C0|AwALRa>-LcN z^jCkmL0GYW}#l1fG$r3#Y$62Qn>s=n*fON0|#*E*k-+)k%`T;Y$@RK`0zE- zHn#ZLOMfEARLhISjHAxmO3fC8X@`lSa64-)hf7Tj!a*%OvLh${-gP`F@lTWM8w($& z(00E2bUDxq${E{g{!lG>O8*`qiOLBwye6eh(vh^|noBiOjkG04=9*oR3A>Zw!r(2k zJ_ZtZ%W)3H@u<8KFp9lT55WXuRfAkVs^tHfxBqovwn0)t6*SE>YCyU}QjX0`puR&z z5F2>PrvHHK7_i@6v;m$XAU2F9N*|mWV&lE_z{->6f?`+VwfRXUKVi!5)-#Mc-u{fQ z>0R2oG=j+KZy|AOC~SZ~HtLlo`7^HA*8l{H^mV6v`m~#T;yg>z%BCec=eDvIy;cUa zLS0Sx5Ts~EB_)+xYf+N3?(iU7+1^m|0s180-KHmGWl-XqVmG7p%DZFOX*KR5c)3@#@f3Q$hSih2>8Ppk)1c12nrT z|DUozo+_jNbD~Ru;qk_`lw7UTNT7g|cQ|lkSNS(2pI14@0t4kl?oyUbTwrS}{oARtL?qB72 zvJm9fL>hFbUBmw2Fc$;-^>cw&s16cr^>aB9NO<)0R0{~)8R^$aOGm0)h}3zR|8rcf zdIa(Gpp4+J@BV}GH7Z9dk7^Db;qb#QjqqQSLj97@n=a##Jf}=aM7j1u6UTk*hFfm-lIh%@P{c5#+ z5kdk0Fj~K?x2AhSs}J>6)B1D$HYQ%IUqho7t&8SRDR!)1FmIDKVZnvdV84RGm-$T> zlpicKUa--(!l4fuLmUMDFi5$q7k-!cxCfV>C+=SPMyJ5ERM^hk==xgMUn%8Cq*Y4} zne?fz?`bg_>+sE7C!$N}Do=?RBPO$E-4!hXk#J_1oXs!%8gb!+tl7ls&U;GqiK%r> z{?G$>R?q!9h^XhTWB>P79JYH%7rC{7_`p_oDqH2Id&c~PpK~6Sczb3m zXb2PKnAmB|>BWyu6CL|tCCy}a_B}IC(5%?;u-^WXUe1v`O^<_lDI12OLeb`DCQ-oP zFoW~^APd4-fkF{@3zlkj*PB{{yu?-cKp3VRxx9iQghs^TSkc_fL z?n_CAl@}eq%8j=Ukj+^eV#h%&ofA@WUUI_z$gbZzqR0M;>7zwMj>h!Q?P~V9&MEs+ zmNYYPeFb&198uTard(7ENvuh8+I81}&y!0Nh8kmwRji^gRHV#PJ`*tZwUErWbA_D0 zUMH7Y2k}%XP<6jpAw)Ky>+`#ZOTIMsSp}-Xue!gt3wjZC4M^jQ=J^6r>j19RpN()0 zaS8{FF}`5`i^}VlnSSiRP<(n^Z|@o!okZ1+fwC9U$y`djl7W**fM-Dh1C??!a*lpb z#4arsTeI2Tj(W^H+^OgRDMU4y%D0AZenvt5Fa|~Sd-1rp7=E+pvP-|*(K`yX>F*ig zbGv7j)|KPsvHa5QSWuK3ICeCv% znenG(OVXlI77#ouPKfH7wTGN1Z^%c#`!3yGQ-lDH8Ow8We>xLw6_kl^ zbpyO?mP-|Xsg}!I7+Q`vZM;;uOhhxisdoOFvzQo*eK)*ouH8{g!!+0?Exc zC?bBht#{;;J!?JCJ6^@ah0_%=P9$b%UVl`1agdL~N<=2s*xLz0=~hq@Sh-NWVC`$C z5r3b+g=rtZGz0RnLqhy@e-n2S&$C?R8PHTce)gU(D&uIBfLY$maK@c1`8&cy&TQiJ z!!JmsU#TF(9wjL7@gtcQ`qF;ot<53$;{ef^)C{=MvGM|_amdg$aJ;QY+GX^fY^`nU z3IF)G^;|+yyJ!3UE-LgN0vdjG6#ZJlx5zh_rIks~D&PGQthe`mBatkLBY3259h}NQ z@;lmYNs~o%%){q=w(nGq4u<;e_>a5{<0vZI-o`uzsg}DNMZYmf#uUs`KU7gPAmtLb zSjd{SFb{qNZb_Sq($CO4-y>GbR#sgG*p}I5ZAQ&ZjB8KqEHkN=pXk1uV0B(FM11cT z6VK|br*T*|P?vI7mJn(=GBg**?VRf6eFAwOkQ2a|`6*C4FegBKh%d|_)o6C>Melj= zgM+X;#&M=%4ti?amBo4y>s~XGK6MByN-I zpv&G$Zu#!xF2|Bke~PA#?7KJjbt7diflW`jOqnSrQfKbSTmz?a z_R%nbxWCAm(KIja;_?_sI^GLZd=TSh z6-$U=hwmTswi2kNFnMn*2_5W6W`6zL|6zCWi&mBbB+e;vN~JwdFe;nNUq5DMVd>yk zD79MhmzxXapIjm)dv_IIhcPGCeOt#=j%YoSos9;bs*{Jg54Br`vjn3klU2*(Q~p{4 zoXAAZv7#&|awMeL%}>e0m@7yph26G+baplD-P1{b?_44gBhCwY6?W(Oi-`{pDMZ?% zmXED=AOE}mC-clDk`;5JFlU-Ks&9vq3u$(b{f(V65s95uS>`_?N7Y+vd>ej6d9Cam z+NK8AlS+wWr5FbsE@|wunn8oECl7M=4!lNGPLYW9G<0v}$;*;=9nk=qF84J4SbnWG^!Ghw z;idCl#%&KhKDV3KN0TP=T!u;=*&Jh&nQc)4JP9$jnq0b^-ruXePea*CqCNu+V^kMr zZW*eCiPLq~F6ebU``nJA@^5WkV+t~9F2koo8}bHR>DuLDLO5I3jA;tw?)g#NLUbId zl$BX&_*y8-xsj+kX&ABjT+U}gJW|F*rDBArdYL1UG(z@ry2ePvId!A_cV%n(*x)Q*b@#=|-qmn-l|_vGgTxGrxJ^BUA#taSgfS1diP@qu;g5 zy;5h6Zgzyp-Da_~)uSmEK9u{+dmd}@s$A^7@gc!)`s}4iNfMB$Dgh}-ew)pJW1FIE z_7p@+8`Uv&CCbtIp-)n?@=P^40G+;`Q(iicB6ZdN;QGKot@!kfWZp_-x53%3!HjW& zHY9~O$ET0WRtYk|m#;+-i*s7q^iPDfZ!l-xehOc%1D)AW*YuusU4BH!IT5~99QxuU zi}W4qrfg+YF{``-?-tL5x3#`emm zVdo~lmK0ZBh%=%!>QWfnM=cc`Gvjjkh~gC>W4h#f1XGFu7rR3*9Lk7r?FKS9xa%Uy z(nvX&nbbBzrmiQ>2r}jlOyQe7a^$`DZ%X41pYC_O6+}jXT3jc4 z8NVxcwoSHqa7>=FuTOKjQ9hN~T~Xo9b(ShJt3;rxJ@q9#Ry3>t+J=-7vZ^n(kv5c-nvI32>x34vdUgLAz z>JQhqEoZ)6vXw{PG|t;|3W?r>2HJ}y0g%?@e@nmeLt_eV4aG#5Jt%P9g>LWltDTax z8E)?fk3RQz5F}bU;_7_au@iqw!<@D1GQ<}}qem&$yi(GEr?;-jt?reSRm{~d&zqw{qL$2Cmsdqf$0<}YzuesvlJz1!IabiOz|DGCI#QLR@(R=%`U4fR4=MQAGN zRPSxegVuSjTAw+|7OaFdvP{cs-v&n&b}`9`kE?&pK;l1B!p_ZWRHO1ufkwK&2>(fP zzAwUe)#z%UyxOTAZZ1}B z&}EkIpB!DVzt^9@%D15@AANiPFM3Rccto>O?Ix7-(=@`ew;_)UG^)IZw~k8t(F&vG zDIN9~tHz;Ez7^z+PII+PR_4Y{PU}mAb(tO0Y&2O)C%NAxLdHDXh|Ikz>QDfkM!Kfre?Q-Ayz5Q0Yw13%tCKq0X0r5$1FDIrT+(0V0p84G_JgM4E)1Y z=<@xb9=agLn(JRj2lSACWS7@R$IW(1QtV0l8_1b|tqI@nxN=AGy@^liyWABA$vKI{ z^su2>%{F)Z%?MFG}V3CR?};nw?=EMsFevCmiG5oCj=ht}Hj;);UV?*Ir9`KT~r zStOxQ{xberbi_*UN=LDEZ1MMNLE!#2$aHZ`A-vqNh1Fb@h4-(AUT=XiHTu7*FriYN zB0oJ3N&)2C%W*t7j7ye`|Dtg}#nF=S>w@ar6yuv!%dfJNV|gBfx6Dj$IHcsUBo;&K zdK(IhcHR;opK7^=qR*f_O9JV6>)DnHl%rXC1}*fvwRVW_ui`IAN$7~rVTPrQ`s>QT z(u_OqI)m}uoco`8w1h%46Q881dR<#0Ox5jSdj5+1c^fQozs;qz8y;RGek5)-Q0Wpl z7W7H)cvKZ|Q(YHE%+LvGpIy!?J&WbG`GdZ2(A}$rQ5hc~Sh6 zL`xr1O==Y#^2wR5O=r{J^qYR7Fzx%`HROuwMkntRsXiSe{l-Kl-e$jc@_g|C52F(b zo#+xZcQeHGM+@Pd-!u)70gryBhTJ;ofC`f8u_w|ozg8vq$I{5Yd)R9NRI2t^+0NRs zUNjDcwe$7fc`G#A0#pVy{VN@hwp8*FPPR+hc6gvtKdX;;Ldq>INp_+BG$*Vn06o>J zA^D&;rKZQP(e*O8Bvp=fX&V=sB5B*N>Qo|j)pU>W+v%R%vlZF?ur7$;SI_&&MN^*p z*Y}L`%H<$!((E!R-rf`=!t7TqC5%BUU8{3gJUg%0GT0*Au*~YBR1 zoy=P9t+~YOVgsczohAih;%=eJT-@&pzCFh6x?jz#hj?)MY#azz?JFduP3oqA`f9T8 z9d8td7?ed0o}>=;Q|3jg$F;~F!NM)m1WF5&2?BrC_M9~D>raDHnrFQRJR z_Ih}-2`Zv8DypcWJ-;fDIBVOo{>}iI`~B47=hN(aoF_Kpl8ke2WluR>mrNL)m+!gb z!NfG%$s^G-Du-z&f288KWT&$TI5^y|COv1k-B)m?{-M{7ORaKVBT+v%ITSrpjB{}; zy#DV{eLRND@E($kGmo=CLX& zzZx?P)E2CMXMN>P!*3$J3Z{HgY{YS!* zamr>zo@B!DK>qDt@z#%ns82_z^{0Cp+aM%oP*_H;gZGASJ$NNq_C9b!Melk?7{uX! z$WP!n+8!119U1Fy;#=_*`+-mT>^aV+9UK_c5OZU*#UwVG*=K%Da1c|tLhpzQ3*6kR z6u9d2qqUqg3A7#!H$@KtXj|hn=b{LKN;&Y!>KX5oyxTmh*E~=b1j#l7@pD<`^#nbi z|08;f1rp0=B?@4K3L~g)TJ1pn07z8qu#Rd-rWJ*fmS{ zd0o%{g@E*#jox>?3^hwUCtSTuz(_csOI%_2XsB`y)-G0_X}Jd|%19u-H0~Kx1|$%F z@+%M^$EaWGdWVfi@4J>#(f1};}U7vqJIo7a? z75CTyAGz=JRdpBuquWVn8Lfuh<^j(e5>-q8T+d-IkpgYckG_vvKCwMk*0T?h{$PO&@k!tKR z{2f@72}iATAp0#^-9AEY@PQv$2cqM+CV3rv1oTPHg;q1$H@gj`R-sN7`Y7K~7`4qG z`KvZs3aEE%Q^*^be0YP*&(rBXU-^EjB>YJ4ur2;)H*r4*3JyviraC z`}yeg%RKhkPMf2SI|4Y?_8huCXD_x)mjJ#|j^!~LO9|^=vC&w+U%TEZnYzxe)QH?v z&jCl@j-4+3->A6L+Zt7`_X_QKN242=dm^bXdw@xd+n;L@uezDgr-;nB*pBh2Tor#sKFJxcbdEhJdZ)Y%O)1=|OM`4A}v_XYEn$m8GrK=7_| zl*&#c&b{lkub$V9d8f5@xcsS)$a5=#+7Nk+aBEJp!^6z7d_`j{o_)cwsqWTZ?<)|< zIw{?jP;pVVH{Z1E=4fQXx}A-d8+_sXdB~}=HiW4gBuC%o8pg*=sA;G*XMvyF%@og1 zpKG@nRSTIW9~?PA%hmTjDfJ$bAbX#EXUa1iRJTO@Hp+t z!xLsb=5hp_l2N1zCpjo<9Ind-^r`kJ=rqlvnwGV^MC7PR3Ii7!nzMO-MglgY1yu(W zk&BFTZuxUxq@4)e%zw+m|K;K~`le^3H$Wp-wM;c2z})7*(cU;`2EF?l$-Q0{MFTKa z@aL+J5NxZjsB<#?;2rYpB>%$u;fpA@3WxR}E2g8E^jr|T@xl_(4^JUCb8~vOjsj1{ z5!657z#Iq~6MjSh0+Wdp0{uUcDy`qQXB+XCN*d)DY+0{`Emi-JbJnV~?sC@%QTy%Bv16nZUIRMxUEF)L)%(9sGB?UT?lZ z5QTUg!UB65sVR8u!$r0YC#JMqw&KC_6h6kxuTHlPOlruLa7zuzm4V*Y4^4z+`a>uY z^tuj^u;>BmZ2EcduQGmjETVu&vn+m7pi!M5^`cQLR!lGwZG8BZnzmp%szp)Bw=#a` z3mG%Wyl_2jpLgF`hjvUA;t@*7RSbC~eLKmW}%)wj=s<-C5{h7NQLn-x{s0aML=Rj9V!_X3}PM?zLf ze(X(7FSK!9j}SsMEL|rOeeezn?9ZtP;fyqmw+CaFGGZt?BJ|4%%3^%6MVqT(Qeq8P zcRJ!CdDogn-eU?A&lzchI$uW^WIrQkpIyLxUaE~h0t}Li5 zR7mmysZ(0oidSC(*Gx8GjR+BbC26t=@9;!8cuCWJCvewJO!Tz6nr6vpZ9znRW7bZ!Q~>991f8 zFZ5Ce4B7h5YwS4}fs(p|(5h7FKSn)9felrc9vn!1jy(wSb zTeZC6D|f=={*=q?FAC#^c}J#EKN5YD21&)25L6q`<>gaof#+GkwQck2Uj;5uh@_6` zMV-^#z*H(5G`3^YvihZt2Cfwu&sKlG9U}mxISpY`jM^bQuwA{q_5iejyE8)(#(l-j z_7y!JSfYg&!zPbPb{iYl9>6nw^N(aFP-d1Qx~1 zRu{bcqfZFbs&m!va|vYjIdXSd-R2hbN6g^mO%5|kM#sIm3T8h5?+A7^agBsGVBbAFL365)mB8{Ov`mp0Ms-ST+m{GS7;qE6+C7sDO35{ z;oHh@XD#G19prrmfoR^mR@p&ch8>^dt{eCX2u~pRS^MIf2)YHQ}w-BA_agt==F-Hl^X=&^k2 zF8s-MT9Q6p^j`77Sf#j)7K^Lf@wR+|ZmQZ+em|@SF+$Gds3B(t>)zqa+! z@l+pBC@qtOKUPhFh%fX`?Q6!)^+$<$YDIDOlW*)-oX{;vJ8BAfdOKBxE46@vQTZOW z4c1fQ9fp?7q|7~?0&+!LCon_c^0_th_Kk-s=|lOfcu8@G_tU{-<2Nzk1Z>al- zD=efV6e>+(=CB(10Nx^w*%l^Ey%|JI+M{8j4fSYb7#UEj-Qa zF0knNZ>WcJhEA@gPkZyV#dex4qkCYHU)_aLbgKro?|qFs1RZ)2FB$3jy8W4Y339s8 zb+5qfHa)-etD`{nL0k6~&(~L+>^IaG!i}tLH>StmD@K(zccG@TKCgYtnfv0)1!j=+ zUqWQaoxzW$m)%Ph6!7$C6$<9Bn_b<-1l}aCY7S#~?-`H?Rdjgf2jMEY#yPZ|sTazZ8t*x{10ggV_g(&SGE!Ls0^Hp6bgB=cIy1Hp zKkrzMFs`vX=aYXj&}|d6{MyKfV-sGeb%7FI-!`?JI+ji^4d(43g8Fkl zvW{$>@+2oC0Q;yPI*raAd3?8^jac}(v_Esbx7Hkgdsn~&o&E{+Zn!$l#$$qW zkWb*a|5}!L3}?FICzMwc4bYPdy)B@-E8;SMFb(M&(6ZC^fG?x4ypPI~lzV7#NA!qy zFvcCc3<}vS{xSt(^npqcrRUg|x<8965a}dcMEakPA#ZbRTv>ViV||cY7$1C;&DC{~ z!J-J(p;Ci-O}v>T1!uW)Xr1D?Op7pAQdgY)^WcVtJ5RwD7`Xmj84$FV=*AN>E5YQp zEfVhISg%z+j9M%7*+flQ2!b=VLDzADpfopEw~^3Y?g>^Tqz%rPTUiP}E3yy0z-_yO zXR-9uT1)*vHr~|ZfJqv|sfh`yRcp+TZlL`0`1>z<9iNCuuZ87!+~c{MpBR5xQ5h`` zIMCGWFKLWfHw42YJMgjdg8!QW9lP~0p)ItZA?td*rd!lv^+M!+dH|tU`}K~_8M2o( z?iyn&B7OJfz$1dJetzhA3}Su{IV*qE002JUGFigSLa!s$jH1TQNyw$}J7b9!4nXhfm|$yA6Gc=5q1N2Y=zxUc-n!B)4Ng;CN2E1F z#LRfphY7IU4nS`i@+e9dp~CWQ%zA^ZHQ&LO#ip2!X(X68qRlCIL7~!^9Wav%lZo!6 zJqeiUx=~P51U4Ze^wv$s=)jM*soA`pib&sOc?4ALjTWX8>X{hDFz{kswnoO+$4GztvMyxsA|V1ErOV2)#*dqv1vhj~}XX5jN9Q zogv+8r#-HCG9R?Z#8Ytw{8gpX%bM6-+?J!r3o*0yHXCrCl-*l#gp3A|c>Us>m!}tQ z4$__5BB#hw)T@i+hZmgdAXp|gWLijd8oAf_=e6xFAuxN#(%b3<)D07fdPnv@dmQVHJ~Bo>z2pAqGH+lqG8uu(_P z0utMb6TUJ0R4ob3`Hgu|AMK!en#&a3?>+apFsH%$33Xs5`Rk+EdYlU>!Sdc)Vw#9e z_6!(IPA?~;ufa2NuV1|(Da&CQa3O3vj#CXMV;G~<_x7Ajb{>+icW{(R7R8lrtM8EY zpLbC1NVY|om3?KWD6>g6yGTs<|n0LiJLz_b!I6BH+gqmu_T=+L_K4a<;U| zq4lU}rGbRDdSGStPE0a-?6%*dz?q^&<6sry&MF-Lxe7pyzFGCE`Vf5O55> zY5fz-%2Bj`R<)6T{Fn|rD0%BAfNzGg$IX-X=(C2J|S+?vd#>+%!= z!=qdA%Uwr++q?_ld3Mrem(A9`MXC`M$nfn@dNlrcpkZp(RYhhwb&Cxum&gaL{ z=$z2?vD_kUlVHdYTIMaJtR_-kRxf|`8m$Ufx>h3=+cH<%kgk>~I49Hp5z~gCRW>Pa zp%ObuOIiQwVNDuw1{JA+-RP~%)t&oM3~k#Ny2gg&L~s`ukHlC4l+L~}yrNqumD9n? zZW`r#U&X|#nJymFZSr$Ue6kEnyrly!*R;%HP!5b_XW!^ujfV}LQE24*+8a4T!I>FC za8N>->U42NAQ_cfQfVaq<>aZ|cc*vH-JY$h3t-0P(NNEG1*Rq61k`D;_u=fyUgRx# zCf=o)a)|$e==h_V$;k_4YV60_gga7sqs?p#CRzzUYJ|dKJ}R1yJ~{!h60KKIzF;At zzLt`~G4}zJ)cP6BCgCHcf56iD2QgXuH=OC44G2fTNt?340CjL`2ZOk@sEq6ul!t4pIw7)|V>b3}D7{RjrfMMQZb)eF z6hCvNbD8;DXP0nCPRuNW9^@-W(qF{hj!A;%_}c&{T>NR@fA5NlO*Cb~Gd2Ehm#~cN zHa*z@?bl^Vfyt1nU|BmpyCSw^S{>4}Y4?@m4%v?odEjiv#bGGxq&rSw@H@FP_hUlL z?YA;#G|`0F-{XQI1@{=s}K3vT98A6KSFgmk7 zgzG-MN1kb$!mlskthlx{c9Zlg$IyzC{t?_n6>?JPVP5-`!caGzLl2-@7?E}Bj_3G; zTpAYPHgKT_ec7RZi&N8>tGO%ISDEAfWW6-ifqwGtQVWkNyAhW1ci(AZS-T$~?prV+ z2I=b{aw-gi$ZXxT2onSRmAB}YWFV`fQ%X?~8ymu2p3b2dpRq!q>Z^0#+pre)wv}bH zcghT_&f3y>i225h*Y1x-K;PWqaz`y|aTk6%2k^~~RO~5Rm-bK6xStFJ=dju`c%%^0e%;Hqb<*y_hz}D#RlYW2h{r*Z z&IN%n)B9ozZTS;zS|p{%8Yn+vxa=tW+z^& z*kd&^KHfs9leB#D)Y`+<9S&)RKr209{zShd1F7i*dKE9qDY|e~62_%_126P_0MM!* z$?~{!5sv=TWK@ zn&yATZ5Ieeg)Or)MA9b$2-|@;d$*Fxf{d1RK?apOF%B0ygh)QCSLt+jy3U8ki<_j_ zZey>m%Xt#=%At;>rZGT=aEdoPakphc4igfpZYR<3`miglVyVIWWS;tK_n2{aS^55j zd#5?BSExfg`oTzJv|PM~4mTF+0w1TzS>bbh6GK3GaDvnz8T(`wN-j{-onr?%_!a2w7DTAdO&px}>+<5=fs>5fnydKGa z?~he~@GhtD;K4O=_}=q^`kiWo)a zT118;nyUiHt>Gie9VpveCOl+6CMjpW;jvx5dT$S&71^w{sW{4ZE=~QD9NlIt_|+>E=`Z ziXS5Ky=W`tjBef--MOiNM|@7RVj(zbD>aaJ-ZYJF(V;j>nqiak#>FSyR`HyVX{_i| z!3EQw+dhD1apFBr30=sdCP{R5IMvH6nRu6+QZuF53j&V14ox;Io()?`tnHDIVjIT0 zK18pu+Xu|OF70PPFzTqX=Qcb_ifwTH6+?rGH-0mh3@4wt=hHxhdLlj2!&I$x2D*M_ zRrqGa(T3|R(MS=&^T@9~*b8CFUN`2I(Y7Q=y=m4;G-s>`VtqVd2-lKT_1>x!UyLpv z|L8@YTL!jk@g@6YbrxbMJrLav7Z|#PYI4F{sRJy1yF`>ZHLivnv+U{Ln(_8GU_FH4 zk6u4GC+jm>LU~hoIG#H45j`r@&dBu72T5XE;^}t@G!4dU5GTqvANGu6JPO^kul;tZ zhPl~*T%nZp(2QSy>RPXXpjW?|UGPp_v?Jir`XDS}Ym&BShGDxEBKP~t?)ZO>0} z$yTV^R;T}*lUGje<7I*W?+6R~&Yv0V zM5`TTRTgaro|@@8x>TCc_d42mBx$0ym+&Z#`mZ)hDc~zvNhj8k*1z3Z_M*Ri zD1d`{5qL(PBinP23nOST|ksPa@s};B6A~ z9MBBvVUj#}IR1PcVbl}}VTgWX4PaNIX zAH~`?fQ3!y+N7E-9W|QtLwgGY;Wv-w7?>$L)kWTJaJ!{6F3-$?3f`u|1-`YGzq}5i zFekFOaspOZ7)vZ3$7|L9pRVv6K)rbkVc-f2j4G(h58iAFa?5=@i>n3&WVsW91{*;a z{b!JZbIAB{oCSbJUn-TQ%<1Za9<+kjB8}q4s}IM!2y{BRU&`1q2k;#s!nhNj+?#)L z&z(#)2>j215*E}a2L!vorKfhLkb~n8atFjs%BU=;Wiom^`(EKHV{#KhyDl!jEei5` zchAl;4~fYv8~b%np{f<0pe*)SpelHik7}s9n|>_LG>J z0a-+EK3H0k0J3t_#$34_zy5ALD1gmNf{u(-`VW0KN;=}L9XXr)X7IomiqG2o^7;7l zn5ZGnMb9T$OIzhg2f-DO00Ngu^Xq$UJMGM(*|&#ZJ0L* zqjKds9W=9%_p*%eIgr)A%k1yxPG6sZ!%s)(2{{Z{_^%DNwu;8|3q%6j1Bi=D!%xQa z8dH7S>YLP|?nt=9BtoOc^iTVi!0_E8=l!q>U1xdt(U5ptqXDoA0-@SAXlYBFEQicq z#g53nYEnj(fo73XARhjm0mb5iU8IXT13jASVVZkdAeXsn- zb8Sc+m#DbEQwo359)9M(qP6bS!7+q$7a>oI=u}#8lck=`w5Gc|IHon=Lig%(c6V)W zYVftJv1oH3S8b!=YwYhqcmMhj#6{gxRcPdhey6|31-7`yg z)nkrFQ-+)HDy!q@P1A8zvE)|QP|)ic##=70BJS}T#X;KiBJ7c#37PkA%1qTX6BBaB zE24d+NxM7Q&c$}3S$jQjmZ`0A-1UYHZbIKqTz{us@Yz|^?XOtkIJK!9++Q`T`y?y- zTwT){GSX|~uH26)Qpy}u7`s5O)@*V4_3l$M1&(h4T3Otz4;#}VBN_$?vi{y(#N!bQ z?8S>s>Uhkwi*AWv-(ivnoqD|aA|o)8Dw}um%`7WzTNVzJmjlCr)+=oD#erB6<8p~T z-(%;2M7r0XOJBDq)mwUtC=<=MN8`;~0+Ar2A9B>&D})+Kx~ zd(gFk(_;dvOb|b(cgqPp>7Q^kckUFGFYf~C1~^!^H2)1u&)4SP#k2HpP1cL^)Ukv# za^$HJm5xsS0VaeN93fM2*rbaIapuI8dB-;`&<~)30UGOGRlzr4l32!>Lro-e-d-9^ zM>VRHsaz{eAvB}eQhh9>TL2l8to&6my_-|u_;jahuR$G@6Y{Yn^dn%M6o@w08Vsn| z5%EjuWZ|)m85)zmaR3$g+O2aK!)_s$Rm1}@ zCp3vE@HGk*KoF=ndd!pLL?ER{Nvsg@oORcpgQEd|fK6$K^oMRQbNt0%6#Ix1i?-G` z}rhPZA^s|E3)Us zryc!mW_($lF{O@uUO*TvTVnBj2$$T8 z-Vq}GdLuNnQP@>bhWlq_UDl?1m{f78NV(tpwH=<&%&H99u0AgWUCXOPl^&rRc}RvP zW2+e1cVYaX7oSSnw#J8mK2_d2j)F*MZ#0SSRLNi-q93UUJ96$YmKUQT&8j`(DK5OG zUp)@uOop3pA+4L*lFT=4VAXMnxo*<};wm93v~Z|!ya2=*2o$fzQI~7A4>|Yz4kZu9 zzJs(DlYN>73mChNTGuWp7|4s@3bGc=B5K&q>72Q0(HRa*l;W8x3%6&+dq3`eZ!0&w zJ`zU}9WTX?>)>%rU`C&h~Z4_eaLn8u7%M&(IuRscJ7 zZ}^Xlqv~s?QHELp*0J|PV~KKLj^^6a8lz7^nJB9P6k~L!44eO>^;k*ww+Jg@EVWA1 z3$Y6n96=`XKUUy@X^nJ`vTPG?J6qqXM>HU>Y#ap_Mn-O>vUWHZo$k1vukCTAL^{LO zOysH0c4AcDymxGZc}&E2!Hnm7}M1u1F*_)Cv2ZGicWx9g5tN zp^N+gBC~nQ7R2Yn6!aB^3U7K&)|Cu>II)d3@whO90nTl^j2x&Y_0sK=$$5la%^eL* zX3?$%aPfwbN0>?h6rw3WRBZTBzGbLF0S)%$_TrXfqA0!l3x`0$m9B5GCK3(iZwmyZ z)<}DZIfHn4$<_$n@p9T+QaZ~lTY+CVrBptlTb##4_aHq(qrTEYtZwfc=tev}XgfrZ zGU~g{bYa|U(K?eW$cwTcn6!)mW+g9wEUY;F2&G%IR_qY$t-64@AE75DUf(xv&WNL? zs`W7|`6_PUR02@YrI{|kOfA7SKvhM@aI-8)PzWyA?{QZsSa6wmFLnD7MkiH5FZ)77 z&P<*lKTJg2n4KosM_eT{M{ijw`POCQCW>~G*^)BprrdOM_!eWXcO4{j4=b-i3;X|h zXq+HMQZaGz;tTiNyQ z3PQOJZ8rT*cO8G({Qe?ec+;S=e(=23TP-VdvN5d8mrgi|>|mK$?i07VS6&1;U1ugn zd3en2C&pt}V$St%Y22|!A{nX!$J6&hfIdF1?HI8f_F^I6t7v$4R48z#8pSK?wy z#D_8cUyu(2g&RkDISkjF)BCq0fg*zxS9d!n_&M&C@t!2|$$&d?oIZI!!r@xTz8S}J zZA#?6l3MCooX_AH6+Cjus5|jEhYwGN*T2~v?%OJo&YU2U4QKnA7_=*$lbAo9(l2`C z+HXW|?m)p-Y1R@9O;|iej+ss;f(;we!ALeU#owJ0lw|Q83ldT@K1uh+g!R-Nn?=OA zS%VRr60@TD-HOeixS#i17)PChArK^wV2fFt|`RgY>6Sx8rht8p(L#9#3yCK_+?N{in#L zxj1JKM}J7mm<|7QK`TSMBjMS5ovrN*JvmbCN|Wt3cX$QwPW6CkY3~0d> zjMdmfm}DcKP~t zaI2M@c*pCtMZFyLM^h+vAV!PEcmCM$yXJ7MwleRvEO)G;sG}WV3QR2(aHz5;G zz}c-$VO!;*D(uZXBT?g`GQBB13!?-U3_7W%v3&)+HY96q);eY1Y0VonKbYr1Zwbf@ zcph~E1)kpnZmGLoKeq1>MkSD*5sgZddV92)vrC%tu5Xtv7e2izOi91%gtSZhpVp?} zZ!!HaDOTkqux_P>$}mzjU7P{Ehc^DzlNO#Uu>GiP?m}FH2i|!DGZR+yPZ2D@ymy^s;A;t`-C%%36+Jg8(t=Z)*((m=Nq;p*x#NY*pA058V2>}0N!NuFnZNC& zvf~tP$Ya=u>&4uqzVOzTdhL@w8~F<_FVf0nA)TIICPCZ7R~XqpAaP|=M(P}%Bgt8m zyv}TJp_8?J_QI}f#AulNJ`^aT4hX=tX;D8%pVGIhIuy~sTw zhGdXZS1YOmKgknnk^p?-bq&MTkKB6$+qpP)Q8P;{Hdxmt<`rDuBp3{(SbKJ+Si8y= z;?zvMP^DDppOB0^>|`- zLQoh+(jZ0`?34<7bFX$dX0rnh6A;+4Amg4@y#Ffh99TEA69f4+Ek&1QF4VK6st;tE zwAvd@hI?2Z&&Bb*ze~++BAtc&TxtDd??QnO6NE{PhT?ioV540q$-5j+v3ATn$ex3( z?_B0qdtSS!lW04#Jnjcj*^I$ao7;@e9?cQerxwc~C57RZ{UpjFrR}Jhjah1&6S~E? zV7a&wo#L|rN#F++%(M30Y+iCLw1Dyr@1I(KM0|zBxc0GClSGggxt)e#RO3YWa{>r% zSpeq9FCIfIq3;0^PQydm@)qY;xIh@dqP-z9?x=j#D8eX|A;5&{CvEdKF9o=D-`R!QN?AoTSwD5wksZ`t_LsvC&^Q^00NSaiMU0B zq=hUNLh}iLl#WGb;Z~tk-m2Os&fE;B{%yq+|3etn zPYp)aL!gySFL%y6tPHnkpq-%K(#(^uU@LQ+#?~9$!5|hr#mO^3!w;LZ*&|%bOLXd4ke5EF+e?D*I4K5>DdDXldyw&7{@}(v{v_Q2TK? zz?()c`*96Cc6B+|r|O_>+A!^Aky`rMKVka#H!y`YpO8EUA`6FQRq1sM7fupYVHmPC z<1asnKpboeuDkFS>t9}z66m&&m$GAg+_Ig-{8QJKU*g3RjXQm`=U+y->|3hmdP=)` zCD7(@#_R>yB5d2?6owttsUzcHZKJ=ZaOmjDbX_kNm4>8C-^f zNY2E1uyCFwBWoBW^ONJRX>ZRAuz_A?z^i&`BJ-IpAh=!^Z^FnubT6+ zH~i;~_TQ^bb`eGyIDSWHOmrD^&E|7T+SN%%!@3FK%>G=E*0FHuuwFb(Uh)SMP-3@U zd|;SSAG&vmyeD{v5YNRz)3SxsGrE^EIVvm0wLnD0=vr!r5H-vbi{4KV=Id|F_n5@hGa88zdqxzi)X4DQ4n% zJw-D&+0X@c^5x0}W}6C-09?F)(t9?Wej1c-(0<;?7u6$9OFuKiiVr?dNC~wKKMBb> z6B)Y%I4L@C+@Rn6oETte&&*(gw8uJkhLLX<`*VCM4!Wb0 z2djp{%4BavFsCpxNl8H!Q7Vc)#Wg1SjtG0uyh2rzL-Emz`Ji}(rfPeh@ZhUs%RN{V z-}!EITx+^f27+$ZmZl80wH&WA@wjum*{0mAj)R=8QqQ*zf+D$B@(jPZd#^ zmpM218zWS`%nC+<-18s)dN1;qR(On{Ukdpc58Xm3aYK4*Q5*-JXIi&LPA&~T6BGw{ z&QZ_1${1lx3}Fl69N6+jlH5r$QV;i4O1}neV(j-onlbjbreNaH4VSb14o;19bFLb+ za{INH2FR=;rc1(eM3%YO#!ChWOHaF$eIUc~8-GRIiZ^r@D{J9Bi6&c3U>0hI_cj820)HP1=9&Mvd03OuL#rKp4r>lV_1 z`AN$d4j$jQ(0{pmvmnj z!Orsf5p;16W$Tsk?`JlgnP$P(gvzHce(-DYMdK7m^2b!sF*$(38Oo%wW~7+c#3(gC zK7B`BfCi?>kN$M~!Zm#5@Hb*XHWD6!(?KDfl* zuL(@21v8IzorrNE*A*mQj#P8YiLvfb z7U7x%fA=;56gu8ISYW6#m%DL)bdm!qb1GWQvfau>>Qk?vuQdFtDp8nY+qKK!H3~tsyry(Ee0SWY~S69d8%vAkQL-+gDA5z@2NX zW5y7b@)7FbH_^r%jbjM}a5O|#>!wzw41QF=Ty12$HTk=jIK1-|B08~kYY_>jsc}+X zt(#|r^SycU*caje&b9${{pw7o%QTMAm5xZK2g~#tfy4Biws1d&$2d`e zQWk!QDZ-vitka3l;6vI~L@v6RsggE95*_`Amo4MzfGVq`G}8Ac0;PZj(s48^fR7HJ z@Z<^!01@f_=Sz!uR*ut7Ah^ohSlN$j$e^Wlw*GSZNr^;nT|eyPvBNM?@`H@kl8R6x z=SjGOdFZ2&i6ZhuAbwJvMY|^4wlx>0k`pFKK%TVR;P6}|=)z-?vNgVt@z}2A%O^uW z!~)1T(2!1+9jYJq#{|okhbDz_!>>Su`wlsJ#TbQ); zR_;|(HPOz7opaFp0Lf29%ZzoL8EZk`9dB*-&{aQ31OM1AAfao^$o^YO;=w3tXHKP= ze`Q-fPk-C@Up@=m6VdG;!0m4{NR)*K23Rl31GIM+%51>R_oQXZ3j z+PntTUCN|@*VEW=PaF|WYP@Z9I|;9ROGhM0#+H<)I{QX^#byk6m#=^>7H6>k@!xtM}{g4v%2O!+LQ$WtjZ}3B8%q zgIS^%;97-WM_JK`zaih`)#$3h*JN{P!;Aa?G7XS<7ePKplKK?o1tA&yq6=rFQ`s>U zL3f$qtnsE^Z6Ac-KX6f~<5}`VCkqO%K?1fCW%jfN`&=9^GJnEYdmmzLXor6x{Sjv2 zmT;WGG5b5?)1lYU>Kb7IY`5@A)3II(P*NjFGmE4mx#^ye!r6(5`a&XT?j4f@9&Zx> zVY8l*7bekEh9CZv1(FvzAH->_Ku@#SJmPJzIzDY**mkD;p25;rW2jHM#>ZCa|J``K zc&oQNIvPc^F_fZtjF{H?)3dQ3V?yKh;0B*-32d#f>SeP{Ese#_!CE57hgO#CZe{fQ zTQ9~01L=G$k{N+yXJSfl(?YJ3kuE?S*xuf(&&gCJjWAfhdDz|$%19V#DR4{P1b}y)~xH{s#r=3WM?r3%gsv-B= zyUpIDp+y@G0V0aZ^di~O$>dQ??=QHk%E*frylt?k=mw{g945f&66t4C&<&k#^ynHaw|77}bAH(@BEAy3SR>(my08%D{G-xwbreMzJZ zZGs~lkM7GL&-;k0VLyLEyaU7BzO`VzC~w zmT>-B;wkX`<_qj2>h&zO2oH)VwX+!<4xEWIu?L#+|HFH8{TJtE`te`%n;ArQ|CjgX zVE-@g{a+xQ4FrV$5Bd$plzM#+O%3Gy#l-RdgTLuy>^9y$BJLn)26(@PfIa-+TRRr` zMF$3bZ}9R3Ry|PB$s(zKdw%KVe;Mhli7E>nDB>{MjrRFb9QBpf{{E`}^)mGJvB%c_ ze)913HScBp9{IoY_s0;VCdffuPzY**D0hOH? zPR?TadncMw-gk&jK5cj9mw*Pv0v{K#z|W<7+9{1>;3Ej~ej|QCc;fS&$a?)>_Z44S_&nP<`8PP)+y_r%8FlBxr5dV9ZdkBHC_pPiJr0+ zrD_>;8EBeXHsi`I`xpcOj1_E3T^qJIzb){fycUSEDAsozo`ZzYfbuS7hW6$-fOY3B zP5vn%{bPIgD<1U{>|4UOh1FraYLgsk&&Jg!n;mVo&Qm)d`9^hiMfy(h(snZ_Hu`z* z#*NYGEl$YOYTz5vRSIxZfZ%Is%KfvN`2pLomOKcVKj zzuM)Ro)h=0P}uFXWfW7l&jFp{VN2=N35P@xXjuniwDi~SQ5HabWl#UFqQNo1ast*L zu1aOhB)v%Rs)NY1E5a|ET)~CTS!vYALOarWW&ji!qg07!sDT{!@ zbG^T-Gs@sbH4F|wxTVv*1y3malEx-~@T2?L$C4e67%1te0*}0}NdNS=;D^(562Nw~ zwoIibJ+0k8eI%fM3AtbATJX=tyNG!AkW?g z%Bk_frlBOB3&~73l+KdAzuZ)kLh}6?KX=Y^!YlX|q3#UIu&y!5*&XRmS!XfoChIk# z$-S(2f;|wS(Xfmm|ApYuxzix_prC7JR9N$^T1hAR$~e$B!&23UxZNLoLw|d|NjS^z zHx%5rIkJ7Y;lJ21#UK>U+Eg+TOgYFCzX3{_%O(!R6l%EluFLCeA+uMESL2J ziQD$JN^G_{&{-W^1C*BQ1^lGAXLJ1m`7?kGG7-oK@V`rY3SxX!Z~yN$K-$UC+fY9NBB^7 zIAf926KObu)jVwn4oO@6hgEbf_883(;VXP#$^so*=;PA&zi_EKDr`DbtFo2WW!jn0 zcy+p<^?cR0G+Jc_mCBdSkq4t3;#K!P6B1@>P^v} z>dZD~w3wpBmNMyXlJefIHyYoHeoXwDPCfLYtd@f#X6jdinHZ0^i0M(HFoiHjnA{W6 zK?lZbfhqEh6zFQ8;8E`sqsYZ_Wo+^e(!A1A*!6BAa>YBvlqE-!PeramL^l!q8_9v4M4@H3E5!kp%QPBuyE|{tKs)4X@GIqZr>^V~um}djRk}^e(VrB;CGmL!>z`Q*B z{;DqXWMgc8V>njF?itaLEWz&RpSKF*JNI^{rY^N_@KsvbK1Phk-i(p9u0-NInTbiZ z*YHRt*vy5`L|{rbkG}lw!a)_!2-JUx2Q<1U=IM5Uyo;uRXo|Kxd7_o*Nq#GOd2?0X zJG}y88ze_0?B1kRtBXe#e4^4ttqifRHx0B~Jr3WTgSTKmt!lK-B3f#cnHQ2IkQDF} z;D`b09gBE1nc`z?ycd~(%IY%vbejF2Y4*Yh2x74oiBl&vzC!;7kMOXL}MF z-8xW|+UJW-jrN@f^1F#HH}+H6nIZ>A(%f5U8`ol8#J(a2$j>>eS))CgGA#bBk%PF7 zjNL&5lh$NzUu4k+q)h)4z{b{}uc4?;eFMi@DP8E|ov$VhC68v;t88uhHV!{FozPreX-|pRBG25Nj-t z#XmuJ-TS3T2J@)?s~6IrW%*&ll_$V?kb%!qTJwBoSI*r*#PA9@{{s^)XaJsE5VILL zPLO2`YQSVSRuyRS{B-d~jk>p0i?l!?TzuCZQllx-WE@6Vptk{Yu`Tfc8Yc{Y9KS8? z0bc)2!1L*Ul4fwl=d>7g*_+;Kbu^)68iR5Ne6J`KJqjy_CFj>Znc-p|Bqd2(g}*t) zb;5ki-c>6%1R7L!8bISyn?txpqwcNV0cNur%d6~F|3dlGm! zF8Y0p%wMylDA23xMK4*6mi4{s^6liyuWm}()JNb$V6IC9{2%?KanP3L0j{0pq$7&W zFLD{XHAz3)+ z?YheJ7>E8VI3lGh24r}obYLiG&UqEQTOD||a@I5}cuo^Syf=cpj)7dH)>?04{3b$f zg1QU1S&t>t!}!H3A|1znxYgirD&AM+!xqFYw3^xCgCFg+FRIXFzB=mnGpH#x$M=G#s+vuLK- zry6{|gbEnnPCiX3H;Nn4u%1VFUn7|gEB|Y0Nk@z2&pAgB^1n9Ucgavxf4&nlW!_Jb zFL@%(W)3oZe<)qNwI`1{irQS@;D+dADlI_*cI~Vy@?(F?BDYB|q}pM|{kQ3Wpslvt z&dK#*1lzIEM@Wgp^M_yN!a~78!WqKaAS7wuOp<& zkA|fDIj#AVR-AM0MUfxzTc44gBPp6@m&Wx^f*LR%VLc}tl0~-E0>j2Bgj`0M1pjWC@8^HAZ4D)Jp2KsB!k6Xrh|2tz>)W3Bc+WyF?-MHXN zF-p~vPf!91F`iK#+Ny!YV%rI{G9U%kQW%=Ix!m+1Pk-0-M}dDCJ-6+lbF*sd%1q}V z!}5!qvOypr{zOZyihInj5`dsjW9;?de%vm z_MvQWh7NvkI>?}u6&~dBq++U-V!hF&<%Az&477471w8!Oq+2$$tZEAhmO4}|1h=ec zQnJur22rVbFJr;5z&Gn?J&j2Du%2#6FW%rbh-N$n2_m*&)nF}flgOfe1r~uSN;R2( z*-$Gu;BCC8-L%7xA1vn7*=fWas!1=7f~|_{@A5~jxg9@SMI1r+F$!*rqSz^73}K~o zFfrT~_00_1KLp=>?FwJzB!>l9Tx_dVz-${^MXHFNEQ9} z79rn`c&#C~OG0F=)nO2jlziav>scHJ`}@ExuY1KoKX(%6zB%Yys>$MAYE}bjYm72m z*J`rOo};gUxc}aCwn!*zR6?KEZ*{-kqY;h8@g?TF$vZo#ASL<(~LcZA|oBmE0C`agwIc zx3%G6Mh$a81=%uA(vD|c0TOwm^A}x|n?iCw6}@dBK}B-{w=I5vop;rh1H1KobTm|u zf!M@MN;e+ygNAl`ex@7p2xQ3eEB`Au#BgtXLRYLE{U0RTN@d~7X}parGXF?(sThQ} zfP7uH&Wa{0!j4h!0L)BR{P3QNwuX!vp%J0bqI-f{NV;ciDjus69-N=&5WdYRe*`8b zX2L#!K4u~xL*pT@F5gFOT5@$a82%4)Z`~Eg7k+y}2oQq1lMvip8UiG^Lx9HJUAl2A zf)kwJ!7ahvEfCz@-Q8WMzjN>U-I;j>^Vg}>eO9mPs;;i5&a?MtA5r%)Vw+!wxqCRP zkF#QvT<2&+aMlnu9h$|S@PftB54Y^BibI2$IeCM)AV5i?#e)JuwlR}zO;7jEr&5S;6o ziZW|;DBD}RBr;?5IzD~blvt5yYzgm{5oiuuR~pcR*6^wI)VKStnIS00 zKpM@Uliu1^X6hRA=b#B?BkL%C$UF;5fvL{Ln$H)l?q7uH&G|Gy!Qk)0eI6YOhOXkT zoGgXf(I?6_Xjgwj24IW9Sf*S91SfN(43!Z((4df@unC>ax__wlqeNq3+9C^IMole3 zK``0_)VveR7di6H5#dlfdSvnaU@8~sLQz8Db}Q4bC0n;KbC|z)@mHMp5rNUFYAzSZ zGn}79eJ z0S@&m!qt#k&cp6#|GR1D_$hTH(U$I7wC7!i%v5?ysefF%Gj5w3) zr^uvzbktUQ`r8NO1&3tDbx;+djJC`h@ljp+R%syc4!bpPuut&Y!+j0Y5OXMXq3~xI z?yF-cb;wSAbx$8>-Yidj|KU=8dg1;gPD76JCg7A+qG^t7*D^psbww{Pd>H!$^->-JI=Df(QU z0t7%)b;7N$tqMOl6j}z#NlHF9B#&&(e>?0>rlZBxRLi~j^)6Ah!Nf$^u<#);t$0EU zz*P7%`>?OGME^ngF55HXHQ6&`wJTiIeO0;4iiAmLq)>`(N&>=9szs)(0hqWOIWjB4Q0KLM z#k=x+?dc8ygE}>LJ^rWC%6jVxvqr{^-DN@FcOypG+T5+ScBF?}e+_GcSKpbSwdpVa zXivlWy^*eb^P}hl9CvCFQzTZpeQ-;lnss>{Gj+u9tj~93K>~J3 z@=|hIOTWQhiBY-tQ0{Ea>>o@vCHbs+{miK@5w=QanlN$A=}Jr({rTLNxLK5#FP>>I z7$+nunL}S~!$Wp34`zz+4Te5l=ukMM(mS~>s;XUo7ru-5=|C8ph6P@Aj9K@I4>q22 z>b2?k81sg|8zelK%i+C{xIt#n0;E$yC)nw>hLF@x*V?WfS&BSMYNw+_znO zBS3!n$YCQz8@W!XW7phcXI1G#n%8656n~cOXg}rXejKmM7m$dO@wJxj({tCytILnR zd6+Zs+bWn=rO{SrRYue|<+hiecdgmNSMU3eNDJuKFJ)&?XC+k9GuqD?ft8*x23h{- zKw_$fUmIG&6KxfPDhoPg%bR>v=4O%1GyrmZcbki_tMqFg!oYSyMSynWQ1CAt0=5T_ zF@L+@E6?IzDLIpgS&B9qyxn|il`bH(*^2YRoTA|~LXqhHC2JN9aLs)K`l3Pkc9YD& zrYnU|C&f*LnnpauIiczk&{o8R;n^tu_j+Msj9Gy4?$ecyN(T0>^qKeoVh(QHTEF3H zkE7IkU}k$O1_$p&nU}=tH#6naAX0H+Xf9JY=jt*aLJO_eedkr!xj(lN>NdHoUsH0( zB%8sFhm&mKG0M}C3vX>)= zTq{zPOTy&z4Fy^y5TD@|%`fFo_k2vYtb7CCUbRz}l)wFqsgPR+j^cg7A_4=X-9yq6(e)XszQtfe~;W`tbzJZC5qAf!+qmh|BT( z0-7x5LQ)-yx*@%HuKSUfK|a^)iJ_Z0U^<3XF40b^zpris_^=l(5XF6fd;xuEtGp|< zTJSneg9SpUSMkNClYWFXA^NXD6|XVN6MTxQnRS=V^M7itKj%)erbn)u=O{hMqtGj< zQ<(ZhMd6g>8+jWBy0a*nWS!%uW64?Z*%lRgbTTrIQJ{U}mJ%$FVkt26pCNOqxr$HG z+d+D3gWpyK45g`d1u@433IE}GlBBH~8LoNStFdZfuW5ZmE_zY7pK24X*mmgs*oQrG zIzu_rZsZ6-EA(m47EY+`AZWqLR60hSW3Cp}Ags+jR!K{_6!a=08Ne^$sAbVONI)2% zx##dHAI_^*zH27DW4#(N2i7I zOI8565qsQ5@DE89GUX2GAEb;VGEQO=R16sO;X+UhS3-pAJITWmx2A*$-(Yw25e?!C z5+AX9c6NmT%9Lz)8p9&CIWrQr--7#h0cP}K81%n$vqC60z2v>>HcZ-)BU=xTC1t8< zzCEP?IB}OUT&{(+x`ha~7Z#2+WFZ4~Q3kFG(b3uIPBNiA+3%^yO( zhNN=^TMqCf!ZEytDZu8_$f`JlJ600XD_npsv-;1Q-NCtz4m*t4!1-5WzeCmNyW*YV zU0tnl(8X8(x}NvujXVz2=AL$eZj>6Pm=6aq{d2lBT@wBB7Z&_k(#XDq_f-dU)$Se72B(L<- zQEic?5}9OvL4(M!Ob_sTSxLf4rny)|8gOSP_RW|#nOPM?8pTq$$66))u8pPV^gnW< zkznP)-J-}+mvlVJcZXjWP?a??GuJf=+W z$iQM=w*$7xcZoWGlew#0CW}NnRHG#(%Z^Z8(kQ0IpL~J@r9Ta&Lu!=-_BIIyMEn2h z_FuFfTlSz0rRG!KUY3$ixKzc@@e)_Wgiomi3yP+4p9PMelV2;ENe@$ zBf0_+E*S4gqj)?g5OzfStADd`#UH+?qviAIo2mJpa47eZ}h7_d1ymbsi&IYW| zmd_kqtpHJUoTsCq1L}$Sb6F~;t10Ejmdz$H*$N$UZ=%**B~!qfP4P2Xs{ROrsco)| zkd+@HSLp4ZT&egqDoeX+h3iQB6aAe{+l+|c6v|~?n4KylZRyHwSTPLu$2io{bJZ;T zOT7t_Xx*l%E+AT11@iidD^yTH%0;xS8?9P1{EAF4OLr()tAu?(Y@vE|-S9xv-G(MM zEVWV+$1hC2c?igNC=FSNU`i3(3cr2t1=7d(OI0pQxi-SHzqtNDR^@jj`vfy1*Coru z&>(run)xA`N@TQTo)@KDf|BG1Ja;TpJnmS-ym!C_^c{Q4W|F(~T$n~+SkIZ9wt&8p zU0lp3)x0lmk!;znpNm4qoC5|t_X2AO$vwT|}7O={QenZ=3}rK*P?AU*TfDE=VET;3ozT+x@XN>EX8vJ+E;S_mWIB z`lp9+PjyVgN5ol{=g8uyh;JTAjoVCFH7W6<9XkzSv@%}l^SRl0P4DMTndcg|knNY+ zlaAT!KtSY~(aI;mw#%1QzlMt*+y$OjEf^hkq~lBA2h14sChmE zHqWNV7)(p}1vDgQtJRb_&kf2CX`ASQn(~lddpgPWR06_p)Y7U!)I1F%WGC;!>Z$33 zfxM!Ku?7fyfP?AriXb6-R?-l)Qd}fXNp~-rWT{41kXQk;9+w8@(Xf@6_TP&Wm9!Jr zr&h0DA|N>tsKesV0ke_!&Mi~BE6T#5pY=-;laemHSPlo`AM`YdzjP4a$uBz!14srstSIN-`jFk~?&Z($1 zK-07^d2MvSp-`Lf51JRyAUomiL#8^X!|C2Ao76*ezP-1)y0;_Q(VeRL-Zax{lq2Y< z_Y><3m%aqr-65l|*!-=c6PCtLNGHMoDD>#C!CX~$&)Q}#I4S@~JCsncn_HR`2udn8 zGE(8V^t&d1m|`fH0l_fVD*IKbHTiDt&+064+?ALqY>=l1zTnk=m(I0#R44Tl*fcEd@v^ ze_$BTdxCqh3m=Ka(6M(MGodg1$Ox9JI7p)-5O+wChdDSmgXM|zHwSD zE~$01n&C}j!@|L;@@t&0<<4&g+_#JGw)(46Ue9NRGeWB0K=YJvPW=v6(a}HA)A~;j z(lje|bYIRqvp@b>-COy3p}`U+(tY{yxG51abHjuAYsP_jlRE`c zmiOoK3Vt)I#*Ix6GLCkj+_pk$(SI^KZ#<(dg*AhZs5xc9xEk{{Vrsg4sYI$}?{g(# zQmmV+lrV;90@2~9i`u-3XJ)+b*}qLtY?R3Yl}-d#-T;vb9g*QGKd13@Nmd%uBxn_K zOukU&oN>^VKW@`VtU|_-89t+PGakp4*OfQ3duJ3p#af(1!bgR=QCLX7Qm&NDz#+lUeTV*t#I^8&n%hv(4d()G?m$?xjF7DgImR(pd07kKKh-P#*?{=oF9@)0`-u6FsKBol7S}GFp@ltsjhKZ|6@jEpWV)`K z%u~n{!axtw?UTKQhv8|@)6J=CBCI9Fu7ozZNyMXX|Md3X)#=Cu(FfH8B4cSoNU-QoD=FG zv=lp#ETqVDgJIpeCJ|ND=@57yC8xu!#NT3gsC{+JZJjzUNp zfBp};?Z`4yVDD=d*qZ0RQ4~q;G{U24yhA<^#)R)|;S<${05TapCHi-%tq_c7Fsg8? zUA7IU!wpO=i0w1UWK2*C3sA~jm_*o zY?jB5RugrH#yhEmC!)B-3^NCX&{s;A-bvB}@PPRVog|67@Kx|tFqT}pE84P4bZDF; zr9nO)d@dN40@0hDdXxi>#Uq9)g>crEr3GV4DsHDMj@~CrV${C*U1ro;$TRf2&<0_N zM-;Vy>!!Lq1-)0BRU)Bmlc##!Lu#h+2TZa6ITs_zzJ}0Jm{Cs3>R2anom&#RE*NkF z_vX$E5uCK4U}%&`X|UZR0z9CSL3s~MG+65-ISqeZ_?hK+AcY_1#2%Z&uQum3 z<)4N0+aDFhtHW?yZI^t(_`AVa(Pwev!DA8IffohVz2G!k4_B2=2?C^HoVwj`*EB!b z>a{E-*#?R$EIs9U38T|tIjba;StcNl@ZV{=&_@^?geDK!XLr%0LcPQ4_nX8O>|@m5 zKg5>s6i^L*I^rUOlZn!4~=(6g8bUIUOh* zdJ#&s!MUZxz51f$1(s2FZPKe|F&v+Js|bGjRtZb3I>r&72h~D5v@d+yyHkL76&9a} zRIiI(BrSTz)%0LMso9IF0UMGQ?;nJVR71Ko-1kQ3v`{ISEUBy9prqgT>3n&SCM6QrT&PVSIWrFen^W^}VaJs1w zcIFQ!*L({VG6cuf>!ZP|?f|ShSlxSohmA0|3U%U&NVQL#Kg14&aIx^VMYM(Xcfe>J zOu0vyb9>egl}aLxz)f|O<<`$^0d&zKlTP8fCsNbpqTT(`k(s6>m9X_Up1H~rKItd0 zmrRKSyH|c-=QcUP26sR1lE&vGq3-jl{|*NOqKzp=stZS@?oz;=9)7Ss%&PnIeRh?_ zAj&pvWB?uG(>s?*T`Z6AKbe9(;cf20uO;^@Jm0eZtfukckLd@07L~VbIf7Q8d6+6m zxYR%2p)K|5CqH1r%AQc}ILVzgfA)-wFF=2YIP}aq z%xHDyCd!gc|K{ijnM`?uaWGBKhH3Y50?6>`fxO{;E7en$N;R^c##Wb=maQHGf(7`N zmZtTNYz!}iG>XkUTAb4o{|>bIj>;v!DJ&-I+;*h(0)gp|o_E!){KQxLo^*N*+PZ%B zWNfpoDinQrkPS8yM{%S?^^B_%SkjChNlGdyd?@VVs{L#cs*%JiWVjcjk>B<6Yg9~; zN(+kEmqSWE;eZ(|It(j@UH?{R`MWX_VuYtSwx>xkFOb1K{!j?Px5au$kH6Z}`nB#5r-+ma8p9m3s`ZXH|( zRw>N2hJ^vJKaCpC^iupkjtO>2v%bj zX#4!gzPU)-O&ULlIxYnCzb!dm!d{JrmvdGaVE`g32!ElpXp4HXC`!r)Yxt}+e4|Pt zgK1%9paz?6bMe_CDcymYTJCx8qKdABp8vkn47b0}C6#6#@~Z zjMOcRCxk;>jW*u?hO+D$LiYOo+DRtDWe!ob)uB-=ZxzTqtL6sTvKS3+=8~*n*%J3v z$gA$9*k#>M%+hLND6^r-0kM}et9#4J0ET3l43K-;u3_kD78Q%0{b$b2PAEs{w;X3^ z^|aOFUycUHjX+6f(IH}jRU4^#^_~gaWHQQCx!>6~58lqAa#h3SZgso0n!hn+GI^w$ z6~!Er4IgLD0?XErr*i)H3!d_0Zlb9 zO~T&zo2qO4lXuYwJ$Kt|LZ|OKYB;@Mp+3k_gEqE`+&{X+$3qI? zFdjoG+k7XX7!*@%r>8&7MMK?atNSuRg$uzLVZ`O6d+HfZq)|QsWgImlgm0KG%Ev*{M$Z{a4>+hOh+qfg`82|6hIG__?0i3eQz z8fZ-(*@P= zb7R-l=@UkF4Z&uYJY83Mj(Iqgz>15N460$aI-+t)yv46kLK>E~;cjR~<5N*5dS=z< zCDg}AyYvB!bon*fgPu{`z{Gnla+v0?crDO!I=EiFWixda!zIrs`=nru zQ~t>X44zfPT};JU%MX^XkCfSuxH>OG8hX*fBk5b-qK(uRA2U8~08RdobXzB%Ik>hA zWnuhOn)>4(YUOmUh99mwM*hC{%G?@KD|ay?`&^F5tCyzVh&L6Ywuk=c(IqH~CfZb6 zQ0b-+2g<6>=MnS#rm1LE42aGX!qv(6S1xP2N6822Z>xCn>5rgpKiOhg2Cw$D1a{j# zIra(}6mpv4ly)=Y0k-@_IYp&vv4)oREJ0Us`!>05!$BW<*L7t^u<<_J$Y$h9t2z*NdeRBs6Kfue6xVK^bfdzRKCN=9(sPa{j)Ts(~*1cbqJwx{M^=QmT&EIM6#- zkQD>TcvI;*vaT-q*&caOKs?^C?EO>cVbDG{+lXbcq1fpP zm*&h)D%BVyh=FvX!8x~BOIy9?+RxNPUSdzqugAjioHfnBqgWVtwLye$`%M%?S!R-S zk_E3+fy4`@2R31{jDUTx779Lu8=+V^XZS2>xw67Vy)iC`f`L-ciLw>#X2iu5eOVeL z@flv~4K0QRL!hAOrNUgBcIq96P=HT{T$B~&EC>3HBSz)#L;CR|tn&VWYyAjrr!uNM zlwYI46M0{KWvZQ?BMI1e^Y7nli1&$KhBZ3Ffc^^F-<2s#$rP!9cCdb&9ye`_ z4`NQ{xpAK}pX=T``VRQdtKau_$?w}RYhl)Mjd8^kSCg7%m!PK4hDQ|IRydjoErl`q zKJA_kk~;V5vIz`mg(HqE!3Kok@}jcxGhVVRrhkKQe94D8-nYlD&lV$m6`yALhu7Rz zD}ruy4Lr-VJoPE~z4bbKfsz2CrUJ#bm z#zaeXM5)s3yslp3_PtzD%3cFT*hNzdg1`g1@{d&27x!eNLjwo{BnxYuXkwzOoxAd! zri_#JIkk+s3j@@pp}C_mGnXJp9v)tE=r4;RS#uideqBm^TgJ%4O1#~M@0xBbrZoh; z#&~}ZCS8sGbS4!wLaE^g5;2+u>m8-c)kow$!^}hv-Cv_dsPYJVRr~*n437g<%C>+C zC(Qh}$S8Hec4$lDB|@+nr7m>&OkC=@L*|4_@*><4t%90zkA$^s=S16+s1gOoM!yMP zm56g*BwqY^w;_23jB54A+6t4)z9XAQ&1Iaitq06-3d%a-^GBt#g%gm6Z;i5?t`3g1 zFDjV*Fk~`5Wf&%!Bkf1J*4wZs@{U|sl8pIQzj%iKyYl`}{*~;`DH3Rl9cNsU zimJu@Gti$E*2o64aHz|7rX@&UWg9Jh#7Ml|S`rOc(v6Cz>3L#Kp_cnu%B0$L>kP`O zANIMjf8)h9`uSSB%n7rZ^|orWc5awLQUY_EvAI4`>*0MVhO2{JzO3ZeicQ&aj@$pP z%{zHib)Nbe$#Gsq%LKAjZQw>r!J+$zMB}IY9*GVv|i_W2sEa~ z=8-~=Erlt3-&sMT{ieabWbA!2WaW!tM+G=WoUXIkN1{0H{Ec^U*1(XSt3YUl#YU{! zokWvsg%)Lrmz|R4Gk>!Cl-G1(^C)Tpk^#@DbZSL57Bw!@mQstYI1m*kh4p!VlJDhX z!8OWwCaCyYrjg#_I=Jct6>&*3-Drt)K;i2aTJ}9H*yod_xtpLg1e}2&Pc&(~I;4wKD|f?1TA{ggY~g9cNfh&bjpQ0(kUt+JyCd6Hnkvb6`}S zYE@#5)?6<|L(5lAp!#rfdz~n%W=8s)uxju?aQRSku!;ZEN*?BBSi-MiO;e0343H=+ zspMBMvYq*-#hbhij~{eX$aHwY<)+0H?(_blBmaiexUt3$52{jWFnR7IdAIt_hN;IEm9Q;q3pkK^kV65Z?o{rn0Fw0PoC#;@2uL`tB(*Z!#`0 ziblt!YMNRC>=nR-L2)Z@WdIXcQkFH2S0PKVHgoFIPN1+v&HJAz^3e|4<%=xZ`2hmU8oQbwQT2WE>NrJ!l0=HbE)#Y!DLI&dYlwpF`Oa8ABUPzz?HM(T+jW>iu%h$%K=%h z%-Kuakaue~94^L0v0=vcw{54IRV^EWmW-o z5_yyYCq{aW&>phgPyML9QTFcLZhTYk4k+ZDPWH?{a>f?Rtj5CPR8l?f_NzS6uLv1;e!d}H>HT5VF6FYmQB$?T% z@|iymUMv>mX5+zZQq3Y^zc@dxSu^FrDuP~*-|DW?UQOT%RIji*IUGbTi-W+q$x;#p z9N_?5V_NRF)+#O5lVDUPzj?(kKDo!CKo3dPTyU#=j7qkjDKx~XCX*OLgvP!o23SCe=w z>$9Mr(Mhl*3$GIKpRUc6`ajvy(p8mn9ZYT6pnqchtHeGN)!KL@D!-AQV%4D10Q3Tp zdQg9d(v=F9AjHfg|2Zl;jVY!uxn(Vx%l$>mV}6Q3JbD#JQVn0u7;jKo^|pBi&zi}p z`0tp+A{?u=#$DQ6ZHpWBU&Ztp&k@QSc|7JLJNz<<`h#VuO|3H7_l9u{%^eCqSMp9} zv)4%ZP}RL=oBl<&v_E89maYw*y8$BcBnxmFB6!}$;T1_g*mM^`RGu>38p|@5fo^R` zneXe{#8`8t_v(^#J15O&@g&(a!l>b>?AVXx-YdOZAr9ZNK`Et_Tws(U3)!8^p8Si= zGNYbeonf?XX}A5!PN(T2qD`#Get&>V7Is3V7a3JnZ(9h+Y0V94$kNARC;*5uJB)Zx z6TCx4Q$x{RYTZn#Wtl5v24gy2JS*i2T~Btr3~{80K;-TJ^l_vpy#+`0qi;7YP(=0f z#VZ*Gc6>cwn${lSh~<5dh`8CU<^L4tzz7qBR?;M)ISOZ|nY>{PSeL_eGeyLuE^o0A zD(`+zTYy7Yo9F_e4&Xg41=_|1LE+}738I(s&mDwO>KZoVRt7R5 zM?Vmv#4|P0;_)0Qt^=A8zV-UC0BC!)$=||O8{CJyPX$xZopAc_5 z9MbXj$yR%MKJD+3d#n5UqjT+gQAKUzjVK66$fyt%X*Nm7&U7;0Tx@Kb8|4F+k~FnN zbctCMRGz5cimZ|x7!Ut>+Y{J>;Q=m+QaNUMlC;{rv-g2%TKK+mHI~h2jK;?l(I}eNYuB|V1Dk0H$w3$&h_~|Z?9tN@Jd>e z)S6vu+dm(L0qvUf1q*t|Y*h3ki;EL^Pg-sjK3&1-hIcPwi{i=KHd2FqC{NY0>t7vf2gM=x|8dNDdhp0u8eN{ zG4E<|H!oZ3WVe*C=bjIF3&)tDFGKeW(L3(`*323JP)<4kX8*20sUF@Ec}!<6+!F({ z+9$^%vr&d)H)Zzu4)+aC_)-3qdssDi=J$7q|55Io4<~#|k8iLo3-7%W8x&R&1w)K4 zG1k$Mof6iN4m4T*bTHZWv;um<&ojn@wFbdfmDKG@&yl{L`s^n@gWg|*iPyH>2hx_TKRBu|We9H2eS1Z#HDn+9Wb7zM4&J(~sVvXEQol7q>qWxXpch1<#12;&$2>Xp*X>{+;YHTbd1?4;O zd~+je?NoNiykX<6jkMcT6jkqNe@J3E+GdH<%*-McqlVcokf?^h_ z?9?_8qqs2Y&I7~uA047U7bMxGDjVUj633FJ)rmt6;&z0PVn^%HroC&QZ==J0&(X(!MhOWJFT1-XS|j9GBD#WV*3NK8ES?$EgTFmAB-^(nSTx z)c^hA5Xw+F-<^h{iM=YPW(kzd$cmR2zgXXnQ?=y1Sm#%&2PRD!xJX8SD)rO3$x-Ej zHc$=Xdg^F9G}9yN)wq>k8kY7L?P`=?S~M=)_eG#)h&>ipzxb3ci>6T7kw`8u1i5oO z(j=+j?aocfz8ln7&E0|V<%K^8qHi9A@f!}?5FJ8HM>OV45-g=K6ah9AUF-#1dFw;O zG6(5$w+M5);oF&%Kej?$sg{Ul>138neNQEox8yI3&~IgoJ5r=j=K?GLe5o#wgOq)} zdoSg+&&1i2mLi&_Iuz=t5K73xWF8!%PSU(ZHwqew)%jqHE9E83nN)vhY4!Rql|e4` z0|>I+h~K^5N3Q&x3UK=}UzD4RF8#JUHm#A1U!Nizg$t1kt-P$_)ru`%{@3p(v2C+7 zjn}$Q{x>gtYEvt(!H|{taoBK?!GT-;i`(yWmhNMBHxl%d7)A^8d^a!>XXjY5y};&5 zKo*(@3t2GgAFe7A*SXOgyapSo(~u&DxN{Ukqt~lec*mit z*Xsne-BXJGRd->3K_Xn8-G|-If3p8od{I>Y;yhBDj~|NID*+O%JqXIa8N&VNPqJ## z1wH*0f=`m3^U6nFQZ-2oa4x?Dx7uQ%$A(rVBpX1tx6BMp1QuJJ!4TMbGjsG?L2vZT z42D~vMCWX9$#$TRs*y>zE25k~Zj-=mxX`tCNEs`gM#UC!xto$NO5$hL95Y_jHh-E? zcORFN*`^spupB|k&kb@=2LG@%b-^#+Ui-b`{mGmAxU{l{5MjIGdu=MhH_zn#X*R6h zWjg?=-uHQ)^JQs>4n2QzZMX;q4;m@c&h1c$U(r#2^rRk*Zus}Hj;2j4FC@`<2fitF zoDH(tqq72;P_6*ub@Di@T|;E`Ze05f!ACEvfk$>r1aem{Z+6iWGiEvJ%c{hc z4K~vq8dA%^DsfIW(3+Q|oB9GHt9OpD#4ZrV=jmt@T$dYPbUfstg&nU}JQSzhrI+qH zZJN}0Mmn#4p6+>GQVbJAEY*@9h_)JQ3>Vr-i6Fo=k-K_nV^8rsvdVDnS{zYQ=;H?d z;c0rs={lB>d@pqQ_FpyAwAl=#4R}7;x?xNjKDp*QM?}PDgL^6Hqg~{ee(>BkqnSMD)*C*|lcssXd z>td5&jSdAYy&n(|#as7bb1Lfj`4Is9>*0%sqU1Ar$k8}eP(ii!68FgUCcibcY7nh! z4l0m0grfwIM;pU53_)2bk*K@k>)Qc?2pZUGVE22@uMMq;qS~2bboFi+44x>KKn)za zC4i=Zc@`%%OJk-xnx?un?&2U;4g)8Jf!09_=R@;8J#AZvUnf+f-I&+FK#w_$KDrpT zlfbik$N9C+l1y-TFHqJZyoW|Ybd;`(f(3+5oJ;zJygRUiuc~p%*wWzZ{1+Z_ZB2d9 z7MqjhEE&fxJE_|(OTshq%?wz9tWVYK%L&)b#@GfK4_Hq+f#r)_g7uR{OILQccC$D{ zRNJr9o{!GdtEQ$axU;ql2-O(f$$-}%nm1bvCr=-n{o4eF$zE-BX~DNJ0(d0qqNbeu zX^}ZEZkpbz6lsJ(fO(knILb(DzE8!5E4@M&*iPro^r-td*zT#aZL!SceJ|y1HczIT zC851t!k6psNj81+9pf=&l*hzaBPi+)(QV%Z3Z!jy*SFtvb_Nv#U32x6%hnNB&s#-p zUQw{_d|i6HfyQVo+#{u~C3WLyPeNswj+_CZxdpsofLqDvcw-S6>h!PJV%CPw`C>E7 zlz3zdXuKsHV35Dhu@V15q@jf`=w}*L#vUwiQqWZBbPfu*6v9QADuxWd4HqJogI!Cr zd45YZ+#bnz>^eZ`vzN+W3>OxAVstx(ei#dTL``sii%9D|CqfoY$`syOw_0=h|o-cpMU4pU~aDO*2%Tn&CQOyxEJcDLMyTM^k5()5j4`VwYfr~Fi3XjT`7h7 zh%&V2inbt6<`SQSOHz~4!}&1&&HrcWM_&s|z^RXHELl+BMt1#h=IUZ|DZjgYs`vA% zou+7cEL4V4Nxn|qPld}j6)JY$ph4raAN9TV8Pa>u}fs_!}_;Lst4#^kLY8#ey&Q2hGirQ&78pGv0(bv z`S+K$nDnHA_O@*^gzZ{gskou@-N{6Io{2ZQ$9rjDVdzwW z642*XY=ruy4kgq+WQ+gQvo%WOwC~6)=F>iI6^AxIEpC%p1d(-{DZdjvYRYEXz=Afc zH=^-A+01(`8SdxhvJyJzxv)#L+I$O(Y1jMotB zHMqYBVJ3*tQ)@PfeB4#1N5Ba>WX04%ek-NWb;4nbkOq^elfl+E(1Pa_O$?GFO}ITf z2%;fx2!0FH?-ig6YmI8Twy5Lt0a60&&(9ZbUwWWd5PBf<(n#cGK^VJZbI0_pRqvDS zclL536?d2(HBjrbNj`?zuOPIRhL8L^$<3*vunXs3QvQQhO;c45F=pB3c~7XGt|^jMfj%gn)ofPmW(k@#*D z4C~G8YI&z|yth_39 zhcC$|qzt$CscGJP+`-C3Xa?t_m2_@9!azdo3E^)A>egJI;rVaKd|9`lMHAnUn+gg- zaS(r$;uT01)t3BJF|!Xv13CrIAgqZ)=&>iK9Z_T`eTqmNH_YX8F6ZzYDRf=fHX2>L z{Mj8CsLH%tU2?K2N%DlSl5W|f3;U;Q!6;$ol*~DQ-w5s4LhMX|5#MB`nAdyyM-8v5E+@gj5b$!qq5YE;H_=C1ca0pAf?=fp( zDZQ)(ErtNzf zVW>14!LO)gZl#93Q)9YBP(=KrnyvPOZ8n9!Nnb-*tUN1+N@{pnK24(>wpkUkd@CUEFe1$awcMPEcXVfw`6{1^le_#ra$Ia=gJWWnTC+sq!>6zU8yGqOv{B1gnea#wzX zo)k!m$OyQ(v06AdSlF5wIa)zkO&sjlOw1t8R_0bFM$TrA5C>N)sFj1gmAwUAb-sk^IR0kxj+S#QCGnM?TJvyj)x# zdAN8#@^bShjcdQ5{r~?CbMSxU=Ka5S{C{@X;s5)sxOw?La&dEf0IdKmHFBi3>K7^Z%fdxc~Pi&;Ma5@xo^C{D)0aO%kF&gh7)y*+Ks= zXi}@sVwou@YT3(2;@Qu{xg7>gy1g3u{2w*x2p;XOWAhcch2XXy_Y<*kMjg5a{B)#z zaGt_9>ZC5Y7q0bHAn81_aWM0B8%BQ;d37IV1sBG=fQv35c=4;x^&@sm+@Iz{Y$_Y=5t)&7=MQSWyU>e|5XQcz51{W1*$W&kBk7&*o-%N)*^sJzP zNy{^Ck`=zqR|?!G?F_6(V2%mJ8G~)KuJ8 z+rl23$kZ!h>sQM^1Y1m}aiUr*sBv7XES@0|c!@&#rCT$iWyZfL4Qli8OHmJfzoY8` zCZoZOyR(W`M-(H;3_)whp(DyH`v77#sfYMOapLuWraJmHq1t_MU8N+-?DSt0dcT~l zVok*wB;T*UEExrH}hO4MJ{nErrDzb2DSYy~lAkX52A@V!tf$-Ab`G zUSd3JT6Yw-r}aOwkdmGwVqoy0v@t0nCjU?XHS>dIDOg&OxE8cbPSN&uZWYGS;> z&MDGxu+w&^uqxFV9hf^_sTA|N!q+awxKF(cDNO?7=>r21LOR@;OyOEkh(% z*dl6vC4&E^rM5@F~r+kRQc(o?P_}uKXm1*d%$@zLLZQ0apEr}sIW!n(wz`hk-!%_>Yl-IJLwjRbkXkA5 z_bm;EjE*(4SIK%v``XBx;!L#}J;t!Iw~l|beR#iMWbkLXQfvT;LyO-LQP-&d_I@j_ zmn~EiK)H4Tmvd596m>fOAuc6hM%N9hpQ`oNVuugmh0l&8D5F4vZ>l2duY^U&{-Y`P zWJbP)%lR%_1gdonQU6X=^Xo+tS-o+WK40Q zf4=gIYD#0=BgP<538;sgihfZ}B3Ib7h6K2x)y$3-WB zYulds{ZF-Ywi8f~%ES7Nte(Bb_GzVlqEf74j!RnzmKc46WhtijF8rkk!S(AmkQ8Yh zf=+tm@m+ZS0-I-RbI|HsHq>wD0?i^&{x1ZsI=J}5l>FUBD*RrK1-I7&(zZAy5eTLjj2vMN9)UW^L~ybBtk zf9Te{#w}!7rf{in#VwwKw3Bi_g(9hg532|f=gfgV>y(A!+9wkH$A*Im;h%`(j*$kD zeBT~OBNa$X;qXg#2euf45FSvY98##rP*vW&qB3&m3SALCm6r(-A(I?GHEM!(#ufA38DDuHgMVr*59EUP6QejjOmBZVn zSn~rWK~9B&gQZ4^B~liri}55>poNXYgYeEMX8-?v*lO#%x;FqdrT zVZPKv!H(%w>Sg1rSF~B-A7<1naLzuU3J#3Gy9w!eKzVt6(fjv5T;ZFQ<08?dsGutoTxA)I#9f@Uk>7Sdilblb?Y$sD;SOx!>Uaos44ZGd^!j z1#5lC2nI|O;$LJyTMZ=_{9>kN)bMF}Cx;22Nrt|EsSCP9w!cseH9nIc(j{ip4@v;8yr1o7m!Zt<0kX2+uT&t$>=d4AA37p>wxdWB+rdH{U zR{dN$wqIWpJH9yeA<}l^4t(RR?OvwNhUw*tuKzZWTCmW+YaVR7Ios`9UADDCq2eH)r^97?iOTUT$G%p45&q)~FH1)_cn*HNCr;rKD|l zj*%pMLiY%zT*5owF+o8;7A5)JY!t?iBVe^25jBus7$>LbnZ_K~ip+Iz89B`@_^pagOzdTNx0p{tW>HH~I|@%g%OuMok~PO4PtVUjaFCS@6AD`w?&O+`&me+Q4fmz)3+uMSbgAo!u8Th6Ti42u z{)L@FZ0W-k^5$U&MMCw$N43MH`c1d;A5tE_i~R{NEdYVj${&_#UEx zJ;OIZZ4P>#Z5}|Nhig*`FOJ8Ddz>{<&NX;kUWV6sLLweVO2r~-2UrRlq0+FU`&`f+ zyd6mQeR|~D8QhcZW1>lx{LCa^S9n1_-RiAgTVaysD~*q1(grvf~saJ&5N zIy;^b{wPx*tjTK@@BaHPv=bhC)PiA-f;4X_;jSD2y2PXmap@`CO5OP`_Z_3FaeA~6 zGtMH+^4h?+Ut3N1j-dU;Ft=ew75hWLg947A%sc)?_bXzMPanm>H%+d{0M$EYl>gq- zy*z#2?$wv;B-nCY0V5Kh3h z)`Vag1u^2Xwz@Oa#EYrtt!zV>V0JmD5J%cN4za;!M>P7GjH&&DnFCfiq4B+7X@SAT z5<;Yh_-}>^n{8azgFmu^nYSV1hpShkk4-S-V}`pR*A>Q{`n299#sNH0hGZ!OA`Xk# zGm%{lEBzk+9+y;Mkk&d-{u=4rJ)xWM(#an2t_;~6!W!`wRhidD$8fP`qOvKZVB&ZV zSy|Pr0>>g3;;^u*c6xzVw01gUP`bOyD=iUS;T&6k4Y;S-^_CIw46idA5J?ROB0cKO zVF#Vmh?wa$7jRu~%mU@v)!~Ha@jK^izyI5u!-nTWJiwwcS-vHYVl=whB*2swj$z+s zv_d*-L`o@=Lxr-{ApgrhJjOri=u?YMrYUJ2gx>So#3Hr#p-?r==zLk`CRA#4_&unY zsD-{%qoKrST{QPX(y{U%Na-7Vy?n8Z?0+X;9VNf))$OFfyT}74?P{OVK8g91XN>>n zZ`gP!-oTpm@qPKUW12E%C^;cZ#xZ03S?)Ju87`EPVk9RB$(wnd2K*@5C`<=ASH34o z!$fH8DCZm?F%00;*64s+qIZ>pu|GHxm%v|hy}hz`Pw6r$N_c3%+$t4`3|dOjNeK00 ztgtc=59c$bBk+=_v#8#aXIbtRxme$~>;P`m#J^V&D}~%LGnF^?VLdDE-rsBAZ;e?) ztfc>+V}5K%#MNonH2JB(e+2%+<=O0@{ox(U(_0=~zun~4d1FZm{`ByLo8{@WB|OVM zh@NoFtTY)O;0?g<`X61JNhy2+4MGPW7LnfPo zDC$D4jeX}oyb$zWVj!cCg7`)`0SPAme@$+T52c82%J;fN5AFi9+gf7~7g%5BxZAx) z-Zr$Jf3FnLe~TI-4%D>v+V&Bc)m33YdG7MbjB^#5z4ULK{(A!Jdb2zb@6mNJ20knV z1=LI&mSz2Ki1r59$T@Rc36!v!DI4mMYhF+Jy9KR*;H-;&Q#bfTyk~_#a8|dP3FTR| zkOJzFSMyaF*2k2l{8M1oMMaP54)z;BEkU0n5Z}NuX-crxOY64i>w%yEDr)FTF7};o zLkuqLX(p5Lpza^>Zos^4t>tV{ieqNY6h!}0A&TS2({BeZK;c>4`(pBA{>yXi1K3-= zU2H`tjzO!;|9fwgsvtS+wsXDzO#`p-eE+U^k8R_{+KISnT(jH%1|<1Lam=3rh*3p( zcKDl&=%3;XfsH{WIQvKfYnrZE2RZa6fcV9 z`!>U+g*WC9yXi6Jg*GUJfkkg6^v_ym%ENTs<^Wo)v#PLt^?GO)mQ!Y6woYqY^x7Xl zjjJ(onT5`kiTaaW7E)oY0*-C+SwQb6?o{cB$-D{sDK(wn)Il9|P*y$N6iJ|#{zQe8 zdGuNkA#eRD2QAHr0xS1kSCOxH`=p+yaY%w?TMO&~9^pW%`K;R43=JX+`|9?2hshX_ z^CgKJAn4K2stblT2h1+%sHaEt9)8;0QijJf?KvpV(lbZB1f-!A=;R%w3;^UsgAC{f zpQc~XS@}N;T7NHnh(1Hp0Rq22Ab}!&<{N^2iy7XmxpGH&eh)V|Xsr;_sMB)-c8!0Q}&>PcU!U3o|4 z3WG|Gev&^z2q?BGau=j~BCgN^`4@5zve)kD!~(0wz8M)c79* zb1=K9O=0GYP@=?;J0>|*>uUPZRS|0Gy1vfNp2RAnY+75xC01i)XI-02aa)xA%j>$|+Mpam~#QOy(W z+=(5Yb+b{fDolQiUE&LBzki?)UrkJv}0($X4iN4wSc{NH|{z!=`EyI{QS+tKR^ z%a?tYyozxIa_+Q3p3RMWx)eh!AIE#WEIhzH)2AyJ<+nG?DB<&<>CN^6H&ttU3D0h$ zSkPJWWJf5QL>Wp72n)+PcOpGwTntw`T>EluF`y$tEchI0r@G9@!!YSND)AhkR-F4Y z_v-5a#Rc8Y`@NrOz&Vr3Z9eGC=!^4bK)HG6{h9~R)w$nXul(5f3Jpg9kwQ52OB=)z ztRQgo>}1f|jGxSrw6Um%Ey8QFK*D-|GKOQCo{=#p+WS#NTVRXXKs5JN&%p-d-=bQi z5gjA`O;(t8Xth?LON!6KV7zjdqnP{zld)mA<(~4~6Qa!ZsZOjX7QahTQuznWohUD< zoV^R=?{8C%mn^(KYtJKM{w2io)hH5~MnUSqZDH* zz*INHF9q3;aNSsbFdp3@!GGWIz^TjO$?b&HFDf}bOwOyG4G`Y4Dfv6K|A*Q4Ni;%F z#KDx%UQ3o2CXAwA%zxeqfexRucb-?4 z7C-U7xpCN9bOUg*`+U3D^_6*i9vdeszncwzqzw~+ZhpLcdj3;tl#HUW`sA77Mz#)g z1C;-{#w?ZnxkLx3-SaT_u9K5}9FMj=6p=PMSE55cwKt%53YJTHW_J#64sj{++uj1^ z`sE;6$iJn5rBv~Z%pN>qM901s=CIo&3?m`<$}^HUn;ZS%gfmtmo&j%Rd|xG~bab@v>!9*GrpM;Qi(XJ@6V_x2cyC*-p@s zBFk2nkXDV<*Pw8ENUP`Tx^`4g7y7^;dCiyFjUE(>+XYD=(x4d;8`%^;BV5X4W*dJ> zgdXx~+oy0bBrOd&%HHM*5(->IpR}?M(h#r&V~H{5l7#hkK&-)hA3v8ahE9;|HEdx^ zJ9Q(DM5%q&Bie2;TPtUYt2^7k8H6AH#yX{q@kT5IUsy+=>yO6OeEdgaiU+7i^hsV( z^}j{VLT0U0lSEANnNhdfa=t0we{RST&HQaW9qt9AZ|FRi|NWYg5feY8NrP?iG*Uk2 z6OygW#TUrw&Pr+f;;g(fuyBGV;=AtQ6%*6S1q$|gFm~=7wtPo!yDjPNDRo!_?N~OZ z&WzJ7yQ{BSo1}H6uSrpsOfPVWlf-J#5Xedx*Jmh9$f0V5YGukDemW${{2AKRTFBnh!8^=^x zY=;rH@?p+`{%dWCr)u3?pEW~q^IkNY>VbXkj7@YPx-$kx0<9xljvYVFfr2U=R6#>jIv$j3pX}->WIBC<`(E+CDo2-@1RW>y z4~G29^^JT#w@0}gp}$dG*AGw#+P_iE<_0afg|W-dHH3@@*AyPFNkH%x!H`L?mbzdA ze+r^fQ$zG5;$x)w5T_IF4w)}KH~lIGG#REh?N2?L#EWMS+7AV<3>ntch)3^`K>#ianPF!4nm4(rVic4@_3T?P zYFS@&)MbHTu*0RT+&azmp0F$zb=dd^EtS^lqpfN&A}ifCAox7z1II|aSoi3z>p?vF zp;P6YZqz}}r6<{>i?EOsfea2;YeT9X3i{Iz;7D4^6`A?;u+ds2xUY+r6SX)Lnz+)X zzWkqOha?apX1G50V`YdXjXlYKTUJ0jEa$lA{8jqA)y#VNeH&LhuYrF2%VRGIwK z_$*vzzULfv9iHce>VAC4c#x#Px|$nzvK^YwU}KZ)3u^kfj=&7TAs_wlp+`Rxnk{bBSA!l`Go9sB>tR*})Nm^`q;(2f5)IYXoSd&#r@xlgx#Q zNbZBSs*|8(_54qy=1RI`v;uv-ydpvKY_+|`RE1dE&Cc^M#8jF;4Dv#xI-;pS;2*t7 z0DH%Gs6`;LX_{u_FH@wcdK8JlmoIh?7m1vCot?kT}y{F<~q7jHL<%eqAK_{4ZNuTFBKIzC|>lk`P30yPdloY$l z2*pysNJYBaJLF#l$XVB^%R>~UDNnU^f=2<0WH&F^zZLad)1^eI*>)!f?BIm=HCbDh zXZ!sGW0h~gkmsu@_z|63a7@#hRNYLqN|n1de*;N)vPPceY_Yd9IYM_>|(j3Fh zdl6hDEvJq@MMbxDY=-`UVY#-x7aw6B?WKk*_Y;k8$2(n$t`U2|3QUps)eSTQp?R>V zK2Kw%0SgTFCLa_!J!~9ss7UVCW5Sq0CzE~t0f#GoJa?}Dh7Z{A z!<>zwCWj*JFKa^#)62Y<->gj=myV*R3$0AgSl3v+{`|t*ye(rqKx$xD_+xw-54B~e zQl~X}-sy5*Bie8Eo!dT+EV@3y`(j)Ovy?a--js9gw01z-JCgx!S_xs?`XbecFgzvx z+(3?e8;jZL;IxKM+HNHHzwKLuOYNwJrhd4dInoNoWr~hO_aw-lxCYl#PK3|G4aZ=L z)=H{ySR%M_OB#{al(RyPkTl_}K&kA>Q#OdDw~C)cCU+2E)kEu82;7=6^^(i_`wVLrq!uN}H&&QLJJE5zkHY9mz+M;|O+ys* z<7D@?XCL3{{AMoVKKQ!VA;hQo;(S5oEsDOi_&>+*wcK6yx~wi8jsfbSeb{>f?eIWF z6Y#1?UUf(?`tVBYN@tzxXwF*pYUq_Y?e7I67h-7JAYG&2)gFXG)zFQ|X8@|6UP@k* zL#c1jQ|C5G|D>J4pPu0Tn;K^6rx^S(?YR6=k1)1w6E6C%4)pnxNpgNR-KqEXC!-&#`ij5B zJKZHsetRahv^=&O`8Ok`caOM9qoHRuC@ka!ntt{3kAu`3{VH58;?k*QlaYy~1psHU znRJu$8hFn3>$lp*KQw~mY3|JKkD?X;Eoi0-?_65CPd?y8Py8WYh(m%yVjDqI+(mXV z$Je|&QU!Cy)e+<3#v{&DP$%E&mtP9x|58ABCWDGm1?t%Gl>kP96K*_e~u^TSU}Jw5PEw zb8V?!U1mO3NBaa=vw)8sfsctz`g*=2?WR6*)unQCTkAY_^|1~4=a~^Ll@m)|PA62j zMn#WiwbjUJ^czdv!c**atWb^0)k-)ennBfw+$`lO`o-H3^lT z*YEQjonCD$^QD#!;1?6c$}+=cZ0n{P^z#iJoVVm@leIRND)-F^sxy1A<#Amla#}P| zrGL1NL?j*Dg0{4EZm8a}|MGgwL8}E?k|XlnHwwlP=JP@Ub5?`L+Z=4B@0Th zH&H7@{x#1HGGbo8HeysYk>;}#wUVsUKB7k`Ln+7yEB*t*Bi)84dX>VU&&Y>@rSKvb z3d^683p2EE(>m>k3o^^ugM z%h&dTotJJfpMIlyYrYMsD?8lSH)eriR(@qOW?NlAEy@l;H_``gN7cr#Djl5GBht#9qj(G)Gjw8flLwMd>gBn6CS|pB`1PfC3Gp>JOj#+4&i+$4 zE5onGXigP{Op>^9cm(JwV(6&;1EppAwEF1CgeS6be*G{pI$&;`kaMhLcQP>A1^Oq; zjy3W#Oefil;Nss!e$Gym^Z1?cq8{P*1x5E82YYVB_l{XN+ z+vxeg@;&c`B&rwF(L2E1`2BUQkefezu{n@pPK;Nm`PmN#koU(7G~W%Y{$| zWt};^ra6VnQl*#g9QkjKO^Ekzz;T_CDtC$GosCLSHychQlb@S8@B<}OnKiq3bz2Kt zk8Zg+;$wE?c+kk>#<2thA@ENOiV%=Dl^O3d1tDC7PiytQ^BwU705^QLVFjY0b>Yk- zA2nF?7h+D`_S)DgybMvOOIzuWxOwOLD+7*=`BGyIuUqUTnQClg%SGJFBk}Lc{_5r}QSrf2t z-@BvAumAK-#kCh)`(uZE*`F(5y4Lj0h^Y+4FM56w)}#&$_$*zsr1!=(z{SY^pC18tX@h4EuJ<9?!{$OhqwF^O@Tx_hPSCo| zZRG7^raiY)){|e1j^mM10cz@;?3C()r?w= zLJkE>{c<-pm-YkUvzubIZD!a~5M5ESZ!H@deL%$s*m(qh)bJVA`Md^qtE#FD;{f?M zK6Z}!5{+_XQ|4t|5zc1zoVs{31i*Sis&#kbnzdRR$MUIyw+-tc0Kq$k?G86dmv|z_ zfXHuxIhtBO1!f24ft1X|O5URFx%q6$Kk_)5{Mx|UeqQ7q6-kG|@+bP{l@toow?cn( z;>v05D_Ex3$&BZUgZnBRrAu|-wr#k~iy*5V4k?UFzY&?bg*^iY$mOY%At1=th=*$U zXPa=F6xVG_77V2#Bkd&DZQ&lc0X;-5Cgx2+>b@3&9N)4+Aj*VxvGJWeh%fyPG@m0T zwFD?us5}&DDPY1bsKZU^{qtYs(zQ;aLkG;h>8u8!6QoqBeBr%Q*m}lXYaR);BS_Sg z+Th$t;&)0I)?xcj#KpP8ZSCx6+{@zRTp@isaZb~SHu-bmwLXy>KcpjwW|SGxWgtnU@wVZM-5}Y?e8al<<99db_w_8MLo8^55^V&rV9-aK;ofOImtHyw;)02 zGub%?%lDk$=wAXK`)Mb&^o-Fbk)Rg(^J2wta=kTT`ab#5zhpe~<9$+3$+pN2gW&=M z(H>p0G@6A^{O|f&U}dI%V{ckt%L`){scWzqVDSj(d1e9^YR1by#c1`5=lhqWem{y9IgS_? z(1`I*7@Qk<5f(?fAQiqoe-4M13Hd_#Duu z#lM%;!#sRDvp{o0$2LcAzT+IDO~fUgck$6#)gax9B^pG};F4Y(`W!X15C5QNbWl{k zmi^}(ME#I4-lc;5FyUnV{e?lIzVLx?9h;Fd_i-}+Q~fW$wC;1NzxNw0?2Jq}WqK)? z|1QpfXFU|IQ2@=l{NGOP7XiGbv8^4DjX-nW;DwIfqu+)0O$p`2t@>h=L2 zFxzEfO7@@R%`a~>(kB~9V(w`h>U5rE_RzBs7kzJjc?PFiwq|UmCkY?jfA&DNc~Y7d zF}n~d52lv`nO961Fa|<4$4y3WmDxd;6Rzf3eongWN40GB_?1QddjxOglayYqlVe{E z@4(RlYzSIs8xi=Vv&va&bJ@|SmM8V1GKze9ba?*ir9R_Y6PD5nV6P&uI;C>e(BgMu;@)}-w8cfnJLIHCp*4O({`msO!OQOTKJmM z@%V?m?d>9Rd}|4_WYJmF@~7-iHDH74qXeWPsP34IV@S)=zY_%ieh!qLI$nqGCrlm^ zm=77JS{;{>xf?GeDV9XG^3K(IXP`I9jICdczL1G9u0^qYDR&%?yL5XKwm+d*IOQsq`Sm^y8frRl3#jMr$*ux-5{6!l4(Dv6g_ zv9+4*x))FZXcPVzj`^wW9lJ=)Av~yrA>hfu?Cd1oCd$iKJ=02O^|y`HRPEsD*e?H% zQ*!z?8KabG9FE~bkJVG1v)6diJR2SwsSoH@P0%HvT$_z&-7+vNcl=d__@S$SBJtpu zr}?m}17sg2ik`>Vzr_DA4(_Ts#;mjS9pBqSA`NWeAdz8jijRC`ZgZ;A-HMq$uw1vv zj}QlP)|#bEv#owjQOpk~TX9?hJ&IBUn&U0(!Qj05i91hKkKd^Hp45}ebo2LO7zYi{ zo*s>*Y<4bx6R2n=VbN~X_)YggrLFzU z>j{jsv7+NT2_iVBxnrXWfWPktDC>MSx*DZnOeUisZW0|>x|NB*s@81v;lnyhqQfen z*y~bV^E<%sl(SeN+VPCCN%qafW^|f6i7yFaR(6KP2~Qzfu8=IueNmD7v)yE70h@;I z3qXU8L0(V5*2|#VKfon-%qEcG7UL%@J)tPMWScY_=jGGIFds&~GBlcmZBh{DiTBUO zQh4*m#d~ZZ+OY6YRv>|O_arRj7gM8`&_469a315F4-FweKE8i}MvOE{0$rS1*r;o> z)9J((NO)GLg~~iX!?K!R;~BVQ=eFUJJK3FrO5$$HFd@Oyu>{+}02P%6w0<_`*$8e>Idswe(!Ppu$B7}46*yZ4^3soi* zUNkQu8W`^aC7EvCpNsOK$n8oNY_sfC0QxCgT11aaF z%}a*^f-0?+blG@Cq@3lcw<>z(&i5^ThAd5y)X!$c6{a9tE$Ml- zkT7HamV+XG4cNJs`}D+sIrVgWZKqna=VXN@#qh$9+)>j#wGvI^e=~ zupQuUeO~eLhO%Q|y_h@fS@kpZ>(=}iQJUi7+jJk}Tm{86wAt=LmrpxDy>tw+p8y`d zr3-!E_irEUoC8+hO}{w;=jW1vVsMQ=qp!o5gd@{g0(we646wcL4&w*Pn)!xq25RMeOvMgujjTW&Z!)dZ8# zDb~%I59J{pk)8(q(&RV561U9TAJjk%K@a}F9K+)0w`Av<N{~6btSjEM7N_pB;@aM%-+!c z{3kP}RvltZ6}*JePNwW};cpy#v2&W?;z4{Rd@Q^Z%7Yo4s4AKTRQK_#AIg1}N%P&@ z^;Z?UlZ9GXi~T4%3>^r>Pp8+OLW|fas=~#t6N-c7rAI_Cc*KJN9_PSfgVwl2=Y`cn z=6G#;?3s;_D-TqS(1ALF6kncTzmzaYICnjh`w@lIZ_kdy{^*NhuMJy*uc-uUK2*+! z`Hc(?F-@i+E|mnpdh|5ST<2WvNz{D4mH#|nK&~lBXhm;6c!xT8wh8+W;i~Q3QZ3c9S9Q5B@t(AEHj3Z8@*;rs{QzDBr zY-I}yi?)sOJl1Vk<0@GC_U3kDV1WMZVIo2DPf#U-iKJz-kz=DzH|9HfY}W*nIW_l0 zW7xGZAe~q@aqbaYgo`tlLg>pI9gyxnS!TtlzYBh;B`E)VFJ{`RXHcIzzX3I$(CO|p z-KQK_(pc3$unR_PDt3-M$Il=@O-m>zmMG0@Y~&gYbg90*98S8z^5wj<;b8w5gUOGw zzVK`Rns_gX{IS+k-f#HeQgM~~rcAI^-_yg{ZrxJ@?X><^@=W~5Gmp5kodtCLEr1(= z*_z*Wda=h3e_7acp;c)QQ;zF=6vfL|)^}_HEg4pG%~w<&soW?-5`BL4j5(-BjyJo{ zup}Tlla7b})r=O@)HwFTlJ5&UB6dqL(Y3}eA+J^h-&Xpy0qX3~p-e^pnI>A?YJI}| zWttuwRo6^8u@zlP^k4nv&^B;o=;fBlI`B(H&8N(2ZS5>Xtuf!m33F5yB_zqvhyNiJfi}(!jF1E=fZv8Yzl}pjewM)FL3r{v7JJHngWe zSM`gETCfA2B@7h7VMb-RCQJ&FpjUuaF)(_F;dUOi%ev&hC*}lk_M_kXON#OdY*={!3{RrzaOsc25_^Sf5)CWHmh;ht-TqYS>tKI*GbVv+uY2l zXLmbYux>ibTQfSHx&dRYqqMK)Ggs&wWOSnvKORr5JLg{A7{#QepE7!AG^REL#4c`F zd1Vr0Q+pzA*2X?Nz2pS36fM-lu#ojG#(6w#Sx4?I)Ir3NX=S*(E`M6dA)+#Opaj*F zm#Pp@h4ihCzv_=)B07zutZDvc17p7pQRVFNcrkyT29~w zkAaT!;*#0xxm-ioHp58|+%Rj_PtJEjT-w7y3s1DJHdkZ6rU*VxoyD_#rAfA@NS(Uo z2NYd!uF)YCJdj=nHNb{I_%EG}{C(0me954KhxX(dXX;Q0N~O>+y4jKhB!k1O5dPIW z$X%^%F?mea6Kzjm`F@w{1@sJoab38;OEA)~DRYpqxaacr-|_;=HJ)%N(xuxL{tI zyb2RT9SyshRbsX#%_RQKknz5*51&zF2D1V`=?pK;xZa2Qk|F2}EO8rEhz|c{3%e>Y zE30p<0K)6$LT{&c4$UHRptoT6qCd1ok!^0Q2BN!^P^~kPD#hv;q~;wQ59RMRNZLF^s;??{iZ0IMWW&IKe6BvRn-rS7UK@DEKzO@l08fNI(!x@0Df7G-DvW!9z5HJi>X-*!RQhni~O{B%6=^lkbxn~`)) zYJ`DG-?(?FTU5O26VSgfldmdTRQhPQ9_4jyF3mQfuvGt^Z|pY(N1Y#Qt)y$LY8-1R zL*}bx?o1z5@ZE^cbxK<3vuu`Gi`+w0=A0O|cdYk(Xb4G!7FQzA?hY9V_zOi2uwaW4 z(?x$CUU2p;WS5o0bXe9yEBgej?JINP5G0R-_4mucn^lWkFUdK|W3G$`MAhNl_Nw_E z6}-H0$neY1WyFAVQPrN&U+w9!chEO?L@7Y?=}{a8Zl^2iUVT4fy8qw?ExkO7gHIG* zVC-Z}*)279x&xxmC*j@y#slCp+BiMg?6~#jc$lvfS>#wm3EP;!#s(%PH4N8ID<6#J7NO zhmcB(n)`NgtqTuX=z2adC%!xIYI4ZFYaD5sIXkq{`;u9*cJZLvKH{Zy@Se#s!cz1^ zJW&R(6643Ff$_K-vJu3wr0B1V@ZgHlYSUaJn}?aEQO(iKQN?Ah0gh_>uyGA~^rSB_ zW8*ANhP?(iMD7CFNs`{J3_?8DL~Ys-3bHMjIo1f;>5fB?hb9Bpnfszci^Puv?}Sk0 zC&o4YyJy3%g~DaJ+f((P&*H5(+Nt~v$BbtAU@8?g8HwwAcGQ1(iUn8}W|yzmxV%~7 zCJX2#8J}Cbh;})Wl&n_pM}GdtJr6g})-6s`;%bZlXJF53L)HrWIlL8|wS^Lx7nYsn`s+QKZ+E)?*} zV3f9m#DK}a<%Vmz3eTK{{?2>*4=RcNzoM-NrUIcqae-6`J=G=}^S+6dO_nT=Z2g%; zJ7*uS6*cBxs<{tBX{ogShDPE~(^*w7Bh5Ks{!AOw?XY^_n zs*3M$raBW$VOv5Nb8(}2^$mAmWo1BJv-ozQbmGF_i{>LfpMIfpxP!v;qWdlkTdN3Q zUHu653Y~->BTf}Y_I|qFR(Itmg~re$f-9+s3IMsHjqZSTWf@dz7*3%~yQ;@1qV5RU ztvEfh2BOLu9x_hY>!4lzIC*;B?vnuR${uhDeGl$bY4D*j^~2~}(_?MC@Q5YSv1X|^ zUa=yuVHWH*(+kr!^X`;8^673bdmc{ED}{@ftPkZ)l$yMr8;~mQKR@{_sm_^EL_zg2rpKnH={KVa%n1jWqeMV6qow1) zu1C0deiN=soX&r&KQc3>fJB>yD?hX@#cW`K4zsTpmW1lpDTOOck6>s?a;zuJ(TPkS z8e8~H&29@RzNvx{i}iiD5dFUH8OK|P;z2wo^b%^p89zqa?of$yUYnmltMQQMCgB&t z4i`t(@&kbZu3<`q;HwzIq&%>pRL6yyRX922>j0fzmj}Z@|6O!0sd3tUD1wofq2$|g zn!QnrC98zrY>KxxYCdBk@_W3qv6ByG}J}mo_z&4 zDTj!?lx4*hES7|?{%?8Zi(#x3TW#E4P6kV=O*+`e9ogSQuziY)hrA6c#gARpGOPsU z=r(1p2`wE_?+D=TTKok{860S0tKGE za{9r8U@ss0DM#TivvwOR^U(1s-^(Y^Un49@N(-7UC2x7W45x_ai%bWhc( zQ>UJ~pChaBTaD~HH0JMnwv6s|T4zf!_Qa1UnRL-DS1gGL)ee52&Zs!Pt6ft8s0mOjN{nL+vf4vUehPkBc|A1Z_ zWoq{3QMBo@>d5l;QPUsNSVNwp0M*}Hcl>vDjptk7ZG`mwF?PxFrR4Sf-EN=l8TVQ7 z->u;L{q!62`|}m>_pPT`@cr%mzp+Y;3xXDxA6TWAU+>R7?@uj)PZ0kftAuWM-za>| z{6ST!ZhVKKe;fF~F1-TMPs_4*f@jk`{|!~z#07M3egXesjOof0y*~=RzW|#+=G>rk zNTH_?3RJMYB7PHzQUY?)-O?$2>~)Zgac+61=|cBnoiV_vRMoXu`u*Nn{IyEJpdprL z+WxH|fZ!%WI)zBd)@1kB`wPmy8wS#t+cS&r(-micKyKG0h|t~AzJ^t2Bw8>xL)=@Z zM%$793_+c-{Klh-^Ob0%C8MkVCc|XMVCSl5n66o0qvKrnt*OzeeaFM}?LI9((X0FSld|gH zY16^tz{d0)fu||$NW_H5q-J?l;~q&vQPu+Jspig22!H*9O?IkkEs#4XvJ>TbVez*4 z)`M57=O3;o>mf?HN76B$^tBT5qJ~LcBC=KoF%9^A?n=U~Rub^Fqc2qh^-=wnzG&ek zKz#yMcA}=)H0O!VO?)QRWxFDn{^6mVb7_76TbQS;u{*|Idsvt1n108VR4L^;g%iX+ zjjozU;ou)^;ceXSfZ1pJGhLdG#R$K~KXW^(BD>>V5(y?_wEm{9#+(Hr1)51y1?5T^ zS`R>#l7~w=2!^yv@S32m(lMafrmARWRS^UIUPOwrdce4>K=PQ%63=g8B;P>^a4~P) zP1~kok0!eG8?Q)42yqh9Uaz@>yn-X&#^9=6keCU+A_SKinY0=?*9xq*1N%1H1n?we&j<;aZZ&^lKfR z)#{-l@2X#0--d@9=Mr4j9Z`^RL!<|qq`2N7w0!+G=U>Uo<9|@0)GmcNc8nQ;OmwPJWcjsk(z1-&)UhZQifO5*hRdHX@n~qgtz1#bQ-r z%2&K}kNCyxawj6Wd`aqTa7j>yrZ$tZh@{1Kp%v1R>l2qIvN~w63GDEQGLDp^BXGRQ zON@AoBw?P4eo1nwl07pr5r8l-Xf-;CoKD;w!{s36dfMKZeGevo^N?Pg~cSK6~qkjZtZd&oEG=nz<1Ke=@UIc-u13) zA&00Baq*t_qr_}T@TZ-xZA-@_Mb)WEddS$k1!!3OGLw{pxUE@2-ap9o(C-%VD8U%U zu2}6n`#!ODAk2J65x}GJc%dH)xK~DgzmXs-RIhp)3}OdiIDAh0GZ@q*N%Nlj$|@mM zAw+$=dBbe6zyB`~?=&R8Dqggp_9qamCRKoz@m5aes~*5Ba2C9`Q=~Q#gp#7il^1#_(mn%eNAUhM@C0uu< za5v3d?nuzx0)|?oGENS9R#|^kW(k(-AqkePp4-PebpJdBjj2Pi&%^QP1^Ho7CVmP+W5I9&OH9)3 zh2=d+Ie+HO0HWednpZ&ee=KFhcsxwzNo+npi|KwqW_HuoxNhpGXHKC76xMYIf-;gz zzmj&w}CWm#uFfex(08XcmDfvbbT^p8~0<&aubX{AN zgKs+a+^g7+P`|B(1O?jGmm;=S{mFr^3BS@~&a7F4!H9+F^_i}GB!geImRA2&et1Lm zLmTH<{Qwr*`Im{IvYJNdWxRzgIl7KrJBT=0EbL@-&W5F8@V490Z_%MTYoxfAbNz`s zTA-!a0hisn$RPUP$i%)hvRyS&=Gqp&MlO8|<7R`J@W+TBzj&sJ>h5T@z-8jT0p$2{ z-`KGNePf7?+JB*cE@*B_PJL|2s^c{DSM+JkOW{&|8-?mTPzKlgt zpGTf&$q7A(K{>d%mLtQu%4hE=2i0ipusH?R!1QODM;}Y~zDiO>nou__VU>34>Ar~h zuQWa?tzR3%P;V>2P~!qx*65fC@4Z3E?)VQVaz!eXdg!0YC-a5-xs%9+1dxCH7)r|p zV278!X~;e3vd?bK;5+!7Pq1Np|FxKxTKFfb(H^gX?~mC5v9uqy&qeEn9>dsNGm-vj z9gu%du@9T!ulGH-@@{9KwD*2|7C%y`7^(90mZYi$I;r7Lk)KeTh5iKnlA1*HAMtlN zZEf=y%fK95OLIBnh(9pHYfkDr7+LWFgpg3GMB0>L{#ECZFfNpcTxBX93kx(D>D^Y6d7JI3ePqEeX zHOHs?f!Mik7M@ zh`y9py=jy~%DE^P(S_C&1UmhRDc<>ngBhoxM7Y4)p^Ex*FW0hZDiE7`VIl+h^5PPA zVIoJthbML>-wUCP>#L8jl}`X>iYn0mjh-+|x|dDy)-aLveet==-38zYb(#J&KXZxIuqNWnQ~bw%QqNe@bB^s-;y9J#-=c! zY071EQ;~7<;v@I}kXasZI8aGf+<6R*h3O^W*TBC(j!5$xpx@M)kM0AT?!7ig35)pZZl@Cm3ALG>tZ2;*SvVVf7ixa|Y_da4vKP{p#P08!b23 zd>pEM?AHa4v{XJr;y{S2JLhdF37khuYV_HSV~%@M=6V^ed>1VZDEWr3;VaUX^i#Xf z!&{l4ZO;ZfESIbx$U<+A6Nv$gzk?KY0~1j7uk!U0|3RU!hDg~60*Lg_QhD^O%`flz z`do0c6m@(ZpZ#lwCsHNGE%uWZD2jpZ zu;CYyv8dHi+cR3IG+OHu#ox`^97kX;Mi%(C!jf7>lAA@Cw*+Dj|2Ow z7J8Pn*QJw;GU0G=AGD~h%2Tryi*)rGP3MiJIhDJQO%oaj<+pYCY+SbX0$IGx1+_+1 zM|p#S%|&B2U#iIz>!Pk%h?bcLB=3`SFe+x`2f5@bwv`!bwcw87VgSXLv|QnrTIetQ z;63Y6fL~<6o(8v5j##ATGHswWUSk*)w$bfym|vq2=2zp{?P*o$)^8e}Rn2d9sSO2| z;0hm+=TkFt<_L2G{_cbF)bv!Xrtg<^-l<60p!A|8T0L|bKDl6*oY~Zhz)!bQPUo9& zd3j92kY(JLG0b5Swc$=Vv$#$DKT2eEW9-|UfCUJuZn4P+`ff`sGVb~a?YjNYG*pwA?YRgYG^FWNz{`IVuuqLQ{Y$CTR zncS3h29{1ALqHNSutj4=OwS8H}v7}-(Cv24VovB@#R z0eR@3-t6M=Lg0~L?kTa7M$J;={&?k2hsXZ*(^Z+zd33i$x#pxw%@``S{MHS5+e*63 zvN>dDLl;7}r3q8iXQ#4fk+en!g(5cG^@y%Q7N3K;L@i+|DMD+0k~Q{2U!Zrz0PT1z z7jLEpQxb+@P)_r;j)X*Wfy~;JYM(Nwu(0LJIL4#0%JvbiJi(u-rU-TA zGN`JbMCE2~qwTBW5(oT#qO?mO0O~QQJZg9s{Isi!K_3|_S|Otsv3`dwctP|&xOvls zV~1Wx7=+T;@U_zV#>t8g-gStMvALc)pvGFpv6w{8h*7a zuQFH_BbiS50@z5yI8KvMSFhOb{yMl~*-?Lma?mf^I9+J5KmdqpTi{%<IoEjj=LelGj0fdAEY_qg(>z< z$;EPOXqNVmi-NSMbt8~p{61C5D0%s)HJZeGcaDY$4h~uYSFJ#W6Qlcy_|&DvhlQ;YD3MTebJ_ZGQGS~4ypL&VZ~Jnc@VOYV-r#% zx`wc$y0t!2$$X*aXxx99gGzG@VjPC1r(0Z~`b~epg6q`Wm<<#y*&_^UzgSyde=BIZ zM|{?dr<=VD*0!86ctO@Q#UbE`am~6ekv?8E>(f7vU1k?$24#4}1^o+&NSGm268^Ag zr;`x@MB8uz;!fxr>`wfhq>o*sw+DIj;-hdSq_1`&z$yMrTCw(R#X)k+%%U(0P!&uA z5KkC14*cu5X8;q%vtgLGhFYy`Z@Bjn7()wQdhJVho=+9@#B{HkY$LN-84G%8{P*sA zGRN=2j^C$`iA;3-G&sm9wBiYfT;^}1m|os#&U;kz+RMJYowa$V8g!?{)G*RqwK8`s z6qIB1;g7+x+q{1Sn`&!A3g3J5j|Cw0-F>Ck=ALehHo#@MTlp#M0Vez(LGTL6hvyej zNxRq;2tJv%G$M}~WUeSonV=#TtU^=j#5h9e2GkvMn9%Uq+HyjetK2Ksz4iqr#AjA=>uiVixszm_ zG+M4l$G6M-wsyRwS;QtH^0Y3JxjUXw4v^`K;W?EnxO<^}nR}avUY%BuP6HKeNR*iB zryEZZhI~UhepNawt6TO($j%u=4${|0b#PhjM67Y(8+RZ4{bqO{Cv5GfuW5P%+#8>Y z$67YcwCRPyUR)*>NWdYq2X$CjM8yw#N@U`R&O!IfbkW6btc?l`!al9((CWqeWkCjC zeJij>n*1j?Ux7;Jf-omHed=Vq;A=zq5Tw%-{Hl{%18WZKZVsQX;Iz-I#%wlIlw$nM z$$}ckCr(#q*6;TfMr=4O`o(+!B$C1zThC)F$4GhheUxNLKGRe*FWn`yy?Q}gKu=rM zzk03n6n~)AJxs)};&-lBA~i+@bAPnETPR|q|Ma#vlrzjP)BL7|ZjHbN(nPJ3X)+ZK zTtowvr}&g%JbLS;W9$5A_@g(Q@#zOrG&RTc#@dXddEu9%tbBG2nmmtHz>Mhe7{eB6 z!aE}c1Wk{V@_1*qVVG9Qr9eZ-Yez)O@|#vX`+4Y3D+jrePXc@5 zLLZ`y7!PuO1D0`!%QWvirPYvAX&}{T_>1S-B12F)Vp*pK4K^N0iSyS`l{_=LJQa$h z%SsmFZ;5TT6_yRj?8x9-pjrPoE!Zer|KcmpycXVYJoa@roef7_9d;M0yi-VhqNZ2{ za;#`rwN5{~O5S8{4bE&3msJjZa-5{i5|cGc-nnHID?%bwzqjd9^~s$`hxc$A2OrmJ z7Zne61#Y{GUb}o~?##G*_VYPdG?_*4P4JO<%I$8n#?0y~7c!xiYoDN43lu5IhBUX$!5v=U0VOEs^ESj->{T2dMETQ35rxoXX<^ z5bXHsemYb45xpS{`QnqN?AzE;UxsVn2y)`_C6PjOkRFHjZQN$EX1y6!tBa4Yx42Xr zkJbe`UTpyz)y$;Z2s_=zGI^$Qczsr~)%jwn&-S_%-ofd5iZrRN$WzeQD~djCl_pe( z6fD_L;o<|DJen?y1W%(8sCft)MCS>o>#QPRUGUWvvI) zc$R+bqD~b?9Vxu?98bNtODl=HqC|vR1A*z|@7@gFSJq}*ppk~a!)r!Oi-7?9hU~^u zmJeauCeC~UmT{n*UISUbudn({BOWOB5DB6?C5Bjy^HSl_blTIEIMx+(z2dVI6K-)E zysb;b+5(T`=>O5?>&}_P4YX$s2uS84n^tBfQvz3(8OdtoW zCp$To=3-z*3aaC_1|0Haw5FGKbUEU1jl)U0Y#B!(4w8s+>Zr1xoVqzcajHDjLiC`k zq=7q_7|0Z;N2c^#8)5G;>VwuCH=P-~{Q~ADqk8$JJf%K-xVc6N_D1W+rW+ep0EuQ$ zQ`N*ZjnGMek5i+hi|p+Fw~y=aV?Q=0h&W_*EL{&B1+zAnCWskD43T8=wCN ziu0R^8AD+y1X|^&fDx#B=@aIBh>R|E>Me<6bG|Vi;~;LPTyh!gM{S}nOnUsnzdpjv z1d(b!+)Jb_1YySch3JT1$F|f4crE=vEp#(`A*5$Difk0x{P+FT)e2wC9@iPXrYXOp z%)ol{20se85t%9rgaJ#1vWmXh>~@j*n_~X=@ybM;DC0cDd;=VhDF+_6pY6Y#5RC1} zc7#mCPy(kCJ2rP6I|m7ZG}}_#7e5KF2Pd7ye3q6f0lv}3d+Gkay@17Ic+)#rj9&BS ziE`Ynzf;%S77UMlX=&s~dL)e7#XUc=<0$X4>s-qW&tHxzr zc@1pw%Qr55%vD<&I~UPNxoMXWg;kg!(mDR-+xKfmo8JNiR3Ejy6De5kghrg*vz=&f z(G_cB%g(HjO7-N=0FA|{a&V4qv88zSI)Ah7yWd)o+SJ8@T|z9oj;hp(;p_uUto_Qh zO+R{w%56CFQWjCA0bD+*5?58eFo$_FaX5`6T(@hf6=yq{r8x^CnZ&d&4`%ca2TO=K z!qbskB(;{>)N6JLNUDFXvk$Zahq52`!p`8q6DLpIX!lY%poLy8;U>t*^yQAQ>(Pu9 zUou=R_wFf$e&5ihjIHJ9fII2-XRV7E-VXfZP^pa$FK~!*aC4r9o)QX6Ns{J(>dlBL zb#&P^U@!swyUiQaFhVOOzDD`I`+&f95jPV0%u}?QAhw~kTmj_ab0!*qP$)sn-;b+* zttJI-(VN@^f)*EkhrheTymo-0`fOOK0HQ?r# z#79i@w9)8fz_E%lzFc#gRn%VNzeDfhYzAqkpG=GZE&rY^@Cs_e@bRr+@jCbxKQ?cF zmW1mIyhz)SQmJtH*hq_!-f^JV4w4vGJF&@RUK4JM$(Vl>sAV0$7A^iEernQgVJPh@ z*BQh$49o$88J6*6j`E8!Km1|fZ)dgC<0yAMz>-k6L?ISh1jbdgX?tT6oh4REeb)Tz z{QUBOD&Lz#i#%xp)%yH#vnvxMeOQ8+y#Q$YkYCVTVTwzpU_gIa7};kDzm33u$1ILa;-ROA&+8iPG)RvLs+J#p-= z*@EMC!#ntd6Ke^x3_dm=+P6{O`(p*rolX_SN7-mAEEq-6CbGzK3G&aPBJJr7qNb-K zq8qQ->gU_;#<^E@t0?)NYwx{2pO5EHVrL+^CHojp&Zk!M8+lU2*s}8`Akv#|(*rvV zmoAtW*~G0K4@VT8A!?5|jVCUHkL$TZvmn?df3v0_h>#B`)DM2|X{= z^^R!!)xQ(b_L`^BW5n7Q%XZI^(}t77H#x>56$5GY~A_whAOBQ!@b}<{?`Zc2?)Fqv`8>iBu4UCCx z3jY(&@{-&+T3NwRAYq27gEY0~ce}M>8-_!dsSD?hUD;PGr8CCah@OZ5uh?T*#J7PT zlf+#y7OXRG&A|~|=P^W1p8|ld(DA9JVnjk;?s&CTBR0`Oe^&{N|CmLws*%&yDIoHx zNZm>ML=UA9rg+Vu{!3`@0|r+G>t7qt2kHvqb}hF#PWvkR75d6EgI_{)d(HB zKCZf}im~Hgt*5f%6l-jVwbrCNN30BTucj&(kH%@{sm)ww>FXfa>m~vag$s#PdhgIv zTBFEP;zUBSAvYP+*zNWKnJgpar1b68ALJxr(X+*$8=q5LDl>Wlj2HM{FN0q*vMDue zNLntUMA9t7FPyxQ7rvd=;I-NEX;SghrDml$41h64ni@ugT_oJT&$QK=pB<2*oQRbOzXFI^uWGL#HWjm zdt3d%Hu)zC?8z|3@=23a!nSjYD6!lextE0}IQbQ)BKDM_q_+M?@`B_fdd%EArZ?Jd ztc4gx_T~+%cV))R>MOB)PrbU1w-QfC5S=PB2jaL)p3vcb`xYzkNbh@p;R@zd3t6s) zz~J~*9!@AYlQzd%C2ZtA{a3nlgwb@LSgv}K_Q+N@rNC8Ozp?miN<*6!TD9qynwHr35!-uUUg?PJ5Nbc93f|JE72xi< z?!^nlhT68bdGk-e$28NiKTCdDw+qK-SI_>A=srD8G)ae;*G{!j?EEM&lluJRUO(4f zN2u|kIkU+-fjU*CZmVxhV){nTH|A*E8|6?Vy^-L}U-v?*Pq^Daoc4!&l%5a2hGHk; zoMI$l)Z|gXPx14IusJVtap?2=Kn${Pu zMAb#mbG^FLa2AfjEXUR=uTyhv#tI{afcoOPC%&$Xe`C|()xSV9ZycwIS)`N;_LmS@v3V5^iP-#-GK# z7?>5R!pM%FBkaYRlg3;OP<)L;Kx37!ErJf(aFhZ08W& zw{Tk9(^@VV7#^g-N_PX?Vy|eYc?#QzQ}7wNkWS+*iVr)3=qRDCW4n@`OdREMo8$YAYUo*Aa>bDdVd zV0r@^3u-NGZe5ZO?E@2=hpWj{9OqOW(&PGDh01V$Z4#3{fsi|z%r0l~Gk-`xDtwzF ztT!)wL4Nt}nfEJmsaO6m%9SL;uU?1u9!oGapgvt*i9APiuBO4+ji%~bOx7|lPa={Y zD`Ryn&iDK*g-JrSZz6KFML|hwMIrPW53uWi^X(VJXeZ+(cx#THz=sE)uBpp3^S)EhdwE9o5*ZF2R>ZI$W%?+?-}rX z7sU`!-+^y^ui)H1e~cj`rBTF;Wyl}Ehgs+lUOoh4Ao2I_ity*-kxji28XNhBWyu}h zUfXz_wyW16^QmBt)JNH#eI$;qmQlZ>d``vS=yKX7?5dp4;%d{F;y@ky;i> zoY{CtpG`P3tb@{qa3S`6iPQXv4ll_R$^56qRzBKNJlqbc#P|?X_DZLX#nM3Yz8vkuT(+Y$x6=Xq+3_h)M|Sqn zS(tsm7+dZA@bh0lXL|0pq|TEx_{*)EU;6N)5sPB2Apsb|?KAxMzCcfaWn@zTIVgZh z<<=7#x5AKpNZBkKbZZs_&a<J&Tl|_?UD=*TLaO4s6?{#?95xSl`)=)a5X$n~ z5f_%6Nw;c;)n$1Q77Y7|m)<&PDj&+RIuuvd8p8$rXXR&@$0PZY$L!b*?maH?)rx|E zZuaHT3Uc^2fZq;DZb2nsq5U{a_uR#q$+`zNr4Xi)I!$$oBU%lhz9){sDM$n3M-I9!AY$uA_0Q&SkXC@DPk_(g-XdSC3xE29O`F zQ`_JEr67?pEQX8lgiRwP;&Msy@m-LcS+1n`20<#!JO`Zx&3=Si!@c7Z`B@L0Xj5smC zH3n@UiuERUmFvmg6~ zxN?(i!RykpLkJY_DZ)e99@sw(e2tEpS--{f)(FsUff`s*&9oC*A^^d%dWO=U=Jw49 z$kF0NuD%W21d&POM@v4k_{AhP^R)7i`iQ{}J#I@?B>p zwgZ14y0+B>M86(!!Rj{Q|kGH3Dn;I*xr zAtT9TG9Ij3Yha6p5#aT=-i6CL?-IwL>jA(aHfc)JJ$0EEnG@(#wUXo2D@gjoSQ@@O z2d>kb=_l{I+~vbQe&Cg38*2HqLBdi7I3pSASp@_S%BkalPQPrDFpVpVRpOG+cFrMs zVbKly)`>N`hfmC~)%mL!*{U7%jHyuv_tVMtzQJl<)lniH zvt*s=2OBP{NUQJ8Sht+qqA4p+Ux0R<9tc8)>C=>*wW!MKpYrtK!b`3qzeO)uh- ztE{-tshT=I%yA43;MtMZW`g#v(iOIP9QHd?@lN)z=bSY-D;ti~NC}HlMh3KXbQ7G@ zEi$VH4fVf)?6rkY*hbiNo3#>0N@5(-IJw+61KABrVs`4sn-y8-c=X-~6*hT5-A8sj zZB553zkI3JB{Zk|#8+vH{k;4IPwiifJ`M}W9c}FYt2F4DRGR+z0W5&&m<^Oq<4C;5EhfvBH8Z>8`op?n>MiT9-ag zqQ(6bd3mfYxnU8FjBj;J7-k2(kvqRROt9y0l|%%8JFA9hS3a^>0(e#Nu7>xF>W`+` zXGP*(Cv0V+mjGwC`!lvNMe$m(iZntNad?+7Esv38#l^vrRZH)B)FCDEl*z(VIH&ZX zOOuH#OW9S=%laUlQxS%M@9-y~ADU zy$w$x9>&-vw|lA6GP^mfAPe=72U^IER{9w88ke5nYvqi-QNAkZo8u$VWng81G)|Ey~H;Mi|%EO2LOB<55D8%An#UM@H1b3TV)$=(aP3&HTwsaR{YeXr*u| zd_m~i8u=%1oAXOst922Hj)a{e$I$Ql;1#U@&eEnT1u9ELbOYFy4gVavA(`!*rLWG8 z-XJO1PFCW>>eN|3J=(xM!?Tv=HWz=N!*B`>v%Bn~&-RX^0v-WFivVR=_pc@ec+6h_}hnA0WQMh;z!oq}_*hL%?FG{wuq%i&p1KSM(To zI(z${2byVw)usRjD0ATK* z-M((Z+LFkjHvh-fMVNXQ47pSj-FD_GdB0^iB|06^r{^}cM+gpOz>808K6yhqlqp{Z zH(utCaHmZtylymHEN8y1UKQKIrA7YOF0{J8p3Emu4EgcV7YlDYI@aorL%B9=rt}H% zeWEqIZH2sjR8BX(ut(>?fCz)n0SZMZ^Aqf#Z!(Mo5PtqE)FZXGa(wh$T6g%=oMN(ao-3driK&xC+OsOb6SJ(?4uIF&BHA4&n$>pe;o(Ksv+WXQhCZ0Cto*K z_(bd>;x!#yBhi+q|HdCbeu~6+BZ03ojIOk%x}Vvi4H7-k&|cB&A|)E9^`I`}~P~nxc$iY03r#V3D`Cv~I^z1LsK-MO47?oJNxhGaLVPgWiW&q&~C+pVN zeRHYNm-3{ReF&Pv8M>AZX7@2)8>)M(ct2(Amd(DJ@5qdd<@D+-b&$h)oy-9_Hw^QP zSWwBPXjpB$4K?&-(ogPD0KXOnZw!!Ni&vK^t}+v?$CfpTo|vMv*kB8zA0`=DNNcxo zRD6)Ya71qtQtel^j#PJA66VihJ^jj;6@&S<)GsH06OXe`mmSCWBz(7Y59CM>$tTgs zcs)87lr22&%X9X~@HfwXZ{fKbspW=z4^qbM4Ax)8IFCN{B8y{B(C+M7cg~@8H5=~=(|aF{;8)T+?Tax6ri1h&2vx}C1W~O0U>?;9_wJgs_Y|7 zG!#|gh^lnuT!=;%?fUd|M!CaiIbT$hwdX!wDjRC>v5&X=u1b|PC-oE3w@_he%c67~o#OtReXgAvtoOc<+{dddMNT}fHC7m$m zrIBUR(9crQ9I;>J-wUiW4lMs`dfy}_I6YA*)t|e|?$JlxFm&kC6>WTlDdNq?uoHZdyw9^rXp>>SuqIIi`cSYMB zW9!7k(8Z-BHhgwxiB8vyQyZhZGW(*@t?m^H>^bd-BK5L*$N1yd-fj5i)g7PcH#j#Q$VcT{wDsUoXXjy=9g`%UL+-N(qUB9X7 z|Ml+o*ygpIChFEnJeDg^@?(JoX<_kCy%ta6eucYyEq6LYBMvQ@7rhorV`958ttx#G zIz?L-40mm^uhLQJ^#VJs^@Ua2EZ>4T3YvHbd0SV2h)l>v+JJkx>$j}gwSkHf5fsl1 zO$OY?F5p8=N$c4Y%zs&O&NlVBNs#=j-$6bsE+t(fA#+f!N%@(!s3k3wBfD4I`|^5I zs{&bfVJ35K*L`l|Ia(&Sz)NOhs$pblont{nxUI|B{nU0_3gL;Eyqf{_TavPBWhe*L z*FwrS=mv&%dA&ujim`gRe8^D|_^MY6*o;sn02n{d{wQfYU+A!^L9;apSdtnQ)iV4^ z+Cp+wh1+~vJ?S$^Ng{3R^kC=eMJ?Ox0lQhO+$`v7ly)+4>(ayEW|WqAPrH+7^lt9Z zXNsS?p=+@sJ*nZd+!YWC$HKw00-L+gDgvNmx^BEV5|+ zvH#s*=)qQz{P#NhUv2NO?L(-4vK#XFI%gX$hSVgB+h`N(_em3$T*W(>bP6Z*m%d^U?MaBF-$!Ke zn%VW`Bn6O5UD>*?$2#DJKZHK*vzb82ZoVS;P zgS1wdLA$3fkX9MH3)ATXHQ`=;!>I{gU20=l*~===Pfb_456ac=T>kc99c(8P)4-{5 z@=ZJzKt)LLHuXEqYqYzspQQeCZE5H^;dx1+=Us~L-@k;vJO6^S1Nam2#44tqJ~_`J zJ{6at2nbpTU%>wbztkK zFTMXlU?=EKB1wGTQSyD=%HOJsaf$4x;*VL{oium))|WWDPhKY|;%$uvz)PW|;!&il zya(@E&%*^tH^>fOX=iQuUm+iUDgq4IWL5I_UZS4jK!3p;RxeFo$C&!QOj3@iZaB~$ zAxUI=|Jcwoi%HBMc>itxw4&AZH~4-A`r6v~em?A&qxj~@$dAy5xA{mEHzHkSYMioA z6EQlnq$lE!voeWYpy_4{%#+|^WVtL!TukA)H`8`-S-TInVq#3NIFod+OVqaH`F?iW zJKeTfU12!j-Q!!BGWX=RK!OjyhxZJR4`!mPoW#4ny)9&to5F*?%d$JZ5d8_z+!l@8 zX*A)JgEuk3u{8EIDIo`B0#%v@wd1-G*AgQX`%xN%rZC>%eetHx6okq_COkM<94=rpsF$^6tw{+nflTlf0HO!i0ZsN(Cg zjW|<@;=PK{1wG&g9e}iQ2j6zwG`Lq0Cnc zi=sb;@R+qiH zj*#6ay^=<#W2Aq*$vda*HTe1r*`UE*~p_S4|+X)h)pfE5}JsX*{qt`qOMIC;bOEEZRAb< zYE9%dgcE+NW%cW<;OZh6ykzSfMtw3lVOz7r+Z%D4_)W$@#BdF&CxT6k$aO8oi67nI z_I_^j#3l#o0BIfq&bV|WgXkf2^KR0yX395t6pIUj_Eu}uB>y>}fN&*6mAJ$vNs zlqVeRL4)<~!3u+nqrv!Oasj$lqiWQh+RV(p%2?4lws*Zy`84hco`IO=H-obSe;__; zbrF!EX`lJEj!G!vj)b`#?*0GhKfl;y zy?n>lp5YS-(#s^GcC?%EsmUak0DcuFtkapN%9dl=MP7$^7&En9&3G{mtpt3cHgJ#A zmPna~d^fS~1XgQ8Ba!P`=1A7AusE>qGS02_R? ztuVxH3Sz$5~5G(T%^?eW=i)>Q)e5+tJaB1J9Vqr$LM z=Y$&RZRW+LEAsUi>Dk6ggBZOZ{g0!!v*7fkGFtoGX~pH^|%Mbm?51p`tE@AySX$zu5Z9usDL|&jbtZ5Zv8$ zu@HjW;_j}2EbcJD1A!2nh2ZY)9wfNC6P)1g+`jMs?!(=4U#4oNr>Cc9x}UBt`BhMB zh}ZN?B{k4`f118lH>owWMXH@!Y*JnZ(N!wseJ@jQ#b43r5@45fE4zB}F1q4lO%WwY z)$IiUa>_hV9GWL(;nCIpo8m0gxl3ZSnQ9G9n_4p+`RP7>X(IfI`>vP2z zqU;+)`r4xU7nZuA#T&yjffV=qouR>V+O+t98ZbaD@2a={)eb9bb^9E{dik78@(Wx@ zRU%j3#GSQT1MU|KodN_e3ntqJ&dMEf2wN;9w;qBV0S*ft-6b^q%)+E*igxfJh%-FU zN`HEc!H24sWPY21>qBI=@qG8;B<)>w5vzUWQF>h()5_$qd|^|t=)%^IpN?H)mHWT1 zl^cP#tsd_W-lZshGp|jYJch5^4^`+t)PA%ybRPAo4wfV5CQ=%kq#wUS{w~yGF`nGH zUc3}%(Gai}1Ywxf-C5s5h6Fay8ABRn?@SdLQal5=M~+=&VLAG~a&lEBSyDUZdUgC2Hu+ub z>s8|AuEmZq0p4tfJ_w#vd8gf8x5--ZaXa?bkh!dhw}lspa#t^@7&TLJN6or4jkA=Y zO6HM7G@c-wXsNOl-vN7Fe%{BYNIaAC{t~EG;8(H^4R*;&MFo`8I?z}doQC5jye3dv zA>P$j`BGX6IbA0+b9%5i4#UtFC|>&PV36G7sUTTq-2(pI&R-yK(-vO8BBUNX#+5D?(iyT3@rZ{beo6y28!35Jtb!`tw_~=j!}341m#2HdrSdLh;qVElH8R z zCqjHe-2WHyNxJ`X1)I>1RbPMU=daUoMXaVo`_1=@GOvMVq+DEB4X>1E`szpNs64$R zhs;?myJQ9UaZHi(eRnopVz1PHO&s&YULQMO&w&@OdY)Ie=To37^WBT|Q~c{wzRry z#;M6W<%Spd_~+^MS6F4FrJKF=*FDPfz1nxOB}y+(|7kH(V&D~q+VS-G8t}?*?JZwX zA1$J%YRTPsh|L^%w2rSg#Q6joNN3ZQX1ItmHaL386KV%izoARta7LEp>FiV2iReW{ zmi6l>*C+0(Ed4I!ADnuU_x0LbFmH=TY@hmQ?CM!6cCcSbLEm_&oGJnIk(d{sFy}28 z)6a11C?^r_?|JuCc(&wm*`wa2qQF}^K2)L%s-xgUEW35RwVGgHT zG_i})=I}-NpCpnCn_`Z9ZuChH+A02SaZk|RZ>+G zoDZ7&*9Mx(5FL8I zlsZ#Dl4|3#Ys|-4IGoG}RaSO=O~em%LBkt>!M`RRvQuRcz8+^~MqDiQL)F>Sh7sBM zNBy}+qyR;U$y4J`*%cEOWs9oZaaYj3c_LRn?x)7WzKGQ?iN4cQOCR5C0yBtg z`H8L2r|O;r+!c($Kw)i#KAT}O>R6V$yjIpx)8VhnY%ghv-AUD3c8Vv^Ac#q^n^PPh z#@wVsXNyyAB^O3CA}WD+#fW&DB(bxei9b~%;W!Phpg2drZmHMD4^P1;-r`r!J=qB$ zZSq9V=rP)wvROLM@E|@klJh|c#moOcd!8|dD#x&jws88Q30fNayq0rrU>C{p-*ZycNw@Ln+ImVW1q!``F3nh2$^?VH z!IP^!VFZGZ(HQpqVbRuDR=p%Sty3fj^UZzRiWjI2AvZ}i<4FV#i)U5Li>1JEDC!oM zI4|kq>d#arNeOlZbe8t_3WudVG_&DW1Ba}p11Gig9yf><-upc)sp#Xfe>Q;y0Vhs` z&t#N0oCv6K3PlUBlU95_1vA7A-a|Q4wWT zymM>bF>aqzZc`QOwPt}@Ok4^m)jDMV&8`NUb0}?PNquW zi?~C#i5+xTp2S|V&Pw03vM=M(U)=)wH!(bc?QnU-jNj)^m-wU@8xbHff#`ErMNMvl zin8#%lX^nU6}4OjK&gr_RgEgTQBqML{cv3cF5FVE3yuMCSnM@T;d{qQ>OYJ)=QEp2 zcD36}mSJFGAVAdA)(fJ05ik(I94}C14RT@o_sOxgC4W*rtHzUvnf5R1=$E}Hbhk@b zvEHw1)6~&6qetdhP69F@V~m(Y{bHfeNZwE-&D3GJ;=sY~#OnKXB;pKpB+rL<2^(yp z!|c28S<Qt6RIP?jlE@by0Xfh?jFNz8aIw=f!edL7Wuh{;p#CBQU4e^cF!yf|H&Fc zkTX_wC3MUuz(d$N|7aP3De5|W8CB98C1o!4o^<)yXApYtg4RgS&d(s5#&hW4$qgYF zuBEABN01tBSqhSW-}_0cFnynStbCAAew+E%mcM8fa6mqH5NkZmG;1hPFRQD!-Pp20 zm)yZ0Mc1{)pg+h!rHXi34{R(HN?x|d6zHJdgOD(cUcqK1vWD8k6@IxG83tT7C7q54 zaxQJslnw7M%6oJtqw**ebYm+gWYlsGatoc)5eN(82Jh zWmQU>0r_HuBKt;y@?GWQ1)!G2NK)7CQEJX4SlRK~afnju`zP10{2raxIis5_Y00~7 z)T$#SV=#Hak&+!;QjNz1FkKoQT>Vj zleUfFw+fkQ#jl5R3aH$zvYZ5@xK4y0X~lc`UGd_R2@Cx_as}C{HbHzvkiFUKYa!8aZ zW?o`d1Mdm#?UZ^D-7T1vNS8C;T7?Q2L!mtF&?zM_XSNFO`bJz{Ogv&-w??O#yyRH-zSeC zRA=QDV9KBg@Z{Ewk1QD<0{$p4G;Q+wPaI|2aRN|GutXRd<*u_yP=m-9X#MGA0*_P7 zVY@6ZbFHuDsZzl)rg1xsXmKJ*8nP!UPk|J$?o(I@$$Ny_TfIp|4=!+@Lv^s!Z+sU{ zM<(3_q*aTgpcz~y?m2J&BsTrdPY0kx$m6z0PpWzL1iT14eh~XU4GZmy8_$w(;$8i(-KJ!^!oNCIm>9ojwUw%W#cD8 zeU0oZSTEL8!6zM5BersT=ztGK) z<6dB~)3)XXuCpG?a4ba^Pp;yxim;Qus3EiOANguCgNMUM%{sVIx8K@9nx0c+f6y8~ z+7IS4?mQaFI*f{jj^t$MM!QYuKcZJe{#Qho{I+3kBn}*0+G5k>Zk(N3E>$XMP>WmB zNf#W)-;eb2gmq3C?{5_yqVO$?gy{Rmi$HHe2pQ#W=_tJ9ozgWsj38(gT$lJyN9mB%CS?tVVp(pCtG3`i zGk7;hI+$BO6CPp^$69-+Ld+)OF$*ZeN;kHeAbgrmeB!KrOK>P7`VY)^f-(I;Amo;l zO9Lx=3I|&Ez9>XE@%c#awz`~=k?F`<)D9#=J77pTGhU3@^@NQUDv##h(JXK-kjJ-Q zR{!sI9^p@lK>Fxp+J$wGh=_L>Q zx=Cj^%q59SQF&|Vb(H~TtA+J>#;WNF6dbBLRLU7^J!r=vNtx6jnjOg1Tqba9jneeK z(owz8G{{(f|1MtpnO@G%sH6Y#8ZY~M(foF86U^pNUBN?dqrhcVR4$Miozh2QrYz^? zeG3CJE{DlNEt?C9S{HI6%0QQ6+?2x8+5p&xh3$qx^daV*;1(jQ_PTdk2I{|xPixAjPM;P!Uq@mYxuxRhT`0Ww4ey1 zG1%oMOcQC-%&g7$EA1)D!eL3oUMzM)0tQc}Ou;oC;VAOL8z$de*eaEqj= z*u}_1X(I`KZ}#A(9HA=DL!#DncqR=65MYDOT4%O+cfR!&hWAg`=ZY zB&;mSikj!W6D#{E;(TkrxEJpov!mcLS7%JLkAXcf*!zzD6^DxBen8D`y{*Fz2*Hth z&F2iE4CG1kla5pl=?9YK$lGrWyYwy0EuWlFHHoujnC>YN76bVWe(^$>1UfTaTnc2w@^*z z`E~Q7DVq@>zS`w_W@&{vJY(2RoSOOYy$*d1)n!$?%*A|`raAG_pH2q33X}Zujo6nd zz!+Ct>pIfb7f$dNh29URARfmZ*ro+nH}8&g zYN*sEit+Y%c9#|HQnmyAS^z2^v1*$7OWqD$K<^Xwp10 zxzr5QjT1+d@)=ykuCu-Chnj*hMZ4>_Cs1C82NWx^06xC;o^PJQluGmONo%$^(<{;O z9JbIAn!^+A{plCT_0o$Q6iKHSKlzD;LU^^cZ{mcKXnR+1wRQ|es=7Hk1|5#OkXoB! zp-X`n9?oeq3B{1A@5{Ci8K+g>$MQsu{-%wXik`rDTNp1>aO)UubTt54=!*sq{~`Bq zrUhW?lP*$N5y+2F&OY;XiB%w=3&lNSWtI0y(VBv+NZKAKhO?kBEE+wo+hG;0Un*fKHy|OcKMPi8~^5B+vhSSLry}o@2~?S`6JLY!1d2PzDPSu}d4mBl9`qWHdv&ZjO9`MJHDM+%EuMccn*ure zR8EYjp(~76qOj{f!65mRrh-~{fQMxyc+qngYRX2>2NOvW`qpI;@>2Tn0?>9b%&|J_ z=D1ojw_bPFeeOhN&UE8@>ua4AGpFt!`9bXO+p=`HN!Y8MN{?Z@U=9VvOm@8zZhZww zc5xjgd?cSK>{qbO3xS&(eQbsTzeFiD7Esrp+TZatym7KFrdDZQQ}ZwqxZcJ5Yt89wtK?5!Z)mKFAiNaQVTpv#M@v%7Me6nJ;JHh)QCJ?F-v9H!Z-iWmt;& zfSC!g$7|k$uJcj>$~1d6KbxF%mEvJrM`^s*%6c^*=c%)QUiFRUXUKle?PNl_Hp;1{ zmDYjax{Md2F8l-dx?>iJS1=mOMxQjB$6VFM{tP#TH)`_Z{&syKpc5e_^!iDAUG6%q z#_{J9@*+Qn?69xoOxi3}(;>3fYFVXgQpR=iDVqkv;fM90v{!f9%cn|GXal|H>pvOm zxLD9@DKGQo8{(X@mprh{kbnNyjzO5hPIp+)D)!P5ECaVS_n0@w4XDDyN0B<8}Rb3NTAY!kon9g_5Bt5Wx&;+?palgX}XMt(*ehh;YWi;B#EOqdQ z^0Fj_lf~Lpn++4KSi5lTbb>CLNH8Ok)~=rMPb^4H=RpNP7e(CNq-wlu^l4>bJlz|oH1tn@mOZ?cc?56cR)=N0T*5a@+{l({2KHhu zP8$0APjhxel`P*caSSk9Jo7o^lNneDcRj%CI1pjzEb|LFl*W z&x}i3DxqTmNGO>4ymptf1hs9&A3`1`_Wc6;MY7yFQ+fAT%c8v7w(c=4bQxzxhZT$X+|MqxP|qPi3C#$C}vm2enBKT6=?_hJL%EtKo0L5ai8k zq{-{l))4dn#cKB1EjHJSvI256H0LzUnEp3RGNhxcfHBSHRubr0`5!%(vT(M`$$Eh( zVkk%9jF=|Agy&D$>(8Vr`M9E*b<#Z2mS>$&$ja=|~b=fg%3*%rC2A)tc z=G8fJ0JY+;aXSRh>>F|FiH6)`^L*8cunjCO4!WwHpvrIQHJYuVmH%F!H%-1L+X^oWUdZBl6YuWeJp@nM(tRqks7zqt_hZJL{4kW=cubTko_>3Cs*qV09jmOsz7ST|1- z&n==o;BY460ov)!e0|iW_wQw6p-=R-4aDka9qP@=3K^~;OIxnb`Oz%0wKj7l*9{pE zA;+VV5MqC378l{sSwHrhgZVX?>92TYA%V6A_RYU^ZAmhNWc^lTeY?WLb7~a)8Yj_# zGhdpSz>vqhb%qaRwzrR-fa5Fm8_o(YM8&|Jw-(kLZG+$o0wWYXLc!j&BLg(23>P*u zKJ&->cW7s4yzxfZ(5_^{j2@YrpNWA2oHJOtCoQ+J6^Xk=vIScgjqi?tY{Bg z)bfLr(Qh26KcxS*@x?VKPrao_R%UZ)V8jv=SkxEfFQT~*UeY-Wf|~B;XS7Rv-YNH=>`U z9}tf}noz*KN;;5u%yUE{SvhSKz`(cmBMELaPB~f`Q@;M3PF=-FmC7Q2lW5wc6U*_= z&f*&MK}De}Hgdfqf&qcdB zUW;=C6=nX$^Z8WIgz}p{h47B&sBUPJ}&$nBRD*wxevRWMaP>dy%3ZibY)=r4jLKHJl+&Ha?&)5g<+8SuSiw8--n^t>YQ4E*Pb^jw*hqmnj(MRu%+F8~|aK0A}C8O|-(Cig2> z9f4(OI__$Z+Cf==eD_cMyq!Yc@~h;%ys;Tg^J<|ye6_G&g9!VzKwk-$Q%CeZJ@@n9mPWaVN5hr-Y8Dh0mR`%Z~>%Dk?HKdRSk?j$cKWUMNQEVq*{-4ILAH z(Qg>-y7JV%O9$w&-3t?DiF)rr!XMO2eexNi#>{utW)vJ7y*+jVBgmfQ4SckizHA2Q zs-#dHspadGFd`O;XQJ5WTcZR!ElJl?`AJn(pnMv z+u^rJ`Z5z%x9yEXXZCV!Rt(G!)Q*=m1e(|Mb#!D5ECG56*Ldo~DzHE9G)UKbo5H`u z561>`h3sgMFGCu9h+uP1lMQ<$iGh()VOzI~5#n9zUCGaEu${Ndw?FjrI z#lpsUU2fAnWQ`9x$&z$6-JBFFn=YJ3@5}ISD&nY-YBC*|OV$2BV~PG?#uhC2^N}x% zTPiv%Zvm44hy`tvY56dr>iG!|d+%Y9<-qwzAc^sKDMi1YWIn0TF3iv|Yg%V28+_pQ zsy#{Qz!+)OO!L@jNkSBR{>6h6>{$Z|#EIC!!e}fJIQ{(j>3(nhp~ZDz&AFA+jqc&} z0lN2ZZQz5+t;|i#UCV9eE27Emi}R04Uq)BIRbb6Ft#X-m*>6<@y7g~ukj$jf6Fsfc zm-f!^I-kK&WB5uN~jIT%n1bT~wer%PtPzX_YP ze%SkXknElAFb*GWT@d;LUvqHf3SWbGjez^{8|!ZuOR=^@;>KYHjjteEU8T#p*rkx8 zATxe|cgut482A0IM&5&XTTP9_C$Yr+q0teA%*bVz5{kb+{dwkg z>#(oeLCOx^qsxF8;#N504co{!FO#72p4wR7LwaDLv0Hy$b!NHv`OINC-Y3H-7Pb`$ z8rtPzB_^NjjjIMZ-E|<)s(Yr0rmxah)vy77?M4sr!PaE4TqU;jBVL6OA(J)w#pmw4 zJJDmvujvJNq#6w6-5TrYBrQpZ>hveLMQ6pS2^nz8V zv>G0-pz-7N!)kPr0!l-=Xi+9+!gH-5OCub;ZHrl{4F#Jt*vCo-s4x4^25FC-s^*&Dvy5JB0rbG=LV;R!8$bH%@L_ancma1y@S)d z0sd{yPzj5@(NlD8oUju2?ec{Kwx~=?{DG=O*l`G6dSGVs(K3wJMqM_ny`IMm-OB{X z734TBKm0|Ifm{2~8td_^D;?arF7O{B)=a9LC9o8R*A#E3at9-^#53I(j82qFA|nF7 zKAkRuesuxSV384zU!Nd#umVP2tUFRhC)!$3rgg!OmLj;^JD{GoIjuc zg2C#@$5fQ}?fp6bwp7RgJ6Mh6yQ!P}Nw~)pC^*fOm zKe;)vZCcACC8?-|$S*7u(dDoSK6y;Lq|1Ar&on0>x~KCR zYD!U0Pp_#2Gq@G~woKgkf()-N_>!Gu8uVUsWFL2+!YO4V#G)4qwg2eh_pix~wKBM8 zc1v#ASCv$B(7%C)eWjI1Czw72xRK`LMt60nWh|p*N88L|)fj}$(rW5{Ct06nbww`;rLR&7J}681lz$WCy62G=uMu(O z6H;$AIjE1!h>VDX9$$uZ(oUmCOwZ|^AV$P@W0PAQyo7q&@X(2eeLYd&FVc6c8^`j} z(W0JquZpvb3Q#WQtu_OJ#qQiQ#6q20YjzVgTzIxkIPK6Ye(G7b(yz4^b%>*aS>He= z282xfu3n%u9`qsMDhr98y_5moVApu|#@OMWJS=;4fb-b!K7H(^WQ0`Jr%)g{p^J-q|me z{T*x0h=0-{pTU+2TKM5a^SEWZ{b%K3OH}r^B^B;B>`Hn0akJ1RZC>b5mqN`_$7s|X zqq#{(<&3Uwtb4Dn-Zdj)r#Ld9{7WA1tw3^CgO2A@g46<#LgL?GSqjDetoYHOz|d6q zI+3O++B)i=XDQSlr69qg;b)T@d$>bG8dhKDH7|(ICvtRWjhHmZd@|8#qTqq2;=6v3 z-oZD{Lm3~G0xnD9dD^>mnHZbsa{T02Ycu3+C5@+oEqDiP)%V(+t_8JR2p%@=@uCD_ zpNNr-Ry7=fJ)TUfb@71Aryq^DTzF1l4ZUBC%~4+19_m9 zHdH_#DA0_Sk8mLeSo7PM?0%=0eXb=|P&bQ@-H)%%#?b&L>gP^d>J6awXv%BVPQA!% zt`FoGGxHK4u{CUeK~}snz|$|FBCO%o|YFpcUq?aiO9ux{yl|j*h)hBIQRa!5^7m*Eap1A zzh?P`cr|Tgf!B9+8I<~?Zj!E)ovnW3!xF+9vRafO8S9*{29I?xubwy@npTsHR9KE# zxMrv&gr}XFEiL>kaxpVOMe<)MLLW5Y4aU*X%o7IG+bg@6KE;&U_~-Ai+tpVP5>F4% z6coA#vEYrtDKQReP}Ad5oxXNxXXKxJ>DAD6cfjn#IHVDiI1pUoJLA!CglX2bP3*j3 z%Wj?DjYDufZcVGY^>wvW@jFl7+qo}e(3A4j8+oPj0ICwdDOP?YirRkZrL(~W`sN)J zfvWg}#2Q5&TQS2=elwpva+qHf?BW2kI<=?cV+ZMJdo_pR(W86h3-Jrad;^RE}Hb{IrwGgo!Hxo5~bJgSIHo8^g+($SuSKzC{-+q)Y+JRLgRsxjQwd zLAF#1MJh3YwX@wnloiWv6!ONN$I8m2VNnBJcv zD%-CvHt`qQK9y((MDefT!BTEjT$SZ+m_ODn&Tuw)1`-m_TqHyeZ`TbRs^p)8Qj1{# z^F;IlCfuxU#X3xwFh_>)BUYd*2_uu}y~05J-SV)P4ea6j z1CVKbaB1-Ai=*(H5#h+N#-e-0lq%WH3nPlo=q3oi}^m1`1^_}Ar`wm#m^-P%38EATFrkmO}`V#T`coZWqw;g z6KWj)jb=G-qHx9t-|rz?dCfd_@P$84CRrLIe}6X|J!1Vg4$@6_@G;d6uVXgN(UQTH z>}d6IXGc(_<@)u8!D>HNU$ZqhOD1`EkXUAJVAkara{=03i(O=!u!CV9p<;1L;i<1g;c2uxkArd>2S zVA}pUj3D^s6d~>V=1|4QjDtE?-zqf+=$qbpYg#6m_mJ& z_rZ?;vazjk!3e)Rw<1((G*501^Bxx#D~do{j_6_JCZ&>8_D#hKhkN3RwrFEx8BDGi%d<#XgRd7}=^ zF=j(Xhf2wo2b-(l!oK6F-G10mEOnQj+rib5c-0KrWVKPYhzqFV;Iy4Nlzsu;%$oREvb{U+UQYu zr!nDl(8@~2^LZv4y>1^h9Q`uH3obPl4B=<;3YIBsg$$3i4_%F|C=y)>=9SpYFEz6* zah6^<0kpj7W9O>MF%hhiJZ8n`mIt{4?StJA=*IX9t}W)4^ZpCsuU)6_I=F-0`E0L2 zeZ>^C@Fi=sZh3PGo)E$gikR3ax)agSbf`V`e&ZSf6D^{Sy2R|?NiJeI6;l@#vw~Ni zhwV!PsJ-z(-+CkTC2bn6Q6!Vuw^Yjsa=k>aGeCQ`tUqQ#_jb-ei?Y!1 zT8|$SNjujr1|^C{?iuG~y+p0s*696jZ`Ls1Z+$mUTc;v510jl?RA-(w;ksqp@*Bzk zs*o-N`dBnr$A7Bj#N31y?|b=G=fIh}XXRFz2a-VvrwUZhw z5u%)R#I1Ghjrm4WQ{FK8YRu33#|A|}g=biEbnf@RZli!lc9Gd#_v#%fpFx=hhAo>RsdIoBilXYtaU)D+OYRbc9@Eal-{GPE}dGBu2MaROddpEqe}M#N3d|>#FAkL8g~8@Kgc&7Z#i}MgDk(JhB#;p?7x7`5mbQzK4gD`lEH3Ghy40IepJt zN2IU3Y;F4h>3JqRCEV+B|3H^7@%cFk{;{L(={vn?LV_EL*x5hw>KzaN)1>&=NNfVsgbTT7Du zY7gj*W=7+06~{TxY|tA_9p3K%2`Vj4T6@|+^3mVyWlT8-vS%|9gqEcG{7Ry^-C-Exld|V;~ z?Gp9cuz_{#-^o0s)=l%xpk4Z}F@B;UXZh)Uwv=OaF(@;salTf_CaL;tKEAK_B9fY) z*MZTmN`?F<KGpIsmsv*g51+M0 zWqQ=kFO)L>@OGP5l1-^4NI_Ee7$)F+T!(E?Ah&ND}%W70hwC?B>C*5&!uIb+acm&ahuzUdJd&Rd08@{o6+W>0Pe3 zva(}B%~5=h!Ot^r!toMVQPJ|=FC945%*h07ZVXn8W>KIVG3Li$`hEQu_m^TqttV?- z5BP3}JfOtao;&F;>($DY(Zr^x1ov#s-?xw&Nc;CV3eG>tzlm9m#9T{NVZtcEP})ej zgm?VG?X&iIXRO^|gU%CVy?|VsmjX`*Hrn}ukw(3u%FOp2`P!+a90QH(9Hwq<86v_t zVCZS1uOM5VP^Xs25xR4*z!;0Wdqc7#YYLJxU=l=dy&1~cI`j214GF_V4;EEczcyGU-X2trV z<1PRGbs4!ZnG@Y>Z`t1ZR9UzM6_YD6~F4yCr>K%ZqY=* zgV-7ZzgtY)ks6Rf(=lb|9P$eDZjjJE+y_~=P|>r~wOsDvf+1IHL9bIP@3%)Xuac{3 z>{6Eu%L|pG-WK)8J@cA6%b#e~0+aD*JfS3Moz!^Wm>b9pK`by@?(%xsJ&)2$$7=V` zM(qyAotW*dBeMIgN>lgd9}7a_g#1rdg%;wNWzD>-#QP_0D-TcobpCPNyxljL&7TIx z!nF!%W=klPPDyc+QnV@uu52~)?74=jyX#Avwlu2ST~444)$VVJC<9!Vehij*f{J&`t&`uWJk$(>AyGmzYq`~12foiRFO zkE6a+zMgr|WiH!RtYk{O-mn?hv?AVfvp`mCBi3D`Z;@$vk zGk2F})e1S5WC1sW$Z>0y;sS!K-Lk#m9@|?oI1Nt%Ho-@k(l1d( z+iBcjD7yNBWe;EiCz#j0{ZvH_MndtTnh7Hsj$l`H%AD1(1=BDDC*&a}BVr7M43<(> zxGJ;Nuxc6E7|_3sXKI{vc3sBSc)?!a=#Y29`rh?fG^PSE`7Op*KQuGy1wm4EGw0t$ zM6i&AU3~@b*;A45*&_eH+!y!JJ6PafXdPYQ>R)|RYwrLaIy@}KNsGT6fxAmYMnlg- zDflQ2Z^GYv88!M1MzRx}-UuKat=A(Vc!DS*#7C4q9^DU({EF>2b~)+bCbr!0FSSQ` zSmxpFfFADxqsOezA}PQc@`M$oiDP znq|S?0G0Ix8QxGt2VBEsMlV_z7r0g#T6^l3Ho?GxHhrXIjdp2=S1&xh@(VH?6+e8) zS6i+Lkk)=sH}uvW%kz^DkMhf$=_UOa6<_&xA{xwC!gZBQwf;99%xK7UHE)mABL+hh z2GFZM)O1q&=dgqyaCv|%!(RIBb9STpAA$|@+&ocg{|0Q#m11z?NXQx4swo*_HWLv3 zI{l6QyfcY!V6XA4t?TshA%aaHuejB8kZ0J`psiB#DvZlN*SPs`ENp}RxVqx~reCe~ z1JXVF-Q!$VGscQo<{;yO07!rd)B7+l5s)l8tg4F@WVpL1sSp83^F}^f*J0f*TQjn= zWv#;SocWieCfH!Q6LMpNAAi>o`>%#Za?S^N;xU4Y(yXG=CFX6|#2vpi%%d7*a)pO? zhM>mRDzd^NpxBz!wYwMZg@fDmNSD$$+4Z=&tY?G8>?(V)jj14Gwxu!wiJ(j!;6a0y zGT7HDz<&E0YOfgBEzC95q^JmgH=_Pvtb>PKJ2l?4Y)3eG=y^kXaVcOYxA_!&6ehvcZ73y_HnrvQlN63#jA!49V@d zA@MY@jy)Qp^ol3`bo{}W(9YRz05ub8d-cI$ep8(NJzrSulng?2T&oYWQb=@1HX6<7 zA~td?QY!~DkK_Iil=NnWkK?)AzG9mMIyCYS&e~fuVb6Oh&^K|h1hTqBU@M*IO0t+g z=dwThTV94lM?9h<{-m$qaQsS7i4`b3W_7;7P6QE7g@woQc;a9>tDlL-QDm~^hvVA> zK~jl-Ej4;szHk4T(kK{~*Nx@YcOf15o0)eE@|Agi2ct99uhPc_@r&&(|B)TtRt#oO z_oHt#R)O#|&QHtiUi(}O01f~96qGvu&yM!eM^q^Or0egpt;*NWVqJepp@VDdU;ENA zpmJp1o2P=7z6yo4Q|++Y1&-J4^YOLRQtH76__^b9!Ki#U2evGZl@QoPi ztT!JoyWujBo(K9$UNW*fV65w?v^ny{-w$F-8&OO}k087f<#gqrL!KQ_2nIV|Za{%W z%u`}yb6{*oG&Pu+iyrVk>@k@rHv98$9Tr zGfv^6Ms)}QP^b&mcW#LrXz@qd=e)Fn!!HAD)~^wa=`jJ?#Twx)-^G`O?1P!b6cx!B zb_|$4hCgl86#sid(7;S%y4iLaP@yRHdCvyEAEMQoQg5~6xC5!3{(jq`biv58eeeXw*I7=imK8UsrEuw@qi=k}`Bj^v?n#FS_0`Dvl=V8hwHVcb5=? zyL*7(?gW>?-GT&foZ#;6HUxJD3-0b32=4BD^M3FBb?>@==1kR8&vdUfeX45r-unnK zSn_l$GE&b_1i_Om=c$lYeI1IwtX6fkXh=4w5_@ z5^xG`s9`E1Fb^Ya;VmQXyHSm(du$3_tZalA12pZ^@lonQ8b*2yIIjRNcw=o9yOSwk z!B(vRBdF=4IyfuhBz*lg@(*;8EtzQ~uFxl#?y!!hqnU8I50#O>ICk`TK87)TOuw)3 zOKJG`BNF8;AzfvbndL#=0?c4o_XUN?igK1jKoDPFmSnX^+21rl6Pods6OCnd)T1q& zhx2rCLR}vUYimVqz5`WrQdcnKWhGiRBW*}*0#B$2j)m#|xhi}HUWcCIcXem>YB<9| z6JLKY_K-*G-pa{15iG=zPGF?DC zuIqzSM}L@{A!hf)c*M}{Jha5jim4Bk!sr|i>d*b%U`lgl5(Vs`waT`%e(`r=J7@V; z&Ken`%aa&IYg0E49@Lo!V#j;yMe}D<-$V@7r22?>Re{HwAgxu{2T(W+;!ax7HBM+W z_GZm^eKiJwsg)_xrACOBO|Wq3NqF%Vuzmf^T*-=X?%czh9-ZryBl+V$F`Y*{d6*lI0 zFqw|eWgOKW#(JN{RwqTYSwuwCwMGxF16~!bD%w|v8r;~(>exrarkCi;j6BHEbt>%4 z^fTU@%wg`o$&XH(^W7yJAzjU+qPI>BYUy>6A8o!#0*PpMBYKJjS&o$oU=;D%p=NWU z^jppNaoY}Yx0Ag6T#^F^uR`t6tWj_2Txcb% zc={DZHAt9B{zWA>B(@*fO&BWGH+3BF1!Pe?7#<7FM@j_du)zU;{7kAUropme`tPc3 z??*fJfOgt?(u57FxPWXi$@+26kNB{{MMi4(;Xl(POSX47wz8WN5J>%cAH3R$-H@y8 zo5bQq;bCvX;X)MuvyI9>y|gr~JtbYscBDe()fW|a(Q;3Td)y;C-s#`Fo!8CdXS0y- zY649;wl92csp5!22=nc>r}|oaf$@2yVBy4lAXC!y1cthFv6R|J_5R+<*~hZ=pNGcg zy{0@gOV1~8>So1BaeNmFLp{eDWq_eke`hj?={*tY&IDt zK+kd<5?*wB!rHV2*x@GG5vogQd{No>uW;3>q}_2~I*jTbt+ux9O>XMrpNp>O2((Y> z9wO?Q6~hd-S@X@y+Upjr_J!%%WR&Cfx0g#PdCbi)>!~>~?+2D(mTlG1x6m4DvYb>u zTFxGMn!%*0x#ndJ#e#!@dB?%WCNgVV!1Rax$X}v};;>Ka?H7qb%V(>VtEftLU@_$T z)!(FJWg$ULF%M3`O%Xwp&on-K$nySczBr_>h^B4q&*Onzj%7zqC4lp9;Atcudvp#SPI+xsSXzIrl*(t%1lejY1dx@mulTG zs?i&lB*8%<#%EEI_;?k~gWLBYr?W&eA4)=4p41>0mVIizx{<4-x&JeONA>m4>>pEp zy-b^=uQtZk#0UNI4e&mKgeuBM04EtS7?nh}l5wE7H+`XYYivS6m;|%7v$oArfCRG< zcgIN!8vk|F>}Sb_Xb+fkvkP|l*OQ;y-FQ;YZsc-2E8Kn8_uDXRI*q2ZLa#Mr%eK+g z24!~hmW|(LUwLhKw zHAT^g)_=JfOJ+%q!+>O4e?c`A{_q3^^inhP0XGgi98nnZpj^~6sUwB)yE`pe>5-cP{ejr_5)5|gX=%NkSzWo1ebG4JI|$!Q-(b9Tyb5GCoCa!14J*&? zIADI>9LO*iQ!RJo>>l$J!(d64RQemb_m}l%>+y{f_J+V~>apptEn#Xd^efyGgS)+1 z>MGH}ak;+KvnMbU-j68$tYlHA{!tynjN*GPZ}8#OaZ_T{(AD(+#{t(_Lr?KEcp>% zOu6FCZG!1C+&Fn5hZVG!h~(Q|Nqw+T>%(D*RoFhGaC2-}AOWaZ^o@Zsy5KDOYD!Dm zbTG0*@?0c+mC@u-b2C|V!`MogD)ns_o-MYF4l-z?0!LHeZp|?2we`2V4coLu33A0j z4A_mPa{v@rw*BmM_o~{p43?9sr%0@Lw&!&Cn6GS%-HrB-0H7(d{|A3Pu26|MNzW+)Sv&X?eNd#(IxvpA{tJ;65KeYV?=m-o%MLGgxGC_e~-Xm{2V z0KjlmzS!r#R48y}l$-{BzhcpR*`E?Q$Z!j)HoC@Ba4b4ne0!An5;^uW;s~~d%MY*q zgDh~*^4o-=*;i{)_Yf?vg?S#V-&aNG-#ZHIn zI^C8&*F`j7n0e3V*Q@bBqSbDowJO}J0f99rJfy(#Pw~?mJtj@b!YJ?iC&g7Im313C z6w`-};B@_!aA6PfaqVytg|YNa2R8OqD+H>fVg`G+O6N{y@vH>qpstzTfyfhvlN>RI zAcI{KI+iZQv@{8ypYN;PGA*pBMAsXbrqsPt{iCI=i`wCyJX`30l~=+IaW&C)fOKyh z!t(9~)iqllxF`+1_OY~`bfq-HwAxcY?FG3kipvtE?ece~o2K|c5n{r_X216iiZ7_) z^&H(sFrzO$yu%{1I?IbIFv|QWEX0XixF%OxBd#h^ee>7((xz1P``;s7guz6^Sy;;V zEMp58e3dG%r(_{WWCpVG^%Pr}z|xVT(Kl0SWcyo9A1p;v4n>h@Mw<(XD|r3!(uq|R zvWxZXeZ@O_t@6?I^3=ED34h1-Iod?yhFdU@C?tvr*Ap`wJ=PRyf-UT^{l|)_+a&8v zIK$q4(}HB&{71)d)I?2V<&Cd0fG>Bz`jEn; zmv*MTekZojp{1jMTR3)g*u(3!^2W`6uKBC;iS)y>ROj$bejN?zvnK8`ESv%PUBivl z4Wh|H{!rAQ0Q)kiN80|x_VXXu5BXZxx16->#^v`dD&rHI0}eOrw`C6;8Gfq&B&VvN z#Fexs3?Ztg)PR+5x#f;$z`P0XlhKWX<&4~ak~$%bBR~BB}@i8%C{( z=?&y3-Qcb$7K|y=aeSo1PKrGW+6PI~E&~9`x*(6b(Gh_*)^nX2h&qeZ5?g?8LT#^) z(2Nh^R=P?=b$R1ydO)-0ZWKVV^#W7Kd9^tHQcUd?|5CSNAQ;Atb0nARW($RC6nl#4 zzB~SU}w*R+EC~MP?tH1SmWsy*L!H&=cEc zVwIo1(!&N8YEo0%xuG>Mn*7t!a^TQodFrHpR{97KqBa@(+ykN??uFnWJ@FKS%1O5L3K|C*pt{r{PuvHw5TXq=z< zc~j$N;e7x;c8>p-6&ghEw;g{1*#mjuFQlYM)*V<(V!H2I3qMi#gQVpB|MP9|diz!_ zd4{8u!9T7{hfS2;vgnh@wUT%j0U*5W8-013mwLK>f6E14x`2+1F%g6JXI4o6Bf+Ke z+kV&EVD9nzOZEHPAn+7>u@3<6?{A2}fVSyt{Z1FV%lpa<;D;Rg}RIIHdPMXg~RR z*8oGczK#9Pe~j;v0sU7Ml$QKGS0cFD4Am+Z`^ZpZH0_D%HQPwwRbKXoCBFd(QzEM;$mpZs5NpYB9r#E0XS z{yB;42D%zuBS;Xy&?oKqN7j_|sfZXnn3{xH_#l07Rk78HHCb&9D5CU8>#vsfmt zb)(=71VlE&JZgeG=1$J5qEgK&E@7GIk?Aq@{pnSKfJ4fL3!D5#rvhHaa1F1(e=3n; zj#0#@2_5%pzF$5LfIz_sodLs)dr8l-GCJiLA3~xSxOwmK;R+D&?JD1x5q}HMFSN6g z!daMv!-9`Ki8|eytB|w?!=Z-|Du^@6`NHY@M#4Y!iUf93mGM<8%cxOOsYS}6+EBCP zVV0!L~KR?}%nLUl6{`?#pdoP z?P`h4TK?x7bl*nD7QH!b+$M@aAE@3-7b2x)OxMP=5*+|H)*4dL-4L04fZL;@3X-Fy z!qg9w$C{8wBStM?ZphJfab(0BdZGea>1u#x!-574llyyw>4hCS$TMQV^GCa1uH+PC z142{rq3_%WGUfT`@OOaCJ;oI-zM)W0Thj_GS6Gr^Acj;IOCMIy=p;Y26Np@>g*L=2 z)EQ%N$E=EwYsn)dusxPV%qhK^tA+nwi8C1Sq)wyDe;&09UH3Ez{Rx6e?ghYusG+Ol zxvwvdGGT_gSFuyiDEMVb>JR9rJh4wUUwe@5s<(gEA>dP)-Iob@*X>orIEe7(kT{?z zi8Hd$;xGL}D)8dc+MA*d^5jn_pgW!HW(f<&8cBupuvBB7Y_DZrs-h@BrYjyY@Q!;G`GzWU?jS|!|jX5l6McC?USyq@ZPkoy-{cpMi zV!{_#5V*#&PWU+IU<%cKBiuc2xJYZa!Y%}xu+K^c0rdEPio4{j{?)?>ul3JtmBIZ~ z4M=8%*_WY^Xg3Rg<2<=KQTs(jr#U=R(Z#F%2z!#7DK7)VC~{}TgbkdMiP{BDwJ&r< zh!y#ZRiHBSL~H37QZ^Arj+S2h$=~r(lb$k@t;SYEm76fal}g4Xd=#cY0_P#W>iHPv zY4OV?vW9ZlnIW=hhc4rxb8poNVz8fu_7?qRlpR=MF68H+~THzM04}DhGp=?oEpBjdqptfO1i&0B?8mo3<-7UUyBGr zurPS4Mm`TeAx7tjD~@b%g5n(6?z}{lGl0Xgc~#-;!=LDe7|rsr6dhaf^RgR`Y&&GC z+j~nTOe>vG%4{ZiOMo>kLytl1ZV-;b$KB6${)>3*E8R)j{y+d63V$hmT^6K=Db@*I z?N%ANp43ooc)wr(0}O)bjub_sDSV8p?1qW|JTC$R{`m`hD}4OfEc9O2=BP+uga41W zDOQmSoNzGO!MTg-PT>^=xZ&&g=rfkTpIp<7ay>exOa#Zg>~ z1VeFhK|o1s0YYVf*taioWel*fN?-aEF~&s#r~dXc!)ZD7AOuUuiC&iTTNn51iH!++ z5p7h~Z7iJm=z=VQ2omU!r#sEUR+y<7A4`mskR_rUsEi+^2|tgjMf`=|`6X)Eh`7qp z(&0bf?XTFmrI-ej9A>o*>JVx9ujZ$o^COhqP6MpGRHlxZA=2${ok!EF7}$t-ib}f| zvu*G<6wJjNY*X^lmFIv2>KHM(tM*h1yLgu-h2&KPpjXVHJZO^@``50N31a|Jxr04P|`T#Ts&o7gMG(kW(6h z&ZlQfydgY<1FsFCDTELZlG&o^I|Tyi4Z6hjP9i4PSTLsUCe2UYrdbSoGq`C@XWN6_ zAEOGoLzQ7|>VtGKFeko*@TO7NBC!5oToyS5NdOoXn(K#MdUe0K zsw2FE;lykTqllV2&(RhqP-~_#+Idw3^|3usC!Pz~+&({;e8(*?0R3WE^FG>Vwo--1 zc$-h$&BvdYUQ+ivPfbQtS-#k67nvEfTwyyfOgfQd*U5g)jQ6r84NN3l*3Xf#6 z315D^8crLBChN~&+CT=4aFf$zdcEKSt9X%{yljeslDs z6XR$EIr+fHRMSu;dLY&X3naU!sy`(G>047;-_OY)QS@$z^LJ=bjxU(cBi&Q!&Uwur z)>jioQ6zV>O%w}ZsT{y|qt8@N;lG(5DD zR9I``Z5%sS@XFRFY|uByOe?nH*ej~tiM%4Pumzr_LFovGL#ew8c)_o&9N_F~(bJfv z$T|f3VB}P8v2Y{IUPQb(7gbt3v<0I8dSd7_iAb*`eLB0*878HjhEQs_KGBRgf6ZzC zx>VQv#sFLJ0vAbhjXCyTXRI(Vsd2Q9uD<)VB3k92)=29w7^73Y0n^Ai&1=KY#T z-EeYB3W}a17cK>iv7N}>gAs^EtUqI@XK@O4Y!A1$fc|nC>0Ht+n86K5!K$`wK^vP( z>A?%)XF*nwF4NXp&ws~F^ZONtszw4zgFQi-^Q+!8;byjL^Ll$}E^>ctZQ?k%;-I$c zJhQK~`Z}4w9jEyQqiLuhs?YRFpv?jAl>Jo2B=Q{n%|K~Tts&jvkac7UIH1;i|3UOj zU;%zKG97}8aaX}L&xr+spQ>ow;C8;jQm{Ap{2iCj#11A{bG~$>pf*OZ*D~U)zBoyM z@j9?kJ@u_?_&K!mX`O&M06tX|P#h%Den)U|%StuR(nz^mh#!!p;4#D2c51x}%Yuxn z_$kppAR5Cvkui=B@rzZS{JZD~TPHXTfRM_avOuWmUf}lqe2RdL>XY291AkP>;R45u zmn+WMB{*dAf_xfkG6gj9j%T(CY~zLLl1Q!01n!c$Xgn<5YC}%O-&9d~W%{LP zDe2OW9lh4{FL+&1#lg94zq?os9YuJtNUq-1@!c)^$L?L1jE46k^oQR;{o87vD0!kH z`gQ@d7*9qD8#o}tOquWnR!^|Mk_B^^DWu%f&CLlDhMu&pXh}b2JSaJpoiW57xmC>P z-$*C-Spk@Jms3xMMZM;qe5L1i&&_E)?DdZ=-95=PC;zbX06echVRNd)J^T-}A*lb$tr*f!{W0J%;pK+A?OM#( zV=vh5cV97_l#VgaFxC66E(pyzclx^MibKU7LEQfPj2;gay$~*EKUr zs`@%*&6k=xI{OyHbi4rmMpg-7`PZ!>`Y*g^3`LCkMo698V(&tWwHZ0<`LP08RKV z^n9r{1rPCuH}6X}!2`S3A;aP9%P!wZ2yn@Ly3P*EKyUbK=J+ z+J~9qcJIPwgS_Y!S($-<$UD^xU`;lOblqFc0-m8wMqMe5N zweWD|H`!~@HE|(<>sDJ0EZx`Lvx4UCZ}jHLYu?hJe2cMqlHcOH^)Q@0?hGvP^)U6k z9;wh2&B$i$^3FCBz?gE*RV*9P+Nje%$dkyS>77ioVcd`bLTtR-ql(Fq-+HJx(9GCm z>JJDC0~EW@rc%#V+ix>QiIw75BaF!6p0sI~i(kA0iY2Xo2%5qn1+-nyTsg9x9&u8a z4%Xe&t?_Y9G#eM({m1Dy=tIJiu;U50Q+;VqIL52Qc8b#@fZSSQ-nvc@;s)oC3&9fV z=;06R$@IQeUct`l)07hKviPK*rotmf_O`K90HBB3U_p_NjE2%>M9^ktjAp$l`mXQM zTR|bD+XFkIoo;8ewvjloo$iIdB}=@m>maK)eyhhKb;y!1!@UX6iDJEWR;Z0~!)C;U z`a_qWrcv5q;@rsIZhUhLCz}7WNs=4qEm!=2xQ)Z1U*Kc-TH4K?^c{&d*AJC+k&gZu zh`b75txoX4aP+Z0D@F)0CzSVPXD8h-~g*8u^p$RDJ;(45v>?5y%T%^fQ29}w zy6)q@s$C!tfssUd%(S^APz0-6w?2y{7mDQ3x?CcK7mAqmk+zMZxbl-^UA+85oGLvp z8@|Oqkms;&eO$=vZi>xY&&LD`gl1Fn>&2$!)?AIEqu_sOk`MgDU6>jpY@|dWY9*U| zVTp~iN}C614+NW;PnbpS%ibPq78@f>mOr}76dTisOd>)tR7*yl>u|e3t~Eh(&fGbE zm|+Nf+VYftK0O3cVG0pvZ8d2Td-4wNRT6>u>;FaFDFkb0P0nzdt5X&Av6CA%q}N?Es1H03HW&docJj_`SnCOkiK z<4Er_F8_+XUvy|DdX&?g*?Dcuj7z8Jjn`A9oi{RVzwQk%{;17`G;i0c_l&&UKqC4Z zlj3MJw)0tEZYj6?16mY`NqdG|CR~iCJNIZYm2r zC-n$p-ybdeCZpm1>&v{PHcdmrPmSo9Sw+T*(VvvG_jJfW!@ren>>9u{4khP#k)(+H z{3e0bs*_YFlUJV*+1xj=Q!aaOmOT;mK^XA~m%|jWZqRtq0|6(>x{I{)#w`f_@!mxH ziwX=6>is11i^3`UP56niyaR7fW#grW18-tILR_hF7AKB#szKhRoOfDY+J0xZbr!5z z544g2hYQ8lN|;r?Ewtn15Wb+o=MJ{)z9jF5I$q67bnC{JldstlOZ|{XqryZ&Z&tYk z+w)kZzq4V}SitnnTiP-A1ZCRH_P|CDKi#rz&z68_*x=tQu^itqRwJ znoC03UN2qwd+y}!j$E&1(l#=sulXZ$tPeQ2bZkUYw?|bo7Ep*OP>sY;L$47B{+K)@ zNMbD)Z!CY)yuvwZEBNUiiuSqmAV(wZ@B9%?8ANT!mH(34JdoHjtdq(|q{_SjR^EtX zZV;=zZVVu$TyCSwf3l9+O>?O4qLZHy_^AI7$>H-jws(6mK(u0=tnc+x?ir1z5Wk}2 zE5V&!g}5I26F%P;c`7=R!`W_(w6j=BcWxxU3(9Wm*=CT>@We^yWxrqLtY0PNqmrC? z6_L;F_OZI=zUGC?*jaX+Kr_K`Yg_7fQAbijAzWbb7{7%R2P||*be}k3!ECzwm)tnR z)6VVhc~uiZrhVn;(`nhj%I@qwa3~*>sYeEzJLRq4>$!Gt7bPDB#0bS8C=au<-{g`A&4UnqlFXiL4aQ*WpLF=5X$`~2Y7 zIVwEANgUPjK3VUAZ_5J5e*gJ2{LhT4u+77JM$sHKmEhpV-iPs%NMiKN;99zjp<9I! z1>3AdxV6e$p>NABe@C&FU~zNGOAKGR1Jc*2=!Cgr2z_PaE2vs8YrZ+we9e_UX9OTU zW&6J#22Wads%l9E?@I9Je|zP+rBFO@n8jd(QSueQu5cg>=R`RMZ8EC#(cKtP>Gl_EhmZ;{LI_S7P)` zXS-?k_e#)vK~sK+g>`KA`%tFFIv|)`m7@z(d_9x4s)yB?_2A^rtA}02xq}$_TGnx` zUOqOneX(sM7eAGAEKT(2&kSDf(X_Qpl$N8#GMYsQJw!}4a=B@Tx@GID{jgh>JU^t` zAjnQ!^7lUg?3GL+XCk|r`OV=nDqGFyIjg~6r`rU*EDd$4Rg#?qPiQ|Pk;1Lf?G$>vmhhD&p;y09uPe6TZ|L^1FcA@|$($)I zk2}%JWAZts`VlAXXW0CkNt_l-Qzqr_->SUtPkp1ZUX2Svkt_hf*ge<AGFuQ^dm&k+ikruYXF%vtGA^M?AMl;JKn`;2oNW*D!_2KUK&eo6y zIRf4qW&6B6)N9x=*T@X}JhKU;jc(!QfIu1{tb$BMDffw>oGhtlcQtiHB7O?L3CRm+ zA<%?`z0WL`j`i22muq`5CJ=}tXC)b>?leF-+R)%0M2MbY;ZU%wtjN?ww@KnUi0JY_N z#j@b6pjhr}WrZfWzyj(c&Dt6P^7hLpgRj|7N0&Eq2S)niiCa!ne42BD{E?$k+)Vrm zn(M?rUCy;5Z1t*2B{WTCmlhd>NS+kZxnc+Dp}^AKmZY@q1WjMo>M@B$?+{Fj3O(iQ zOjt6chZRqyS+Gu0Z54BvPLYnb+TP46xRcb^s;Bte zRg(X9e)V_H#iG}OmgBx|uhAA)@Kz4)T+dZE|KaSip7|^|Lk>r4ZjP&cmylEW*|wpj z8z?>;Ey!z55?jPibq4WM`oQ_ZoysEzojYYl%BLhpGZDWu|GlFBlgdTBBR%nC{xG9I z%9`D0tCvuSHbFAh@}GB%ZAS`2JsNu4yVaxjPKJ=rAydZV1l!&ir{gqCw8|gSx^~tI zGHv8^Y!?A66BqsF3-b_I2yNuv{( zwHG~qwaT5th^>?8YBL|jV<_gWOflBmK>Ct6(F7sWub#3k5r#+;3TSo3vaR)uA!bye zDRtnR(+UH!EVYlc<}nga^^s{R#2GaeB44w&e-Lk zH|O!IruOmWUan0|g%SYDl80Vdw|8j;vX{l# zJyyOwYT~UbwiHMC1z%?YMi`2>EYDm4-CRIE-bGxnYgdqr#oU7EP0OP2yYCVkLiD0Iq8|z zI_Umjn%k?dwL|{VF*_(}<_E>O#2e>VuZQ5i{YU=q)NLM_lfd=up@SQ3(c@S0+%wII zf)jOZX#CkDNm5lBw>!GQVOy=P=26dCV)<7)b#LP^k*^8&)?kiw@_J&C?<;YMy@T)u z-`BQM{UCV?J4?h_AIaz}NkPGsemw3U7X$90S>1g4?iPOuHSS5_|1ppBv0K6O)A!BA zuu zBNzTV{=?a)CD83C*29^h_^dP=k#P{tR-15OI&ydFD5lue^y=deqEySKfb`TKJPN=M zglx(S(N|i_i{IvZ8eO?rJx9~H})j>%HaF!?RqCm(^q z+uKsS6j;p%UA7|ld;GnYPU52>;SKGwb?;cYvJCf7Qq}JR1hOoghj~)W!-~sOHkR7H zB!{Kh`7^gPly>+wVR*lsSR%!Xzd0(ZR!>M4TqZ7zv3#VkyT@3bQgcxrVfW#9F3mNX zJwI!R2f8WPTco$-J$FPpqcyB4|8=xA@pb8DOm9y#Kr#_#O5+?g0&4 zkD{wyAf-$jB$j8t+<-tz9F+(zLO0UYwnLd1&rNMX(Da8ah7)_yFNwwRHow=_hGtQV;H>fCff5T7ki(OM#Fma|Q(duMZ z6k_|!m_?&AG4WeAdd)*V6XWZR>}sb)C}oDw;x9{M>S&TmKC& z8*rMzfy@dDWi>j6ZbZoC|DjnRmpkcUYp%Wc%@Boyx-j8OAbmq{Q5>NyksR0`Y|W|v zI{XCp%#gOW$$6Y18dC*s$2wtW`Q6Kz$LJf3H)JP{khW&X-zqb>?AoYmID`hy@sqgS z4)8|$<)#kKW#ej5#xrv2_y0NFFX0qH0fy`Q-oi~WVtFS!?|x4imk#o?iiIN;=Pw_s zdJ*)859At?1b@{`ZF=6=Q|KbJRZURxcBuJe@#=d)plAn&t2q&V&Hcpb3>U(SGB6Oz zY}2_gxd(Pa;%B_H$z-Wxm-pOA*Z{3GTOP_`s`}XD%zWqIfb0@lffU_FT(Q>+%crpQR zH0>k2abYW|{KfR>lCN3s1Cj;y44~bSUgsk!V|i)8a-^jAg?!UTzI=PsymrwUtmaEL zSndTID8F8A{fUqd#<_2AmSpflaBj)JpEq@?Qh_qa%R(@NA9k(9Td>0+M8~*JP=GNP z;n;5UB330hJ-X69I|%cR7v$I6HjA;+POKsnm&H3pmN=9r7WJ33({c~SRuvB^$D01erIpSgYRG+QKR3Jm&`p5h#;T^ zTLnqbYWDn6p1cr}mZ;Z=fR#)NGS=h67k;U_czr!u&a91ARd+2y|r%g54DA$TM~u+Dt2>R4mY}v^nuW^-vLYlv7oRF zdr1M3rP6#05lI1bvAZv1E;Hv+Z-yQ9uP>`ACzB$a(_(3#t0c>hj)8$i6HWFaRVVsz z(SD6w-E!Wv%f>VT=}AL?p z_dPz}&^T_ROk?XCqBQU0EAD5-sLLqT<>28>xzo0asL67Js9fRpX^d!Zn?-~@H2RJ1 zG4ZpMmBs+;N@L|yub}j=$G$SP%d0}LcFXWU&pWd%^WY_=CkzenAG=qAc zKZ%JYeim7tipoumv!!j^3&QqNJ$`9p&Hdh`4xB5Tnh5D!D$DVbgE5sS>gb;;X|==O z3S6%RJ2nrD@Yi3EbHd+bmjzIbj)fsrSPc}uJ*y{3n%)Z@oUBVkpMDzftiRShOpSL+ z4)250i!tO|9uCZHwEP7ed-_Re=La;jXkN0-L2*9_|7F}BGL6VKb~|5(9I2|Eo|lP0SyFo2Ak;q8UGP{E~a!vJ1KSi zRC+WEX{)8I5n`@k?U1UU;&U*Y8iw_^4iU1dzgW2TTqMVAb#tLSDTTJ8&u<&r04|bp zIXd8Ya)_+{HMbe`sg2RHky9KBUhl9zr24>`GCs#cJ?b&mv*&Mg)PH4v@7vuoaZMRk zA~`W^7pPyq*R<|pug-4?teo*nw2Ya_RzCKTv#v}W&K}zL&>ylM*P&5WFyu_*e4%99 z@9Z`F-e$OOhc}TsA|7c)CXLAe$RVvSa!4bj)i(>lj`ghOto+-GuBS028*y#s*CRb- zp?I#Snqjl`6pCKsudM6ihz2j|>{P+y=hc=L+5YAlFlyP8hLS?7n;x(>yKY>r=iOFl zUpjjfj@b}c)%>g|H(7*w31z^aKkXNkxPk*6RwKskK_ys3?O^#iBhE8`tsT6mF^?t8 zv*stJx`((qJ7GcTK+}^Y@}A;;_h{MV#L$BYrp^YbQpPBJcOg2mwZO+sH0YJfc4p&< zp3|&{nT=1Ln3y)l({=zorGH%ea`Nh#@XIgIq(GYll8U3y1)4lm3<-jA&TDHaJsKG0 z7`oauXyQbn=mGnpJOn6J_I+zzC?`e!uCqy`zh|7GduopN&jaN1mOQOYJTzc^4(?B8 zVL_vkx$YWRw@2iH4dqXx0)G%g*N&`4y-k7cYElCSyx@^2TkFEf+Kl&$JwEkl_I~vE zoRxIEgC@mn>l~IqL(>a`w(eyGZpJ~kzv_qBgU#r<+!Oh&ZJ)%QdW_$&|yivyfV`MMdJFjB1ZX3X! z+i+FG((~!{s-6ZU&&U2d9-|ZNQLrKKCY9H~9)6lUan{$WPY@KdKp;fXt?Q!m{#nQ6 z{190eO2|{C7DAJ2$MbJjf`FWY$>%YA${s`vIoY_v@oG;rPamFAJ~*8~F~|QnTA{|_ zwOy%;C5Zw|&OKM+3#b1U*Ta7&8KcRw(|Lh%#C|W=KV$>d(|n2gDU)|^8AKVq3TN0U zIL~hx)->{Rh4IdQ<6MbQ7O=Ar#`--^yY>Bd{0`so-EzBGl{yD*Cqe@Niaf;BD2qFH zF3|s%JJVqAxH4lZXM%J%$~<*|TvM#(t{fq_Ke}~o0UrhFV$Mv!lB4mx#BD3?@! zsNYxzik;Z~xAPj=37wsF{tr`c9Tdm&ypI#yAq1BIFWiE=LvR8Cf*kICTyW=b2@>2T zSa3bu-QD4WySv->-c_Hf-|wH9soAOB*{z+K?x(w-=1T0~j1Z2pi>@8O`1iZ3&AX(Y zB`ts4oi7kLEx)N6ZJgF$RZxv3C-FChc%Cg4-{T?RwMc6bkcb3~#sT*+q@LxaKbNCD z{KpqU}G%WM= zMaIOtTd`6%KI7-tP2)Tmgbb(~kI4E4i9(aRV0%pS_qEzz5p-O|i;tPaB)n5NT{Wmf zBE0L)yt9zU$ZT3uRHyG_=iN@|xb9q3WBc7%4zFP|;XrMZ1a@fawjb4V!x#p(e~cny z-pYLVv9NQ;pX!)reMlGDkHeuy+b!3^HMa;G)zL9u&D!qR3ue74&4l;No} z311YbN(((46q;pj65j8yqnGFzSCmT}CqWK~wI}W`4x?inpVJEWPQF) zOMI^1wOl8vut$C3nQ}c#lo{X*WA_<(v;SG2qg=@kPED)r3H@_r?quK@U~zzz?0s;S z$0~ge5$ufQ_o|)q)fKSTW*O(d|B=vXO_~U@>sfk5WsNQd1LWMRdt!QQG`rU>lO?G+ z_j!fv5oVDPrbf|{+rPe}j`0uI%7zJgn026Gl70h`f=(`p?JyKWQ!$$m^V{pE^@H&l z{|Qimy?v3*^YqiBvsP*R0|oXD(0n>Rn*Jhr*uftl5-S`9Y-ZnWvX8vUeAjon3bfgl zg{irFUe_<<%ZfGhg~wQH#A9CyX^lqlO_Id%ds7m)q!Fc5O87Lar-P$;vJo?drG&Oe z>W5Q=k*I4#^?vk7$`M&N&pWCmu2XU%3ztB^ls%8v?7!_&a?;{|P8Hs)mEolbkwv3r zZb;B+Lfq)nx|RV@pTM*ThHZlnzC^p+=au#IE+B+iIpbXqW>wi^BjaA>Nmd_yrC%g( zw<#cnlc9lHEs{0ff4#gZId)Ze^tzv^2RE<8MGcug{@Zp6aak&-v9VL=RGHZh;f5fC z=hhBWD^JK9^LT>Spv32dE%)v7U5h!%t)L(CHr}FaF6WW8xlm(_KK96-g|IS`B)X!s zz*)Y8sLDG_r%efu>t@;EZ?jlz?PR#CyY#+uVp22o>252V`2*@oR|E5}7WMuD{1;1RS(XwWFoI8^F7PCFMbG zEEHxPErw?IknS8T_(U7x__^C7$-DdtJK5N^Mz)}MbS4{W=m$fb@?UHbW(C1tTt6};Iu?+ zIHNI7Hj)%&!7ASF8LOG8-(`p`$t$m%(u1uw z)jBCbDHwnm*+avk+X1$z9%@MbucXOGD6)A&{IJUAKG+~iYV^qNFN6qc3@*MFX2zXg02%yDVt-YlT)> zFrzX}Sn0mQka>ZWCvy^Lk#MgFKcHYMx_I_-DI6qC@KSYlWFW4 zWuIC_hp-mZ#}8|H5^+vqi>cXzCjJNwK2 zs~y!4HlVKZ5R9F?l!znN^~gJZ9ye)0qt#yQ7WSx*ybhAB;^&x^l38jjOmPMcJZD5K z)wWcEyk~2c(!!(9`#&i&)N$SDJlk3xP!((v3Y^kDdqazW4v~K0OmDov*=CJByT;i5 z&Zp(J^l0tc$TdL1+)O$GO!5)CG2LU|`ElAy0wpFfry8MTa zr`?9ZQ3V=B3bM4H&z$+H0fV7aU*WX12e6dA(F2&`ZEkUzw}DUb_PSaB+ILe^O*?fR z*DO%MN^zk#yncrhMfSTNdxwq){m9Dv5krom1j};35ipVs%NG;m8b=~Y{kTMuJ_q%c zVO=3DNgTrLf<1|Z5+P?6#svNBfYT4WMr9qy3HGCcHxlP)f6WLon*{y#zISxPZZ{Nk z+X5`?HmcezWrjJ128meh8K<1hIHE;hC4B}P9s4%ZOXw<`$@1BNk-bBEFWMIM0X5YH zmGiM;GndE%O}=_*rSA28nfj>;oXB9cJ401qeW6>2R_Ze=z{g++px(Ye0~uW!-KMC2 zhU50SXDw;EGNfp8eYoSS^2C`~dzve^drEMgS^gPJll&qxNS;6(qzj&V zC_e0ukk?)Ju}(9<*r#nlg|VC>u_>RJmkMEwCo02Yz1ReG3^f(1ao8XVlSu1+^%pm1CEkNjF0 z;NBN!Bwc2svtJ?Z99Z})OK^00ui(Wzoo<)SaQj=F%?*svk&;ZH{B?!H-iniANv)o8 zd2Fb@eEriu{l%r5{lhE2U|zmg&z2XcVrQk}QG3hE0ORYvRYfLZ5r*SNV0FpY-(m(@ z*ar5w?lL=GQ95R4s(Q||{2c9!>l@6p5%9BB#JKtqoNPuJi_0vyhVfTHN?jIS#5$eY*1C8o<9dNW3` z+@*Xyf~|P$DyRnHXy>Pg*0L$w-wy;%-He&6sMy8>N#urA@h@T;2WpdQ(aEx84liS# z7-Q}i>)yxouYNqClMQ|I0gqAHC1{S;2c(s#+#(tMv{3RerUGf%Yu^_z@euMQf7eG( zbInK4_BC7panEX?=iojnSssedPA3cyQ+8yO*A4QgHq(pwKn`3pjnnxA!~$mMD2(Z8 z0=7R%=}T*@88@(lGozQ1p^tAXVMm8NgNxa)tJEB8gPWh*lX`Ck|{VF)~4ajk%8Mer8C4v(*y;5VMC8X@d8(wCpc7HJsg?P)Ke zV=>xl2JIF7gl$K=*$Wy-Kjw5r{L()Dd|QbTq%C%00b^;2AUD?QTF&x`F`wt^J8QYQVu{XHL9_$E_DO zKz~yGWWn*#=x#u)KR%&_mHYdj+NAat=4*%frJ)XOxNX3wh6a z0ExU0CBdvY6OWN8^nWLZz8_s52dzIXe?ssAsqmhe&iI5ZF{6(~QCqnKSatQC%m@*a zY_yGA;Jm?)2Wl)Nuj=tO6H~Z1_X9gS3)*GZ{HbmqyV$BoXhkj`peUU^C#C`&%k80M zm(I`4CC7W)--nNC$3Lyw{-Yb_*Bi0@SC5>uhD5ydMnI}z`O+SpM%aq3(jfD#o9|c# z$}G^FfKC~PK4$$b2RUj@+2f^?rR2g9RYg&;0~1Dtcc!>(-GhUM8E24I3frZtv4pkI zw6E*9C}~=wU}$^=C;5j%F)2$uGgMAj#Ov_B?mN}aEi7s;wPdH;j=d>Ck8MUMI#lAN z?0jrNMG{ahlt_Q_9d$r!FT3KH{&}0!ho-nTZc`cH+^JRM{h?}*Xu-#hbm~AiE>#5O zjuafN0Cn7}D4Fq4&QoU2(w%He&#Bp+$jii*#pq}a?mM(cTKtog`9`|VDyMtxeExM} zka}TNbDeo-8~4PWL`?YFk+B;!W_*xry!``U_GL7$S=*T5*)*2BGlUIA{8zV;2zH@r z#%osRs2WYD2RTfAMt-{!s0;A5-9^v$p$Esp^dCCDLI;D{v^C>B3p!Wh@`@4`vQ!)9 zfhFuRZ(j~K3^Fr73Jk>_q~1q;-kxeWC~`2tD}73r1es}vFZHI3nv!woOsi3UB}zE| zElrl1X1mBFs-<@bRP)8~k?t{Yb-lIRxyM|=|9DS`M}ZNmrs=``nRym8lWySM2}aWI z!Z?#-`{ng*LgboU-ELS0l>(3)UFfWqu-maOnA+}po~tPkj^eixQCuZjCkER)5W*s( zKbE$58@w9`x)oL?{|0eT2w6qD5_<|Jk);mL{T_=|`o?C99M+3?2%LAaH-18$YGO@{ zU`R#FLebhB1qx;vf{d?SB51(VWxwugEjyj)MZs#94V)r5hRzfeFpS%=ygZA`nD zOqvpX0uT9>|EgY0IKdDol&(0~qLfub##B#j*Y~YVMS{z>HUa(vP8gY3{*CYX3n#IG zyO3+$)ihM!x%ac-=p5M4@TH7qF-vUmB`LPvp39hryv({SZNiH6nniO0Ty;38(2xei zKkC4@y!9jie7-Vht1WMPcd=+yiVy_TDNc6i=7nL>wL86?Z6)1qh4`+Ob@Kl`me}Kg zMK+0l-J@C}_eo9ze5(F?gl36SR!suc@I5+-MuM|f#f9O_YB%+km)(0&o=vqK@P7xN zQ^dFb!E4-sU(#9~IZ(>LFAv=AZR0NCl;AxF>9I!^@wc%oRyviqj?BB~kHOL5+#+oU z@rBh);#0|;id$wX zp|?0bR@Rp8~LiUqO;l&QbS zJZ|W#Ys$8HGHnDovzBchtj1;_!HHjMk7d2(RX)>{z0Q;=^IwUzk92k^|GU0mJ~#Fr z>24^-CY|^^7fT5~MC4Yub2YVhJRlpZ`w*0b$*e-QfQc z3KTo!xw06)f?19BwlsoS+79LEoG;UE{;6_o`?w7dI^Tjn#xt6GjXf2tl$n1K8{#U} znbwUag`?I}B3 zzy;TLOo?VN3aJYi@3eB zX)WgbYe!q>O?^!md3-vo3%V?#UO zeG&gQDS3WId4r!lC$ksMvO=MFN9`#JhBc6H}_=zIdyATwYNG{I-jAxo>5w z`KjIvd~Nclf{8>o#G=f+SI?#Okr;pY$B!Q1^;yz=3^21dfj;|LK0fa;yAJlEkE!d z(1w4NCh%bAwR7%6R0}b?(c-;+wR0t0Rn+u=fX%ochi=DGI=JtmVnR9V3T7S1^79|e z@8~FeWw31sjDn}Nl_|V5(^7>-Hl!N98j^|s8k^e-2JRcZQY z6Mrukvb%vnS-$#E^azkQH{Be?5tivDhSxr8S&`EE>(QopJ07aS>3G9-dOa6Ymbs1c z3(xw0kQSs*DQ1bk&AP&rlw;e?8 z<*FXOJ|_2j_2r*}-2`)se}dTJ92=gRvcUmqQ>Hz@ZV)@eQk8V1%bc!`r{khsQy8K6 z`%7cUN5OrJ%NHZ?%X{25SDuA~xwDItg$WQH)&iN@qH_ttn0R1~6s%9k{0N*J94VZ$ zNObW0ya^vs2)+vP{2xybCl&wy2C7rB%W_c({{Pu~{;!vZN`UwOn!Hs1AL|bv6@Q9j z5|Ty=JU%irSb&2=fJ#gZ{r{%mky+sjjWKVH-u3pC5yhD@S(R7lGKO2vS>@oMoU(4^ zw8oh_dq=Tkv$5(UBrx5%d?0QpyJ9S4i~p~g)^Kam|NTX&?|m-Y?+v={_p<4*xCkA>sXTQT#0v{O}BW_oLsN`$q5JXSoNGgoPW3%qvOI!DQ$A^Y6}=4a}GS z5&Tq9wo?*o%=7uJ@V5C0|EzgLVZF-`e}9;# ziL3Z(1pCBb!*?N=V-6N)imMKH?|-5FTFwSxB_zos`MfqQ6ZEE`yEpO`MgJXFuY=P~ z=)`EXm0Pe!EmQl#_K2ko!^X4T-XugxC3liQYz`z#COG`w_e&p)n`!Fu7kc+b!1B_@hWS+J`2u4Zw!Gp_#9XS&Fsx zJu1?RPbO+;RPu8(uWa((bD5<-a==ujv2{`3%6nNoS=>m?8iniO0naoy1 z3c0rWVar)}SFyjk#~DB%9JXeH90#TXIV4T>pYG|V9>MY6A5#EK4RXO-@om_VmO3+X zq2wj()M!^)rjelz$pExK+F#d(u%H8Z-VoMdBm6+bBuo-OsElxi1W`Z-HTZcar0ur6 z#0{LfCx?oK;wpNZj6pCKsFQr+oQuS&+mh619D4f_PN(gCgyrcR{(Da8^o7G9{14f}O&$BPe-niz6wHd5rS{O&u$F4O*Y-HE>h zN{$gWWcB>4BQ9H&s_zXh1$k0MNLcLu@1I z9~z$pX~%9tneocDZgkO$-fZ8NB!9M_-;DGKiB$sZ69p0;y8D6UMME?N>JqA}M8 zA2{W9)x)j!Eg>oKGb+w}i#A~Fn<&IkqZf%q*gu7zy|=qS&Sxmk*x(ksMT3zKAaSk_eJb)I!H*C4xI5U=U z@uHtBn>3PhzVF|>yj*bGkyCErIKJyQyBd|)9(>ow%d7gac_-m$A)ndF*cBsHMyhFc z_!kYk!dh`_5_xt3!6j(3L=Z#w4|#23xHUDChrz;8X zxWr+n0x<%ewtB*nN6`At-wQ+QzTtfH%R}7x!Rxm+tr$l7jWWd2;>zB!&jHJnumt25 zB$ld@QMjN7N(9t(ONc*wHTHhHvg;GMrNJ^Phi`D1qI2Wd@ww{+QdvZ|eZQbZtXxIN_u}h~3dUWi*R76lT?&08 zHK@gJX`TIWT)Yw$+Zp7lpsos|*0j;qsgM7cfTVx`!2^FC76p{}1B^?5XiMQK(~5+a z&Rh5fMvSp7nx|TP)IwVNz;QZWq!;;H_m4`xccw;AM0t&@5i7e6itC|sZtr-^)YiPH zKTEmr=QM(ac0vN-a#1)_+D8-)>DU67Z!D7K`SH~9#0WWENwJwG|JjvFzJO@TgKkE` zvhWrHLg>J)`oz_h@ZZaOe;MGf36SF|9My_x4`sl)ebp9}s1ZeEE+Euia|&{bU1@=> zi?IC#xa@f0a>N#Mf3vqZrVk?@R4lA3bqK9#;V%Ak7^GIm=yY2U{$tpPi8Z_I#0o^D zfEQ!!EuY1nz4tfxWad^eYiJCqmgOE85;{c)M?wWRgWMQ!BW+dMMKLaFi@p1^y&~Uq z{e$6{T<%%%A0|mcYkeO0tP9}#(#9JBffDnK_E8T-hJS3CF$)DJGG?(gKj~I1B-J+2 z!0X$MYS91+Vt3+Nei0~Ps2WGXE%M4~du{4UV7WaSuM{9!)0S*;D=B*46pS~qEWkKJ zLsr02@?RjR$8c$_mfC{hZcTp#{vNUGvgmZSA|_Me(q?vMio8wAx^A)ln8CVDZyTXr;)SxVXT(XR8t3iYWDj7yod~d(YewF!z>w9ca>ut^> zJ%6$7lj48CK0sbx(HM#MY7;?e-!9cjAg~ACGe4!OnJr_wGf#6a{T|fSLjAb^gN2<= zo%Kx+=>p+tsN`wi#sBM2hTsI5stF^8eg@8litq;U4qJz45oN9!7N}yDpSmdfj@a{6 z(=12!RM1{pr|m6JNvyeqf$x&;V{pZ+^Xw-k(_c7$s`=6is8{q`qEo&ecAvO-XQF2z^OGK~;u7+x+c3!*VeQFNs0YTyV(rs|k8l;b ztq6_q$Rr?fYo|~yBR5^Ho%$4GWBhWT1E!`@dW=-0iaD^3>5P;}##^lO%V%>G#_THzM zIKbRmKGzx26X04bQ8v0LC}%X${ra1+7p4~m9{unWpbB?)+cbkHC{_&?BPy2E1v$f=HcHNS&8u`X&W~e8$NJkhR2xWPbI(PZgNb!; zj9}5*URaQnMoh^Mw7gdh25VP}pE&+LXwkd--xxX(=1@ibP$(&$^!oY9DmSst$pphX z0=EF3vM;k-e944L9(VKcEm_g4h0D;SGk-T%*g1}vfZOF> zrMpwE}SYa#*{r^EC2WqOg7#3Kd%XypVxk6Z1ndSDJD)iy@P#*U%TIMri-nGgrt*s{m`~cO9u+e z=`bl8PM?uf9PfRE8!Ss#;6P*DP6$%4!_X1bi;A|GRKnHlm`yVLfaKJq!59P~14ZRx zj<(8lI<`J6g!|$wbYF{lZr}SSLpdTN&Ke^dO$eDdny@YX9=NMa$o?4rq}U!<1VMX5 zV27Rg&=|OZfvW2(VR4r$2HDOSg;1MH(JNoZs*TOmW<_e&85-)(C=|R;_RK>65%HI6~31|%PL=WFCyZ@ z%JS6se@L>WJX9eYwSKatH)wcS%O2WjY&wwESx2yTng+?whp}=;?PU*)LoU02no`su zJ7BU;F?l=0FQ9D$I{ko#?2s#Oj6Nt6Ts0^Pb6frdBk==+aoPFYz!o}ut)bj`w9%P6t>X87grlolS@q7RU6 zmW2gH{b4)ye9ISwlz;!S@Is=i-QU~ri)p|f+1&rOe9Xgpj0O9qfW6zW^`GA{EQzQTo+2IyIq?<8wU z2v7#qG($NCa{5Tis(uu*On)8g7@v`t!+9Ti@F1lBYHXss-~-Z1^Dh@9du`}vrv0G9 z(!UgfCEsH?pO|o2*T{x1D=4+;@(u+P`{?oLU7K)b;odUT6!`^d2c#LL`@a5-Nvi9 zD604Gk#Cn*xa;d3T2C<3PrK}UAEDfut8`KKRt=2TvjDoaYUp-4WcGP`#S;VVioQ>} zdlw#p^9M0*53rd~JaLGUpfYk46&iqknmT zO5ZxX*=BKVUMHhwvUzkz8y&cqRp4&^YVHd48~*=Mc{j^ep^b1iXtYv{R8 zUsAszqBD$aCT#(#s#N=uyg4gW%8mrWGSx04H1&I6&fyd-zVn@87J|qoM;a$%Un(EPAn>V(*8wxL{q^z+Kw}dNt5bZEt=(J z)IO&n*u2m6qi(U=S z?mqmAHx40juaL8xLs=?hI-Puz*JM;&5+7(SSOo=_#W)455;CFMDRn=&|AQDnt9|eW zkox*?fg%G8U1neq65l?2T_x-gS#-J54^jE}FTwt$g_jfyOcw9nN!X(pXCFu0b~p8S z(uy}>sdGNvTt118+tr~urPPj{kqIL?am%5L3ZJf z7HpDGjeQKQzO!Z;XPsTFHVQj;jP$?>Rf}dJ>>v^LcyRT04TMEg{U-GpRs4}AA#U({ z>kbih6rEBI91oHYXErgYXkR7_`S7J=vaN$mxWNT!;0YyCn(qQ_bmTu=tskt3z;f0p zy&F+Mu@Cgc<1bnvzBEOzY<`dQ4~aAjEeynMq0MN>jV63jj@`vhc{)+C*KmotDJIWK zn{0454F?$vT~;d3z*HHzw0V;}<;D%(F+Zqi?d&oW_!{?zC1M*yHdj#sV_-WZ2>@lGrx8vgO7Okns^yma#0Q=pxOW=Xiap7@K$TWU>o?%xtSD>b z@{P;a;d=b=o$M^0CHXtY_fBPhHvkShqG?*)fcyHLS{1!vV^Us!_%RBL$3s03CyfNT zEml7iY(Yi@@Q>OI;p5JTb`3G=f}+__hDyde9SvhbieVgw+5&8rsWl}f=%4-srRUFg zDj0brqNymJefIrLdUkWHb+tjcQ^bD#B>#+!`Tobn<6xP<-D4F8`CiM_RU=di@wIX> z+lbW@Qvk?oSS7KZ+;+TJYmtE^fwUFM{N;QZ>?Zv$LMEYaYVpbF=QP|_eCXMT;+O9X zGxN;;6&JegqFSGEaB^>&PB1H*KGDoloKEkGaykXQs&kJSTDvTbeSyV_I#6JXcUkL1 zZ&qPRjugYABUNKVIN_pLfdCDKFGPx_rmhpyV-2G0vTIQmc9U$SA>;TPD3`Y^-RI%T zU{7JF922F?B;0`4W7x#|;bNoqs6;A#EjIKgi^i>Y+J`bLH4@%go$aph%ey8tAqF;? z*X7h8qcTxby$!mkBEIpvaG>A=MV@-($2Kgf+Dg`5`*?9^F-s(6WUGb=sY-;J@1p91 z`X5me?uPbBJl2%d^5Oca40a?=qjSh4*o4p=S7ZA*pYOg4PXj}ug!7j-5+A+r{Zxyp zq0~8U&@KOIJ_mvp9dK~nDS)1D=eXgBh%8<$twIs+Hf9NXXTNIqpp_$d%@gLC^aV4w z(VCso=Mz?&aUjC8iB9Hij5x8N)H$Y-spq?}a8yZY{T_HM#Kg%DVYWwnuP4(F?Atq6 zep3LMTLpxC)Zk{Tv(i4qWgT3R{&8~Y z&iP5AlDOJSh)0R-63S7P`~XY^e|rj=_zkGkOY_>AiT64@@&B897rz+opjJP6eBn7A zu$|C%#IBfSq?Bg0J5)Bbl__ZbSgk=`f{jm}F{$oR!4&l0@_U0?Ki#}_>g^YfRI%zO z5eiLx#m1dgJV8Le%yU$ppmXl*u3h)p+)dbaMQbwsNf76 z=dZta=BTxFwD4~~)}&W#0kh1y;U5y999pXN!v-QFej|L#-s!fgC`oWr~7L(LD5j!*|aJAa&i9HgdA)w>s5 z0ePAc!#UB@LnL3WnB`5p!5fyC#BJZfYUCaZjM*X96NuD9IIIjCEGT(S)}yd|76hC_ zQH8^5`e+~e%%ZpAquZ9f(f?~&sI+^yCaiQi7`8WI3FjZlos4hWr|A{iH;^;KOQ9;y zNEv07+r}bu99`snNu$#KR~r{AI?NM@F;1HQ+MIcgXCPLb{*Rl*6Kt4EGpHfH)@{NT zeYRS5Q7^~xv#C|dkP-Qp1-Ip*p#}#VzCS;rQLLh+wQ2MBNynmC-i27q@u~4IBTff+ zP6lB>-X&^zreb36mfc}^TLRShTj>T9f?vlYB`ytr+M+PVGiTYT03-+Ul=DS;z{bVV z>%5;HG|pT;G##P*Ip9NN&xDV&hv;}l^TO_X8GMHL5?B3A$;njZJ%j3RtNT60>DUHl zjn%6RQKi4zBb0E5%b$qzYl@&UAE?zPBz?c2jya$ZbCz0mGFt!CuG7w(gV}|__azH0 z`m}Gj6SCxMgeDFz#*jq13fmz{`vt~@u-kvl*vAxj{@h>}Z~+S$YTcf6kc)Os-KmirYI^Sx&<=xRJrrV~aNt zvK=;UG})nE{`^6;Gjkn0+YHske`DO5dAcISx%%!;IyoNXkv1ULJQoml;4-wsm(Us&}HM{=NCljk&6x2;nuQIwzG z4|>VOEpzh@MyO1*wGjP08ia>a`cUM@3dfQCh$ax!sUdbfDCYhBAK@yx!0P(>q(yZ2 z^QgAav|23kpYunr&w>lq0Lp(^@G7s}H`j?TCiyPDt!F8d^S@;epTvZ}u3AE`LP61^ z##P3T9zozOu|Q2egP49Rm(MGB-`QbE=5H*iZIHL|=`tE}mj-yZ$Jq zoUl=8i&2C$%>nr^l(`G|cfUCTQk4n}Hz$0+f5=24h5oC1UDny`X#Aefl8jIiRM_5x z7ia*_7<}A(&^9XzB#G4IO>T?+edkI@;nL8BX@=~Vp5>sKpE`j(S93PEg_az2gjKq@ z&lD##_k-63;sM|B1+r#$rz$$Z`Dq=B0z{KRL!3&l_t)F5jeA(b^1OHY?)z_}R1KRC zjOZV70WtJsmdlP=yuC{ba2N5X>230{*vLXtr_ukR^~-G+__f?@sgq-H`&_pOmP z*_a(d5GCD+-IV+_y-5$(@>a8bEoLGlT-MlI5@kx*(P`Pikw$=p4-U|0~2DAdwj%JTjW55nz z=d%wuO~b*>6FUc>y}O!u&P^jO5)e-xS+`o70yoQwhL>G8xIOR;Ct6(0BD}#uP+6Rn zF6O*FbOICXQmpd-$j|KE%f1ll;GkF9Di1JJ!@2!9G>PwIp$_il1t+lyr9e#&_mV3D zxdSvOdR?zn!Y~A#J$B{vHXK>VT*(?z9(`{mTPGU`3ahHg;ryp_qJ>!YRb)JK#*=M} zxZuaekE^i;qDoO?>s&FOX|BL5BhZZK5yqop=d;rvB%$Bs>c^nfSZ|#F$+u1^wBtZG zj5aJ*P!ld2y1J`wtzOd{Snx-?#p_gIWfh{+x0ju%% zLXOy2i0pyC)Eu=#5ZXXw=gk1Q&huxL(?-sI9qQe!U%T?0G3B}~PGFN>*C$3@XSH{z ztY&a_uaN!ZSBKcO@A2$AH2uVQrieCGe3QE2KZks&DYHhYr~wAll=FL=JPilg8e$c) zOx-@v>v|fr3S3 zeejBi^E}Owc*6B%2I-KHOJbqYP_|zC5^1qeWWsvg!-j70hFHsCG47L9Gset{r58}p zXa>voJP_p9&a{`T*24L>9DpLrZ(>?G!HdV*?&^{zD-#hTOEAfAq&30^AG974$rb8k zuHT;P}!KqxysAB%*kq)`K zanZ!!!NNCRK5tchfCi0W>JG1cdx0RNu$^v4!^>DOd#2Ymng8(d>6nhwZ0B|IUHLE$ zUtbozeVTcje!!$4%0WhGL|>3Re)=1{Si~fV=@yU&>%Mq|bI93^#iE)Kknm(aCvMiy zNcsZE_lDC+H1IE+5gOCN8jSFc62d8CbpT&-W&OtzpU|oiTA7%~EL#g?gWDiDtk;rm zteB+jW%C}lOl?2-rL2fX_e-1%BN0BpPrx-DK+i|yeDvl0a@%nR@832L@B{lrXm{$q z)yu7TRp%9e%@^=%mwhszbLVSBdsg&pqU3mo6*hZ;dP#1mh0c;gI%6nZvTJI4Pb4^h zz*l)1llwk24$E)DPetTK*Co=ySkd1c5C(iC<9aGgG7qg#IZ{cBs5dO@bX+V@>g(Y%qBhQdCQ9z=Uq+n|xdT!P}vC2(R zQb$f?lA|S)Q>TYI+C6IY)*Rf9IS!7i?r}ew47O;_2jM2Kim&0`weG6)kq5i4gc5Fm zR9Eqgy#9(LjUB6QufKnq!)P_}m2QV=n@78R8AXN;y&|_xdUG1=RVI(_F<#AB&#&%t zqrhAKBMYwM5~ZzgHHk%9p09^QkmDdRK?rb~q0wZ4r2Y7lRfE3a+q~V9CYbdl<7r2E zxa7Yrr8H$J-`7Hs`JrRgcM(MuB&W6KJc)@!ef)54=&FV<)^i6u+84TaFe)cV_Tm+k zGhgknt3hB?w)UbcysA-y%^{o?MYge6-Hy!>N@pF{HqlVuk6A7t`gE`wT3Eq}zMW>_ zV%9lnN$KK|z|?Mj8>V2+3^t88&Lf#Q z;0PEy8xFS;lwzSZ3HE6je5o#?eV(}AaH5uM=0GrJp1Zbhb6XJlFymP?#+gVe7xe^O zN@fX5nB|_j9w~1xGT;8BtSGzIkR1<^kbJ2|>d+nFh;J4Ntj5C_0NecRk2j#APBO?~ z0d1d^)Kv~s40ttE!0nc>Br8v0v{6M?Qq|b2Fk2LK2#syI#v}`=!>|Hb74YaDwBeqM z>J+`tT4kYirk1xpGhq}7`A7j&x||dJF^9{{u`R1sBZ>=QhF}yE#K)kO<=F4E5S2Cj zG?5q8b&Pa#(fAz>oGTYJauDAb^`E|guB_MTGg8({Y@}#W1NC;eNB}L}JCvt}SNix3 zz$n7S`@51nCYF1o!WK9zjsbIyCe^!{46J^m)2AtGI;r^dOloaAFOH#%6-4lDD+kHY zwcb$UW)eQ3+18E*I8M!eT=zphoR{*hG;)A(qTy=IzU?{)Ogw*ZUdI(w4qdKwO;ISl_xznGQnx0Tudl9*{KuquA=v?Ni``O@6$^MSqL#UwdWLQ!u_)8n#q5PqJ^-dW&bzzvM=OP6RC|>H`R<=XfHq2P zBE>Y6v-xBv`1%33n34@%d3EO|$XYPLbtYLT$}BTL6WbH?D385rxvUm7bVhjgD1UEq z7rb^AceZ|4*@YUFMwo&@Lsh|LxU>!r@=`3ZpsSKu6v@pU9iqD+w8+TL%O`E-$Ha@T z0>8vQs{lPR+%8=9!aVf;Fd)!U_cO2y_b(>ey3(2RFe(dhcT*0;Q@Ii{xVWXu;T2n} zxi^T!OIV?Ou`1%}HA_Q_!TIGnN>EKT{31|f&!voSe=_F3gg8!jQ@nHB?Q9-SImI)W8I~nLHM1 z)~%1y<^7FC`Qc#&f5jlFlEL8B^6P8llFcc1PGR2!Ql62LU{M7$MZ;3iNw6v{o6yNsbYN+R285Lp5--ba zrre6?2@A1?TzSCBHCPToJ5bQ?^2OUsAj@bjoBb{vr2f?!7){W#Oc?n zQKA81;g0!G>n>kC3QK^c^qRW?A=zAy;u=;eS0fW7W~rr^4XsCyoR7}ttnrefgz$yt z%PO87g-jOvnUy8of>2McYN&%-Ln2`MUC3+SWP7@5QT+vkkUdmw10J3NwhEcXV3SL} z578QK*w}OvjrN7bppFv0%7h3CaTE-%2U@z3S%2Cqo&L;kj9z0N29w;qb1{o&t9}k% z3tALKn6yQTlOdKERNS?(RezV=H)vCeRb!Oa;F9X-=G8g(s9$kz==u}x0;@ZKKf2&7 zAuisZmi2W`dQ;ZGACyfhovz%`jdeH2ZcegiF=^H{Gk&~7gMoE+jUy&y{@r0QpT*_R z!|eyA8-Ljy%c<$)ZMOLNVM9ODeh~iEw+73(RE>blPuyQ`fKzQ{Hp=^~M?6mKY!iA5 zjU>51Z2NpTPe8k8aBC=gsS;L%`1gaXjt_>%hgn@ntA^MtxMnz3l#6f=FO}<|TRSKp z)dCdn`vucjx8>JlNL6<99o&jgdXlJ=8K0sn3+u=Xp~r3jC}O;Q%Y1f~ht(^fgniB7<4O3zLEx`&98K|P548V~kca5+4`ljc7! zzAol4p-3x&1>yX6y;_aO7V<+bRinkX4M-Z@ZnU z=K6SPKW;#}L?BoZwl@II&y!SiZvj*bzzUb`%9Rs`sou18qv*S3+wyza#1-Rl0 zJT4H9NTS!1Z6Kn}@l|Zeg!kPme9vr^DapMIu>JRdp)jePdwmMu-Ac)_fTK47=G}@- z|4@eJe$x40O_?>P&FyK1Df<(=PuOznrz^3sLo`pLIaooqcN=^|*q)T##%>D>#JDMt!*!zx zRG&_A@K%e{J|23uCyYU-WbYtCW4A!y!m~FNZdS zo>|oRBVdjXN}S5Nl|J{Up&+3`(nKBSeM*q`bJxS z1WjuAJe%<#jD3Mv(j^qg&Yyhy4_|BlSnNQVfA?0-Mi#5%_zez;%g$P0rvmLWik^5? z?^ zs3vVYd6EVcJy%TMOHO+U(G$!oR~9D-mwamzNyiAw_q))O@=Y4pcS4#y^Fy*#asMA( zUl|qE7q?4;lz>W#fOPlJASs;+44p%F=b=$bP&!3QVCe4d?ilIr?t16{Jx5fbCgT23vv%3*g`Zko-;q{ITi|_=S@f+tWBW(_ zmPm&x?WnwHr2A6bxZy=Vf%si(8smc#BsC&HyHF8T<*2J}0+f)pxn_}tO#~{cSsJcFiTWAzhW$`&WC_l=M&-P~2 zlw+&p1cN`Y{;>Cwj!ACJD!#%JFeIjqXa5C9WYYyuzLWZt-x{#zg%_j~|7gpp8pS0@-djBb*nGCTFB zt_6jv=O9w`+o0^}`<8d#nK(Yw;^6e6i$ppBTeg85KfbSus(ki!{Arc=z7ZeiS};}8 zC$cDtX)e0~N?}=klbXXUn`2~Nr1bcU7gKWcHjh6Y9hRs zq?Q#Lj>I+^p@>nORims)qps453Cj2;;=5{9$`XsYiU(&J`UmG2*O!Z>OW>TFFCDs) zp{*24$ie5<-)}d91392~<$$s;vC^D``VCB*mYZk3cJtWPmAJpb`V2mkeDWTIlQ_Cj zqfw@L4W-8P7={lcwD3BPV8cG>hBjm?#yLaY>wLHOGdd|HRg^;l!`sGn#uKgII+23v z)v<|et2wKlos(DlsM!D6+i9`D?2?Z(=97~}(rEZGDKbBJ4!XOE0^;9RQ4MJKP3)|bn=*31RrW=_Wdo{f74Ue@OP6GMnxDziR>730g++KiL@ zS#r+@7pnMcUSdM42zk4rt*Fde8>!^&^`Lt~FoTjVYZ&;XQqf8&dZj5x+*^;b;9hUl z&Og+iqj64RT$7|}4uF^K1FX1f@8x{CDFZNgl8J3%*p3!uh^*)eGU_M)mInC<9ZEdvdTs3d2>@_t<)t&K);+@Uh6BmWinWtXL;`)9Je z?peccTlshhVrW$-g*Mm4o;!Ih>KdJUFdp-!E}ngW1G{y%bKlQS&llURBc-}VwPJa_ z%ouw&fb^%Dp3r7UF#3q#au2+Z$Juq1$}^1q2|z%7;Lt5!qMVnf;`|Ju{T6Lx_9Nt{ zv&~XgbeTuXk7Lan2bf?-Rgyc)NF5h4O`@K>lh>g3(qyFN)n2c*e$+m*SDl3b;qIPb zRegp-u*dZ3$FGJG7z9GZX(NP(Z*WZ;ywlZAfgNhq);dI&Oda2(WsgE}66=m;hwf}8T)*`kT^@O6~PTkbUJk;}MM zfXLF_OK=&9ieltufLFp>x)@&CI?xZf3=?!gap2j z2~IH~hZGMI64E0Ka(1zpnG{U)`Zo>%9I$Ucq-_SZvLEKk%cZ$St#=ykg=o#p=^U?! zP$h_`zXAorGZ9{BKbph9j>*66sDnF(ZDBTVn*aRf&RqxDVlkNo_OdvU&q94hwj;*o z@)vOhzfAWX9FW@+D9Cpi$tO~c=yH=iRY*Z?ALE0-Nj$z6 zxQpe$H<7P#4yurm!|Y&7+MC~das9&J(6Dd18@4*A3yB;mbDoEPhp%6~pDFFz+n`ij zLdzM2x&g^dLa}%I(Z<#rD*%ujJ@%e;xYFK&ZamnoYtOaE&a>Rm{QX$E1+{r6teh#g zB#WfUSM5R*n2@JW7hI%gk7{bCBf8p~1>=_G=d$gPa3-RhXS40>(K6{HXIt5 zVyoJHM$EhUcucta9KFv;0eJ&5D5(;dOu1^Fp$IFf*Ner}$_@o%90J4Mf!;Z@a384% zcPx=*``0nLMqf0Qgs+YX!y_)R**y2{*S)ieYj6KkX|9x{Vf0cjt6N7T&t?R}Gfrd1kt^PW3kDUKi zrSqDde!Jc_Z%Y-!0aE8g^n_tNn6lHuD60U9MGRQF~1~_)6Efik4d*+exrXklC zcp&gps@EFN&f0!lhXtjw9@6%H_c5)}o%bE3N_+zdtn{{GA8sY+?U8RGQ^kwJhmyts z)}Bp@PXUx1#UZr>W;TDAqunlDp2|^W@H=Xg4&R_e*nGk7FI^vut-~+LekQawuMxJ| zHUsPgXcbFe&wSW93Q8Wha-v&Ezk)FUAIcE!xW#wzwA@?Zq8CnK3{4Kd@kDAYluL|} zEFAx;ke^3{wG4lHXM?#%^020vfwOT$X{ijV_F=zi20 zyJJDkP!p^a)BMs+8O4lG9|d6!z~LXL**otVg7XngCEi>Q-50w1A$pr0fB7-@FL1)F zaKjuuoJVNPc0=k{XRHgO{f6h~b74nz-!!x9LfA3~f5;mw5< zTL0ni_tRW-s%@7gNA$N7B_f{Ch7Fs;nTR&)xrY$dd}n{0syw8{`XnIEez(@#ul;D? zhycpHG;hd>^d^PX_Ucvd7N7v%xNE*O8u-P+rt(=D@hF=n34Hi|Mn1hK!J%qF*YEZk z+nF(}Sr_;Fsk@+@>mC|qRF&hqJom6y4x<_quThMnTT^>RAssa_1}co-hM-a-X#cE~ zTW~(Zf4x!0UF~`-(KmwX~R%8{SCIt5p-8Svmcixw*{p$v%b;yDvG*vH1uN9OHT zjFfhWU}<@(TCUq0=cZnIFHw?@F;2NdfpUv*su4u!Uji{(JrJ zL!1%MBk*p6#+DgruFz&Mi6Wq?!~&c&nuWd#e;~p zU>lR**Mmla$=f+XU6+LO;%x-`HSvw(KikJ#SoZvn7OVHKQ35I5`jyVXhStV-lI*Se zM$Q=D$foNGqyWG+&i?gk;y**;z2!;=74&=NykZ9xuxYC}g`C?~^lq6AmQ3NXn$$2K z!vtIvK}$l0lQp>*TCz?t^P7-b7Q_{mx9da*fk!;5eP??U2M(IE6v-WCp64z3R{j>XTx7fAzD zHJ6vh09J`eSbd8~z=|U7g6MUcwz_gYf+eFSYw~7GnAW-rxL-^x#XLE-S4s0L4e4LZQbk>-#42NZ4J@}x`?h4}@ zt;PVe_%dDiZi0um?lhWBg{FMlxpIbd3c1!fz~Z%Mn|&78Jp7M7ya3rb?B_^1Yb>_4 zxY0}{oW*88E;Ukqlu#w&I6vbLBb3dL@+j2wu4C)A&HZN+4F?Cbl$X89X4`zWLZTH% z5|-7vW47h>XX&>WuQ-@#G6t}o&heK0d0$`=F?^wHYzME5@~V__U+g5_FtVopszGf4 zIyg(Et(|**#=gPOJF2sr!TD|dmn=vOik|=8&|nKJK^&KSvGw%>F9FpKLo)diYq=!X zW&f}NKR^aceHTi2%;uFX+?(eCHwKHvGw`N&Nk7W@P)yeSC$E9t#w_=fBpGjg9k1yw zU=J4>&EUxF0^i^gh3=njA+`^CmS^vP{Y|`(tIf>PLz{Vc@0wb%xR~T7!X(QS6N#O( z)y)oUr<0g`axiT(_Y;+);3arSU^FEh=FrW+ZS^Z^(cN1jjg#j0?GW4vEwa2uExei{s&>o2vAQe4qfvaK*+5wt$WH;tEAO zy+1~r#ut1~n^tA?Mhi{WER+>){5)Yyzj^3`J7{y~|L(uS)`JU|W{zCh+oPHqyc?N#NFKfHLvf9)`{YH>NYEC{5nNC8(3=_AkD z(?rm{~sQbwq?$OrOy6C8IM@ z$BDc)Ta2(IlKUP`ASY1^O2REGxDaK^z&eyLtTXCTa+7+M|3{QaW};E>O)~j;B}o34 zdXpHLO-}pJL-iEuERZoUlqB`H&bt30AJovRYU9#o*Gkh1CO;g8b`vB+XR9$M!`>1% zE(N9r4?*!0FqiN~!1F;`R4wHzOl;!n+1mM%N!3in|EE4%JGkj>YV|43W9f z0)L|cH8<0Yk?pWES~XMa=Ryj~II2asqg?E56oGIi3!R_zI6uve8Dc&%WBJRp;4lFS zVJv=z$epf{ND$K!!y^uFRnPo|+W`EPMjGEo{5ung>D9vH7qZO1OfKUvICgPJCG=ZgRWQznLb2 zZV2_k&Q}gawnDF5+xb%KX;4j2{=`$xEX99iL325lPqyp?XT3V=J#F!6+k@2na--1- z%J(N9%8IP(`k?L|4hov6}nxY{YyDTcrr7ilzwWI(nd=E-v)nvQEuf8PKn|)IlXw03tHjMg_7u2pavxQ_h z`*YVf(YcnR2+@GuhOZGkMzTX*)G(89HSmY(eala`-slyKbGk=@ax)p+^tR}Xc(zC1I;% zy;qY|QfS3L_mMY1=vc0@vf@7@t@ThfI|aFXO)~Z|j2Il17W{Nz2l9crpLp%E2;QrA zFH9WQ{{32TgVhDIO%rUn5^f&3mdOzH`Oq46v>#vBtu_QN33j#n_%~LC0zMCz;gpNo zt*pt?9$Qe~t_=K*Q9@!if&*wKpnIwR9s_Z6P(!yZxDfr;A@lPI@34Vy^sJzr=2#n! zkA^D7=vPK&6}wMb5mwFAG+E#rKU7{##{r?^cTU|!^_%235m6>ODK$$!DlBkwlK;He z-Pqp11eOjJjFs-digzrs0928*6)D%Dt9BY$R8}8xa+HuMOJl7Rt}mpH7BMdCcmaX9 zA0={jTm+eZ`DagD)Nw|ObkJ0<{=6w33XQ89X_cBaYsid+kCuEpj%Wn*HxvRI4seXH{(`YlE|RZbH`dtxy6c$BL7bFu?xJKr#Egt)>#`W zuLW|Y-qh8n2N=SqHKLf8p!y8ac{&|f+{0{(3(+#$3XSkCyJTt2!`9($)BXN5?qK~q z9`OUz?d|keJ!6N(Z8l zV|nYMR((8K*`gNE$}HBs?5(3Na`hp3M#~8j_q0QBXpTR#)>!x zi|nP;9dX&jUxjEfKNAu!iwROk46)=nyf#$zg>W|L$}guM@quCEiQM#;!_bKm#Lr@W z^GFI=eb!!$OSKhZnsHb=9|8lo5Oe2@M0|bDdbqyl`JI3HjMt;31dnzi+%0yRt%vVw zzzDuToZ?~NQB`YIeUWXSfZuD6nbXAOm2R7-yE9#l@XkaICqd+=?n{h5{w^8iz(e}Z zUbVT~h@WsIF-`DPaljVAkqkoBE`t8=uGRO7K?;0ids)#R zLBqY=|6JNLq)C1yPNEWw=~ChaQ}Z94r%(b>a={4{SM&~Dppb<5UAFlQoma*p71(?Z z86(Sm=WzOdI}bMuErUSUY^-mA+Y7zZIQjvfy*e>jn zQQu+@1aKR9G$xR5JblVKVYh(f;Y$-j%G@l}wgZ^|xnB{P*vk>Czj!dXrH_3-+hPtBCk>aDeD;b=g~ow!60f1IgB5jKLBzf9C@kxAdd2Eq`2g z?5uR1Dk08~MU59D(fOQb)bOk>z&7RW^OXH@!`97nvg=O~Nq~+w2?U)PA>jxvX0$ z14z(4BN{UseySgnd5aFP!KoS!Cw-GA_zDVQVI`efx_W$9ylQgJ+CjI|u?{EY|Jv>R ze?4~ilgi(qX(Wv*qA>tm+#o)_|I1D%qh)E7MJR^W-B3l1okQS0#~-2VlZV!g9&DG7 zLl~Jj8SBRi#i`C0Um@!Lw--Wgm~KVobzC25)M%1q*|yv0A9vc!9&MoKH!lak^U=!_ z%;wwk-t)@KPYrMe@atvt52z1-AY-spW!0kRriMCkW` z7sHpEmFI_*Ta0J9fri{Hv3p^TIdJOp-u~XxL3h&8OK{TDf2VJIM(?fFngeE6UM|{R zSYLd_MDbM$Z@XUZ>Rwp$Krc6KFFP+6&j;y?%nGAc!eMftE@!WqFY<{yQ;p=c1}#AT z;^c*uNm#=5&-7Ve|Fp+663q)NAkD=ex7dB&yCgy0V9dA++vX@iP^M<*?V$q9b4J+4 z2ltmr*!N}~LnY^C`<7Pk4kY0=)&<7Pv;AUOKejR&Zd;>##<}SYhqQ}OG1~@F3u_{? zD=oRN^0>6QpCpQ^4Kg+Q7jVJjxzmmJ zF8Sx|5ba>fE%5#~i^cM0zZr-@<1hzFS*1)Gfmn&8#NweHfWf7{+OMKlWg}b&- zO-S&Cxfi;-@wAD1M`kG3kOF1X5(9b@7PB~uPYQG9UTWEb%n)rl<7Fq7JT5@-MzjQC zJF5*==buX5s`*`i?xJl2A^w8%mN$gvxt7Zi>m0P6GAn36at@Mzw2S4l?I?nXH1-LW?repNA-Q;}J4~WaAX=+GSjJLH$k=uSGd6a7HaVOyykwWK- z%`v<%!;KjYxYWsIoSxFi!+?}^&lwU-flYiYu57#Yf-PLE`NkP42oWht;X3&&goJmV z=`!syL9^bzWX&uuB`T!)rMzq3#vQ8nny`$WCgKlUC=9e!)S0ttso#EA51`J!e%2XwQaL{qWe9m?q==_{&z!6$p&wG+S&lF&4lv8?exZ^(fh$S z-sAky*H+Au61loc$3PgXtSm*ZennqH!8vIOOOyQzB)p@#BPWAZcvM8vdx*g>mxzsB zF9>|}HjG?m4>k9D9?@vKP5yH0Jk)@yWggR}D(Z4wpnUWVlUTd~LyA_gA9KZ}JF)!V zSmS^1Y#4;FxB4ZcB$;KxS1BcLl$(W~YhR(8UUAAaH=hm|GXUXE;!AG{9W@n}7FlxY zjgr=+yKg@0;wxHfgL>Sne#6uX?#NMVX);-?KE0T8$sC?OU_9 z%QrovMbE>OO%m792%PfEjbKM{Q}<#Ox#`_PT`oiIT0Jb^#4*Qn%a8!hgxkDW-B0AV z$72NxDc8Lkj(}C|h2rOIXZzyb6|}Qsq>=~oe7utss!{4?)Z0Km<00!uyHjL~VD8LL zR7OVAs>M*39|Y>oNPm(NVHYr#`M_rL+jwR_lCU?at1>-7UyPD$!cEW93%_#Zb=LJ+ z*>A%R6^$LkgHCT@Mvi?e|Ep$kRb0?|ubqFrS!Y}Y;3 z$t}6^P34tTz4HEg_XsLT*Y$a&?BXLjvc!h@zGsFl_SwI1;qo8%ECn3bz;zLP-}PH) zKgl5{a&nQ@3J81G)cj_b?D(oz^Mq0R>+3B&gdJe;0`@QT9aLDj_O8&YnD`KG z*79@4G2tDTv@k-cM^YLdsMJX!I%mF$7af3UNoziQj=RfH zXkH!DYqA>i3AzsJ<6d-`2eeT=UB0h$6^ZoU)DQS1Yhbloat-Z|6*L${H+F{@{xDvi zYw1z4?}E;@JT%rBE2}K~F2U-+sGc`&!9dco-_mNHhasPK-Kzp5CK=T8Yn}q-0_)ni zOpS8gu~W?JmEmatVeGO?;e0Q6w^A(#ist36;loBvbiGVSUGOfZU-Mz{T=)s^pY58b zjLu%iK^I+!*J~w7i|YuaCfVlVESd{=2Z}pqln7d@A;i3% zOy*&O*`0HfSgciGHhUzm;5}~cnx;=DJRR8YawJ82lzub2y#B$>DiKH-jX(Bwn@m$u zJz_4JjRf1GaEqhrkPpv;YWAk{(FoQnwB zy3Mm|=0Yj#8G@}=OS*Ed^>G|X@q$r0O>*Z1)kyg|K*UP+84S^BQH>iwXGdL!eh3A# zh-g7n8jJAAA}SGHhvFH$lD@zQy$Z@|ED{(6r6QE&7oc?$DkGL+#$cy|bN)nAFnCYw z@HXIl`|{f%U5r}xnqTri%wbSK56^1{!CghWd48Ol5)o_78 z;vUjQmCg)_8Su@~g6G6BlR=fZJh&olZtVt1CEi{S$;VX1!~9a+gs)68_BX3d@tI=i zw&^i*%xa!0ujHu{btcjm97pe((k7?8M!M)rdz`!sq$+VUMH_(ODD!+2CwYNb{%-4? z#!EA6o0PV*6lc_0VoKdl_&93O>v)BECBTAW(!n9vOTx4#Fs!dFPMJM_slF>RoSiJu z=NVJ7u&m-jq#0X{7WtruR~55W9n)~8%xK8g_pmc|)U`b5;IDZU(QcDPe!IYuK4O$J zmzhnH*|p!Mdkgd_T&Es_9=p~B6%&+w{7TD~eEM%f)LF-jKa_XKtnZePo;>~Gi#-7O z!>6i9_uq(XA8PL3U$gIdG)YtP89!@8x*vF2DvNnI;%j3STcS*FeA+cUED@$1TWWoW zuz^dH+~r26hms$EWY#aY6fa{GW&YtMzP@WAuq44A^aP~UQ}{!+>MfNu8`__Il9k?t zbO1SlIrUw>?NT=>77WMZ&W;>`XPOMhp%wLrN#;E;1nq`%Mf1>ggS7+1-0jP^_(>RH zc9!EF)S-e=TEXQ`d3laY)=C_=E4BRC8rvxv{$%Zi zv4e`sCm=SC&?rcI>MOs~ct%T->fnK*)KS-Bo?+3e$4gWIlCL^AW}MYEb`-A_xJ8je zCUSsh9F!Rvyez3^W!2*OIx~tmtvEEXVf6H~Y1cCGr{6ZW!a}nSp&$I;8S(pcvnZQn zzD4+rFPx+^FF0k}Lo9v6W7WKNT-?JQIu))`%nHnQb|-Pk@b1sh|2sSU_I399wu{7j zt&VG$ZQkrtCz7q7_h3OwrY)8zZ|$M_=SUQC29F8zPk63K5oi zEeVhjO6lktW`?24+IQEH2kY+i?Z@qj&S$X7`ZlE+1HEoF=O0iB-&iCha+$iAR9&D}`)Jdr&H2yDTZ~z3@>;x{ z-WJ&DW4LjqVdi*MSfFzzs1-^q0E|vc8~6kz2I3U1Ef^E_%T_s{yQF{74fG0LBxmiz z;tH|$psB)Q8=tRMEUoseT5YRRERDX+E3WD`&m%cTI;dZb%+={i;?lKO9GH~!GOhhL zjZ$|CV{%}^Y)-@%!#VGzA@eb_RhBcaksA={Vc$}c&R&ip%I)AJ!ah7x08p`lz8)|O zk9H?vl|h*e3|18=HxDE(nkjQM!p(Bf^eyp%zCh{*JEZyO%P5E33@`l)qwPC0_KUC0 zXu9>#K7}N=g5;a8SkM0axgj}WM2;S=w{SvnKZsL{|10&4++gGQAU9)^ zJ4Yp4|2aXRvVzmPJo&0X6JSb4YW|BBoMa=6np~TSNE}^YjOc1H*q>h6DT4a1hYiZx zrf9r7a%(;2pl}WIq!o%7-citpx^|SXlo!b83TiW!Rp-_bh(>Q2@aJm%K6~OJ(^Aj~ z?vshtv*bw9J-w}`M5+jxEQ1_GHk<`HAb!5rf%gA-H2h^do~P_kxtVqM4CLrez#r zY=rYev5X~GvA8dW1UMAlEMeI&n&;`m{bBPYZgOILop(CGZS)6!hwx0#h&>L%!|bAen8Uypt5jPH!UiOV8bPj`P$$j@9geVYfDLRCV?w%jnV zbchOE?V4+qcl2%idz-8K1T#EFg3YE`Lx+la#av@rp|BnqW5|8QHaek`=*{$3J}VPM zO^0swnQkOcV1m)0ZpKsDgItS8ae<)}_I=}=s}$8CABP(?ZPl|vZSx%_!4z#a=3tBs z#oUoePXC6$;TvH@@0z}a&jSH-l&zB+Nua~MAZJh)3yP5@ElR0a8!PM+MHSPK-uWZQ z#*isp<^802E?<+Y+%6xZR_WS<72MyI;9w%Uxq%A}*hZ%D^dw;KU$sMqexGn~_qsYJ z9&Ly32QR*x>Wn}HFk%U&jg)0>yy4bh@r~c9MnB6mutJ6)M0(o!mZSd??0{(pj#p*E z4_kw+;oulFXI-9eX(3n=_E?DbY22AsIW=KZtrAoY(KJXo1@G9|y9qw1b7HM`C`0^x z?RwdOc}uDe+-md8Y+SWTOj%hF=E{~Omj!OqViz@XSBKAj<@yG;N?f6vbf)}_8o_w6 z@tO|wl$N`uRNkA(cgzk^8~x348}^jZp=Dm^0bO}P`>RQr`hv|`*)9_5&w&#N2igO3 zUHYn%n1P)?UQ?P{KB+wJHQSdL;NexEk1GcP-3E@s*0xbR3EXq{J)cd+4^-ail~b~) z7CtrdiDqhmhlj&9$eNhRBZ~VQCqKrl^v-MdB#pecrs?fg8=4cJ5hU#Bmofbt10twsyGq_BHIe`7x!BjpD3f@b#lk_HEW zRUqlgTs*)99qP#mvRN?3e!k%7Rx#a@+qcP8?f=|an3YrajsJ2)!U9X7DMGjWd4Eu=w10C_XNcul1&F(JKZK5$el z40}GK`s1Cz@Ekr8`u1W8RR#BvYrw26_Xm)+_N!^UERFY!Txo(cW*ZqA?B$cGYXe2i zv)Rn&|GW?IXL}+Bl{bZ(;q)h@KX2lZu^agnANWd?+9V2;%a^R%+8ry#n1s^MKNJ zf4zfgBiE?m*jb~}J|2r4uA8jxG}Y9;!4FTNH;Xkvte&K{TQ=6#5KD97I^CSzC!=z3 zyHNY}*AsN)C52R(M{R(4FSv>_fsqmV25U}f!`=Mt29f_DX^E-zII}k2$7M7#hxno& zFiw{|W@oH)wcaB-O4$fLg=EIBi3S9yf>LN1YU=NV!xy&9|FMl&z5ZS7v`MRGc-3%q zef;yA$Ea1_oFte_iH^R7&A#vasZcHO6U#uGHVFf0u;YxK%`z8AZ{4j^rg+5!za7Y| zOlM|;w0o^%A0x%S&jyZstoW#}cCet+jrA3oPlaW@^6k$Ie6F^uYs>&&0j+nom+Fn` z{s&8n^r|>01B)yVcI99^ecG6--d!Tz7>c&_B-Lx}y`dso<7HnjY!}Bk693Z^GvTc5 z`PyaP82H6sP9g(d*8Q~G+lXB z96l#G={(dW&*?#MeD<>noN62A0a-^9j(pZL_#u)IU+fUE&<{0<3X!(jzo>aqlIBOWMx#_0+GKpGZdMS91(|HAYHTk#J%3E(Wvyh1f6vTj zGC(1^_X%JjF?(A3)%x1y(iAy{6YB9JoOco>Cf-{Z)zqlh%-+FX)K2 zjivlCA?Uf`fl5uK`$&W&1nMRNS?J@)oCNBS6jt9tG_I7;Dv65e%~5IRQb%#({_dm_ z^V;S72*POMErPLlny2wiE7CmcoB(VyB*t7)v1ni*154c^{}dkHHV$$7=2sYNBF|I8 zQ4Us;<>cMJ<-+c@H131a)y2N)WnitJVHGYAT27gY$n)qE0@BAW8b@MA?wv$`>{OSa z`CuxV@%+R=_%$T<+DDoESQeY*h^g&?W2i(TU(z61xNU?A)!^nA&U`(}i*Y!swgp5| z7@qIi7A>kCY-kx@glcjbCx5_8s89$cp~=#(x0lc=QmN2&P~+As%tP7E4#A@tt=?P9 zaWEAaVUUKSBNd!`hhyVChe0F$Z_cl25#o0;7qPhmn>$#h*{x&2X-eI9R1}pUA%uXh z!7*$3Z+f&T208@!Vay{2(K2xV<*w{d0BBwxF>$sS;rL6PUP$LL${9Vii$}PiZcH3S z>dO9=!ioXQ==Xc#6Gby^!|e5auEMwoUJ7?FA>Z@FVgangTMFDCpC{R?!Gi#|r�D~`)GyP*7Nh_tzk?pjzD`OncRyXyzIs@S>|;Z4ERE) zhlL(_Zc+pj1$I7~>FYl@Z#=5^+YOv*ESe_M@6DOKTOh*dYaG*~x0%cXzsUM&rC2dVe<_Dz0zt&Yccc`PON(!+{= z_v|2>8GDQULEN=5gml|aPMJtkpEb#^tzrbj-oBA`#_joO9?sDyQg(_!2;%O@+P+0# za$>9+F{NUU#nTjn?kJK=9}KdOgmN^k5<`;j^vlw}=UmQqH{!FT;}5Aw0X#G`SnGFH-1+UyzdXInTdJ3v!*}AH1||Wv)EG=dIcFova5X))Cl4#E9J83J zu7=cdU9Ctyn+jZREvqvKz*=I3?}9*1C$BR#nHFb&WmL5s;yXo!pfvo4j8?O#fgDC$Zb2|M8njC_C3eT6prmEWR&&f zpz9lEu|G|RpeQ#b0|gE0%mJ72Q=zS68hH8m1qXjl9_=!cmcj1L8^Ti^nSpi*?Ccp= zuL~4-G`N0tV3+L#)hE&kvse{OuNdz4U&-;Jp`Qq#?ns$qlktBYEAT4&nf!O!Z_h7X zLanG`=`$^!f_Y(pi7sR*y{W$6QnmXqD3UQ291c|;WAwG82K2IH&oSyU@2v1@A(`6> zC6g3fs}EDQAwjMPs6*hck&l6X)Omm4J0nbyDzm>k1QaB+<>3t(VA#3BpEWSp#S34-t{l?2~^OP@|UCN4#bCQ@Og8A zN9X>-wmq6B`5AR=N1a=5=V1SsaVs-cMunilk2WPCfV}3bZgaD|MFe%ihc3Mpn#96O z{h!_x+h6=Xa@tRIIm1|r-6B8oHeqFv=9+y=V`_&5)q}2dbilkL+s*cQR#xU!lQwSr z0?3@m$ROwX=M~)eu2*l0-1#d)uUf@7&aGy z54cWbz*!I!QxOvimhjpTL4NhRXo2Ok14{V-+1A@qvWLIbJ&QArn2NT?cCIc6OLh80 z7)!&`Zg(198tdO5-a>1Fa0CC~!)p;xh-Fhm<>bHchVJdNf`&7{`EXIDa&c;fzmm!G z!ra5Em_M)`&@|gj-dKn?E>2tISc}TE({FHO1DDn)&H5GJFBBFi^GcXOPRris|E}G8 z=tfs$GGTFL^HQfg7?W_y`9en{{yF{Miw{|-63_p_-7_#pkzRdYWz|_a;7+`vvlc?` zXneJy;aFNZg4w^7@(#EvnMZ$Rt%E8hFk}w0|{UB_KyS1S)DMt+82xV-_)$g6Y8O9f*u@I6>P@?zq+Os zl*RNpfAo8~ju?fjwA(n}k;tkK7@lmM0dNiDJK6Dl2^)1x;K$KeTU(q+gxDD9Ox=C>HU~72%Pb*4X_!- z*Zov-8+|0q!@a6(&?|Ot7jY>|p}%kVvCaLy2>XWOp@k;}4)sp$t8dKaYT?%zmog;p zMYA%;0Ix6l**U|te7IM#wo9?;T|8@E?fNA*VI2qcK)hW>YRoqh~4cBu-?AnIWi^v zX0FzV>}f|We0BJ(b5hYKU2LP@Z-#W(wWD(<8_M1x@U{Ncsi6F=onu8gWg+yI<E?HCQXLVM`F6>jbEnRWLeqlIr0$=px*Qd=bjJ7E-u`JMc5T>3F*& zzPb?lhQA?reT}6}aZ}wfXsIk?8>KxNaq-bKbO&yEh-3M;S3ZFjD4A67CD0x((ktVA zDUq{q4b#}fVy+_PO9L&hTS$K@7{WvR115meM( zxXFo48){G3jXzPz`mfpON^LPDRL76kv_h{sHQIUg6=~#FJH$fyOUw9;mlrtvn+jvE zq;tdI+rgzY5gk4TAQe-=taf?LTxJ#moB7>Q$9l9<{pNg3x2$21sL6w}CAAnk)arUV zkZzmF@Nl`~yz;oFhW9?IEjk{TQOPuoHE~YQoKfg#t+_cQylUE9-!A-PXb+=M%Zslv zZypKmPp2Akl$?XEhbQ)xd_|RF-PZ#(%p_7=bD@uF651bh0eKS5rI$v1xu?sDwh-f| z-O81!$dxYN(w=D7JrK)B{YD&@210%oUPBMYCs*yaVd4tcI|_d8oVwFB2W)v+JY-#7 zHnY=hirKtecKY7uk9O+))d{*kESMzOldg>g+N!JUZdG{Jgs0eOM#?6)CgV|S-kyjyVWszjb%WlABs8#KUXVR)LJYGq>42NO3;%^cA! zS^qW`{anW8BuR%~ZNZ&q?n_q*d-DVmKg{E>PsBWrsm?le%Ppa({na}(&CU^%7kPJN zc8Ch=>LHC}cctG#s1L#UkyQ-uZDNQop);ymOX9gO(B?M3Bz-^j-U zHO7HV)EhfhJ<>YGGlfuI8%ghI!$JQ-tX6m(x8)_gk8+JJu#%51y#s_J_0o zI^dCre-&!v?>|x%$B|dg6@)w^Ji}N-~L@`B0ut5dLLNUi|0{#4(?BAue;)v|NB1m zG|cG|F1?)~ISZ@Mda$dd^H7|j5&*5IzGeZ6Qyd=b3~bUUj!xPAO72viu*1$MnMsU3 z@%(rYVA&1vC)-w6URY%|EZ-3sUWs4vExX~c0bgM@8)d?;ULSrfZt$bh%P}~Gu(N08TB*Zs32JWs1NHvLSc>3}5Uo`+C;!&zt^2pP z_ooTg%>~}5%Do&HcC&3%xL*jTJoCqun%_^`WEjDB&NuN|&(G=ytQG%5*H=ch(MH_@ zE$$Te77gwcx8hJJ4#Bl(aEGBd#VPKT;_hCGySoJ_R@~ig-tWh^*1c=pKbbr;nIvmw z9y$B$y^p=c_{uSsA=+vCxZto$U1pDLG7fIRmrfv~W5sf1coyKtHmi1UEj4GUYyS<0 z@u#`mVA7w^c^beG9w$T-AYmZOe5TKfTj&KxeJQ_Mjx?DDMs>BZLNc$Y%DP2jt%jqR zeI4s;CcSBs5b|Vl5Knsc>3#)d?o# ze2DlG)OUg)@CX?HQe)|`BN;!cu;`r@jn4FQv%eTv!56I#E*DK~6Zwel8idFE)w|ij z4nD;xThUHbM+EWKlAGo&dqGoub=dAKI5wE*;K(TTFT^&Y*S?R@cQ5`-GRwNdQlcD? zdcY1yB7_jCm*Clfc{3~wc(lBqaJ;jVn0(rjs3a$hC4fP^4`!si{|dz)us^cjtQ-+A z*b$P0ZQ%_l6TEqkobhaEStAcxChi8q1;rXNept+m!CNh6l|&V_w~VYRnDL|w1e}Pb zA7iDO%u4NB%<$2dlJNfXX#;sF^UJK(1b?Lz7y)%x5RK6cNJLL`yIn$$X6)MR8-?UV zRv<28Z9thjL7nBo2?BTJFw^de@UyImVsX2vn0b>B<1&)y$MJNhq|$4tRcY?SxiH_A zhe~{F-J^IXkDXs~%ozflcYMw{U5-1XXn3=ik=HxBa1YGa4>k>-%g*d~?3& z3)O1I|B#ELukp$pW9;VV?_$^n}0`u?B0*ib;z4?6wW=LWcFs3Ncm z5e{Cu&rtUjIx~ZzH-m?9p1wx<)w1*y@Js zlPcpES2zX^rwl`!O81nWMPAIUEF;)x?xtLnX|s- zZ7Seiqk%j!a>5x&TRc&udo{p^WJbu1IDsd*PWf^XMHH+@1ayc%>w?7(Ykh9KKX)12 zR*jx4WuQXxI*e>)69NbF?JiOQX4z$y2Am{yVHuh{g@aJ&gbCpv z8ztQ8lMyxyu1th^k{VZ?1pd4LH$1c!uFr&xi|Dr+HQS4R%BL!I)VP5>GmHF#T{B!O za^qPCZiaDdRS0}7z$w>+8y=LQ-Jy#k_a{- zI=ND`KW9qoo{ux+bydfUt_)@r9Rk$(VlkTN4H?sJEMG@)OOZOVA&%tJDJGY^RsR3J z?XCrI`^_ZTOR~(w9(oz#G;aQ&TDbhWXbd4aVWUgtx9=H{3FKb&mOm_rUNg;CY>m0< zxh88*w7-*Sf@Yk~%L2BRTQi<_3V`k0YLA<`{;}*80LK6h2ne#bfAqz2 z@yt%cDsSQFA!jqNvlSX`T_Tg=dj|=CvGic?+);}Phm0(T6}*d~=j2)j&V-lK5eM1m zrso7a3AJjXMH_l{Qj!V|Bg6*CJ%grPc^5f8dScW=sAlQWW6sf+TsWR3ZlTDkWP8z| z`C#zdaeO2cHU1u!-#^2E;G(fRIGZn6pG_hq5bG+nhUe>Byjc?LU(G+|_@#fQY|vX7 zJP*Wi9B+8+e>2Km5sw>OPKFm z2}b`Zd>eF7Z{3;iJZe6w*N#H9tkiWu@^!1-Ycu!~e-RfursuZ`AW+C?BioMnBaW)a zw+}OtXrzARu$j>D$}??k3lcm)t{GvE>&nE+t1B8UVm#E)XYRu zD)F1H5l7PGd)PY3nfhFTV_t5VBk%Sw`&mDbhpB!ij$QI|Yxta1ljI3?k)fkb$Y~9& zhH_5NNnl4pGgtR$>pjwWJMz%n_X5mr+_aeK&?Oda9*tB6liOJ$%=o_cWsz&lcY~%_ zfA}PyWkThE(Xu7bN0zj-qjsT)EEuCxHNmMc>TV^GF4BPG?+!%>`Op}nFEXA9qt6c* zYi8?PaaGL&4o!0MwTlEaz%;D4t=hA%#xmLf!6H03PYLwI$Mp%}!^w2TrDBNoQ}r0P z7Vk3Zx4B0c+0T&q+91LUs*V}*qG5`(F7tX^gm($t@Sq>2A|u@{k&A0jf6|JWUe-HT znge=vt6)Iho1j$Uy@3$xInJI=t0y(LQJK@4%lz$kV#-`W5cL5&7%V{but5MtDpC#m z8g*IJdm%E~pv+MAcBO2nqBhR=*8=iC1!eB1bK&@C05{j@OU zpVY`ZOfGb(BwVh|34%w5#Ox(MEz#+^HeiFQ-WUNMmi^!5OWl9v1K*_6L8}jyrCmn#>5rbF{t+Md>~R#Zr9QPat{J=94Dbt*SHTzu;^I{=+z|;e8?F6^-@dbnboP>HXGdt*7+T_G|H#! zLr~nl$KivG{;vP$YjSqm#vt>ezeBfHuT93H$51nHk_}zo4UGITXjxBE1zza=H)aPA zj*Llnx)1c%1v3(f_{OmJySPkM>Hj`LxYR|bcD6HPANDT$eB?GIt$R8f!90NPUgP1= zUF5434vFNCoDC_fWL|XC$W$;GwB+X%)`e4}^=)^&cTypMaXr`GPS{P1Q_Oxi59Dz_ zfink6@`A%JSSEB$z0_6PQr}Wmkx3j6j*yEZHzS`?q-hd$;$P4Y`;(p`*zGD5DCPUG z7SrivRcCT*@QT#9$Tl)96CiMPOBCiT`r=f~d!Gkn5q+9E2+m9uam!5}3SWF8Jwcda z!Z{}J7Zr>Cs+sKO><3RLhQ~DGt#k?q_R`*_(t^2h0z-aZ~(y zrR=Cz9gOgM%o!=lZ@(_VB+`ocrpQ1PG6`Vz)v{k8HeFkIB{lx;PU9X~Ij%;9gc*QE zQKw-lou?_X2a1GBEBXwfgvx=34cSV2k@*%;XZypN=9_OY;iNtutJIOIG%Ter9SG>I zI=6^i>x@3<-joakS}PBYX^VamA$⩔3V%SuOd?yL?hg|x2KfH7JQQdX4f$+bp7fN zTgfQx%Xzuzy#-Iea3CL8^@fKTlnS)cGl6S0A)qXLbiy;9>+;;tc7FPUUg&s zo1qs^kyRFVvoOEaRd1^rbDuFTU@4vcg^!i*Pp#aOT9?x1M(Hrxd~_Ps^!Iq21+gr* z!ehJJYkiy?ij~~V1vaw|LO$rSi0L@` zPk6J9*0i29n?KD3a|%yteCmhH*SKgceD(``lAD3mWRLo)$>#!^NV#7!0Ubd&uwfnN z0(&+Ho&{e%BM+;j`eJcPz@~VesazPHvEtrwkO%%K%FeZFg@w?ZO>R2Hz&NVukC^xP zY`hp}f14$%5n-@f1`R_#+n>Z-GfhtK91T`-jz+FdsF8>M&Y~ zjcD8JN!qp%DgBO}E;%a@9r4GP3Dg8^#5cT6YNPnV&6cf9O370loh*cF!b=Wde^lcM zeBRp|yK5brm#-6Au?4%a|1};S|7-Ae{7)kUfVBvH$)vg>Q@YM)is9(&>0xUu(Hnz| zd|{&AcZl$+;xku!Gvpdp2_xh}K0AD)l^KmzL7#R#RTzZs3)i?oI&poUf48XAU1OzP zh~hZ7qu$nY_*gWWSUr@W)ek#(c#`c73wcqYq(L?>B+)xIPEOo)_Yl8ra7k@Tj#&ZY zl=Lls*dlw^`)zwp&l&(hv?mEc#O4sGj^v{ z0YXk!=8PD#$irxcLNm-C8oHn^cdOa``uv@GcG$zuQ3@ zp_nJAr37?5A>xMm^6~Y@LdOK+Igw5jD8?asw>N9aRz1`!ot=(U2VnB<&ZO8sRbyJhQY0; z=z(d4hDS5{{c?Uz*p}FI<{nOO0}am=8#a;AleSW7Bl8nm|I*}ZZiJA3f}vrvX?X1d zf5&}FiF67^^zw8L5R+_l{@?oKOS5*x)AEVL1~ zt_xh{2Ic@EEvs58XaUBM26ooEAw(g1$AbTRq?Yr0+tloyF$}CDNu@Evt zk~wZ06uoOq@{C>_Q(9}1z_fANM{^5@y6i+YTnHmJ#=P~08nlm_mPC*$2_9|^ElZ=> zCdFo4^AagW)cQc9#T~?e2f{nG`>~1i3iB#j^z01!6TZ;~5|ezO;`ig+I>tfgN+et8 zpiSe!1=S4_bJy2`HPxL)$I2Vu-|yCglnegw(jSS02~cvA&n6v49f#p1&UvjFcZ89g zYX;U1JvKP54Te86$Ulf28NHZLU2XmzcGrL{w0U1thKBcny_4@OKg89eq^=^|?ctNz zwzgZ_s=C#AI&~=L|7hDgJo%=Z?as5A31Oc4(hF6Y%MxDl7-?|Ikv2!PB9KsXQq6wH z^+@r4i&$zg&YQijHock@9ro35xEBv|p)C&%X@8R?2riIzC+yb1;H&400q>0NdgB&S>Y1hp(^^UYbC% zjYJGH+fv0P<@D`gzcc3rcx(@!#f4m0IUvlSMKg9^lO&rS`Is!vg%b__wsfw2Hcyi; z$R>&=WCYB$2Cj`43G>Tc?Z>*w?tw6eoE#sy1s}=)-{D8yJqntYyS`vdo~0Kz%0!X-}skk@YAk8-7zecpuY*p$>cn86psR80=k zhsm}jr%kK&ha0t8FSsYUQ@{6EZn$)skHUr9NQMOjQSrL$#U7(GK4&F}Jtw|4dp`=^ zLJ$7}LCqp#Zi~TK-zIWK@mUYGzA|KX!q}qML&x8WK2XRIwK*f?tC16XCes=^Ql9fm4upB zfR=mH(uyL*h9#y$>&DLcpt1Fm@cS{m&cqTJ{*_F-sr$8|)=RSDJn$>cI8|eLUEI5e zZgguHni>@kwk_Ex=>9}s(h&BbETsBeZLtHyM6B|VfO*cBC*aLA8m_Jv0WYPuGVe2v%Zl!QBo->5(KB)mn zbpDG%sRaRzcsT8QRtb%h8L1((T&fHI zz}zKu4C+KU0v)tK1++x7%1?8_qRXvYtYO|ijGpyfWe*oPgurwiS+r zOGfTUxmFU(8XR)`++ah+b_b>O&4c*?4j=m=;+;V|gcsj*1E`-sCs}ZoM;@&n=#CQh z$PsCd{)uz3B7j}iIO|{8y%;V#2P|L9!JxJxKla2*7K~=OHK^F@vF7al9vj+FYhdkT z4)pEB4o`JuF5z_@q=#GtAj3wq1EcTMxy}<@+0YG3uyp)dx4vph(W{p_;<2n0$lfPj zwku2YMC0y!?lVSn>3HtDTr#On(Er_6o^jZ;($Fd;`gyag7{@!_@_mY&M&^fh|K0is zDLbV)8?B7RL&Mq*6u8_B-VV2Tc{}%6@VazvcFS zA1(-T3Th=Q&x{qTyOU-%l9c%1+Vid~tz>1UZ#%V~ogKEcuYEPGNS$4T;G@`BJX*N>oUWh)_O6F)`)q|s{NY`_S%Qbs) zZ$|iPu24YewTME!rN+|JdRbY?p-2DH7+TnqWqq!g$6#gj0S1Y)7=Gln=wIjaR2F?8 zKDXptA~L1p_21vN_x?@ZetB_0Up}Y+3t;}qHx}}6yYZvjQDQX7Zn5r}M48{rvBBX1 ziCB{u*JHmueA3P0gZxZF<RlQ5qtYHRAlZb>uxVj!>E)#>KwWvzB^vX z>~qP-w(npp(NdAnv{78P(-Y22t5_=l&0~`;afFl&GRBl#c70c%8dP^ZYew<&y+WaJ zz2h3sCseZ}*(ZC9FELQP@14pk$k;We`@}r9u~}{EB!HjR2x{8 z1nVq6T&OK~56|zyrlf1mF1FP}vt>^8&oge*tEkO;5Oo^3v$R2k3RBw;Y-!!WW^|#yP=sB7peDset zkfcPJ#4h>K-v2NE0_f~4Fxn@At$V5m4uLI)BG#_foL7G5Zv0e8=+JL z2$hWy9=bpVC_&15R#m`t*G~exywji(6kETnGtZ_INHpEUVCc~Yoq7EbMUoHVow==IGUnLN5_3(ATyi>L+c4g+L zAywDRNr&h%=g!5{VLpY+%07$vFp}P!p-1VJ znuh99%Y6PaI8c7B)PQQy`d4NzB|#lOGCR9RUrJ8$^G8C9V1~%Jca2qw-;I`$GvaSE zw?Ne%G8tUiJ*&rjC9>x{wie{u!Iwjd{RF3=8G!+P8L)qMf~vZ9^?`mj95S{bEeW7} zA|U;)PW1BoPK5_G!lvnzje~&U;RzGKX|E6f|BL3L>xQf_}ScT@{WHm>A zj4%@=H)f>~eM%-k^0;mRvYLs-n5>Gc69~fI9E3AV>tAP#)TBpx9x$e z(G|=39^>w;KNX&dL_42#a2u;!>d^u29KriN%oDnQi{kqz9&@tSEi0lD(L%6uaV9ht zCwy)!f>*g9sPbn4rrxNU(Tu2u2S(&|RD_f4^SaER1cd_^Eh8S}y!7}d+`P@tX)wPE zBXY4MW>#R;G+_p3=Hu73vyFvc9TgZ7b)TjzXD0_|nnk$98tfydS*uY!&jbR#Rt$sf z@iak~mwTN@S#EbNis2?l;sYzlC~Ur3NpY|qrr zNMQBO_VC&wr$>&&LX~!n`)DS>cRU^s=663@u;-6Lx>wBAiXky232#qsQDomveV?q3 zoN2S7HNq){(fYPko$p&QId?A?gq-B9E{s6QG$PY^{4*=TY*^ni-{{8Ee|#2xVEE@v zq&t%*h(uNgEe)oCM9C6~m(kt2w~vX@2)Hn*R*Njk-7d8F(J;bE6VC}4{yAQYr%v7f z>I&gUttWp+#M{qYo1Ru8JsovU7*Gjwv*_a2x2+@{S)ur_9_p8?@cbrzqU|hTaL;YV zww?xejPCylHZQ|c@#Nt_1g&i^7rt=lZ%gW`z52kN z#GWloUgJIr3ZY30;{hUvO_6V4w(k6gf5lh*AmcXGlAR5l?z9K(=;d!rbJ8tod!;RO zAIICnRR?)Jh3Zlfoi??QAm^6(@#S=yw3rO#y%&XYf_ordLmWVId)!f_T^9pdd+8a= zubAO^r~Zm6|HeFd(GrwksIz@H$e0j15SQIUE=x6p`QF|yG-D1=zgyTH{>lrDwEdX5 zfRgeL5^^2Z%e&8cUD$SU{EzlA3E$p&c;UkU(I*nPVzanyRmnu#Z~r>_*85M$6}9{x z-dC>E^Qb-Mb@~F>F3)`OKdHIK@bx>#N`?@~Dv;Lscy*1(VMzlrS-g->)0yPM8GOS5 z;5ry-8ZCaHkIfy*GGzA!tjFgcZp&UW>oon>V3HWZY?>4lG#CIERV~N(o^l z%~2#x@u=0Bjo%PqIg+Cu$PAN}MG&x56=uqZ_5k{cPuVLshw8Ks~o$h^TA)eg$pc z`+?wxAOIfH<4D_Xp{A^t^6I+O)Qh=nwcbKdGKRr%4__{@G!_|n6LEGS+Kb|sYGGf1 zvWw-U`M)Md5IA*fzC$x0h}ZqL$#`v%um%27v|z!q${Glo>O)Bw(b$W&A$io}^xU63 z2s&ijXOs)!FI_FWhh+uU4VC^Hc=e-qlWE=!nT4+R8w-9m;*xYA`c=Em*-!fZ_8C)0 z%h4d1lA+aXUzG>%v&LmDL!uOmtWYultGk-ew5KQ^@;9iOW?!l>*s4Y012DZapZRWN zbAG#(t%+U^N)I+@T}`K6^WJ)WvJePOST=x8coh{UEEAM@in+4h&17+>b|X;Qs$$d# zF8>jwGour8V`WtMM|l$H&Kjo|^~JFXm1MAPkj)6qaCjHV*1S&l_p#+qL8fUyYRNt+ ze(a00uc6%Hc@dFewZmB>(Rc^J2zFvr>Y|$z{;xof{tsDsP29)J>yFxCh;7wvE|!<} zY&U*xdrCEe5BuZR?wgZYb(MGK`}PQzzBvpdhR6dODTUiJAK__qgBquzzpM z#8O}TXA8n1PmD`=ySzgXphu76OLpYEZ|#xRhFRzF4q@TP@$uf}ioOawo;iX_{hf9D z=Zfk8x@-iY-i?`buM(O;bC=_GdB^S4FM09%GL6ZeL?>ei+o_|MUkM$v5z1oIF!h}o zPcq69u3xkp?&m-W&71Fmr{F%zBdf0}uoYm|1Ic$u78^%HqD` zGpT{|eIx(>Sw%;td#|8{Uq1cj<2mf~qV0 zGRfx9hkz_Gjk7&qM-%Q*3K2-g!P70A)^j*lL^Xc>bQMH?s(`h1BYlgvSDRBnO!3!| zEo*|-$d$c>)@LY#bBksBZFO12SSl{2*?O&$$(hTt;{i;m5_OhoYOpj{{R^KheAlyQ z=HNV=YapXzE2{BIBO!u9wCb_`Qn4(#k)X{1>knJu?T!Or*MV;Qqlq?&#Fkb~-bN+% z?jyg+*}jd!3*|0avCxv^fWrqDvfYFx-Gc;=oegDyX75eqQ={L$E7MBh@i5ue@f8wx zN9ALEextcihv*?auh1#%9yq;J+BUi1anWw8jIs@`ov?T`pD2!_Zn?&z+4~ifExKY0 zb`HswJ$3+A*vU4HPXkSlCX&OrqPNiq+cFll_t8^7^UXrfC7So+sqJ^dgsI*pZ#f#Kiq zDq2N0L}m59*Bhg*`~dccRIyCQ19hR5JGA-fZ-B2JdFY8YkT#RkV{5GEE(8QHaqI~1 z9a(k2wqeASEY+mkGW*rc%j-NxH<_}H!Sr!(ar%|ezFSOob7gjb7PHSh!H16w4Q-jU z);f6G_pK>qS@>LDBW)qU)5SUuJm!rOgqYVqT01p$Y|Xcy#OZnPMClLyf;Ur}ukxHK zt;VBalpN2f@}?y3VeJF&9bfH1_iVZd;Px>N)6=;9>eG$cfx;A?YwjaKC7CsK#A*U8 z_~yX`%T|2{D70E@CEsc1S`Nt>@xhGVi#O4(CF}P*v z$sTUu1kPmMv*G^B*{)}5wZIRw8J_Mmz8l?OJ-yu({kVqe#G(|!6G>Y=3NF2J)}=|T zQ0Ew(IATJINp)suc_D4#g0jR}edXNyA##!wH!LYsh}p9GY4^`^th5nt2|gmSz}`l- zezrS=EdvU*zakcGZwUC!R9F)&Nd2ufZJKox8U%kuQ!=sHd*t$5Zvt#6b=M2{uz?<{ zOpjvPie-B`F(wbDj|{J`7c@6=%2p<0xm27Hp!L^VIEaHCT5fH zAcUu%a^;+3`E707P_sNf*r@hE z%+OmAfzBOR0No!Ac(0+hbTyr;{TgP%V`97E{Cf4vl)V)l%Q$qd;v9bv8C%#kE-?~> zcRt_3{%_)9UcP}(y8*U%+SgH-wmI%*SC4&sY98uD7rAj39E_dadTE>8TA3j))*rPj z1Gi7~c$y&L%;czCHqxc z3{?MaeOCr3HrW`@&&_AfJkdVrHyOluiRxyo0roPDD@{wy6l-?HXH#MwS3X3Nt?LbT zY<(*eovUb6{=0&pY1=RjJv!4oP)G4}ims6l)esxp8|1RIqURng@N(^YGF+#$x})*S zEjtSS2DJ??a(nL4onEbE{kht1Ws7=_0?lKIhG<~eq|(@cuV~zB4P}v5le4uSil*J@ zl`va}`?0P4XuVf(1pl<#{HW#_Nvf$xAiQD78D?VoNoZJNP6u(XJ7CKoXgB*tb2y?z zA>LZu05` zr~!@2{sf&tMasOn?;@{+KREBHprv09w8Tk5f^+m&V9J$0mL#m|&!hKMyBcw;AIEBE zGBcY2qjl-UdN$#!1?dBrt;U-yB~-$QXL=J`49|+5@Wg0Gi&T8M+4Q^suI8b6`Q3l5 zN2>~a(zP2Q>0g+l_n3z4Ds2M>D&b4~1pqTUC#>7HRPJ3k{9V6Z7VWCFx`Yo6TvdHh8!ZsCY~ng3XILL7_^AM}Rc&qP9f_wsMj=M9j$1vnSPChi z1ZDi0THsxALi-df5D&h;w`a4FPh3}%-)!Y<2C3KL(#E1}a*Z^f=*5UqTv`+D0|q(V zZp4+inev=SE7w2GL|K_lfBR;v3L+q>m@TpP*7o}gjyPCBGDeTfgLT~_)tC2BVqF-B z7$1u=w(LxbNsk)qTqaCQuImFb`VU|2bK2uY*|yoIY=kNn-&MSjSVorCPQNR!O;QpX zlA#xYuNj0zn3!ie*>P2>@z}=!8;4cTy9g*5(>0~l*e6qOdud$l>)Yz2Ml@API#j`Hn3s}Sr-t9_gFT~w?H*ub+;G@Dc6 zQn^%83DM+kNi%T75vbApRAbe3J+x)8Fh?Sq8hD20u61cIdYp{tZOb6GUbvB(&2*^$ zeGQT5e$@4+jov0aZDGjfSD8VPnRm>mxo1A@MUhD!>b>Mv16RGjJMD``v-lMwW$KP% z@S05cw9*FWP>eRG!?>2Ys74)?|rodgJ7B#=jI|MAW=C#i9F{42^FLh zn=BPQOI!1^{hF{6{4QZ;Tx-;_1w$uc<_0}H%WC+OE~fYZyF~_Nqr~?L41pgr0q4Au z4(fpn0(K(~QttC|XacfXtc1=XnV4~mA?k4&hDQyPTXngEuCo>?3(}$uKC>rUgzY>( za;=nXqsSo|{z*uKz>gk8++!u-gzI>_(dZU2%w3xKkpIowL%SY41|Y1h}%l zr6CTs^T!$~n73}MHhB{9NHa!UH{0Q{KPtqHv4~#(@jJ3h()wN`J~#vpCe2n4$z;(u z*~D*skTW=H6)+c+V_Fz%^+GFBQWK~-yn#=4uc(ajm`NPiiM_Ygrpkh0a6Z4ZJbAoN z2YxQd=HL3W0H=QcwFJ+Rv%ZKCZi|w3tY;JQ!uABN%3`njmmm2fEOJ{+c1-!4P5b2E z8W-NmoOU&>k#!(vxIwNc+pnPiM0B9ABO5Cx(NIXwPnpv_lUUdbNSL%EXQDg*zY|CM zSB{=9^l`#mLU1;wHwXS+{cMW-6Cjd}kO{zx2h=D$@g&e-9(AjG`<3Z?wwD{EpDRhL zN4t*%t<30`>ElgpPs<92i0Y!cSxiw*1m66qICe%F&$tfgk#$G0zZ&<=Cb9X&FfPJ< zSv}R(NQ&kmLDr!{uO8ZV*4wfN@!43tY?6q>qv*OD{3>=Qd>Ib=il8ufedbkfOW-bs zm(Al`81dVQ{c45krZ&nn3vF%;Ojo8j>eA>*w4wPI`;cg+(2*GHlxV-=M+#J|Bym99 zh__&aLH1(Q6Ootkk4Z~t+hl6$Qf|Dd)Y3+NZA2A;qIS8S?s^u?WVqUo_E2TcH$%EG zWJndHSiVl$TPyb)&1t+`LTU8L8qjqk{8d=Mp*>P+jMmM<5KweyZm`jsL9^vR7K%5e z)z;4Dmh!1y^+U})b+ogAc221AA(1WC!9SxL*P7jpgBeG@Hl?@Oj-Pf>_!W6V5Q#bY zsJb1Z-u%T!pAPL2TVS`E&&3Q=j0w?SYz|~Or(0DwF||xnj5=v3X$$T_*pkisTZD_X zrB71T&njod*;R|xUqsnV(ri;!p|NptN^QSl^A*!d~eMbVpOxbz( z`2KHGcE6QMR)LsRA3y2mKjkn&3o%mR-i=-U>z^|WV`EC;7vK-BMrDWEHlB-$cAg5+ z`24t%JpK^Qknt1u#(HPuUH-(?-s?lG*vlzZmEjI#;q}!Z3TC!N`!WQ)?sdMjyljcR zKF4;xE}ZRk0i* z|F(NZHoQscd^x{;4Xk-i+R3@!GkQIay?c3yU`QtE$pM~WzY0cadA=2UeE>woV)EK$ zC@S{sb;fLTSgLf@%bduxLP?W3FlI_Es0xkJFUx@2_%QEfu>+GW-VqeHlb-LSTK&v7 zB!5L`F4%2!#;SNMzo?*l7rM3Eq;miAgB6sW-(~LC84(T9o2!Nz61-8nS+K9y-X}pn znsA-h9$3d}&^=gDldD=D3AfQv?V;u@Xjps}QAX`i+xJ_aA)TvUHNqT;FMPExXlHuy zTnqs;cFR(@g3=|`dJcT&zMJvffRuR4HRaeXm!DE@P$X zGqa%1YwH;81CfG%{$Q+oO-l%kT~7ku1^FfOm7a?$BB>c{5$whECV7%_Qt}qNEs%Wlc=5N~5mkQ!p=6 zei9E7=xDZ42un@*I~$8i5U=mWT+nG6ID4O=S$*BIUD(TY2(cw;ZJI4Ke2DcX{tV0q z94r(4Djo~m>{C3S-+&Oty2z;?m*^Uj?-9e;iLIJ;BA>NmAG7IsXTERgqs4TKs@q%G zpjCaj$n0ASNq>_lm8K9^Z&0i{rRt!|Jl8<|`m}%?mOuYse{j0K)n=spa=%-g=+4z_ z;zg~f@`5p=$8XyA!QV@M*(%jZ!x1QC!mwTsC9$H`w0no8xZF8*$4$VlOn0I#*Z<{P z)ts!V`k>9T*>M$I67L+xvwcN}jG}HuMk7j%cIiy3>z!5Vx%hAy_R;cUsPwbFndzlv zRbS42X532pp`%fHw=dn()YFbKuK$Kz1FuJ&THvv*>iFYKy`b_(M4kHin+jlo5)ycc zDzfh5h6o~9{4LNgc$#DKP|(mW85pue^r@^oxFYNz+y-qvC!z|YlY#ct8j}GN+MHf; z&(|#Sj^4Jks6<5h&9Yhd?;~{6b{Z5lyECG)SNk`HbnW>CnPMt4Sd_w0G%wr73m6Xz z`X!9$qc7?ZycT6^%TZ6g^dNwH@?qBc(oGI&5%rx;*Lr!TF|J-%Knr?;OK?WkX5u6F z*pJ_ST8EauF_D_2{#~_NBUecZoTXv`Ya5nQ1oK?aMG`cdlTC)JKazxc@ghar#6sfX z>JOE=lAyxxlkh`xP?o`s&a;N;nSYO|JRi6qFH44sOL$BuPuMRxnj(S zKI)>%KDW+i(o56G(h7`#--z&!mbd!oy@$XhuwvbsYYvhL|1$j9Mjfypa1bEemt`H| zUse~heJV+p`?aHNjH{9b4)LjW`l(J;F=VL^Jf}NZ$#S%hL30S1K}YBdsQ_6tKUBE? zi<`2YcnO44BrFe~ey@otka)t3k5ULBItVTj+#?@m`&SW`HwsX+_G|NEhT{2dgKCf?UjC~ZBbz6mw0t-p zRfWu`YvV?a_s5)%ohT%tM$tDL6&oS`3J>Nau?CoMN`M~nkrDDXw#fpn9gM1|F zurAiqi#;jK8VLbjpHfaJs{}=HdHQYZd1thCdiAfCd-e*PymZs37yRNSJn|?%uQ39_ zILYAEgA^OM70-PUXCF@xi#3J#&$S%bHf)tGr9A8NS0EUHJaOfy)0Lh)S?&6QlqDj~ z)YQB1L5q+c54ltf1uu%x679`z^n`DpC+*%%^Y}^pC3qk|BMcH!_k87$vKK8UbAJx8 ze%s(MN3HHDTTN+GNRKwTaZ@kqvTUVyU40(th1&4Y2J5}v;eO3UqR}YpqWX=e?bK%t zGTp`v?En(3DUqPCFI_SzuL|j^;4ZA!N_k7HMG3A}Oh{CI+#Nhoa>DQonw@5o+cg!C zmQktnNt%ES1giRrcVAkWA*$aN5g9X1D#2`VO+1!2eOyGut2q5pmBHI+L0~70ThLeW z_`K%oOBu&-9*ar4cc%IDh`VVYlcApl+WDy=bY%~GtjSRb_y?>; z&}#EDrY*MucMYsSx=p0oxfqJ6B|peetlWRf=UqLQG3^QpIWuKF}^KXA^Ws#zzkLk z%kY-k^vGmS@a_EU(DYew42gcvQ4Wi#eC~qkGYBQwPNdj?V;#xs$6{jbI1+6#5sRl< zTP{C37Rs#gpbZg5b0Kqx8C58=96=zUVT|N+vrr*17bTpC#koFj0uRpstM^@kWjCLz z|1hFBh>HCAzGI!5;WT$nX)L=tyD_1%t#(V1>29JV`m_||&@3EolavL^&PXB2{xg}nD?Y#8^ssZlD z(rce#CSnTdZKZbq$Pnd5z@q%HAhYv!peXHTa4D=ZA)Pva28O{ZrxH?DQ&4J)`DD!2 z3;q-Ugn<*nRNs~~49lHYIZ183&VL>tE*&F`v2Rw^5!1>vM@Zc8U)p#(FiRf(6aWAK zobudfST%Cb62NP*(2 zb7r;m7I0=Jie+W93`B%}>wuF(8C@L;h5Z||6s;UIlFWryIrYJ`VKaG^Q-5aiUs{+F zH=43CCjJVCtiT^o5@N7lWA<&huBwmP5(*H`A`I%jyatzc`bHxbwICV{!y&^V%`yAm z28Rqdr{vuN8e9rh1B|6Lxdoi?0s&rAaOG_WoJYk~A#WqYG4SP0a4C#dEAD31#Jkke zhH_VQ9v&8nGqsekZOv%3qGk${+kQVwQ87zqG!Qp&;1l#zhk;)wpbtr+K( zGju%86=K=P7}^if>qd`@nS{zN=)fufCp2o)-=>#$1uuu%)2~P}!TXu+f)Ggp5RK_O z7*LZhQI^F$3oy&{YGU`jL99Qk3!*yp+EZ<5U{B%F?oZR6Dub6Wx_`CqQ_!AjldQ53 zl?A-a$8$)pewD0WA)?CrqwC{MNDl`Y=7qa?8)j=oa6%JoLh^_4gm$R5xeYUA8@G_& zeVB$Pl7$e&&At!&lBqf4NYq95VP7&e1CBe_@je9|t0N|Y0Zh&RHn_5Rc5$=sPfL2( zWa>j5`?nc{#hW9zP=8Z>o1Q82K2w*9qT4VV(u4CkCu^Lxrzwjdd7q<%_ApaIC%B91 zrDjjzsNjE@j*2^M0_*??6n6?qavJD3!E zB!AHnOJP||Bu9O`|Tt=Ne# zrm-t+X)1P@MSsc*-iLJqR8IiSS*@F?*nv{*SYNgEISds(L|~Y}$L)c0q6b zt^v9eJ$)obLEX? zLLDYcS!bJUT6kAB^l>)np$Q8DBkmt(0x`!4ZHNWMdd?dYDjQovKgYVT8=r`C?2_`vKM$$tkOnGyqSJFcZ zbmnyBP0~a0R<)-#Ru@=48bWr(QnE;)xtcnFri5v-9-ww#4N7`wn{wrUf0`*_nk+Cb zY6@mSB!7u&R^^8nc+x7Qx3}rrQ+yX|x>^nX6qp`5;Q40%rK7y$a?4lIRg$Ox!XMTR zP(8rZ41Z6XBvE1WeJj%>i7EwkoK0LulBn=j<(QegPuQ&M?!uWu%P>aQ-Gw7a4HD8@ zi%(B01Oih5@!@2(N+7@m_LmnJJbwZAD96m?q&5P9P_zrGv7~rht!iXs z(nDdhfIXClZx)2N&~r##s9oM)WRZTg>c51X>7?22S)})2COwq(%qgvpPp5^A@UeVDvwqZ6}2T99tqJlru zT7RLOQ<`c)G|q*u1y8Fi9iC2-wn`1><*1y8b^7+ThkZeQduwqyhI;D16rsN2FU!Uuy^qeD=7yQe#4Kty~O{AQ}%}+n!mqPgHM__IF4CKhz=I=W)OyaX_NEQ3j~t(186Q3nEVUj)myJ$TA|Y!)b~}51X)LG52{A8dI8C! zuS0MQ0NQ8o-PIvL6?m!9y(Uf!&wnO0qkFXu$&-~R0PVrmAtdeiIF83tFK8TFpQ^j{ zCGkeD$L#A6!fa@*Jf^#DjCW4#e^E!qlzx(6pp>56BU9i*q+G8HtaDax%RdY&j)X|O zljT_1pF$@OK^vjC=d=tceCEq^e+t0Q0am64SJjkRVP#^7!T&Z$ND|f#cYmgpo&%mX z{gYMcIRFeqb+Qc1PE60bE=0( zavR7@uXshXyeUXdSQOq7M{!kHnKDz!(VqexjGlwmZ+Z$u10G`xXUXwolu!=xMIn$XqYRZ%2|*G=Lh~}sry;bSHq2!PjMwwIlCaGpUje*{E4H_N)#s8(f~%^*={V5g72P{q100tjK~_7z<;b8Ea0Fu8m^Et z4;Qe~mihkF}lsxYPYJ%@GCf6kqi+IJkOxDB&F41OSBM{EAm zB?wkqsaEjE7_s7@K;dH6P{QxXpbwWqpzm4D%69OmweCAk<=hg+Tf znGz(HBvt}$o@)mgjxNk z^~wz96o)^Bk$+zdc@_TvN+BsQVo0v^5^@gc!LEiGp2z|HMrE^4L8pq6CvuFO{jDz$ zq})Q9!4-0e6T>*v{iWJ`NSwI!s;rFpHc}75n<>fN1xeV2rBpMuabhS{2vTb9S%6#5 zysKHJtGFbumlto-e<_K9u_{X5OgV33>Ry_2KlP@Z7k_>rAb3>T(!ycbdbsRe*(^g! z1PcfVKKCh@F#Lq`Icrw>&BEcqTfqwgoRd4DxLSNefOB#u(*c1?v#$m#7wfK7GxwI8Eu_y)S36ji>k+)!VEkz7d$ynf$6`K+;{F2 zT42=;%OzX~qd^V!RN~XqnCI7-H|WlkIG02{N1E-Jk&Rpvhl72NizXP!G7=Yhm*q>pVnI=MROXo4zhiT{p^ZlkgzYjApYyyfn zWc}kY>c0gX<1uj|lDrM-E$>s%BE*VQAO+!KynoxbQqp$}+nFijlA*=&u)MrBtUalj zT7QZo7Thxv$)9N_GfmG=>((cWE=qgYEUC*6QdH)>RO<4fv$Fp*UK^&eq5!vVGZaUx zJ*x|Hp4UyHoT)9ydH%K(S$j5CM9g^_IX=(r^0joGRC$b3ngo;5DJ?0g&b zB#e;$HiL5~y~?F0Z^I0L%`}lKQ|-J9mf2+TLD3G+V8Bs!Bf6WsRojng#nmo@>c_*n@GXg9)E^m z-?6eI`cr^Lt@x*?4gEs*i+yUXxiSfK$ojM#m2%Um)vTChAV%3WQ!(3cta8o1w!bKv zRu#u>n7-c_#3rpNRcT9M6a2AJyHew(#uZv?%%)YPq5Gv1SXbW2`P)d*6!5T7++Be0 zL~Ig+QXnc)Ajle6E$>e=xN z)cAtYTCmjFg*w^xA*bXoFDy8eThZQyDXfjJ%G*kHvP?erWSpy}hbh2<_W|E|AEvX# z1X=gom3N`D#VR}Vz#N5rraBd&N;)G>l5Z3)9$#bVO)0GK__h?>Jr#mpl7Cw7C(GoT z7m>H2bcp%3BJw6I)b|C^Gi9FQNLt=>3dfd#eOtG0Y)LH9p8iXTUQF~Yd)lHw8qr56%)p+26g|1BX3-#}F@1z`(I5qV zWDG&Cq$11*-?)jpRbucF%A!F^THee|g~qZ(jRMrE?}0Uz)fjQLAU%@B<|?@{Q+S5V z@$1L_5;7k%iJ53qA>)#>9?xmt0Tv+u`t^O7W^&q)^*IB3L)PaEY=0(^Cm?{bztm*> z3WUka%Aa$lg@B+7U!yluAs`sFsdY1T%Z<(JTHn9TTnQ7?0Mkc{Kr`u;mkPLt&4@;N+qW572z%mtK z#2WoQ9g%fDE3?a)DZI)Eq?oI}ZY&{&$*R5@loDb>_2F(kONcALb&2kRkfdn?+^>AS^R|-_xPPURl-{-AmF4Pyu+!BaM5S2& zQhoo3dChZ_m6d-!E)_xq|2oy`>JMQAZZKEFJub{^u0WA_`U}DgyV0)5!%=@o`Ec%a z__D@?8yS zl`7+wLw^VlPa8jk%Z}0#9;b_N**Pw(GLE^Y;A=`&8K;0F>YLmq9jtM(y1Q8t$R*RP zf;%Q*IsXtYdoN`oo?LDDThBo57*#Ltw?J;5E%|PHlita(DvV{JFgttYs`Rdf!tCr- zTKhVMUxbCf!lYkf+;a=kEi8QO5VnKY^V9C7&42OO6$!HQBAeifoSgffva}gXwzlcm zUTZ5by$$mUOm9;#4cZAeVU^?lG!quS)uF~?->gW!yV+~scfq7Tlkbj1cE$fTrqBHN z&gWfV`V4&M68+_s3&^+cd~Cpmb$wO%&dcB)JF7)rKmuNc;J!4;1tb895=#SK;RsMH z)qgIS;@1MTfMrorFhs;8ewmB0>bwh*{><{|;BGzBp9?&rTHSSnyMVJ$Ld*Vw@B%Wl zung{JK}^CjEGbN%nyHqML<)$Q(}@1JHFAxX5{kbI9guFOD?0HRreZ`)f1Zii>ibI# zn-+;*#uc47F9_9Xx(Z^PnKJ$Poiulz>3{0UdL_-BH4@ph2984Aw_c~<;ky9S-=6rn zQ)!}umy$lGrrl2Gb(*mFI_pL@E%;hBJ-n1`TI?2;T_Go5F?_8S)B!YXS`|t)bhY)i z*(z<{7!O-77#DCzZM{u@?u_Bbpw`V`Tv++ISjxQxnZidDNAq_gYj>w0e8nVy9R5oIh3Q1~eVE0#fpM6ol?=hnM2yI<3v z;Y5vH4O)zwUL%osavx?eZU$Z(tJd=~%wpUMZ@h>6@NJz$k;Z1}e91DI=#g`*{<<*} zIh8Aa>fFDR6}P?C<_nTk+%kv8pML@~kvp9GQvG2p%%%NMGc^pu%u!-NrF%|=VVHSV zzTy&wVZ2j|rJgA>k!iD(yy?8=MIEfS-V$3yX0atDmbx6jO6pu)R8+TNdO=3~q{`mp z&4g9si^-^J-E=wrs*&xz6!$DJcNpg?D7&D`@rALf^rn1r^E8-`$Q&@G27e9NZ+uB# zAz#(BT$!&ov^S_R5yPr%NbjqfR+uG$6X>g&CVdmQiT<~(;@3GImX}#ap%zda}Z0kNbky)~$>H)Rky|8)g73BLJ((nfGm?Cn4bbX6eHsT~vFHV}F{Z zK4LNAxPpxz+7gZENqq!tU8nm}AxVxs=j=`)vGFrILhet~IWH2r1b>EED(gs-BV5W6 zF3A2;o%14G%Fu!fz`6`c?`}2stv5|=peNZKDY_Dp3gnF?vilTF?(nS*&HZV5rqq*6 z|58UrzqIjce_WUG=a=#_t%RV3+;mZ0=x-GNrDXOa&wcloml4jwn?Cp37`jZnF$wO1 z+}oo%-^NT{GEl1{V}D^Cjx!Kv>#h#TS!iZ+Rm-b!2MlfvQ<}3l`b}V6t8FxYm~>H% zqx#ZI^cQ9xG5X8PE~=$mq02KZ6VV^b#k(uyxv@@b1a(dgRPTj1?Xjpm)#a)~h`Z*~ z6lP&Nw?z!=d>i?en5wnno%->VOx=_-UPwfn2 z0oLeBdTkd~HVALy!F)g)z1rkmi2qWi=>Ek0&a_NS(d9kD^cSQ`W@QS>i0ddKo7w=J zr0&0zBu^R0$v+EH{2E4y!Icmezm5@dR|}#+;t+1z3s9WDjU-RNI?-2yLh@9Lu?q%r zGWD!>WwUfqmw!M`D3Vm&nG$ji{H;@WBSunI9LYa88}R`$XOP~~dfzHvbj zb9PtAD|5=m=*nrdRjs__XKxLEc_pVTj_BL^d$$^IrtF0gk9o6cl>P?k?EK%zj3R6 zh8g5=2N<_u8aYVMOcEh)!@hRSd;^Tz(=?Qlu9-u+%6U&?iU4$lnyJmSWDlp%%02~s zQy>5Xawq689aDb=VDz$*9&g`fLSH2A3oDcVHcZcyeC5FCar!S6BDK=gOAX=@BDK~e z)AIIGy?^y`c&=LvXL;rDjFlVdeA}JS_58HNj!>9q+h=M!#FSU$!u9=WhS2rXgVWji zl-esZO?aQFiI8PBGnljQn`I(oB}(ql2<|SVA{AcY;VdwwMG_Nr7A#yzFqt*=wx=1B|Fk+@IhB?9=1hTbrPo+}HJCeTC3;kf%fgi;_t8p|8z{w|E^tVc zm47iX52z-#ysFX92T<}hb6r^-XF*yqt(barPraZTwsNZOx3TaLK18j)&045tw&5RV z=559ln=&6JkOsxY7VAx^8jT!sXN%LGd`(`U-=L4$@O5t~q?ITm`np#-#E2K~>Rvda zfKm|7)}k2KbQx*7>f`>O1?re10SX%I^s)2Uo3{cVzyRA==)t}5*o`qcW(P}d6h8Gpm~ zxTA%G<`SbDqN1l=QqHNfhAf%Itlc^ezpp z?o%)-u4cYoW&B5^hLEemGm0g zocQ9;Q}j2h^TGrGs<=YF^?xpPW_s^kdR7h_+3fA5y5ucLlgOYK#6<4~_1k6TKTp%p zDTNnbsf65?3bIb-ZC{Skn=|WVMvT7nLg}(9IrQp3jV!Wpq!3)`6|Nfq-IU$U%B?^R zhSdh;Hq7KlCgz%~HYm53YLG}4*~C7guDp>&w)QcqFCoGr3*SDfU4PJ94+$9|PHnRc zi)<#?Sy|ENmkQY|d@X$d4U6oJ>*g$$ILd63-15V>3FL$d$*%M+$ccA0|Jw*TE38BF zUs@OjPBTq+v$k%d&bq-+(y3Hd#ykyv;9$d-<|?kODd|}`5OTtprWPNDb#|F~(r<*E zP)A0;8OX_Guc1%9jeobX$}6m7qqD7FEV3~Le@|;9Q&V1q{O1EGWV3nZtSq1oR*^-v z^-`O=r&W5-&qdPZe4F$xjThKCYNXY>H2Kz4sAeG@T5TCz%Hl2f7Y`F)Tp?do>t`h> z9I~SibrQY-&R7}R_vhJJyamYVyFQCUHU)JOOw5yjvyevJpMNQoV2M|wxU+LCEIjm~ z{RI&go|#uF>v@SFigF2=gv~Xb3w%7o}R&Ouxim>EkR7eyl$elsjtd-CsANTxip=FNi5*WW==8 zpuf#@H_6PpS}YZ}ry0O`$E8jcpwMfs?0aVl(%Z6Uy?+Zjco`xK_>0;F19ig0OlN)g zwiJ1*604f20Vffj7OBssDHa@85@>H+Rdp|kNrtI0pknwACto8GA{Ox%_hA~XvC@ugsOl`3IHH6bSC_l9yb&S>1}~$(AS>LsC~nUR z{|YxQirceOH0s+|86q_M%%X7Fb!)h%NYG@m6z8fvC_0kQK-Nn-=MsF&B|TEa(? z-pKn)H9X-;p~r-9*WzWI=1!GD1*_5EJqr>Qln<7|3YWa;xdw2

~|;u+~z0Jd%I36EAjC}YaV!5jFjR9(XJX7@vka%E zyVrGP<4g8K8}t_6iw7$F2)*Y{Xh5{_CE zN!cz4;3ZGOdk_!zDq2$>g`<~kcXqQsWGl#KFU$@zt~=E)$L>9fwQr3tZyjhd<7BT1 zSke+#)xINseAptuIAJ?j*-PTKova_d=%c5&u&T@;6c>76%64$JmcnCw9*vYG@J1sm z<+k5zt%h5Vsne27?y?zRsb&5qO)IVWWJX}cAZasC$)SmtjcrV2uiV6oEZgg|*NEmb zXy1DsyBR~wuhPKzW3acPCU!WhbYP%%PS|9a+>ki3>W;^$;}~K@W41r=RlYzLHO@@oxQ=ZSF2y~r zIBVr{T7difDWl!4%R#aBvOB|M?QNKXanp2LxZSKc%U1C8B-knWr9d8U3_`o4=G%GFfvi^_pKHQR#+!qb0w8^+I00 zNf3O`n&SmsDR0!M8DsEg5mvj;HCsx`j3Dj@jD%iCK(588u%jw7Qw638rG=vOp)GYo z0UN7xJL;8|_}P|*UMR;3M!c%}_iw7~cJToZH}el}t4QlKJ@(gVfEe6Wi{Txl&l(pW zj*4woY|ci?^D>X_U^hfQXHhtH?{xbNWTZcc#wx=tM0iI(h@3TY#pl=<`Y(4Gh6X0# zA>fDO@%NMVPv4use?~Ab%#I-1DzayUBGAGwHC9fbzcw*gR$iliG0Pf3P8^7gml z&`#|MNph#JP6;R4L<7Ym^4y!h><~T{mB^JT`l^dhJKZBU~VsepafiUBNV4_3*jY5@%j4QxG6qR+jHa zuT-E-I^;KrTdY(l@{8@vw@~?Bw`-qVdq+jzHkT5$JwA1RbREk=yy#a?{JA*%S3%M@ z0)?gGASnu|9cg4#htzYa!Lr1(aK$W9S^2InCCwRcp<#bRX@od%ngh zn)>WYd@auD9H>l9W-Dp)aT;rG*-uT1iNBnO%7mRsk})_Ou6&Ua@zc&WzA@u!zt|mx z))-s=0vOyUydBGU9$AM-{hAQ9#4!J{+EIEhyy}Gm;MEfKtT&r|v&wZ(KK3l|X5FKA zXR!{c>@i=}k8W{11^TYr8$xkKreFc@7fYSGO+7U6+gM2@bw`ah9n=x?6#U63YJ^Eg z#WbPM&eMlcS0(W>)z;qw%aCbm6hd{Pn|0q|v=8~5bor;Xy@78gbKKR7Y^(pxnZ6Nb zG1+j@Gzt2swN=!JO_n=k6L%ThlO@6r5my_=9wjfvRGmXZ;@;Pos@h0QmV~Lor2smL zA46B{zfwyG9bT)9#I*Jv~(5FTZSkZ zkvM|^ea*!dqHOG{ZnEDD1H}=!unE% z3e3__l(Uk1KcUQsZPhJ1E zj|~2%RsRy5Mxo_AMu)7_VYbQ7KC;~r!5nlIV-MWV%hL=dm?PgNyvtk4jnZ2AS*pA( zRfMJA0VEVI8ERvvJX79Qkr|60W><)#1hfe_@0pLsiCt2_`q}9)<{ut14Za+MpiIgLYK*j$YW z1569V_NKfDbJnfrxZL4Wmq{gXK@n4Ny8x?+$P^2d9aXkQSVI4y=)x` zOo=l;d_I6T<)*I=O{ug@Ca~gV}Xx$x%gPBQZQqZ;!m>z{&s_*Kl zPA5!Zp-9@;^z8JUPR|Eq7HGsv)7 zgvYEyH}Yq)s1-n?%+Ay?*brojAkqdzRT2WNX@ZDERDI$x+<*du*_^cc`KGI91b zCQ~!$V?ACCt1O>8)gBH%hMc^lUgZ*u(0U%e)*!19DV-~+^&^EEi2+0>)!G39^T*di zRRW2pVWbTfJ3nL9+fL6ggtA;m)uHcgawN3&ypuf|$(#W>s7S+TopuJyMRw>oe9W)F zzYjATK!spi3)j_4lQ@`giQ*`E5dW|*KZRAt@!1-AzI&l3gTW=@z;d(!2?ff`eekQ@ z*E#KLn2q$Z?==SSjAJcFiOyT^04J&x>Df-Ibm zvcqGcs5ld#^=0LOp#5qEVCk;s3V%VYvoV3nf$Yi8FrRo5%{fdvj(RTFP*|~&7?&KC zJVS04W0Ehr+&xEJ6*6n*w{lo-k-p5q{G20({9?7YCP;S_ZK!=52Oi?E^2U#b)pg=b*Lv*GN zq!~jxZTPIgYHc>SdSn(aUCzEUM`z_(9s~Yy#SrH*u*Mdy6vNv3>~>d}qF;vl<+KwW zVT3^@wf!ldimavEpl5!_{aO=8{lc@+T|5y3G~4}qQ*?#%YNbk6zIG?K-xKZ5D6pw3 z0bw(El_JK21An!;-fUc!!+iK5m)%CwPHW+73mD{ti@gbUU>oLHCDr}BkbGq(=1 z$+$DuvvIo>k9KRzcu1zybHy0esHnhRrt!B>9{#EJIC6NqEs z;m7n?DVZDsK0|>!i>YM{i~93s;4jlL^1QCByfbm&V0@N-1tdp9ESn;Jm#as8Zp?~m zg@kl}Ol7mZOhK%p33(-YC%2Zou`>DSFzskwG7}LgI#+Q)+y(1$P+#3*<3`1!iA2+*;FnpwF$mfH5Pp`%@g20h0eJvV1OxAntAx+NTd^7#{I0CFdvI6IIB2U zf>htDmFzR)KIk5WsMlno^uQcdJRKOWGNc>4mJr*hKC)&laoM-*A9fkW?$B7NF_PZa z(OwjXA!hMq#cSI5#d$0g(}yaj@xg7opciR8ZKda>XR>bjGpnpm*-hCzWQyQ@41l>` zqx?nHLKOl})M<6~*z4rT>nQ!o2)pvD(O^C8V11ZpUyq5UK|u6}7|V}co%`0Z4QID8w0Emdnl()$kt04Z= z@#1kq6dGADnr3s-Ve@kGa_8}Kv$8TY-*CCEn(@=7^*(6zA=_5#fnRdfZF!b19XIgP zLW@U%$K3+%i*AY0<=A9b>0)AUemrgiLGwjKp^ZldvDNmYPlyGVZ1>7}mxEA_&P7Ge za4%f(5g*r2*aJ;=QW*LvG=#EnF*$h6E-|pRK@;UGV*K`c*7DW5xs_;#^3u6%f>D~R zyp6b{zoox#ZNJzs5eu~)J)^~?y=7xAJ6dgTMYrJ}vCelQthY0`st0b8F3@V4 zVx-*EuYWI$HC^sfOf%?sX_|$tS zHV?jo9FG8Bchgf0)AG7@5=P#aM3U?jEzN060y*K1P zJ}p$B`q>ohThl$INTqfg@+nK zo2l=P9SzT;INnY50za_|b%^`*@L$549#`9zn_7*I7{*7@mrIQaGMM@cdqsbK8k79C z18*pV99ftX9q_%cO#{Uy#EawFbp$>47M@lI@X<=A@7kugL#y4s^C%v|?qrIubi)p{ zffWln@JS-BM+-)$#mCGRn^u;?%i_`E2ZUj)P3X(Qsm#2!%@FJ1D(VvQFZ>qqJ4Zt$ z_TAyz!Z_@cC!T(vF%KSljl1w0e?Aw+Iq7CWt@UHE#HqxeQjqZ>FAmQn6%<=0Uw3+T z{Cr3OMpqr{{enR5=xdS5>!%I1=0L1V=tJ?_(qTK&= zm#w4{5-{5ldG}btP(W$H&GQrfzB`82C9peKj`A>4nAn2M=Up?ZEM2G&nrFx&LGa*u z6k6;OJsIh+cgMKsbcxVz!-&Ghlc`uEg^&$zi-5^j6U7(~Vwi%H=8FWb{eMm?Vf9VaB*I@qHk@pwt3CfD|r$-Gw?-) zdQQRLyduNH@xFlx`tkh=UbJC~HGj`zylUH^Arrp1n%djpnU1)VlgsCfbT6`K=+|9? zITE7GAv+5rWU9=|WA7%<{UI)}3~SNBl4RxRdc4DFsjyn*D=!9rM{}DiM~=d;??)ZC zIhC0bp3#;VpgrI%Evulf<)q?Bt$PV<89fMqkFj#d*X);1kljhHl3T!U6$y+Ik+gq(Q-g&&}aA$TJ}okB0b;Gyy9DNKIW#A zMiG*(p$Jw62FMj2N(@v>!gfL`Uy2BR5g-ny^kYK1FIyrK2HLFEP{F_sCdb`iocQ0G zE=MS-IeDoZV!ag&(hq7MeIa~n0T-9BJD!uc+6(2o|1mdz`wi5@+vkrCOepM!qHEtt zJmrA1#|CNe4YZPwW;1@nG4`SkQy*;HpEp7b(QG#8#bNPjVtkc6u1Y;pgw^aOJ|oIy zvan1`fW%JN8k+;Yot`em^12l?v=v_TApLG9eH@HjSz@|@tNNVuaCH29!0OFNG5%a* zJKeMNR-;X2WO@G{eXCNM&69YFUZ@CN$41tf;#yQYO0!rF_Fakd4U*7a7X?&Yfn4Ym z`e96q!2G~`0L$0Run#Tq$G73qs6#@Y`oY;)b_RoCE*wDA7GArT7+yqf91kfs+UL6- z?bpB;sE30o2* zE@M#S{fpzMZ-z;#4=~)cF^tyOCNbZd^jc9QmIP(F*4Bxb>9=J#pvwe=4VElx7!OO#)6vqJ> ztCtiYODEoUJB@{US?QrGCcv|)b@-dHbzkx0Q*3-G=O;1E#hRKcFq@7uL%}-8Cjix3vKp$T?0%mH;1v?L5R|!P`s{3Uk@nye!j>%`&%&Rb%aR{ z;Jt(sguD^IMJp6WxqeJi^5MUsAO9fJg6qna*Ly)D<01aj1HI5|YjNe1%)=n{Dv=D- zKyG%WgND5i97vHvjQmGiT7o#sqp(a+D@EIh9f#C0e231%UK12?ZdzbI|JHmOMzHL! zF*D3PMHbtI<<3o_YNj4DhEt2Q>u7PZelyLL)V8 zp5DEv1H^47nILf+)TaZEuA_I0TLnl_*KhEx5Q&fSk47bTTYQ85`T!h8&Q*`V8ny2+O&}4e`9l|%fb)ou!_X3)fV=Qx_rXd`w3uO3 zQLNurC1qHQ0_QeSjy=uV+|;JGJt$f`q&aEC2!U%DcQ=0E1yih&{~kMUp2#b0>KXFk z388r_(79=Um@S!zEGL$BQVfSj*&c0WlQig(5g?Adg1DnFh{#s+r9c#60bxrH{LI{w zhwno0(ZY+Og3c&Yd{|dJ*RwXpW>vNoG*5E-6)8-mcXfm!?sq57%Yh#|l$XixaPba7 zu;-SppAB&$uxqoS=N%P_)b)L}(Hnsk!DqN1iO}6Ao|W{YFS9V*g$1R}=q3Fz=4!s6 z6hpCpZ~uaV(6~Y9g6K3^#Hr~Y{;8m<(tkdb@~gK(s#I;)T6>%vOC4pn%CDku?d(VF zN5ktx`P2{`oN^|)c8K{=jx&t>i=c&80_faYv`$^47zIe@pN{8SZ1DL+~UY z0&S=a^2{psDFFZm3=v@DQ*k~T{)d&)5~A>@!X_1e4~fbu)4ANJ4>Hu@b#t5Mr@JXj zs1SF8}oK(%+Q>1ezUp_^J<+$innv3mPDMFv;RfTFpe| zcV9iY8pE0*^pX#!j*8m18;iaj-`{nrC}O=vf6i};5Ra4V6EnfkRiwBP@BmdCJrd-U zelkX0q}E^K2`dk8bYR3)tSeJo{N-9`P+Z88Vi{5G&aJNsmxLU?6#0}qt7MH+ITzhm zYr?2OhDXK>Vkl`gdx6!SXGFo6=kNa*n&baeilLgH_)+jfS+??`JLc>x9bC1@!7|!L zrm+j$ukkBo3%c_7)b*km>nI@y-E~*pO;acTGQCcxr$gLt4xy0`?<9EDhRf-oRGAsnI%sMz{DSk`Wb{~ibArEy zoG+Rcf8Fsmg4N)G-W1!P1RtV>D5PZ#6jr`T8sM`?lbr}j5TrVmJ?m@?7}gBlsprAK z=noooBk$=HaNLS&*_W+5S3Ls;tD1c2(40=Ln3cn5VY)7#=Sh!5)X3hyN|}Z6pEpjO zkD!XnnKx$$KYBkeQaS*v{1scL9=YepL(ZgZscfz+w?p!|G8`Z7AnwP3NW$jxj}2w~ zBcQ&!f1G=rS{cKSGvO+(VDuz)?m=e7tr%WfL9N}{Zn3JN4lMLgkg5e`c>+#_DR3fX zXR_X{{GM{T?c0;SAa|yE99(*(F^(u=DjBf0C?GojBEfg_DAu9Eo)!bw+*FwX{|P&+ zc9QY4Is#2a22R85m&JRW^-U7ta+<71>WYyM=hR6r{z;>K<_hIMoMcd4)zhgAdWs)4 zM+uBoNt)qW?Q3ppicjjo{W-NExVvs%~{Q z6R$W8&hVnr_yo?+nrZqgi)~UL$JXe?_xI(E&EvqCY?ZY3x*1NEREAAcI-Idp7)!Mf z&;42*TyT%Wv`}lTP&17lQyoSeiBg7fO!$RZegK`0%NqNZ3Hl-%w*Shu9`PC%;`Erp zal9$=a6Rqm)&c?IRR5Cj7qrd70Zui{HTk7<{*JA*!aFao(~`AP%$@76Bs}5qC%M02 zB#wF})r;5tM*Scw>t_n&%O)%lmxt%o$#uG>&jyVZ;3~0_d;x58I|w1v%{4g(0js0O zJX?hwo=Z@oP&DsHl?leUT}UK;y5}_X-9608b%EY&&iXKDl+52(bQ(xs-%5#(o11BI zmqhG1I{ldN~ zNT)R#52rEt9UqiFO-pyg2ek;8s^8zL&4HaaR$?y2$;vm}!{#nu1Xpq_Q5h-18DJ=M zDCrW$7M`WTbgI*5W8bY1EKSaCqe`FWgSwpWOfpYZoD7Y~hH%08Rt=gi)fXEvTb}CV z?wUlGEoNFA@k=%ps81V*2(I_L_k)oL(=V{(+$}j8*}Cczo7noP6l#qg zMr^P4oBv~}@6#R=C3TJwwqPpL-j&4I8zcw!_V>tuSt$M6NrrSzA^xAaIjA)ma{3V@N-( zFoEjR3eHq($Q$8Nx~TdAA&N6jl0mJU&PdX+2teW+_a_>+y+1M zTUi8^Csu^p^b>1Qk|1fZ87Bdj)JfHev^BapwJX#r4UF?5SHnvJ%s z$3&REsMj8R`}t%g`Yana(AkoJ0Owa}#8TD@SFOSPN|o0B^>S{DGs4LN=cgEfDOW@V z`EZ~#96J1%?>wS$wlPu|m&4GML=*OmV3+JBkUe)S$!LLD8X0yjJ``JsK=<97uZ@sW zSYM2F=L=}H3U`l`DY8DxKa$ll#3Zo4&;y?Mnwnh=xgsoRh|IVx2f&zA;1zj>k>OL&&#_ zp^(M7hy{rK_U zkqxK<%Kcd&SoE=QM`3jfxRO~9@n#{a)f(a6Sbrx_P)?D@UULh;pzd~sH#!)b3~{kh zZ;(fr8LG8LZ-T;Tz)e0%Qy(*VQ#u`}tw&}Z{Sb%5R4h0uAANfuYzCyNsEnj!wS99-R_uCJS7+fq<%uhQ`;vcY%ahA7VsC#j&zIvW=7F z);x9z>%wZYiw$rh5O9slMsaxroOQ;iwjN+*RHx z(^yM}O;rARcb5AzYFs(PL_R}Q;Q5kj(|pTKqf1MrzX3`kXo=YP)}E9C54C-txUnaf zRgYFPLmoTe5lI@A&+Wfv%4Yjp5@`WxLIxQddn-LlLIy#8LI5~rv>^C0gOtQS-c?`U zrKJdggba%OgpT%3Mt^ct3%fdsDmv;pf*TCd=ihQ?|4kWo#{X6setR=L%hz<=|BWKR z|7}G;|C1ts|Ft4?|4a)0ztHD3vHAbr#QZA@xza!8?H{Y~?^XK8eE&%$F7V&4lAySd zqML)Gk(Ic$u?-;?7a@bJy`ho)>)KHMSsN-s1_dJ%GY3a|H$qB&umKo>TNIsaZ7q$g zz^ez=orec(TH-=VHlpG}(t5W4URFYeSDjv0z~0Q((Z-&T{nauR{?K^1H=jNm^I%Vfr|5FiGMs|82E7R-LgJpwQ=~)1*uci%RW1?qd1`7kSG19Yx zSiud9^dJ_NS8HP=WCMY(zUG+MB4lBE9TpRap7}K+{Hp{26WFDI>>zr;tEj(PKtK?9 zM2t+}v9i;%ywZTclfVvQ1`vXPEcDE5EZ|J_Y+x=B%d3Zg7=d7s;6X641L@hB0PKWp z%#8GmtZaWH2QjmNB>})Kf4T+FEE^j=3oDo$2o}H!R+NyLorNCA1_b`Q8z6WNS%EA7 zLRKIUJa?~pGlECS%EAKHo0%TK0-kp*@au2p_o~t#^ZOq%*%LAYSYFw{_GP4J1?!ro z6*Fw@&-hXJ<~c;YAQYOhlJ=uQN>EP@Qv7d-Z`^qaAyr{DYbO?5zou`8 z%$C>otsAJJD`i5Zmk2D0f(K1)xEJ9N=1_oAi>RLuG7RZ%N00%O1NGsTZri`h=O2|l z#~^*7WCp^q$YcsoO*y>;ReGOigFm=)qW{WE$3_i`nk-qiC}F>@66a)UM| z9WSaTbaT!@IalNg(v>++qn2!6ASM|OrAzfH6I?34N`*c^qEY*#W9IotWDE>7Z{oGm z$fNr&Hv;xo+xxQy|Av6Su<`%2`vd;r%m45EpB3~^w_{=jmr!7Sbu?zMW3sa`g1|P+ z0=9joS3`S!0ocJ733hL0W@dUuu-X6nVEM_&@daFKrkJ*@4VpJY;X6WBn{?!N5zmBit_;dc3_F9BsKw<-9%O7?&&?{d58Qed3{xvUuk-Rqk8MPK6 zJGlA^X5Yy5wlaWwVL{W>x@Cs45CgNHm98L4mjzFo{jYPH6NwC&n}~PQfTJ-aZpD zq$cKZiNof_lgrYC5(RfjfEiu^lfS=zMkFc0Qu1~vE8IHS@qmiN0j8ruEU-9VgC0za! z9{nvu`acg#{|rt49fJPfhb8|Rmi{=!e+4Hnpa2K~;G60n3kST~`(N~bTK_csIsVT$ zq(#X58U+32zki(L&+)GdV88hbIRA+L56JbGU;bhJL;rUX|MKU*npplI*sG}j1)i|| ze}^ZpvCaQqNW#qe8dkkVLH|UPzhTMWf|*ya{C|Wb;JEKUBFVD3Ki|i%Cte|KmJEIe zB~@>c!^As7pXC;z7&!_Aw2VNQ?OyZj&erd5X8;4ApBR9`TO?wPoXGgHhOjzAh=+Xpq{Om zk(r6<>&E3@n?pxJx<7X*1`$g=6NlH`@arx_K)}Wo>}7P|7@M6Lyw#v%Vqv0Z0@sHE zF#ZYl*?~;-Y^-1p{}1J%*k5V`q>!$Yao8$ksL=-!CC-EA{|81N5x1H19 zZ1L~A@BiVh1|VbtFoOf8KP937%uFnx|6rU)o^F~-qV=cjPV+06IHp2*sfeK)kl(WS ztF`K8nfia3Q3Eg zP%tcF?LEsVyO5A4-?X!y+&rB$N%7Sz0D;JKb?95nwc>e~q3<^esUR!=K&$wg$ zz3vE`jZLh;u7Bi^MFV1h~YHN$VdkGZrKfPJBn0dB(I}T3g0AWo=^Z>5453a< zPJNrW8=qI;!t%^&O|`S|a;CY8+@ey-xUH3`rPXxjxC9L@&(qIz^s zh+vM>agsU@6tP1{L6(_qE!JbK(^Aj%E zrgE^bjNgAiCkZ+g#@hs|QbL@C-Ec`yP@e7_I#Wj15XI~IdqVe7A$60}2BMgIu)HHF zw!&W*g+3;B{d$~9e2R)k+8Cw#f&X0>=zR?dW1=q3$#`b|IR+16zhh=LqA!^}>wrDf zYzOrAxp=oCjJ~lFuZSM zj7YCM@M!lBbfZ5*TN4!pkpWjF4{eJQZOKMoEZ8|O+tR{~&MDn1&^hvK^oZ9LnqbMR zZHj0Wy+DrNQ7BDx{1jQ_+}>Ksp9CRlGbDyQ<3D1X5n4v~bqSeKj%3x-kl(i1 z$8Cvl;B2GsL|&sy&F%MGD9>mMKh>iy$=(p4;H_->Nob0Kr~qi_o0nZG>Nu^Ten+L55@wA1JYwws;9q^BH=brR7}?fLXkMgY&BCjQin7> z$7)T~ylk~1d#*KO_ELazSNY!C-n02MB8mWhkvBwMoO2&GDjZCzeE9`1td+lm(8~H82 zFWmE%DykdbX@o97ru|Ab^i{|L>dXY*ao9yci(+@^hn@}z!Iyl2xmF+Cu3knp?y^f;ej9A=_2EXo!;|K@ubm|ZsrK@ODBKw>$Co%6Xnw`=8R(H6L_GES;+3# zkmP?;=&UVzVJW}j2RPTwxslJ=!5aB(k=srnIo{29^g(Q{KrwTAW=C=Vg7bUG6*pY7 zC-XWk3?y6z=~-2+QuQ9*fUh~x27bdKuFQD+@elC8XmSpX3Se|;b zJs`$jkUeZ)QEXAfMKhc|sPHEQeH^MDY`I?ZAz9{5i&yM`LKM}gmf>oBFsIyOE$|ZQ+w?n24*W)cJ0AGtzUvXffBwsQATjdXi`Y@*yxYpNa*Ed{s%27F74$^em=1{|X^|m|neI zf4ih$cGN;cgTcx|r{%bMxU%uQAVlP?Vi$ez= z@8_>iP>AifQ^S>b?pF+zZHP9lcXNv;c-}W{H@vr}1EQjl(Pi|7@f8rfz2OGy60NX4 z!9>nq+6H*yD&klqSjBI|Du;~S0$cei5^V4tpR!&~e{xr4@MoaOrkLbW^fS*Xu3?{0 zU21MOt=24Gd{;f|bKPvES^=Keo^+!n40WH!O(Do5`EWUx~dBnn_f6 ziE)1fa+W`J3J`tb|FI0b!LHBz%_HTYBkEuia~vyQKg7KK^_%kZr62Bk!>LCj!C0rr z%(_M+g3J%zHRD@ImMr9m&cstrSLkPm_MFFEZP)kO0T8E@w0^y|aJ0UO*@%eUlBbxi z9}v4rPqAsgvaW$#@jv;y-%>2W+Wbg8#Q=7$uA@D^OW-Hbd`F|pUKY@zhh7#)ZW;)h zfTgpgs_ok8Np|{%AP7h3qVJ1!f*v=uJ7EsaDC;z{ooy3ct&uG z{tV?Ez8Yn^(q(hT`S|H2*yI$7E7T4vvJFd!m3-}uR6s{pKikLH0gX3MdQ5_SNj-)I zjPYi>{0xrWGE)2m)5&icI+dh;I2s$03mKlmc!prJVFl@uGc&{0FyYoPLpfAq;f4IL zWsA_I#~9vF<8r8Ze~TK?Lp#!k$A(Dp^9>t(|C-nB7tcj`S4^JnGq$%+Y{(SrhChA3 zss$*1$C9+AmieL33O#JgG1h6H7L3jYXR?XcignyA0l%i17Ib0vR%KnFtPiKGSGW-? zQ5T=S*WL0<`Oj~|rznjOiJP#R!KL3}R1KIbx-cwzC94n??dTl4HZ2)(-?iu=agCFA z(%+J_zM1cIn0VV}NxFon)0w&c_LhbVVm#-q_m`SC&yzKhZPNt_XtGq)X4MfTzXFR1evE47*6v^TeJ{cM)jF+9Ho^FgNfkj&ZUdb91CV0hs@ezV)PzY zR3*{to~X?^xRpTLk38uaIFq9e-m<(`n)kUG&$hBYIxQ?e6v9$ja};N#e3#%%V-Xa!U6Nwhub> z=?Ur;P6`R5#&a-xN^=4XxI*3yQ{uG5`dY6~J4+M`*GX~=n?Q!ZqPDFx=!&&I?#7w#Wrr(F_J!l8M~sYhwRewo9q`yq z#~g43-NS}*Fna;j%!F6p|18d1@X82ZeS>^*!0zNNZIve?UFQSQI*|CYpWHk$!Pk@o z*33(rp>Vs;nwK^wb?k~6V%-CYR^ss8%eRrH^s>MPE>x@F^dFz+L_gnusyuURDhe?t z_DN)Ep0a2HTb5foRh+JLz!dzx+CK$4EQv=<>sj;n;9un?fJ@Dw_%h zIo3qUL6Z|a&$xn%5bR#X+IoVk2nZLGs#=;hL$!wrBXy(Mi za=kq_nzRN==0P!}vm!2w6~{`t#`X}TYsT!RA0YTVqsE|+0I7=8-8-rdl_o_MT{C>d zH5yqdOurWvReUG#-9ac3f({G2zHnq33mxdW3>fQGN3n`g7_={rm}JZek(@l*3$T~% zRNYBEAi+ORgt}c|tNiLIX>uoFG6n~2CTeM)drXsF7d?M}{zG}~7h4^wSo)(O+ufM_ z`U2IK8;fvR9fO9vLd1)ay2nI;qPB`dAhj#n%=+llkf|L`TcQ(JV{uNpD{Ko zO&7Au(T~Y56j}nN09lHRl8BN#NkC3WPHZCFO{>&VAh$)uK5ntjFIQe`n0;ukIUx72 z!U)gE31knfF7?et*1Ez&IMEoI{9eHk5$H^}PFZ6C5EX33umd)hCgg@y;6zNdl(0)2 zXbi}ktD=iyI?}BT%!4v><0{ODw z9xZ}aOBgTtDO+H9Q7t(hkFYUG*H~EPJg&QJi`Wl)x+Z2An(MQu^_30XDs|-2m-Ag(#WSs)9jdi28Xg3Gd`XLQ>oo>%4B@lIzbew6tn@ng%}FiP)p6aQb0eIzC}gz`&Lh9KU`R%YKIFhwhi7evBE41>ymx{m;@vBUgRBwuS^zl z(R)~5DMDn%cciB}h~Of0;hAVv}u^1I;%^;y3L*jwhi-9!P%2SL&e(Bf{DS(V`3zDxr{r!s2D7glGs8%kF8% zQ|ZlG4g1E+MF-6#!yME63`cVU==tgHcaNPiJz&dE&zIxyn9}Bh*Ya`r-EpVR3==EC ziE8qJqk(jk?r5ao9qCb+zqo@Wx8u&ik^o|{gW=SYR9>3{O3d&=vEsqj2R%F!8=MYi zhRUKz)RlOP-x$YuFw&7GKg7gL?ODr_GZPX(yw8sjJ^lri&mG_NJ&a$N~9S z!A;?-11n$Wn($9wFjKj8`6${2GVQM5ANM{_HY9OnJtzKNSmpGrhq%*Uw`eESO4IYw zfTa(CJH5%EUgoVIVE#wiLxf)k4={Lq|8w;CBZ0R>ec0D+wy-=+Ke6Zg?Fq7LU|npu z5Y?!E6VHuOrc|_r*}H@bSWxL)Nou1wNh91hua%E^jIK*3OW!^blMXmmn+!{ZuQ9H< zw58ib+T6WTb+&bjb^(msw>a6#cue8eUj6iZzkcjq{=7zU%WA|ympnfR#R~+n_+>km zJ!!wo!5$FCRSI+=pQ9Ow*xSR$me&#`voekByA*rk?mQzs)tV!KVgY+v;_sE6*9FV% zzo!2plXxC2llD+*P@x=OXdw;t4+!XoS*;{Xp9^|vtE%#>Y!owc)v1<#E-5~a#fpg< z0S7*WWC)ns>QEtp`YKRfU~fLmB!Juaa>7Z;#HuT;@~~>qs#vMAt%SoxGr_i5tF=?e zDDIq2v7G~oZnt(<-J`r&3^wuIM&}~>GTo2Qy%yK z-A>s~-A>_dUXNF{-_+w)fJ)8hRTpulDOlwZtd<0f|K@ioCq_%h7rTtT0ka8< zV~p*9T{F@!-Jd=faZUmO)(dnGCT-fSl;I3yzZ0MoW?W_5WgK_te~23f^$gOGt2TmV zbImpaOl2m(mn`rLD<5~s**v#tP4m3kt(8+1hv6?rUr_0$aXbuIRazg7_(fqV=CA+N8hgOEepSe_nSC*g^ZcOKt3yy=J|a z8Gk19!GrQ>JsbDgC+B_NabN!xOdJO{gx%`A7MR~cvh+BaQmk#n&QLCi;K&51bZ+Z3 z%}2P{rLea)x45^q?J%XD{AV;gmGqSUD48nxC}}UO6kkhvCBl*PBsmj}!`#m}9|R30 z);?=)CGL!M46t#uak_DI!(aO~m32~i zn#KfEJte%vnxE1uD;w)-`5o(d=t|w>0qkjeEUdQsX~7*G{V*{wRA0p_!;)=0dMMkR zE>1MzZK*o=erAr@hk93M!AyR6q&bG$EQNRal}kjy-8eVwZ@p2EN~l5r6k zudI3#0Asmm#?nngnbuRtM@5;M#yU^wPK0+tfb7#<{hWU&vQeX(JllR|$HK*BH!1%t>$S*^5cEG|ib=C%%=9 zeK{H9Ia+aFvU=`(g^f?g&s=n8_VH%~LDRU$vI*t!yj*EAX~*E`h35-yeQT|I^Id^? zVC1sp0|EW`sZ-^%UJnW2xvDQ(-%n=?xDCR&o8MP|NC`RgdG8%>cXn1|*ni9(!5*BC z3y>eQj_VMhN58VZ)I}sWZTU!Pa3>9 zxMmzK6NP1^;mFPW64$}YCK&CF5VLAC%!1a$tq5h2*CLY?wlTJqVajMbaNP<5zr*DT zpxT3EE|fFc{e^_zJ1DcM<#xvE*F9&Dwm#diP9^JtS*is~7vZ$LW6Yl{`ESmyxw<;1 zBN3i_eJXPb8@ScBKB|jHp`dipcK^QPiOK13l^#6D)!uaKsBEpr^ZIPB_rideX>EoPTl@O9%@Qf;tdM>j zr<{&unrWx}c&`|D!p@AD!{5DH#W=H|(0~fN z(CRgdrDk_p-N{9hY2Xm4g3_`}kF5ZVw?OA!0(AX&yAdQ!wfv#{*I2tt4TEC|IjL8F1IegDmBK>Tvj_!f{A{@U*@};{A{dC9_jl+!YhX zsv_dE1*MlQDWGmzMdk*9wL)RVqls*E^1Kn$(d}%r-f?2sOx6ZnU^65jggyE$HsHnH zQZb|Fx2nn*iOhG-flMB8Sg#Y#Lc-27IDF$X`1)hd?}?oq{>BC7;q2C)Cl}UvGQk#p zX=Mgs!HPN9(*yhz=tyoXO=9^$V--0Dn4_n7VvbJfw}6|uDqm!BtjGwCqu>GpkvCN+ zs3x%!1WYe@DzCxEPNA~oPYXTf-Y(>|BTgW{#Py3-GmXcZM7a=e>j+kZZNuwUtIA4i zqrZr%RoC|BF)~kKrIH#$4voG45=@x%>`aux12Dv;i&NT&ZQ~LnJ^jINk08J@jMT~x z)1+o2MFt~GOGqgtSy9t6wkuQ9PxH|1Nb`dR4C)5FO@OlBLpA9Gje3Srhf;0Y zrY}<@EKC(u~x%3Rn!0x|fpJU%R667JUH9G~;dC{;EKJh*gRb`AlrmuqU{YZ&{H zVKIi`ZI0N^CIn;shCSg9vsZ!{)sPTbAx)LT!f*qYv^h$?OIYByb8UR}8tV$@H0Lbk zu~t;IQ*~l-dbU};9O=Jq8x;+XZpb$F^;>cD79swr7vNr|xBnFaTmXzY6WHF%9UuL- zj{Dsq;?=~Cx>9D{LOp3aSy0S{PsryXry#jk->WfkX<>%=iW~AZcXAL4DXc&3H}pk~ zty-wsI;gTuYrvmM2Yu1fgMb&I4u!*eLJE<-J5Q)x|3-Is(uQCWT@-1r9@O=0jO?^} z8bjSc6L|$#wJ;QBR7J^jt6#d1U01!V$HC~T+q>iQoxLUb_5L3UG7`{FMp9fgj9INX z;30J3KJ!wMPp5ku<862>1#U=9{7aF2{GT$C`?a6kudqfnBYF>rt$Tqn_+&41Bzb-T z4aci#v~ljmn^G*jKS9@%S)s==5tS z%zYJ-b-OIUq|cH+y6-AoxH0_}&+*F>+*C|h#=wvlEhibWq9ZFYSx2hT?1sbO*7=Y8 z48`{dd=tX9E570JUgAn&prlA>$lS68?b^0G`#t9>@BHcrQ+~Bhu7PO?#fVkTvq-9y zQIt}i+|0dxi;5BdCW9K*^Eis#oO0|{Dr}}s?7&TSUElgod_FNq7z@0Xacotg(#oQw zVL(u>q*qs6z~10GdS9h*pdE6}Om~w6q^6%{1>xEKCD-9*2vZNxI)AFJ^clqDlM7c;WW+v-#-pOKk&%Q$=em0T6zU8pGBYP@_+7@Sx?!7&C{CN-*lg$y z5lD-lMvBK-cHM!6i~Q_2)#u~TP46L4q?OX3`LAS$;mu*p01gs|_5)+(h2r`GrJ)f% z%kR$=FUIr1z$iJ_<>WZu&W9#;v$pE-Y`1>eF8y}XWz(!zoA%qaU&+=tSX}$l$o1*65NW?d{ynN?)Sn0*&4iJ}%4Vlh46A7%Q#-S0%65_giKsFr z;s)!o#mT5geNm_$0^#V>4Q&LZ6L9MIz-bTnCsn;$ID_`R0SY&xvy}2S?*T!* z)Du=I%su8xqu~17m;f$!i)fZ&wZ4?XMV7sGT4bN%A9d z1b7;4+D%#{km-S_MA59=Tbe(OaHp=Pc&9+8kY&Jfh%wxvV%N@PSWYpbVi9=}GD)Q4 zI{nI?W(|hF<*_JrlBtbiP}!8L(Of5m-tZlH!zXfx?kfYsJ;M=r4m?EBB=+6u;QJ1X zN72Z()x1Y<$niA%WY4ety!hYPaiVQjoEG-66UBNymy+mddc7D+zx@4+${Z|HLp_U? zNo1)>{*v|KPS8M}G#Wu`P3dr@-puV5j>77{mqi?crPZUaKEa_rdhkG0ax^G}09Ak} z9XcLH9uyPCHpE)ONkU1$B1Kij1N@?1gc(f4<%i6qAXB;%kG{Sn_I0z6XNSawUkMZCql1wF+*Wfj#*g1-LEg1e=+ zb15*q`lsaHr2xdA(EAtTi&5Z;OXnxCWqKSr*EhU3Aa+4r+)oh( z`1Eg+?&j=pnX!x9SLycj4d4`tsn>yduKj#nb;6sKw&XGJ_+hG4C{=1O^P?Aw&XSc& zm(Ogv3s3Gj3CtdgdH8bj;3;9R?!AN5{=Rgak-oqn4gw+4WAm!FLTuBlv%|?yP2;4_ z(QkIMR~1m)xQ;9tJWR*AzAhXWQ!!``+`CEI+SVT6%BK>6Cux+RpZYA+yvegNY=UfD3(? zXPEcl6$GrC9q;GkFfs$<;iptq)25E=gf*e!w1)b*yrl~fO19mbbFOdjKfnG&5;wXu6$EBC$EXga$FQ-270$E}VByR)LS21Q=55nwlZ8%G@R6^G z?+4Peix070l{BoURHeM`Y`UH1(DQOsX;C`ps;8wpzpgJYoOHqa@P*&X_y1lH?D#Dv z1O(W_*Gfc?u#m8jk0<11rcY|%vJoZoxB|(!c#56^OeIbUm%;gZg<}jP5KTgBA{~BFutaj0?rmJI(lXR8W8}WZL zakJ4AZQK>;7kE(HSbRNOP+N3e#7hFjYXo^Q{}OYB~e0`*CHz5AL!Ij|(|Ie=VyG@0}J-1;#$Rg!x;PP7n`(O+3c#Pp&> zLOm|v1crFrYaRJ(*>LYaTKN*hA+`=#aL3;VS(zim&#jyBlbqgr2HX3|6V_Sr6PNv^u%&Gc}XVODa-%GucUw12#K z@z-ybEC~iBwe8$hZ7-s{my*i1+1hD3BA+XQ`#;g0E5fi`tz6x^M!L8IOc2LCG^10r z#bN3q_8-FMWlcRZ%);4=u}K*2h*LRs2KThc>XI^a45JvaqS>Y~wiAL+Bkja^GlYt| z;TkS2V$X6#wCj`I&q+nuOK&#QUOqkGc_(iycR8G%*5#8Ad+c>M4$qu=mUZ$hCOyH*ydqb8Zt6#7}R) zn2|i@2MDqS&gToUFvKF^npwFlZ|=`I%4n>2W0+NO#P%_)z3=tEB9HQA6XK^ae5IYroB4 z7?S@vU|Fme=ggmzdIsWp9c1W0A7D_3b4eMKCO0YsjVjL`wr_KZNj@g^xj} z_k|eHQAx4@mkI^(jblSld}VXj>|~$SKHt9J-BZn{p~p-L@lWa!<~7uoU737fiCp)f zEqUhcA_}V)?2GRiEKA1vUA2jZsC~5L=z-AD6KKCUdC1H)Tc;|G9I}OEG^eFlG zDtu}8v5gwh$!WiRfTYIf=>7Y676|Uv1nzYj&F7W)8eq9`54daMXdPs@l6|9z^(Cu0CZT(c=@`8q}sCJdQtdzE~WL=`TV`1TX#}+NAm! zf2AH&Y@_)5#SE@07FsjCsYZ-(EyM7Nco-pmSh5FL@}IFNq#8OWAu|q^8#iyd0Z~Gl z{X+4OT`FZ07(mU#nNAnGXij+PYbd7Lj+U{JdA*UBFG>Q+CPcbsPbYmj+RPk);O zhmgbYKL36xyXXLhzr(sF+<$&4xc!37B2Nqr(FgcO8BR`ib-V z0v#}u2bf|-KgcLQRr_!Jm*n?9fBj4P2dG*+rLv=RIB0=%QB#AVU~3Ys7%jE>Sn@w~ z$m+GO&h}0GPl0u^8;afob@x>W=pJIznF0!ZK2vwUB^f~VQFjneZN>*hxnInhbugZ& z>_Jn(`5}GpUG9z(7Up)ek(%yO)AFX^^Q5GCT$_bjc561bUtm53yS)E?#HMJf3TJ4o z2%v4?ei*3_Nz8sEg`{kJd}5=b5LYo=R+_Phvuz8nBVHcF%Du%oP#}@==i*}V7ua3) z`oM-cS{1)ck=I^-#qb=!%X1HAKypj7cK|$Z+DM+2ll;7ac20RM~vy;6mK#N236Y$T>c`ngajc;Q#pX) zz|Ov&36dMO&bAKjU;>x^+~bqE>a+vD0%T ziov6~hR3H74U|Lxmj)dBd^k{gmZQLp4@Dr^sv(o_s&2l|wLFr0<#iObSU+8y3{Fl##@Xu z#CViXt@g``>;&Z~vXO%%!kTLYeduK4#}JkS49vlXa50qOhHTKvEYcxKNUc!QmJ3UF z5y+N@zEI>bzvzy~k%ud)(Td@-8f8cKAb^!ntQqdR{~;>~lPejX2BJ;B%gSP30U%h#82 zPMa0J6LVs#ym2@-p2TBjqNG$%kuoRJTw?K)TyVoVy>N?v_MKnYj}f2W_rXZbxjT*^ zGs_l68;L!wa@kfU_CIJsLf5;NmaG5j0yf3edThYfH5N>svSsv);e8%7EAduJe$rNB$Ye zVF;b0#78QZ{^vPv21B-Sy!fKB@SA-G!wvf>JiPBU%5CLmMk75VHyB@|clH_FKwUcl+@OWczdS4G^>B%vcyWiu( zJJ+)0lCmc9PJ5oxdf(cOtjM5%-<`i>rt&jcB-Le^C_00qe~3)MM7wwvXxWytHe@2l zgVVXg(3aKG}uC8pq_0oH7pAZ|U=TXR@O49eh`|)G+xM65*n9HtsVK&JWS2Mgry=4-wxl0Hja#Y8R+=_(Mva!EGXGqHc=Qf+-KObG^GzRmfn_3`u>R>m*=U%G0`~nJyp>~O(5vV z-nyF%t&i|f^737OnR~0=OE|p~lJT~CU4VtJp@IkOOTDbGpOyfBTc63e-uQ80!O#1} z&zf@cDVgQ>H%n+1W;>V_)4X05DR@X0S10nZMEpEU6q-*vWKe|ts25|4YN9^7g*TF2 zv_&=2O?Zi{3lQtd73jA|?~RNnFo96d#3hqT430=>-mHojJe){qhHz}*QC>1Y*}ymygUf)M3Y~yj_mwcIc}Lg&AqYiZm;!#h20;=%FWqU*JOh(9i*}d zMOC2wX8h3V)CRSl2X?8CzTqe4SmlG(f~X@gtt#mxU9d0CSx{bB1obK*FAg(gdbTZO z%q&2>pyaXyf;ahVgdxKLJu=6dDP_9Unf_57$ORb8s5b<;1WzT`gt0U8XmJIQgPS_ z6K2!pzaBS#n_YQya)vE=Ar18HEldgYg+Jx;(U)`*~pc$$jb ztWdYlKgk_8mmocagE#La8+^k$6gTpCG--a~txcF~g+LA9%zWRb67*_DKNWzBMxnJ; zS*18R+&S0h_!%NF)}QJpMXp}H*2wEfnEep1l> za6@cP81<|2on-dj7xth`v#XIF4p+qGHgDoD z6OEcl0(mty>Um4yHe55XyUxQMchnN<6fOY0C z+|5;?uBP<#letoKIlb2WcSsFD9SJ(7>QJFi9-eu@JpY3lts+;Z$Q9yE8r*&tKfgep z6OsR45bD3U^?$Rq|CUe;taME5{~8JZLqf4LveI!d{Uf#i14J?YD-Haortp6kQU9_A zbgci$8PGES%Pz40BT-BYO#eHRz{tS-FKNK=51;=ZCW?{aKY@(@14aEm5dHrOqB#D6 z@Bb7M{{KMKe_i;$m-+uGqW(n<{@wF`izp^`hJPabe{#L69yf250ppd~k#lgSWf(dw4pdrFp6f2lW{0k_;br4bRwd(!-Bex-_ zj3k*JYkka+0xoZNK6!2*0gvA4gKl-YvpZhbEh~DT*%`k;e>p1v!z_r);INra_1aPY zvcU%up~Yu3&a{3`b~FFg+zc{yUQ`yH;qKRS-3QEq0Zs6GWb1YBZ1mRmm_}e1|5qpH zNGulHgY!&Giu)*W_=z#&AlG~&$l)p?r7^jj*WOdA5&thsAcUUt-f`m4gp6%+ptswR zrsP7n0$H?;$3wDh@Z}#O6`?ESA3z+x48;gNBVN#W&9{hpO((i3nlr*E zmJ1wL`cZ&=Y186@EJDO)=ne4$+C0KZ4?nM2yP*IdLvyL0w(;u{{mo({pUaOkRMi1i~>^M5)y%nVS%jZdS9ex(*QW)PV zN<%_!h{%b#5qK_B|6s2eX_v=6bs zlKta<*NsSSSe@>)o$UZfVEm#UFurnRuAfaq#LGnfK&m(?ZP7iYF$df7J)MAoE>9Bn*up-B4H6;*HeZ z|AeEny0fOQ0q1?;K8w-AZyQ(59(SsX2{kL zdZ`9tNmyWnKMisLdGb1|;RDNPPvvFkd131m6LNuE&EZaH`qbB)(t|J?f9R#LFn3(1|NB?*>>hcuk?VA3G`?66}&|eBJ{um)<=^ZpM1ZQMB38CsjVZ|KHK}!E?ruivZ=k` zr(RVVN9=IRBl?Tt8iyfqNDi`VY3uj(Q|U{IGx$V9{h+ylf=FEwaXa|vn}^8jJlvrt zJVeM*YytX_Pl4?e)&`lm0f0Cd#S`fCOJF;fivcD2stnfZb-{t}%<5H<5}8cwYF%x3 z2LK~a^=aW}>W$?AKb2bPussh(b-0Z(FL*>A>jg|Nl3N#4!V$;Oitvp1h#2%+KDp&r zeUy*jRfG-dgXWd!qyZgw+;^Zw79^T!-&hb3$BVKQ-c3bdB#-4Un}ohFm>qcAYeo#p z6{#uVXz}VEPcDolwq|5}0p(uY%Qb(^l>k#MjO0 z=KCPlfw&qd`sA0hD2R8Y?!R6WY-crZ$h(7L75Fd$xEs|}0n?8BOPts?gI+vg*B=oL z2Hatd`u|2CDE6Th*lGo{fup?Rqf{Dk|KWqr*A=fvI+~ZOmnIz>gq+yge5{YrrFaUv z{*@Px=z+RnxG!HFsFVwZdjwez^O!1WLEf-Sn$8aY%#Q2g#@H$!xn}#f7u~qmB8%J` zbDtQ(NAaCdicS&mOJRfnfO3`R1D&@2FAyC5ettp9ALTcRIr^0t?9=ppu*c5|$B z{#l8=w;w5+Qr<$}jNk%T{$&FQK1@rH6I7 z)dLOq|A9V<5&VD=Sgym!i+slqMl9sPV-p2Ta7=L{SpF*@*2%Z~EMx!jD-`+aAm8e> z0yp9r@e|?+yq8btjdX@x`GMP<7WU-P(PKtgWJVBY_~I?S6^0pllPYE_jzLg_pl;Qt zB>*J&afW^4m-+d9zu8lM&=5OhOCDsPFJ!dk5q?{1b>;p9?D(#f>;C+R*n65eV8cEF z&C28z?V`88Up?N=aI~PTGP^Uo_bI@BBzx|QL+h)(%|`xU6imF!-WxaQ`=4o1d7JO| zLG5b3)k&}u>yk)MxPL@A^#@2)5wkm6V^C$25$#Ob?qjDzTy5oZ30@+zEm|5q?JoP=3SsrKUX&0*t?FoE1D} zKjb>;E|1+gCG99*Q_07WkBK}|y9M-!?Niz&y^h+B;E(vTA9MWLW$3ad&!t@7b!2tU zYh~1>)oZI-s(P#&ty`_DE`P7mwH?JejAxbpQoXjCWYUmlCDx3$nut3p zIR+gq9d#vzk;XkJ#H}B`1g3dD>ou`&Sih3FD|g3qYwqaW`s^Ux^4t>LD%~RQOz-IK zFx?W~BHlvYX6-cX#P#UEJNo$QYwH)-2irH>53nz=zu0T(6|H8f%~PqXK2~m3uBh5p zZm2((Pts=3^o>!!i36}H32;+)5^6;`PE)@e_jes{GMk1v#!K8pJB)Pc zj&y{NbV8}g2RiP@I?NNCxb60J@LzjwQe3i|2u=?Ya}Z}x$>LNvPCxKDR-0m-sC%-y zxi2`}usB~49cxW-PsQFx{De_v60Ex;2~M%pS_Ed^t2R_!sxS1P6;v&kA1W^{2w7_q zoJa$m3g{IW)I$o-EkBr~p>^}^3&heDi15i@lORp3p2=k5keRS$VA){UAXY}tWZWUl z{`xSWbqZZx$XC&;^eKOtJs)4VRn10bE?ZZvs@zuYM6V(*H&vag@@w(dav|?U0sGI= z2j^Yr=f-I~ulJ;#>V6+K*{0o&E=fZ)SEWhQ^FORVNgvYly1&n%$I#LDHD51WL{-t- zb+m)-9Xu9Zm#jsJqR%tw%k-V~rF$?Q3ynhK(jDoIb<6>TPC}lbVd+Tp!}}0?0_0yrYId~y=gBb$ozj`RAGv|Ps3WxJ)-EMs+2pX{Z{lFS<(tDU|aN6^t6r!y&A>?~l0MN-jN)6D_ePEps z*m@A(fV_P!n*iNAux_|6LylzX45maJ^v0J7ONN z+rh3oyormpJ6@X5dj>eNzaq&ylJ$^e!iX{f6$&tmhFB9%Uw5!P{tI6OJTP(j*?pi{ zhHO)TNa-NE{qPR))nxLBQW*OpfmG#W)?-?Yu{^M>2qa93h^)b5o)0kV0&In}8!{{+ z(#|6Ct?}mjU;we!#M80Iqu-4&Hssu6a*a_}Wb}p8Q1Ly*(aD3icR-Y+(qnv$c{l|0 z2~b7^Vvg}RL}W%}XhtEEcbE}G^G6JvLN+9!G^F%N>SKzI(cMz9Ng76Bya)t;h6MG9 z$t8Fs2_l9hzXkQe`1j=BaC!+OheWqXGO|hRU2q}l8y^m--!T?F# z(mTYrMPUkYOXRQ;!tKENiSTPX$ zZ<31QNR=ckB$5~hd=M#$l4wUN#f-}GOsn!#mQt?DGBB%RGOJQFs}e<)OqK#REa3$S zu9y;6EOA_xT(UWli(<(o(KID?Su$Bku;+N0Orf47@yq#-W*}P$<#VD=4JK(c&y;CH zr*-evcv`c`HK-R*?ctS1bB9y!SX*=Mb-UKEYtzql!51d4l&_#1BXb4ypK(3|{3iKz z^DF5W+A0(Eb@?aq59Y5By|TUq>?igQ?ysP4$tnxhMW%n>+irv$l(gTS03P4KyCZFn zxZdQu!)}MQjbY$#dU~PUk9@s(_nrl)Ya*Ifx#+c zv)U!5?ywLuS7|<8p>fh`AMY%4&F+@kcD>+oxpsCv^6GZFUhWi`*X{Uxyj=1d7<}h@ zfuqA$zS$v1z;CS9=`x$XlnYv7;^Bs>T^cw9J zf6fz2CR6J)-q#N0c{;3~m91B5y*$5|vDxf4x{uduW_f=GTCcfX8+E(%w~*Jg)q3rJ z0@A49@VLB>j@4uFyq@2z$-nPE#@Xd^d>?C=?el!Vzu#}XZv7l?2IKg?j{qU^^m?6# z+&lNUw{Nj--lN>Thq$}e( zO&4}lvpI$tOc!Ml!`?u3Y}*QCJ5-8Qt+w??!^zpE)yE*~mkVpedJ1$$40Z>XyG{V} zHE%5xQ~9buG`;ohxZ7{46nA#gUZn_Zh8tI75Owg+wU^pol+G>?&e@Eu&BBIW-P?LO ztvO}e6t;(A_3BlF<#5IU8nekgB(1E9L@ceslRYR)U^ps&_bqn`5l$`X#Sjd3;0DFO zJYTTZ3VgaZb9UPm{hP7(G4X!MZ~9ZJRsdfo+W|Xv`AeOTu*yw=rMADYajA7S55NpU zD?bzS5{JR!VHgZPh~lTJT($WSycAHs^d-7GS$73M+tbCEm*8F<>Ja@ys&lAoOV)eS zC3c7>r-!`tw9l?IG}qy}^rB?0=Z6D3X8=F|5EFivb3{hCX2>b~#G>F4O03&&mfY6d zhLS_*KbANpKxbe1T~qsa_%OO);^69*y8(D^1oS&e{M9}KI~EkzUj`o8yQpr~l80hm z)2R%`uUN7P;X4jaCMvoaNXnH_8fiN&*`!7yxib_9K&uewu_pQ8r%OVhMOp&q;b

jL(hp+iU00{CfJ`30aw#~U_~wD}d4l(dyryXS%uyX85b*MY6{xA~Je$tnleQh|^j z?IXoQCcB>QozGb<@~ea`4TIZsKXwwK(%Qz@|dn!Unx-Hox@QDj{@{Yfv84o5mPGXqD|{+d4^oChwj~ zV8Ew*nx%IIOG-AhClrLi2(-Kc@RdF2H*xnLh5Ic8IvKKA;lrqlA&3nNxa!7 zbpp-8_=o#fQ;KoJ{i8i#QcXSrewnUksKu9@!DX$`D%NQzb9PJY(r2uYisdXtqVkpa zKh?4@oj11x6f*tsbI1lnpos>^O@hA!GjPW5nXNq{>5UH+M}W40#O7#ap>U0hrwqm| zib@DQQLE!c{EAcdk+d}JnoVqMoZ!E3UI=1|Furi=X3xwSvh>7Y84T0I*1|a=IilJl zGoX`^oF$Zgs_O<|E&V?56U0Zg2Fr_i&FGpKFWgdQ$mU!|ALI%QOlgsIT8N; zHS#B9oDDD2bxut16bUOWlEkAJ5u-z5L~9wb-#%?uabS|DPf?EQRtu$U=TANm1Qf0> zd^-Ri2SQIGC`4rhrjZ%LE1YBkH6k`bU_N8E`f1A{?Qo~~*gIlRqNS%CrmNDx zT-wJLSu14W-BNNwv<%PC-h#Qdvr8zSq;KJZvUsk9+|s%<&ZPVpxJjVR^n}cWQo{}r zeOmbjvXBjpGer}5q)^Ln9ufEV?r}qo8n9|juwAMrk9%#XpEr`e zI^2nLY0%zOH$#}9nA#Y=i`N>xC`rV(RQ^~-#J3aL;dBUZ_c9OTgQh{&xt-FBQ#Cw_-Bzk_FA`yM>q$cwY)VCYzb z%l8r(Y?2gQLLzZ-F+1hDkd#{cRU% z#$j7h;$pE0J0&FXPmXr!pZfiqekQ8R|lpmU+5EwB1oT_V7lG zl^@DrQ+RyhgE6VPW4mLv^4!rwsK>r@QYvQB=9aAfU})VwQ#V)|0uK5aD@xJo9a-Hz ztPI8pVJJ~7VJ(pm1g16l;a_!%0?0uY5j-ObBenx7A+=B^R6#>WlW?!(fD%!#ZQDagNkvNuICqHj3up?}#aV)ZT5~iK`P>O+$)r!CW~EH} zMa9BH6k50vAH@4o3vH07C`G#@IC39zs{N9h3*hNZj{*yykWoo;C936&r{Q&h1e`}o zm3T513C*9)N-l6{)zs3Nq+D8>`^QWw|Q$m+K&#NDQYqL8xLTqE zQl0ezzacg8;5z|3D})N{(*F$^QT={DpvnsotZ_mgh2H!PFnxbFh|5SYpKWSwJ#x1B zVHY$ck@>nb?jy~`(AX}S{rfj|2p|v|9*p$Z1)7V{dnL%juYWoBO@LRDa0JgYrj?Y( z{e<>j?eYl#(4iLhDZ%R;kynQyJtis1UxTKVgVVJz>)7kI=b~>K_n-*Y=Zb;omE@`y zDPH^`y11s5JD>Blmx@#(U->;Om2;r6I_)Co)eyT{wG=@auqc(Y$Vgf!m*1j9N(fZN zft*Y=1OzFS0(ziO`SepIrBys}&`A}Ql8frQdR+4$ z4uf{;FC-tP7(Vbf5!3iOAaS2=bqDc10VJ@_;Vrx&5Dpu4>^0g7A7kJ0c4O1(ZWjd} zZMcT#`gLFr&IkMk_X>Zxw>qUy6@0e-<^4Fv)68^tVfCQ1x6$)^dfGEG%QT{w%k)R~ zPb#GLi&paLfV-RTCdOm(O5|OuZc4o)#a~~_UD1+FI`~RPiM%Zg1j#El7THD#u9`~MW!i*)2aaaZdg0{vxMlO5@|A6p2_>n|H-vGC)$9!#=b^yDC^Vf8MFbd~=qMZtTzv8uF#fOQ9vCSu! z2{kRYw%~#ZmG-7H5mrwJRhE#nFtG4AyPs2Yhl6Wm7h=r^0gwKBDRU-%UNraHBAQmM zU#ndNZ}keC|3xW~H7}r|J{Hvh2@ClHS$r?_^A8M8XFsN6bm_sic>n5$_3BaYwgsSh6bPMwwsd8y9NMk1Idr~~{i3ls7g%$Y#Y5R)$ z7B$^vPV1fG3fuOFdwa+@V1EPsF91kDx4)ke2T3D6NPpUZPNo~!R8V7&P4OBD}ESX6blE=tea)!J^zM*YsUpj$aWn)c zbEO5+68W3pz~G7CKZC!8GC~WW7s|sN9Q`WTfHOM?XW?VKF2UzSB06HkOFZ@MM;^v& z7FkBND9@nxVK48H&&jvA1N};>C~mA&rt#t_D_$Nt6&=ba>3Vt=uXpGV^bZ!xl36Ni z#&TIcn~bAc$X4RDpMAz1{45V4pEIP@(ne{Uv{O1R-lwS^#V!4<>St>j)?5ria8Yn| zuq?PQ^cmQ24RN!;BUCPqei&ZkalKcg#O;Cih#q<8APq@t(iJ%~l#C}uV0*gc!~=9;ia2jb&$(!4(q`7!*ZI*W}*Xc%jgSKMbNEgxv#*1I7ijN%4#V@~e`D;+gkKo+i z#ydYC>&Yy7Rn|ipQGPc~{=w3DDti+?VV8)CZ-(<+OJYeSeS!5vDR`N;mIi{Gt%ny` z5qSjm(+&XID)my7KY~is<3;ck8Og`Ma>yhz z;cIvvwW5JENp2|H$SFFGm(nP*4=CR*e$Px&2$P~=9w}h!c(&2f>sBBzZvWegF?^4+Rzx7 z6nY#Qqdiy{nu&MZ24BJo*hc0DABHJ-a{L%|yQ|cZo|QU=8q-qxG3`TF-@3lYZ7;FI zcX+)B9ig>!7)tqv&n11fEk#Fie#i>EOP91&K!P9=HwF0Bk1W&oIP0LjDOFLle0Mzf|iu-VX?+c*c0${iRV8nbAwzITGtP8`M@E4+tVMI>?u;Dfy^Eg24L*gfN zhz9`T-Ue`t1c*lmJ$^kv;vfV{Ytm={7mmw~G}#=62gl+ag1~Y3mWNS)Ru~O_{ChM^ z4kPsnKx5oHo8UOprUIlN0ceWz&&2s;IS{V^G{doH;}}}_0djD>EguDFg?YKB0q#N` zwB82L#)`NI&=z;w_Bg&yIG4^X(LKRFx_*8KcW?)H@c$mVIqu*N?%)pY;12HK4({L% z?%)pY;12$mL5cFhMH1iYS3%o-lw6Tj6?CmX3M77ofmX$@5QtOD(iO^%(59dvYe^HZ zrI=-61Jje4UQ1GCC&kt@gne+#40 zV9;tc5O24~#)8%s9c?kUHmYQq(biyp(h&Qk!E5wea(Wmy8mV!*Tl-|Z=1IISGH<^n z#{~o`=;m_Q_OW6K3sNhun6K2)>w;h5Fv7ubXs$t4T5{4bTqre3eZ+iXVH3L`rrSQq z;(~&r*eFl4%qVv=cT^@5$h3Kw+vaATC^w68N4dug*naY!;8l_~VD$jfYQR$icE8z$ z#01|Ou)2Tn^#RjK%Pzrx#E~7V$b?m7cd(DZs^F?s!Tw}Nus_Ws6L1oPaM9{1lH~K- zKw?Dtkpi3JA}a|c<7FWeIIT*^t0dvI4=?)-buCDVe-YoR1qL{{t|D}qIp%JX3=j9LKolzqz&Ed5c z+E;*jC)PYQ)kZQ2*}gJ3Fi!g6Pw`3#`k~#jNXJl~5@G*7*%fC_s0dvvqb}Vmq0111 zup&&M%l_e9UPu?|7MV^NC5=jFryZiY+U|*K7vDE(ux)T$@AwI-3Azzc_t_@I6~;eE z@0Vxj9x^SI*Qi#TPuV}A=j3y`^CpMG$)#wg!4O-l@w+`)=^6qWGg|7ENtR+^ar{Qi zb%7s;w$gcWNyOw}P?Rc|CD=s@il6{mhz~(TnXSz;Gh$+_D8#ZSxt=e|9Fv*R++y~5 zl2vlQ3GZ#1Q8umZ`1dxy|KMW>x6hopefzAL_Y~0g2q$;#8d@FSTg>{aSOy+TtWs_A#b!$>~}G-cTC{3=?c15&3AF40a>Cr7sdh@~5^+g>iAr(0LnN2Xhp-eCrKT!#3-5_}u{p#dcAWw!NOgNGvaD)` zlA1}Y_O*Gh?^B%b_ z`TD-b3Hp)7hpZ1pEw&!9e(Cr!{+dI7RQFO8jW;Kl6U|Pu{7UE=sBnSRNHw8reuq3+x1r(5isa~7gmyH~0-n>~BF0Z1hXhdZdS5;@zSnspzu5MfV@T2R= z!KmN=`QDAYUpT&HuygnBHn}5?%{uYr=m}4(FO53;@proi?l`h_(Xgh-$^N0Qcnoqf z72fmLmkcq64u%B=-oY}!G9#Y#j=9e~K6+%#bi;$u3k;>vi{qa&XeAdDUsu$LpQv*c z@fh?(FvyQLJ50ovQ4FNnKE2+??T6@Ah@)fuDK=+<#GMTc*5aYA$u8svwW=t<{I?5skQB5LSvBGF5h6w3d58UTP}(ede#sioT)eas%Cz9A(E}*d&#QJrv}f@`zTlOQoW(l+XuV&Tqc7>1qxbuA^vMZG8|Nrr0n101 zwi(H^pf6)#7D89o%%jgd@%rogg3ZX#=h%Vjd!7q!M%7qVBfJw~huzZisLS8^6T3Jb zOe{@Y9rZ%gYx;Bg^YLm;l-=0S!8GYoy6zCFD?`yUM`>-=sHoFMQ?xNE+GsMM{Q0Ad zTBpr#+=M&7(d4%gn=QeLYWk9illO#dqVoDZ+?imo3^h+S&o-|xb2G}JU6DgO0lV2w z?JHf@BcvIa$SRcL7G=i$eZy|PfBluM^_chAF`}Nf?D`uu zq{Ljhhac*u4(G&3G>wp63(gLkFlswq4(8ud0cLx*_QfFzoDmrqA10a_l7E7 z@L0b_a>h%^&S(Sfw=K|(-0v+mE4)J=%xpGO^U6kA!7L}q@oV#9hsI8hogK?#D`-l2 zYGgO7tmRZhbrqFOE2ykxi%?k=I%Z<`l2t-Ot-`)}lEGrm7Wzt}Z{ixODz`W4JfVM^ ze#2>-BV+c}-2cwJ&w^K;S^UlJPiwM!Ebl&b>z0Qf+QIu6$ESBoZ~fzi5rx6u-Yu=1 zMY@ogWc#bzj#qzLup_@>!~_LC5w<_a zk`p%cLK@JEl7Y!8Pjjm^dl=ilG+5cCx#=K#^q0l_&)rK`1+BrViu1e4cjV-Har*3o zs~m@`919*uhfDtKW-+8eOy`)+zOVG(rc0W1@(4UaX7Xw3BHdK|bi+fjOQ4i2>^akA`6SeY~^DrY59`)$Np}`cA9C zS$tGOG!=A=-<(=(^1Dy~@F-w1o2aRRJa!<%UK~LG6wAv}ifwgXjm>YPwv|omyc)FL zf-QfUacfRNT4lrt!N&Gh-2Wkq(oT5}DU6BT`vG~6s2Wc-x-zB+pBy9pB) zE(_lH_~YP>$A&MMFn0dp(PI|1?7XtioNc=wo&5rfZ?I2>~I; zR=i63j-5Am=!k{$szcpY_ISSJ(H+|&-eR2MaWsTi{8=p>U1R*7dt(N8Mzi~3COXD= z9&$Y5TN|TFhYDMj zfI20S_j-oaY@e`DNry-d=upcnw^^q~LihFw)w9&G*5bEN%gWS~w{MV@Hyfi`6@vod zvn#fzHEotvPbe2sK;Bv{iWBK03I}ZpYp3jvnK`V_BR!jw=7%R9s3xk{S5!Xy&`(=- zeL~-SZrTH7+h;zqne;I~G^y+CkEZDD{U;FhN0*3sUGNJ$HGLf{fAJ{GdiubL^-G0@ zQ8 zJT|cWGiRz_z~cjfDeyUO#lL(Ov)r**nYoZFCdxk67NF3r)c54`#pY-JK3F^q)p0APIl$G-A)+doTMERJ3hrc#D(tXcu!=E;y8=ToGT7` zHOfKnMy|5XS1gM6Q-B{Rz=L)(an2SC{g=Id0?1wJ~Qt`R@6`Ez`Q+Q+QwSDdaf~)c{EO z=;x@wNSyu=Y36kE81-09p?MKoX+9;rE*~>rGwak+KIu<;n#bw_=3n%`7=AHoxSkuh zk?FJ=iF0%#)v`*Z$GBS7tMDM`Qt6{HgEHpg`e>}7aY~ZfDKoi(PVs9%t^d|9{=mW^ zqC=I{`K@{vj8d`QJ^5LFiL;fQ@Criwx}N%Ds!MvdQcv_EZ8oXSs_1N0iHfS8FrE7_ zEMRd6{Ab5?cEp)cZR~luj>^0%3U@1SeS{N!Jcj;~`NRq1i4zN@Fh!Pk3F!KC2{?NV z8n};}m|ArRPZbdQUFcjsnOaof!Q-cgiAOrjS5Hp8lTEo?^{BjXcgVU7DgP*3FgX z>g0Mc8fLH8&>54c(L`CD4I4IS011gswt2DR+FYH4t{bSVGes3Fg1);m%%W^Z9r z*4q*@x?AVT4XQS&^erZI?EPR9*lUoCj{b4r_Dt$LMAdiP7xZ|~(d8*h4i zOYrJrhlAgi9zmHA^sCvs8WAJua=wK(!77*FZ8(zoI1&wX@i$aPvO-1b27?1xk0)Fmr4C(P z@7h*_Z|nU^*a;MS^X)xvW1m)kNdq-KMXxP)*NnzHOhg?xh&tefkNh3tqvLI~upSwr zjv`i;k^*jPEcF8RPUKjpk)f-mA;eeDo8m$XN?r8|(NCRPQjgS2^trS|8|NEwZ>`jJ zGZ&yJbi*MCS3Kg1iD%cC44$*1;mpT}!AB)TES z6y?Us(NQiHrjjMEn>BVq#TI zD5{BgNE~hDs3_SM)|F0N5qxLUN5PHz%1O`j8wq*bx5qvFz~uSI?{~LYNa$m;uC=Cl zyGYIDsl^A$kdMxh;(cQ(o=l%o(yiCL9*Z`f2>xC&ESp%wN^V8nNmgWPtT^$a%17C< zICE;Wo3!s}saB#?r$%LPsZ?@7iGdiXF1(=1%N4APa^*4*7ao=h4O0xfRlYs7pa>7z zigFd)$W@NoC}HT_DjG&oGsW{?ga^Z|$LXKPkJrf3p_=FEpg%j(@|tdV^P@PlIXEf? zPwp4;M8%V|@)ma~Pt?jYv%<7-dYIO45T+h)m?k>I)b0pBflD)(vs}_jX^(`mfP3o- z*aQLKX?T?F376m+NLCkSuEg%P@N@Z!X~5HcSqbi}1^Go&b88A}*GW7!5Q3U%IeJ{| zRyfw|(AR93vO{(An@7`XZU`daN#O&QU7eEsA3uRi+f8M4W~ zZDzaTS?uTPxQf%`FN)Q~-BRi;R+G&4H)YA$IciPIdfH}kb8RQ>0JeaA$W$}5AG43O ztiH6Amr6VMcWOz?Ni%+qi}z)h{Tiz~i@8LA+d#QK$0~BmF|CeJE@+}ckCj_vME*tp zU2)ix*L#;*6Bl%gq%) z?G-+Yc#eqFt~}RKsm;0ST=Y@w=%d7=k8%)r=%W_-$`)1o<3YbglZ-QI6qmyVlH+qw zel8w}!Dvj5EGDY6=BSgSbGSb`M@)Rb7o)bEn|}J2FVve9O)Y>T6dEBY#7*#@%Ch=6 z{fMY)*3w5qP;=uNN_+$QVaXMJZA zYrwsRM#h2cUOvO{fN`Ngt)r4U$I#r^gLYxh89{&tkimlZUX~pS|+*F6{M+O ztJi2u~t{E9hq{G4fdx8U{LsdUNc6ZOGS@oYC`I}YE_~j~>kcIWA z$w|r%``uovPFS&KXl&KuJfKpeBB}BC!qgg0sYa#d%q7cGflEhRx}Lg1-4tDkPSUAy z>s8FO9$QBC;IMH^Rcv$7^*ZBh6^|Xzr{_(=3?f1s_No0HbI=SNJJ2yl?av5DGjdd@ zBEm;J5Qou>a8%@a!mp}yo*b1i8WAPZ*AGNtG%*}a#HdX~f8SfDGZF1mHpP4tVInpL zVp)HZ(L*PzgUFsad^WPCq^blxy%FdGd@8*UMu>;m{z8X|M4O|d<74CFIXClYU92vi zZ;w4-eBH=mW9{+OmFTzhi0TpRcMOyUY6h75S%yXpiXCe2?-&rjBz7$|$2l3ZI&~VG z&xH<*qa=|entXy+aS1oOwFSbjz4-+cI<-+|aA$Dg;wd|Jwiz-rz>-DLt%VU}5o!J= z>9})W@W9ct!9&~LAc-HIC-Dz{``FvT59w(#kvwxe_}qm{!A<+$AcI~B{uVq-vPgV6 z(LE9T62d+$ufg>hqg!R2w-w%oBWIadC%WL&?Y3aN&O@aQ9`APF=_x%Qig_w;;L*=Da z_??wh_mVa83BMoEs{N)MlewkE+EU0jDN@Xc5!J*Imt#RivLcNBgd8(&D(2)QM4e{j z-*nW(!$g#aHKDjV9%ZGJhkWkMwDiPV51oFmTm61rL)VY@n>3)YyUS-}^ZeD_pL#Z! zE*PB;p&dKq;<_fyQRyB0WhVnL<_)8U3h$-`m*fjA4?qu_oTCLUst$P=w1)aa;eC(X==oY7M1 zDGytfw_}lWsx3M;hQ}(afu2~3Z}3Zop>I4gc}Jg~gIfjf>oso7te>BJ_KyY9A=B>d zfz3HB$j1Xq9$HZK%*o&{YsrV^Ny`SbEpFdoj3;(jYWB0ECcipz+*@;uOP0^Mw?}5? zg!--a&zOF;c-pr(hji5YL(21{9)5#FoyZ?dJ& z981*))`oRnh_1QjO2JoVoX1H1J$S0JVtCCF}QVP@mA`N{2uiT)AQ0dCY2tjMZ641kTuahTHuNyu9%)Nv@m8$ zOi2ujF(~IN(N=?PwFNe@=ZLRc6^MftI=Nv(s+b_u5`xZc*nld2uHy1qc;WGp!K(M) z4*ogic&FWuoI4;Ls^0r)u=?5M#PBWaQC)U)|M26=cOb<7Af%chtPff0Z{GlXmIhXz zJqMawa;(kmouQMZv$d0bAPle!v<|SF*QnQ+DC6k3$SOS5XmxtM#$Ys=^wCjPtN1J9 zc3VX#w_F0dOHjSlBB+0m4fj&?TPbd+(S(3qQmdUdd$i4Fx9T+-r_G8{t3_`zxy+Vm zv)N+R=+$hXA#jqV=&`triQY9kHSNxC*jlZ^RNg;=0wKpHt(AtFd?QFsl7mw|?IWFJX)#UtFtwLf7qPujf{R_GO{b zeBuNKxhHC4HwWkjna~Zgpc_Fks2T$Qb z=RLLR`xJJu#u5DCmnHkyi@5n0EOm|QRQ0U#SAlT1>4@vCCs_yZaKm^jJfM7CZjBWO zk+1zmjPN)NmW11hBOgn~%42mGWiD~XWVK2o%Nm(! zTv}bUR;!bltYI!nqH)b+y-p^mibPjILSNrm=$B{TjWdw_jU=hvf>g-*3?A z^e$k%d(ah>qDQ;Gj0o+tA21k?y9;qSQ*Q|UM1vG2UkUxp72l$>gqLt(lT^G{ep669pAEH=Y|D_f`;P_;~EyGml_r{SQqoOW4pojeB6%u2jULbKM{Af{=2qM z>#N(xkfhiodupSGSvfqXQD=U4qyFmr)Y0m3sWbEo^{4cI82(7LWM>%(H>ag!#b&sp z?L!+(Za^C(q#5&!D~ubBA)~a>xW{y7wqV91^_BxnArw*~--{t6umu zMQf^TJ~1|1bWE(r$He=J@cSsN#BxU-9KYvCr{cStP55LC$?ULb_JfH5`=oakFWS-5 ztcguNk`O!m#N@#l6UU9+;!B*{uj9`7-RE?VHX0l$UhSmDcjXt^iwa9p1-OFZ^dY_%cFw)J*CiNqI?uB$||zoRFE& zHepKA$|SjERBlYJqial8M}fM)Fwj&GGsH1oeV<{hX;RE2$FZc3^`FFk68Cx353xVQ zeUW%MDU=lFlG03R(dkm2$uD&^^^`_SpCtau|1|5(HY1lQ#3!Jo(ApA=I(y1HI%3xO z@laEu7rC%GQO9saMEr;T6OH+ApuJw^WD&o3XDK(qQiHe=Uw(`W3$ zQ~R$gdaO8I9K0Gl_Y7HmwBM35Z@%-oc>C5f^bM;-sdvB`e+={0qvA$wN((O|wnR zM1-(*PFJ2&6~Yu)37gBtUp9fi|Tn zeU#AU5|*;uE@W}Y0u;BU+?M4g+=li89uPi48=$ng_oaD-)b{(&nUSmz+TQ!#jiZ@= z&Ybg~bN=o8|D1m$LqrNYHx`T+pESJTc*pyp<3s14+@E+qq!Q#RF0S?wv0LsVGAPtC z<>p#f3)5&`&MY=BaV{&kR$gcBHGjze-t{>?XHjq-YcVQTkSwEMLm*nL(bIsD-)8kI z$_X1*Y(d*b+YTFNOWVs|5RMD)2~z?`XxA>Vg10EJQWvZ?^3aLql+Hg*{TT9@7A>OG zC5=R>L8~=_kw-weoAmP{FW&mb(B_l3Z~Ru>!3p(;L&M*Ha@*F2@A{`*7an~GvtL=Y zfU$hOgt5Q+@=srS>(v*5X3Ievi$JpSfM)LoyNgjl9At6PKqJk8x zFqQT<4WB#D`-D067Vn&b1@<=If`V1{4c^rSA$!~xD%i?z%{$MW_bAAPt!B4-rHeRj ztgFDfTiL5Hio)d=$O0mQC*k`YAYAUCnR;`TGXwj~*z7AN5B~fW0dfu&63bYOi(L&$ zxezR$8OR}-u8Y-e6Q>~rg>`SRn)-ed`iA`ciNX2$p0*Z`;=yD_*tB5UDU?u$5SP%5 z7@oc1Sj``gel+~18Tr7S;awANF{@1T*4?xHDZI}8=ol^rTVTRflkZM` zrl?OJ>%;fowXpAT;ypNEdD;Q-jvE~e7C9ws_11Z3dxPEq?{`e!H9ut*eda3jKJU1f z^OD(B`HBODqM0>W3uK(fR689U%Om+ACw5Lbf}GpWA(puh)2A#4=gbSxr!8_pabP#} z^{B`D96p9h(0ME)vg|`}NS1kJKEx)Uc4^B#$$xCNX}b$f#l}klFT#?dKmdzuyfq(J zJ>e0*62?n4&^hL+sZMG>%RnB3(BJ7Z?f(tn%X0 z0+Z~@Yw$P7evjvb8!PUhd!u`Yn{%hF*pEt!%U`e_x4vhcvU0`NR%<(0MQ?RYx`ZfX z^$Mzx=SXljNCKYoB>U{Np7U{9i!lblin+;V99=srwPX*XneN8SbM@RDnx3bP^$XKC z-|eyB;eBtP?SK7i&)xKR{ulk&pJw0Dr1Br`C#m&N71iyth8m?rX1OozbR`FAf(*>!?y<2k z?vvxkFXV9*7v2KppL%3+6>gzpw4(=uZJfbxxQuHs++{GhMT0@$IEFJg5H=eb)@kBw z2BSd6W#okdn{_wnJ;ZD#li4rJyN$Tm*lKJyvPQ4N`JmY}wFi zR~hQDV>*Nu3NPdg`fEOO;aT#AB z_{C;^p44h?H+OQK{B=U7bTc<#rBC(;k^Wti{M7h?68d2LfQy{oJ)qEiA~?}26Fu!WW_AX! zzRzTThr^=N1-&_)^XUOvoj>3qjsLJeU#pJ0I;pctH<21JM9=~VH2&w0CO6|3-kp5- zRtT1#!}}(OC%T#9nlzV!q7hRpb+XOnw!M@q;c}{JZH(30;Rg{@wXFAE2NYF%4yYd;($OFEho6 zNDGU+<^Cgh>%nP7g&1MtG>O5_bxqJ`Di9XXEyPw$lx*(Yy?=|z>8Nm;Z28!3&dZof z`l<#=@}>_9-84Uw+Dy7Q=UUH)>mT1d{6O(7FaOg&A1vE&#lZg@yRQ41+giAa`&Zn! z>AGW2KQmFud@pum%l(f{Jir{-x^?BZzcKNqzK;A3sO3Vh1|0^LcQ8*XN0blP-#gB- z=N&vpbaoC9a8$v2loOs)o+%HficX8uWrqa?^DeV&wwNsCM!KpPG5j~K@K8pQRmF4G z!wh)#diHt7J)DPS8uDEF!eYNm8NEenB?ae=8;O#C<`*nD-|~0 zl8>#jT_+exa{%R`GMzV94{>t{w)2spcQ!t}Qjy1IZn-M;1XuCE(~Ae%>TjM%F?aRH z7u@&i1j+YV1paU((8P?q=%>LhyC8c_OZltBb$qAT%SS~qptRUqT#cT^%5wX1*J95G z!v<-!(q->*t@gwXaj9F0+vBco&j`+wc!T+Rw!_dNUvG-B5ko|dnPhhXC)hyboaHp5 z#!>DM%oZ>b6hQ^ocFuc5(4gE)oL_(~i&EghQREfK{r5IK-wop(X7jx65t6EarhpuADuA#-vX zI~teQE z^;+BG!&`swL^`#9GHQ7KE2~!Rn%eW|Rs;LU9`8z5O4(YTZ|H@b~XLkA^?zHeu2&a(6 zZD~Lx>a=3T8nkY-?yz#!wCzix&cdRL{9Mh+JvaYS`k+B<(}VQ6x@UV)$XPSPED z;4Zxc)Ul!4gO}Agu)^UoF2F71)^I&sn&Ty#D2bBUVUx^=6|s?u0g|Pv-69rCRR?x3 zrMAE6rP`OinDtUmD_!T3BpZxOsyXvQ4pM%#(?WOayOLxtUE{wN_XuDs?tJ8mXzTSi zTyf=Bv+vfR{Bg$*YJSF=CAZJxp1r*0H*P~dvzB3$dMtZ3h#RC0@&@Au(+2ZqakI2p-fY}# z+H4-H7^}3B9is9XbIaGuoyP8p?y7WIx_n3ZH|6h|?yGvB=Kk4_$xoRct$eKNV8t&g zT!my)++NhYUaa(+WX`8n`i#z^K!-BuX`Eq zv&>qE_z{Vp6p~k=;1l2_VFfcJHS?g;6`(1aMHX8CAWz^5YK_G{>?`*M z9iD*q2y^`bp?oIPJY3LxVkVyHt0%20Ae!G;KVHwY*6*li>J^M}Ia155rSD~(t2vnf zez1+aPnujojXsIt&sFP>iv}T#uLf(Lq>7t7olTf?YfC|}a#m3p1ez5##jZFMmM=A{ z`ADh~^08qS9EzN9Sz;;6N2O(ElQ;w1;HpYV=Bv4URIC(|MMF!IqX(KQFthr$+enl$ zNZkDCSBNSrDr+IGo7;5Bt{wc5Ep8gPTIe@G;)eCj`#nT7|E8|^rXWRi$h5ujf_umFG^UWjl^W*M`{rAXCQ8B8T> zv?`^j6r1gS6P^;p#nR%9!ho09=85>P0=(srNyZtd9sIDq{_#x$r z$RkX67ZzfZU)weD&9)jJnak~Q~TCJugW&eBzDzJ52; z48bBM?=Z4wfEF^o;cz2_=%u!1nP{`w)+~X06%fgRIWqO}K{#XmER($^si-7?s^9=u zXMz%V+sFk6xOyvisH!%AR5(~oGf2VpQ6SM(_wT zDsGiV3e98^bPrk=n?sObXeFg{|fy=eha-J{{ejs6uX%+Yv-IhM?UQFa*I-lp)CSOR`gvWW+KMPthGsh{KSqZBO!oEU^e1>P*;F zDh7i=?YlH^{-Gd*1&jfpprkTETx$II-)QQh&pXjI(dF}edb&%0MI{@2*_toOy&~yx z(|Hm!RU)%n=zu8a$CI(2oc0%cs{e3wvY)G%xT81GG0faeqIYfg?=W!0&g>5=Rwtgx z&5)U^ZP(lGwXrrLpHgv2Kq)BHqTAq)ipv8WZ;~8*zT~wVIK=Tr$!HPn3UaVcp+L+x z7J~EU7iNmpmH=uLTExpNi`b=nP-qjE8y8xa+OD==Z(VKQB6N$r_EG*OAuS%|k6E9w z|CzraRT*tnsLEVvsj^nu>zwmYlYKS$vkO%+;j*%!ymK!5~AWar8C?a z>+kL7_|K&RJ4?5q1%o8ZqS0iM6`Kw8YxzL~va3g?mIZrct3~~}O%PSVX17-x1PGM{ zi)=Fa%@(KGY!Pi%Yqcyop*h)?*13onEZ8~GYBO2PvQ6e#yV+zSS>lAPb}PvllAY%j zGbX9JJIt*42!0|ctL-vQ$hVUHOJ;3QYPaEp?N%Gvs9LFAPyHi_ zI47R5ssy)qWa`8LVK%{d0L^2LX+33;XWO}HJKNN$gR;u0NZWbEdREWLXY9?W20rA0 zfrDex?%+r%&&ptKdw;=T2Ic7Us1%4nso8J@OvoVdjYGm&TVZuPSe` zZ%D73-&Q`9?RK7L1yPbP&r4)y+-S9e(KTbM)vOr7W;13ZYf@z1%2;LPCG?WSDEjx% zu#EX7GdBB8tkYy-WeLJ}hF8oWg-C9c9`fg+x8druL_{H1x$pn1n(R6)V)WZBbOGwpUoUR9T6c<6aI_0mzFe3!GdgBZzqje$paa%PYrHjshY_1 z7`75y@g0*7zW-=#L5=_5ZzsQjzxvKwEt4NHRe18VrL(VWxG-s&_*Z;&=VTYLUL}*O z*gpdQe0b+UYXP>BQ9M@AT;*J6eOhLN<{%JMt(qNB$U!hkc9+>>uQXPgD$R3EbIpyG z2W`eGdzIrVSEs$x(U}*uM;+0;QGVDwYP-pKQ{J8Cuh@3kcRB8H?vbA~KBqiuJLddY z{=M_h<_YC9=Tt!vm;jRk_5>`y*XeNh?XnXttR^rRexvL(8fAywZZa8pw!mve1xf)^ zSMWjsQ*eZ7J!EwR?Lp@erXy%I^bYoxwT!=$v}@nWkpBw5izwi}0bmW}BW{*Gay2 zgQREUE=Q%dEzm?WRrDo@e3?X9>8;MoYUW?%wpAF6lkuOuQ(anI{lVB|Y(e?#?dt-Q zy-z7s<@sBzg?ahMf5@J&w^J2FoUU*>Xc^OEHA@Lm?h#esa@HCJD3h}y|hw^;V=`15M&!pi`%5H z;ycBAq|fj<^7>>2o*`CC&EogP-(rDC?_nh`z<>pk$d?vWf-BCnNHQbJvL7?BKrooR zXUK#M)i6alWJcy{i_9Fs)-hNl3_Qtys6j%hd9MW{OVF~>vcq!LVz8u zXo@^avnuHlqN39eUP*9zn(l7tuVa1)-sy*QV^sG56w8a%TDJXQ7t#~%56!?8B8g`- zvWSF8aP_mpggk^s+RHM7oiw$DnDKWHSjp`4)0xBh&64QKznm=h2i&CSFG1PW%sAoW zb4_b-LnG$PNd6lZ<~Ee%RWXmHuA6LUyC;5{7~PCNxsMh3`$i^i*d~1k_;&!^!6KmC8$Mx@xLsUZ%TWdg|-`|LSvz zndlpNPjxQ;)n9)S2NM%#adNV7c*lKmxYI!(m_n6$Wi5NR>w} zL0Ai9QZ#W{^YJEaaakXYzBF)K@?cXQpH|hzd$p=7T`;r-FMBWP13^B4a7`7myLCg` zvpB(w8|W4d@Ge4~)%i28yg8|fLWSlHAZc8aFYBg)s%kIm5T_kPT-HDghFe|MrfOPy zs|L}OFNt<*gE-JeOdWOd>HzhJZHnmSCbr*E(9GRQgGHd&6x0{oi~6x*nbi;D!K}C! z^l3|DMu=fo-2S(R(PCIt_u_#pcoud4{iU!zhgpv(XKFDD>v59S*;Z|Fo#4sAO>rHt zey3;Hz)aSMJ4h^Cr{hF-)3q$r>0%FaRxpQ4gwvj*=C2RbY|&jIN{-0>Hwc>32;Iq( z$pj^v$;pDh=sZ9~9v+II0ej^c<(qRJFI1~P~h}NL)#^ zDsT^qSAsc4%(gSkw>@3J!FoJNz8<&n8G6hZFFkKlZ77p;-r+Bk@5#EoX(W z=TGoT>8dX6vvXCMFWXX5%`e3Esmy<_Hr^Nw@{QS*hs;@X0pG6Md?Zn<&Yh%*Ud3DVN4q89m54 z72}pllBo?Ao<-ZyN-;rmGELpAmH}+ix3W?hE1qn{D*($0^H?1A2X6Y^Obb5m=UboG@q=nm0qow`)NL7g<_**kh8D@a-#@G(?wi#AE% z5*6?nc++G3%V3T?-p@u@7mOJj#wtD8vov1xq)J^~(jQ^0mOKv7~nz5KKh&TuBKpQhRQ&G8g##8(}OnuxDM_E5TCdh1Kqmc>)QNV))hB>2f0v zW=9st-CR)5p$*<4jjYOr+XyyXHlIWtt_aX9Hnl8Vh|4xklC8MiC{iR=o{%nPs3WqTLdw(!DY7lkCb8W-biH%#*(5%Gw)FBOypum<4z;5FXLzOS0!J7k1A& zQYO88qdBp<1Ce1j(?^yuEs{ZJVPF&{;~l8QA1|@)H5UzQxkf@v-$>WDB}p+SRjT7S zv6W<6TSqHOYDhEVf#7Vze6l_k_5+iVOtJ3^B(=#v%5y)TO>+j}= z6UjGiw(Tq*R+=n%lcP<+z3L%9J5eRaMwr>3#aWYYZpJo|XE}MS*weKzN;NOO{@~+u zbDmgC>3RX0KepR7a7y^rAjcjKziFSCUKz5Y&U%Ljs2qW}ioBLf881VQDnA3bO&$J5 z!3lc^?uUIOZOo%9Y$AP!GozG4BaizFGbY85eJuN{x2fu&NQV9h6mteh>m-K3#wR3o zRN-$-TiE9CK*K#ZUj(R2>)>8rOO-Ay$19U?;GFBGoymzEfJith%LL~kCW(12-H20d zZ0Z%#ii-*{u)s76DzstBkq}$%%5yK_%M>r$>rr4rmF5dj;lh?KIqQ|_1$!1+7KlX) z(gBZ)|0ThZC`KAj!n(Ls74q|?*eVruQJ(i<;aS305Gy$6HOKQ`5N5NG*;9x3@OvS| zkS?L-K{}TcBbMia>63SCQY|KtfDua|Ow;AWge*R^k7R~`??+K8YE&)&50MC+V?v1p z6d#4!9z~v$XzKN(NG?&5DHbz|*Oo*$DH5lPH|E}uvm!y(0@?{6gHb06%GAxg; zrcZYxVXYY0QtUgz=tqnlp>SX9*eZJZ8JIIv62_Gj>0q%G1q&V14;={lv6KTUfx$3P zJa=yDO_k=s1nenQVhNI=B*082Zi@=_5MmSg1au+6t(gx4o9-+u_g6Phau(2h6%$ni z&IiQ6Qa%qMEGeW6NK?iaCp&2cNV)?7w|B}Wbt zV=}L9f%EWCycP$#J!%n6w>u`}HyI%de%L{2KeQ9+GR9;~6bRoGl=Tr|!R38KE=f0-gv6qd$`BNCN}F<# zw}CvvdAB5K2+sUxQKWGiN}0NHn8kr1h_R`;5_RTfu@69PtoB2KEqWjQ<-W2C$S&FZ zNUnk>^%W)_4#Rt}P$J3=*tH3JlJ(kIg7Vl*RoQ3K5W{VP%E9@uFw+Ctbl!6CtSG-Gv<0#4$7A#JxJFoKt$gADAHj1pMT6@M&*BePKt{o||c+6x807gqf$eJx|jhu`cHEyIz!8I=~L`6j{4>3nVk?*y$!^$EP z@(~50<7D(E3XHz<5-BeI2S&C;6C zfg3d~!z~~*Y`uwj+V_4lKvO`Ee?hJo|FKg4Q-AvZL#|la z|8ZA#w$6frc5Vb(1T-uRY_#<39REeRVxVVdpk-lZB4D8Z=U#EpGcx_3$Q9H76}h7M zPvJ^NnSk!UT30j-j0AK_&L%dh1dRXCEH;jR1edbAy~%%mhOmi~k)wsZvz_CAAXWc* z{vW;kr)&L}{{BC;D<*aphJSpPw27^mv-v-uiI`dw4>_$ryy&d^{i((q@HS~yJO0Yz<<$uRKKnL+kST*wD^w!-K zjg}??k_}%HsZ!so@5^$)!0tznbB;K$o3BbW?0^$UkVhddc-q_rw3*H2H4AG!)} zsKIh@dfx?i{_Lg)bFLO2Ghqd(zZn|O8+#1876WMj9^;AXiFi2^L6a{Rk7MnGAt-lc zgm!R=SfbCv8ynM=g%emgR-Xn}t45>~_5C_`F&p5pHnN&!Hiqou>pLUGtrzlC$R0Bd zG?Wns7G(VaBV7!~E}R>#6Z*J|mCH83z&>=k#Ym3hYbgB-b5KC~^)0pu-z;M$wJo8tn7TGY zP{o8r=y$xxk_HNPwCtn3daKaGH_)O=dSpLj0z-`CxC!J1Go3fC0mQt+2Re!vL(mmt z59IC&E=X7ONyto~k6SR&4z| zN8gt?$5apdH`9sgr&2X^+NSU9nVTn^!zLw|Ru4R=FLR}3*|Uk0%Frhr;Pize;0i4n z469tUpE!&3&Vt&*Q29xd)TyneQ||@Q_|Nf|!q7e3s~9nMO(A@K7LXcxD^cw^j=D{I z&-BWNue58?Pap_kSXu+8qP-#5E@FOK3xIAuySI1UI7jef zDEAOvmG_UpZV(RLcYreJyaWYr&R6(EajV0n=Sa~%=wF@$Vh5}2CvzVgzv~abfB5-O zazh}+eMx-~BKN&+4mb&YWj2W4G8s~C1=U013ZdgYU6dq*@qj9VdSnhd5JF}QF(iYC zf($liUTcMm;hq4rGRJG=*&)kkMg>7U)y zEQhA<-#jO*e1u9^vS&L7zJXE-WRS10TfA03fHqBF8C@f`=JB(u1~3OZ(+}>&StBvc z3A1)}IFERtU zXw09AGvr-AGL87Cn}M>8W_JDZ(>qKsINwt0j{$JYpakx%FQRiWP@4gYbDHrkL#hseEZGy z_JTFR`41RZt$00g+v+2qnvs{gaQ9bh*crXLAwM+mBlY<5Ej6pg4iRnKFHn(wwd?SW zMrD85MtPCY33I~wPHPOBKFjztyZjl6ctYhzOP|6j_)TZ-5d4crwZpX0aVkSE&pLY_1a>eiE9eneHowjk)1$xGT+H#Oj0 zQ-?zats>vYnTK0%D1A@vmh=S$PY*p5#I^@W2;@uHTaeo{7+VDh*0^mi@y8du#Hc&x ziH8FDM&wf`gj*X_52{d~auX7L{gXWu(){wC_a5#|b;^(X=o9z~yx@@k2Tre^>P@l( z6>snR-UA}E?wbK8NH?x@dljmwo^|kcMEY}pdj(u|&y7#)qvy{^q>&BgDb<@c_F*q( zE-Ty)?at^W1bp8#Kz$-ax&sdG5>)38x_txci&K{B-{QS$Z{*>Wh7i2-& zCbNO*Toe4I3h@uvWCy`E9N=SCS%8TXA{!Aav2>qf6*5XWaYrW zMe8%{4RMfc4o+vUHFZt6aiBJjM=Qn+&JZi0H^k|bIXawp08%uhr1y@LLs%@2aKwZl zSL2S>9YjrfIi_=jxWF%)fKi=4dUpAQEQ(Hbu&7w7kXJ7{{YT!pNh(T(NzzPWu z84DRbAh2(=kAL8EZ*u?VUT`C#N$(r2cQN-w?$zvl^L_IZ`aApE+p2T;D zlT!^7js2<{4HpfgMlxfWZ^f?|pVfV$?z3KXulg&))yQqymo9b!S4y-)F7!H)!#=bJ z91d9@A-JvxoVPFmXmDErxUGIX0SFi7Iy=K;}upM3>`Py|F?q#hLPp}>RAgTEG& z3oUQaKApSQuf*VvaloKI3n1Vn1mJi=aNI7S;9%e(;47{6eGxc!C8eb7@~OPChkn0& z_WJtJ;45af<6afPz;*=Syn=Afw)=f=Rg`)Ow_|??L;`Te+SedC5PH@Q4M|V=uWr3^ zNlple#|a)BuSm*;E{z0wjQs)MiFGytP7~W0zLy>A!D zQ)P8MUKcWbYeC@#(mUFR!&93EwVldbz`%gM zoPX&8Jlx0Vg0maUyhFnYrPBxcg52@{YXB}CWVbKthQAZYH%RgZ?giW0kCFzXWPqg& z&(?>Q2KP)4Y&t-l2Ha>sy9J>)z^xAOIDbeRB2ovB=U-e0Vsqj62G!&LZA`o_Okp5Q zK%^Zcb)@Tv-=512AcH`P6c0&8ekAaK;GSkJit8IhAuIYS zy%rvbLlPR6*fxgs!0qmjLnbyBc|>|ZhUW%{6_!d8pE|CPAgw4)B|%&pr?#*52Fn}V zi;RDt2p}<mRD&g6~{7spDH*+j}=kH$-k z-xq3+#T(K)0Lc#O>n}IvuKavEpYqH9BS9ONJ2rcS9!29zOz0S|O(a7i={P?FOq!%9 z3QDCIKA|jzT0!o<3lLu_S4;B#T>w>!OH0PJUgU=~0$SHgB9EsAPfltiY!u z1k9Z6OaRIphN%NKU8KG!gb7k6e{b#M(npA7pme`lvY<_b)X2pkQRmWlX^?XvdW!UKLF_ue zZ4iWp@Jzg2`WVTHG2?gokDzaH?|kG`;Lk{3m-K8xS~zJVO3K7STI_pzyh$T~l$c#o zKcfbm)#!tU=ZZgS9m1rE`_u%K2IA#dga+h?D`mea9A_ouZ?pWyUPNIJ~K`Y4f;kCi29zRqAe!kbr^0=s|*Padu~t&T=#NR<&nr z4r;R6>&15IEz9=@JO=yOM*k{oIhLjlll5EJMttFg=-_{6iPG3D9+Jve(P%ZhjL#d6*|xnVBec|NI&7Y=tyXsGZNIbs+O&PIBR6m*r-s@volouYX`@j_>|mJi4C8``MpbJkRTAwjHm_ zx6RuwXkU-p*<0VceLdcfw_0*pAIFO)e1)0+)s7GN3bOvQ{X-Es_U+rcE>%o)I8$Az z&efIG=kmRMI=i;yWjC)_HPSKrpZs=HQ!)~=j-K0eWJ9-2RcK5T=kwH@8%Us{kjAPZ zR9D`y8!oVds`P7h-M_fmv7kj;smiSGj1-QOU9i4fG+BO=Sq9ae25v1w4p|0iSi&#p z0vo!teMRPW?Yqq8@CgUOREWD<%<4vV?PUu;Et=wrv(xTK5c;^jP;(@BzF~`I^ycL` z{w`cZbmXk<$OiU^)s4D&>(X8=VaG8OEbqb1tVwL?gp11m3qQ?9aVEo25mKx(6A5Hn zON7t&H&KnkTcx*`cM+LA;?EQ{LC})^VYqlh47a!rW#uDFs&5`xjw|{o+~Mm5|!0 z)I8GOzGy9hAgW|#=rzd_y-?v)aobFDMdBsgze52A`%~|OBjJKTz7UQ-@;6}Fh|7TG zYYCyN(g25c@Cd9}Y}>M#iSG23Ei4@ar}_s`#!BaIi%l&hN;XM6{UWwcf)?76>ozTwi%l>xkV%=3$QU1{YGl)r^OSfN7_g_JRcwGBOb#mr zwjw6?(?I%x7UDG263ZO7*Rkk5!U>|cq3T_7^4diJWp`T><-mH*r1?u-IknqR@f7Uf zgD!P+wDuJj=Zec*y}8%WMJJ%uySDRy+NWD-vKeGq|5jl-!6KkwT)HzNfxV!Zk*}PU z6(aqO*dl?OmgrI*r5Wj<)>{73V>p)OgMZK0U5Vrcw^vFi)lLegG2iKI;*nM@OOJv3 z&@u!Vt-*ZvGNf9!y7xenbR&DY@=xH;#Ij4Jn3&<@2*|gk{j<*pySLYKR7A%KYV;7? zR?e1udkGjhL%8z{@R?~~u`@Pzs6EC$cL2ckJv)E_3Q!>Zc8isc0egUXQV7;3JeB&$ zJ<%HMDzobu{YZ+xyB$z|8$)7To<~)Koz6wsHsR>Q|R*(;fqU zK8^upfM2$qjdRI}of6Jh%P3Z20yK5#xsm!IL)R^XO;~L3v)ei=KNi8_e`0s zb(=C*%tFlZ27?}#CX!n3LATZD;Kn`2by_X%Wb+}wHMtJRl}NDtH| zL8@PDnfZ!0NtV#z{=h(%ZVz`5zpR`d zWXLH9%G8p}o@*i8T2+UA5O*V8$HG$60*GW#((Oe1$f^us_P$?V=O58Fe1WxOro~sF zo$FOuhRH&?+XtPg$hZofo?S0Q-1XznW%PU!>(}oCuFKE$zjLpJwXwI)S*6~e^()_< zUWj_aeVSU1L{H~+!+$$(HF35ycDZ+-d$7MYiZ6k~2EcaJyDZda==E4KpstF0^)8Ps z@mU*J)%?*tPlO@XFQGqeA5l*%TG8&bUOzbBOg}c>n{FHrx0|D5X)+7Ddsx>rI##5D z?ZPA&C`m8nCN5hiFI&eiQ^zhl$1gj_E+dbhagU#`kDuQkKH|D~m2`8dxSUzDE0`{$ zIJ;#_6P*KMF8EKwhGv36>|5?DlU!i-{1l%l!VYA~q(8+P%Han7eS@5C4K z6=qrS+Nj-7c%Z2};V+M3T^o;e|7Mz z?E_)?96oPlRS_Xqc|fdOsp#G=saqh~nL>5ll!MW(J&Imy^uV^taGxXiTRHo7lP^DPO#278 z^6y2SC#(k`Z08cDPO}S}dna#>7r>5@ULYWDJ{~cpP#ss&zeDVnnB%EYut;d*5M8dR zer6I6fPiDJ)h&pTGT~D0+YcT0QWV`^3F)E6;ZixIIzZ##V=qIbsFBrcVwDavIXvKe zd*x|cjmKuH2TbJtWf4aG+e4)BWp>{M-P#uq^@!aiDm9Yv>SqjyTykpDB8g zva#Nh@4N#!AVYkid-PTPI40OWe*+{zriM?AXnna({^<2vPA%P%bTZWH`lWvt+Q)Ir znf1|66>g{5^}<|3Ms`QGe;FDAL0YHv;)hbTF}1|3^#kah`E$JcykLDwU1=#w*z$A5 z!|?2Du1)p?$A{IQ1RzW1u|0(%vX_wCSJn1GCeXZuWrqL+ z8PLCQWv{V0zS@OuE7YPkd0{9Z?m5$Q}9==fPeHO zlm&{^p>Vt)TouLEv&zOYYqR=9t{Yjye+Ffn?m~A5xeuHs5T@W>VrQacqHp4G;e290EP=1v!?th$ zi4K@&El1bk*oLUIX~G}860SwPALxbwSh9a%Hh%Q>!{0G6Bmyl2C&fb^c`(Am&fzu0 zb_wkiHajzWC*h4|*<-wPqc-Q4(#xljcA*EGm!4&u&75sAI9aP*sb6KPBRejJa?c9K zoCvT)jpnHJWv3PM!pS{uLw@1;{fp%P@y}Aat3(2KNQ7BPIWjoWlLku=Fvdt1H%)v$ zB69@Skh>vTLpy{Y!!%}Mk%xYscg~P{H0RpLl`Z;n?(qS$Ed;;+ zs`J779hQ@qYcTWm=cESitE7?o-wMC~%cmoylFmSmm~YqE3mdF^oeSl`*aKLGR zYhZPtwm_guXp3Oyj}Ms-nSYU_ngnpM!~&0^@Q1qT1A%)$m#{9?F3GJ(EBX=-aHl+% z>w&bE_Zfs6$d9zHpOP`Q(_HB|{|Csr_|z1z(mL>woS|M@bjm!~LTtwxA46a+wCoE> zI{>;q9qO4F2?@7AOCRA<5sNcbHo5wk{Junf_!QdCAoB;cb>Z%V4@u}9$O|hv?j`dA zYI!(4EM5tm8}eW~sysYh|6qdP>S0H1AoT_`XTpw`&6)cfR`a0bgaOor1hTtymJB{2 z^jhi?gi29(4w=r-Iz6DyyW@iRXCvFV#!{CM1)cXeiKgTTKeCrB9%=3(;auzvOe`ew z@as4^iSS2cqWlj(`=Cc7&t%P>Qyn(TKD+jyZXn_j?b6R7Gf4Njvs)OC+z(*q zIFSKSW>8_47^oxD#Q@XXmVOt((8K8GZr+H~ADXTO4eCM2Y5`!UoxuKG(1z zA?b_g6N!6P7d97vZ)n0Awzar3k-Pj|fiA7!b#^!oW8b3NZ=g9ti$7es^wKDoyl%Gn zCd$%-WRv!4pZEi?<<#_8v0(!2w*{wk@wRdA5kTB#)0F8;a-SYDsYn~rt1hIMSt?J2 z*SIaH7J@8Lm}$_(Bti5b9@;m$+iP!N0%+c>09~**#^RxVMP#6c4d^Cd)mkfTeK%lV zJq;Xf*GUg>pOQCbSLs2V^HHymBpCmNhaYp2#BMbP&Mhbe4k*My8`BJsaq!`sDJ(zh zOEMEOL6G<90grp{5F!5PHlF*U0VaR{CqS@yIM)YYM0_vl;Ex~Q9{d~dk6u9cFZ^Ju zZy)|0_!r>+xc%k=P2{3SybZ(XhVXJxa)@vEgD}#pgBBnVUb`~QO=hhL^;ISc`(y(V z^I#?K)42&=$eGBPd@ns{^k{b0Z+^CFJ6^qny-ky#*70$xLXt zwFz+NT?52GfqM;!c~6l;WRJ=FODY=xDM2i&i1$&5doT$8V7eOMOZuppG&TL3N&I-7@YxH=;tVve4C~DlyiQI%e)__9 zqY^P`K#fP^buFQEAHF$+4TmMNF!JV5&uRIO<&hJ=87+wll7z+ee0nqk3Q{%&BS7BY za~bJDupHzPVtU;Py&+~#9u{!cj!Hqg1Q5}KQxq73ijY_4VHmS$*d?jNjuTbdHp(<~ zOuL@gXX-4SvwD^<8c7z+7<)rGkz6XJ4;o1(R^cVeHIQABrgLTis^n%}fe!=brE%l+ zZeH1UPLJcYt{18CmvWZR_G6Sa%u;#lvt~+rP|Lo!ZQ0IM<}Pgiw0}zA-4zRhC6~WN z<1dqGQ(9T@;)O5kbTsIg*LygdaKVd#V5&#(rR;h$r*vHR#{|FdkNS1TIZ!x*Y4l<0 z3wZji1HbeQ<;&q+BJ;>SgxcarcED^iKdrNUBB?!9=VMnE)c#Ad<9vza{M#meLYx$XdIA|^jQ;@P&1xF?2SchSat0MaET5zx~Who(SIpwEXzuG4(yT+koCmHjNumqm^w)+Ggbz0*NTVO^<&DD8AS z4=yI}ZS%`$jZtj;b#n}aF!R_P;wKGG*z14rZ9*`AuF-4cTAfAlvPh zS6xq=@9sduz11pD*3-i-87|h})rm`=oQ4W(Y`suSbGlOkI>)`9I*wjghc}uPqF|=} zU}_8DDR!$Z@uW8B4dpq>*0`Ei59uijLT~w|(4gw|5ni-j-Q4S^(y#!&&7eP!2E=Yc zj_xNyJzjM8_F!(~m!H8K3bSZg-Fx8Mii( z&}srC^@4LlNikcbDq)Bn6$-2FDW;utL!+_8qT$N~Tu-X9&X^Oll^i}_3&`35m`izl zy-?RBu!;oOybyZX$7+3cfesNhy1TO19GE%U{oBXFU^CD*>b>msUn81t|j437%n3#wU0_-#@`?oqpMHJ=b z(5TQykF7vdn^eM}e~$@%uDYXw(4N$KiF3X-<^p_niAM^&t|@KJ{Xc-|>=u?NmOxC7 z37Zs6C^Fu+xT;ZDQir0OO)HNZ8s0om6ZrkPqFHOhNkHrUYSmQf`+z`6btv^Z`C+M! zD*8=)8-q7j&b zS6$Y{p+MMTd>EpWQUy;n-=C$64zuMz{ATdD+I*Er^^NoM)Vs)WnfJBT^0i#;=2IZ# z!0HyJlJIN@siX7xY;<`PI^B)&c{y*bb+o{ZNoa;9odoh}`?EEAJt|Rh2h9Ez^AZCV zs!xc6C#PIXR~BbVvmijuETI*)&SIy0X%W1Qo$51!ancHWZDO8p%ol zJ17CjQTN{AE~g^n{ZAk@S`qnuI2MAgq3r2+WEn(X*6C*0Ai=Z$WcBrYojD`VrIvNF zlWGQi{D^9AGg&pr#a4qR7IYw=*#Qt6R0}rOT1gK%W>8*Yl7uagMRX=B%%cbomo(%r zO*GLL1SmwUPy<>An;WO|7cPRpZI(j;4el1ET-a9@>BCMA_N!Pb5FBZPlKJk2vano~ zvzadh?Ru*!kwv$hWvz^N?NW?v>lXvnEXU)iaMwYKQ&3~Z5P%AP+A9{jRIlAD9z%KI zm*>6{ZdycEsL;4Is;4UgnCQXZ*?_m8*j7b(tMn$J8J&WS)JS}%!)|A`U%w3r>W{y& zFLHKJtsshfA~^|MvhJNt9ib4dSV}deYu+|_F%$ALHHy0a7wx>nEm42ERwk%|MyNA* z^Hh}e;*n5G{y`MNt&9nM7TG2;PYoX8ASV9Xruj8AyF*UX;qRcEh<2I1Tx|_^bFi;# z@y8RNh!l_vQouKpy3%rT4^B=g#TifpBDr{IrUq^y6KyuOr=G#%Xdm^Lz=+T1E>%l0 z_D#3P&gm(0^w;C)Zv3X{v!$f8w%qfc0Z3TPwMXgqt9wQ!mK)pqYKc6TmQaWNwJka~ z!f|l#rmi4DnDCegmAIZbT7yFb>ji`b$M(=3O<@EHDJUzS8rvCVk+4N15f)-I;25B* zhJ~_Qe0T*PjhVyUP3aFue|f(Bm#$*d8#*B&9PY*;Y#Dp7Iq_-SKx&~~B)CuI5L&HB zr*hu#w$nh)pHSjG!LU4Jy#ePs8+MJA5ZB^0Nr(Dvxs8Eef_Cjudty$HTE0?YRvS+iVZXP<+FkYhglCbtDG--SzCgAq0NR;J97-meL*M)OtDup&J zU)4n{S*kvLFbA$1(5KB$mAYraSqqThozhpNpXj84?`UU(0(NekQ6Oypnh#ONW(>wY2x#N}e^h8@U&bb88Yt25N5k5uqj1y{R}{jTw@2L#?e?&sGIVr9)+bhgZ)d%_Mx**5KZitgL zZXv5HOs;r8oBwt5lg;Lgm(7+BzO*tcNL$mnIY3HE$?IOD9VZ!U*bryL7KQ5R zRykc$;ks-rR~9+1FTOYl*CnlzNC~^>rcu(il;jEQo;Gi*7&>pAI3H$Z$0#ypw@*?i zcrF+KZF*EqC3+|#0yjBHKN}IJHE1Ky57(qWY1@lqLD@i@>4&!{-+DFj)1)F&Gm4&q zEuq|ZviGN_=DzPwea!BvrF!Q8G@Y6_ig$*(>RgKAv6Y5y3Cql?)A2#dvvASjTLkIh z2^uqTVB~P>WXz;UlM9v@l-^r=kEg1DdyZ#Y-MeGclyvOTS_Oj4Wg494n8fK<45sNMKVgr9; zHAU^0BNW2Srw5qk!cv2&bTh>SN0J%|%2gfl@fP@?YDI85%1?x+7;lOkIxGq5t1nhFmjqBx) z=uPZ`(pz_ji%+MEU5}!d(d}HZ);UOH)OYF+{-FYYg!)&Uwa4Qncm7t$?CRtduiaiW!2{E1*Y zBs3WHc~hh!WBc|UOyqi0sbG>W3osVxBoc`X_L(vi3dsKsX~GOx9xANKHGgOD#(u`7 z>=l%AKu8In6&J6ZVBos>a-?}kv4SDgDD><0Fm%1jUdLBEtvC)ZbvlFVx<7YNwZ~$MO*lkho zpX^`Lcy1IXFMD!!Knch?N%&Tf01 zTdFNU2XDchw-^Z@FYbmWo95`ekTB#>=KPNN$rf7?TO0;fv(a05lc|$Be^{Qk3wyq~ z{5)ZsHsC18|D{aQUklC|ZUP-$do0hN@nq>N688|Y3;cI=`&Cb*|N zvqUy(XJuE2wh4pXEMjh8L{3@?tR~Fky1cJ$DXXXh#iz2^82Lqctz_;q(Mf5hs4f|_ zv!nijMH~D<+))E9gD!{x^>5W7nNU^yexac<`Go9%&n6mnMcP0df89EMuv--)LgR_& zsF3MeR^1R}kf-(OxC31_dl@_T(}~VEU{z z^Yk!i0$h0$)qmJ4sz>PCj0&f*dHYk3TAY3HVfwezPWpSN!@U+(Ei0v_zq01fl?r2* zoA~BZ#l%%i=7p`-cStP9s}aSimcoq58V;>&$q>9xJBE;H$ywNtc?-!TWI37zzrM1l zP6~-gN_*D_1UynZ19TC(g+T!WX*C7@h7j;ClPKcDgl;xkZuOy^FWaaY!(ZO1*P4LME{}gD`3Ca)&LHLbG#RpGeP$|Fn9ijoW+O*U%?v|liir7Y z&TI=>wweqs8V!ffWn&cs3)<3gaf^)6YA0HlP9o!b+U47ktNeLn`RK6MG@x zXaxB$@G%nkaZk?#cq(kKu|WFWp?|;4n(j5{SsPCrKP2wUB_!~#4r#*sqiDO7hx<~I ztwKq2vxR@OkT=ALKUm$s)ow$N*3!Ex^zIMU4IF%a3cR9pIhTi~jr=TV5s#xs=`|^k z;Z2(;LgEYtV~Uy56F`TLg4sCF(yDcC3EZEW$yPmajrS{B6aaBe%FZ6`6P`DA$?B|V zdFuDG2kv848fMOzPp7wBJ$t3P+qknw-N^9O`XM$4R%I2*&MCoYB8&;v!3qWJtY=hV zJ;KiyQ6M#FB!VQNWQ>qXEE9+n$kJrM!s)vMtb|4LAo9XNgFyP0wGhSZh2(xZfB#H zD)!VUvoX^%5TG2NU<7?#Q6o3HS^f!NX0h?WlHw9R&2OI?C~VPE!oGM_ zy5>;Xbg~wV$Siztl4&-3Zv=B1&`DvnN^F70WRo|~VkCvZVYX6M%u*;=FHPYQm(j$? zApJ`}8ABMtkyD0-&g60wQ;+Qm-{JD4Vl0kt*7@+xbPIEJ`84}oJ5}-GD&cV5sGZE> zJTS=SlFRL}*3zK`_U!FyGHnxRBkE+C3wMkOdT6yA+8SGx#b7M@lu8N=C5je5OH*M8 z7fs!5I#pKcHZ_mzz7;7pj1VNX4=KTYDt8D@NKfDtlRnIKk0R zNdtipVwq(-DzKq036|*PDj5iZV~$0i4wAC}DAB-!{e+i0ocCsPObMd+pfsTbR+J@& zsHEr?WH_W9#wym0sEG4Ir75in2dt0sn;lc}ZyUNw^si0}$D`hWj+_&mlGd(~u*+c- ziXu`)b@(loy}u2oCkM36x`Q_6>ctQl^55ZL>Uvyj(>8e<Sq#A&k~jD z_LCeV;8WW61ax$C7*tKJQ6D&LQ()$yArmA_R4 z5^OsjuO*=;c_TA|x}EYli3T$2dJ|+^WMp;;AU0|a5W9oRySBx|3FMWLN$(floC=O@ zI5x?6MH#A>BvK*nb^gXpqxKd&t#1L-W^vuT{Q%283wXetw^OveS;>awV2zd~Cq3oa zAOH4xFSw2|40Rhu-dCAkSRHOgIE*i~NM>!wh9@ELqpdAI9;BH4He|$&=K9#6r?WV2 z{2S4VN_;}L&Dd_YlV@DxJtsO_)|lLO&W~Z4f8je3NG?1x4=ywj&;2joaS;w+vNI!Q z98kxS1_>JC&kf@vVHUL-uIaetHph97mXGp{NA`(cNOPy0l~2e|hGvR+bWSg%+t1!v z38RMnLzDR!gzKeaXpKAjgporn4WTq2K;5l1QA19BGy10CHf1j?& z3$C3?pETHSROos zUFeDiY*{ZVxNxX#(^4jA=xK}t4=eMP04G;9ySPE;j>?7I|n zM54#8*Hk1FB6T@C36}&TB8=-7bX2Ty>9&C$(O~VrWty-}f`MtjfgZsDM*hO;v zjO;7WG*y=+F6}Tic8QJ@qoBoicrhR7&wXNBUdfH-qj3Fiw7msX9NV@v9DxuB9-IV+ z;L^0vXmE$%4#8a;*O1^Ig1fr}hhV`&aCZpq?*4a@bMHIny)nN3jqm0g45*fBX3yGd zubQ>iuC-#DNR~1H6)8yAv!0y$r)VlF6&CA+mk*-9z(28o5$B1*V7RV_=9Z6k~Q8)nmts- z2$9!1kayO5v*xwh`i4HWI@K|NB+`(fn(d)<$PWW>ZjYfbkx*vKH`Ji1EjvVh`-7U3 zTNK+tO(Rjqr&4z5bmaq%pQq^89#v1t+#Ng=BhU-zAD+@74eJDEX z5LF;ObwM!>Ev|*KW|((j`>%(NLbMIeOQ#h1N;J0GQo{6&OX`5$lCoVJZx->QQtWA| z@~_DmN~B*>*lK=V%}vtSMh`<*JcyzfEblaC+NL$~n|8{Iua+`~MqMJH!eDb&jq{0? z-_M*I`5TEFGZGCRz&V$P9M&Dr z^~~=wuBLMNiiJaKo!Q zU4dV>r{+tiBl3-oj}MHV(PGowXrXy5loKM7Fh>&gxZHT19X5Voz&11G|0->ST z!EdA2n$Gl&RG!w9|JDm+E_d{wahS?TG+p-q@>B@k7dIYNSeirljt)l_81EE3I;$~0 zurFwqS==A6B$Y3RQVVB}v1ZIUs}^nZGSf?;toOF*l>^ zlF{(G=7Gl!{~UiI6#)Xf!&^?t_!+_cuIC|LHbFxKzE=AVM&EYXxhyTzC9SnT$&GsGEti?R!kI68BMzwK zagJZ{Kb8HgGKIe*N<+&n`h})hVd#Nj{834(yF^pDnu`pys2vAch@T8q4cW&~K@A^f!DaOj&Qu&5wA+w9a zARKdVqC~S^nr?s|zg3I9s-m+=?NA<8^B{(^{P69wPL}NI@a3o*4#!so^yu24s!+%3 z9%9<&B_}KMJHt?H#~{*ys_%yeWzh~OW9G66yS^ccK92qwJhHn`>Lf1Dn+&aQUoKRb z+oEaPZszD7m+c~X?9?B)e^6h+Z(Fj_4YH}XDYa>)Wq4HfnaoY-KK@R$kwdM{*D^AU z{b8)UoV{z~x45@y@2LR%qG*0Tui1N%eFfz9F@+m56i&XRG3q)pA2#!RSerHwQu-lD zjuJ_(@hY=ORE+Ay@H72mVTuyE38AF;JhQx%?o>K$ybXHF&JZQ@CAwz|soz5(qG4oIHB-gutxsjBL%%Xtk>t zPFb{I#e+xquXlTs3yX!F zDvm~#AY#2)Zd{=hTB3WtnL6kTZ|YViulCa8f;B}Q@*r|eg61t)o}(4nQZ+leZA(ax zVUmUPOX@?F>4T9M)^EQDVh3vXCq?uzboZQr+R-eYwuK3(v6t?7nHy+~@W||Tzi6&= z+(B7S>nvkXuASJe>zQw~7(AQ)aq0#xm@8=Y~o7 zjySEO3676CQ4x!EYs@send_WH?<#12fh|A2ba+-u!sULu#*fkTVU6X?!M=R(*}Ads zgBv`V(gP)AN~X3X1tvXXV^JO7!GYl!IwO=_Msnl8H%3@vPp^{rjqXm6FhD6T22JbW za%4|rF>TqHvgez^GzFiyBbi@YD1Voq8#R-TlT`xccIs1i@3IpYO}$mV5sv`U2hs?e zkR`P^k>V8Mwq+k4-?Yn1@tvgCjzwYwQH~xfX)W$%(_8wwSPahPKaK1*oX+8*xe>ew6;}glYzPIJvH((NMGPu@0gtOx6CU@QB^!u+9>8`I zz>@^|^qGA;+iCanLz_OfeA-L8_4dh^%>6foOIICf5@YVeiPz$4g~c1{OeRTFAMWR# zzcyZaN~IY(6*}L(5>y|@UE}AJP2C_VO|gP{J40L10R7@^#GHuWNY2G6VnuIj+^UJH<9#H+3!lG#wF@EiK6(2p z^9O`TmEM)U{gJJ4ZAq2j6LL%VrI*@C6)5XyLT7A>D)!Y5O8!|_sG~E;5EU|x_0($%+q2em$mSi_utVxM@ zA03g$=XK(1ZYOeHF ze3Fhak=;&SdwR(!xKM;tgHWw0NeXn*oL*j{;VIKEU_G{ab!5BS}Weo>TkI@3ehm`}8yO`_H* zQ@R=(eRek%=KCy}Y{~UH<85wtXqZ_CL3($+R0W#92>`_$K>LkKP=pz!u%JjG4m*9D z=o1&{$cf2Z5WiphhxOzlTN9;^OM{Z+eBFt`_i1h}yy%1WIqg>GFC+_#WIDMH^;=gq z*IBFE@)OYw>?SJH{l0s>N#$rx8eB#y2(g3R<^i=FUks#2QV5y7IQy*$zmI8>KWlsg zhWIpkgw5`+9-g_aOEI2`uoEa97fo`uIo6*)gx0Ly$RFs9ru)cv)&OxQV zcBaC)pr=vj6#XFVNah?xyBCG{lU!R}mlWI8Oz?`^_KZw(hHmH@m=U?`^WHcnTFq38EFE4BJ`K&fD}Yx;s=vCmp; zLp{4D!9#SUS|hinE?6v&<_=TLZJhtfo(X}KEiM{`J_f`Rt32Us%tO-)U#p0B6=>c-XHhMtQvCAkq3==lon$>!SQ6*4@O0(~ zN!-VLyoxC^01>KDZqKfxbA!0JRf@Q{>+Vz{6}9)&o}-C1RbAKCsr#Dav8q3%I8h>k z>-9rU^cVIwO<6cKT;0l+Jg?6_c!!yDXS}+4I<+8HhviGwZh3W zat5LhZ82hA-D&595qqmU;u=p$-o^_R&c@@j{NM!e(ZW*`0ktjvh8j%Rq^?4-CxfLv zvIdg+E7i3gPOA!G#i=jv%8xKSY`L^$354pGed8S7}1>FdllTF4ltwE=Y>4 zj$pHa=7Eeg(BT4mAZh6Zh%}|vPG&2@^h@SiIfn>+zwn%#)LB*6^7My~w`sw*l?<7g z)Y#X^`1NAB=O*^5wqJq`m}{s5g_y;}$P+x68ZAGK`LP~3o~%#!;Tn8;FPkwJcvwZZ znZ6;HAcZy{ODL@j@)$Z92&bnJ!apXU=M+;7;dc1ixY**U;wCsr;8;8w95nRSj?T&> zEd7)C@FvcuDaOaP?~^y;gq5BdRtU!Q(Nt(F$BX-87f&!m$_(5%hHxvgw~$sesgomZ zX#EgcOFPInJ|0-jEF2+e?YNXnxKK)6OK!brDpJls5p_8WGc#in-@!Jc5Bjvy)Hj%~ zFYan3x1L=8#IAcQyl|YD%-6hZB&EsLlO>P?7V!V3GAEzDCEcIQV3^dfSVBHq(1YB^b_3qpc znDAci1~1|MdgyfNBLdTdt0V-b_o59^Y1CpLU1>}R!z3J#)sfWf1m7#at=h~)D8|eOx{z~Jp4VZoz)Qj?XOm(@oBhuHhofquAB+Y-;0KxZ2XvxO$&Xq zaRuN{oMf$k#h;j-%oTrML+iab*}F%Mtp1M5$vPCT>Sz-=AvS!>B=C5_L*uRcO9w02KR6ghQQO%k;DJ1-_^<{+|rX`XTz^{qI!Ez7|&m=S*_e> zPv6?U$0O_T(9G;pErH#~r6D$)lM?lj8pQEPykFaem{w{rV9=N_p4Wn=hl19_l)0VE zPD>>Q8$6Lh9ZgpSp95L;3x~Al!*Y4~^|?|GiIR)Pnwj{IuMVqZEXSTM}S_;w_4AYqp!_l}3j^;?RrAXVa?RS*fEbQm%w!c#kxBVgSV_#LW+ zTWbR*23(1ZK8q1!r#d?D#xs359z z7tP2BBuLZHrVov`E7YE6k#o*YUq_#d;aoKN^lD z1z{hD)xWJp_#$$MYowvOjIq_zh-Ab!FXpBfgkfoXb_&KRI$`F$qbTGM-l-$+W$hqiHwu@>rvsY7Jht0veM z-#`92Kk_`yDW2LQ*Td&oXZ2fD_aC#IoyqHFjMoL9_R|;gV;!%o?63^mxKRqJRfK3r z)p(tyT@|!nM&K10Clx2u8;pq#P0?J&Oi&xQiV%+qk_V@(a)op4g7ouN2&XT>Q*ni* z;{_teR^1n|J5$guWt-~F(#$8@b1T{1E+K_tDf}#ls(WFBN&^$Xa1h$b9Ow$sRaf=; z*|}e6MTSB6o~!0f>9tVaDnLdS*LaC_=KZ+F4mpKO*Ac~~->%jVSHziTB5tvUtrg2C zt8z0`bqEWn9~)cNaj=GNar3C|pG=U(dp`M&Rx%3VBRe*}sC&DGy8l@Al3hQmUKE~M zKlx#s=lJrF^<==vvz6C0;&D&F_xSqY+!-cL)P5HS#JilD_%j$}r9^j1SG!Z7+oebf7sRKWZVfFsmGYg_dybnJQ(q_fT=|Gj ztkn#wVt2{?8s3IpznN(FVPU9xp7SFjCz(k#DmO)#k!k73thG)_U4Uwl2&|?;axT?2 z=hVc%B2!^nX24Rk_GLeA>Hv<5O8FhSijlNV&uR0rSy-+(q#jA;@jR}fs9PPshNUny~?7VR%3H}!G)S)k=^MmboW*{ZNzl1&8btn5at zN@A_Ad=EncgUyHS&(@d)85fP4BNLalTm;?} z=QNz(^wJ`)2Z|3|ZmXMIqGCFamFi_{B=UaMFLkRf7!$~mxDoHQqh^*e@8l^n2oQa+9KO1VcM>qjI}aCVu*iZmU=Zi5)i)NqUZM}tiAL0u8TxN<9_ zk6-k3fmHT)sD5&U5)IV}#FW`yF`HpAQ6f;b&_ZF`{<%-7SHVZwTTl1rAk3 zi$tn`+Knp8j&^Mg1)_2A=I1MVAIIVtY4T!@^YX#X!Ue&kuWqbRUd#bzdo#4?Etj|G zQtr*fdn`sbC!%Z>uZ8J$U#>2}c)V$2-{n5SCbWFp*od{Cg})Fmt%*We|%VYu3n{kVd*35o|o0i z-Rq8Z}M+fDP<;N_E*jaPz7yc+*~pi?=mrZO zDV`c)xh8aS1J0*eL}f1Hb}xP~OepU;uj(J|%N8NTv)%3nSY2GqhD_AleLf8@=Ci)F z+}_vlx@~7MY29vd-aY-ausFkaFcq@HO@h`K#kV#S6uys9e9uneG+t?WZ**;cT{H7) zrfUW%e8ZKBUH0rsY;T-uo_Z~CB{piyy9>8@c4I}y9ik2WP!jH56e}KigXBB@jOTwA zf&5!ZM1CtPYkL^Hd?PnE8H<3moq?ep{0Zx?A1yK#cu_@rG8PkNFu;k8-O1VlM&A2mPGBAl5%X3TkU* z;9_gCew%Ej2o$iUH2&*0x!a0>*2eordkZz#wgpk<}2 z%MNfgbkTQZ{V!kp>ktC}HCDf9!Oi189xgUJJzaZSJ8NAh@IRde5b&E9JI609;2+>+ zt?LYgSz8;}!mR&&1b@+D`$Y@z+Y*6*KS0aG0A^_owR16qK!E?iVu${w1$Rn6Erab3 z&|#CSI9S6AeE-c6vj5f=Z1D5RPpm&7 zFHH1Ibq!z!E)M2w|K*&1PA$N1dBOJEaj^dhd7)=z4z>lGo3NS!o&UiL@Rz*!jRp7< z`T}5QZf9p?Yh~wb{m(Nb;J3a2v4VddL%^Sq7-k?_JwrPSfSD!m9})xbTVk-m?H-Qw zCnN?$A7bEY>7Z|DZ(#M$Bluflu(PuNbQpj?p)qWnAbNTr2WK-|m%lke02nT#e`^dj zD7+T^AMk=4Myyu)Hu`!ljuw9t7(ao2yLG^?6F%TiNDTPJ6^PxyLSNSw@;9ph0?F9^ zk{ED9{Am?`LSg_+O<8rVL3&Ouj{gnoFNyK%gwOUT6b8r@stYwXv$Zh?{7udLr1h7? zfI!ID{)E2JGqC~LI_jCSgDn2$E#O1-mz!XNpCjRy#D79wm^e8@psYrgPWr(Ax-*>;`gy*`xEj4Vq$F!GiPTtF*N&|vBUlC zZ*>7+Xa8w{Y=1&sK!NN)T|-xhJ%_pDznukKcmAa<*dS#8J6!<}Y6!x6T^L%ygC##( zXvmYXNE;fM>i+J1@iSfmhIglcKmk9)A8&N+;5(9mfxmZ@H-uR`*y$U>enmcno$W;x z>~-x8e?1e0e-(~jyTJn^zuIa1dRYYiGW7SSMc94@T7Dw_+~bX%wZ4MkuZRuH8zB*R zY{k$S9-$Gp)HOB~_-iZp*H#?dahEeo{Y z@Ap|vqbJ7DgZqjg^#XTefRy5v^z7tR=lh?_!-~HKgxEHdH*EOFywV!;mdeOR$Np|!1;iKm})Qw*DlK7NZ2N-T> z%TAB??eBaYuTIZNy>4I^5{D127AE$+Zm|}+HY?#N-YGuM8_B2d?ypX-7rL4|2hmOF z27pi&U>RvPNLU^ZJ3zGa4NhOG3K93`CyGGJ^zPet=%zWyp9iDtD)TPsOw;q7vrx2l z$MA_5MSJ7s8r8W`XsToio=Fc-oknnb%F`n0JrsV9pAaA_(^D6-EfLs1AM~nKNRJt- zupT!Ms%P&+?>y?0uzbAIe7m=)QE)1fal*Cuw2DG4^hSas!Q|kqtp;2HcoBVXtIb0L zemcAv%UOCJ&Dm4e4>TNO!+s-+i`!Q)0DZ|6V1=3>jnOs~tl62ScKSIYO>p9-=t^Px z<3n7&Qc3FkM)VLJ>Iy9I8RO1lZo>2>)7h_1cG8EpT&4~WV>OXNnI`x@sW^Sw#==a@ zxEu+^cvmDgUMvQMG02rYS1F6h6p%JcRyv6b(`KLDImP2qqxOCGA<4#*;wr}(p2M;U z^yJybycJgPoD(MPu3<=!KCwq(3|bq%&6ytMEHsUv$oWbS9%k(L^ zR1l?;nkW@!BE+@J`;Wa;)6%;>y?;AnVAY33&4++nZ=N*)k!A#>1|-fvO8&- zP8U4|%dgK-Sw~jJ(8apkdenxBTt%(81nAiNj2HyFg7clSUn1Afn>!e;Kv4pAV}w6H zm*!H(ovAw4<3mK8gJCHmOIu86=VGm3(LzidIRxKOGqC0268lJ4J;FuvQR*Om*(g#a zGtAk6l#y-Lb|REJoE(y}fs5{mUP&6JY)J>>HZ|$s!Jp#- z`vpGl`-0yg&^nf9;yLo9<6gb(BWzO^wZND7p}YQ@B~2EK`0=EIh)nwYexREb-oo3C z&IPK7SV|q+qDPo3(jdNVPB9clwz>HN!(m@*)xFK#%=-Lh10*O!xLjKo6=7AVLnW)U zH|>?Znhf&1k?Fp$5oWsUBdIsE-|52IN(-?fLNpY5vZDPSnMJ>F87wmN6LNVYjct$A zN5mG9gcrb0;&$tjP2N5d^yRo#BH}AavgR)CgnJTmR9K0Fu^Q6*cgW#}WY5XxO!ffw zYK>r(X!0t`m59b?h+mlUq+gn!f?j(vt*4nN>pUh5AI|(TJT~!QF_ol#_&m0h5a@HL zFfKuGXMdSF;wWbi$~i*-3Um;Y^Bjm`HlK@r=sXjB9=aL+R(j9sW+=@Y#AcTC8lhtV zeTBn2AR!f(Rudh0hdTi~fq<}9YI_#J+E)GT+BBU!_b!jCsO(3ZXClHCF_^XX(Xg?u zX>RO}k+8mO@{sVleEnh<%2z<|zOOMd-w_t-s<}oi78^0ScYe$%7a%-ux8I$Sh$hpJ znp*X5(7`;e21PgOU{@ei?0h6zbt9;JS_qJQJExaBH$xdW*wZ)1$0o@|8mos_@A~=J z_K4-FaB;<|jD;O;P{vemBs1q)fr&EyAZ>bnQrJ7Kk&SkkL4(EdCt8EbqRFW}=o=Kg zcEu-s(k^7tQL_$uJV8RNtUa%?7?8%TFUap-$PbDx6!bg4>6DvuppAY%o8XjD(2a`8;iU0VXCHovtCXnD+)G0{YKRIqHkzwb2PJEb zjiI&rI{F$)M1_k{+zhdqr}Eo)!t6`qS1`&`t^TGj$}+`Qc4vKKpJKnJnd0Hba-s_( zEL{hN!I(d5-F+xW3ITjj>mvmw&NLKUqW3BNd>GbTc=^2BjGpQ`HY{<91tQNm^(jLw zTzCz~OvTOeEAj{y3?Y`3(VFWs10~wL@Ub=HS~+;m9pf+{lv{D=}C?% z$@!fo$j8J=QUG`K!_0QdEmGgO>ri4Mdg`W>K97naE70!wp4ni^o{GHRw+lZ({a8_V z_wD0}V`iZ2`_*q=Drlq}{fs739R0l-b%?Ij8ZAYo2jcuk%AdD^p%2{FIN2soNw6(s z($3Uj8m*H^F{(IyPgb6OMjCZ1Hp09roaZe*4i0-_@AMSO=k95+Xw||oS1%Y?*GPhcI;zbTfR6MqWv^QlQ+AX>lig7S9c@mj_9T|vRUVHvOm5*S==59Q zL{zyOeK%#5-|l3At+Hanj6{|$Plmej%RMqGAvbR~tQ~Z$DCg=6VVOq?Rw;m788tca zJHylOMuqJSxVvI4^ly*GAEN9R3!XVYc|U5;C*f0Au_!><61cS&Ehd+ZwOsUgMX1%z zakmmspDqJd{Si3vXjhB(Kt1#Q=fxJMgqXg{=v*On5$Out`|m@D0mc;?11ZJnv?=U; zgW(VO50fUIjz`D~u*`e3afY{3g>QL4te zanPRhS*w#oG4OkId@vq?T(&8SR})UUjift0gmPUnvTCvl=mR){Z6y;NwXPT zH8EctB-)=5?DYA78`V>;GdN&?Cg!SP{P+amq~KptawPT143lG@$@(?hP+0{(K`{}e$H4@TAKc}&$7%D z^}uzn84nxlw3UjB9|A3*MZ5x@faq3O%!oSaY&x_Y7y2j*&p5#YWq%hIq`*H=+8w>r zUThzgSM!B|A6z@N8A%{q{6Y-h&OPhYP<#7v;1s)}pq`^cg}p!nH$1gVRqy{vtyRw zIos9TNp^lMs@an^~9yh7|3Q>a8{7vpOv9spA#ay;({6Fx<)NLrkd2tsy#44ZaQ2`zxAkEDKH?d%Y0lJo3-L9Y%>=o09{({c z*wb*YIqsMV!9)jk-%$dI)ae+E>UD!KbM&euxf(gK3nF{P=Acy&6>CnJgbir zsx-tX7lIT{TTty2ZqT_`d517xxa!udXo(6oSo^_)q8EqT2fk;TLOz$taTwo+*UE$p zS!lVEcq>v}w<|A|UITkHt{>^7wkr}BAV_@C+npa0&8oQk2#wN=iaTc&an4Zbt3cQ1 zR;3Ij1dk)>GWME#q%mK-)_TDW%MV6V_WKete>KfXw6#P=bkyRl@q9#E18}X3k96`z zdO@Co^RR7IV)rWY>d9E9f<&T?~OL z)0r5e<$BkTLjgzzNI@wk)qu_VC{B|z_{K-RFMx;0+yU(-_VtDyZ8m4uED1sxxb#$$ z#AKjrO(Z2aSlgu9yH*kZog2mor$&6`r>;f7i)rtBGla~kW~l#_Tm9lX6FbS5wHJ#&4qkp%Bjp9qc^u{z;#*T9X<7M*A%Ud|;y8?-^l%xYaY2 z`TYE&>KgXrgbRLoyu2wbe}$ONA69TLTCg0>zsjzn(R*_$7K@K(!6y0LPCb@A2?+-% zU^}2Bwkd;4I;op{n9uE$Sv`F{cp6bnl?uQVXKq29~T13g%W1RF6@ z^seyIQIG0rXV7UubA7g^CrlDt=-;Rhqw~Givl`^F?2j6+$tX4-8`Y%0{P0GLsC6;2 zmTOC@`^jO0=Jj_BHPef=X?C@IMXe-5*G1c=4J~2v2`RaJHws3dve;)G(HStQBbAS4 zPlWd0_$E1-s<$X>G0!^byzegP@vY~Lgi76vM*F>l$*rrW+bzbu)KXx1QC`S9?uzC6 zac~Jxok{7owzEZ-jVr3Y+~;afNEuR+5;K2snzpuSx{4HM2b(ioJuIJ}yHJ5*ShqxD z@P}l6U2?+{E1v9U}A%pYnO1GC|)ueA_LpZYwjgoNy!qJU#7r zIz8sQtwvE9j5Be~Xkv5&01bf#-1qRRBPoJwq_%*<0d=gD3W+^;cSHl zq-2qt{pg84T>cQ^l`)m#{LLq^1F$oZOBA6RJZ(-I@B2|jF{`iwNmG-3Z`VCd~Qc9x;y!E>4)hGH6{=3hvIci<~E-2NuT90>m-%!3Bp-OaC z=lVi5Y*Z0S0Hw&LHzE2!c&Wl@CYnUqxv$1 z-P})TuLY)KN<u))W6m3jHy1PSw+43izRJ5;;_aq|;2tSsC zlp^C4>)lE^2SfzyS9>wcT9!KOjUQZkzat8xmkzhqavY~Q>otTq&H`-n~I5Ey`^&gM#}on)gF>#ANcf|y+$=q;fy+Wc+huzCMDzQF|p9g-{Rw&`VHZH z2S=w6HBSa8=q6hn*|i;pM|v8jpd#gw*4SUO8+$gLtpj$UtB1Iuq`dx$%#%2Wzjq_& z>Nv3&j|`Bn>OZT~{_z42G%y2!TqF#{jZg9VkWbJf4+vF&Ue(!PopM4jRq^< zHAwQKN5)jd3w%rt?U`*G|4Vn3?D#q=FM7|W!r@-cNYW=neu#2i2^$g&Fe;1s&3Q18Zy%;@(r^;I%b7{b&7I(o(^+1ix;h|wwdKgYmV5th1_d2W zOExmL<&|f3jnwq^Wwez(mM6*Q@^VLF--in_p(&^eYod=>>x@|z#Tw}@clJ`&h2Q5) zHQB5?RToAsF6~`1(e{5m9IB0OXrANqAuReb=V`PKXtSyg_phm$Ae?VXWYu@RwsP$7 zuSp#rnop^JMe2He_Z+KyW_P)!X5R?v2!9vv_0MAs69 ztVyM=Yuem#e0V}cvFVuBNMgV32>OU5EfYVbf582nngvl7IP<|O%Br%!MSGy5_)&fq zC%4YyojGPqolLJ0PWXGPN|sdvyq#BH)X1CsW^=cvAFW+0u90 zJxcG&AeV`nLua_0XbIc6 z*H)CR+u5sDc%PjKip9t+G}h}4h{l_r)BjH}Sm2+u*ucNmm4M-Cng1Ni$QfX10EF6DnCSl3YQdlRR50)tFAzLh z4o}Mb=RlSYj#glMfU%W{g~`98`@ewT3liaD1z?5$1;aC{|2dQ~hq;9X#KO{r!ww#A z|64v9%=VMh?>saZo|gIFLh=6!^ed3DD9MZeD@M<+Z*OW1Pip>riPps4-Ui0W!s5iP z&unaJZ{ncGY--J7Xr*uGVq z8vq~T|HUNvd&1+dJjVaygvVc54i2s;^+?a!w{ep35aJM{lk z8ib9F70mk2Jc!43fu*F?{QWJ?H6phrWDHT@>)yqMQ>!=kYa3!z^sL)EOb;ShpIoGo zDN{YhoK@*~kz97YMC|wG=l91>@%Q>gN7wf^*QNL8&%8G4zLDOYu{7VE@81XWX@5XC zJJq>k9f3?1F?sYlSe0O#21+QLo%I6#(=@K1(y`Pk)pWpm_`Y5(6&;53j zQj^Z~JE@9nX|q=Tc?)sg4a*(hbDdMKm6tcmsm*tH;U`=*h09(y$ED3j6Hr*oaQ5BZ z@q*WFO_bT~e)IiJ&8?UA1VKYEnqQE2{<6C7#GLN8ua$Ov@cZ!y@p5!vMPJ$~n+XUc zR7!-*k}Y)Cl#Eo|tnSHJ?@!iCC%k8@@6-$R--&!R@}X>0F3>R-ZZ_pGzrK<-bqqg! zR%%VK^(1GVA(vAxt~>XeWQ){e_AB8E`Ur#9YYm;>uM}c%m7A3r6j}(dW1>o<65p3m zG3IL-8dQZW=0*05Ner?2eP_hL4^^kwiE2%XFXwaxbkT!!%oPzwP^%>LOBfO|toTCf ztezV4ZmXVP9&2v$%*x8DXjF*KDu^sugB;Gz{C)HB#}ozaLTyhKPo~h$qr{6A3wY~4 z?v)RZnFnuH#dXj`KOF$E1&@*J)fpL;0VUh@qN&U61xpRzN;H|^2K8&F-%g7n`_RiJ#j{-L@M;@SNp?RAU;POoYV3R5)m?dn?GT6Tgi+4EJ>xKj0V%VW=0^Bs zBT9bi#&m|>rI=cwhWoFqy40`L?~!MlwW2F#4!^2WBGxQBu0JB^G$+!hC)h41dsv`k z+2vx!u45^$+@pFJA3P?{!WB{#|K_WpMQe3fRs2qZ6t*%)i2`=nZMRwSmQYAzJ}OtU zN6&OGMV6ci4Z~4%iGZo^b^dliHZ1{zOg7tfN&dOM$3wgeiDLf?-R2M2bmd%L>5#KJ zKci4XT~+&;;ceA>tJn6Q^hnngSglXuLHATlGCLi{ymy-UH$t(+XAh_NKIYammMEhW zl)b1uec1_UTkf17tvx*5xLA>kd+ktQ4UN|~mfr0Q)@)SvQ<_n}d|M{(uT&@s!5w;C zBHh9P!Ch)^AoK|4LA+Czf8|u9_Cqde<45*qbwkI%Fm1C&&QvA}1n%20k>ICtL}edw zrR~m^gy}2Z=Q|EqtZ+z;3DranBYE{C^_UUUxbCfK$|R`jLZne@j2(^ax=}h!iN`Bp zrZ*gx6_cv+yIA536;CwUuomlR#yvB)9g6T%x5uV;u+Xh#y+WVzRoNJTKjqXTN17Y< zfnzg-0Xx0?n5FDy?fTJ_!e`X#Lc!mI3}r&s%?3KAYkT=65G)bc$+VSr>!x9SVed2# zb<5n0Y*R;JK5dh$PS?-vcK*iSW!y7^`d)sG4pEfU6!A3xS|TKHa<%4$vGz6w;GHL$iOSa(`;D6=d@ z>AWiSoygx(*mL#wKnv3qZ&6i=pNw*-2m=|mZwGvCbDx$g@vVvm@Z?BFI? zAJy)>^ftXW`KY4P>GGjWB$|geXKPw_HvicS9_z~EB}ST&w;O$vXcdyqGi$qoF$l$t z7RS|Llnhcx28U>uA(shM3}a@8uc+~!Et*LKIeWpZ;ogYGd@ zKlpF}RaRl;ja&<8;HOJLR>v-BEXeR}BzdpXn*KGQUpsE&g@aE>%fs0*pm<~K!lV%` z>eaJvM_=A@>BoNvoIun#pffA#6#b@~Jpq66F4-I5L%I)4l13^$!^cUILjm@# zU!{~>c#28QuWi;)n;z4lk<`42ec2`KO&CMr2Yz!Aj+Jjd)KI#9n-asGKTE!=vT739Cd* z3bp!csMTTVFD5`N;>n2D-pj)xIv?#@Y&V5r1#=iLV!au4-w8M~f2tJt+^<~ddy6N3 zQ5L3U9uO$dI81cypk7+bf>rO}VM4HNZu@kY)?C4yQCOd0&<&*uQpn+mQ59lTpV~1Q zuP#!N-LA(VPQ*3k9z2P%GU`@IDslW>-^}CfnP7a};&p&Q^4lx%ZB&MBgc+UjfR#A< zR36h{{nO`cW80%LK5V!?lE-F>+-d}lA;(WciOUGntTl=B#y6y&#R`%?yI%TC$IV&t zHc>A2OtbdAR{DZq@TtVmRkslrr-?YT+~lkWjb0 zq({Kl>-h{Hzk{RPaR9gb7)pWTsHqu+yVW{RUs_G1_slA^EuJOg_wreI%)TzZ= zem(_sz3HGJEu`wp2dlq}<+F2lbiS;SaT&K=efVN8oU`>7^^B3&)Xt;Uy_b6J0wBk$ z%Q3&?&&0QF6r`F-6C=MxsfQMYSp+olA#J?HPi{+4uFa}mbfinym~h|3Knxn)`5PX zts$Pd(M$aJ&q(}rt743CiWWMwIIG_o!4jNs6ewoo30U%qnZO+;R)$n?NUJ^Tb^%ZT-Q2>? z)oL+)aw$Y6VFM4kqu4U8NhbJyQiaM)A#^CJ! zPH^a;n0%LmzevT*K;BLVQK(s4wfA)xwwJjPuj?~3zzh~Im+y3XfM=IN&4wS8W$2w7e}<5pv5$pm-GX5?Q6 zVt8UohdxiV8XDy&`+*047{u=7I(x3NOyyZe)-q-4e@OW0t^hoHc1&&@`H44nAbzy) z1USmcY#hm)U^jPIw46aFBoX=4^{r+<0wRKf*S26kRdIInsvXU;5=muCZIZZWG!kOv z>pUT-06Pj;G=C^I_XNd9ejoXPOv3e z%V=7_qFi^G6~@vxpS+cCHC^t!_}A>KO%mMLg(rwSdB|qxsEs3nm!AZ}vR5bNa1LW| zo*=B~!A9&Du$omI?dy$5QdxCntu_^)3rTIornO!E2G3(Ds55-11oz(DG^ECS#LgQU zihued*5vLl`d9_XJi(#BjVP zNlL0VlGFU4E_n+4#)bW(EGMB65`UB_SH#dkp&_UVqX@+wG-#g&o~!oQnQ=nQq@X*g zR@ia~1j|~Lxg{devN(xiWu^6xRj31&c@h^QqfUUxqyZ~)`L-WOpFk3eB`>($6kEA~ zTP|s4E{{|>vqiX?vO&R$dta#%SyqN-gwMi?l+ViN@i3cf*%vY| zd5`-Ab#T5#qTP+27tZOyos9_k>Hz|rw zM0sfEK}9kG=S`Q?JJUQzW9SWzBx+Trr@xPky?S0Arx6$+b!)e7`0q}jZJwg|0g-VS>d+Eot4^DKK* zC3%1}%;RfhQ9x_38Z7W#qB6gt2W77F=giM1Te=Z;x|=&~7Ib=mhi={SEH7HJ&>ST3 zo7!EvYboy?(-{Ibso_^8u}N3FyZk6-!ncGK-NdEtrm6VWQk}s83Gt_q^|>H|(>bkCaZBa-qYfX687DRK#ke*6b&@ zohh@N628D7&nVW-#Y|J;G_zof^oePN;u$qWQK?=wV}+teFK z`tvIm2WSWll~~C-*-aSiqud6~rLc_S(CQfU(~BOXbgWQYc3DK2Ao`jxN>2BaVKv4O zk_2$pYc*WgG-5qxlJc~f;3^p4=-#)SkuVq?BOyj&?xYK#VH5Om)J5P=gW4f-y=pWp zHJ1?{Xi@brWi0Ohncu6qF0TKZMF zfbRiyd9YY>giZvIl%=}_v<4%63@3rOWQ3* zZ*t7h;V(!M#SVq!=9>EpN4|Cy$I|z&1~+dMFiv|5cB?nKop#}=P#04WD#c~@v$B5A z(R}r87P$@4=rz($U}{J| z(hG@*Zl7q~xSu6?Y_He||acu6WreL&K4M)5DMqVV(z)Ira%n~ubn`A#k zR#W|GVTQiZy39U-O)>OmJxe|39mcSXh3U$|*jX-;W4BTye$$Ub^2-PMXkrmkQYR}! zYKc{8OPI*0##i+r<_L^fCBpCp7K`SK#IRvjwAk>xCe_Jf;0sX2-q%od|D~SA0;ilR zV#5xq;T=UV-;&^{O85d2;z+qgP#}FT6^`9j%t9sPloG%iGuy#X^bdLoS zby1OykmiAkNQ>J?4R>0C8q!hChYBW5g$ZhdkC_%By)2c!_-A^PNB*gG4W@X<{@;oG zE7#y_*93XbAY#7--q3b1ie!!M^Qkq}8%-E3mK@PA-_?3pGj10LKM7PxPt%TuBXvnw zd)`s{#H^W$iDj^OCZ3Ey4Un3E&tgy5k4fyGxu&y|(sb`tSJnutKsd7NFa@R|zi+<*8DqznFZ+L8`>8~?cP41m| z8!HLk6tp)?!oR?R$eEk-PIpPA*9>tF=j+xDPd6N9<>YnumW&{5cR` zmpYw*KU6pXlWyE#FK|pxq*v&W`-#uYP?CbO9oLMmK)d`|#%N_LESmka6K_%m4O95r z<~KSfwQ;7NTMt6OPjv?q_k`BLm=9aZNYn`->Qtlb((R1$!-=5s`Xw_!vJfQ5hJhOC zd7|{AH~W;BB+^KAWn4cL{vNuKiDAj0;pzx3jgksecV#`M9lk}(yv?B!C1s)amtO#avw-HIR<0 z=9?`;RDp7nZ=Og|WozHQ;3peU8IuELV1)8fK3(dZ3*cHVv@< zHmT1VXsp5lMVeG9?jg%xQeO?-`veWtgRfy+^uKrW~TStF@l{E zo2L@qx1fGOZWL9KXA;BQtwk>xREKat|O2cHvz7zDL+%Q>Z}DvHaLRGNRR_nV-NHpxaJ{FV5aaa zkI*@*lsl@#Nvgjd+!NfMIiKP_Rf9^%5Paxn9&Bp`S+h&}k|9-SvQc;ruZVr~11%N} zM98`%_*Cg9Qmp}j=CB%0EtHYSQ)~{$P3b(C;iXFfHUCXkx3?U|Hzt}%1G~uqYNC9I zTTvXy;7Uim?6;0U_CDbFWw+$b=$>6E`}8u*Gx9>WmRF8C(d3Z;)XORzx=b*78&#>? z^p&^!r!4UZeLS60y1kpMn5xHthhO{jcq?dLHr-i`6(ju&(>FCo=G<6)M^D@iSON{6rMY2B-MBd|b=gIH}am$h;X!XStW z#gAfe=JZLO{F!`;{X$2P!g&cLOC>i#wIAej{!TlZJBPoOvb-o_W8*2-Au^)scSLc2Yq`L46EDLAK>RGLL0CLzJiJ84(R# z&W(T}-=GY~n#|yR{f?1G(=>ZT-n0;UbXgV!54lMLl`5a&ZS899T`jW}$@zNFT`lJQ z`HS`uw&`F1AB zbi$5}L~cofc$K3Bv)S#XjgQ_BjiW$&)2Pt2EUieJuo=-8+X({pBjhK4ve!;hH@$ZM zOdVNMQTp5afP)91JNMzD>jA=m+mY$K@So~#cBGDMOrL#WecrRKsm?m6PSJw3eJ0@fF$*6}*g>{Cmhu0&5``r)mStu`hB5`QZHOFte% zPE|^5K32_$V=b0IcslD3=7Cn(e~v3d5p z=)}22#{;IaFr-bX5R+87bfLR?zoJHp;4I49(itmn(dc$J!uWy-WBwPOR?-qUu6zrg zap*uxNp^)12KTMLN9??!-f~6ZY1SCEat3$$bSeupPB8*2BBJ2JTp3~lDe1~U_31=H z1-Tw--WiQCgpCpCw}$#vUv<|M>w!3Gq`bM9TqSt}1<__{p-_p8?HX)S0Y%f85*hAQ zEc=1=x=M~-FDdthu4fFtFf||4%;qF089!MBH*g9^2=h7U~1PWb}U=@JZ`K;b@QzAUtw1wUe)FGR`;qx!LLO`ZI$cE3DYga`p`&_1XWu43h-N|4=H zkiMwKZ$jt6F&t7h6Sp9{*MvNFu0%#fAAdUmQsKI?_ooefHy+KL!HG=)+a=NHA5pFM z)Oo?In2+K{A`-%gixdIEdeL$rzpRX8p?GMCry($^cA$5D{j9F@LH2yyjA#) zDNp~rc$n9vVOVB?J0#SK6@r03V1 zFDtir%3!SNr6UUO@`OPWVDgddTGVcV8CI$78x?o%yh|rJx{Wd{AysPc!MgiD+ny65 z0bed>WmXbEi%kp)E{cCqbTG~`G2aq}UG0PV?5zzt4e1>_;UxmP^`~>+xEU)Sr%_?| zDff7Gpm+>q!*fqIQ>-l5wJ4$;4>PSDp6e~?vf^G!Zrq+4eE-&;ATBKtc-|V*ek7={ zP0exRp0I}r4$oQT@AaKjtdEm`LodeXWZyI0J25btY`D#Hs_{mu~co92P@p=)I<~Ib+qrH zTzp$6v48eK@qECj9Z%%4L7W~PHN@7hAEI?3zh z=ad#^GJCYi814o2=T6CjcJ>YmMCs^xbg=o2Ch@`3vp8xQc$GbRj5;HNYm--xT z`n)FV`n)IpL@_mcCR-Tm5Qf~vMUi!H!?<`}8qgvx&W{&9^s6PB#&;AH5XDLui}%pu z<+#ATsKgtHex0bYZavquwqzd|&((E!O+wJfF4<$*+R_xtERLRJ!FkCQYev+7Dcp$0 z(FykGRhjd)i#D#6MUmZ2cxB+7&zenBhLDps5_2yLwnn~Y*<)Kg7!;&Qo zwR7)nekuvMty-`n7d<2QXCuHAvtP>}X_a?yWi4 zpB#6Bvz-~7|6=UwENv|z((ReHX3v-3;x)Q4uTLo@fHexaYwbd>->g_qAZ?v0G|Rkf zks44c9;z1g=C2Y?n{0Z>vkPk~iN2GHVGl7k7;>aj%57e%_r?;-ZO++iljEzP!v@nC z2syIqmti+Fq4JxVXqiGzR>2zM%gV{H@ zW}(&oji*NJy$}X1U4=s#9AFt3P7`vHvGpcwC1MHuMIw_LaqDXjtg&OI+uq`9tr%wD z^-3mgE$*6!LQ+n#$z?!cs_UUgvk*;}S0{3mp{2?XnLL=ws>NsBsUy)20D)cZ{lS>_ z3O`GqqNszrI%Sy$=iGo@TD)Bv$Mf0g5 zh?mZbHfzZQq+BDWLTa@&;t)0w5|U#BFTq6LhJ1CQb&zb8q3-_od3A-Ul+B>q8;oKD z;i;&CrB%w*z2vUj)legfBnPuGCK;#l@2;(Uuz}UzP1F zb5~sne^W2OVb%VxGHa0Y7 z#azELXkoU|F2&5{4C`hKAe9{&;2cPn%Qz|;228L`GR*rT z!cJ|kKg*J-ZLBV>Djt3~U#`dJs%n5C;+$+DWL?%60%v6UGZ)=DI1e>W^qI+G$iFKt z-M0Mt{mx^rpV+LXrWd&A$onM+!Dz#9i4~JgvXd}EGj@q+?R#(C#RJ$dW9rV=^1A$I zaW5(_Xyc1uY68r|=g!SG&MfUrG*v7KB4%po8i{Q-+w#++dd)q?M_cHKo8 zY3hpizGol|K&qlj%*6KAF%cNgxUaAgsbF~K6=^pxiG3GFqlihmVjY#Ew+8h#_D(8& zWunm#?J!4r7Bkiu&W=MENED`H?pd6 zL%IoRNF>LsD?@Zq`E0jS;49~7>I~n0{@o%QR=kieQ>0}Vvaw1I&!Bid?yJ+p&b-mL z2@Mo)PR=sPp^}+scF7@q?SpMo+B@Z;vsl`fy&X&Xkl!z4$)|1I_vc`(os&L#-#)vq zji9zG7uWU9yg1?P;$mBHB5{7bNS()V zrkJ}=yAQ`3IFwh8>n#b%UxW>FW6|sYr+=|Mk(TRB4YzS#5tPe1*PHRm=yx{qzJyA_ z!igc4P$l+00bT(U&nCzrYRRZTJ(%LlMUiCKM_RlkKUeF`Vh}$qq@HZRhqdN&;1Vh$ zs=8?}fu?jLA-WS`)Q2a9kGHO8IW|8HHzl+jCqThyN&0P3_*&bQ622quPUDJ%`?zem z$GO%35JtQ>!3mLQ|CrLm#8cp5zkpHc53V)O{77f6uSS#z%@%@g&c^xJ>lm+M66)H* zple$`)#D)Vb`OZ6@6Bzb&Lve0n4>GMX-ag-`*-9C}D}x$Z)|5b67G@ zVdm^sUk;X}g4L5-p5aElE=8)g+K)0#%UhhbtXO|&UuVwXA*b^3Y=tN`ei3#SNkHI- zcjA?1xkjN=q!wYJn_UwO8upJh?W-R1JgnNTxk(qWAH1pK1mJXzgwGi5Q=v+!9sVAj z!;pqNHsUqRUwG*|If}Ip6g%0DPK_!N2IhZxb&NH1dlp~1Ly$V=Dp^aHaNj2Z$t9fF zKTDCB=T6op=TE)mk){2tAG!UVr5LY0`bzc@TIe!zhOmnb$dbE5*i|&XtY4{%DueV2 z&Yk4M@RB8e`P2L{pBIIZ>dIO%D?czLpkPc zCo%hoh+JibQif zh#@u#93Qh1DeYBnD{(j|ZMhrc@=I8j>h2WL$FW&ah~Jec>a~mCae{g+r6FDRek^7- z`A^*1tYHVlF1NB=#3nc*n2E0I@gfYWQ#pDOe@HPovdD#q&BpZXr(;i4O6b3q$hix- z^U)>=9?SgrJ~(=WW0uTEg@EOi=ad7d&hRYy67Dane-oM0N;O|ben1^N5z`IC#~OFz z!n7a)+9iULYgq4b%o*k>aLkvruS9^RWV$u@WGa&5L-tk*KcHU-tfxh@d~`q6I4?}6_i z*`X&TO>tXI@hW7P5|M*Vt$G|%rZdGl#Oy_%1iZde-V>#H^RVN2ot}0dd*OYRbs~>I z$&lKiV6O$~kkb{%-4d;j`N8Yx4D}lOj7<}q)l&zC8Hefx`(4%M`{-^CR!b0)968eX zSdN}n`yUY&DU#_EG%lA(VK`Jq z+C4)E9^99BrpnEQ6o@aiE64T#V#yKy}h^a9c>&B`>Wb{t2_V1$BXTi@iflfE{(nYqp)?l>YlKs$8{;9YG zYzLg_w1sd@t@D11L$GR|85}w%fE5H{X4n}>p%PZ{_k-%{%23;|5l)tzxesj zco;KrurvKn4#tsM=V1j+2p?Ul3~VuMNk4~K69iFfpVxsQy2xd|#R$&62J_yW?comJ znwvl+ZI?&UoZiM#({(Vvd5S$*P+*ShI(R(a`>2Ty9gGrtoWJMIbJxsSb_AERGkFM;t zjk~zNLvX!c?aw}Dt-{@pjtz_^>+(*ox+Uoc*64J;eH_zO3_qQYCU4?@N>=FVeVe)? z_<}Gj@?@Ny8X9zLaDJOwC{Z{K7YxIE(#Y^bGel=LRuQGTyHVp};H1cBWs-dmx4=KLxh|6pLQ zib{3gPfup=_@?a zk?oAD3rrt)(^Sj{Tj&g75AxJ`mFzNTq7-LG7>e#nbLXEWH+R;optUR z;gQb5sZe@m>5RfoBsWMv>9oI?CNLqi*OMih=N^MHEKZB*88qhhKmNqdb!S46OrmLsK{VzQn{G$p!bOAUR9~UW<-Z z;Uz1hv7>7Eg5v32t!ntER^GPy?7^6P ztvSx4E}3z{--=G@R9cPjLymg2w1<^gpyuo=?CKFrn9)*^g$}1Eg^3U^Ry*Y*qoBLg zL433kJ;86MG-eIJ8ifpL5<-qq0r`SQF3LB~NaGakWzHV8x-X$vW5Hs~LK7a{%xMog z7yEL(+ur&&kh92tmK7!pwaG>D84-T(#$ln@u)6u1sloC=@zEp9(fAU$^DN5hmKyx5 zf7DZb&#>Y={n58KZDyLy@rMmIrC-zY^gpjDlz&$;)YQ8$Mcv?@Da1~lO`Pj^Qyy> z0q?@Hyu#28mrwFFVA6SYVh$6sC zJKfRBfS+Qu;dy8L_l%J(H#E~uG{;(jV7?Qpb2DN_*&z!@gX$=Z09c6;TM9+n8=W8< z^?G#AcDf(uw)HX0D){}O^p9(_FJVU64({7)WJDnJ zTug&p5OS-c7DAg2ab&>F->X8n!o62$i0X0AZgzUizWB-A?G3(O%+;olq(O&cW8lBK(^ZTQK!w%0+z`Y@# z=|oUNcIiFsmj8jJ8qrY{zNx)adkwB+CBK7S3(VJjtcgSgz;~?pxWqTiwG@)6^zkFN z?ha9kEd68&xHnt?oUhw4EHTs%q0ATzQ$*92v)ti=R?N&mpU9ID8O7S;wy$@OhX%sNHN|1OS|MYSUI}lmiXIx-!^oK^se{&GXm8&bC)d&3 zcE}7l_6!T~B zW8eCgqNr`<6$Db0268Awd{RvR@no-X7{WRr;^5!EFxed~OuAW?xorO-`70>Bj`}*j zt+yIMiBw@Z<$1qle5M1wahjs%AoE4?fZed3@%i^I{GYcQt&LNO2ZwT|ZNGR)X!DKJ zShVaY+B#K57(TBLehB^3j_gn0QXw1t@~=K8=gCmx>Q3x!Txz@v9IazIZ=u|?>B_-h zeJDxzdUzpc!1!7;5y<^Kab0;!voBmc;-$O+A_JC6nQ(GHVeDFxqi3WMxeu!ap|SFL zGI>xL_3J0 zwFti_nrvn)TTF=E-jk{OXr+a!5f48SZ&r39xcYA>8!GjO&~%21I7YBgW}Un>IA17J z+DxjdU5Kcga_&JY-0VSRbBkQw#-G7{10co9kUzL%CwGIrkmScHVG^*=bb$1py z#guCB!tpT^z7D8*W~^17%CCeS59laih!yzZJ-$)5%K0GoBSidm-uiE6D`Z1syF z&4<)>F?I@|WAowDi)|eqkBT-!h?Pw@$6NT?@5tLh%<_IMfVGf9QHNHPacYM+LUshD z-`}%IDSos4M4>6mw&2Arui$R|DljI`J*dNtCj>q@{O)jTd}`k9ZlAdXF`;1;1)eF8 z8={kT+ZugFjc)|bl=UqW#qiz`7b=sQWuSIFrh;eCS&Y@~hYk?Bj7RBO_;xoKu7J7< z2~Bc$LI6fWO6Qey_d+}%U``i!LW_w^bE!DQG_Fqm?iR>&u@)3iS>j!$e&wIN0}KDQ z*VE@gRT~~?cj%qZ3*IumlW8)y#6Pc}0ly6+_q9QLkL;2K*V}wfgwd$0r(44}>lSfv zyQt4$-)}S+B6%~Eufkz$%t4{gE0Z3Sao?qAh^M z-*v+yIonEYc+$FpxFH_n9qKp(cQ|ZGe|*;ZkZ(t0#pZ3q2omJbyiV1+cv>R( zvW;kBFXyvShzpUENg5f033kYJhSiE=ubthQ(b+TdME9-_p34V39A6MnrmGVZ#%DgvfYWXi3&v-5 zOrR-#&?Opp=RCOuCz`^Y9wwWV?wyWi9Qlojw#4CA&}G|*)gNDQhH7}C$`8TCb^Cn( z!>+sm8J>L#SA9VM+NC0@p=jh`H`c@HyrlNsbuTlEN4e2Bo5ME@ER%)mR-gT7rRh!f zbPsT86aVl>-;Ne4(kgOI?pF(c zRdNp>XCJF6VM*;L!{by-UfsCo0$7sYlQgbfN(@wa?QvbzxCNeo4!VHil#UxE-e-^6 zl#U537Bpi^qXbp6>MZrF(ZMy3J~1qz5A5)>%cE5a`sG{z+y@^q?Z zKOIeh@jf`d(McOwn*h&&I0`^2|B7Dwx%9aOK$aAh5CwpM001Ds58!hZAOwH}hk%3t zhlGTHgo1*EhDC;jg@J)ZLqvi{{)&c)`4tTV0~?Qo02_x07X#xP^*16?G73rxECL#O z8ghCPatiXlh=4#rLBT@9qQb(Wl4E0FlmCC-KK}qvpg|=;alk+b0iY-#U??D;eEKcYWjf_o9&CDHuVQO4l-Q4{H0)v7>Lc`+X6B3h>Q&Q7%^YRM{i;7E1YwPM8 z8k?G1T6=o?`UeJwhDT;*=jIm{mzGzycXs#o4-SuxPp)rn@9rNSpPpa-;tK=-_Rnkq z(|=~{ANWE6@&yVG4h9bO7hfQtF2EfO1svi#1LPM0c_@85R6<5yXf(mtoSJSJA|{0^ zbOZZoSPWw3ZIbK1So?feR>yE}yGWQbpE|86A@`3&d@b@`VxO)76)BgnQn0^8ReYQ4V z(As%E0e(w9Pie=+|G8{s2{zc>+?C8$WXkhfqLkKF7{mdwix#7VQ23{@moijmq%&*D z|8a5AA74g+#XEfhUSEwq0mEoMJ?g-k3r2N)0(>mHo(Z$4&S;I+;{O{cMX4@vB-_hA zseT}Q0&=8!H=k&NKLMe6|47=L?@5Zz=SY6>6L5K+{0ShI{{+AoUB1I|egfX#Yd-;d zVH~=82in7Y+qzF|AAxj8K3k0hAEeR$NV)hEuua_oEK5xH6VTPD`xYzqMv~lhM}GPV zczhWB1jLc=d-j~{_}J|7>;J~tu+S4ldbfT_HyPr&9X-G_Ky z;sfIUZKMBHZM1lu)AW2#fOy)d(wm%-7i1)GqJpJ;{Rsdm7l>u1qcKvi*(DtpSY~tjQ}CckHM_ zhS=2su`zh-iRd-{^^LX;5pv)@S5fg=k5 z4*~}jvsh#Qv0!Smpn?3KobB0%@8y_pgKr|QX!4^~_A&alP!4S86QGeLuUpLW?De;) z185jIa9*$@G{jA02mfRGz0$+ts}s9E?juUi9{!+3BZ4fr{AxEYy=YB8c7U+JuTn?= zSasZiyHJM=BvU9_z43TrgcD%8kqUEmPH)ym)^7IQ&n^-Yk zQLPCDS^ZDkP9=A1R^_$6Jk4y%T#H36s?RGsc6{MdhZkM7S?-HHy!4X}48_FUN>^Gj z>Dxrw5Gn3hnqw}kZv0w1SiDkCt49VZf_nje#V{c&sWs$TetuL*Ch?S*YfgUxuRm8_ zSX*l)lHJi@`ZH+>)N`r(b#(Alddi#Z2z#NTP)&2NgnuPnRFmVv3Y)x=wemp8aIW}t zm8fJV>L+gbg=r-j|5JRPMQdb5M_^^SRpt6_L#GER#%%2&m+lo0f4={YOK{=BqO5F_ z8=cDo_#8qRoLKYmK8(BdB!1?d!*}(J4)lIc-D}cFH4d$b^2As_V)7Wy~G4)f%d zbiF=b9C>`?gC>?)a9mnK59>VIR08RUWZS1vAFDae?9`q4FyGib%nOd+&XFiIe{?}UaaK6Ct>_;w*e?mQL-kq&I)0y-t;3F8ke3rcJrz#&J2P&WH9lTz zjUevKIZ7qrn@S4eS}9H196t3>{B9eGI!Xo`vBYe37Do)wV>>P zKY>7#SH}uOljjGX0CyF#$cCqOnr$9M%}gdrb^f0y{*mX?sywygSlI}zR3r5gyyV;( zv`s7v(LNTQ)5A{r`S=ozfdlq&@kx5nTaWjpF(;i|*GQ3``i9%@HZpaM)0ISFn6#Dw zelo{x5Zyz2_->~^dTY+mR3~3P0Rwn$+$h^#HZrxLHv3&ki}H0!8^jFowK;nczL|R_ zom8z;Z}u;beUY@2Qq2;~aF<9KGLYSEW490X?h){QJOX)%!BjlhVdVMWa>K|0|FVN2 ziOZf%5{z5f+^m`}*y~7O9@X_+6R|%UC4zhcDh)Sz677jTBw3?B0eWRXZG>l{?c^x5 z%^KCML$G~`Z&7l!sV(DtiM>2s#UexcMi|Y@EH*U2a3K8Wr;9=np2Zr~q*GQ9Mp{*` zg$?Yu53z$r9!+N+uB+>)HNwqd9#w7PDtT?@@RxjXUc<|}N@nt%@@D=Li`O(;Y8qNIW9SV4?Jb#$|?c##st zo*apn4KR-5KPFc2#6_(=-x+90#ncuUuf)~BN#tFPY@vtHZ4|E1lI>j+4d#V0R$2v! z&1vdLD*7nHadVpVe=+2&*4uMVfjeiR)|dS@J5VDP30IDn{vLv*6frUz{bI6D#kUr=6}Z`gtB1&Nt;I}q)4vsMLu{f4zS=}bLSp`=!O;v-Ri zte_bk__4nFd@*jEe`6~)Uufd|5$j{*Ld{xvJxX(Gyg0N{7Ki&>4r;=Edw%?4x%d?W zS;o!Wik6sv62$g^(I=pKEB1VFp@m0w`y#81HV~uq)XA#QEdF?aHN;q!#Xy&y)ms#l zmLdMuX*csXe_iYBodp+<;By093K2@jtXNIKAV|v3x1|ADhxh?I{;x;5VSu;Jtarz( zOM*k`|AZ;ySDR$k9^rxaY62PZwu$e**foqL~iCFb+8frrtM>tI0l2n+V6#R|_j9ktn!2mh*vB z5BjhN>iGR|V{lSYkmqWSLGy5-Cz(>S*1*mzny5sV0-+L|8Xunbg==Tb z2_AvVi-u^DwXPMyZLM(fy;m+eV=})@e=7oi6$iV(N*mGiQ?}7*v^TFJiTfa)1G{I6N$XP_l`LXnIxeZCH_ER z4PE}3lss#fJ&@fA)n`+!>Bmc_ErFPeMHssUwT)o(Rz5w}CI~c++wH~Xo^6w0PO@!l z!%Z^y6(?IEZB8*hu!^J7!^upf@d@~PQt9UMs}tA!MW-UKv#Q99p)lTxSi(8^QE`|l zVF{br7zUZBxEt>muAxJ(4^n}wpVU@|&Csiq<~RC zcQvd_1YI}~O@WD$!sNRWeB2KJTA#ud-&??&_9sAJ2`J`N8YgA3PqqUWv+DB+K6&Qu z;aZxk44oulAg`vShGwwRx6#4eNX3!$56B4jLjzKz2p#3GMc0b zRB%e-Dr^z;nB4N)RzN z7K4W;tk&Z^Yw{7`(4)W93^`t2F=u->c&FM;b~?m*RbIdsTIa4uqP@6%^HzQfP?lX- zERq+Eab@xqz0q{dlyf6l**HPWrRfyEMCCEXbsF+*EHO;#PjD)2KtJg_ZS#e5Ml~2Q zll8`{52H79-gd5M3fueI{Yr%w1Dd0-?@P%K^qqm~{yPNvzD9CrL;XD6Zn%Q$Zq$L= zG`Ku6y4Eia1$Wqsb{r48;wSrsgBw{^XEXFHQ>;}Y@-4e5*nte!x%e=qND;E1R?{Nr zHQB)F14tC#ULH`>Jir1UF{c-t7FE&nWZo;TK{bC164%X?7u3e-KWe!B3|$7fP}bMl z&9UV?#H;uh?I@Mwx~M&01twH>L{(C;naCDI7Sip2D=1U+h51)9+^vc6?36Rhsq%j7 zp#Pg{r5S^%`}CB}_s~V-*8Pu_kVTCN{M_2psd&>|VWU$Ay?}kA z!zN7|j%lF*ATs|rz1Ghi)K;8DXLAQXlDBZd3?r|GZdKZ&ut6Vm4F=v@J-$#1B>FMX z{*eHIcA%_83@<6t!O*9K=T&E2mM!DUFj>m=Gr-azu3OV}5DN znM+HQ#cGCv+9MLHW{Qz0!=59;H!${RB`->xO=#C5jjNEWi4v#$6|*aKIHRm*RcMByRvuBu?0^wXd{a#^l<_<`f`1@fpBr%~YG3(8Z3ejv)k0Vu#h zJLfwPT4EE-E9&yeDdcLe1hRO1aR-XhRFW1c(}V{r52gkiY0$4tZD!H})RB>`WFdBF^&T zQ{YQ|fAu9LX(A(2rwR|E=a!~g_O3N+%u=-zrCN`79XFg$3sh%B`4dTvJZ4PGrtk6N zqQ6e(BCCjLxY%Z?E>XOJOHD$~ajO6@$QQ4#vhX%e)yfObw0g!yLqXIwI@8QH);6Up z(%9ZtkzjX%_$20I{>py9TU|fgL;HB=B{4!zvx#T1KO}b_Xg5fAN657lWgzT=kf0hz zQLxAg0I`TK+<|12^ex^3q3QEO0R9H--GNSZCR`Q{NX8>3@8Kk_s{TH9REGhZcc7CQ zJ%G1bJGcW;mc$B~MAt-&_wY|lJ6B#)dFk)0BhS`%%@T>OOcIQ`c}#!-QVU%ifY@Tt zK^P^#*B)(4ETI8J(MDA*K*IMfz{T{rg=^@EIgtYbQt-x9husLlTz~x?v4{WB1Vmrr zNV-v31;UTHx&tp>*4BnJ%}2_S=pLcgAvZ$3js8SKBTDuTlzv6Q{<`(15}F7(z60@m62CWckFK`}(No-@%$+oDc25p5>N|$S z68eb5#?R# zD?iVah4vFL^|ZkK(yo7+P1EHW)!{oUs*{C(nR&q4C;>v2)xQg%WPiC681{%147>Pg zd;ovhS(uv{MqU0-+%mI^HPQl(8$k>A-bud{1z6RUF-QwuHe}HEU#xur=2d6SRE|lz zQGeqC9p}UJ2oDr2*^uUz9pOg~7c!?SyP%8Hzj!Okl4mVW1g)!0RM$3*wQISrv;A;c zlblBOq06q=PS8Zts#qap@~jgcVSVNhXn0JobqAW11J=FsWV}CI^c&<;YapD3>#Sd( zWtwh>;C3! zeK>7NsQqm|J<@%5f=M0L|B^Xyn)`*(n?)z^^E2DX9ommJh8QK;wte%AJp&1)66a}I ztJ#CqwzhD4B#Is}?(Y_&FWHJkseC zT12X!uc5Es$q4j}03(VOpCD4MgFk%3mGFtF)%<0E)M2hmWmVEVCX0G)3~N#9a@D&; zgkF}hZBxLnEwnUeitB(%e$v{|PFT6pQN9=JgI zCKS^qE5D!EpFSL`0i&M&(X`BTTOlICcZPy(LmX&*{#ktFcE1FaHc9)N+g09!f%8RJ zljE~4x$PIsoKJW=+=SgDDjT26CMpG~)@lb~eO;AwiT0X$@>#IQo@%Yvc_-?6O+;>Pwxd z16iP^7|)gxSR0qumPAP^k9snpeeu4`GKDZ^m&@flydt8C&PNtw?s`vIOYT5SH!ZZy zjGvTf?UnLon(tTMyI-l&ih;%In6Iv~3EGmx|I;P?#B5i-f9|tNf5xa8Xzg&QcxIEb z&GGf$a-RW`D4w0|AMHy@ShM`mj0keZqN}HLj?7Z>pM{z87vlCk6=<>>-#k@DV=2<> z&w~k$DJG)KCc3%dKf%(aSmn-qs4O^p2jZmSL=%&-NghAL{4i7*#(}Q8z-{F=!hhw` zt?3Yt9>Q5pP5<_TAZGV**BQaqm@-=I4rxNugCZS;fqL;&nioS#qA2vSMrGj*8V-5bW^TkH)=5ha$dX!MVhW}%2y4JN7LKlX0(p#8^uTFsfF7+OA12Ydh9ixx_k4U z+liG~F0lkk_KvTrqAp!{FHSm3gFI3s4r8q3nRRCLv_-}-amSRADm5T zForFF*W}1LrHaz8Yj8y8E^v=vAnUf8bekLc&Ml~mvz3gFf%_$^_WV6TI@l`Z8_ln3 zY&J*+V7K8%wUPWG^t9`0#yKCTq}1iEF+eBxZS;?jlj8V3j!izrXKPA6_cV@`iVm8s zc|VJ0<(OK-z>3JzuP?80q+gFfyL~+OG*95u{l+p?dR>d?>g)1YG`qs+9`*0a*$)hw zq(NpLip{)DSXTUa@~p!%)19Q<>`yr{xKiFgxRSa6FBuJ8xP8agbROXv-@O3o(m8;) z5H8%vRD3BH;WQ*Wy;TWYp}L~50Qf`SCU|&SogqeoVNjirlECYcCE6YmpR1?)ksBkm zrNqGAv?K912w-|P;Xws|R*LfnNRTeJP`V7??SRxhUArPTY`J7-a}`L?<%5>^8oGHC>5vinAF*zJ4&I#X*#N2g9(frf{t>zHEuz2y3% z?iH(&>CjD4=WdsJfE&lO8(}C$%{G?rl_zHPZ|9LM7WFr0d3(YY4Eye%5%sCubjTj2 zo-c2Pq`!^V>?(TC(P;>P$C29fEbCIqPgqDztUy%=1KVfTOi102#zmFOw-9tSlcWk- z=Jxt*dqzYGMTmPikI!Y6Lb(^ecGHS1+~?q%*+{Z8mJf{D9VU6Uxf2!;yM!c7qKt+j zvnaSBQZYX)N%@#Cs5mz{$8a!jx^Hswfpu4mRZ(K}oIP+imH+#-9B*=b7HVHzGYet1N9r{3TMH8sEv|3>tw&2v04&jJ&MZmMiN1GShEE zt|36!Q&x^1D<37)g0gTT>e1C&u!uE2gE@#dKf5(i<$m2AcsNtMH+yc(gPb868{A4g zU2ihwgyFx5)}M5`V`IstJ~2%fqCc&H!Bso8_VFgxS_gFOGS=|Cn)I2Uc^NH9@`Vgy z_;6$ImF?i#2fH#9O${c=tGBE&ZsMed9s$ZVR?vi+d*^C4?~(*0t681Yk)9<>MSRa^ z7eDAviQ9arjPQgSQSLhd!FZd-SKdi%+6b>Wy|#+^%SiLUF=Ez-OF!yweU)w`Qk^H; zm^DH*<9M*hQU*uAz9(v=PH!(M6Tb6ANe-hU&MVAnH zjc`gCFm}f0uJ|=TDWIoX&mzI3m+Oq7Si|M(UhnjTd*WiFL|;MtNK{2mHiT#g4i;F{ z-BVKeI%e@!Qu9dRo(AReR|utW7gIZ9!in7>RRbTuNQ4)kP@x*jyFP$s25sgQTmPJ zRBCl2#l;i4&iT^F?%3OO+;7U5BR=B2t+5cPH!f2OEv!*P)lmt5iyyA`wXybTY~ceeZfeF=RpIyF zGq7;bJD24f`T37fvzC3uS9YyW6ojq(@fc87h$FOvggTDGUDu&-vIxbMRzlLWvcma= zeux9b_sC@?Tq$CE5cx>lM8rgVY z2jtf+Z)qZ=XiB9)uMi}0{vhvvlK4p9T}!WUtQXF19q&L{I*qzZiLek22_WdaB|lpe zo`eNK9j@Uu0D)0#P<+@6e^y8ZWbxpUUdOKuI}-;_1UVPl%`L)3pA=ZZ8Q@)`^eeJO zs+O^`8cp@QfpDQaTEGI|4#ftW(Ms|b9|^}kYeVt9`GK+QV^3#Y1v(mwD}J+J{ESyx zc2lnf`3AK;R@wv%a7WpYmwXi5eKC|cK74}uThW;Htk^HcWnx82)jO$8=2XAuWvEGV z8)bua2LuTUihRf-cbQd-pPO_b=_C;LfXK;L)hUS2*;C4{Ss@-9@J_wE1Jz}}-I63p z>I_*gA1hg^DhsudM+rHwh~4O6qGJr6w8CzdS-xkGffe`mqmQ#!?2nn=Oc@SWWPe>j z!V|ip67YfIV03|h+x^A8SNA%_#O4EX=CJ>pc7dm@ed~R;XiOf+L zPnOm3Q~I2DD&3eNw0_#r?@aaZoj}^;2t%lXNW2Jby+;p}Q7yB79Oh~G$hq=%L=qK%B6u8xE&CoWsN6qvX7y*YPr5;vWP8fV%h>IQmp!G- z;6_)RC58K0F7Sb1mX4Uz>@O``F4O_hPw}6+BAd%kVZbJ_I&r%CoFUGMwV_6O;&VRkJ?Ohw0%d{Tb@7R2A&iHyIH0S!pwMr`O73>& zgE-a#6GSg>6F6{fNWJx@2N_;-iRMJn;SC`SO0ravs!dNMABne&w-^{cpHPuiK70*_YW?Bg#E<6iVh~*so_*yXB-DITh;8L=Itlj}elSVnf6Scbuh~y6U zl%9|2YEJyHW$&qOlP7ymL)aYb>ex9)*~r2oH+<%NMC6urUI-zK!YBDQN}1@$+HX@I zKDdtlzDy~Rxa%|n=X2DX22ymimNd%lO^J%n8+RazqJAnsT!Z5p)~Gn6c(@R>o~TfB z!!|^!CrMZTl%QMjHL=Tb0~d^z&VYD~Z?m#+{OBR2fO%ETJ;qPusITRo{J@r@!=gOf zOh<9?<#~1)afC7F(p}8QFh(hBYDtGN5oAQB7m@8iw;}{%LS%Djqq^^s>a964voxC+ zm0cb-6UKqv4jgN${Jnx{(PYexZ`KmX&qGQ!Cz49ymlm+~tS&6+0?kH0BfPyXc``^W z8qn@JpSWD(Lv-|X`Q^5kOttInXJ^~b8MF=u;P(yj zasd(rojpyvSYdJoHG=3zP=9qVw_WF_1&2Jom88hzT*Gd(j3Fz_r5Dt#d%8AJWEr}2y();+$c@x@-zke z^1$qDATk$^YrO>T!qE4SkgBOY>Sw`-y*NADyjx?Umb zeg~p$TR6uCsJP*3f@u#N_%jGLfQ^L#0!^Cj@31jJ(-Oj=`z?ka)gQ7hs^1=_PKPDq zXut-JON9m*%3Hbv{Y^(U49qJ|K-1<+U=;tRN43exd?QqPDd|H2h``c;vHnwnMlBB9 z`LhyWAb(SBmLveOxWB$F6H*eu=b$w&oIbvU{2_=1L}*BhfanY+@u%d~G6VbH{k93e zvLy)Oma;?5VQElr^=yR8rn>-KWOTj?;Pr69dFd58Olem&=%`Y0qk`MMK@!gkvo8JQ z?8O&KxRTat@wmDSU!23xkmhT)jp-kIGn4ifZZ<7mQK)S)bxvyxyXYI9&2GSi7JD(1 zNAugWwyqT_!kM*ove{^^%_IdcMAN1SMwJ#J_hz4u%zBtSP13j-ku!S0<143^v}sw) zWVG`9z*B6&S$KR-pI_e8+3>d!kNS6GsLw_)Wc*LHdx+5g`w&U|Ho-2H%4k3N&0lBF z^52a$-^j2e`6TT3*~|KUB}~dogZ^*)HYx$@sv+$i(o|?RSF^+Y)h%)rCN*jNuONi) zAh%N4OV1^o`__JVl?`JJVp;S0-E=n_HRYjTpBIZ}p|KtyKY|TleZj(YI&UGfkj;tG zSC{H{AR6}cJCHsjU^T~@aepJ%ngMbR_{U$b#8Rz;{~7E5|83jkRtlQOvo|?hPqbah zPDt2spt+?KE0RFc2k4!)@6~=p6T6i&$+ZWSzLJ$1&U$5@#p~)EYa(g-+UlVdKHt9} zr-ZwVM;$#hkt+Z2`e=LF@#R+CF`dNa4kygU_>BZd-p?Vw>AV_!eDbew1a#RnkvFEf zec#Ve`fp^BB@$UP#|pWkd?QBq-`1oiRUZEI0&LUrIaDAf#07{f{CxVzITak{Og8-xzcn+3Fw@^x>my@YDz}GLyl=L z-Qmp>ccAOXHhml`^-=8t#y2nP#AV?uZcO9oa3a1s}O5cyZ@&gyE7`UG6yTgMh9N;j3q&^m- zGMj{KIs&&kXa|4*?c|e{v@mJU$seu@*W|8EH%BG_g>&kg2qfu0cj^K{dqN3VlqBVU zsY!R{gi%CxVu#mA#`cS!{m50uPmS39pp2G9VtlKY`=wL~;&_{$fkYS4swj*w8!!cd zUYXbG%JCJh@IJ87yr{B!<2O!zubS$thj#!{=X{`H=Mo{)*Td?gRHzH4C1i^J5iM3e zL`dx&r^lXsq@y}QaowbJB0TmeXd<_KZ)Oq}IVd?~G&0#A9a2hm@Z8%X-|%SL#=K!? zx9;E;TX^C2vJ6ODd5QM|6zb4QF;D;yMg<3#)VX9m7md~c^F5FK!fqu}F>F~X{M(|7 z`ep8Sq;RA&|DxB7|8F)CoCHSLctTv$xpmA~uepL!HH9wXs(i`mlOy%Fm* z`FFd4Sm0l|fJT#sQyrFpZ@*4;)~^czNL>EkYw{T!%BIzr7fQK#9;I6Le(1(;&6sv4 z>E~~XJG=->+E9*qK3R;KT+nqd$TrQCNQ*@L<)Y7b#OIv%!((a?ZHh9mO_l{tSYy&G&em2^)-L_bzMh}^=oYV)3K# zFxSs4)A|{GKr<=_J+Hv!*Rn@-2x_k;0p|PU|%lC)VJeyO1(3dZ(;PRrKlp{2vs_uN+KK*_hXGr#_ zY3%fptbYIbJLj?xMhs1)@HFV`w2$CnaZYZ$9cn@$_9r(7JGGvT-Vf38rfFW-*kGiQ z1fXteASmiK1M?Q3n z=(?dIVb7lER6}rl_}ojXipA4(w$^nMpNRUC&>O_^KB*$4%+{C)%ha;WQx>*mpoO?y zOp>_SorntvFoy4uV>2N=2W0VxWg9!*cOb(d$R6obk>~Y1K!Ti7{gR6S{B+lh>mA5| zA8;ewUNeUfSf~!;tw-}6D08B=zOb~~BU$y6Q3Bn2+DdWrA?@L8V@c!`+-(`hl+)rS zM19|^RzekC?pFE~8!PHK2M*Hr#@YDj1K>p)W;<{2iO5mq>euKdMZ>O5rp(YSQOUni ziGyRV1l8hdPy14iB0u|mgO8``e9fV>s;Ai+jhAz3e=t8UZAH0-t#vz2p>%atG-bCj zw)#LHKN?#dOmYp0GJK&{rQQR7@Fj0PxcCU$BJN za*68OeW8h{2Tk@o&oNKNZ&h@h7ENKBP-iZT{Y;3X?Kr z0$vjs1JLi+z+`3Hcx7#|{KuT7#Z9Ai>b^Y+DX z>9Y^7x5dMW{RqNTVQgeRG-@vTU=@{9qKdbY4}4?*Jmh6@CqB_Ri#Z~LOV2=V zFt*aK^-*kw(AG}xKs(uYAdfqcu$lOuI-xgIe-u)b_HO=xNkJwa9Pp>&+yJU7hiu)? z(YOq|15tg$1OBcDB=6(ehla2~AD|}E$P{pdWY3F_h~N@Fw7}nPKc$ETAeN*f^Uq$@ zNp3MY{pU8xORVL3dss$+VxL3((dTt!zYj=u3ko)G*(6L#ta=VZb}0a1LY5sfGk@kZ zgZ;1_IlP!MKG*tMJk{HKnS}ie?6KrJZZDM}J?oD00iiRaXb!A0Ty$oqq!1x$V+YF{SFFFc?S-#0ZDU>d16 zCIxYiIwy*)FCd=s26+{mBb!Ozs@-#@=RAV-W^`Mf*3P`Y*3;|v25h)CuNMI3IRuj^ ze45+mqwnd>N@a>C5ZF#(G^kVMh~9J^HFa!)aVOZu~+c2S}^tt;t)_ZI`b z2b2&mt#6)-8?@mjh?U!fRc~`nbcn-i(LA4|4@7#pS|}1d*fpJn*4?Uf*$@t2nlF{? z^^Xnde*r(3?G_;!^E+6N3AqZ?Ea4`asH{A|(3$!1lspkwltkRbuDbd0DaU;TrXaHr zQS7rJ`G?L-+A0)JQ4s8sd7t0J4+A!UK9>%I+dwXb z8vrp(CFIvfuv1YVWZxj-t^e5c+h~Acv~G}-E5Ow*ZUZ4;)(%AP_JeQ;UlWYN0CF@fu|!q@sFL~}08O4|I?Syqzu0qawlhh; z&+t-a7f^Yf0I^8(_y$1iX2J35C9Yr-0Gs9N35bJitV4y?W`%NQ|GLktH2v29zsvu# zV_D$U6!_FC5lOLWVx=DQx2F?#`0h8@kLx~L9kAz+C*`Q0yr*&R|xtE3j#g+xMR8j?6Zf%7p{vV7Xs@n`NZx5qx-vc3uY)QeO zKM))8tZ>|5GQy*$)g6MHj&7EyZZNCiG5X2~dOww&v;)8xzS=psC9~33aGKtLMG14< z29!3P+T~biE}E)cYG+A%=C#kMvUuNOD#o5~_m{jFaUe}R_btQ7y0!f*@0dp5mDX7J zb{O{5(K8Pw5oLJ9|JlOCg^#$@UgIc}8`(#O&JV}~Ic}t7@wI)aDsg7g*SluO-}Wj_ z_;_vwdHof-x?=~TPCe$N<3O4^D@6@3Z15ICWP94?l|uT!rn)L-o65@6JB&}IEcGTl zfhX=vP=ePvawS8m)E>S3O! zSfjlXXi**gM&<|ZQew>1`1Ya9q{AxoYmnwd=^n2f4D9RR?N!YhLCScY59?dQO^HNb zRF5z;7`+3PpXv*Dw(0b*WltvB5bVuo+8UXKW7E}4>5n@fuN;tXWFUBRu8I{bpHIwh zPfU9UXVui1Ytfn`WHaX^TOPc*$43}b@WY1iSSfdw^$5(;(uY6{RHAn6+HCA0ZfcYRo*w zarTW*gfrt}#$G?Jd(!2-MsRXCex{qOi%CY|triD{5J($Y8YvFrDpJA|KAPbRu0`5v>?)_=-%(RdM4TT9Ewo*Lh_WHOYW8QS;e?^64J*698A z8Nm=S7q3V7(qCj~TK7Zdv>jUrQ)W%_&D3Q3we7_8hroIHkc1Th3sWhkkG$rT57`%q zyPTfl3UjtTUw*z}C+#GG%Wi=t^R+VG#5+0j7`4%xbC}Ln_C%S`@MGSS!RH(HXF{j? zHnN{e7^0Laz-e$MC`oCol8B3=(Q30TFBmi2G{w1JHKHTSd5o!)6524qci(KKv4O7F z+1Zi4A*r%Tr7fQ(#8@*NU#cnfJBUj!&1URW)^9LJmr-b@W_@eFR`iGk)+e->HkLz% zq`{#gO+fc&StXXo@DGw!K(eHo>LA08~x`GGIBQV|)TFcc3c*DxlCf5uT{@ z!n6smX;mKpN?%Jp0S$&U8clyg?x+8(sE~Lb6Ofw?hBxi9re8m`QvXH+Y~cs*AiL}f z*V3*UztRYWKGJ}+cmW;oPWnx!q1$gT)}Vt|lrDxhXoir@ndjR!$wy}Ki*;YePp9sW zolD7PSE(Y&V6%<9^}QiscN|v-DY$_X(IIp&`gKWmZB2=bo^D3>zMtvi%msTo6Hl~eGxdmyu{V% zPvD^d0FREs_fj^WyL;JNT_IRg+g_1j?(CO5mjkBAF5P|R>44g82OQ+P~b1R(u>>;rY6-@iR5ZDi4z zsl>s2g7?0gjpPZmVq0Xw0!EhrIobM&_>us`7Xd_%_vq7M*8p~8_=z3WoRq#g^_G1( zx8#48_ClA2x}zQyH}ajAx_UK+={T)&;kvDej6j}L=VS``HAmyixHf(VGR7aSe0v}g8T3%bp>IHOjPJ*ANzbbp_T>)>hULpzVzGCR#;WcOPL znC&}aD{fq`%>}z7zwBTuA!f}dwr~+$yjX?1<}9fy1@ihU%2fmb$Gz0B;M+-&&3P+n znKUUekGxix`!KQg&C<&=xW3LgGCer&(!mfK)LN&fU{l0HU-bNim%`|mM`$uyU9_|K z_oV_Fi*GGmmgC_4nGFjYv_+X^kR`gGXjB0}qsWu#FzcvGIaUCJj{d};d;kU|Em%!7 zXT%sWc@yMtBJ3Ec9{{oWb#o)Jw$NT1c?!` z;`a`dT$$_%b+ZGnOu~A5p_ffJ*<2gnsFR>M&Lt2&HlX z58-b!2nX ziVS6(&wNfR`c+klJxL2Ho>GBEc6o!Zv*0MCoO?S-J>j`Iy9K~mr~K_k@r{jC@8LLQ z3}t*DluQ%PcLdTOhjI@GqPy|L%5Cz0-q?DfyNAz$QY&A~$69gRL-&MIZ6g5i%!oEP z?OyM{K{NEVo;noM%2yv)UStSj6Mv>*ee<1waVQNs@WfnwpFQoq{9Y8wWMcjErVn(- zbe7@TncdSdZ==!XMKz6c_bLQGkZcm+Tb4RKs`VAh!~4`wi$VCKn=N2KIAuJ{`&JBi z2h`kjvPW_^8+lk6S?+AW8!s%)WI1E;X3w0Q)ETW8YwwOX6N`m3q=WNs2Ob7j=f6^v zK_eeCpaVD7Pc&7W-deMOp=!`@%j)O%dugAUbtYbzgok5uJ8wR|VGx(`Vx509VuDMx zhp8>P^B$_iu5Q}{2@06VBdtoY+eon{48(Ms;Z1Ye3%}+XK)9YvnVs%-bz&DEgqS`j z296D+ys@h*!j~rda*Vo8X_JE6@XBJX8`wHydvsYU%bR$nyK!|bMxjLEo#ajhuyt&7 zD|rx6xCx^_j_C~=tr(`tV=~s-AI#G4j!XY5<9kB9UI~A?p9}oPy4R^CQ_p*14|D_uskNvq({6FgP2jBT8EH8uj!a4owd*oFQ zbQyegAy6 zlHUR=Rq_vam?f->RGCM}(QtN|AUVHEK@|k0oDSTY>sZl@?v*+?GY@*Yu;+Yu&+1rd zyJ|v)%UB|`(+@Jc>}|idv~@b;d+p*hU->akx#5OaHoun9Z8R=6i}*m@<$xr_^^$j{ zvJ6i=ZSug|#&!KW>N&>e(C{^tsk9mIvtqlK&k|0wtH<`BB*lz@b9*0HErFLzR`@P@ z(i0sQ5Y1yk$JpPu-_Es-@*c@QZpn{VrKL)cq;jn0qWvbjB}snP)HdvL_&(_Q=!8S1 zFn?jbZ!%XW$A`Rqs!zc_rH6alHYHk{oelE9U|q1=xlDnP=PtibwkLh6CP`oaL?<7 zUW&Js;FR$qd=RzCL~~8>4(1o>*b^%%D-f13WtL|G?V^-TQmLp)gjHq-^FXJ2%#Obe z*wLR<+nz{_@6AO72z6E)FsBtRdf!(Zk@xaFd~P6#DqQ_R)K+}=AZnUinuMSZ!%KOYcUI-Kpe$6=f%WdETZS4rTNL_OY$ zB|29%S~z9>8#HaJFtLfsHCZ-oj~5&Bq_A=x_XM#pIu5O79TSX{jeD<{FY* zTlyW^w7(Amk$~w9<$Qd-f{Qj@%y%;*e;h}>%M{^a$`M`hj#HL8r8@7dPm?gk2|FW{ za7wfO$&Y>d6m5@*Q9{&^;}TS+oD@BpyV94D6V4yV^QVU8?@e4~XbTWj;j+exJN~F3 zKBM=-TG7-TVe{E+KZ|cXtFg_Tl_9r4iv*(~FJ7B^O|0@y5b5r#PRylwyu%j?wzuqX zBqMi1U0*!vKFe=4rjQ@~k_`k0>!EGR%9Tq+1`DfB3Ex=9^S%WslKOsaOfXXzFnt#B zLRIz$*5?rYU}4a_@#)5cODco025E@cSPS~;T1D04hn{o_G@tHu@2X@8?xJ6mg!W30 z?5g1RDpXWIo2!&YED?Cc&ndEKF^QO4F=m#XCJrXI4MYr_@oeXVq};b?TYE zP4@yNgj#$|b4YCTX%Vm;6g=z9k@ohsF(FF!c6^9;aw59~#?U%D#ZVf0AW6vbi}_C% z>C3~VjvzBsx5PF9oN&@JusPag)Nrzz=7Xhl;?iK4iL*_fzV^3xl*FXqSkwd=ZaaK( zs(3$*ma|qwze6M&t%(ijn0R}h)t;{-IhFM%pC!tv3p)_-e$Pi=vC(>*TEj0=fY_&b z7ADCh{BEQhKP3B2<>iTSNDtwXBBoD0nomkR68&^y8?hZjoLR!n>)5@R+*UMsw~s+U zSuG-gklpvF`bbNpy387Vs*Yo5dvW9!^FmjuL`h0>^y9bbKPcyAvt{x63fKLQv!Ac?lYi!j?$I++ zbW3AGf@aoK9d*mvuK6;n)`eT~`Lp&Im|7W!J0OFCF1n_=SA?kj9}i!~9;kho)@9aG zLYxyD`~0rJpONcL_S@9KxL9GCRubwVG&5;u9*_897+1D64zitlJ{V8Sz>pQybl#hg z+PS5W%GNY|$B?pk3|Fr?xBL9*xstHv2lkGdxL%Ab5@YV-0db8%v{2CQdI&>ER{T?1 zEIh3@{W&!;M0$h}eXezf$x8N(0g61*qXti*EkGJ?^IHTC>pFTDU%K>oVT^1dN=Y&ePk17im@E|J} zEfVKM4!1OQh2j7}{@;>pzto%jtD3-MaN+{ql=1VNh%&8{vqnHWuqkmKVe!(`?bSDd zt?Mj~cE~a3mc-c(puG6ptcD#~*ZJNDH+N>>SZBa^BL*Y$o`6Xn1E1d6RoAA1;XjQU zK8@}k#J#NInbNnf7(3v@4DpXzAUdIWue`BNsa62_f%nF984A;OF3_kk+Qpwe}!OM5KhTCM;r&9pqY@C?|P~_&VA#5}w2D#zsV^)MI8BWlIR=Vq~#02nOdwud^*< zN~XruofIFwGEIrQ8qG)QEUt`tfSy%f10M-J%c$_*x{!a2yR1#2#+s3U&ub}bH>K+2 z$8ZFXIfD_%-{tfcG%`j2m8l^7Zovpi@#V>Lth-*rRqfoN0l2W$5I2pt{P?4D?LN${s zG8#fH!duX3vG9pfNVQXhqBp%&c9J)8V2g-1G-R~*J}Ly-(KH$Pq8p@ zUh<_}o*tRkRhrdlJ@*(kb46w6AzO^tb_*{wr930(e8(#vl$u*%@!(#-qcnwpeq=eQ zyOF!;h9wE3m`zYzEBvu1-$T?bI!I*;qc&*nVYo@~;}bCHt<_(N(AT}t{rK^_5y=&oKhcmT7e^4ZvV7rn zqUME?<3sHA#WKR9?-?~p{G9SVpM+?GR&lwsl|5|Q{1HT}s^#6N{rQc!>pf*WvlHlV zH#bQMI+i|2A&4;{)zuvy1g^wl+64Q{Zav1B9CRO6x?+c)N&_tW=S3dqX61t=gPZkwE^{k|Y`+&ui~pEOt;=6R42K`N2B3ml5-!oi$czoy=24 z>&nO|nA=~53Mk6<-u0TDrx%XQjorHTND#W{W@gsa4c2s#epj=05lF`AoH~X120K=t ztTl}a&CE7kh8W-v95RG` znb=2{(?3=+i7DCv;BmP)rmruWO~a}2qL$EX>_z07GjL?bT)Zis>4xPe-;&4Sg=z+u z$e{P&z6K+^@S&s!@g}oa7Vj0s3YvIiKbvhsB4u?v6N`UnG9%_$Dmqr=IzC3&MSohi zrP~`^5;26Q*G`T?Bl)@JYV}=8KG|Eb6OA(ko;G()DG$~IlhNo_hjVZYU98qhNN7FN zCrwm2bXq^fb~;=^(0-)@&Q=mj{no@fgJmpvWhn!#R6DLcqjg088%|VfD?VRwzPCxo z6mcgHP`ey!HK`+KKFN6bv`#b}Z#+Aqk#h*st*>3I(2^IMnrb(4Low{bBJP^kMb*GU zETgTkb7`*{b>NlFj1{RRY*nlZfuFv9r%iE5_xi=Itmp?MV1bNsu*Gh(MYLk82dVSL zn>n37fHro`PYw=Qipi@?01B5e(ULS}O~k#>}KA%uhGQhZy` z)Uv;CVxpwywUoT5<2Uq8w|Zx7+@}-`kFoHI9A!SM;J#v_06Gc=Ucp2cZasePMXL*c zQ2YR%*>uTN|1&89X>rYji~*1wg$M$(u3##0D3vdZnlLm``p50Ec1Cuow0$EaE)^@4 zu0w&OOXk5JNtcO`JCKSY>>(h*KPu?Uj4GA?>T7n+3&=Qyhb6XI0FFJZ3EwK9x> zsAq7$Cuk52iA1a-M%&em9{#z!646N+$GWH0GKHz+fO(@ypXo z{PM`!AJl!l^rOgjILh+=s-?==mK#pdw~q8*hUsUf?U%tZR{>AvOo^A@kU|bqEz{2k zfDDiH0+8X!r=Kix&_6>LpFFn|ZG@Yp3^!rPwb_174wxc8B_9`diaGZzZ}l_-nG9{f zAk3>B#%qFflWJZhcACPmKeTzkzfS+?iZ6OynZ9!QDI>VP3qWq_snV{uz{5!VcRWls zgltnWN8eROmGjB)ewV}*MuiQ{PFs?6QMJmfp)}cyJUI^{PS-J3hNydPS%vpALI$PJ zs*Rl?l9nZS1^c^$$uRiK`Ir3b z(|WWCAkX>xoG`)|t_g2%Too=m4tBj8Rz`2t&Bw)fR4c|G#p;W@rU|ZEYA1>cr{E3d zQ0XVWy6&qd15&cT%wEwokADi6Yg-VEg}!2O^B-w;WwJ9 ziqUtIxvR}@%VOu{OgAo{Gbao#ofRdi+hrfsDq(%gg4x98c*^8eScQtT~Jf zrH9WUt5*Mblx)~%U+v4>6z}YvWtRk-vs-TgCl0*C5m2;w`KyCaH&i}4|8ze)R(ob! z^Fg>>#OX}_hm!&Q%tJA$|HlTY=?<9BHZZbQKu+-Q zJ>kD!E`aF<{C6z&|J~gG{tHVjzcG&RC4XWoVh^d4o}V~rIh3!ynW0lR7IqGI);lw~ zEF`>sHl1x^xBfB0(}8*^K1PmKJRCt)#NYaJguNWE;Igip<(cqr8(_Y*!+W64ido|J z-5JL%ds-t>(f<@h6&lA8BV5|y2J#h9dUe@wSHgkhc^8oIcQ~iI*1m79nH+f;(-|@c z#Dsc`On*|bKR?x{0R0DWPEaY}KvP_wmjGc1&ECbWFBrB`oWFldvb6?jOTUDuy`t+C z2r-cjD66WDrx!t2H|HWC>9|Mzpfd-&l>8y?c@?zni=!Q~9Iw-}F7>KN@mD;fP=-9G zB$?#SW`r_|e`e5{B5DqPbd?g|K+4 z-5xoETQU2q(%k8-?P_-5KvKqmKNT`445-fY388F`xf9LO?89pg8}6T~OYLg2^WR+^ ztnNA(VsARl!Q>=HrXv@A=Y=x2MXB;ZifQQBw87gh*PT$SPYJRQ#AST9|R z4Yfujs$6RLC@2-lj2%M z@92ZnhbyzPj$?$$zs|Fwmf<=+Y^--X%`MCs63g86=nQU(ob>$)zCf`Za5-GX#?BQ4$CIULlW!yzCbNF!a+-Q6PHN_U5JN;5pK zYtFg$(z*7Y>+G}7Z-3`}$3GY_e8(HlxS!{a=en=!z*Kc8Jdt+`P~`?cm}f#AXH&v< zk;P$8|2WQHxIJeADJtO>WInhS(vvitA*W1c(ftf9 z+0@cQA+{p!g*^o8;H7h44{y==mpu{dCQ`kuU1UhHf=jV`CTu`U!J-8FMc4&JC^xR) zYd!=hsR&{JB^2sozv@ojmIg9-3|2fQXKno=G`TD;2W<-XZy?#74GYU?#qY6MB)yieHf`?!wwsp8y@V>7~F_*-z89BF@Ti8ssH8D0ooN%(z&{fsmj z^CT{}fI141Pqw!IR;>SftoH}p_`zk7L zpK~3@?iUY8vkG}jvz0?sRs};7L)cO-C?f90==v(;|Ez+ACNx|oS*?!PJTHa?^@{8AXZbw zp1M}(SI`VJUEGlzg zTt3#uc(fa0$L(5m)3)pn?1)ZUmytD1oxLS*bU)=}D9cS#J9^@KWoW?Qr80>j|seo=j&kV-+%IuV4c|ilF-FN`n)#c{0sOluQ-mK(a^ZH z>EwbPQKjt_A7Z#I%l-m(FZ*{m8&HN*^1bHfLEpZ|y*w7a%sj9mc0JLbeMd4!2GP3i{vSENO3H zwo8{B4M6tw9{ST?ke=)Ba6xE)J1~_K&8%%ADo6_UCAH#`BNB>f!1CtMs_pZKxPAY5 zssV(E*E|X9))&SO9Cd=8V+vf(oMY2dKwm>dY zR(p3*Nc{gRHD%(q&h#tXwtZL!Pz9hUV`;7lyiq%rEa|uR1I30-jT32$l__7w0E=1O zUE4Ht+nj520NBSud<)$@7CB3Le9Cq(vT>cForQEM#|O>{JXIUF6ALqa+mwGIUT#7D z0Ir;#k38*}$x&?M0#RZa^H#l8oRr%%4*`|--0Kg{Dk0m}WdgG!4Lu`N$Vx}++*r@g z>$K>;%+1YN(YsOeeH4V^$%y1W<1#|+C?sAqSYX4Pe~v`Yojr}<48wpUD+PIm7>;VS zd_!~dNjR^m~rf)M;xT^p z#fczR8K^Ps^kWMcR$lY-G;NflJFhw*KH-y(K+ql`DF#CvH$KXv$Fa|_6Y!cgs6|U7 zI9`46T22YP9*%i7j%WLq=;jCaTLUrQ7GjD1eo2~9^eKs`VX~As!oazT%1+Ut7ccX;vE3f zW*7hAdCJ^@Ry;sR%p1UdjwafM86E+x#X#S{6##wBVV|r%x9G662cj+ufJr7EdIJKG zuVXgS;)rv3$z+3%R{yRCLVBseI`QfP@RGCbHPm#TZrR%3vbw z%7|<~1?EG@^q^KMH(reDzI6Ei`-S-*rDk^Vn>jcWRO!_{MuIchHR<#8u#8WobRi zJ4=&@kyTV!Apog2sYKLRCJy(U7#PxB99_wDiES<~VaVJtMRCZ9xU_5?#6@p;w% zJc_C^YC?nKX=V>wzHvcU{jwdckSsj)=z_bRUPEKFRo&PG4c+Lc#&>&>c?-06WWp); z@AYtf+RkHPQHyOi>MinE)z%zcDWQnX#q6DgneNN#=u5x$T!XNTlhQZC7{Z@%HfO%I zW9k|jNJ)PtXCX>5UHed|C%>p@`A(lElSo0Zq)?L3O68R}uY=M7f1C#Gwlezt9dS&u zX`o5;Ju8X}J~dYFM*vRV$}U(9FKFU8q?(6Tbo@-5daKjb3mEU@(`gNzX8htI(76X?6*6$tPgS-ka;PiELv@4fSwwZl&Rr}Pm`;>pt9n$( zjoEu}>>Z0+5AB#&ro9csOTF*EO~At`;AzRGfSG73EsR)VofpF`iaZ4JnNhx>RX za@i$f!Lpr&CMEkHA4S@4G$z*DM}OhlwhnYM)eVf?J&UVSOkL&pST%~jv_DGxid1G~ z%K$N`dUDkHE#soSum3( z4cR+4wG^bOnRTF-)j=;Mgfl=?D^%xbSXOMKNjUUwW~VlCVU7)|WYK09zCI|=~IP=Xr>GQD-7#~~ss zk7HngU40)4z0x&7&B)E_*mt;4Ex>Aj{wP3xk1uk@yHNuTqv7y7l`t39l zHkF`v!m>FjBxPHQKih3t9ou$k;`~-o3QTpwp+A;Qm~;Li9uHEPoPjhAFEKU{#=q6- zi5ANe*i1n)62w{1^S0*g^V%qpc*5EJ7aw>D-lU*ADYe)F1>hK|W3-sfo$J7y&DjOB zeGuHq(UH#*J?Cy3B+dWH7Jn<+k@tl#=I1RVoZZMrFO3~>Y^-WH2vf715tg0TV|Emt z+jNcFlEwFcCKN)C(TxlXEYh#kAY;^k3%N1gs+l zhK0UYwjU-vcnV#+c}WVrE?E$4W0^Z}zSyj7`{k*3gB~cN&l>pTx~@^+?;W)E^P&G@ z)4$H>U0^%ymy!GBIZ6+K8&~Q2uN#2>f_uvPVI}`J8_EA0qraX=f(Fanbv&AzI#1d3 zU!<8Qy9ra9#8lsl*@HVTjp!i7_mPka*>+jb9G9hTYw!j4GgF&%UnVwLMSD(?X4!#V z45SuvqanESUoGC#xpju}kbFQ%H%6@SM1S4Pz@4=M(#t+3t+1?=`6K={dy+! zdy))hs>cu8e9+yK-1pnB6R5)4bZy^bH_F&z>Si0;3R?x2Q6Fta@2RvZC?0T}%zk=x z@(>X3DdmN8`fwlwC!C7F_CuDKvIoT$6I1Gb{u4O#7aBec3YDWT&8o(jOsZ=_y(`3h z)jdg8@^ls82WBSl;&Ki7Cm3Q*;b zrUPHwLfI|gaRFpC;uIhqR2vM3B?IU3w0WYH)dv2K%{di(E3|c&eq#gntyyTY+uL#- zJZ>U!FHM#to{AAbzm(dCZUF-CWS7FfD-rR40R9*l5F@sKyZ9k~q^EY71F|Xu5n9v* zd5aa0P=9Dl>t3LFj?(0NvwSvU*Bz#}x)sEEx)Nkmqd&X5bw|2NV-OX#^QJEvsol;2 zjn(=>Vv#@nFE3Q=4x_Mfx|NA(cWif^4RyQq%4Oo>{jN!HxlEa{?XU@ z%N_n8TQRxzaGB~uOPYbg&Mx#WOWPDn z)-&}AV&?>ikc@z4U+O~NIxjt4Q%)YNX&9M3+-mG5MQ_?{0`Cs9rEz@L;0alJehD@! z`VQA6>%N`WMLV|Y2$X_kfJ^9=-Jw0M$g-)}S-kgo zpVmz#tRy9@j*L}_={)6Jz`-ZDcm;QVv#m5plcH^JN4TvxbgsyK`Is~|8`>q;y-zA@ z2VEg=#_Yy@L;>}Q69@@^jk*BovNhZ$1V`236UM()pO-6_df4FY#y5uvGAsR_>o#wjGbqs0Q zCuhrR+XnhY6>F!$TkKPdMEDOei!M_WwHYdR8iQp{IX)q?#=sF!ImT?KrA*jxG2zw- zc!Bgra1@Yx7)MwqY8ct6=q(v#Zw8wRZyR>g-dIisFH2P?Z3-mc#Fd{iO_=n@2s#v2 z#=kVjeioGg?}tPTNrbK#=_NCG*>DLoWY+jG=$Xe-nTONOvuYaDJGg!TFSv#W#v^Vt- zG0+F39GvmE@L)Js|21X|Y0g+huQrTmyU+4Ga|eQ4(4UYAfQ_o}GW4ts>QUh4!gAD6 z9MvROKw+etvpyJe)w^IfY=Q2mdyFIZ&}-m`)Fnb)$nT|{Ib9+0B${2at5uRK%xoP#IpX|7qLOHbq$jk2zV{pM&!8+tKv$@l?F>kG#wzn%=ZvzOuYDXqw3 z+A|)i2BLA36*{=V;~wSRP9Z&ZQtTe8d7FY$Ml4Z7Lz|GK#j?z8C!@}>%R*W3ha@0R z4E{Eal`!T7hjJ4HA4q?1Pl+I2q3z2-4TBH6-V$a{zJLc>+fk$euAVP}+q-vuPH^_g zL{4l?f||vxaAVY`RS<97&N&=*QxR@4s;Cq19;a_U^H}_Z)vc&wgrG?eyENnq?i)vP zbyQYLsRyE_irObK2r2jE<9qpD?G|j?I2QqaSc7JJQql~#GpIS{F z+XnZs?`JqPBZ^axl-t?x@IJ`8;bjsVYH!Wy#$=f;<*Eshf30@`aSHdCZ~grTGF&7$ z)RwZ|CPc%OAnQ5pmL-Qu?g{hzuO4~^&Wf)4YAFSH3pQr<_!P*xxZm$tpP$b?7mcRO zGJg1qh8mBi5>dF()ey6?uBECuZu9(L^Gi&Bw3}Lu2DRi1df(SHW@FUL(GGMQCusH@ zam~$sUdV~g9=D=TYpNR>>*Y(FZZ4#rMzaRJyd01vI(hU&sz*gnx0&BAK2Oh39D%l#)g=zWouZEi(t;>4&lgdjoe${A#ya2Z^1dv65&EK4w~wPQ6a{P5m0*p4 zcJ>FOJDTpU>TDn96yAwmb`*oCI!W|{d0`9Qo1#E{)*b}&F9Yk$_8c)mT1RKQ2<40J zB_-4tcn$nRJ3aU*zH9R@mQRWwedO(>g-=SB0)fp8<>jr8pX0Te-ss6fN(_tmn|!gE zX?al)8c+j?q(l$v$ox6q?qT)48F~2>8^?-_4-Y1a2>(HpEqn)62axTs$y0LS#J|A5 zKqqz`l@)xuSA@4*aPs;g56{c>l+z^dRzm*86n}|6^0Q)H z(X8f=_3Gac2As)_Hs*}?6Y3C3^F}o=cE$;>M}x9Q%|_3b5WlYGHry$}#AoC0UMc-h!f9f{Rsfr=RM`}fhZ=YsHDRVlqm4q z3$_?xi==NtR9@YXH~9!p>^FZ|qd@qyc}ys5QYaZL*QgK3F?=hcl4e`JI1dn6 zHMC58Yw~iw8I@l}p3vALj^Qbh_R6x=GNhH?-l$4f^0%p}jpR8nBJT6;5cb5rkbZ$B zTT{9npA^suE2DxsgA>SJ-eF~Ll+0T~q=dL|rR$S(cFwM&qyI?j zB|;x77J)nmb~|$g79v7{;JS%kuiGi(l`zQ-iWbm$Z!2QGHktD2*zJstU2L*71E0I77b+^Y98*i$*G+5wWLB!yt}!HLS`&6= zY+Pf~2y`8&>s?JY!f&d2r7H}taO>1Fo3Pezey{=lZ2bW4>u0B_r>@f9gvjw5E8@>l z?Y|c@1}>6Xw|V65dfw&q-}MoxrEQ2^gj+hB>`A(iKAOy%bS{8aQ-ur3^}ALzX;+o^ ze!v(=6R)h~MWnXtR^(9MVF_wG2CA>=oJ&eMfN&|eakmGk^(8SS67 zVFMWY2!II59v~=H{Dnl+zVq-3H|qy)Qt}U4BsIO@J@pB&!g;)hb=gMZio1Wr%6zuf zw=$9=w_spZM0OWYI7$HobnFCWt;lPMG8oKvH?cRb*20>UjC9ZY848M03ewneZ7&wF zE+f`%iqcUoL+-c;z)2H(&Q~ZPK$pK?rvgT}3 zI!v3;Me)#%*K>XmrH)D0o+0}2WI_>emsS~|rK6-vP$&Opq8I8x=4|Z3sjS`ZI^J|t z*1C7Yqe%1;uR9#Ikn-doy#&wy2BZ7OC|u7a*b!EH3Tp!(8Rub;)mOMXo&;_^EJ_mu zX+Ou(lSlTqA%tXi?m%#rIa%p#@}pNZ>W^O4lI5i<98Qtzqrdg4S}EPFLZ{4%umMoO ztZ>C1JQXlYRNsw$_F!Tntr{R#|T_BbYd(H1u(0c`x}l1Pw69i}KM7Yv%z0 zFPX$TcCja-ifl0v#)vDb6a9)kX;R*5j%Ey;*Vzg<%E-nQ7>2TT`uDH&%fm%jTt%0o zKVsMtc`e~B2z3)F`|W75z7Pz0cF*gLRS8H2y5mN1yD1;VM?O;}NRH&ascz((a(1Sid&Yt9JauVg>UDK;+JvU{hgHE}>^$jBN2VE2 zT*6}RXQL^B5!IakrQKQWHA+b^-xgdf+?2;kvUbU>d@)VP-q?N#m{Hw|YA9mJa;ssE zQ8Zm;-+-o*O@D6SL8lEPdx3OVx5oQW3~2{R3<=z33-T~3@xgVvk2Jy5ce2#!5OL8N z8XOgMpdtFVW^D~2Gk}nN%&H}c&pZUPA&wl`OfXG`*hTOR*D0CNhEa*Qkd)_vjtpRX z4|BC@lYLjG*x$VyDEL&#IMzpBWJb;rN6~M1kusb+fC(Td))K;q0 z&|`b#fLU0Lqaxh1lFi@5pkNU48QujMj6+Y&+kOdAAadq z&An9wa&TRdn@8Ji0MoO;PfhzjFS-hLwB@mC8so*)$=r?OrKYuYs`%bvp)sG)RlV-@ z6}fa@);v#RKf0FTq10I%Ec8S~waaMDFzEJqay@0L<;pu(+2G*5CMoD!gt^1%^$C4# z1K6B-)Eag-Wb`||S4L*txv-`1TyHWA+lS~TsqHsrEV85IuS@e2f6%=Z64IRuo9s6S zx2^DeX=?yV4FATH_Vd{y%r;5?lP6QR(x|B)v#+7+8kvz?R|=|)HUD&_b2p=WwY&`O z+fNArOPQO19qlM06KQ4P{hCCHIMN`uv^5Y@>sF0{(!7j#t&o(4Bwd$R*4_1!QVpxY zq#TsGv!a>{7FBVJ(pWDUl@jjpS|5K}i|Q-zrdpk@-lZL@;ear5Q!s%ysz>coWH4PT z7&FFC*xHHxd*qZbXABjY4mexxOJ>oJ{wO1N}5#5G2OPR=NKXzV?=bqfRYZHL6RJi@egUe7ieU(OAdjj-azgd!k zZ35d$zj28H-5@*{T+rXB@d87HA`-mpDeW&6&g>6tf#Te6+;q@S_E}Jf)*t+A`PNy} zS3&>!$rPP&sYcSyCmyxO?J73-0lwN}k+o2$4|hz9f>TzIU6MX9Y`o~%SFz=g&zG1R)y{NS`8;`+SpM(TaWYZ4*fI~eXu5DRmsGdld`@HO@mmg)`P+w2m|C-yi z6p?uyWxw}hJ8dK?SuMb~;^#>Qd%OYc{Urqsi5`o*Ln2NU7fio_)E1q{L@{rZsQ73v zYaZp{6IBt6N4rYe|NI%@xz)DoM}T@BL;QERXxmZ#Uf(R^eoqW1)O+K#pFh^+0x&JX zh~c6c(R2`H*_<~pG-5*NK%1Hq(43$^20k{rLpT$;#U1z#*T-cSBJ|!1u_j7wzm?%T z+=sLvVC);9S5G5;zGj^yX}^&FY;V;{nCs5@`7Xe!7$_vZfM?;(u`z;;#X~1pfPkSz zOZKd9$l0aSrh^Q_d6Yku`BeBaZt7OS_1g%I4W~W6b7Eo%sL?e9XhN`__poQ9q>JVT zbQ7j*$r&A^Qqb5LIDRq|D_c>XPKJ~DgtI2mL>S5F{6Wu@?bh8F&7BpcM8kE(JcgRT zo{?qKS26#^GdHxwP2*^@!p98a!1WrOyrq)4h)Mr#7V0uaPL?a}353r-nX&R0(h$5rj+}tyd3aR1%BRV16yBhXfe_Ug=DrC8e+h|jQ#~YSlJu39c zgmaVIcxD;gfnpUi9&%V)iuAF4w?|huO?tl6V)_*6)~4X<0jZZ&>_)J85*u0U{kp{H zCv5}KAsH5r#RLz&a=ExdN<$AnfPilU)W_Bo|#>XDke1K!1a%l z;*e<)?`HDS8BcM7E?0b){RC>GO{|C`oN>)Uz4H%n{I<%TkQv&(a}U4%YBT5Z&MhEq z)GPRdXy!vV@=r@Va4E(-R5rs9!+6O;PZSey;{uU zoQgP3y4!1kh~y`1(eAz2@~cHP9zd%5e)AsL%x8(}9f2Hk3U-f=$_+2VmUzS;cs~3x z0-x4;1}1jk-QJgGI3OOV__)N?)#J-4skEfPwMVBZJIhAS9u??<@cLC?(UO6!FkWyb z=q;G=I46zBIetV&oDwxtHpIoOK3_j21?^Fp^oQ4uk!XB4ydFU!QJcG~VTNl;@yhsd z4(;a&#+du+A3^2`v2Frxc;0ZGC~kc?y6yvj7#CA4%HvRQtZ6JBcPe$5&N^~)-Gg)@ zr?w_O+Wa#=coYnzGfrc3VwCVRC*hUBrYDPN4b81b!>K;`!!h~>?Ii6pq+JGIiL42P zI3{bYGfNmKiJJZZky9kCn8i|d8(fD}9z8@8t7l2k4k}EbN)I4-m=Mza4=5aUX}3 zYGv#!+peq;De_+|9gPA-^;s5x%5-uHKt1UJ)vc_3{O@q5yP)rIzD&SY6=3F-0L1C) z2>8*)0K0Cxuu~ypKjRHquccKtCY6sysF8XX9t31mx&7JvD(wM zLs1f#sxkyOi6gB7<|~&c!=Qckeq{Ho*rB@yO6|~mxL5LYR8&(@x|(d+uPBe%De1Gd zdZY56AmvUjye0LgPVtPkCBp^LijSE!V$L;qtoh$jLZ5QYJ2s?)jL~!4R29@NW;R!8 z9%;1Dm_x8^>RypG(oaRt&KMGu(tw}#AQSgjFj`lv97mEwTqNAvgOpfKt;XpEgLC~3 z%vqej=pC{}H@ZjWAyTuXh!XTE2H{T5xtD%n!ZMK3Q#c_&F;j?^->?wP31)ag9@arc z!V1~u8VNZ|NXXPbbV8Ck$v#PJZewLhz$tY-4UGw((2S6>aGQ9-l5cC{VFfmG0|-S5 z_-aGE62v(r$-H>4q{Kpd?@<+lc4HQ%cpK0CuHQojzs463Np4lMR0u0^Maro&yM|5o z@)C5}ZhO|GsoSRH)mv+UO8?%s{7Nb1md$*d7BkLxMd z)TYfoV^V$gnj%{1*#KDrX~k<1vJPX#smdP0oyi&0CUE}je)~7ewUjlIB7C6U7wwx1 zoo2oOr~ziDjNEJ<%u>!j{U$a4P-Y9^l_0jEg{_*CYt;VAv9sBeJf+-gk+E;QyI%(l zDe9YVY^)oTx_6^7G?TNEzZToGWk|qVot4qamUN-l z<)Q2N+7ao~WxG}~Y{A6nfG^D2m_jqoAC?mkHi*x|4Yhv9MEABkzd0&((4d@;Jk)dC zHI1Sr_>10Tne>$ zLAS^<@`xe*in8Zxud{-6A07RHiZ1u?y(M=@$A#|u&)b#y9uWBlGtRy#ow6a)2)ONh z1S9mkw7C9cA%}Z=D?v&a9Vm{n)K9&l*bR6Sr_7zWC*40je)w4rtu^Hu&;Ulbk8UO- z%G*oqe4e{}QU89*Y}igvFTV#;Aa*{$#`tm$4SOI=bH%J3JR?0j-5G*YN4`wRB&g;h z)TwZuP_mk690Hm#Q>Gv~e0V?-6Z4qd*fsj<4EO7tv>=-2@uc59eGIlS9AI4Oz(OnhB@{fn1+)o{LH1Xkh)3LE^@) zDqWsc0OhV+pirITFj->Z9s?6%gzi1d`wq9Q&&6@6qkI|ll~YC1i9}50RtP9#1JF0< zv;iOqHkq9dBL&Ln0OC<(j~Sr;2I**AD%f~SrvEi5(iT7Z0Famdu7wR$p#QE21Sib$ zt>7E(wZBpP7ifu6e%Ux7T=!oT>3$}aNIn*TNm>JlU)o&Y8Juw@SG^wTyM;j4T|NVE zk^z01k5!*5@(A==5gYVGZ zff5RHi3P0+fWp+ZOIk*&6sVCI5Tki=71j{N=ZjY}Z zN?}Gy*Yk1>+g#X?*4a3lHktE>jd|FBs0Ky=gJSfH9}x^Shol&fYj(*AkHp?87#(r9 zZ;Th-S*40N%9h3MUy;U0ngi|1fa4ko2VjrCC5Y$W+HNm*(|^kZH5Nulw}JqE+#1}N zyB2$Zu!4W<_jG;^*ls;tD~0}^wvUjm{I?yZcj@vSj{2s9>>RlU;24wFpTAyfev#xF zQ=)Ik+NmO(==iwGb)Yw-P)L?*=F&AHlG((yR)huIE0H6oh2$p`TLp_6kV~kHMsjZ~;Dj zagjT!+LFgRe$9$DO)?!p&lBc*g!YfpWLA@KXW&A?F_!L#`7_s3+=4W(>XdEDtE09t z2R5q%zW77468hkYd>J8id1_6W5u%yNex_>H8>P1dRvvjo48a@Lgl>r+;qXvje#RZK zj}0CvF$Cn-SHBQ$e4b0BMJhI~4!;*L%#P#q%2bw?_~-%ni-IJrBZRvcSZCO9oq^zk zj?n7P?+|(hE^(dc1SbB8GUv$613(Dw>zwX>QFLDSa3Sj`L?jf-77&o!p5xHm< z<^_*#DIBZq1P&i;e%cvkumoxAS_Q0nXxlx1@YaMy8IAiiG^F5Xn*b@Qs8m^MP|lY| zmHee;liNz4^V(vNjt?>3 z{ZmBe=j&?j(LUr^+e-E(iw|SWmJ)q}{8H*L#M$~+)v%^Ly9N*Y)0Xg&B9=lgJ+Jx6 zE8Pv7W_f$!lWr#Fr2*H_$~W!wGQ0!u?&>xq5j zCN>naG_`Jragtr7fkf^vX5Jp8r$Ik@52T_a^2=2FdC-ZE4*<65|Kh_mwSq;~MtfkN zlAy5I4%wft$n|P$m1uwnBqR(Y(k{(tsBdVhjh<1Lr0m7j6fGVceC@z`k2>x3;1)h! z3_V6X`{y$`y3B^~*T1?fRdpyjbPWLUlbz!Ota~-Zg||`&BLjlKP)l zvg28GyAApBL!d_i=*0W_Y!UfqTw5xTnpZ>5vB#i*;wQ*a=nc@qSHAW~V)3`v6Cltx zI^iX|J_-V&KLx`KL!)D4m^3`FFM|Qhs=lIkB)2Sy2M$2H-%=ke2=Wu9sK=u3cQ_g} zK%}p3seiQv`ya`HPg~$&awGt=lYLukmn8JcWv5M|=w2(wE&FY@=K{bM>w8M^PjpG{ z0q-HAmgy~8H-0yk0a@9zV1X@BPIwXa z=T%wp^B+U}90VTI70)#_amw}g(${ABfb;Z_-}MRBheiRrts)$anc+e@?_Y6QmU}y9 z50$!m>@g+AE^f{YFzi{V*N069?ui%lv_50YlIu~eZmn+$95v!9F+a(r&$DgiQ=V?8 zsz+GM=+ZA^ANM9I_@Vvz7&#SPslf)khKlJy>HLzaxVLwR2zvlc!*_TYDEOg$<-uEg z3TS$&(d5)r>PX=_55ShxXQ1J)ckGXq3F=(1uYQxt107x#jm+ytKF5jMk_-HH+hgx}exJ z%Ot$KdLE}x+Hg;Hi)LDfG`JMn%dK<~1N7M7SVK=xBkq8_<@0P}VGyA(4ZZn8p*cwC zf&Loj9Xd=5h)4l=CryU3Ct4syZsdeB0uA+vcv>dA( z_ot&us{LLJLS^^-DVp) zjnqDRa|+0}Qo**nO{mBasU%BSYHo6=O=EJQ_CgOsxlxp_G%?WFYh64xL3Q}Pu<%s zyY=~t+caZ@OrZ8w08PN8Oty0Gj?)uTF~s`3spxG_vbUM(&oQ^PxPvVx>6FrnpM$EE z;!ckcuLnO!)ElpF-Pa=_y^$xy`x3os(LDY#GeRruU@)5E9jH0o{b)LlQko!&Z8Y4t z;;U|JTaNW1p{?8JxuyaK;?rsuv+xjsm@Y%VRU&_KKkE&)a;@o)`!o`8j-U4}M8wfO z*KVy$e4dsv7r(5Lpr|=f-F=Znay8#G5XbjcN>L0Ek-#o-i%8}uH|MVW)q<(94)bbb zee^6iQuZBA#65|?*(wI_dScuietZ*iPZ!*Ee%FB20Ur^Iu9or9YKIRkXh;E(PKt>AkFpZakv-Dt zBt0yPRu)|6R3N#!iwgFr12riMkVcH`ZVzEwK`V-*4%dfLV;_a{6a<1l{H5#IxqbTn zf4}SSfd5pn1@lY#XXu5gw60p7IIRv@|Y zpqZZin~Dw)+@_-gkfsn|_WmX)RCasm4($FTXtmwihFi?@8U3NeF$MD$1V96R3`;+_ z?Wp70PFDcLFs31}O*)4Nah$_9RXluq%!E`1lduvF0haX0T3-oFTgA3 zZyI#Oo^uxsz$l=-%Rs9**&P9w>(Vbr6^-1Q`2a|qkTjW$<~L058ESV%^(;jMn**ZM zqehh3rbg?JM-}5zN9=>fw98`d&?Sxl2h0y(8US~YIoW(D3Y1cUfXu=~?{LG>z$!pc z42&Oe=04kFU>fI8!1lE+JP(Nvr2<2`o|Tz@ZK8;5M_VLC8 zmNX9{C%$1;TZWpOGL35zZG^+nv-l0F8S5WPK}^uC4gfYz*#h8Q0E)q1#~5ZYe^sjW z-#NyA(=+;y?=maV4ekHk<6qe7f8#g;g7UZiKuqr;a>gbF#D(P3-{F>^cc?|o-{JJ? znrd04t%{|Fw*%`(?LLSF{+6iRsF;+@&FUdhS4~ozyA9}V^QdJEV0UC&C@uRmPNfdE zRkJ_=cU(tuTec%|4uqB-i$I1(0hWig(E@pHiQzlIv%)0hm3}BuhkKra8lGPVoD~9M zIpc$P+Os?aQT6m6afLhYGgKcL*4f*v%(?&VQqeDKT4P+;$rc+{M%nX@geUsqt1CFJ zVAjXmPdA5We^cT5k5-TizIaF;ur~bb z3GxB43sxhX*`^FSx&FOAC;E%-AE4#=a~`AOE!)^=z@sqs(kgD|o7YCA*V|-J4@S7G z`K%Y4cA^+@*bH$9R;!=gY?5xoyIZbY1EJcSRT9$C4PNU~7Sph)9b|ZowiS^hw>#7{ zGQb< z`}*>SL8D{70alu-dA|Gx#epvOheUtjC zhO`5|s12jaSp^0JEpuiK^~k2!*VHEEj!8n%2c#3{{B4&?w}6L|umYy#kp=p9I9!13 z6@~`%`BrrRsR%ppc1WRX9qYoJgd><<9-)^yY% z{L6!A5HSpR?P`}Y(dZbzw+ri$BF&o3`wct+%-A=;KrWqeyFV}+cY6W=HDI)@JBWN+L5GDVH8{S%vRT}! zi5%bT(gU(fpXDWDcg3ksgd{B^A)ItpA)gSU`M8i`qu$sek=KeL)_C*;eG6lsTjgpk za?GFU%1PC|7W1IqDUUUAtf`BsL!?3!T-VBQ2kN#Y=&)vEU|j(jx_yB5NdSprdCA2| zeSV4J;>b|h=k1T%k_zUe8%#e0E%eWG3u7#_kK8>*01T2E*SIhqOH`TVWG$Y1EDZ!}FE@die0N-n$Ubm-k+GgH zO8zSrl~0e;=zC~Ut#hv-gfn*SAb-Ozy)z4@{@WRr{xoENw#j^YqTPJPm!-@H>zI0a z-J=5(U-Pr5VQ_@Mfp!l61$)#K0C_s?4~<$JC?w@0RJ}l_d&n-+PW$=3=i@I~Gaf-T zC;!AOfe;D0e0WVHC<4q$I_PDgU%||dt6TJy=2&q=bv+zrg9Hw>XV}MZp%y8nzk##H z{%qTSKFRM`Bj~^K$06=5=3r&giSCNcF_t4A}u!DZGe5%k`IV6=`6jAD$dYm~)G`{I)p>P;8wXJV_P z{iM00p5Si+0h3RSNWV5-RgdM>>wyGl!lK@Lmntg39Y=WTziZGxtk_HcWo7#F0~zsK zli_(wLXJ8#>YLn&y5zk;V5_&UHT7PX5HO4#1f#F$5n6SUVS$7RkC;<)WvyGOj|oSp z>tZAm_E&?GAqd2HAD5li7Q-dy`IgM)0j`Cg9rLftSM!$Kc(cZewM*YEM5r_;OMC9F zkVI~h0;kOPlGDd&<$x&CfkJBXtgsJ5SSIuOW<` zgX&wd_B_e{g=I$TUcs%>(RniJ+0@gcxtSgL>;Yi+*r*yq79Nor1>(CsSy?mkPLmt< zkf3o&xJr0;ZV|Ru}r?#=CV3!9ddY z^fuqH^QR<=N4=FkfH)F3!yxi)2`CBsc5AV(zkP}Z8P=oPON@QXzi%{GB$MZ?mN&r2 z$S-IoY+*9(ueS&{URtB{SZ5@^KKh(CY19L*Cy|=H`NidaeYc{}juo2fim)l!UvI_w zjr=d{#QLw|10Z7w*e9S+kj402JPAIQ&zYMwmmmouYf>K#;s-likSsynWnl*Xs25#NrT2nrY z#6`&mkK>dxaS^^vD?S97twKZcJ_C@J%55!-j>0~KI{K5+aw9qI6VP*w;%S4EPu_~`-e&jDSFcyk-@O@<@Fnkj(bD@Kb{;L?a=4v38} z+G6g|w*K7>0#DkX)C0FX5pUlMKyMx|K^Z~-1%#8`iC00I@ZMIJ7FRN}gxsZqf~`+r zL-vdYh_EpZ=?#xp(Bw9|eVCm9xck7(G=J7QsoL{lUM>lT;mz~&i^wcl4?8gXszSXH zj{_nV84JQ`$e00ChyTej%Pdn{)ljd|9+2e*o_HU!2wU!5C0sR{i7<&U|g5QcXoVH zI$Fzd%cz3m$&yi`H>ZSQcqxB_1!#0w`YT1x&nLe--fup_H_7$II8AxnAQj z?nbH&Y5fNsvLL$5q^BVSPs3&jUJW@C>EErgwaPr&ay)rwCP5QHER+OuLf)*-ElEb4n|0EIQ80UIAPglqHKrfhU)8W+>JeS5*e9rZQ7ItOVH z=Wr)fJTX_v75Ax*i)e*{Y){$`Tk(kHb0x?Us!-rVlMi8Jowe zhlIgKicOWZb$ZDJ`!kqp(aYuFlV{uXY4;9wJ@MM^@?6jB@ObceC>I5?Wp#+06=e|V z6_M!0Nb1|U2Hi)xc#@QYS@z?u(rSzP&$d7cguD$P%OOPYi233 zN!lN7uq6cr+n1VwY^cQxzzlxW?*tj{!(%9Fo0E$B`l7`YT|y+|RmM@u4Rs_DLa5VN zSq{FOXt>5Pk20U3Oq*?@$A|NO6{g&dE-3(kvU%_^=JF{N1$&}+)`@XYk^MSC;iM5X z4eBD8YlO_V&)M#h1p18erXZc(^;z!C?a}{(y|0X_YgyJM0YV@UEVu@Dg1fuBE;P8i z%OVgYxNCp}cXti$?(XjHb~9Ya za(m8;S%z=s!fEK!M^34{ro?U~g5>EgB*7omtcIw`2P@t$9y+g$)eUoWC;0cI@cCud zD&fXsD@QGnyViv>;dtCd)jr+ZNP-_}ygtY{7D9~++3Xk#I~vrWFAy{O#X-E%kLpz# zjYLRU!JAt!SD1f_SHf9jY0%=1>cR~zLH1?(0N)mZ(CjS~dY16b-1(gvrAg_Mq~k%) z!Zps3pan~W4TYb3_uWYhy4_C*8SvarVL|iPg$$>$YG)ZGTlXfX$e>#&M+ z`X>u9^l?l|__rDoRJMp%5fW!1`|g7^ks{eN5-y~rBl(}WEI(#{25}5CzB-Q8hEAGQ z&|(SfWT$&%`evRf7iTnOqhHCotVauzxaLycLj_6gxMP1gWV-iE-q7faAoW0P{fXUJ zs@$$YCG@3mDf%K5pL`9imWOr$XbAkuPub!Ax>RoB9 zJ^Rq4{TM2~AmO45{(#Y1wblw3cxj3O!*d)ye$W(!B1)(;}Dv8SS??z&gGWQE>7 zCvOO#ltlX&#T@ko%8HEj)x7-+!YIFR(}p*A2xxsvw--QN4Aq$c z-`-x|m-28$_Dv!&ONS))fq;scg`CN@H(ELQM-bA}^2lO9w*#O4G&h)KNvKUoqGiG< zzh#ED_%Q-Ufa75>TZ~GMw;9GVbx5syhEwSYXpmHGh%#e)A&$-`NRpJ(bw!_5%vmI5 zB)eHZC7}&A89AYfmhxJq{HB8)&1Kk)z=8Ajt(wqdGc>!|r~{paO3t{kALc7?PIKG{ z%x@g@J=Ud;%veHZA}jdCm(J;-j7)_WDSdMba=DRT*{$?+eJL|;Tz)oH0I3WhlilZ3B10B9+OX$-y3kP zSD`n+w54K?C^XkrO<(2}lT4gCwCqFpz2;2Y*mcmUz3$bOeXwfGI%U?e=VVox!GaZ3zkwH{w$!6rXS@N|2PBo1AGLkaYa;t`=}xXdE>9~d?A;uDinF)5BCRb_Qe3y& zNyC$~MGE2V4?-JeTrVt50FnmWJdUKDC|9L!R?djPCMg4(EZ$0!Y=B5vdv?Hfo%M@W ztYaiS!f2#ewG%)%M(eibg${67wXx>ruMFjDdwoSlksxEtPqT$U^YS$N25hpEJq*o% zuf&xV82qRrKij=6h)HKJCRqp*W>w;tFj;(L#A-Vy1hyY`#*CH~@8+NiKJ@b4;HrR% zezm~auN%O3P6aHm|H%}VbugZ=7V#Kd0iKlumfqug`&5V8w0(b~#QdE;>;Tf?{2qKY z4sHUcGN%9U;4?pB$dkF3i1)XdtxMelrG4tFS3bYN9+4GQL943p7oyM8NT@3a2TB=*p-3kobw z2Ha-XzWe(-y-Ga&G)f$s4Rp4x0`+w_yj4roQWfKRrj7?rzO3&PDalEwzHtMKj{-ip z!SP3q^#?AB?P*)tUF(6TF-wZ10;Mu$}(6Drj6e1fzjRT>$ml#@+fDr{l6=VI?@zWu08Sl)c#9 zk5X!7IZC0+Xbo2tE-~yj;+@tsTO`97xfL>bU4hLQKy_02gvkGIw^|cESEZx$t!ZN& zT@_=qYxbiA-AkABH@iPgOHGoYSS{!0ZK8@-@T@iqk7BiW1pq9bFSvD=3)lx( zP1Ajr%zPl|(!InR1;Y1o^g#BMn(hq6%_Cy!H5%OBniU?sVI$}R9Z53fTx~5q_B)|a zfF>D_gS=Qp4r^+ps zBvh0_QL+qYO?&O!yEz5Kz5g(rV!yrML99n$S)Nk$>Fv0L7s42sqk8Iz4k(&f>rjOB z{F~v~iRw{RteXv;6UEU^W78*E6N*scXc9&=#Ui=Ks~Fz;>vyfciF?E!A1DVrA!JsW zV!@S9tj6#|k3F&=BvwOgW-Nd3aAVovyB9GrKQ1xj!Ek#_B!q25+{i-kjABfg-R0JE z)t`EaJwk8b1$x(uenR|lLi}0owK7*u5%uLW9ae|n_L1?rQY4{^YCO_RXbj?NdDXwh zlc%n@qS%VcsHb#rN>3-$jF@t#lw-!J%o+?pyjOgyTr$;$N$BG~x$^JdN9>mbK+Y;t z^7$VTg7TTC7VL=nk?9PhL5^l`weCPZci|G!l<*@rWuUA3v>*6lHA z!G@9HeD!-HLK?s8oBwJ6_OIYWR$zy?HN!%&GX*lu8MzZ!+mQ^PcJ8Obr5_76qwTYN zd!X$6$D0_OviDK?%X)W|MXYBXHT>D?G&W=> zNf<7WrsSr5+&ebE-T1eT$-7^|Upu72eH!uKOz6~P$`GezUO>f-j+d{M&9tOa-%ITf9Z1?myX8R9TkuAnXYW)^_$xn4iA6~&|UHydwi%sv| zAnGfXhh)2Y+aTh=BA2kRR~?8>6cw+AvoEt_ekYw5*Cc;NUJ{`?6|&U$fN9T^uI1Nd_Lmx5D|3;@U$%$Z=yZO#d;3*wgKDji^)Xqk z2B}E^vk2AZngH^5VzV{^N=>WU4iLLxm`uNfr78h4PeoPHSbG#giR?RM?3nZDx3=vK zBY&KYf13V|T!H0z`h9EEdq+k34bzWfhWqbdPEs8r4Z!v;4O9%;&p}evDfF@&x8Tq5 zrsAb$_f-cTmI*MDNL848idu^-mHCl_coVyN3FY_~a`=yKl;6xf6@slQbW|~VaM01w z0(D1`V?!t#iPN%9V%Fx+}qWnj)_o z-ud@psBqQ5(=n?|I>Wb#L;e6bD&K>d`R-ozJU;-53|7i4JZzol*8$o>s^cZJDifZz zW);Fm7x4OnhcN8Vs86Nk-@^`5ylbM zkw;C}6;3A8iayb0g=QMuZPB&r&XqE=x_IgY|zz<+Wl ze$$4{HYExtcd0Gy-~DV>(+}^6>&96iZQLO910?h>)J&pqX~G*n=`AbP2q;Bz9Hbz- z+Do~W*j(WxU4?w1+4faurGgbIvkJm0EA*d#mv%=xIsF$(Md7Od*J!dY5w~MceZ{5{ zbB|mM!KrF(NM_)|A~pTctVTj(_-gN=JklX}tnyU3L3@93cRrc_%!m+m0ZZ_E0OlO% zZDZ@C=#MksxAq8*e80WWZ`$&oQaUoR79%}Qdr9p@uNXbQ3|$4vIMt7(TiSHzfxmcztosB#{vPg zoW6bSwc53j>LxGz1a_Y$|%VOvpX4_F9x%M#@P7y6x-xZKkK)&Bmm$7Jqeu?)o8@_B6^VOOhHe zidjA?Vj2bW+0~Zd@Rb?DnF!p;P^b zKHCOZi+Ks%Y0Z4QCD176g($DTNEFJQ%TkjbD`Tj#L)ZYS_Al^GZDX?*o0x5wvV;Ui z0e3s1?i0+^0Q$JuC{&E_zeLh-$ZAyw*l;#jT9S zji>Zar>bN;^7`y>X}P#14}zu`q9uku!0ghxrmufAb+E9bi(=`Uz#h`>4AjGb8*1(^ zd?zy3*WrY#Cg-ungMQ^S)lhk%rX?bEYG^5m#!3%Pc(Y`8FkP{FYqi#^*B}g}gO})F z8P)^tzt?F1a#>`{D*s1z9`-%uubdf{!?L=;F46lfSoOaBGY_xM+pq7h)*2+{gb6$> z=%FaudZ3OWjtGNe=So$p#fYtH~VYc6GjR%T=ecZXd5ViMs;JPGPBqP1V( zE!Vmn#;xy&hP#%koc07Tx5D?hHb^T` zHYJSIxs{M4ZN->s4-QL^0BK|8%yI!bxMSSkv%^%E*`qeXNvEu(O|c}uzK?+nU}s-F z=g3Uv+(kFxGCPZ3-srdwD0~XEgLh8&u$N6%CqnDK`6`6{hj8y;V5y$@Qf?Qe6K2(e z6?wtoaA4kk+_Vh18DXim<|D*Gu7c0GVx-6V=p zpCep4pf^hHvFc$tYcfIiZ4;MMuI9WH!m(9Wy!x@~y;0o~JqqdvuN~V}Ut8D9cRD~% zg$^04%a0d%it$W`a66dR#Iq^>xG33XBMlfV;ipoIhJ96b)Z|twSF8(KZl|SPJ=4M# zrnT-}*f8u9gU_7{fhS0T#N5fB^u}iQ;twqMDjVGa^&cbkV9!cv01_3x$XeW@l6=~l^h`55DVJo=F55b(>>15 z7!GC$WNYe+PpMeFj*tw;${*i?a@mX1A#aauP|WiU+%hF{s_qRrt1EH*69#<+`ImfH zYs6$-FZWbwy>VQ1=C{{Pi%2@lM^HLv7^~!IethxFqOIYXrw-kuj{-leBA78SLSFsws%gJO&XQIkpFVdN5H z*QTSA*CuFCuI**lluwFAK>6+oY3nd$WIz*dDUv{yvt#zKjDp*t*UAlw=Ssfjzp8Y& zZrswq#WGyP;TRSpX2--$S5jJXWfE+dV3C4744L0oGrnK*oPip4e%$}D^?EkTiWoE7 z)%gumJRfR_$fTx$tGhrcz39d4;|7nYbclTdOMa$GNuC>oLt|PLUQY}7K++I#3 z*vk09R_Mj8Q;(P%klWaiVD=%o>qZK?x#cQN#1UjKO^V5?68kj(lQ(NBjIS-3_H%=0 zSsT}g!A_!4^0owS(%E(;69WwhGUlsu7e=67C5H{n?N12BGrln91rINl_oFAw>{lOj zgSqoPVOF{7KJ!O{Tgd?mC2k-$lTZR+;Ee#VZnP!> zJXfW@*>&R?;h@>i5xDj*g+7Xs=pjjT+l^?cu|2i|y8>XP>_&Z?r<2k1FpFoQhT12v z7I=5MROQwLu$tNtPmbw7A;fsxplT6Zm}g|=n>!=8-eGDrqO)UhJ7}M@AIT}jD`aPY z@EE6bBuLI@+361Z>z6_|(*Ppn^d`?|6f;$ClWYK_JMO;rUxDE?bl>uAYSjVBizUEv zAbSG9)qAo7`A7B}&+QHA&k1tcu5w?Jre^;-GFsR9-}F(k&PpExxB;g?A9&?)oCaNJ zAR)@6TlX3rfH_wE{%de6De-v6bL^PV>_lqqQ#HP3Ay!p2A`&<~oYZQP4d=~;> zCapEPk5M|H4U>9&(zH#}nQ7X*f+{4fDv|;A`OowT#Gk(QOomI88fzfW#c69K1VC&? z6st8&9MPLpCGSJUgCw|Tv06WsiHfbtT^v%}Pc{-)LaOMSI~*6=9Bex*_+HL8pnA4RRTgC4}}B_1sXEvp^^rzS+LsGbd=dWI_zfncE#VQ37Ybeti~ z&NFf7Ow*M5<%9SYWi`|%=*_VrMX9AY2}1AO%gc9O=Gsv?I2crKvr?=EE>3PM63=*U zG4YXRe11Zd{CX>~-};1FGkKh-Ow|eA{iPeTm$i1n#})AdtCi^!wR=8r6Kg*HAuk0tZt6 zW(5A+)?nURx`8vG(C@WWeGHIyE%FoM2@d<_>8S#cDu9~nk0Yo#sqhcU39Qf_bmKOx z(UU6LO|FSEiNrqM>2=KAJ;H_b(0MaUgIS;zdueYFx^{gG;XR-ep7kv zwMt2qCxj%vD8?=?DgjA|BC6=R?15J6CwAfFL19IELe^Bz$MYjuOlerE9Y{PKWYt;YlFIlU4GHDywiXBAE3c~nyhQ=ta*2p|^K|J!{pe_HQ=~%VL5)YYr6~f1RbbEtn zSgWQoj$x=6O5B}l#|lg88kst90!(W)2}2LO89YB#hR-`^w1jH!iFplgvYDru3T|DU z+T;nTA!WO_bbG|U@voS7C|A5f(|^-^ zfbsfGz^8%zbAJ3wm8XBqQ#nAa|N5dScvwOt@zqcB@&7qL0j$q&)A>&mv>L)!9Lmt? zzdIs-Qq-(+7&6uYpcD9mnwq}B7-Sm?fqhlV)ZTl_;^6<(*;@lriS)Gk^;Pj4#oGcT z_T6o9lcBz^_h081Cfz!--dc^?M(uNcHszu!-s~^n@qqyPm&FAW`ZqJFSsiz}@hmbB z@c_3EzSrh)TH_@%QPca&BBmRt|GoXhnC{kd%u|~uf51Hux8C#o!!F_#qS z!a?gl&^?!TqsH&7 zl%O(~fH%<#;RG;{ETnl>me3hG;|YX(km0$~OY?}M!0*IxYC#0|cZ zqQ#IZZ%RHMkg2F`OnEXK{SkDsvaQDm#&$}J%9+#B8ZpV8TNtr-;Eoc^8&AIu!m{aZ z&^E1gE;0NzJ-A`bQusuyG*PtD$$FnW;$a?L$scsqL*sxixn?_reRMKIvZ-QWgS0JnJfcxgemrMEdISfCY<+HnNS`8EH9qsfSl=X5^ zT^qMcA?v+UTy5O71(&JkH(_rr$wsEo4am?{lU*vFEDvuH`<9)rx5Qn^&&3716S{N@ zG72S-ZPcKGjxvUqge68a*@9(ui|GlNg~`h)Ye!=1Ub|;3X>m@t?kwdYM`xmI5@{Qx zF5z5`CCe+46UC)FEj49T0oUH7b>V0lBmhdzr|;1$IXp0HUk#QwGkbPv*w4I=r$M}* z>FY~T{yyd<^$TrkcNpsGq>Y+s2lLW33l9(in|h_hmA*cHNmqu2N*t6ljzRRxRmvXf zaP#WnNGEz&3H{fBlC9~TLAkOuicxzpMe6c?bu@)^s}NRqFPj$*N8O?%HhWsosBx^Y z`0XMQ^=$d5!is3-*K#eS9Jm-8#SL&mDBer35A&15)R;e4ZarzG8@tJ-6cD_76gW+5 zQppV9W9(;-IF+>01UzCgL!sS|EC*LQTsY#P#?d@Rpr z;nJ|wQt=I@DC+-JI)d?}NhCvEtGW8RJQt)z#wNltPE02%D!{R8LA-Vn+{9zGN`N5X zP-^yJ1U(5z!y?@@?`@-61{FN=90vHDfOE6q2yPEJV*@EG{wR}fAZq6_#malt|>1%4J1vB`lXyvu9sUW@O3*`P9Q)scC4yy_3v9T!Gl^4MF z7vE^|SK&W|V1L=f_WX|ROY07>0s$OLz)v}A;9C=NO?d`i+B`e}t}aeyn$0(`@fP?J z?H2z8(D@Moe7go?t*4yyztBn2o(w>$5Ph;RSl{JFX?@clQb<{oE(-YtyhmHM& zfWzrrxd6}!;FUS>?Go<9|7rUYkZsR!+VceO&h`ug_)7nQRr}Z5Hxd7332x@D+CIr76d$-k>78$2}ZFu~BXgfhC z%ReaLX%82%1a7>8y_4>P-(`FGgXjkI1%}x}TIJeT^7mHWwr-}o8a%f-x874)d@u1> z8yo33fhV~42MVq3W|sss&8MSsJC&)$J_=9T*c3m?LGDLs%>RLU>SQot`KVU9 z=o*z`J@CF*B0G`9!YO1x&8TF%&V z`M_034<|-K=3KLG?{2lHlkgOi3pN7+175xp4`S9U)o#?=8<^dXCYU!lbYHS*W)w@3 zK9V;6gop@4-%Dy;VZUp|o|x~rli2baW>TN79)eMd5Q={|s%^+pC`oZVB|;`osqrib zMT%-*$7`<^GFYe8l$uPfb~V;uc?csugoS$b%`Ib77OWZVmlj$6c{ zP)kedeO1`_@}mFwhVxq1FdI7c`MK>2ZU_0@G|cf!kHVBf57V@3x9!xDG?ojo6k;wy zTc#>>_ILGbR@HCS9EO;^6wephG`$uTTQVIo&1M=J-Rj7P?>4@GtYM+BMoI@{r?0}q zI_|FvJ!}fQinLRy+R(vtEa&kS75=AiIit80$AnO`;*zx=tiw3kEIVTArss?>CkL=8Jujo!h? z$exa*RvE;^(%wP5YCuu&;A0t88!e*HFm?jQcCGL)ewU?HM_szvP$CIG{T=GEdyZ(X^6R?=4j42U-QL(km=#f!6l z);D}?`Ddcr=qt8A0BDrF-=kOiiB8So9XS@BIXRRW7Rf-BRc?%VuJ$~|vAp$XA8&Ff z+Pfh)KY(U=BWfMZDI7U~d7~1{+GE>!%`!V4y{)R38tTDS=&@jWOtTZbzAR8r`lX`~ zQC{p-p2a<0OGW&)hBa+Tar$6wLycZVr+6DsVGOxV6$*djlj6v#4XlN()av;j7*WtVgfDAfI zzDF5?Qy&1d?9T_>d9{Y%nJgl&XW&@iDE)G<^!AS(_3J%=PJBYRzj?a<6%}y@^0$8L zb!KooB!H29_Y2zSI}5%|=FKFauZQ`&il5lV3KeWj>g9raFq$htHf2MaLqw@DB{z+;Jeu1``cZ){;uck|y{*h%I(k6@psSt(&Yc6J-0{Do)Aqb;R!0Ovg)7! z36r>6t3x8@5k5@U{@HZ>t}rJ4(>7M-sDsShy_&;5OCc1!CJuWg3vt68bL_@q{+Ie7 zeTw@R2S&A*;(%CL^o3G=`0^ZQV_I$*WIp*f$Y@u9EMfpus0+J*Cx~9#P@lYhwPqI( zGLk0Y`{C*(KVlsVZ^{}Xf?0x@Q_ii2SY^FjBTI9vLf!obc?Olg9D9n4cC%UH`NxB z@;V>e;=Da$jaKwv^;n?R63h9@BMW&*>n`Fq;8T6;?H0l5WEvf?>gM$WJfdwNMiKks zN-SZ6;2z7Tx?=**i84&+lv!Jd_D??`R8u{jfZ8Pz>nHb+yL>mtHqRVlzqtbt zuDJigm%%BzLi_;}Dkj(p;(xPx{CyPpkG=r^oHMdEba1pc(zk*G{lBSLy;WaMAO`_k34t z)B5Cacf0U&&IDWG0aJy~9Jwp^xuK=`Stea@<>wTrq1!P=mbSE&R(`|B*#`DH`}wn{b_Cq)e0ukD2}}}aXWNd)95-D0H!7K9Y+v*6}~EWhNe-^qvx!7^~tJvS+9z|6)q0Ux6wwlTgu!D|(wn zd8S^hZ4i_hg>CxzNWDKi;ANO9m+Fnjag=CiZDqCxtALsF%wC#D>pY>v4E7a@nRoGu zNV3}%QS`CNwMCG54FT0vGINuu__zEmDzEF%Bjp|uEzxcl#xbO`@rws>#?Yg65n}Iv zs{&dKgm$GO9p;*oDsPDmWoGfs@jZ?Aq@$*-#%B*vb?GH)9SxQ8X3bc&7=~-KpzDJEG&VO6RMd;zF zbw|Fs+_O!=xQXJ$Lyti?Cu%+FnGA?e=EYO>$PrA#h{{P3jG$ubeTBHuWvXNKh+;~n zTSUC2#xysY&&1`yG>7?|yb+HRih}^=X%)tdg7BNrfGD=oYnN@9A6CH02tP zUzikxaK$X(nY=qymrGU9_gS(xR-Y+MZ%~p&`5?#DqIB=-(oNx3)-&4*bH*aqk*`um zCUAmR0jGwegm)_aARap#tbC@e7` zSzsVS+#q92v%pPePlwyD?a3>PVXUkp+k{Q(!vii>t6^^r1M!N?o}fVvy~f}RJqa%c zmY*ttH(f+hQ||;5wnbQ)oQrdm-Kq}VV|N)bsII(X-z^unZh!j`H*`|SgC=C8^s%F{ zYe6%tB%eiBYLKlc)*PKBjK4;&fuP!?U*{|3dHZ9}9zJ6iUP=gIS0gem)bixR6tzeA z^B&hb$q&NxCs~9)Q=Hn|QG(6y1-s`<(&*~uO&(~o+);2>!#OZAFE2~bkMjmTrq6tI z7=1W5z3j~EKZVQBePD1fkaKOQ6kFBbHkNEa`34*HATIoJJ0S7={EY}bL2njq!kq}} zq0>=Ltb}{Rnzdw^g*V4`fGrNO(Pv7sqx~CPWQ<7I`VmVV7Je8DSp&GP@RGSJObUkP z;=QrHSpO_)J&T!=R5kY{p(u!e0$>v)foUIFk(e);X}9!a0OU0 z+lq~FvlZZ*C`?8YS9viN+<7L_?A{y(?<7OG5PM}Y8Y^O2iO?Wc$|L4+ zBlnxzv?2HI$qF>4(%VMGq#AQ1g{-!3Y%j$V(;2#3%I5XXMN5RGA4o~Du-Erdt^mEF zGULgM+jT}A6izslVOHYFVdSo*82=&7JW~8EP4hsFHXoj~jP)>HZkERA7a!u01|hLzQTFkyOksAe z1y(eh+-$m+!=Q`-Jl)rFDQTm@^jU9)R0fX=6apQ8*tdtACO0&;Oum|5)Ze_2heiCL z6M=v5&Lb!M5zA6ZBzp_?lLQ972LU;v(Akwqw8T8(JB&E?Z zqB+k+jkRa-Q55oJtTq!wVEHm%>1&^4tqupGb|^Eod=@9f$x-t!biz$|LWP|=t{=5z zhbT8!1ue_E@VRaj9Fq05=)=jnw;U4<-$}}}%6<;KOI5hXvUM~0)B>T^EulNr&JSJvEQXuPMYG0@1I;pF3NMZ~@q?!g>zX;p@X=R?hl{I> z7rn#7A8WGWqP)crnnI7`)(Bb%dHJzYNs-fU?jM#`x-KA}(NmM0U-)2o-6oW8sUY!~ z&eqgeWbVz+x0F;M-|mIVsqI6-iH-02(F$Dou}cIyt`1>g}0BSpH7gcO=VJy4(V3@ z9`wamr4W71XLFXO^04k>j;%bx*DD1HlL+mzv~r~^nQ0gf5n|?BdiCAt!zu|fHK2=QLOoj@X5Y|G}JF_4h9y8WceYheEsT+g!Ok=4f z?+g#xBkb37Mi!JGl{-#2(h7;iS(Y?G=PPb)S_38#EemWMgbzEuSKYrtghv74I-nhm>_81q2{s|l+g%?%( z-i4VLlleQBf z<&1)XmNl)bPGPBFWEy^9hu6uSW*!ADQ>(EMUBGeE=jJa-2$Pv)D{CKjGNj|xh*~|u z%E=$1UTz-Vm2(Vkf0lQ)Wi1s4>D?RSE0AGL&ktr2Rqjo;8RHM>(^13>UK3vwa5nY7 z#z&-n$wR9Gvp6(%RIf*KG3#EysET}k3BM#=&aph7i!q5wBTNT|??1*8>Gd}mm|d1G z#{9N^=&jnK>v3-zd6K|6&c$5*p^|gyNw{N59VufEOAbfwvij1I7RqR-*qIr`xY8u* z5LoFUS{ABc{LqxX8*sVBnH}-2jZp;Jx&4M!K4MboVj5m1|JGw!ka{~LbX{f`AI|S_ zQaNqRn}u}IVK5ZZ)ZxPd2&wP{qv68Ndlv4=vBzAsge)*vq62)Qu4l%^Mf&Dcd z)E-B1zCH>QMb6ucYKZQ@8K;G`weYeTCzfTV#i*zQPKMeB$&v%Z`8f-zmGF!^S<*NH zmdwnrjl7yihh42VwE9-YCZ&3V?Ovk)6HiV4u9BZb0LZk!Y*stD#+GSKZL z^yi@4w=A!hLOhEuKS;1braH)3(b0#Q;QU8IlL3qgJ_g# zh7n71@lvH&)T5$v$B9R0Tt4(0;ZOLbNK^eAZ|?}YOkN~{RPZ(WKO3iwF~(=Sn`_Ec z?o_2c&!cRVM<1O#sJEiM5bi#8Hv+w>?j_3S<=^A~e(Yo}OfLQ)TbgpiD#<8Uyy?Qe zuTt-xjF6i6)*?H{gH>)?S&)hk!E9x$$^6Tn(}m&HFg>oDKK^$%{r)-c)-T zRW~8qG+jGOGc8i+$mrxJPDgr@3iY~#-abYts7l#pxcPkQKH5xIW86z-sg-2>BxaJM z?5INF4qa1CQv%nOOobK;`C&gTZRHCuZvm#og>RFJXeOjo#ezoH#89I?C~(ibFEgjEtsKl ztvd+^3N`QOul;f9%(?&k%B@JL{yb(|x#a87j>kX#d(7g9qvkXH2o&qE-#R=j45=`e z&7{C~V8*FP39>3^?4UJLRNYQw8v7PJyKOq>jSsOy5%8v|N@7nFb6N3aYmL3ppfcR! z{5%7Yh?solg<{19j+k=b-;8owy&c?G$->fJoXWj zk@Iix(=>M;C_G*bN5QY}LNkhvZQ&Q?*vBK~2E)uHa7nsybHE{XBY2HiBq1_tkZho* zAPV;H9cXsJ(j=GouYcys@bc6yWPdL{8_&@GIuZD&^CtVyMfjm=B4z$j=cJZ1aU!A30w7kFVd7{(3KMT|~$8~=Q1ev$7Gv+Kz z@oJ5++t2VcTXU)YNDOfjn+Q)An6k-ShcbLKX<)5*^Rj~c?YnB~SiCnXt%#us8A>mXes_)IA6AtYX ze}-4oD>Fy>tTvwRHKl(USEIl6F!~TpKNX^W3HK)Xk)4#mh$G`|aI^hqAJEx7X*{-q zpUbLK&vxfb;{Yocx};vfWhmOBe9IWiQiK410XO3qf3$D)(4m_?X0Wo;P32G-^J@wY zv3f|6op%A>=Pn#@^uB1OAk*1itio!;=EG+|xxJNf2}RjNk*%K6e=o0YOy$?hTh`i| zYMAkoIZl9tq9I)Q6;8^C158r{V-U%!H`$CqZj|=qdl8JY!Yv6ysW-$F?+bucnn-Uv z6SDBz%nESE!w>Pz*BvZ2ahkoz&Nh^+F!(5BUnL^Gsl+`gt!PPA6_`tr>I^m{!K}`r z1v-9vUk)`EbOme5Np44O7dcW5jzZBh{aQe0vQ)A8}5?yI!RCdMytjh_RvisgdNJ?B2Hp5=e*Z z;z~vB^Cx>7QoWcUM7kV`^IQ4zn^C?oGB5IduNa(a8@NR2`TiwGwEDl zY=UB9qVv#q?nMlSjitc?d4K?hGYVe^f-}5d{JN<(rS54=ql;DR*KGdsg$IMydtzN? z(2ihN6jGO)r^$d~Y+*6QaLBe)sc|-&>de9hBKX{V`AH_E_}vFByl?vgsBQ8fxupqV z!r2dE@33j^-o8>QZTjIy_ELa_PiPI)V!D1HM3)Hx%h!t9Hur6Ws@jmR@Cyp>Pzagv z%8R&T$>Iwah&bcO^w*SB_c*fyBk{g^B**3rljjPJ3FVlEV(#Y9)gv>IK?W=KG+`qr zC1Kd%WZbPC>S!Izslq54UO!*0JnJvUWqkQQkb-duA@uok;=;!cPV1+#Z5}^h}e!nub?Xt^fclqpLfgWgXFt*(=S*#?FIId zt$Q}5)i|%pCvj*d-Ezp;4p=f~dye$@1q+Xzohg`0IGf296yTRO?9*1MlV*T5pg;AXMc1pi49yNh zx;~$GNr(T^5W_HD{UN9>){|CRVKtjjxVlJE79;q-l{`l5P;BySuwP7O+@+)4k6= z=WO?V?>YB-?)$vY_kMTjbUEi7YtGT*U*q>X*RVH@ye+KgWQlA{8n+o~l&lH~kl_z> zk*w`+&`EkM+9)Y`@lA;4%hfoeciyY%>0w4?)1C7Yxv0iWJ@M?97|5sim)t|*qHj*K z5W&;#Rw>xu2kYb(<|he3!=-@sTXmi;7;80dh^WMyqke08kurA=ronzT*B;7Ob>mBM z+F$R=NyL*_glzTA`*pY05Q?!t{4IU8pM>4u zh$yp9@wIXI;O%%pL9*Kul8O^QxK|YqVw!FoEV;Vkfr#gY`@*$c7ugR%u4#?t9Mzte zgT7@&ukvyMUO@SNjne!op`3c9s0aM){j*2zMm$~e_Rn3r#EN4+cIG`+Ypo%oex(?i zUNlQ6=zNus^sTcv&Zl%5-F;7Vb>e!xn2APM*lk1o_-War^kRMi!ON6{qcPIN#OhBH zQEgG2p67k?3MJv{0il9lu+GNA7!#8*JMsqO~FHo_ssaM0;RCY+T^gilX!t?a%bP8-uQ@={7<~p z`izEK7Wt;h(=Edsi!%935l5N`+eUO1WA5V>hE$C`M|I|%PrFQ2UO0a$zFEY;fs%k*8vagNzSO8*ZG`6?~X?nwt`bKgLi~^Z;^c*4iF((!2_O4 zB~in18_`@sRo;kF=i|cKVdTb^d)YyYr}d=?_Q(VSo2^s*RePJIqXqE*fo9NVLyAd9 zX}#h87eRxo_~uqCuAIjeo5eOVO6&R6)?~@9par>7X zjQa~htXM0C=M1(^wahZ83Nfof*OoRA|s2^nQt5-0O3F6@SAd@#F~ zam}nlR&{A4xQj8gr4CO}lLO?xCY2M|%*d;a+d_2o&07|SY72?Mg~59+G%k3gZk9xr z38kL5-{MdlYlb~2eVt5Vsr=?>D|@;a%rAFL**7FL12ffJjgu@Ci%y<_Euj)+riSiC zwOaTteB8O?&KW~OSx>N8;m_yFZZr)CXalAun!|8S^x7^`kiVKSk;UcaBitx^m)G@k zNOm!Z#_+)t{q35bNvW5(;Lb{82TYYV(A23;Q9t$g^gY8#4Jlw!r4Ci0a6@t=Z-d|! zE*19TbaprOQx=-a?4ZYABR@|)1JhK`b=bWWds$@8Kg>aQ@u~ohwbIXjQ{H^_`S$c~ z?rahM7dIX!$3OY;o|!w?zc#j$1Y6mFt&Oc6s0640f9gKSTZ6P&56*GR@-$0cc3~a6dk+cuMsEEnkZY_!DgQ+xc%-knVka zO7-{w8qzx;RfLQMaUbD-{`sFe_@938Kf~aErh@++GvUXSQRrf` z+l%YLqsx?$a9X*@-~Ns?_sc2OVYsU|*K}HNmfx9Gf0;1KOhxni@ITGs>DWx!yg#bT6r)$~UV`7cVnSkn7YwC-^>8irVl&SK2oG*|ubf0a1hELg|bDoyf4 zFOzba1N)Tw5!<_KLlH9YbhC(v$lR-DvLv5$RWS=u9}^FTyX(ZNg7(#8qZjMa{z>oH z+O7vrt((LewF(=ZE-&QJ6wRNsFH>I5FJx1u)E`UMZIk(QpoBJzXt>-Cw>sXLdG5Qo zBx0}S=`9LwR}q?qZIfF*0)x4&n(vUzjWQ;WzRfSl@!QY)=kchmoVdguQc4bg-CKMg zeOcl-D;UzhohCzS)i!wN&C_%2C2Lp`>!rh!+_c_Z)GT;nxpQpTG)>9oZ&Ew^?s8}S ztJ*10RCe<-)=lcAVkE@6J(z38mKir09sPo}FIHM2|A7eohaqB^S2{9gT_aNuXM9GqP4r3&%zpd>l= zFlZb_`}`?mr&ZIfW_4l0L)1E(M@=X;qua?zGAl*-O5m$FORMsY1LLDl?3HdX9=8tz zlTn?mYHpmQ>g#hj*V?By=Joa@Go4*g3O*$l3I& z>t-0w$@?cI8jWbqGTU}%4Gm8N&n;L=A`_N9!f|RL6k_)YHr$#+Mu^1e!;((6p~|5g z0&a2peVR0squbk%HzeDlc=_Vl!(^WE8cBP{^5&WN>n4YrJR|lE$<>K+t4nDky6&tY zu^}VhZhj{X7f+ZZWiERpp6!lbyf+KAn$1xaNM zmn@}xljvAV52_}Edi>dQp5$K11+?gkS9Q|ya;sNWu-UEnI!AuD)J5?DJxiZP{}-mW z%?u%*E{r6)V{PYcNldR?6NGi*erC$&9rzp1mkxU|&BOZ}*aJUs%vsv!H;>dvZbkYO z;MgnMxhZ1vd=#T4&G!p(=3lrHl$@Pr*{@P1fi{x8C=Z?O`E&|9q?sF%rCo$}RZlv< z^o|~NdfUxoEuMJeT}+KU(>5eHtY>%@8W2p;%tF|Wli*? zUr2Xk8MmzFVrS!U6h(=;m+Y!Xcx=|tKv8r~_JUiTSGQuDaiv|j{zdfiTPwbhqO((AF-M%EWZ`#JU_h3rn*Rxw8Oko%D_<3*se?Wmo3=VS@{%OQ{T9BjSJ#Cme}$Pn zR8y;HpNNBbSvn6Nna4cc{4qu8>2lw6mCl$tyBcQOM6YNLnYy74*%#^dU=$U13-8(H z5Yx|=D-my>Vq68UmybG-q$P`)ix!5+d=)?JoDwcvJFI<2CYCb!jEHj7|1!CwVjR{u zDBZW|pA}bf7jxvk0(o8;qxZ2E;*|TMNGSOwS&5Z=hA;s=&A{Zw0#W+OCWO6{=&np- zHN@icn>*#a@db*D`B@5UKi^sZW;YUuh3!@TmHOcFU0TmHRmV+qH9rg!po!a=)!x>( zZj!zu-xlxCF7Tt3yGFgGwCT&b_^X!4`A=G+B-q*kP!8>>IJtqV%2ezM#zy7_&%iEJ z+Q2yn6(1)T6~7=Cl`a+gYXiHVY9t@e?+KNS?ZJ+AhQ{_(KchyaTpVQHH~@{B~KIgG%t{LxI;wb5imA?Tyl0R6M+YyUI<)^ZVm}{_eFM*zk=p zkT-4M|IeiXt<%^AP&R*4L7)BYTk>z;azAtUhd%lR{4=^y$(J>xj<6u1?d_+V<_=tdjgq)6&gp`JifPjjHislJDBNHPr zB`XIj0|y-gBg4;!AfTe6VxVC>#K3sSKte#m@L&GieFx#9Aq?LGAtF2m-NQvd#6`Gk z15pC`L`L}S1Nz$s;T|H8UsN=73``(FJ;+WM*aO!8=sh*nx0u+SzTM-*xcIQIX*c(JHNPuUS0nz7Xnbu zKR&+~?3Z%k0_D1ggoKEM`m!Qdb6*Hy8flXtgA3BIy?@yN@4r^GPdNfXhOtDlC=Ip{zdCz5bg&idh`TBv4QB`?$w8VAp<6=l_GXU_0p-1sINCpFF>kKQ~00eKU0f z$H;T&O-~Og=uChnLeE#TN2G8BZ$pL~0BHh#*L=q40&LFy4f+BAVfga{9mZ6m?8T2t zV59($M67@FA2<>K_7Vz!HIcP^_&dzSq=FRauj!QLpp@S)zKL9l5gyHPq|Tkl<$TZ2 z7>jIBSJmN}*j~XQvM9<+A2iPBC9kZk_{4dYO&gufX?RGY?4W~AnKYFGKmh)4y;-#< zLb@dSY}%SdL~z-?!0D!}#n5`k%ZH8mU2T!p3@fJE=y?$zZ?$U)Re7Uq$9Sz=;`6Kz zOJ((G_s2jo&&=~>Q(ja@#Zyf5EIK{I^F($s64 zBhOK8H?7^Z^4$){L|L6?IBGnEe+4^qfnpeZ?%JN!4^KeJVBzefEwGNH5>ASK)ixl# z`F-h{1XTOihySH_N|~-EaX}vGo0YkScLQ**SVg3JNo&D12vz%J*n*pt`eE(ykHW9= z^1i>jF^WOH`ku^d;y_PkrSeYkCDz6VCIHaj&)uE!*Pbp_9VWtW3w9LRc_vVldZQ8| z!X@?9K8)8ncOi~u1E9O)eKER%9W9pQMxsO!dq1hN&6!K)u}BHeXdAAfG1e;sqVIIo zwoeHmf1XOipz84Z02Yk=kHz0{V~W3$eUO1@F(9@zn}wl5E#;wtE13Pml1ORgZK*@>X{sN=`_Vc#>DjU}E+^{oI_;2ERo@;+MYE&E`HTZ_Sv%Fd%re>Pr$)vD-p zvq`J|?;o)@F?+*pCr7`u+cy)RC8?_&!N1)4zn$?NPcE`@8Da?}HVQ5kDMcLe;{9|C zf|hi^?@jQOKqZSSc4I7^edOGclC7sBzGCqcy;kJC-zYC<=6u~8>>-$8QEFaSTxnnq zVdMM3(xROlcQ)9>=txNjbyt{38{xfg(y^$VfWW#Ig(|p(lkIL=)>Pc*^pez_u- z(H~mmb9{AG)duFaO#aU!Ut^DcH{8O>TWL5|K2j^ z+u;;L#a2bKG!Q|eLZkJM5}oT|>aj1z3Y*BYqcey| z-&Bu|Ea19%teL1FS|+kB*+|<6ZIb6@Pw_;DLR$2MCLJ3k6$s(vz7CBqo`kM81xcM-hI<8w5PMcbxSR-w}@hpoK*c^pnE42d`nlltnr#-i|qZjNK|` z$f$~hSEH|}fkHLlQq)@^B-5=z{DB9ZXrI#&^pm1%=G&kG260ZS50uX|4C6-|jwwQn zMbG%%k_z!Oo|o&Q(_*e=c#ceOCofcPG?7B>MHZ0{EAGGOW;svruZh!U(lkDmZ=H=5 zpMP~LP-vOr4`&#MlkH5pMNzp_zZpv(W3%$f#gsvx3HK9Jti!}wGx}gYe}NydbFsjC zG(Pe9NeQ@UswsiYo4y7wBzNrtej|FBlhPc^s(Y6*4Spv>*H7X|k3khDV9%jJch|FAD>3kdumd(##!gblw1BdHCus2$5Zk{`x> z_7RFW#V0l50gw|a0L`%_R{xr@prMf2I%I3F z6~N3?0Dv=7@73(%z@^2BAXCcXx73xz9e+O0%`1I4HJcv9j7XLKJ5n>Kkew77KiEn^ z+%Qpz94TA$9rKalM#Pk_D#cm^Y}cvg@Us|U-LbQ`_1V18^RUTXfCc?r3g*5nWAXYBZ%(Ey_JA9xC{m^0V970$FTZ z$+w)XyoOO2s5FVXdAZ&Y z6d;kc3$eMY1^+bY!QygL+R|4T?K{hDQ|YmsW*@0XcC~a>j4Upq$Xc>R*e8lXbq?Zq z4u@C@AZ-Qd5$4ajl6_aYYe?z%ounU@HuDhihoBGJSy#UD=8+6(0hm1&u_R{q6yR?I z!ctN@>_-#bCX*LnEV0VR*ja{-q}efb$XOvVX|>LDee~-#2yZs9q^A_dvwF`x`3wfs@IYhMlWS)#XMXACXXYmZ_nc;u0ZAubI7txJ>k+cpy?0T6j`nVSlvRC zAtbx}LRHl${#o1A=9w=IVg}?_$@gwlIL%h4o+9!0*1)EAxOK@dKUWCfmtX;dM=;YL z+HqAqX1_!VMj503_KthzgTPG*(#}wOq4xIhRQ}iZY&_G!10wYaPe0;DdAFb|Hr9rs zqw>aqkJ2x1`8q6t^3)73u=YmYfoAZ&SDg&VynO%iCf4E%VO0@_ugoE9Y?XfnTwY;7 zq=~^O;BswPKo>{LIZ_q5JRUS|J64 zJ#?R9NJEeCM3LqEtc0r-Yvmm0AebpCl2L`LE;;p63x0pjhckhWDe=m;VHGx(gc0dC zOogco7xyZCU#S{0j&3-!4XcVTqXGipW)&c)ed=9=ivr+s*W$2?N%13BK%MB!Z_KBk z=9Exrtjf}E^5&R&vop+`~odwSY6$nb>nwk-DT;21~Q z33+CZ4>lB3jU#te_gF5GZ{EWy$-61e}0k@fP5tub_Wuz9E4E-=z+&OqWS-Rx;}6Ko@*0H z?TEouNu{rq*bH^INjN@hn3Qs#;@j-Td!-d%tZgCD-nnUQVcohtetzz5TfT*Qk8a|x z>{Yhfyou|5{nQjHz?OaqgP%+zI=WGx4OSH&XzD(Q+`;S2)!Gc><3xJ>O<%c)Kz5JZ z)3T!Q)>3-(yk50>+{&;fgw&0o%WU?h>j$!S3Du`JbdCnR4r=jIe)N%&K2J5nQt@RJ z$4D-ARAs9qNkTPXZ7J(R6SQ^6u@`MlZXz=Yk5;5pRO)IZyt!UeEP6fW8~JzxH`d4FaPkk%3+Q z?BHsSDI?$*T8Png^ zOnmWEfEJOC09xc6Xc2wL@s9X1+KBjO({ID-5ExeF23`Fa%;N9|AvAFJU0@hB{XUFv zGa-YXzm1|rU=*4Bh=_mmFEdBc?@zV;p&HohVf?ofR7 zubJ6|xi0JSzdCB{AFrq@kS7b@f4DRGhMp^Ro#I(eiS420_@~!jnx$VZjcIdwu& zbvl~mX1wUb!z?UUY;9dxv*b%6N46U~rHTjun-`zq`=P(cBn9Wi5DRwf$B(HCXP8lt zk553#`d4^dh*%;N3oLMj@Y4KLvrv&9K(3d93LrU9y@4I=H{yZx^`W*_YZ3((*!$FB z{WF~GMpuDb0_XUXd(+J{LOq)vZ>Z{NrS09Oq=813pf2+ zy>z4(z>nsI+czuJu0u0Vq$pOhlAg!u$7#GhdGQUjG;#YG>ik2?s`4W4VNZO)<8~1} zq zekPwZjt-eAzS?4^km%fkTFOQ5<~*RErq7C`?99>dJ-w+9McA^j)ru!83HD$uvQ)NE zt4^jEIyX0V7IG@3U0JZC1J{tgH$Ej6c?AB7kVrPpDzki1x@qYY9Fjk~bNH~^o~=N% z;_Ve@JAD$C5%>WH+29RgdP(4sfC zpy*X@X+q{=Ovd6Q>kk7BchDfM4MMDIf!81tRuB75kmDyv)zk8>*Xlv z>-kfYz(7^gA}EJEouSr+qlCbP9e!2+;K9Axp)&#NP6G}bB*f4 zcifmx`pLX4v-A%l%@+v(1rOzl{*4nLT74~8s=FEe%Oy|@U`2iRyA@Tuqz@Ae1w#3b zVY_GW4;WeQ1?Cg{Y8R8$7crBBsP8!yxH5X|gBj@QBT@04Y81SBUxSH0?_!F+)G5%W ziZco+N}eW8eyXzMFTD0zP>|vK2W*gVV0IAFeUU(t7!~9xkY)ge;DH+3ILF+hNCcXpeesC1V#AJS2N0-CGP%&f%7XBDZCwym*03_a5fTBS7&vw)+3#DW zS>^1!$$#x>5(?`pV%=V4sVq(SaEmQ=~$p&G|D}3 zj$a(O?}IDiEf9y3P;J-o+&6|!@EHz0ij}tfF_|-7yYX$sMaPBAN2$}4`RG6nqfGD^N86k={0y}jsWBl)L*k|7K4{(RX4cPAMlzc3NN!RVS57hDw zDY7Z?*deZqgitZAZ>SNeBHhc4!{UxXC4T5)-v*gJ>M0v!pE*2WU;Ac&SNIU@S`?Q; z8pNFW;ya%!`9~zgv8@ssKb!~K6DWu3Uvv^=6-t~*UKk|q`AM73nVP7YGN7qReC+qKjnxPw`ZT_-XhFP0U@fzKNPEfe~RL45RPwarZg8^}TJ8i6<%9yQ&PGZe8 z+p_zM;<1+5$cBQ2!XW`}-1zh`rR#g0SXC!u{fH=LY3z~A%|fVW*1SelrG1{HEB^X; zpDQ8%llSS8B=@jw>?HA?64U2^K=)al;m;;0&szb*EYk)|9pLC1^by}}fLzP*I>aBs zd<(^QeE>VWYCp6DE|r1?nREeAfFmt7@+PnM+=;}moKNpSvmwm@U^8R;9Y|jta{Y$Y zx;(wBifFiSXhB^^lK?$b zv!^JLa`j5egogAKwaxUJ_ipOAO1o0WsL4LdCpb;|?i8MA{TfU2`ykH&&mD-Qv(_qe zoVFpf`>}a4B#NsoVBk~VG__&l2c}{&yBiw@#EJQ6+_)*G+-}+>1kG!`+RUAS?JSEJ z{^Cz;Rg>t{N;^(pg4WVU9Xp`zC7QAIkta8OLBpo38_z;NQ`nt!&5@sY49E^}a0c~G zz{FRWtz;`3$Is{ZwjVvv=?EwyFG%di%QWF*Zz7U=`+e^{8=4^KoQb)sl4ivUZ)8%b z0X`ltDmGnA_eM|!jYQ7%vD#cuJ&kd=tPV)^V|U)!zHpn(ZP>ot3gJkb)3II$__MA& zmlWHj8lJIIVjFe`F=L_?Z8(lvdPA?`2uvelc5b05gKmwGgVv_#;9<9{Nay^imEFKg zx~~*Zt%W93+?<06CKyf&d(Fr%xfSSBgnblQq6smEgX0kK!>NswA2Fulyi3pDem$qX z?PRksV9l1{i)S9>drHQDHAT*4XRDSAD%8i_F&Uw@vijo84`NqE4HX>(?12*n*I8$7 zo*4@C^yzH2wT;X)KVrBPnJr=$dCKgGXi~-7uWUq{Dmw%680B8))X!h&PjsrHNi)x? zOj~XAUSogd*?lQ?zwIj*$NpBv6ePralFhnArj0QpK}jM@+q~7J8Vy$|Gn|Q=liv1r zY-)=`hM1Uf#r;-6L@{-0>o~Jd8Hlk;w#R5ND+)=OB&yJqaU`xQ zzfzu0Qovu2>4y}zEKeWs-j|;7eDkJ`l{{-TpDy3EFV&D3iB?hobu*)lQ^$N#kFln7 zoY$X3F9m1%o#hp=-Z88{|9i5`btukdh$d>@unxp$uky2S_h!93w9q$Kv3x=AHeb^O zm9C{5jk5#MPEjJ%6%D5Lp68ZQyKamZ^Yd&yBlzICI&Njzes!d}PpdY=gIwJfXX_cF z#CXv8HFIwTS}`k()6=m;e<8Mf6gC@|I+^^gFNk>7;sZ^$PxI0GR#(B1xa5EbI$+E#1_re*pfV2Qsh3)cgW)}OB>dCO6)%mnZ!;qvc(qq~ z+n$w>AJM}?>-cXSV(&oD<@AbH1=*;Nyy{t_O74$nyc(AE#|ovf%5?t}wBFn7a|`ME zfqo>eT;jvCFR4&%JZ4p-w&G-NU5s~JOdGEsg+kl*DU}8UN>f1y{W=N9AIl%v*NjIZJ3M)te7u$3JWbnZ-Y!f9`+f`Zjr;l{ZAZe{?0#%gieM zopCMYe%%2RWX8vB{OmZnJRf4JNl-SLQ&(GQoYIFCkr%4Dz=uiAa<4VR>5a=5BE8B5 zN78F2-xXc5t_x>3&)fO=%m;J23&I&PJZ36g_$pUH_DPvJRHvu5*tJW$8QD;6#nE@w zO=ZIwOx=#2tQqxa4HY)W8Dz_uU8{%c;)bh5%8&;?ymsqGJOx>n%A_9)R-()@anOes zk>+)8)zYUGmEc&$C*^#Eab2n(_2*jR&}PHWDA@$2jS@LuOFw=3h;2Dc?>^{t1=K!& zwlbqLcUF>C>fIu)=dgBZ9alal@jN^-l~Yhmx!gX!c^Tr*q0u*Dkw(OV`3QG-q$kV6S+HVAPV^+l2WF~;@eE30Q3XWQ?jV-T$noFZm~P2#fD1g`iQUSq8Jy-5tO z#I9czb$-i3 z%&CcgUK7=!r%62UAgPA5^wM2jIM)TRF=qOijE+31tBKK$HaRU$Pu5T7zmgvMR?a~D z_C?P8y(F9v-Z`*O1FkJq~(@JO{l%LJ2O9t9+@ef&>gCd}YU&axW0GPy(^^qqRQhvQOgjBV7d`ft;dNsLy>-AO}(%Gkf7`CaOa|TjU z;SDiV99|Apc(EgZ6jH`3(hzbxrqoOM7QJv0Du1>*GOABAX2#zJ*@_2knH9Uk@ot6T z9x!13^Cmrna|0BA*UqpjDBv1F;$MWBd=MgGP6pJu=2v&16!8a;CBK_HP%vQpK=(?z z1BrQ20V>`7%R3NpDz;zHX7e)pX!GV^8zq$br=|utt@LvsgDC;xN0NZT*kOj4YK)bo zaWC`jr=cLNf1Nrlz4j2u^WoL$F)ADl+zjofJc`|3Jl$NufjwA+9LIyc=-z?CEB$Y6 z09p1~P;E0lU=1!`#<@PEghwi9*;`y(7Z+Jwm^q>~L5UL#UEc_KSb6 zcJW`D8DVJDIO1_gW`uXTw`SN=X2f4Ql_tq%fZ^CmsIWjKqIvygy~EYaYl%({pHmR( zxZBFNHZ>JP&)I2blQDC5O>LuUFR5Jz2(E>nYt&~nY<$II=g!p@u_^pvR;^cc)jJXS5n~=8lg>Eru~E3o>r;U z2GRPpGsq6%yoLTW$JKn}DuJ-CV}5FrBJQjZ=RfUmTO9n#3q-KS@Q$X}Ghbix;Ye(c z_*MH>ANX-Me&SQ{Xfs5*j`(M`w zxiiauHmXjZLAM%3e&_OoX*C;RHFO-1On&X106XWOrroEctOh2Ncmoqv8?Gv}kwhJv zN<$BWBlcfR#0*kJ6fK)0u-2r~(^}Dst%hX*`uM-f;SfrjkY^MJ3&)IhCq{-ejcgQy z+D&h;g8(u4jz($_K}32WZl=|M>Qffm;FLEXm@$9J&;RCS0GRS7jzeQUakUgZOIolZ zIN_yKGy6apVr4!#5g(lhNHl%?YGqiN-)bDK=Ww4=fPZ!P|Cd4lC>-;k-jrA_GVS6I z*jc@1J(1fc)Jh-1i)dE|3l+QBCsQ?oX6Ca*|Z~i5-IsnPY(I9bjUK8 zOvmD4l7jhIA#=h>ZKQ>~Dgm*z+IqLTucr5b><)-%@TGk?RtYLhDe`3U$7S+kfUB6Q zZisID5SMk0)Je{}XEp!>^=K*5jbgkOr@iDpUv%E04qJ{1&y7k`BxyZ=kb1S#Oj&wL zgU@jUbdnLLMJ{`!qF?L|r24tL`e<#2Htdn9t#i0QIO5b>8@F*6FU%b-C|IwozHVr# zv1#;;g14oxIm#p1;z1hWhcOIdO5fW-U})?rUvv%>(#Ef4<(I{ml{HJ(qcyMDjgvr{ zl*iFE7@M4fcvPfF2%R~71?6{MxfL2~cPEe6p(Xq#WB`yIhKGN*vmJb?1`dG}>AHle zO%|t*QB{X29i41-Wplj{>M_EGOmA^cREfwhv#Pg2(AhlLA}v zK#^0eVI3a?Z^b*3S%*AimJ{?zAqG0;dQ|Ef))mk--=Zws!+THnzUPq{8Bz}Fjjmk! zv8C6^yO^LOkz;IIVqrAPDwpN0*^lkk%k2>kbP=n z&8&zG#@l8wYLYj#lr7e||E(hREoavSQgDQBqf0rABuPZy)0KQ8S}fW#|Gt_@eU)>a zG%a^WjR6HFmLGHUpxZ?}Xrn>t4n%Dw46jiH!gHi_7K0&cN_!j?UW}3 zqs_ZMKUt^&15PJgiV}!}n{;XZs{!W@gO+4-JK{mD_?G;5i-6H z3f74a1~4;&Dox4*tyoSb%a79Baz%{XlRusAt9b}!7DpRbezP`eiT3}ghX)>NE)a}n z#hXm<;>53j<2huWLO!1?x#1K!oOk~>muLTvj z@9&d*TX*Iu^_CHqLp})(hn|;O0~L%UkBbE2h3@i&zVm zHWo2;zK!LX(!z2P^O^R(I;K-8>7|?aZj&8C4s}Iic=P7RQ~9%`mZhGgo~eE4O5f~i zJ9`ut2FV!^EB^JHHo*;2834JY)s0huvGsdz-mXaFH)xRBdis$0^2tigkxFKAy$<1d zwgb#wIqotuGNDArwbv+vF!}{-U<{W30Jzn-{DJt94EV!CfLm>P86X!SmIR1HRhby> z9sYaCNN*w1lf0ISdkWf$V;*0hoO*TJeQ|X)JFYhu7{KP{!9iaMD>dp=#BIEsYLZvrzy8GB&j(_>%!S~UKi}Wa=|dqZA(q8{5=#+Jj6l#D8)q> z?l*`75UBM~!kKCnksw`7;=4P$cc2%vMLRRG-y5;9z&HJh$Krj!oPmA`aKXI?+$(S_ zV4$u1bUpsLl~rE^m4E#=6N3J`(=`)@HqfzrCKF>A%8UrbfA~5uE-V$HOtI3Q(2H)U z1SXjT(H=RwF+(w|uCEWJH>MAZ4%iL#`k<`eb2<5JWDXYO<+LAhi+Wn5K`l$Iu}Y+R z>$&^!4pgbKeuKN%5BO<>32%4=o93F4;qL)W0x0{BoX>wBHsZ@-06X3NWJ*98j}nKe zDyP>EaQ&OTzIa^X6`IxhJ>}|qN=%E> z(Fpl<)T)O9!LQOq%W2drRVe1Y%7-abn>1i$n54Wp95*a9X9inZHN`IxbB18 zRR6r36*Xg9$QMH0<;QWISoaInsXY->-+NUC76Ng=Qb1rYelBEdmkG4e{7anBe<^YZ zk!E=i_PLt!0w0h`&g$IykNsg`3lw*tZzjN;1H=u>|L6C&5WheZmTv|hv@F*YriMCH zOtP}GsZJrRrCDvFmZ0Xiowh;2E1o#2$1k?z%$;+ZVyt)^>lq)XSFF6jq*)`c&un=g zRv&*6V*jQZ@XPPNI`zvbg}bbYDX~S3+cDkNx)f3~cjerf%cd*{LGcT#T`L+Bt%pJ_Xwb+fURbEF$fQ zQKxh$TM#hku$AO!vZh~kZD0mOcM-pVu~$b-Rytq6>4&Uo8r?m?FC$p|tmD|i15gVR zVHvwBy!xbORWk&Yx!aQyZVBeEc}80-?LvQm63HG0uQq~QD;Iw(KGbu0vG}_2p5i09 zqU;h+ByQ=fafIg~paXSIi`UFR zd=+3U0A!jjK+a^CPm({m6fGgrzXl;=*TtbWxNDQohnPro_ubL+f zr&{hnz0E1`IddotTBCehj}lze z;eJ-Y3XTZ)U{~_Hg)o?bh#sHW*5)&AGYFItC=R|1?8yRPlub(jCeQECW@tWWi=h~p z7r_|77MgxwSS@+{2hYj{Djf&{f+{pH10=M3fCTjM81hq)vs%`_Caw^N;Q#`3P%5@} z&?dmB&UaLDi#0Q#3hwe% zP6GxtjlM>bi3WZL_wCk4bV5a~4SX!4Gjjfox-EIS`+uO`{=Md83XLt;u4_2^@7+ViU#@K3)BZ;YOfv`TKY~y}T~Ht(?eTu+t*@i_En9NAhXJ7I zUHUig5d&+}tkB4RejBs`XESr_404cXMU>NLo4D9UNpp;Dd%)i3r7ZDIgB?*6+l6h^cpo_7Y;fmNEeZ&E)OFl5YlRme3`(ew;1q^U6fD$3ULA4``rbtDwt=BFEazW1d=pkd8Gdm~0 zyjdT=*v_8s884sU6e({Ss2$`|ri9gB-GTNyQvN)fzx`#hA8SJ1vN41#SU>{d zK;)=uV$09)(LWA7$VvrCSa5&VPT@q1+UvP?%;1s)+AoZDLU>5>!Dro_T4xwbmCaX_ zl{hyjn!rLdoBsoECUehNa|X+Xh=`n_Sa^ke9Y#n&RykUdRd!>YUUky_KzSB}&$b3P zS(G`rV!X2V4SDZwbv35t3STRnIS?a&{Xq?RoOwzHj-w^9J!#B&!KsQCU zN@=T3U*sHEWWi5~mOkza{U2B1S!Y`#~DDQSW##sCbV8 zw$(({5O-E%=;7+q)@eq&KkM;Xg7dCe0Mu;-P?r%tWDqnNy0 zZRpC*z=kHvnc(^Cx}0I>wba1U^H+*YHQS*%Vd)oalZ53?qVd4i7p%Wq*2>T8B^}M{ zsN`@J2$E{}!iHLXuI%m)cs^`iaq|)zpZM?y*comE%Abhg(2^Gk z&%4iU{XRMQ$1H71+j#!hj@B1IM^wdPNDl)2^D)qPuTyp8T;3Snp*mb!m>oqnJKE3) ztaR$v3B;r-kr+SOyWzMgc91vLlu{luxE5Sef z=pZ8xK|s-SS_L+rq`+epHJdnq)0-cQ7g%HBXI4q(|M_i@;vYlupT3a=o7Y&a?m#*~ zV9HQNv7%Oe;L6c_5TWJQ3!S=vDc`WJXnukY)V`-3_PR!Yk`~!l&|(&!51+r^*Vc#> z9K7YUNlj_-rhH3Mo4=?H(WaQdjJ>qb1%uW50dLSpw)8IwHRYu3?Uj8SDrVC*6z0UT zv~Rt!UHTm=eV2BnKgnnZMisU1lKwyJy>(cXUAHd`(j^K~f`D{)HzF)TK#*<(rKCHT zfOL0AcXv0^Al-}Z?oPkkH}-zyIp^`+*M7fipX)o{AI7@ZU2D!U#vJkcjTr~MUH=)9 zJxeTi248KWru&{~q(cE>8I#AD`~Y_u-0R5UJSAP?=|CBPccJzRk!(pbs9k z$&pay2bAg^z-w>39`~p1s@;f6FRcjP+h3`4H%rjXQfaXx+#3Vx5~ab`EUM~s9k*@7 zej?cS=a!R6m1h3-x>j)`r^hAKrIobM6i-)+NGWr*xNd12_JH2^>K&*?Q24KnQyBP} zM>5j_N@`_ZFnmKXqE9WY44#*5#(0%Vw9V`(%_9~s8Ie65p1X$H(m;9PWOJTBCTEB9 z!C-WEew{a_qT0l_6XV_UXyMJrEY#9Lsq$G9kFsRBJaN0?{`ulm^(esLhztK=ivPnB zKR5<&S9_n;cK^+e=vo=e>4Rg^FCU#^8JC*h)uSE8xZwgJEc;>dod><(siXqWACtJb zO@HxaQCrIYaJc)(=_*FCCPL*o*`%D>oN@fb2a|x!hBJ4UW~Mx9KIj&xC%p03#!dDk z6*JQBzNxV(4?Frt57j;Cz{A&BQxzw^L$g@P?oG2={?HpXU_1gba{BOHHg${BDFeG~ zmal4J_=+m8Lot@)u@$#mg3jpIcf(Jj2(kU;vlQk_gs|etXD@rYaW_tjqP3AX%VxS@ zRH2_44u3A=*;nG$X(y}Mdygq%XX7LL%)$9F411<1flllIlGzVOH%Qxih+4feNST%9@ zMe+7s%Eg(s_XHrzLmjNE$K9nFauGGWxxEJNdOpyG>GK}E{iZ@3?cMC1o z8#tA@HqxYWb-||C11f3WIFM+|&zaMm>()aqO$km3N40`pHjd>@luqj@Oj~U43AC)t;?awl4)(*P7v(??Wb7_NszOQDC+feO)FmmD5 zJ%4F}I$YxqPj}4ywidyRg=Yf>fj>`z9sHO+H;#`TwKRjCZN_w|-MF~4b^t#~j5SP? z`>-ikQF+$^1>+k>@5&llgN>jAGa08wQsv9T^XQ2K7UvJHO2 z^OBSMCcX2p6K>VBViZyOW1xPa&NJhf%rf)MK6Z*0)s`TMHE?lK)+o*BojQmNf)&&d z$nxHSgxoDd-%Tu$(T6QpYb|Lk%)0)&Hv4&EJN8R!FS5@PP?&0G3ELEnGhx&}ps3>j zvdDeV4=ABTpmYHpaZ3fnyW=djZG8+;RQN) zRrql7&Xd0RRDM!eThjUEPphMWT@8d9=Rh#6(zcYz-U`9Hv?6>qAZ{=<=q7o zw^ouY{#P}of8iK(Vi7pE1X!dKeeR<=wBoc39(>M1xilZ!Z*w7E04jjeE|kDK1<2lE zL_G#p3vwJ`;7TRnJ<|*F%>VlP{F2T8(QquO0sY&;G2Ir?*#`RkXW9kY4O-smPkdOJ z5~wo!rtjr@y}pR_;n~3?pxT_ZtmK@O!`VIy=2NQ^-!d$4eYDl(y_Z$z_@P$HlB^X# z7Mqb;!h#vUm8VRa8`M-)`%X*HcL#Zw>MFv!d!LJj#&J?H zASwpAL(v)-5xg&+lI(EiuvbuiK2KS%4l>{{VR@AJePZgs|jI)y;O45X^C@K@g|>ke6JKLm&Vm++#{VTT*{qB9{k z$lKs+P5zIUZTFx9bnq1GPp7-eHuINn8G|a+8-+jFz91HZWF_nlcafT^u2}k!(BE=@ z3k5d>B(VxT+wpI@w9l5c<*SPK&dly$#W8W#dm3U*Q2I!GDll9I8U_MnM627uNs@MCYRIGqN{Fr0K4Tz5ptU*8Cdb63F+#DmUML%z7Ibl~F{AMgN?8{L<7cBkBmg z3TemPWbj@nE4z*N^-cVKSwqA(NU-d?`?NaQOYy?yO`>I@+oxJU**9Kri3E673fLEX zDC!KLPmTG#1lP1J1$tkE^@n)ddE!pmhIxj0RW7a&owSYm<18hn)s^kkb$`%I@v7`5 zOy1^E2C6hjSk-qhx4>k0&@TLh$C4>6oT4A0-y;@-FZ&68hkovZwg33)k;{7r_j-(j9P2WAsvbs)0AEISCu5 zmoTG)-MNeq;hungEN!cO7^V&$5^NW|Bx(FR4p$V2LWBVJ3XpL1zxavZ{m%#VNiXVG z$E8(Iu&c+tVd(gH%+#POKJ#QXDXx3*(AYb73E$*p#z0+Dz9Zb&4XQPtIC8AMg*67L z0S)g8q+S|F4rnhi!k)QHA*89bKH&T*FZ7W+ok>zHuS6@~3Q29hh&+zm=FnI5OYrFY zYnEwQ0`)hT1V~!!VmqB9R@`_5lWbce+P1D;rCx3F@>wHlPPVegrSF6eU#lf0m<3nn z&#XHRi9Yp;QY5#5!tskeOwVOc2O8w8qhI6N4)#t0KW01`+2pO75gN?&4hzei=7+-5 zuen3(j_8lWq6FO+k0z=fih_xnjZqE7a?^?@z9f-K?tZb~y#Y|=`{O-De}r?S3FN5A~;#4w6>-dtpmK z%Uf*F$Co^MIleD1ySwvMd@spXz7MAJn29l@u7F&SQ%E_Sg%uc)-@mM!JaUGHDyx5Z zIhXKq^W(6vVk0hqpw$hGL(Dx!F**Z<3IR8Cl#DUsdpN|^nALasaK z0>#3c?Gqu20o1V|vu4PzhU{I8qSqN!QAbe@X&@YP)mMvhLZT?&taE}hWDvs8%2&)U za&nVCZdh)EK@Vm#5^?3C~iI&t&oRG zrqaYRky3wndYGA1j?WglRK)f*=ZV`HI>V6{k>7bvuBV)5)cNbd$C2u_?I`um_%pl# zGrp#KBO-OG8YD$>pL_UhNh*O_)E~ssxXIJ1D(U4Yg~L9%V%Txj`2lZ2`^py~m3x@r z%FPERn%x@{HLta&Da(c`xAWq^FYt9u>4fVGikbsB|Au)=2lSBW|B`!=%sbplfU_Z+ zW%RSmbFJ2vwmAK$63+rCI71>!si59UZ1BUIj3Gf`$Uq&aEIe3edt@?~4U=VtcWv>h zDB-!!@MjDvpS)bEBKyqS#9F-uK_@%%9F+{uBvde$K}PQ0Zo+#cfbZ~z5dB>Gf0^_g zRh03BQe)sjER`=m@NWnE74FI0k4lxsU&z%2LnfZqu7#E4 z3EsKr)fN!_f&tv&?Ls;q#C0(4bz#r2m|M6qNR6j(C#Bq^u;M>-a+jv9N2;fd7F^;! zk=Y?abMV+lUy205Li-d=XM%|Q{RNlU0OOr1(oS@7+y`WQjU{Qp`hooUfQ|n$vUBf+ z{sg?x$9OLR;NPT*ub=S=|9}G1HD6I=q=o2vLdpa$r5Tzx-yR{{-Io1;S_NFbhqt3T z7yozPjWv5BbAsK(oEJ34wm^eqd0zGTD$el!Ad2*~i@QrM@Fdt=YhtZ&gpcw4HAlK} z=s;{;Tne*96-4g%OIUJJIt|y3XNS$C_AxeKF#Z7!;4B8}#W>#<&gKLGY`}6pY{G5x zBH{XD(>yKvpNwd9n9HOUnZsj9*HZucGrhXH_VWYdrze&#S6q5$4G7%3PuGDQz=L0J z;#=+a@zRzLB;zR`E>-WlT%82X_ED_fR?L?8Hc$M$GnBNA+l$d%n-^q8PWp7D<=U}y z>YU{+?ZJ=2B`8r^2Dr8~pBZH%UXu=VDVSg?-^!k_KOEtow)&3~Rm?<4HIaGW?!UYu zS0S-frJg$}!+Sdt-ovbD*z@hmFb>Ln-Jr^tHoy7FHb?2SaWbU-`}v|ed1yyJ;H)#x zD@}V@eNKI=jLPFO?h?jbyXusEOA(`eO6A~rf3FHYmL@ukp92r2E~DRE0Zfe^_SGX} z0gAB=4}9;j`|lUn;U2_Y1F*-DJ?;-XIi-& z;`s~9Bt8TQ`?EazqSDPrL${gzhppsF}}(cU(JxjbZW1zzXM zaj=z`D7E?y5p-i!1nls_^mKrOocB4ZFAi<}!3qK9Md^Xm^vfG10BRZk$2R~-c}4&* z;njaWC2?N3SPE`8p6q%V*m{X_JARpHBCW2e4;S3ypH#-ggOG(*gm)1zw^hT!i9G@h zyfT3H?WDOo3%c16^^T*TNGBUYiL9yYZ)j41Gb2sV>m~kdXT}!@her=87v96$1OOku z{!*XLY8oVgr$ z)My5KeYO@C!LsL3fKP2zZN0S4Y`>{F+UZ27j8sFQzREkiH9xqj5Q&!4^y!rIz*F$0 zwAo)-I+LIyjO_^lqG3AIk#kk~C)F5`81h6?sFzcmF5j)|t=y|4JUs(3JEbzZ?8>zI z^0K?bm{9OPm2%THz%(uDCB@-(u{pCb8V&;9`IqID??Bpg31hN)#cqM4i|eclV)A^a z177xta2`icPUpnpYg0mXbM{~|JlHpSFzcHG=m^1a>5KK$Vuc097*!cZ;w9=r*B((} zQP1j`U>`v{s(T>N0zGTd{7%1Jr*%W!uo5wM-H24LwBDi@TCNz%$oZ5jUxZD5DMakm z8lDjYtjYr+1%eNx%*i`sLI7gl9IbsDAup#n-$URw9`>)C3Mv!+lVV!W3ml1drY@=H ztMYLcsM3Xt#O)J~rM=hZ+l~90p!eD7o_%DJ7a*RB@;3jSa^Ce+f=1p=_L#y}JphEZ zBsDqOpqQx%GO%>&A&d2xO|kpV@|d%%7;liw=9YH+&*r$d0Vo5KevAN6L1aLBg+(dG zhuv@A#rRl5dwD{s?i>5`C%AYny`UvQGU=Mcj1S2Pi>u1ms<2zU7k5vipPPzKo&IjY zGx0zA0p)965Pu;v%J6BAK{TGtl#6^l+`uqhrKAlNc{Ta#0pp7un!zrouka{vX)WS@ zga-1ael_B9StRmZ#Pv^QA-gMxv9OUlsOnJSr9?thP~YoMasezbe>9iue>d2F7PFD!;Jp+b+eA{O5&E9i z+B)$h@(Vj}(3BwL6ZGup)(h;{`e7oZs4OMjq(j5Iq0UOSDIU-W#1}>HRDUgPD;#(; zbhTCum1C<*1>{rpa0_T~vb^>@RFIRN0I#NP3Z5ea1!&tS>qG>s{}vApkLWRYRkH@j z!53u%1@h5xA}qk`xLf)GbpsFJqc{eNe=TtUFhb&QB@niBiufb_Dqk`hKv6PRIiRInGdZ)d) zT_w3|dh22aEfV`?Zn#Fp&bItx%O;VmGHY`l1B?1ePS8NdaW}l7PcH1~9a%==M-Z`r z?V`0{%>YiyxgcXDKh-C5Q$TLK{b&05E#L@;Ru=yg5DI|Dg_^Jd2XX6ffKUlw$$n{~ z{SW$u6F>uTccBLi$>`ts7sxY%K>wUe`kcQ})ZeuvA2Sr+xoU#9UPm>n5W)gD23zny zsTJ4TskdC$qRB^~*x{>_4%YS{HlZ(8+;1%t2&Y?a^CYPXWrT7VP3aGG zj?I=N(gy5mx(5qoMWIM&(r8G$6mg+Yq4=qRMpDRBX`fjt8?9c@h^HAcFc^e$Q#@7o zq9YB#pWP?cF_W7lD`{aL03GqR?W>r_ntHA2sPQjmP<(vlezA{S7!gzpYu2xQRP42W zgctO_UR{*q7}7&>-=h(@PBIy!xo84~k;@&~rxJWh z)m9L?0OZ+qs}eok>TG8LjT9R~n^* zUDi<2`O%T&B-UmB(Vke~n(ykO0V!PwFzyhwRke>k&#drkOy?nv<6jK*gVxNMf{~8D zMG2o^)`voqnhC;fF=fq|SI&gCe!6cfIfzd5Gx6>9xZ z8@yX$H%lH!k`9v)h&bd5&l^wICPE~8>65xJd&N=4mr+N=Gh*`@rTZ9< zuNr76P96_BE*!3g{bF{7E`)Bzvxqz%(^jWn74KE94h?r}@~pQ&c~xO5)$+mu0k;#K z-*AkLdq&ppxj40XE{wTbSX7N4nFNPS=|MPgYIQhMUt+xkE&>_yP(b^&=|n0SXPs?- zHoXE)szi^K3yIzP|6FQ_1P_SQEy`8jcik9kYfx1!D!*YXj4P?UY4k-Fp6D z%fN(Y^h3#QiVfC_YNz)cziU!aY}dzTg(;$5dl%q}7)psk0gtPvgG( z2X_buC0o7LcepKCyMQWStu_;broK*VQ~y&BA28y_R<7U= zfk)F-dqgNk^+l3jD2 zd0llvH6lItBkoq_SnLvi(@2?t-AS3iw_cQTT%DJ0M2V1*OE$a!1g z`N~C;rm`;&lMPxzRk8%6Exl*vB{k!-0Fmb2(oUV`E0>KH?yV(B`H>0{s^XS&?^`+U zXmGztCQCQVveAmBbV#>XvQheQlMxtpdh4?$uJfh9|9xOVvx_N_pqVQW)K79y8>?v+ zr5n>1g#|U8eGJ0QiFkokdJ!`&i;%jUY?=+3nVX&Mc!D|Gv);WCuKqq7Z|gWM5M8oT z^kv-0W;DhE{Y(qr_;C%amw>8OoR*JKnD0gFHay$k4N&Yq0d8*43%>aXg zJ^6~N4*kxf68-MQ2M~ZD!vbQglBCR^tKaai+>sUM!~i}2yDT7$;By&sKi7PkMRUo} zbo%f}XwUz)^XxyKDde0y_Ob^+0TT{NR_=$##f8@G!SSpaH!nJ37#WUJ>07 zQ_w5gFs+7Ox`@5_sqv7kt2111GL1S2F0*asKo8ZbygU^ZJOKy8h3{mo>Br4W8dMQA zJwL4FeCG+&5(FU|_m9m5FZYP1IDSB#+yOmQ*H3;x1&`n0J>7fngoU-y{OcFPzg|Yz zDg5s~&CdRpJO_EC$;vVf1;OQf}h`gMSZ7 z;QT@#7c9%&0{eg_dm6HXM1LA$1|wNEC(Ys;i?$>e>jmTe&HVzZn5UAP zTR|%O7G|@A{Uz>@)iy+{IF7M>+XX2{mvV%xGLh1Ky@ut5H%qw8ZtP1xSkEE%p-FWu zy)47`%M^``U*$cHcP_%on^O5}e zIw923(~3M|wK;EI8Nb%n8_Fr>zi$OHFv)w?KyP2pJH}#%rJ)@JmjI=>jT^v(xAt%RfO?UB1HW(&t{l82m6C&N|q?Evu_) zZc6HO_AO+fH`~{*LFfD$dYiE`ha{XN6XP^_UP86bzJI{ul0Q66-abdrTw%ZLB;gcu+5UF9>+vm;eN_xCp#pmwf9M z?ms$JwC*;2{=3uo{r3s~)w9@7(VPa=CA>=FX=4#FXEZ2!-VeSBUR+#qCth4UG*PNa zWL>><*Q*ZUl&V_@S)QS9n!V$!5}aqBQDV_3>KwLLMyB;YVw+U+@tcvviQ%Z?n6T97 zt}2U7`WUwM7NrOm?kVB3!03%k`3F;-$F@dpLoF>jo#Z9zcyNx^40vHc&u8uip!8bt zKz?MWx|2;Xe=9GB=Pc;JX?eUkBz0BM^Gh&oG?BLah@`N}Fu$to15F>^)o+8e4;_Yh zWsR~+1q2+IAtD72mmz%+>~wuBpnbD5ru^YI#$rOjBB_(FWJ)f9q#)!~TEoRJ@+tU0 zdJA0xeg54xqch$dVg8`=y%w((Z-t9= zXqy}d=cAgjw+~W04gE z$llyzs}?;Ty%*C4g1D(yf_J1$4A6?0#X|6qZNKGiZxf^?h0G68MxdkusLK9@llo=W$!{E0E--(J7O(E%V2tF*)+t z!mK%5)AYz%SZs6xF|l`nIYiW&GffwzuszNz*rhC)6R}4P7!}scv5pd7KzI?juzv8I zrg%zMHL%-6sD|x4cOK06oqEjhy0~zB3Ja%KQBuiQW6v=-VE_MC^z;ujM*|QvuqjA@OVVuICNRy>GVP zm@+?N{>**Ttv+gxUt(MpwnA+%yjyHu@G8}RY_1RU2!8;_Y@9FbBv;^T&77wuY&>DY z#oJl-l9Ts{IO{su2D9YW-n3Jp;(Kz!ONA8h?r9as#51KXowUc{?`RWVWZc#wnB#U* z0Xx*JOr;|D6&YZgV29t^9wQY-6+MFw%zdvC7)7iX_%s@qGC@HbbI^WC`ZFF-y9QK9mN4wz`VFsA33O1+9DH6bf1`26TmI(%;qYMUXkZV176&8e#ZFMdAN z9*LAx)HIzWQ$dqjSD+m}mU8g70I3=+{$S&Ub{?+?nMo=7{5;JBuDugYV7@0Q5J_8a=fMHHTqmoUa*Tg;ZuutJHcP&M)xXlm!&d39w&bf z3Kt6sD-jc+hPC+4b4q5PXS6g&!z*HnmDcfeN+>7}If^V-34umT29YE{DjlUOk9qIb z6t1IPuGN4p$0ajr*^;6&0=IM48-_R{?6YeyXM9i1XV z#c>B=P7dCIjR3d9ZU+FD0#%M>W3q}yNAdHTnp@9#=G3)iM)31hm`TLMrWr_s>ZmpzTORlqqrRsKXCD}Qn=UXu?PZJa zvUze8mJ)t0y)bx>N9agcSU5v|o?k5=Ju=oHA;ja8^o?ke3L2w7BFMY2d4?TiUef5;=K9_CF8#bz z8ah9qZ2bl-A+`C^BMu|IV?8u8ec7{pBxHeK{k zaUxV3L`?F|L}sG>tnCLD6W^RkO8Ih4$ohzhWrlrPv@#uG_^oFM#=CDhb?s zHCt<9Az8(_Y9?jo5xt|~4rw42*wQC6_DFaV4hwXaq0i7VHYlIAtwbjE7wG_*0ZP$m z9BBX8KIq!+cjY;J@QXJD=Cg)8rX5#}@l$wxv{f%W>KQo7sd!ZaQ8AEk38N4X2SQ=? zDrh9&BzqS-Jd@`_t#JprG~|qIbREu^&G}593++~QvYCh_aW{;;^z}==0Jl+agtZy) zm-fp#u8>z$Ip@+*I192El7wm6OVJ7U70tI@Sm|tBI_n#VU#g3d8kK5#Nk-1k;L|8u zYAjYE;rq@Fs)Yl2RC#H-aMgS~7&H0)YC5KN1`DKpA=rzw(S4kd_nP=}04}l$#9mun zQ;`f%k$mu`f)V-jO>Ork+0*5OO;IssB{1NeeJ~_JM@)(u$(P7Ew=`-MB0p1p|NO(! z091?!qXo~OjRG!5(eu;iCkGhduFQ}-FE${urS)!vug!M>i86ixT(9p#+5qgE7Pvx} zU+M>xRCfBqT(}D};OVHz-XLvj19;Tue`LcHan?>%{R^P*E!6*g)A~O(HTsilrS&UM zHwS1`4j^Iy7waF8*teHqte2`E|Kw@)o)n5*PYNFi)Y!(YlzCq}>=$m-=!A)JsjyKbss`Gc5Jq;M@HE zu5#b&eQ0-$7UqSsA^iO_kzRW`mc|LM(_MVN>dinPPd`V1pac^~<4I(iCD;dbj; zvQNsrfd;U_T!K+neHarj8KA zU*occ+@b+xX8$yA^08bl$%)_vU%j$R-NP~3R~Pa2SL&vGxBYdrnD-b9Xg~zongL$? z1pe8*Ao|k$-Wv@7n!o@lqvibkID5V#WnlBK(g(93_i*fkKg@D#>zVnt14-EGHt;Ev z5z!?+(cgLhO6px-||XI--`>YQVTwmtc13wRw}X&`GPxs(I`RUj1J87m18 z*NzsjN!73;xy_mK1(^^>>56Abs3t~+)*`tlWdth7NJVWckX%LkWC2W%?=}g8?b=t^ zKtzL0bghae^3FvRP%tCG-IuyR5-fC#y^%1By^a8MOJeUquLKwJ=FwR(V(r!KIA%p4 z3y&qhf=HjVQ4c_qs6?T)v|dkt^->+o{0ujWMTmG~6^cL))Oyv@W{$^EHgVN*iuT^+OJd93Vo2Q5=UbsTwVf|88US=u-<1~qvJ%~}Z5 z0PBE|7BpT_nZGd-Qhy1Mk?^B2`T=$P)@JYqZvH1{S#58}7ceiYDO<_sC;*~)xT`a7wE($j|3En1KH4+bcYn(Y-a(%YhN@4cK}A0*iBj^*Z!B|v@bSda*AS0g`VvSq8#rs*DAfU^{ibj(JJf2XENiy(X4aAkR5UpD+9LpSNl@T&?$u}F_0P2 z*i0Hk@vrDlbDTX9?XKytO7a8E%K4L;uidF zks?*fW4&eBTfYnKvw0w>kR;91Mggzl!JF?U3n#-}RDdhyF77-c z09a8{qgf#5k+Dnj+kU~>lBSSjU%(7O9R61{S$M$!o1RKncxQDb(OUFphX+ec?$t?)6L*n*cu z3ZzUdjSS3<&#QJU@q#4+g9GN-rd~)JysZ4!h_W%0x-LLGMCajuH7g zEP0gtiZ^2-`t{7SHQ6TC85cD9bn*8vyOG*W!~9}KL)|Kszs(~x4)FZ- zsLtl|7T?iT)9H69bf~L%$Q913r=1 z^8ldtyT3j7?|VY50*gXya`LL3UbDN@E#=cC#w&cbKxD07ic*6w2d!x`HOjCT zrZ!+IwG?l0cCJbLQxD7QD{K7sA@~7C*y0qzWMMA+TG9 zVWM>Qh`G-LnDKmXr++}HAhl>Vc+a9^W!o>IX>k>+i$|-QjI8XIv(J=-aAe~7(t+1< zVUNOv_?8=an>r^z8ShBrskIb>U)%XriDS7_O`&;iTD06Do)vQsH_+w`x_)BEWqdi( z7C~N7Em9n*Z1;4A`@Nv=YSA+E>iUd>UOTf&1t*U)02n-!w&hAxsSlt^+0QYElZbs4 zNHEMQZBV$K2y;F1hV>|d(LEQFC>}?mRQyqcXn!SgBe9gAzIST2>W}1JR=>JJeLF#f_ z2;rqe67;C~7}2|R2!X2w`l|5{sJ7%Q95caPTEL~*vN;2{U!$t+B}i=a7&)3!CYsnj zflG~mH9+u|M}m`mD=Xqbpqbb~ZmY-OKlmIRnED;8cX+m_1>i%><7dFlF{?e*TMfhK zvUuPjQeby(uCIWrXp#-6KALiR08E!hY@QB$EXUwTX<#qI-6R%(lnC-^KC}b^IFg5>T@C@&F5djVF^d2GdHMhE_z%hd zZ_4+-JdV`H&Bu72_du>-vrW$B7Ys;Tjz}Rzh9B4RVCw=Y~gpI>P+`h3Xldee6DfZh}TZ9Dgll!REckw@<46p7%W;~2j z?|wj`aLXcryQcxj#H57a>6@q>_EjxBscAmx%%V87hRKhhsTfQ)DzGhuRc|l|?;i8g zwpjXxGO&5`>C(Y1fi$4Am05~pIrRW~b^UFC<Wn%ig1H*vp zEfY6i#Jt3HPbm;0S>@wcVt788ESt=Xu8PpBV~ zNKE3#`J^3Tj7N>G_3f3O%ZQGZXKl>`vu81FclhohGV|=$BQya<}W=s~VPdY#?>ChO0z=R{kDH+{(R~ zSz^Jzd2WerL<;D}FZ7p(Jf%|dBewvwWQaj+G}-)cWH>}IOjUsy4tbvdYn!JmI0Ht_ zoWO{Az73j_n5B1wbw)Ca#0$rST4P_21P-^SyVHurnd<`Gh@ng3H@``Fu`>s;Z7Djl}C>e1r|Mr#(Uj*NzTi-wSDg?YsOqitxYI>*aOJJoj(m)oM&QU zb~;Y8a&mPcI|?L>GkV>>{3pJwDhG$YX8=gVPkT}x^i~tyLBDl@{>oVf2DqZ>N zOxF|oGD#ejrJsJh%I5Wn-e(@*<_0#;YBYF2@oGq4?z;6pXx@F6eEsZfcAJp2#{WPr zL}p~Q$w#`S!IC8v^{qr1yP&HFOBIsb$z$t;XT*(#uYW)lwy}Oy;bZ-|-X8phA7&Ch z>S9WmU4XBSdwO1d63!kjEiA9BBx=$3R3nK%#~?w`S=Ki?hVaFZt!(L=puBmI(Ye)% z4gM6`F1X%X1jrp~tL>ay?mM`RfJ}ZfL)Ot+G>)H^#}3#wDu%M$i}ad|gr{*eTi4!v zfMFz!m?C&yf^ewP!>e~PTSK)nNbCQ3A&7rPn%;%8cgxibqH8W zP_tnPL9fuA_#;Ii-9SG1FZIV@#zbjE-Pj%n&a`ls!XYzU4Bz52#(Z0RA!UG#%HH=u zTM{q27XpKfafCye0aC>KmH z+8t4DasmTk!YY!Wh%tlO*zFcN>{v4!BU??G(aMg6;Ddp}J$2?1=Ke+__ zZ4mLf-{@x9px>#S0r~Wp4)$;q+DkI%TF#I*bM8IQBp+1mr!yLT=J4?$me#D|e7RVu zhTn+atFY|DYy{f;fQt5alG{xqoDggNl~C}IIshL5#?H-B*P7XXD&zjGq>aJJa~~aH zRPY&V_xqpZIQ~z(TRDsZK${$2YH}r#t90N?Q2RcF_o4mEZ6=xiM#^kkQVqeF zz;7u#dVi5C@BqA<<7OaPkag!J$W`urn7OkxPcu@KUS89vAO1$z>@Dv{JOun{<-m<_ z9yhG*hkP>jH7yLln3V&zxF{UF8Am%|@}>w`LERNdBsj)8)^v7<{6;-b1_Q5yPTxA1 zi;f9`cdzOWBNP|g?fQ)Ne$FU+>7ZR3QGHV_IGrK6u!p^HE@*CaAS3S) zxn_G5agwyt<9HrLF{gvSmb??jZ#UFuiG;oDH=;*wnPf2xg<$i%NY6*FSSxC{G*FvMQD&IW#46@@*gCXhF3aE{cHz^2$>r~%D%35>Zl$z$%vPNqs4 z#z4nt7Px%*S-=AKuvbPxZq^2`J0&&&!rS~i3(>_^>@PrBbI4yESj;BPLJyt&I;JlX zqDz#Z7cRv78M+gJzaQu+zwCPERqa=7GsgniZ<8D|J)I)x6|RXYT@O6Pmx2skD*~!g1W%N zLzu6sG(ohLX4&rCvKO($7n^ShPM@`fPcD--WHcTf+slvq1?6Vw@ci2JU8@$+7{iQh z$9%FmU(j~{^HRaMg1cY)4=KTK{F^yP=m1=;M+j)%;c@m8^Q?UT5bH)r3(%w7mkw^= zwtsSx@`x}(m5AWqV(u#d0Gbi~T_6$&qd z3Lx+!hNTH=j~&C9<{Ds*h;PD*cn8KbMzLUrD^Uc~fAsAj0CS-bjxe=DSsazZ62-#! z?;2%%nc-3|M$lZCH&!nD9$n2jIrQd@y!r7I}U`y+2KpY z2SHzn5j5Y+3SwshJ^YzRjopJ;HBFzXBJZZAZ-s4HNCwcA_puf7jYNs%kxE-0*+P113TbJb#Usi-=At@RjK)w>6-Js-N{LJ=g&)mjwf1OD-Z%(Tw+9>UK zSrn3r^{QMSmBA@VVxwf)miFKY*syTiYoOTL9I7sOM1t-!O9ZO)32~r)?up%DEp2tu zUG*1+{eAB=%B1b>0q$>***T$?;iAW5RSGn#@O01+e(p~30!pi|4qEH`^upw0A3XZr zhGmCO_Pjb0?`8JsA)=*8`a+K!wAAH9l?IwI@_F71ZZ;{3KlD__c zlORw>%aAj92Io3LW#%h*Z`D4D7oKytov)0Lo73v6H!j>w=Jjc|ks7dEkOlL`G0;OK zNYq24Z{nu|dvPf?v4p0y^Ceo81|Caf%Wa!!-3mxG3P|&xNq8sqStv1*OQ7?xba&JT z)-$~*1M$6|$Xzs%%8#Hg=rezkXtq*ms3eY|i{IH_9*jo>`-uB(8v^$RzVgu*rv#;r z?Bn#v4%BcV5X5e-ACCbWo@hIoVksThbT&BZz!n{M4x(*z!K8_sSsgvqTa{vMj_DZg zx^VSrH!pn%qe2rtTLj%hXnX~adAc>bvN{%2xMW}7&^Qs$t!aopy28B z%XTeNr{(R6>5r#C-rr8i5Dt-^*6R{{3Damr`fl8e?^XPV#=W_vb9~qk`Lu>!O=qRi$~*b*bK=9Z9cWP=y{)%j%LzWX=?Iw4>y-+G zVJhBI=W80zKaamrJHi@BrVsKe$R(zMMl%b?_t9`Wv5wKOxk7}(kS1vDbt=Sa9bDmm zH8OW#_GvGEz~pN^K6_ww?8Q^@EsQD$Lfs&r(yl_ei3YubuFN?$qUobI$8)VHVp0`i z^QM6V=(xJcskD=P1l`=KTMEkLX%6xG7YddB4yK}wv(;}qOPNpfn7HME^FBG7l7qPW zSvyYv@PpBmu%6wLi(h8=2ufyE{F`TwUVoG?z#+pT2Mr+g^ZIw`iaI)7o?masgcU5E z(1#-~-zcQMk6LwBjFp7nOFuvlHAp2s@+5H)YObzuTpeABhP))>O?n$R0)h^$B|9aT zg_b}Tv0{0&F0y8jC8zaa8T@qT+`e2WM(MFmRaiVP=?SkNeTjg1&CEwq*f7t8V`lzl z|4J3~2p02626KH66EVEt9yye}6j&V88l7ppK0=ON1SYGbAW_Hu$&drFI9`x0to@D- zBtHA=`v1e;TSvvwZTq4mBuIhd>?PU?FCoBdg5ukH>;%0 zQ$TT9XmUUN?DkH8>0~YBrcXd`Mpqr{KeXoQfdDmessK0!63K?uOL&id@(b!WARiaa z5D@-n5)c>!4}e~#+{u%48(gqk3jBqgG3vqb&lPZUb8Iv_(@#p}h}vFWE94#|4b{pW zT8m%$BlArooALGpIG6mXFN&-f=C33&g!Czt0H6~D%MQB947oWH*AUNPyts~`4{^Rn zd9H@zdzu~Zf!>Gv567OPt3~D3o|#Ok6??PvqWvohuZWE`2yMT2p)eMA(rlAqs1u1MW@q%Y%ve2iBG z2gt0aT9#MGjm>55m9mvz|An&9nCDI5P^na&=7PTV(Ri7jfe;#5{V-Lu(M4~zW1>NvMB0SC z8`5T$*OttqlzELck4W*NZ1oCeQZhQK465itzn~!#|Ef;(j8|@n@74w+$OS7jw>4M` zd_5Oc`PKD>Vt=m2@b}japFOk1)Xvs=d<4YjZLYntRJ7*oDvT{`2vwR26RN=0=3%b3t#f%5x@zozAVYp^LWvq#Rcx5hRoT;%p1GKaWwi4_CqgEr zJ}%ct>Bstx)3`(2Pk&~Ibd1z`37V2S#XF}B*;UN5u@v@tG%>mw5eFwcn=qMbS4XIg zj0WT_-QA+IDF~szCn)HXu}qYxu84!HXs#PqEN>uFzr!GGB~(R1nZD&*1i)BUtGdhl z^-fmK7fN%I>W8X}5mDdEEn9)s6R^Cv)%osgsxu}v4Un;h#^%n8ba+yyozgZHslmt5 zUQ!rr#MXu>DhocT^*iHypB!B|;YYQZ6E-hHbGF&-jh*OslPEzzTYV2$Qm1qifSW#< zgveUdk9sbtd~2{~oB7hL)|C9RudR`XJ(gtvDiX=PAg2FTv5UW;Sm1sh@6B{w zfs9}cJo-9}@JT37=H6oIp+3vpmub5!Fl|LqRQkNppuS|$BjF;)yGRt|;TWOT#hey` z^96SU!Ea|{AvXC3OsuZ4&{3n!sqr!_1l#da71|qU+B3dgUd1v3r?gd{Dr6=3r7+^r zhzCj@MU0SC_t|2<`_&0- zaGvEnBRpV15I+2cY534DjyCdKOcHck8C>!Q>~0Qs5hr*V~Rf zM$Qy(fXuO+?lO1SUQdEJ%aWzT#jy-y9(}ua%PXX^sy!%U*a49N*WmWWmUI8^}t3T+wB|~P`5q`N=;LC zV1_SpOz-j=(o7YX3OUK7aJpa#uJ-|#N~XI4B-rOI0M7Xibgnk=1h^dH!Y?6R?*KYE z&DT%L;0$p|19?ha2tPvD(U7WYK#B5b5VRP0AbWE>7qs3DU^>Oz$@5YG>971jKqwjx zK$SY#2X5^*tti6&H^eHy z{Rmz!jdkbRY$;wb5p(8JhYA48?IPN}^U-qSZ+zCQCL+=8r z>Q9c*a8p}vZ^%B`UBuap-~3se&h}B8xrZ9tmDK5~;NR{NxwLW*2XM&z$ygFk6a;9% zmMKRVpj;pTspXn7ulRF8S8rvIq_HuoAZd`os(6Uk>&@UCuU_u{B3W`(i9tEH%hrdb z(VbS0pqZM_=xBqlt_o{mZbE@hnT(x(GG}S$6Iu3~M~8M`B?R=WSu@@9yDjCe)p9Lm zt+0&IRGpo)7v6>URk3;nXRnwrVKVW87@^{Vp>8osqyQSAJd~bzJVBVwT)IogH|1V* ze{)#@!S_JNN{r6n>VRCS5OWqSgbaw)bD8qSgHr##@BAF&AWta&G-j5Dfn_p9U`ClY~_4N$8$8MTX zSag>XkhnkX?L5&GpL0ak5hCd7=p4EIf-+0r(b^P3h{sg>PO+^rO>$Xb!=TtO>wL#h zPRhJE*1F|b;K(2NQsAA!(aHP{)@+K?conb6dRgVp6IvdxCZq@|#8;u-xG-DU~f};#+B$7V{cq?_+31$y!f5ZxrPGwd17yR7f zgfh7*)LiKS8QXbS?SM~6w2X}k+-ML20tVNwSUc+oFCZijRcU+5?g3rW(=x`Jde923 ziIiR$)@4d0FD6hIUipZLyR5xlUb^Q7@TutZL{1eNbr;!tj?rH{n6(Q7GD@KJE@akw zoD+n4hbmLwlAwJytXpnI%%Uf#OMKW`o9U(4L3Z|Gh3&_YJ`NTgVd7LoF-IcgfNPAe zY;r&EQR1;P)JJ3FQWFp&P$GCteP!-yBq0uA>buKuw?5!JUUO*9qE%oCr*!q5)Ne$@ z5h-q37ZbWJCygm^4hnI;%rH(5VA=M4tu&X3G&<*qJhwl^-yQh-xJhtxA&or_eml7B+FLA-j+gyHAjsu+50}UW&`l4J)t2to zJARLP+WBu5>{wC6TuoyiL_(fB=7hL%(mZKjV=mn;ly#|H{P3-e=C;qJ0#Ote6DN-G z_iewsc}s!NCs_VmjH`$7VAA$bBc70!&w~LAVS|;{-#ah30RekGWdI<@dD&R|^?Bji zvUe1v1?GW?RvxnTUF<^W+;7bQIYfRi%pz;nu-bb~R-2HS`D}G~SJp*El*W+p$e4ZJr1hW=1ZApS-ai zL#L`I^$aidK^8u>eYkvG9mzOk%vLBqKU0~-86Cc%xO0tdC5(Eu14!|1g=lvniYx94m2lf2;cH)X9i=+a3{f zrptAe;@223@(G|V4Om8LuTP;CR@zrOsyEzEO@`uk=xyet;J1@Mic9gP+uf}7jMvK9 zJQr;U)Q&++s(rq$nM6JB;&k!vwzK~~{LD1Nwg$brSWxSpA{@Ll7H#<<_~EryxUp3y zZ`F@>MqfpiNdM%;@y|BbG)vd>yqvcwF)doz7ckzRRH>1i8sRt}=8Go(onv+a)X#9G zQoY4;MUc}F3Rh|F)IXdf5HBKsqENWs&CWc~y=xgzjFwS=U(q@8Yzr4&%svv)WW(UcH;p~Q%QC~xu-Q~g>5rGLRQXs*hCv!G7D&kv4$Uea7OJ}R8 zEfgWn?NoS!4eZt0Ap*VcUhZ9$>;28QJ08p?0Iou`KHg1d?Lz(f8G#{+@PbJn^z(zjGm_^`%b8th zm=o|he?xj-&)`?0YW7*Zh5;u^Y`pc_N_i<7{oxIVVq|1Zd@B|DuamG@;ia(yS^1RE zkz0O?fjCh-h8C?A0;-+ED$3!MrM!XwWB_#BIe(mw*H#sb75g{4+UcEE>k^LQi=OXF z?i3WX4&T{&6xQ2qXBnik~k z$o@~ppd!E*T63CyaE&j0$YW41LTg1H%PKUGp5eYO8Pm+wojF^Da);F$Q|cbPIUr?! zJJ0PibNYI!Mo%=ZI2(#UjPyRh6}$Lj)5;jlYH{gJxo+|OFDS(`j&PGnTJR}rD_9wG z>eQy90`bYAA;*C$QBc8(mYfS>!HnW4 z?!a4`#JE)YbxEiI{aP^wtDYnR{LL5y_ePzwtf<1cBI{)CIu0xZJ2pNeDl$!3PNvhS zG10(dJnsS-JckreH`!(wKhaMsrl0y+mWMYCHZ{BKm?#q1XgbxGj*cQG)Zcs-mYD_` zCnf3)f4H+%1+*z=W6c3MGv7`^LiMM_GSqxDKh2n3&d!^7#i+sV`egX-ucud+-zgZ( z)z_A@=95Q@H19LcIm#=T`ZrIPl)W^gw)*-VQ)Wl{B%*SpuexXR&c zkWv|P;)Tu{%w5ovX=(na(TxHu?t<9M{@wO;V!}jmM+}A3^=uZRt1UEzJFiW*Nf!?@ zEpUDB1nVOoyIK1tPf!EXvR{2Dg73)%&o;sJN!>qgwa4b^U6t-2>Y8NxR2@8<7d)tD zXGU$6Gf(RENJg?Sd&l;B5!5B8Xsfz4q$e@H@;N6hUACS7C1!AFG!HNxZci0BX3*sO7TckhRK2sF)0*v-gj4j zy4q@bqHgS6!LM?5cX9u^Yr;s{^x6i z@!!BZZNTp7W5$0B%<~_AUbes`|Np832GoC}3bPnMsHALx{i{)v@)H-ZC;WFHJ|zHf zPRSYhFAP@zf1O>GgS{-+%MvgbQLTr3K*bWKMavSFuiLX`l6x}T3(U=;l2Q}s8Remr z2FP2}x1Eviv45kn8cG5cs=;$@R#OfE7DxVahb)kHGRXH&^POj2Kt|#~F|fB$?ty^V zPM-(>&+GZ&+Sew&dWBt(M<=NlHsM*gCTM&Y*Z>7AtwH#Jin(OM%lj(O`pqrS=|lY) z-ODOx1ah_=V+{RYO7(z*;Zx3P9XOTVk85@$My-ZE0UFt$3nUDxm(!$w?GVjR^hyrh!|R zX=UXQ^vP~jm#whO<&_Qbc9LkEAbtq+XBM@3R0RKw z1#Pi@ogHP@^)1pjlPUcud8>mh`UUhk7MFcw#}-ipcS#YI9JWvxrptnQJviHF1|6%X zr>trqx8sh{(@PeY&X8ochoH#G6$20ICELFKTnCmRlzaa&p7}NvC!V z!qq&8V`;BAJ|?QqG7dtieH+%6I%E2I?4-SV`Knam98D%fP3LW^2Y6R2w;^XF8KH|c zS2|aT*sTBRbu4q_^~N^}t;3ARZWoU&Y#EDVAQ8Ts3V6Z->9-JMJJ8+r-yW74uYyMz zA=-Yre1aEZ{L0db9Bqxx{lKjm(HDeBP&wO}6#>KS7tK%fc649{qB%mjmbT!bT@4{C z+jn!c@$j!-;BfM^s>IaCx1?;2BU%uwGj^C=wfKc31t4GkJRQJYG6g zHM}ox7-;LliwAfDXow8E_?pon+hC`Q#aMCq^r+VjQ`K|k!DT&$*Z#Bny$fc4-4DLk(Sx7XdwyG^Z-mavu~Kh>L2s}5pg zknq^Mv(3yQM|Ez$0|cd^9tTjz{jq{dEhAIK&e0U(#}&_;9oEzLnul1Ii#p``V={ie z;17Yd(Nrdpyiq@!Lu1<=;_YGi^c>tk$aO~%GNH;IAFT^JbV@#`sW>1`4_=6`HqpOn zR8(~(C6s1AbZX5|_obYyT>7L@T8Xh-UOdUDrq~<%9fkcH(Jfx&XDd0HUT*ygd&B7y z@4Yv=zTjESm(VswP&WCWzoXS<;^A(yF+j04*e?t#7q_LmvxNc|`e}o^hty>JO=eDB!`o-`s zY$@0*cLNza6n8hdIkqerjpR$k#%OR*M;>3D!+kjRhkW_GH*h#a36TluGk+H=c0E=1 z6sv$U^_yN5BwTyndaOj8YgWZZ>qmH65!9ppKNZ>tvAph9pL17yg=Oj^!?p80d}E&aX2=) zd7Z}Fh-JF}Vil^Mc)JuXX-YYf)t}r2ZOPfbLx_8;C>s=fY#apT?*z0biBr%#@12Vc z3=!*)6VzRxYRLJ4FtIGR6Y@%`k(YTrGq_)zE0=q`?6GJ0*s3$gM&t!X|JTXRVWu3V zrnDb7KTmdI_gdoS59ICOEsqPdPxYzA;kb8B1@19R2r;Ax%3MAryJ4M@^~Py+S)njR zqbqO=kNsdI^+_Gq_1Z!m-t>v83@VZd;duMI;D_Xll`Q6VosUjW8iP7S(Wg_pX?LK{8y7B9>X&T2V?L5oY7DIYmVNtI0_$ZU1S)e(* zLNgdwELSOLbJ5G#=g{u7_VT8c9CFF2QY7ctXJPTTYy3^}X};Y$H+3l`uQg|@EqHQO zZ>Zsw+|F=Gir@k;i1+Pa2GquZD4XhH-_GwF~V?=z~;iLD+(=5U)B-c`N3J805^KDstr|WtQhHPc?^a0vJ;NQyREdCf- zmh)*IcM`dL7zqHpS#qJhEk3i?K`l!?)})NggzTKbXtY1{J6n{R?VZc-Joh z>vM%`PQzhK!4bGl%p;vhjjj+){0r?zpN&gYW+C2aCR;l=?T$wFJPh#hTi){(1)Fz* z@q>PXJmIH`!t}!5VDX~mZT=Hd)_MKDlF;ajY@)N%n~pp~5K>vOWlFLXt;3K7F;B8w zMgY9A!!Ib>`7ii~d;;CB>~h8*M&0Q8Dz;NbSu5Wz%bv5!PC3N1JiUfOEQN)};6KFz zO}qyjwErj|6#ru+^K-7gQx)%QxzroKC`wo-b+;wTq%%hOk(>Em*hRVE^HN+ZmtnT# z_^Zsu9?08iR*D*HtQG+i%X9&z9urA)8(wEMQL!sEsNKpx_~B3P0DjU<<6%HH%q&L4 zA5@+EV>IKVU1ZmXyeJ9)s=2whLOw4d|5fA1@yCe2LZxvtU{SPolgoDf?52c09lt8C zJ_+1Se#88V%)V`V56`VCo6P{lTP}T(Y{qyt@WUpo+=F)UrMfDU(@wPa_f-5bSgt;= zn?GnFi>uyTwyG87NXP!8d(pX>Nn5wQXKde%h<397oycq37ZetFPTDHDob@jE_)WUq z*#val4q3mT(g*;Z3@_1-e7^<6$JWyX?q&(k61{C08?~J z$O0&RrSkxY`w{4lTmix+@kB<5EwC32xcU5n`1Y3CRUf*e$2Z6V5JMH5fo$!9?xgt{ zLF4##K+*mXP^SM|gXC?&KAq12!9iJ|ioYZEYqSD8;Sy`-K5ovkcXPRW%x3F$*{0A#VF;I=}y1 zRb)#Xz6(#(6h-E*Mn&pKY3f4q3dNl$dPWFA{VZQmJWe#=O=zx~#Fv-R5?;$C=FTfr zDi3WKPj#H9!F}UBYlt4 zxYUG#O`6-{7ezzui+_9@#4i565|`)o5k5b#<0q~GI6<4@-Lb3`Is?rIPY%gI7MzX8cW zET2Z!PwQH zN7B!T=^B}ne#Xu;S%0DapE0&=l3V9)&?t0z`&rQJ#`);FKcyX8gcNNB$z>zMLq)dX z)(*X>2i`gE?Wm=Ie_m644!EVEfIpXEA$+wuVkBfRVKkBw{@ccd{0>>|_$SN%Ov*fG z`L2ayvA(IflSZcgzG#Kj;MQQ~(`bKhCV|F&F9IX3()`&r6NO0?wr2@0w=sNNai+pw zTf&rvL|orJwpzEAn0k%Hd#LVm-Y+U^l^!U!We@nPWI5{3VUTpoFQ^caT|apR*v0mp zVV_n-J^o$wY#%;SC4}gq`Y>X*^=Ro60LQXI9bi+WF-d6yEA6DMftO3#!!+UqvzVfB z(ilR?$mkMIwiHHt9WMVF4f&El2VZvm;vugZVuyJjD)ZR$Z>`-K@Pzg7#CAS1q|{o< zGpasZ@0Jt3UXUuVV#mTtnXb4FO!uxCJZe`KA$B!Ld83dvf%xUaXDAG>KNu_T&(VSg zBpt~DgKZp6$Eqc>aoESpl&+V`&f&(6;kLLS{Njf+<8qH91}C5UD_ICe^`~8(xGX6b zc^uGOF%*MB>qRv8Mwe5t2$ZTMWlRre=pGrV(x)Q)q znQ#*PFXZm{;Vh3YrSBB^tmzXk;5_mKJ-)Frc+lY!Tsuo?qHfXmgXe`uDq_vxyYb0# z^Wi{v2wyroNo=8yc^#To4N}euZ3{*stLWEloeslueVl#kzhA#I14R){Ec*xwemiyZt^+rkKrU2Q7 za8cXRVN`$QP3r90zcrTqKj2hjg5Z6FbhSmElX^CM#rLW_8p=m-+Mcd=b01pm?FO>Gf zBX)JK>4INDy*YzRcvJyQkQaan;sWgWKTN!=3SxtT0v5$^F9kt0kOXtV25dbJ39q;S zq^{?Gj45u40O1GyTlgLRF8lv;h8ZtTSCL(xK41zBMIduw7{Gu#8~~o(Z-wyZJ0$}3 zyua->{NHw)%I~`2#q8h98tM14F8SSwz#M6iE5`w}fiWYn?+C!bJpeUq+g1X98!Lbn z0DA-Wx4kg~G%S$#+x;!{)F7EJ8{CPxAV1)>0cZJ^gYI#GJ;5L2zY${cxdgBSq4ovt zxHEv8x45xC4oJJjg1plcfrKys@O5Q`5OH81@z2A2$P8eiLOByY;mZLI=Q@zpZzF;m zFe!ewpV#KgG#(!XTPJ?_7R?_XLeGaD(~(ujMks?-{#cT=pf zl`&t(Z;-ZH-0#ez$HY9-3e)&Alu((_$>bxZa~@FVldgDs(UhVT?Yw@lgaQJz=nDh3A@5Bp0j z9wjUaD|&e>QXrX_G{K0|V9^dD2?_&&mw<+IMpgbtrwV_2XaB`H`;b!T*c74+#h`p-{(O({Y!>i}30s5K$AP|78pEoJurr;1yr=Z%7ydN~K&OAv zk-;t|hBj}2S9V5LZRzr;N>M^5V!o~Z0ZOcKN&ikikceRnV1qW z$eP-kJ6jNOFfwxx@$+9~BQF=?IraZPjRaM1=gP(MgP2LNCIUR?GjlYexg>x2m zgAjh*P%FAgtSh`3BbHMt*oh#O5vkc;=;U}j&1rcaU%q-4_;mw5T|47gJb3C8fE+D# zf_@P`?Xc}kK<;`3qAC!to_QdTjRGLh^9CX4`6ff4@f7qd@Lt@a{{M_s`dl2h>*NAzryjdqj;WC3%}qSwq56~x_1`+O{tA}arhQLe{xf- zS>D~o1y`{LFJU=-dDrr=sZ}X)^+8F3#Nl+YQfPSmx>d54K6@#uhh{>y-MLP!(tKg{ zJr{2H*0GrrU8RXo6un+TsYCH}pY-Y6<$!_y;mleonu|(m7bIlmIX1F32O_@tHLR-n zp%h!LP_Q8Z%>JuLu;=;f!8V&Oq0|i-YfA<-63O({H;?otli4zt@jd*3H1D{F%j=~Z z41R`|d^h^LCrDf8v*84*iUrTLapKE`Bj%oYnhF9lK6d0%`HB6NCen7)aK`KM@W88^!nPyPCxjT^MI`wUx{0@!3&&4QAhPqke07v`<>FZ0Ou%wV-M z5=sKuY7b(S{OYzm&w*KaXnC(Go)+x{}L>q?L2#Fca)pYl;_tCHOyk15Hs zdXG$q;p<4jLVSRJuZ`5wu4?dn)#uNz_6xoAqT^8xgUQ890$#pCC;j12X-G-rt_Lyx zvf%(;acyQ`S1N9#vE&ovXkTqfzp-aLci@_iez-DH*h)3KYL`wSG4r%&P^wv&F=vdD z-s+nfU?kPBDuxnt^(?(EZmwLIofNe%FT~CBn856+*2=P`1I>{TsX*7;#3<)C=sWq< zP(zBtvPQ-pzA|@sM+5aX5q?vnQSnZcRX``h%?jk@)W@u zt+ZWACV}%xAH5QO;GtCBD!9KXp6wKpHT6B9GP5AJ{oainkIu}RuY-5-{%o-Q{Z`)%>^P0WDjqnMZ$)_n^O%mT zQW6`)xZO$bNA1?!;T9g{Nt7hQ;*jh~4_+-*FRn09AQ=-!3H5mtNtN0XN4dJP4oNP) z%ZA-W-_lg9MH*?Y;NoWE>4r&Jo^#T-B{5zP4ue5zFi0_1*ske9dn8U7UN9{*N)ccL@l= zhOqGkI~^m~_Di+g7^G%$0S_j5M%0)y6z39ti}UpJyrZ9AUx%rT`b<)f6VgpH+U~r_ zm$T2^38zD1GMIKFQ$q8{pQ@$~i?*$vH0#_=C1+M`)?7(fDIL{)SRF7BsDi!MUWq36 ztnl*&zg_Vjxh|44wy>v8d1MApCfv1;VyKuD;^_2RYsU65s#;DI?{4(=bqni}l3f|7eA`1U}DLw4Zs$I0M z2*`yLVl%pVV|nv$Zj|T8j0p!Z22Nx3#G9!FkZ-Bc?H^M19A3Q(X>_L2D+%5)`{|Ww zU87}AE%&y@#@fje5o}ONziKOrH*CGtZK2Wd`hXpOReVQPyHg%_-$Q4xp(5egi~nRQ zP87ik+Y(MolwqoPY_P%Mlq?X{Ngrm!%te;&f`f&8#~}r6CV2|AmVX91Wy^8I?R@Bs zfHg0(Bc{1>fgv-`=7^H?u=hN-M#XrxRBXCsMK4>;W6aUGk%3vR$!ka;8wm+VNTP-M z=tH?0E~ZF|+4q2+s4$uUew#W}oDY`sSZe0NAw=ku4Q4h|zDUHcDf^=r}sb6MP+1A)zQbxaI>-%Jdkd zFPU-KVs&RqKL1MSl0Aj}fqoCSX(W)NV=^Bvb>q^hlHWqP`IT@cj0y000k7f!jya0vWO`{FsI4=yd{&hiqugKr?rYI|IcNIzuE47ui+FK-v>JT5kt38(aBEV^ z>+as@*N)WeJr;p(>3Ci&mBe^*(xvHB^|?Khy`QliuG4)-sm`tZ;}%s)h^Rtyl0q0U+eCQr)qYd{lKkC#vpOR4b?%88$-tiEX{0in{Be6brSfEG-)!ssbv|!_ zn8GMI%^ve7VR6-x%dqqS$u+T2fBz68yaaT|i!x;q(a}q^xDQsgP!t^Ls-K0tT|OrW z>xY)Zn|{O?{cc0egDb66d(Tqp^6A^gD8`6Fr1ES2Dy1QFp%)5-JLwpbgGZX$F=7gs z)rpRTXp+TA5;%Q~wey8L=smQ_Kk72!d?YK3LpxH&toSIMndZS3lyi<(4ssw`afCNY zJvGmcAL`Z(v77IWan%Ckj?EZX=DZNz$Q{pEMGTptTEIl$%zrX!{0LGv5)itKpGwRA z;&EqaWZO;JGVi#UdGUG}&#!Xo^QE}q+D#GPH?1+m6Y2#LGq#bJmTW0dF4DJ0agxTE z)S7|lTd%F~wNi36Q7ZSq?Y2roin|6nctOg=X0Qt%2{mx5D8p6=2*qX<6MG#Q)MSw* zv7^bk$@z%7$~5WeUPxl^R14k@DV{njw-3C4e}VbyTt?^z&%F;W7B%h4W5HE$#DOv5 z-NDS&s#1$wG$zVN-`vr~{c&yO*M z?%wAcUn(jADzO~LwEK&#d?3%Tfyy@1na7OXHY`EC1K$|BcY3mSnPub5!2km^czp4* zY;mr{C&xC{9m7;yOX-&mE%xi9774C6e0lT{RhEql1o$BTz9uOe1A$HzsAaZv{W2gw5293Ykf8{RWIzD z^Y4FaN5AE1CgJL-!EAbo?vGlyN!c0R;#r>5&^XM#v6-B`&OJ&{S%oOwwi{h*qM?TEuG^hB(%uVSsj`2O`TVvONKyfm+ zcgsh0=>q=CVS9#)t*_n6JQ~r6Yh`th-HS3$>uATw$9wSJjFc&yc5KVNZXWX0*vfo! zVekB5%Elf`IUNyfkJV*T_|mZ{LXF@p*%h0EC6dc`J$JUmDqSkdP7zE4!PSRQ_|ZdG zt~03~W!u5`)l6}DVJ?olabR?04vpTP{*HPi?8zcD2kI{_57%vFpHCuwut9epoi7ry z9+*kQp&pQdQ0}HG_k?#YF1*I?bR8b>MwbeeH$Cw#8=T7aoMdY<8drFmw^`zkDdf%v z%>?iG8nMKv^?9U}2eMNSi8Iq@hsmbD2N5{*5#Tby99xL%)^41x{4CKtED~F8O<&UJ z9__@;S{aZMGv6m8P_0h0=d)H{o$O_V`^jzLL-Lyf^+LLeJUV|Qk%J-c0)wVS8 z=7bXNBIHAtFwgOfww!S;ryPfxY#hOr+&}^^H4O8X@sHB6{f{#b`}8RRm=oaF=~jJL z%O5cv1^X6kyRGH(MsR3Xi={3JyBS%ydK6 zl}G%t?{Ltn%*gI!zgYj)+5>efp(GyjJxs}opIae@LK)hh=N?=`a~>G z4n(3z>9-1}g|<}lEFo#CpgAXF@`V@6dE2=|S?Zj}r|BGh88dcG21~LVJHSk4t$(0Gu=;Y0&8+~B5|d?Z zdsHF6Yg9Vct4&X^0^7qagNGM!P#(PFICr3t=XkVaw1hLEUCz&J!ug0d%V{-eU*jiP9Y7Uatr?e66vmX@N8w~wxhwo`B0a0j zM@ag!CKjL0pjz8)ulef zm{tigNYS>~;dQ8UjMidaYOT01;=a0(0*C8FCgeqE>W6Mj?fb!U!x(vTDl<-8V?mte z)q5W!OHlR@(voJzRXFryz+4I|`-zUDW-P7-+R?_!c83+FE!RH<*=*Fj&7f1e3xwF3 z%wgCly%A_z7AJ8FW=*XqJ@cf~(qz59{#03tEJ3cA%HRATtX;OpoZz6(N!>dJqqbXC zxjBix`JuZlEb)lxdf`}&M9;JMbrw&tcuOcF#gKI}>#jWQXT1VhEYzj0xIzu9FTpcq zc|Dkv83zSc22irez5bUr1Vdi^{&py+8& z1LTFwoUAF1^u)Bfvr3LYQ{b__wt+KPCl;`9eC{lHU6L1eaym=h&MVieL3zMWz7;@D z{4UX%qp{)@{EIV=?h>ORN*E>F9aRgeLpa@V8>-5PZ@p9oj!%-wLWW*Je zB*>50Wp$a6b*~kh@l>sm1vE|)ZJWdhc06eXmvzfv(jMvndxy9q%*^}RC?*tHZH;$j z6>U=_c>{aJe{_(!PD|<;xO18l1eNTt0jh94;_CL~WISHBwI0IoR4z`oHJg->qnV`4 zqevrqr-fi5-V~L&bcVP7b(WvdwBDXAP=9I=wD6ejQn9t?ymgNkie6SN5^69Ab$!;Po{ab5!v9(=Vd0MS< zoM!NsGRxDlP6DD*_aFSCG;ND%l7^`;UE{TTb zi`YI_Dh(67cE3Ctiox2v4%`5Xml6yd!Rw9(4ZLlfbK1B!(<9Rt;kiC(?X@=Io-?C4 zPyeD8H8k0q9j*ac^2!QjQsS?iuARG$=lXuq!N@p66a>nJhX5n z0lh0HMp7u?Q!cFmoZcd1S4w_6{mz~w4BnbFS|0tby!3}7U9R#DBB}x6=`yAS%7VqA zYAJEeWL;ZnVRI}u_fV)gR#B0#noMftu500*V_hnm7V)gU<)x@P+<396-MLh)Gsqmj z6GbtqNDq{_>phV_b22`a`Cd*s`j>m=nfT=$=Y~%v_72e;Z;)pr+dwZz_-e9MGlA1* zk>oWm1aus(7^jJx`N&rfhrXF^@K+89PwuE(4;Hs2*G(0G$E0($s8mvMqerCm zEZj-KZdiiv6lr^x4$Kui{Hmtyirn>XZyWcx9cxMT>zFINFwjIkTEL!Cdh!zYc%Jh> zw_6k)=Bhym)~k=ibZrP6D`P5m%Zlz@+Eg$P+WBE? z9zT?^f4-~2E`cENnc5_&y5Id_x;?X2d4=PnqnVc zF-KK}UaO>Y(XjC{^jOk(gl3eumQvZWGQpmEURUs9?3r1qln6jGba&L4#;{`$8phJ+ z#rHL*DepM)g}c2@0qmC7MKCdKvc`H{2UK>^r2N6w#4CNRDbyhPLT7i(Le|$PsTw(n zs0VK~ila%z`(G&#%V%u$Wb>|5&DhXj8nx4BJ;BRrB{&__9aUtQDxDt>;WGzP!&b`A zH#^6#!vGnuEmZEwM`^qD3T^|J2M-QRYTAK>SSkg#61NeO-^NO;cdpfSs<%}*INHs& zO$I!q$~0gVkv`_?j|B;*e{@^({^-}{Hq4>Xz{4NU$&$%=Gig#un}TgP5A~y=Y+I^T`E-irNIjOVzqB>i zd2t(z1K^^>UL+$_eL{IOUx;rRj;My^hf@+$$OLGOY4m z+KN}`oVK(t?(d>-&DW42@7{Atq_)gm3sVqruFf_LRdg#E-4E0D;-(ux4&LaC*r{WD zt>UTQMp=J`{&q~?ilw7=fMNZa7i*Qyy}&7FPn%++K^k(LV1J4GtMbcPP!&Nvj@|oA z_Qt)EV+4a9C$1oRe1(3m(ZTr{cV>N=uf` z_BaaHfj&-oJlz6X`bp>*D~V1kgWx+pP?L;i_4Kv)xmD}cWPF&4oL^gzv{TyU*M=-b zuTn}p!QL|5Jq#$%ql87@iB1AMX;LnE^P~w_hWCsi4#KDeRhauU370|%>0i$rLqjCa zjZ0yw5pv7&?xzM+#S|-DK z%LaO&&1IhV5SR*95Q4L~E8fc;dv-xfXYk3DCVp~ni~Qkmh8Pwkyz^d;^XuIi0;2tG z069TtG_q;-_Z#-H44P1>K_`r(u7Z`JEkJf7|6G4a3kK`}a@+hnTULkByAdl1@tXkh zcCycQ0-3P$*?Yn`q%O`As}|i{{aD8GDibVT_G|3pB%HvWMW?^XuICZLkzVC9?7p6% zJ9^MP-(mW65^|c|$af9TAzp~&i#AxI+&P(VB+2GHs5@rbA*hh_V3QfBt`vly$o=8k zl4;sQa#K8kA1{FVxNygF#Gp=2zGOP$Ws_OzVIpNgKHVn@uzXGRE3;IVbrt!GHUid4 zI4@J#HFb76=KPa6JRiL50pP`L_GfZ}F_VG4s}ra^7hRcWt|&-RYxS3|0n@_yo~yIZ zA703p#Xr8tqo8(A=8+~3keqh>EOuL-A3adLjh@Ia^US(mWV|m_c?i1t&2&T>qJ{9Z_VuXNU5Swp z+3Fx+0ySb!QId{mzr#@>UqFDAT#iBie6i%($hFq+b8EuYRtE7eWIBqBtp7E3$NEpP zJ7G&_Cq+|75jz`uJ6ls*XCh7_1`)eYc8)6chQ>e`Pt?@a(%4kO(a_^Ri1SIBTAEup z6S1}qv6Rd2TO75(JJ39|84V8bI$E!=8Be~+1+T{ZVqpZlS5Z{;6un`F5`#X z-8-3(PS-o@fBp9_8vKh7{~RWtyuW zF5Q`PKg_AShPmb&ee$$+?G}NF^eJh@fR;bB!~4o{!ZDyrp^Ckk(s(6h+zkYp!Bl_t_TcsQA`VYK@d9-gXwvOk+Hi|cyY z6nx{!zIjvlOXJyX>HPg^O0#!Or)Sp`#1^)y=V9TOhDcfEsjHq#6J^s$H;e~w#;(ot zk@2rpxjUTaX(m%?oV|yxUBW$toUl{B^z_b>Jq=f=tEx70M5lD#iQt0qbl@YQ!SKVz z!V=FxIO<55)@II_9Z0vsBDobJ!J~O}eJ5?&Gfp0XzP0l#(f3}5a@%!ztWD*cY3Y&+ zhp>*jW6gro2}Fmv+=>tDaUcVg!Lo{?wMduYF{q|qn;w7B9uKs8vFrlQ9Ih9w}oXR z5WIWnQB%Hf_AqSe9$!v*e)@V9ob5{y$iI>$o_cZoxYS2?Tcu?(S{@g1fs6?(Ul4GH7sjf)4I3gS!U{E`z(Xli$71 zbMJn3-*@+q{in;itIl*+P4}rf-%}?xo1i{7BZZjb2O*^7XJvMTB7jrrUQf$%IQ$D% z11~W2x(aXN*|iMYUfS(I$lERLC~J=WaFM};$FrK`0e))3O@iX(#LCE6=#1-o{>Df5 zW}!6;&MKrfe?M0<_|xamdn2AYhL*IB@$770ZyoN^g=3^Y&&2|JH+idf(w2PoHbM=> zmGz}fE0=1l&T~i+9R^CwW6fy9L)}TOr-0rJmC*2=~~^{0Gs|6R8pi%-!+TE!#~p z%xU&Si%74Z>~oCVVe=NDz3oOV^w+&EfmYQCGJY$#1@+Ao>mR=EEBujO!!^HltGhnE zYfo8amzmP=he*wDXTRp1F=89MMl!0j<2OGQZOA`2@1BY%jV)cbx!^7WHa0)|J9M^t zG--{D9dYv$v}(LcU(oME1Y4FrYam`yDV+QIuQXzx!k%o5a~9S*wdN8CHV&OE#ki}m z(I-pq$oV{b+)^hzG#b5L<`Pf_D;&JKJ=+gFKD2krb=tmL*Jsr*<@6aiH6N5eeHK5B zQip7$``hV(g2b@PH-oo4*+%N0XI{Z5?7D5%2F2C>vHVJ=UNaV)zSjy_X8|+Hs(#-4 z(G&iyB?Aku9b-b(&35)HDg&Hkgc>9DR2xtICn#Gkao5`<1TLoyb&TTufW5KKt6c*f zb9FZcgD;CpXT0TWU2=`-7b`=I_@LH-B6Gl#ypJo5M3jmBZo^A^vw=Sk{g-wBE1hwo z)6Cagp;tPgyW6K%=LEsmrSazRHiN~9D(v43`Bf)N9Dl~v5j*tw@AzJpd&a5&$bHx^ zzs{f*c_Z7Lf``Q~ZX{L6zp|L{dECzrPfzFF=maC8^q(7%FL&2JA1y(qcVAnU>RpqM z4($fc7V^gcYw2!J490t1(K*AEdZi7l5y@L}_oA<(4}GU0!v0E;&)LsaHoU*>h7U%% z`JsN$Ew1fiM&r|0XP)Q%ozOwAJ02caOV5|qt>5&Q4K;2QXB4=YJFbXA&huRyJyS>9 ze!jr}F|XnwYZX>~s;E4vBtm|-sd77!GL{gj zZzlAD=RBVGCT_l;4Lu*j-Fx!Z9PI46am#Z}>Q#0kavpnR>9gNz@V2HGeeP#7DORH4 z%Y=HQW=gLlh-N*%6=HjBHM0Ani?ndwZcHVc1+?EyHDu|i3+1}yje5TPIkYpv?T&-w z^)olKK9bmP@T3CtJn%Z*iRlNClXyXCJu?tb$V*QDA#iXUe2cBnOAUD_gYe=1V2JmW zI(@T#WN7;hJN$m8?Xlf|S?GE(u#F(pPqTS%Xvan{jBCg7IQzo>x%y>%z2Sb>D^YO! zw_w`Jn22}H!EajtAp=spp3aQ{jj#Bt_(f#oNd3_rBcV^pgTX2Os@d9&Bb^}RcAWQM zW_>|jcbsD;I3AbwM*5|oHQUn1F*Q)Va>IPR?6Hycadl?#D}vBBY)0zL*ni_M&%YE9@?A}ko}myu*C9eK z`OQ<-elmGsc{^)h;byDA@vMP7w1`YMbSUUpcy0I8cH!PA;4Abbet0$6?z7#@&{#yg}t0UQpHR9S^Tfs9>;&v z_C)RNT%byxGcgM%G*_9JNzT;75+GvlL97E!GZS+{bu%s=R$@J3CPjeLUv-f4ud)a| zQQ6el-qp$2)S39N#iy8ui-d{`)Y|i(lms&|)Kk>o(*y;fS|YTKf3n4yiFy833cXsK zg_!-X#pvHCD>3_Dk^V;6h}r+MdU1AQ9+rPsq3C39tYQikQwJ)?H*u&UY3c#hAOF!R zMgB7s{m)PeD(v58|3lYQ{Hu3D%}xJhruz41{@<9Xvaqsq|IeCertfj7D~}!Y-zDM} zw#HcJB?yo+vpRH&i?{nOM9Pr(z0?Vjc+9+@xz=>e_(^g=9(Um}#YjyD#hyX<>wW0x zD_+^t!}{yXfdA8v&MVz*7qLS>imiLW=O{!fhw+K;E5FE{?DujwjYW2qRjnOJ7+Xgei_ zFH2q97qrgwFe-?$QXpygg6E|G;KDu`U5cIIul8xHx?b`cW=P^AwN)m-khUG30Y>F@ zsH^TNFOiyDK?kJnjCg)Q;Cp0y=N z&DsMiahk3gM`PWI89mK1h+6~8cqt>8MWTb>}oGOxVTZhda7RnB5PDo)~yXHD>8Etw52 z7dBD-(H|Nn1~Oxz%UTLEtS;3Wt+6VL?6>+n(awP-#KZYU+n%1)7>(7sN_+}!CLX0B z#7uQ$6tO6Tqm~}*b695mZ17qN;LY~ZVo*)6nQr?Pqv(0f4hU9uK@)J_olWWGrHfpX zd8+7&a>2X+7kAD9fg zXp&Dus$V`^S9s}#J?e}j8?kUZt1x4kC&`t`y6TTq_R6M(h7H@N2o)-P8rx;4UHSCs zc~mGys4nZzCc2jEH!9^tM#6CIKJj4NwI!>;>fkut!rOh5RK3))Q1HM5VnS+#*yuWB z(Y|^>&bY$jG+5x|cTGt3-7K7G=}p#gRiwqV^^I)daBUJR#?3%=$$K zwhV&-$Ekss9`O;u9m!JD_*27HZIK+yMf}`)nXB)vx?vKBK`ps&*1V9EPkQJ}lG<{@ zJtuYqBW?OG?hCXYj74_23gbY*O=Z{V?P^HXa~~Hed2W-Q)MsLEiZBiu?;jX?aHY&X z%2Aa>95{l_p?A!m8;U^H?T76994)!E&-0_8L=NAhYcedlaJlbc9EJU7JPMO=5g~un>idT=a;V`%2N<-pNJ2S7-7#C_=rb?vIrx)R*H@9d!=&Zd#mzygktG_;-p=t(t7Ex z^Uj!^bbE@WJ*TukMGg=;Bh#inxMc#%`2Qx#qHv}6#_>!OFs04UTa}T-WI&%9@f*7y zB5bM>QrTZ|f|mhRiu8!yd%hH8h{qkg>4z1;*?5_an9e@gHhj^zYZ>E5`8$>MvwhxV z74kK~T3hu+N>m|mA4h;o^=+fsDa&L6LtxN43g4S@aCk=Kfa(hx4I@6-?={HLub2NG zItVR79cgH99yd;Cagy_lQtH`(8w>@%)B_$Hx}lG`3?y3-de?=frBbn_EK?p2%Xb40 zp8hzh27zAyxc+ofyW_^TM=Gf9G1X*GER5;#-2SZ9;gA{-hSE<3&s9=BfF6}vf+02) zV~ij<-i|xjkK+J|2ypD+hD>DSlyTvV6xg^W!|M6=aw}i}My4@=GE1a3JShFmfG=cZ zHd=mfglExEQ;$`WsPO20zQuCkU<(=&&<)ifg3o!=ktnVmbjUivgAqZ|&9Ld%iC$sI zkPY&N-iGM*-cd*)g&a|`m933-;#pnF0^}DeCmJWQoN03W=n++&)Kswp5v!$(4`-5l z)^#N`u=ivl;ay)^WV>8}9^y`Ij#38)!8NT)qCVtzaQVcBY~V5(&g0QD z8}>Hx0}RDjHJdbn(Bj}vmgx_6&0TLViN!vU-yAGXAWS^T*RC<&GZ78)OJTMWJfXrx6V_12gpF*_00gE?vIen1`J^9b=B1d zvrV>>D_*>bBD1_=c&-5lbcO2shF)AiwL5in`DxWXC(g)%Q6E**r9p*-#h>Rq!y|%W zA8aY2;9U_7~agR9Ml7~d|^@VHAQNfTu>vSr=|+JZbYvnUiIas{X9@{Lj+l<^@F z>jc3?J4zViL`}esbNKkBqueGLh3^sPr-<)>sr{x;s(6!lUJ%ZA{Sv&z9hi*2KBXrQ zM?`1dNop1CSU_wlSW+dzqa!sbfOd;ZVh@I8l@lt30O zA#GNv)?-VO7;okh6DLI@mw&|i4bi<_$whnCamgET3b$!6#G8#%!;4iB`EwOjJ|QLp?6m4j))wd=DAqi47QH zwC_D}mL@au+BWep&`!l;3CWPvQR{u`?>Q5w&q$&A9?V|^`qaVH-n+*&F->M3t${;25~E+OK=@G zEZ-=>Vokfq|?b&K!FeSB&#mdoh9js>A+d=BqV zl(F{|?ykDyHq^ghaT!PffDH}HdK4m34A?_s_gzK5D0pFSLFI;^MH^z6tpHBUIY+tf zCi&oyCy*eMIb)4;JYuA$Klou<4*Q-wYk(vQl-|f;D@wx4!u(JHhWB?j%vG zjhe8kVOyVnb>X0nCj=w*F1tQ<;se=IyF#XN4c0&%`SV%iv<^?+E>csxU&&PZaE#Ov z^KjSj5)ida#zyV-GVe$su0ENcAMwf>Vu#&S*j1?5d)VnOYoC?t3)L^oOC0RerzQFk!jz=2%6-?B= zJ-h2wpid~}xjU^=+~=ZC0Ih$+g3q)YHp|Tb*nJL`M>DKdJb~WVV}hAxVO@GyQ-(Tr zpbS%tyZIjMWvqN_-YPyXqhVkrJFtsEE zlJh0Frf2DvG4G4m$xYn2JNcte!BInPwZ*(=gj-{&p-+QN8+!0EFQ@F-p8myj`Qbax zBYByA2?wlSxE9SJ-Ug9+Cv$o2=yaiL?3tN)A(p}v5|1K68^6p>k)MN{6F-^WjA|km zq7lHTxeQpitTY&MU8Uu385LtJU8322b`zay^Ldt&lMb`Es@p-%1ENW1fO{6HCaPTP*Z#XlvD9``yXPVzbl-dkTqPq-@o_qpu^9n&Cfn3GbwTm&GZjd@(rjxiuX@vVZ;ua=7ZI>8N-Oq&B z1ZQFEKMm0;Jz)cfrZzc*Tkj+GAKlQjDRL_q=x~aK6f<0#9VE~q+}E#J85g=$v*<#8 zz$YDTrw-=@Q1Ic}5Hv@(S%tAW5s+P@5Mkx1u*STFlL(xghBPI;e9+1V5c1!)QNLiR zI@psv&G#gPXhg^OZ0`kR^5h+0)<4(7Ds zm{_O2nQKHbMw3?-)Zq?QG`n?f{RCe8W5wDm&mV+D2bX_DKY zs`yHvjJryee~YH~PW_am^T51PKq)L}#7^K47C=X7v0D-Wx#PMpnq0;fq6GAFDiJVPU7P_?1S`Abu6rqLfD<$ z!W4X>s>#b=VgccZ^&GK5$iY4e&lH&Vdll}2D$`ceCvDg+^(sio00Cx|>GFqgxX~7b z@IV4 zeAME@(ZDc^RKT?wnF~B`vB}fSy^gXX&!oif37EkBh|+@j^NKYBHH-(H$~m2Aao-WD zrbVmHN)tuJA=qJEcre9|A=N?yDV{W+$FzwXPnDxu`$rtI6g6$6l0d_GJf6OgvBJ5l zlE<%et@LbP_n-%2Vk51Y9GfWH1Uu!Jl3wZRVy!%TDSAjm-%9XQWzDaSy+~W;%9=1! zzf7{00gEoP-;F3ls)%~L`?^WtU*~KDT6E+8u|jO;!AgTqFB9uAEN+)S)KhRR*@>{WJ?w7%|NL5?_VcG4xZt?;<6=RVVJk!%DCl+A`VK5bq*0$uI*N=;Ksb%-{K+NnOcES`x-j7 z;#?A5b=EOM^z&hHg5U@X3nUqg!^Y?!4IL6h06Fq7#_Ctip-U(B@%5f^`7{zl?}ISi z&?Hz{%fpZzhH(oLvO|*b`nE@}iM_cg5%Gt%w8|fRI8&K~H)qb+)TB=K0$xY&+5zKw zIryX1pRYoA+<&Yw&kJ_-tx|}M;{(%+mJQUvwYHB=Zi0CR9HDo_vjQyvo>pq|%0*ti zcefr9WIocuc(YY8^e9g8hY@VKe}u;!j>%Wz>pKh41kboWb`=tGJg+id-`Xoz3)+ZEu#CRo zgUu5?V)8QWaZZ)ZJ8GQf#vz03Xv%kY)>m@{O%=wzmlQuyI0P$qI%_Syz=Lmvjg8=x z>X^=3TZ?ip$2xrv@xkZhMR9{o%eDj~uhg%x@)vK++4-cY?qM%y>Gcd3vL@b*P0Iz| z34*We(ANzKDK!jiH@k}`=`z(3hu9SP6KIU0?2CEtW_MJ})NH10O+Nv~_}y^tMy9Qt zUJ~j=rOSp=d8t48e%>I8?@3JLRAm(# z$$JI3ZH`uWyK>)*p&DTL^=$+ ze|m-A8|8LIi3d6K9_6Nqa@tmXNZNIS$k(28StHsT7^l`GDb-;Sh*V0_x$bTD>=Io| zfuAS)X0qzQRN~67h-3#4u9Bu?z7A;jOd$SyvO=!BKab&w zCK*74td$N^0=Qkt8)eM#4*ri`mC+6c4w7CfcX!(#o&~IXSYZu?9(Di5?Iy*^b zmGMjTO`@8zuV2wiZ-qD-TWBqU$_?A1f`$b1po7K=2VN5T-3=^Qvyvp_KP5pew#}3v zgPV%~E+|@*1Tl7hZ_0YQ`lSRi88<27p-sIJ2NbRvP0Y**8`aqDwt3Fa{lp^_dxd_$ z>@Uje^GrD?y~ey(zK!TSDh)g0V@|=9Vyx~I$YVw^vA?16ax#rx! z=~I3mQ(llDXUoipcCPlNv%4v&1+;mG2(I6)9!p>!_fvkG4pR2RJR*qubbtVV6a04e zqG(E-Xiua98=v3Hpt1*43*C-4!&JF@@{W5iG`25d;@uV=;%gKAy!-m9r-6q}@$mb% zb_PPazP=wv=RkX9LNgnOM1Fx~ug~eylDhKx4^32(ll z*p@X>E`~^O)&w{jFKZ4=Q%@C>=KyRoNP!T+57&sh-OSpo7UrYeW|qHlHhyqV61mMr-O82+|H zQK+G(g3a1xC=(`IWJG2nZR408*({r;xt7iIkV8v&{6#QuWXz4-CWe#YInD_ipS_s!~0%!7g0X`_Z4KTRZm%Gs`}x8J9%<41e| zpL9%qv`KNcj3HHJJ5003tu~m9D;jJF)8kBq44#x!z72`-aLD=Ojq?-y>-P_%zC2T~ z`PQGy8Ms&7(6`PzhMR$N6yaXI+>Zo_$``}44bk3!&wAyg4X3WUQNipaV+7#-k4mr< zTQHnBT%`D?9!S>=T>91i{Tt9erufU~6c60bu7sF9x8@rq=6}wi&a7Zux2NfK%6P(q z5(OvJk5)N|uKE^^Ww;t*d!H+MEv98ZGBG8dn<=~`CaKv|QAdh4oFN{E%@y|9+l^gp z2ZbO(d8DeDE%Ed6tRC#LEwkMRGG4x-O+sw5Z;L7sCGXHkx#;^<_qO7(BWETdi%v*w z9TZDB--A4KB9%Ry|JcoC%ykIT?P=Zvkcs+8&9f)oh7)7mLkCmRV<#IX7m#W1ZdlcZ z*^c|gOCxBNdHz#uDYa$dk|AMfp62(yyBzw65byve1Cxk|%+}U0&m@JeTWm2Upo0|8 z)ZO^J0bx@He^eB^pTgkLahks;u|6@3S6Npr?f0CDW1MIC27F~(!`!6{F3_B5O&dY3;-*l-s9C9RVrboZl^g&*;M z3-*B%4F4ow=>G=$F9|~olQkR(U?nQVIT(3|@gIa2F<|Ie}bV71t%QO0+Uj?U_}(#b$n(MxX1NGPy#tB5E}qJ^ zIy7e?%bBdUZdif4j(vC*r zaPFIQNNx)WL{^M+g#l5S@j@qyl|ChgfBi1Pqgx(zVP7uy5}1>^Sh z_SK~O0zmluPs1?u-Ksv+VhKzrcPnqwx|a_1HaxT zpJoKMGI%gdB#O*WpZk#LS~oi2OHHqb*axBL+4)sf+l06D^+5mepspOAZ@45?D;U7j zTtfqK@Sx2;H(~%_B2WaUukk^(c;}=2mZzuOZgFVVh3uhZJH<$k4CQS0Ek#oK!?eoI zdMw=jTh8eUc4wEIDp$6HnTxp}DeOPlVKOD6+=1*c?O6aB^Fln97>|)8Z+h213|xUR zbW0E>JQhKt0ZwQfBKq(yx9)OHNxmu~2{&fvLkL@2gyD&$>9;+ka|4r?^&5uUJntUn zeB4bHsv_J96r!-#LY_H9mHnU|Y*&4|lz_GeK@7xI=3ux>;dPVjt%2Ew63;C_k$O^i zmU&iksy3^VPoQU7$()lU(daB~usS)1W_m4Af|HAaHCXQiLkiIox5L_Z^I|0^^{7J3 zy;+?f(aP< zSk#y)y|GITqqUNTFNjiuJ?VP!C7@PYCG*JGVZ5n%9n|?0R5;VT@BHta%*=rpI8wD6 zTas_baP5NB5?Q%?osa6iQ^trZhC#b*^_QQih&_xkh99YsJ=sOJjwsG_>8{kNRV`miqkCfyTyGZk^b~v!ev&m+tui#ByCHt z6905;_*2s5SRCN3GPU>aZuG5L;UsTYapY;9VY$eGg;+<%S-wcO^>MSgm}r?IFT(c@ zk+&R%Zw5zy){5YkTvV%gy#M%4i`8?0CSh>`=l4|>?G>bTVqvP7P_s1Th`3m#y z=%VLQ>TE^9YMt57?meD;g#LioRcF@hnxSr1_8H0=!Y#+6#^3?)vh*}|rX3jB;M<<; zyM}f8z@Y-yEb&B~`o&Eg*Kcx;-d9jLb(wf<8al=NEp^)4MC?0q&;o!~rQgf9|B{wJHgg zU^y=`ota+Z(es$8xwSZD1s~UR#N5>d#lif(sXkYoHILDS+M-zwlvx~XeW^Wfp@-zJ z+4Y^=O-4*7@}Y-tFPtsvh*?w|kkv!F%;MgLz4h!nNXs=6xp$3oc@C+i>lA-BvUxLz z(oS>y~6xBYfCsN&9UDwPcYuO~2jQX~^6pyeXK~mxKa7e78%9J zro@E8?&&3*Kw!Xzm+2;rYUn3= z)R-j=%ANx#qLK@$*v`R;$@jbkCqqqlbZG=#Xz?e7%IGnTz# z7=1~_m9q6TL;&_H%cH1?In!~i9%7fcW4IEpYA!d_)#1ueYewcmB+^$NpA~gkV?e|Y zGdE77GO1{YIddq`b?%$Mf>I*+yMQvd0`8hkB5)cQ%{X6g9sB?YgLZq~=6Ap7_~~78 zV+cY9L>?j(HFr3bJ`Oq_x-Beg8D@g)434i>EQ{G)tmR1v$&jNgXhTxf+<%Z)s0 z#=K&zGk5N^J$m`0*DobqDBGqL`6|qq0w@lcMxx1%CEOUwPMu?o%MWRZ$qPD)S>~7KP}WO>4*Tcx|4e z+&r>8Mv0YYg`L5G07eKJopauNZTt^tw?H!zr2Y`zKyk?|-Dj}Bgkxf;%j&Hcc%ARc zyy<@EeS|)DkoE;Pb6%vd!AzB_ot6q?-4f|>|N3fx?=gIhx-*xn!*8oqx?`<*(eSD1 zr+ucPeBE!Cp7v<`?S~3_hhevxK%7=nJ_hHLNL+wxQ)4tAg# ze%L`_iK3g;do963+t8uP=XdWlLwlA=5aY4cIYw2wkaW>20{3S6QxVUE`;guj-H_GO zmX4q(xMInRt!}=ARGYID9GeB3zfJtUaN^D@1Y~EV1^YJ+1A63Z=0da#$k*a#;qIdUxn!>(?HqOTk~1q|@V z-WT)-n)fcWRuwFh%-r<+_bLB`ZFs`TJYM|t{Ck>A&R0N%5&F-`0o7JH!&?>FH;V{L zVmEOWKlbaG4IM3`jPLWL%Z_Ka9e>cxj}O{>yoJ5Qq!9O2*Yzm(8Bfjs`SpGrvtZ3| zB^~8dJmmXu)bLybhHWJIANG87TMg`rchwW28sS_O4!9UNhfF*+DC!drXpa&&=j_XA zQ|;Z~#YXklldGqgzV$-jcbr7C6Td~hCuL$E#$agGJD2xU8!bW#sE`r^g&%=xq0vfyYy!h2x)j z;VM4Mp`-D4R_(a;&_%YK`D4AtM{SRL4M#8)=Z(j95fNoNS#%_qKO?^P)BVqNN!c9; zS7j@{>&5hHdvB9F+`-|=8i4R{Ia#vJ9z}yTxc@L?{2OZL=AXAV_vNM^FMjrqaL_ouCJ98O#=e`qYdZU2M(Kih zc?OmsG{Voe90XgG_=-Vt67=J_@x#(Bq2qAl$re&+fTLf%%WVFLsVY;4mn$1o0vAZi ziVm>pvDs*#txW+C8NAD8rd8@*bYlL16^B+yETgW(xCmURr&>4Kwkr9EI{yu2L>;$*Z|Or(S@PExx5};%s+N@~ln#n}}+q zv1@&FtvG7=7)j&?Vl8P&;+yP{J(bemt=54jIK*jCYaCY2rwfGWBQoRO8F+hE>4nSD z2TDF1O$du87VtWajI}XH`y*Knu>O1L73VLp72wA~a~26JIfDiVtg=yczS=--MWR6IzU%%^h}0zMuBX>f8d)5u>(b2>g5RhFg0?8tHJ> z!v|NgzIClLbN2tJyW1&TVdj(##lQ-8X#v>4mu(b|eh!xL#-McfC*FCy&s z`UxlXnSCV!BI}5DH@TqW#}zWcY)#8eSA_j;eM5Aho`CNK{lhy>T#-0T=d?mvJ?e3?zklRHE*SWIokFf)@! zzedw6p3j?(lW^jYgs|A6a=uq7(GEj^ht-%MdCT&q8517&Jxrju8UGkcy7ZUX&^@y? zl7#%h*zuvI>*8`maD>o?TH70A7x_wl(RX>yP89_JT^}@x``T}z6qkTeUb|@N{$+d$ z4Cg_PYuQ5`L89PdH}$CKcSA)k{i-ptlJ6;fPDl`!xFti_1WVy_?)z4VOx&fl>?bZS zkH&B9pVHme)!uTAHq#mgdRoaR2Snq8BOgHsldxN~ADQ=Le{jp)UQc=kjW3AqJ4^8j;) zU1pVO+dh|B?z@Q4o&sOR z%{9!z;@b=NjjzGi1@?AR)sfAyCF0l;qrv*#efX#0<7B+Ix8Ka=%r%tX?KM}Tm@j^A z4}B+m1Vfgs8?*3jnxYN!Y>_eMhQ1f2zmxwrB0)MF;69yC%D zcfv~Hqg=o(Q!ok9h{0SBDDV4 z^(8VaYaEs=^-(+zNf&TAt%?tiyymYJX5Ui%_W zIssz0=;>83&vrJy4a=_|_2AmOHAfoAR!Bm$g!E8Lk|>qJp8I}>B}zTcHvkb=b2YVT zv7ocV5aYIGv;c+NAL7HRjjrVaiTi1QX3!SA>IFxy6ZCUytWMu}8+C&onNxbCJ9TvW z%TH>4t4bIQ2&^?n<3JMkFCMT#s_tn>q$uL--b&PFQQh8&#l z$@p6N1!vB#8dsrG)-fI`VhPEoIP);)Wa1!R#`Jp}v_M4-9-#!0m=)=!>+qh6TtlBv zpHI*a@~ave78~fuH!ydlH>&f0r45E*S+5V(Jt+6OvaV2#jd7N%#=4L4kjA78Cin2z z>6x*cIXka>^(GHdM(hm6jZ(88NqxbwFlgDVnh;TV4o4JJO1tDr*nKLR|11R)tv2-`uhjj#nRTmS}9N~i%K9n-$u0%(BZv-cfOfELHp-=`e);kwN z$lb|XZo{Ja)fT10e*w3Z@^>EL2&!eINCBVi(Q zaI$m*7<)1}yFfQ=HnFrbv#0q7&JeM8GJ$#s{}0H7`7d6g{MRFZ4lxfqBL^|m3jiY* zG~i|-X5(OmWPvX z_D;3{8|a-9CT4+ph!BMqGs#N-bJQSaX5`@Ff$~bS#O%<{Ax!MzEg*C`QN(eU-SqntE#=4o#j8y?^#))%|ZdE|6r2- zY4g8i`sexoOQ!!n*ZC|QtZe_Y@B9j1NU80J?e6O(#v=@?-tA|0#OUKY_kdiYoN&Y+ z>~)tfC{P@vf`>fnVcuzG%@E1kdR{S+ks{IVUL#)UHDTvRN!EbGl-| zs#yb9+voIV@B61RiKeCb>CP3hcK`IVLWt0|43uVC_q$~}&=>;IzCKoozdk{u9W(6_JykR0VYZpHr>w4<$ zuDeO|{OuYNc<`*YOAiY^wFYYFAnhF(nt30$u=ShoXwX`wyG8^qiH#3@ah|{`D7YE+ zyycU1lF)SWx;Fc2l#DI{v9x`h+a@L&KU44r)EGQ^{b`z5|N0K;!Dw3nX;j}}RD&pg1;F2; zm2_NK!6zHrKCSOchr7nydHZ10adQOQ7pebqMyDAsg>X(x-xe?7b$d?CJIaz7Db4UG zx!g#8fX!R~AhGCq`qbZHSIhGi!WAtq)^Lw!JSs_b--*n7e~GDtQpw7EoGp-9D(R64 z3!YJ4EOjZ|!3JvRHeYEl^mbu@YOVc`GiraFb8}5WoqdMhcEnTddX3Gp_M0axWb}4@ zD8jkqXzAxU@^w?9a>mE;FCKfA?#=lELTSd6p}kVksa!1ac@keD+6XCHjIwPnc+zIW zPsynldtap7^5a~ak7??=>!kgVDY<*tk8>maVUKXmV<4geC}I zlYx(~_N%5=P*aL)Pe0YsRy5D|(Wl`Gb1<%0d1R6kpqxY<=&(_Dgw7}tw>`(VI*%n) zX{@aL>2dIx2S<>jd%AY|WT`l!pWA62=xap?vYa}fMGKGR^JfE=t)TK1%T<@CZG#kj zzti2c1jiia!21`j{Gu%vXusHxL~XyJ<#JsEYPxP~WY3jSDTGz)FS95t&SMB(L%3bF zuulX-W7d;YFDIfU8E~18>g$Z$DN(g*$R#= z0S%$4AT{3hH#@9YNcic#WuL9f?Ks-j)v*)flG|x&RB#2b7iX@(2R(xIHc0QyIZUm- zT-O+EhHB3p*%dNM=NIcQ-G?>8k_D^{>e<}V@Mhpj=xOGgjfAXmAF7Od;-lE(U&DA; zs?;xI>*9~#0U-D{E84R05@=#n^Pk8bznAsHIE0y63vGkqN&J#LNK(VsQG{g5%cVQX zVJ#flk=M~&Cp&WQ_w(8x_hn5H&bdSKo89gDvivb6E~}VWNDhBR4y~iSud6egE4;Mp zYl_D&o-FgHceEeos#|mzq@T+4zdR}%y&|t&f8-*|E+;+L>B?W~y5q{p=+56kLSL)6 z@QaGLWvY=^qN>HkIaTQDEa~4oB6!;zccDGg<>8;VVP>6PNoHkES$q9QUlbx+OJ{Gi z&6q}PS}S{1z`FA0k>)fY(ttiGjb~?<5noHR?OAERk@7@PV(O1Hg;G()bVX!4|7{g> zcOor_URm^f$*AmTYH{tnTmzpGYKV@06@NaQf;bo03PaHtk=xQq9O(d1S9k(Cql#(H zqMI}4eY$03ehEm(M>Rd*UTd}0!h|4WC zy*I4UPB>thvRui!*hX_Z;D}GBa6_}4vU>c|&PK`*{xEG{%$v#r_GWPm8ncK zAfG&|X^}Lt8i1+bQ&flBY^5gxKT_HaK~b<|8o20AM>EwNF*4FV4-=vn-0EHP;@b&s(=6Tfb*W6<}hDs zin0>f8l}L-Z*g1S=OJpfWh;{t`-%MXRvcEZ&a6*v0ZKdRdaYMK{=Lzi?%SG6*JUx# z;Mg1vdXEBHJf2a(n_7LjUO96D%HMh}Au8+RFGq4j`6U}<3qiV#>O#Tokvya}4f|{T zUvOLPTA~LZ?2-mv?%uz|<$r%GvADOC;QNqyJMyj30aAVXfc7zkbrNNal&DyQJ6v);FDE6x$sL_AVy*qXyiC-OLIK)n@sOC}~drwreZQyr*9$G0`b(KW14 z%+*%8sB&Y6yZPBz34(gEYNei#4@nZMCok>VSgX*wyZ>5W&^Pf>(AS+Z(*5nu3Eq%a zZFB+Yq0tzxqk)j)+shfx=PsD?;z#6?&rUOye6k2)NK2gj^UJ(zxF}oRi>B|w7UyWn z0zg#q>Zf+pdu;%pZ?$8|IM#`ID?4a7l9*2xA}0Mtyc@y- z^rkfw&`5N8R9p(T1BCtuU+)+uNzktA&a^#k_q465wr$(CZBE;EPurNbZQHhOV@}Un z=R14tYoBZXsjSS5sLYIbvN9qg?&ocgFkyqm*ozya+0#a8)*84R^(1TQ?i@W&91scc zQ>I`Mp3n!~%f6idtuc|2E&*q8=SZcjoAtgfJ8YVYT~4y=@{l9LR4=B@tNXVhd)29cdcpVwF==d?AeRJ@_tH zAqi)$3u*(GNGUfIU zmO81&pQjN|vb@0bIaFy*a(O7o@|T?upalu)iYHG=DUMV~?khZ8yyrP| zf62_Nml~H2_jRz#Zxto{JP~vHvQs4H_^}loY90#`qOY(PJ$nlQCGr(2YzE6Eq3T@O zN*yH&pUWW0rK5 zRDl5WSxARsh-t+1WFYo{x*1hdC3AecB*A4!(T|K3$W76XtiM}h&lP*}f7FBz@V5t1 zCpaqn!@frR(tYIL5PJ%fTyoiLTed~Z?%jlX3N+4d*j%!pktS7Ap(Z0E8$L<=x7;bl zR|$Vg@V0^tzh1mrJ%Xcv$_Y#s1ovY>052*Joq6pU+Edf+>5j=AOyWYhzaHw%9AaI3 z3Ir=loMH>I1p*AZQx~W!J{x zQwk^L_nJgV72blgQ8d<)v)k_0%ePUviucjy=*Cyb1m6}c^%MCzb;Z|w#@aXxgdR93 zL(n>i&vMw#@K(r(&1X1kZC%C-OZz%7i<2u6SanT}YU<rhvX%M_0=x44X29bv0m_9^s+cp);c zsf>g;3(a^7&BUOh-F?IOt~@!cprxwz=;fc^2csEzY8~od%fakrVZzm*seQcR3*=k+ zlaXT?-f0zOqusvbL~4<7gCtVXk-B&Dq#(NsStxN;NGHYCx_~v4VpXO7$LE&K6-<_8 zNwy;~MeQ+Jxnm04(wfbZ_W8F#g>i)Abv_c0dcYOzPD1Cg_bd`m=}4~|zWTcgFdV+a zVmNf2-k0{pnozfdu1v8_tI6BC>}Y>0Zq4c;L)HNeuhF6OTr;%%LLMgpcSgGUYmA>s zj)3U`DR{q!NTmkNMRtae)to9B(f8R;bT9Q}h}6)~Hz_q(fke71+cmW~zQ!r7PZzN1 z;{T*jhHQa9#%$@+P;i3rrpB|1XrYaYr~jZ3yrNQli&R|G1|uwMC$)-wuc)Xu%hU!F z-5pO&OT2s z`&+X4(u;VSk+m%H%LnWPD)+P3lo5&GxQ(8_QQ=hY6swdJGkVaUAy>^INnkA@Sug*- zrd#d|;NwD*e<-ZLGWD}D+83FBG|=R7eG$6nnDB9ug-w){+?B`U$9mVn5`TO(dIe&J zuvP!ou+x=+3d{0kld;!88O2zx2`N7od$0Sd>`S>23AOpRL-+*2=xfu@D|oWRhcjh^ zC1{}aib~_#tvp|p+SU4Urv$n~skc3kK$L}LNYEYDDM}PXpba25*hhSsaKBEfWP)R zl%;?|bP}Jf;P!(tYk&iE41HezZP9!gl#>LRkRVX`FvZe;NrC?d)lYyi&9 z1s$3e7r*EFm&6WDTq9BNwhCQbAv40R>3Q(Cbw?kn{W?m5FXyQa>;}f|F78`DIgC7H zwD<0ufRA#qd++8>!h*rg%@kK&=4)BfENszm&XPR_UE+n~BqANIzW2&=)idYWd*mWn!#E>+U3a3B7bnOt&m}wkNaKA4 zqXRq^Z#@g{%68N5XUq;{r^=f^|Ai^2YCoS1!KQRK%Ad}<4ZU`pAoArg1RL^~qy#iF zn@7#uGL)aq3={5nHAw?>Y7k`2h6Xw22D><9&m?)EPXotC9r@&{v=B#MU2>%k+;(g8is6^?#G2qTW8NC zhWdT0G?O<#c<&#$lbhPY@b5HUx+Q+L6$~C|E#bwTh6&CPr? z40tx(-F)s$W~=PIWVi}0W`1|4_>jsPXdfcf_@hKU(jz`A#8#f$+a!TLQkqzB`o{hU zy{Qt!bEV`|e}7lIA3db_yK_k+!`rSiO13=iPe9r2_*BatvzhClfpZS29=4M%sNb-_ z3gkg<>Ad(!kWO66ox|pm00D!h^wATk6nor?=W+qTmEjKe68hN zmI3|cadfYt_Ifi`36!~zX#HX5EzWY@h72Zf1Q zluZ(h4k#w0OS+!e(MY<0fmJBNq!yF?v`u~iGDk~?A5v5W9V0+`*B{NMW1Df50&~uKi;N{zGHTc|8ebR4NFyTrL8#FBY#8(jYn9CTY6%P7+^=7P^adPA4VsTp(Un0kDhe2=CZBdPFt29QY|~X85W3& z6SpjNlU4#ZD^Ym2z&z99LFdWETS`5n5X;wz@7B0?thf!1NcfHuI)x)rW5G|J$trb? zeA8OYZEt%9Hl?D8GQ}x%zVf;f^8kS|J23Oqh8-(6&C$PZiJlqavSFSm(6m6%S21nt zJZxI!gy$rFHrE|Uq*yWvJU(gP3%D}&I={lh=jzMJ^El<~x7QS=sjl>Hv<2kyBL3-+ zu~#9NR~(%j%9q(i^XFlp%IL3X5jK?(KBrG>!iZd&8f7~d%R0S?rvykLs%$8d22fzWFaz-MWa<66QO(xnui)N8l{B! zlNyOQ^51O;odTd+@X(C$1ANXb2$@bebZG-8ajU|XGEd=}L(^*nWFQL_I*bN6huhHU zO8|M66b{M~)6`e3)3SsY9!jIS&eCo$N|8J76YHgBrFTta2a3+0r+EgEyQ_(`D8aMp zZob5}jjmwC<$L^B_T4XY9uEEpyGh>5vfxLu?P2|{L2iZ0l!WT7UV{!(p4i9w#y z72!8Jjuuj8Oeh|3p5V_E#0y(HxsrFGM0tJ9bVIvd(828J@m^p42iH z=+53jTS~QVb28}-t?}2ocHnlZ(@Q|+E^8iJA1^;e z%j)GZH#fR=W9NpWo}M5T(q3~^h<9Vl)g)O96!Whv=@R7^%5fsF7fP;^G%Xb|?^`s| zI6w@TM-~RJkljLhua!)5;F|x41W9)E**q9uKg$zl(MSs>F})6{SPWRaEi<1il5bA7 zG0q8~0gN{M6Y<%7E_UX4?cNu~^V{2|JN5mC9v6y^8z*a|XW+^Dsn;Ye!)&u*R`RpI zm>@~pVw+5}UtljuE6JnuNbX+X*{}lJ~-XD~7Za zN`@lp4d6ywL*Fok2$D-?$|9=YyF_hIlj=K)PK03L3RCw@p)O(J&dm@CLC81#rqO&3 z-Q=XaZ>Kby?c^8`MG;NNHMM({TWe$Ej<`;tXc{@rK@zcUm9-OzfwOM))jXo?ZC*4H z_%pdzJxbsaY{vJSOc7IKZLOJMI`PxiO-@^IEZ{rXgz+22x!XLW)9YA*B)8y2p7)|O zrqcrM3MfzLPmaeh8+@>poWLEnO6E|*#Ef0~8Ifli=e~eFWgWI&7U3U^A^5tQ(7{hcjd}U?$Gv{<$E_ z{T3Fg&F^3Tu8O25@7ao7F&;%%SfwLSw3+Ncs($kXecw|1vrzH9Jz9( z&`o5URNHLBA)9O#M5?;!Ud@YZQVmYh*%nG=@HEaC$cR(Oau=Dg$`R&U|5)GLFjos3 z4*oWWcm8zAP5#5-EAtY_rL0DCZ-8%6EoE^~e#S~x2`6A?20IH+#>SVe9lHtWY4c0?8rHPVBqtZID!`FC_P=MJ z+=D<7SqQ%=0GDI+5-29Ak!S9{2_E5v<}+yv!B0`kPrAK}3L@6gNdFrgHdH(y3(@Z> zMsfYe!^1W;Bym~$qS#>Hj&Nx*=UvI0qA4y&otF*T8^`zW;Sn5F?~A?1kb0VJ>fN7l zoN;@VL!3h{>bpi6HlA^nUdJ&Uviq1`LE{$)FmvB(d7JE8uT7vfG`$&XYOGu&Iw{oP zx?a}%b3DWb`l`02(?zhOSp!Jm>^dhw6oExN! zB490KYF=3T|F~=qgZ%C=Zq{8m!DkDI*;#AA>?=>u;pFCr3zL_Pl#?WG9~)H(1%dsc z;_^%5Zwo_V3mHs|R8lw@jHTew9ie4j_2A9qY;jNlwh$qk>VBSUI0M(mnE@9SDEzer zN)=9I42r8#iuCAZ2{WyWYD_~B=bUuU)Y7Aksf@_dUS%cr3o1x^`ij?~0+_9;3l*yq zcO`xU#t{y5X`AZBz)NCWvL@5X2MyA3$bvKCep=FRiloZ0o1=~<7>K$p82*!RF7)E= z_omp*HrT)DCJqRmbA!=p;Hql{rlDyW5ss-4?$ieWr2>}X6z_4^(l$@c#cLq_!~GEp z2|Rn{v0BZ!HZoDf=?S^VmR6R}3xo-wg_&KncHEHY)#DwoPjJe-M)9)5=kzz##+cw# znti*w-+1RhL^FO zrL>j-Y0e>zZncDN{-^Q=gGDMMaj^Sb&5@}^lt0|}v67y(iido@M+g)J8{pw+1xmQk z49xuQT^-fXC&@QnJF>@<(5^DvEdoMuq!tvxlMdPUVn@ol4+XIuA&tP=SDvt>*BW8< zkH|^Y9YXb^^6TqH844LxlnR!&is?OJsH|Xh@)$|(m@eo`Fb}&D+IR3h&N@a{BjQ;j@dba$00eb0ZO`0vzZ)WXFmrABFz|ds2uybRIrXA0k?r%uBzxe2 z%pUHD<<`HI2J}fgrii8JE3=(#skX2%cj!I!&WK6|xx@|F^Fto6(Qp5Vx=9>~=K({M z&s(M(<#1P!)Up{m|J|Q_JJb9i74GqH+9Z0s@%^H2izDHs%=?*hVY-2qX-L=U3hxTh zbIvv@I2g}K6I9WgYCwcZeIYHdDbv$xx~xmq$A6gD`3x-27Vbe@cJ&fsjwk&j^TxcF zOE_dAbt45NHaUaHMx3HCBVjA4?Moc<;@-w%Z-9Q9SHf81JZw{EG-tVUfk-jTg3=2;vF=g}LI{=`ju_QGOg4qT$D?W#izn{4rl zlfTFVMM)dLP${XAuI|@2HvF%>UOZnrlG$ZwTY7MdliT$t6(ylKPy)uzfkPfF^12$U z5}gfl@ftV-?)fAQ&W%rB^^vS)ADUkR>pjV~r+AGpLsLHZYuI#p--U;e0K|}V?e50e zgE`kWb;I*GtaPLyU?%Tab07=VRKG@DA)hUuORb|DN6IW*eV~E<9d0Jng3|+h3hY7? zvbeN)mf2UZN_j@hYMOB!6E>D?Z^0jEkZ$J7lU;^ag2j^lQQ|VAPB;(u#dBbdGut~s zB@hf$YbDeOSBROGis1F@$GJUjCfT$RvTxSPT%%318PiG1#K;bfU-FOSY(^EKXR1S9 zO3MT!Sme2$)oU!V!U;!A0x3N8bBwAiN+knoY9Ehr)56XAVy9I~TCK1gmo*KK z@;{?IhFCMaq4EektyutIWG_@qC~}P z&NCP)_ffF9=wdRZ7&>^;vV7(!Xe!4j#aV1-viSnX9GL*K;FxZ#aZ zs@=?-UI*D0z+)14m=>}oc?=^ZPC+dO+-`ECYupbTK-xo)DHyY=STd=Tc9^p5Q4;bN|DKcDh2aAB&a zDaB453QoozVBgCRv*L()lTsw9tvHk5m{l5*)={1H%ml>WGR9qa1?z@cu_9hcoz-%YTO@Q-@wF=l@C`>lHvtR+TxPN zHp0irXC&fOk2ylD8a&ySd61S?r51+<<4deBN-5M3rAMDRf*1j5iwj3;s-$XO_HR;EkCVKp9PI307q_XZoZy zDpeMpDW++-pXiJ&Ty!-Qc%2CkRM3Vb|0B=>8GzE-shnUNR?2x_@!BYJ{4*@M-MFUX zY#VGst#xCVw%_Fp%qQyecJMn#mR9QtJjyvyJ-r<0F8m!+mpI-zJtA*~ zF!goANdkBqeA_wyNHpc zr-AKd(860waNl$MPFwJJd@g!h5oVz7J?iT_w&q~z!;{{wFW|#Fiu${UywlWB$Es=T z7K0apdYpes-IR-2GJ*~1#}B4t<-Vi}Y1!F=t=S@S<61|N($58_z1G=LgIb>V0 zeE9Zo>&pkn>f}H-6sIU%h3t|8S930Fq?n^tM$}}#*Y3QK5)KScp7(3x6iv|vXc_67 z;?p;MLMo2el8p*CZXSyi>4hJykdaBp&biKz8>J<&4a7x(n&c!C`Qc3(4M+^C5}wRs z-1fIh{6kZ{^)Wn_XHZ17wDurv(F`1x?%uVN$%=ak%%hwP2X4~m=0O%x7jSY^9UUC$ zc+#$pAxCC450Xcoy2E(CBP#3SI2&2=2fD|QI5P;B)|?FXmb%O19z}WfB22b5--xr# zgG9C(=6jE?Z?vq1n2Zo}H`Z#O`)d^8H!#jS1$1Qj6YfNA_`y0k&SsiQ=4O&crbk~S zrhKNdA07 zM`3;xfG4n~UMoct(*5p(nxnh;4C@mLqZTkuC2OOootCw|IIa|6Z)d#C z(!f6;qD@3S(9=8EcVA6}WJ+P~5wxL_8bh*M46fcJ_*hEkY{=3+Fw}P+iHcUHH?w19 zd4bRaxd55Y>l5Hk!wW#%6iqw!({sI47q!f<0xNFf@JY8Qa5RUi0T?|o9L7ZELUS2c zOXte2>ZnW$?sB~r90)Z<&mOZl7`8`}nK@oeKBG!c-zM?68bvPZ@zJ$8|D~0pp~?rE z*x*h0p)cr3-+S639L2gZ&dky?lA*$fkjM9?R?{-o&_~TmG7T;kftXhKp9W}Md|<>! z%Xy_$Y&TC(ZPR;9spIXbWNBNZnbi({>`id^`_s!P#gO(Dc8hHPsiJFJUC^j-^ z>9~0L*rWUP$LL?`rac-}r z4pMObssitc#{y3ow32)Q^~{GG7YEabW~FTBd{Un)mjGTmQ$^2xK3fbUtnX>tAClPz zeaOsRF4t7Wj3Wfc(7ScMX!|Ujap0t50Q;c(<<_lqi_>Ab+E3Qh>1No26*&#pRK4eX z-!P~NJTlDB)!s!P>Uo?{zH$9%_GVwiPzIaA}cv2Hsu&vl!)+t;sQHjamXbMic8=h zC)IrY^@@Y6*e3t=Wn(sGAv>q=RJ7UUeCVjEx4facndjAAT+#C!umuOl>LH*xjV5$t z!z?VkT2#F#E9fi_dJrW%?G?+KXVMy*V0OHB?wW&e#|z$J>6(K^)a6-tyOYc0sx=mt z8C-U>#v(iBkRfu63q(g>pv@R`CV#1u_DouPs>m<*#y{^tlz#xIEjs)_ep{6)Fhd;s z6sre`^MgPeHJK{6y)F_kcnTg4esX*65NHl(hmFXeQjF{y=XI>uR)FH)9VmWqzWZ26 z=1Wwj`*_IFs-nc2=09Y>aHD`Hiv!Yl3vt}{dIF?71*jWl@8U_U?U9j>?m~rmU<6_B zz`|h(u0oP94BEpxX7yk(Awu)|-A~q4;mellFH+C}Yu1i?&or*wS6(M+(pfGdc*zN=Qs1LVr4{aV~5%Vk?^`WHIJZq5L3(GZj)! zZ)*`S;5n5;X{Z6z1CSZm()#K`LZw~KX~Ji6%79F` zl(zBt_}7-{;sm898%eS>Nq^;pGUDzJrMLttCrG+Rg|>9e@fK8DYmpjIkB$(QEetY(r~%n+nJ4jsH%o*m^dvWWz5k@DoezNE^iorfDI*z`v$ zqzpuzC@8A8s8>ibVefifG!y~D zS~s}c{Y*1)+!*Sswc&vKGrtin9Kc+hCmzV%I~`Wm$GriE9|d6@JRxe&Z}MlZ4C=d(YR3(mG3C?QMiA%FfG}%8bRdYfNiT~rK<}naRsPsT0Z)v z&^Z3}k(b6fo->mr6@JyH2Z+hYa{CSPr0G>AgC&RgS609JOwfdQYEHH|i5%Zkp3_S6 zVMMy8%!48{e9_g=$nMWgwa z#a6llVT`3Bk@g-D?${W}&XgZ?$eE5L#EdT?4rO_l1#C_}e`hat(YJ?txoH;5(|9!(1j_KW`ZwPY_ha!zId`wEl&8hGg%CK`;Fwfb6s*f9?;tGNRDI~S zbBMha1B)|>09m+i{v{rl@94Mc8vWmChJTtT>X@;SsoWdA&3@Bss1H@th)w+- zu@f0n1WDxi(J3QFxL(zXzUEPiBA%M4wJJGYBXZ7Kgu(zLTx=)+F@B6AB}UjE8mWXe zW_jQ-mVB-c0_!yvPst-jn2}VQTZEYB3!>lGv?Vr)7#93$=b%X*sW73+sCipKWsy(( zJ!!iB&|l{zKjTAwIkK@J#(h?0iO(PKXkAtm+A zxm)5YF^@xEIA6wea+*dUiX)R%!$fR_@YP1#nbK3~m-l>=Pmo zVsEn5L*#Are8NartUQ{C#xgzuWfX-LZlJ+q6vg6zy3~(_Crp8ON=NHHW;Zx&>_`ED zlb8fTCPHEOdlDy&3|D3m-$p|h6SbybbU@783+)1Df~@J`9CJNXGbKf5)Rcmr@W&=< zU@;Gu|4a^lx#({F@qv+nT+ME@zJW$j2^Z7LTWa)(9@xE`&#)})w--U!qEU)(z=nC) zwbD(`VWo`FhW3a!wYA6RjM&Myj!s|{^;ZlQXIaFBj{Y_tx<4Koo4W+$ z4N6a4&46u*x567&qm*g)NkXW{6>{O});GIo+BkDfVBqmFe7ekJ0Gh(K zo^R-TF=H}FxYWMEAQ0?kKW4!Vs1D1rXjWY={@i^$Lrf~kY3^dESa@*6FU|rxa|D@y zCjsk0K78s1cJ^?M!!EGsJ;?^?5y#WTYm>vlS<^?UZzkW+HiS1WG85>N9sVNLOSzF5 zGA#xK-y$LGwlp*8J&>7oqGK@r=z!UgneB&k|1H$x!8Um714rUoKION6*=YIQisk3^g#X z)=P*RC^S*=w%TdgJA6xIX3y{6uc?@?VSLd4{3doLNn)vKuD`%CO$==0pyYw-)~{yO z$ICQ6Sq$r8wvWGO{*ZEk?>^ZB`0;BV@=(y$)7GiVY%2dsn8)BYb&j{NIQOy;MiR~q zmy>KXDUbYtRHS*SS@WlyxFPZiPnyOhuS|5KLcZj526Nrm72h2Wp6W3xVK=`rQJFiJ z0dh|7aTCtexyhvQeJ|=Ra%Cf_CfCg{?_ix45u&4@oE1`E@Zvh@)r3TNX7Mni49EsQ zn9&e3xE*|ug^$Kt|LK&s)%9}t!c5A=<>S-&(y8C8o!~6y*wNR2m@S5?6ydPgH?!S| zPQ6sQM+>^XRG8-c`rN}(*U3EY>jd^J@bP|P#ciG}zGuEkK^d}$5V9W-col`%xOn3SbdGg> zGd7yu)r^ZbZUY7=n+`}nHq#6Oo^-~K^#Iu&jhBGj-Td6xdc{O}rYsG^!0!DvW>G_{ zToZ}3drfBI8hh7Z#z9diW{3~;@x(;(50wGUUy9th>3erQPg2emhsOgBA|;32Ld0LM znO>M74hz-NgL9qHChDjaOVc3|X*U8!n(Oy`icFgu_I&i&h~X(NR8}~4t#kg?4GihN zVqM%m4`5%=fS0Z& z^~{DBq%V2w0a!&LWI5+PcBQ8!D0yn4jT!J@LC7|wBD)5J-POJ^hs4I7?Xfp0q(*Xu zTkGw%TgmfIY=G+PG=`RHleMdaD-t~_<{s-++csjfu`==KI8H2fdZZ-pffzM7$4M3 zJC5o|eE%W|C3CS2$?h|1SGV^--Y?T!c(;CSy-jhjPv(tIulsIaX!*qX7wr^ zeO!?%C}7W~;3|g!WYxE`9$xaSl=ge+*D z(Vp2;Lo%sH$#}O}=*Tn3!rkt(LMPvih^!W~<%>vD8GDZ7gg1o~-ni3n2ZOSX0HWUJtt*ZUyBzT&s- zwL&9j3j{$jL%9)cCLGbYH0|$~NaXzONnM@C1?sy#rr}IHoKw zoXz&P2%5pg4*G&@XAcn20GoL3?^iN1*7seQ<+;rEHlB*%R`+=YA~@;BVFmKw0s0<{ z>d-JcIRLS;0156}XNfwI3f9KhDn&+Ci?}Mql(qw}+Az0APY!Xo7miiZU~D=ElpIMo z(xvI^?$vuufk$C!WTTbPBME4Z*rV#_4er<@xr-B_)ab+VWR1-Yky*F8)4}XpsbMfD zft!4<%y*$3d^Z0JPtod3GdoAHHE}LR?D8p-#jxDsdkYPwyWp@~awonSj9gYJDDGSs z>7*SDDLC#OZH1&eEc|`w%gvLU94I)&%PT-<;D}C}e+AFC5NqU&cy2NiNQVNrgM~0bQZdvkx4mFr8-#`9_$>8uURDRUiY+Z z^Kb5WOmuULtWC~bliAi$D@{CQ8@7m>9Tyxv9EzCI!!`4_$~(q6j9G;$-4K;4W&xKl zqjITxKE>ym7_@j$-dJKJ0~$`@a@G(qN7<| zWRGy0g2Tynr`Fd@5>jL^{F=T z@@6SBtwU5vJ7*^kc2j)3u_L;wp+l3taG7HkIE+des?pP4?aMQ)qulRHBgd5s50{$u zaINXw0X2-Pzbm+3UWN}ELI&8EuaAZH=hg1Zrt^TdWJP$;{cuiT)cyBTU92V{S6GZhAfjc$eldL6#y7|un7I-TEgkJ&H$fMIP6f)ClV8!R z>~JDH#pc&HKpC}^7l}U4tB;;m8tyq+eJZEYM2+4cof&tCw%0?2@BMHwHRK!5qFY*^ zds|dY80D$~F}~$yd0zWE45QOs%6iWtojQ?|&rJ6;+AdeQR_IY-=iWFD*$}gM^NDvG z02{#W90vSg_EK7jUJ(ac%#er{qH9x#KMn086Xq7{CNHj z=zJ%Ht}xu~t<5-ftbrh-v$7b?v$Q!)Run7%C6iuJ(%kLnDv+x3aayEZqCUA{!P>2E zJWHC#XLR*2k@)fKeMLd2_@Pd)x#7A6$Q8U&ebNRA5PgnDo?mP7f(L7c4Jf<tNX z{p0Mo4h>(i+8JuArgj2cGw>~>TccSCo^#y0nbu&g?#*6{=HP7rgjkl_6|1;AhKAbK z`0koVBb2{4e%85u8=!1U&qvDGv70a#L)y9(o^D9#Nh?UNZb<*?fVZGW^KJd`{M#93 zyO?hCrukL?$kM#yc?Uc)VVC?pInP1FMFi#Iy^bTeZsR@rVmqt0_VGVIH$*yD4|uHi zU43QL$h|8Zk6GXA#g41*R~(;c_zW0`tK5O#kpwLux zd)DT8m3Fta;b{xVDtARDRl*s7RAx_~d=9=*@j#ob;sD=;xZ9Uusf%Qtb!L*FPb_FK zC=#e6ffqbEfGS3-Rg)@l17?c9 z*$g=u=5GUi2C;`ZG*M{&=v*Il0JrniI<7-}wdm^u2?-wT2zcjVV(?z}hVsKlKHvqHXW!2Da}+h9wkuW` zk*pyf)i;>OT#G7CQS-u%b&Y2Kg;+2*?*BiS`v0PY|AX@i{9h4cL2)5PcSk2g#GnP zO17fnLelzn|BFff3aI!+F8|}09n9^%0DJ<5{}=22|5*C3Z2yw{!;%^PH#(VK^}pDB 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 UnprovisionedMap; + std::map sessions_; + UnprovisionedMap unprovisioned_sessions_; + bool provision_request_sent_; }; -CdmImpl::CdmImpl(IEventListener* listener, - bool privacy_mode) +CdmImpl::CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode) : listener_(listener), - policy_timer_enabled_(false) { + policy_timer_enabled_(false), + file_system_("", storage), + cdm_engine_(&file_system_), + provision_request_sent_(false) { + assert(NULL != listener_); property_set_.set_use_privacy_mode(privacy_mode); } @@ -220,7 +273,7 @@ Cdm::Status CdmImpl::setServerCertificate(const std::string& certificate) { Cdm::Status CdmImpl::createSession(SessionType session_type, std::string* session_id) { - if (!session_id) { + if (NULL == session_id) { LOGE("Missing session ID pointer."); return kTypeError; } @@ -238,17 +291,17 @@ Cdm::Status CdmImpl::createSession(SessionType session_type, return kNotSupported; } - std::string empty_origin; CdmResponseType result = cdm_engine_.OpenSession( - "com.widevine.alpha", &property_set_, empty_origin, this, - NULL, session_id); + "com.widevine.alpha", &property_set_, this, session_id); switch (result) { case NO_ERROR: sessions_[*session_id].type = session_type; return kSuccess; case NEED_PROVISIONING: - LOGE("A device certificate is needed."); - return kNeedsDeviceCertificate; + // The session does not exist in |cdm_engine_| yet, will be added in + // generateRequest. + unprovisioned_sessions_[*session_id].type = session_type; + return kSuccess; default: LOGE("Unexpected error %d", result); return kUnexpectedError; @@ -259,8 +312,43 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, InitDataType init_data_type, const std::string& init_data) { if (!cdm_engine_.IsOpenSession(session_id)) { - LOGE("No such session: %s", session_id.c_str()); - return kSessionNotFound; + UnprovisionedMap::iterator session_iter = + unprovisioned_sessions_.find(session_id); + if (session_iter == unprovisioned_sessions_.end()) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + + if (cdm_engine_.IsProvisioned(kSecurityLevelL1)) { + // We are provisioned now, create the session and generate the request + // like normal. + CdmResponseType result = cdm_engine_.OpenSession( + "com.widevine.alpha", &property_set_, session_id, this); + if (result != NO_ERROR) { + LOGE("Unexpected error %d", result); + return kUnexpectedError; + } + sessions_[session_id].type = unprovisioned_sessions_[session_id].type; + unprovisioned_sessions_.erase(session_iter); + } else { + // We are not provisioned yet, save the init data and send a provisioning + // request if needed. + if (unprovisioned_sessions_[session_id].has_init_data) { + LOGE("Request already generated: %s", session_id.c_str()); + return kInvalidState; + } + + unprovisioned_sessions_[session_id].has_init_data = true; + unprovisioned_sessions_[session_id].init_data_type = init_data_type; + unprovisioned_sessions_[session_id].init_data = init_data; + + if (provision_request_sent_) + return kDeferred; + + if (!SendProvisioningRequest(session_id)) + return kUnexpectedError; + return kSuccess; + } } if (sessions_[session_id].callable) { @@ -296,6 +384,9 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, case kWebM: init_data_type_name = WEBM_INIT_DATA_FORMAT; break; + case kHls: + init_data_type_name = HLS_INIT_DATA_FORMAT; + break; default: LOGE("Invalid init data type: %d", init_data_type); return kTypeError; @@ -315,14 +406,11 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, return kNotSupported; } - std::string key_request; - CdmKeyRequestType key_request_type; - std::string ignored_server_url; + CdmKeyRequest key_request; CdmResponseType result = cdm_engine_.GenerateKeyRequest( session_id, session_id, init_data_obj, - license_type, app_parameters_, &key_request, &key_request_type, - &ignored_server_url, NULL); + license_type, app_parameters_, &key_request); if (result != KEY_MESSAGE) { LOGE("Unexpected error %d", result); @@ -330,7 +418,9 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, } sessions_[session_id].callable = true; - assert(key_request_type == kKeyRequestTypeInitial); + assert(key_request.type == kKeyRequestTypeInitial); + + MessageType message_type; if (property_set_.use_privacy_mode() && property_set_.service_certificate().empty()) { // We can deduce that this is a server cert request, even though CdmEgine @@ -339,10 +429,12 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, // The EME editor has clarified that this was a misinterpretation of the // spec, and that this should also be kLicenseRequest. LOGI("A server certificate request has been generated."); + message_type = kLicenseRequest; } else { LOGI("A license request has been generated."); + message_type = kLicenseRequest; } - listener_->onMessage(session_id, kLicenseRequest, key_request); + listener_->onMessage(session_id, message_type, key_request.message); return kSuccess; } @@ -357,23 +449,28 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { return kQuotaExceeded; } - std::string empty_origin; - std::string ignored_session_id_output; CdmResponseType result = cdm_engine_.OpenSession( - "com.widevine.alpha", &property_set_, empty_origin, this, - &session_id, &ignored_session_id_output); + "com.widevine.alpha", &property_set_, session_id, this); switch (result) { case NO_ERROR: break; case NEED_PROVISIONING: - LOGE("A device certificate is needed."); - return kNeedsDeviceCertificate; + unprovisioned_sessions_[session_id].is_load = true; + if (provision_request_sent_) + return kDeferred; + + // Send a provisioning request right away. Then we will load the session + // again in |update|. + if (!SendProvisioningRequest(session_id)) + return kUnexpectedError; + + return kSuccess; default: LOGE("Unexpected error %d", result); return kUnexpectedError; } - DeviceFiles f; + DeviceFiles f(&file_system_); if (!f.Init(kSecurityLevelUnknown)) { LOGE("Unexpected error, failed to init DeviceFiles"); return kUnexpectedError; @@ -381,8 +478,8 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { if (!f.LicenseExists(session_id)) { // This might be a usage record session which needs to be loaded. - CdmKeyMessage release_message; - result = cdm_engine_.LoadUsageSession(session_id, &release_message); + CdmKeyMessage ignored_release_message; + result = cdm_engine_.LoadUsageSession(session_id, &ignored_release_message); if (result == LOAD_USAGE_INFO_MISSING) { LOGE("Unable to load license: %s", session_id.c_str()); cdm_engine_.CloseSession(session_id); @@ -393,10 +490,6 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { return kUnexpectedError; } - LOGI("A usage record release has been generated."); - MessageType message_type = kLicenseRelease; - listener_->onMessage(session_id, message_type, release_message); - sessions_[session_id].type = kPersistentUsageRecord; sessions_[session_id].callable = true; return kSuccess; @@ -405,24 +498,7 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { result = cdm_engine_.RestoreKey(session_id, session_id); if (result == GET_RELEASED_LICENSE_ERROR) { // This was partially removed already. - // The EME spec states that we should send a release message right away. - InitializationData empty_initialization_data; - CdmKeyMessage key_request; - std::string ignored_server_url; - - CdmResponseType result = cdm_engine_.GenerateKeyRequest( - session_id, session_id, empty_initialization_data, - kLicenseTypeRelease, app_parameters_, &key_request, NULL, - &ignored_server_url, NULL); - if (result != KEY_MESSAGE) { - LOGE("Unexpected error %d", result); - cdm_engine_.CloseSession(session_id); - return kUnexpectedError; - } - - LOGI("A license release has been generated."); - MessageType message_type = kLicenseRelease; - listener_->onMessage(session_id, message_type, key_request); + // The EME spec states that we should be able to load it, but not use it. } else if (result != KEY_ADDED) { LOGE("Unexpected error %d", result); return kUnexpectedError; @@ -435,6 +511,64 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { Cdm::Status CdmImpl::update(const std::string& session_id, const std::string& response) { + if (provision_request_sent_) { + std::string ignored_cert; + std::string ignored_wrapped_key; + + provision_request_sent_ = false; + CdmResponseType result = cdm_engine_.HandleProvisioningResponse( + response, &ignored_cert, &ignored_wrapped_key); + if (result != NO_ERROR) { + LOGE("Unexpected error %d", result); + return kUnexpectedError; + } + + // We are now provisioned, we need to recreate the unprovisioned sessions. + Cdm::Status ret = kSuccess; + for (UnprovisionedMap::iterator it = unprovisioned_sessions_.begin(); + it != unprovisioned_sessions_.end();) { + if (it->second.is_load) { + Cdm::Status load_status = load(it->first); + if (it->first != session_id) + listener_->onDeferredComplete(it->first, load_status); + else if (load_status != kSuccess) + ret = load_status; + unprovisioned_sessions_.erase(it++); + continue; + } + + result = cdm_engine_.OpenSession("com.widevine.alpha", &property_set_, + it->first, this); + if (result != NO_ERROR) { + LOGE("Unexpected error %d", result); + if (it->first == session_id) + ret = kUnexpectedError; + else + listener_->onDeferredComplete(it->first, kUnexpectedError); + // We failed to create the session. Keep it in the unprovisioned + // sessions map so that another call to generateRequest will try to + // create the session again. + ++it; + continue; + } + sessions_[it->first].type = it->second.type; + + // Call generate request again for the session. + if (it->second.has_init_data) { + Cdm::Status generate_status = generateRequest( + it->first, it->second.init_data_type, it->second.init_data); + if (it->first != session_id) + listener_->onDeferredComplete(it->first, generate_status); + else if (generate_status != kSuccess) + // Still erase the item because the session exists in |cdm_engine_|. + ret = generate_status; + } + unprovisioned_sessions_.erase(it++); + } + + return ret; + } + if (!cdm_engine_.IsOpenSession(session_id)) { LOGE("No such session: %s", session_id.c_str()); return kSessionNotFound; @@ -467,14 +601,11 @@ Cdm::Status CdmImpl::update(const std::string& session_id, // The underlying session in CdmEngine has stored a copy of the original // init data, so we can use an empty one this time. InitializationData empty_init_data; - std::string key_request; - CdmKeyRequestType key_request_type; - std::string ignored_server_url; + CdmKeyRequest key_request; CdmResponseType result = cdm_engine_.GenerateKeyRequest( session_id, session_id, empty_init_data, kLicenseTypeDeferred, - app_parameters_, &key_request, &key_request_type, - &ignored_server_url, NULL); + app_parameters_, &key_request); if (result != KEY_MESSAGE) { LOGE("Unexpected error %d", result); @@ -482,9 +613,9 @@ Cdm::Status CdmImpl::update(const std::string& session_id, } LOGI("A deferred license request has been generated."); - assert(key_request_type == kKeyRequestTypeInitial); + assert(key_request.type == kKeyRequestTypeInitial); MessageType message_type = kLicenseRequest; - listener_->onMessage(session_id, message_type, key_request); + listener_->onMessage(session_id, message_type, key_request.message); return kSuccess; } else if (result == OFFLINE_LICENSE_PROHIBITED) { LOGE("A temporary session cannot be used for a persistent license."); @@ -518,6 +649,10 @@ Cdm::Status CdmImpl::getExpiration(const std::string& session_id, LOGE("No such session: %s", session_id.c_str()); return kSessionNotFound; } + if (NULL == expiration) { + LOGE("Missing pointer to expiration result."); + return kTypeError; + } *expiration = sessions_[session_id].expiration; return kSuccess; @@ -529,11 +664,69 @@ Cdm::Status CdmImpl::getKeyStatuses(const std::string& session_id, LOGE("No such session: %s", session_id.c_str()); return kSessionNotFound; } + if (NULL == key_statuses) { + LOGE("Missing pointer to KeyStatusMap result."); + return kTypeError; + } *key_statuses = sessions_[session_id].key_statuses; return kSuccess; } +Cdm::Status CdmImpl::getKeyAllowedUsages(const std::string& session_id, + const std::string& key_id, + KeyAllowedUsageFlags* usage_flags) { + if (!cdm_engine_.IsOpenSession(session_id)) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (NULL == usage_flags) { + LOGE("Missing pointer to KeyAllowedUsageFlags result."); + return kTypeError; + } + + CdmKeyAllowedUsage usage_for_key; + CdmResponseType result = cdm_engine_.QueryKeyAllowedUsage(session_id, + key_id, + &usage_for_key); + if (result != NO_ERROR) { + if (result == KEY_NOT_FOUND_1) { + return kNoKey; + } else { + LOGE("Unexpected error %d", result); + return kUnexpectedError; + } + } + + *usage_flags = KeyAllowedFlags(usage_for_key); + return kSuccess; +} + +Cdm::Status CdmImpl::getKeyAllowedUsages(const std::string& key_id, + KeyAllowedUsageFlags* usage_flags) { + if (NULL == usage_flags) { + LOGE("Missing pointer to KeyAllowedUsageFlags result."); + return kTypeError; + } + + CdmKeyAllowedUsage usage_for_key; + CdmResponseType result = cdm_engine_.QueryKeyAllowedUsage(key_id, + &usage_for_key); + if (result != NO_ERROR) { + if (result == KEY_NOT_FOUND_1 || result == KEY_NOT_FOUND_2) { + return kNoKey; + } else if (result == KEY_CONFLICT_1) { + return kTypeError; + } else { + LOGE("Unexpected error %d", result); + return kUnexpectedError; + } + } + + *usage_flags = KeyAllowedFlags(usage_for_key); + return kSuccess; +} + Cdm::Status CdmImpl::setAppParameter(const std::string& key, const std::string& value) { if (key.empty()) { @@ -602,8 +795,7 @@ Cdm::Status CdmImpl::remove(const std::string& session_id) { } InitializationData empty_initialization_data; - CdmKeyMessage key_request; - std::string ignored_server_url; + CdmKeyRequest key_request; // Mark all keys as released ahead of generating the release request. // When released, cdm_engine_ will mark all keys as expired, which we will @@ -615,8 +807,7 @@ Cdm::Status CdmImpl::remove(const std::string& session_id) { CdmResponseType result = cdm_engine_.GenerateKeyRequest( session_id, session_id, empty_initialization_data, - kLicenseTypeRelease, app_parameters_, &key_request, NULL, - &ignored_server_url, NULL); + kLicenseTypeRelease, app_parameters_, &key_request); if (result != KEY_MESSAGE) { LOGE("Unexpected error %d", result); cdm_engine_.CloseSession(session_id); @@ -625,13 +816,14 @@ Cdm::Status CdmImpl::remove(const std::string& session_id) { LOGI("A license release has been generated."); MessageType message_type = kLicenseRelease; - listener_->onMessage(session_id, message_type, key_request); + listener_->onMessage(session_id, message_type, key_request.message); return kSuccess; } Cdm::Status CdmImpl::decrypt(const InputBuffer& input, const OutputBuffer& output) { - if (input.is_encrypted && input.iv_length != 16) { + const bool is_encrypted = (input.encryption_scheme != kClear); + if (is_encrypted && input.iv_length != 16) { LOGE("The IV must be 16 bytes long."); return kTypeError; } @@ -646,8 +838,13 @@ Cdm::Status CdmImpl::decrypt(const InputBuffer& input, std::vector iv(input.iv, input.iv + input.iv_length); CdmDecryptionParameters parameters; - parameters.is_encrypted = input.is_encrypted; + parameters.is_encrypted = is_encrypted; parameters.is_secure = output.is_secure; + if (input.encryption_scheme == kAesCtr) { + parameters.cipher_mode = kCipherModeCtr; + } else if (input.encryption_scheme == kAesCbc) { + parameters.cipher_mode = kCipherModeCbc; + } parameters.key_id = &key_id; parameters.encrypt_buffer = input.data; parameters.encrypt_length = input.data_length; @@ -660,6 +857,8 @@ Cdm::Status CdmImpl::decrypt(const InputBuffer& input, (input.first_subsample ? OEMCrypto_FirstSubsample : 0) | (input.last_subsample ? OEMCrypto_LastSubsample : 0); parameters.is_video = input.is_video; + parameters.pattern_descriptor.encrypt_blocks = input.pattern.encrypted_blocks; + parameters.pattern_descriptor.skip_blocks = input.pattern.clear_blocks; CdmSessionId empty_session_id; CdmResponseType result = cdm_engine_.Decrypt(empty_session_id, parameters); @@ -675,6 +874,118 @@ Cdm::Status CdmImpl::decrypt(const InputBuffer& input, return kDecryptError; } +Cdm::Status CdmImpl::genericEncrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + GenericEncryptionAlgorithmType algorithm, std::string* out_buffer) { + + CdmEncryptionAlgorithm cdm_algorithm = ConvertEncryptionAlgorithm(algorithm); + if (cdm_algorithm == wvcdm::kEncryptionAlgorithmUnknown) { + LOGE("Unrecognized encryption algorithm: %d.", cdm_algorithm); + return kNotSupported; + } + + CdmResponseType result = cdm_engine_.GenericEncrypt( + session_id, in_buffer, key_id, iv, cdm_algorithm, out_buffer); + if (result == NO_ERROR) { + return kSuccess; + } + if (result == SESSION_NOT_FOUND_13) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (result == KEY_NOT_FOUND_3 || result == KEY_ERROR_1) { + LOGE("Key Error: %s", session_id.c_str()); + return kNoKey; + } + LOGE("Unexpected error %d", result); + return kUnexpectedError; +} + +Cdm::Status CdmImpl::genericDecrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + GenericEncryptionAlgorithmType algorithm, std::string* out_buffer) { + + CdmEncryptionAlgorithm cdm_algorithm = ConvertEncryptionAlgorithm(algorithm); + if (cdm_algorithm == wvcdm::kEncryptionAlgorithmUnknown) { + LOGE("Unrecognized encryption algorithm: %d.", cdm_algorithm); + return kNotSupported; + } + + CdmResponseType result = cdm_engine_.GenericDecrypt( + session_id, in_buffer, key_id, iv, cdm_algorithm, out_buffer); + if (result == NO_ERROR) { + return kSuccess; + } + if (result == SESSION_NOT_FOUND_14) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (result == KEY_NOT_FOUND_4 || result == KEY_ERROR_2) { + LOGE("Key Error: %s", session_id.c_str()); + return kNoKey; + } + LOGE("Unexpected error %d", result); + return kUnexpectedError; +} + +Cdm::Status CdmImpl::genericSign( + const std::string& session_id, const std::string& message, + const std::string& key_id, GenericSigningAlgorithmType algorithm, + std::string* signature) { + + CdmSigningAlgorithm cdm_algorithm = ConvertSigningAlgorithm(algorithm); + if (cdm_algorithm == wvcdm::kSigningAlgorithmUnknown) { + LOGE("Unrecognized signing algorithm: %d.", cdm_algorithm); + return kNotSupported; + } + + CdmResponseType result = cdm_engine_.GenericSign( + session_id, message, key_id, cdm_algorithm, signature); + if (result == NO_ERROR) { + return kSuccess; + } + if (result == SESSION_NOT_FOUND_15) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (result == KEY_NOT_FOUND_5 || result == KEY_ERROR_3) { + LOGE("Key Error: %s", session_id.c_str()); + return kNoKey; + } + LOGE("Unexpected error %d", result); + return kUnexpectedError; +} + +Cdm::Status CdmImpl::genericVerify( + const std::string& session_id, const std::string& message, + const std::string& key_id, GenericSigningAlgorithmType algorithm, + const std::string& signature) { + + CdmSigningAlgorithm cdm_algorithm = ConvertSigningAlgorithm(algorithm); + if (cdm_algorithm == wvcdm::kSigningAlgorithmUnknown) { + LOGE("Unrecognized signing algorithm: %d.", cdm_algorithm); + return kNotSupported; + } + + CdmResponseType result = cdm_engine_.GenericVerify( + session_id, message, key_id, cdm_algorithm, signature); + if (result == NO_ERROR) { + return kSuccess; + } + if (result == SESSION_NOT_FOUND_16) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (result == KEY_NOT_FOUND_6 || result == KEY_ERROR_4) { + LOGE("Key Error: %s", session_id.c_str()); + return kNoKey; + } + LOGE("Unexpected error %d", result); + return kUnexpectedError; +} + void CdmImpl::onTimerExpired(void* context) { if (context == kPolicyTimerContext) { if (policy_timer_enabled_) { @@ -687,10 +998,9 @@ void CdmImpl::onTimerExpired(void* context) { } void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) { - CdmKeyMessage message; - std::string server_url; + CdmKeyRequest key_request; CdmResponseType result = - cdm_engine_.GenerateRenewalRequest(session_id, &message, &server_url); + cdm_engine_.GenerateRenewalRequest(session_id, &key_request); if (result != KEY_MESSAGE) { LOGE("Unexpected error %d", result); return; @@ -698,12 +1008,7 @@ void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) { LOGI("A license renewal has been generated."); MessageType message_type = kLicenseRenewal; - - // Post the server_url before providing the message. - // For systems that still require the server URL, - // the listener will add the URL to its renewal request. - listener_->onMessageUrl(session_id, server_url); - listener_->onMessage(session_id, message_type, message); + listener_->onMessage(session_id, message_type, key_request.message); } void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id, @@ -756,6 +1061,55 @@ void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id, } } +Cdm::KeyAllowedUsageFlags CdmImpl::KeyAllowedFlags( + const CdmKeyAllowedUsage& usages) { + KeyAllowedUsageFlags flags = kAllowNone; + flags |= (usages.decrypt_to_clear_buffer) ? kAllowDecryptToClearBuffer : 0; + flags |= (usages.decrypt_to_secure_buffer) ? kAllowDecryptToSecureBuffer : 0; + flags |= (usages.generic_encrypt) ? kAllowGenericEncrypt : 0; + flags |= (usages.generic_decrypt) ? kAllowGenericDecrypt : 0; + flags |= (usages.generic_sign) ? kAllowGenericSign : 0; + flags |= (usages.generic_verify) ? kAllowGenericSignatureVerify : 0; + return flags; +} + +bool CdmImpl::SendProvisioningRequest(const std::string& session_id) { + // Generate a provisioning request. + std::string empty_authority; + std::string ignored_base_url; + std::string signed_request; + CdmResponseType result = cdm_engine_.GetProvisioningRequest( + kCertificateWidevine, empty_authority, &signed_request, + &ignored_base_url); + if (result != NO_ERROR) { + LOGE("Unexpected error %d", result); + return false; + } + listener_->onDirectIndividualizationRequest(session_id, signed_request); + provision_request_sent_ = true; + + return true; +} + +CdmEncryptionAlgorithm CdmImpl::ConvertEncryptionAlgorithm( + GenericEncryptionAlgorithmType algorithm) { + if (algorithm == Cdm::kEncryptionAlgorithmAesCbc128) { + return wvcdm::kEncryptionAlgorithmAesCbc128; + } else { + LOGW("Unknown encryption algorithm: %d", algorithm); + return wvcdm::kEncryptionAlgorithmUnknown; + } +} + +CdmSigningAlgorithm CdmImpl::ConvertSigningAlgorithm( + GenericSigningAlgorithmType algorithm) { + if (algorithm == Cdm::kSigningAlgorithmHmacSha256) { + return wvcdm::kSigningAlgorithmHmacSha256; + } else { + LOGW("Unknown signing algorithm: %d", algorithm); + return wvcdm::kSigningAlgorithmUnknown; + } +} bool VerifyL1() { CryptoSession cs; @@ -772,7 +1126,6 @@ Cdm::Status Cdm::initialize( IStorage* storage, IClock* clock, ITimer* timer, - DeviceCertificateRequest* device_certificate_request, LogLevel verbosity) { // If you want to direct-render on L3, CryptoSession will pass that request // along to OEMCrypto. But if you want to use an opaque handle on L3, @@ -813,11 +1166,6 @@ Cdm::Status Cdm::initialize( return kTypeError; } - if (!device_certificate_request) { - LOGE("Device certificate request pointer is required!"); - return kTypeError; - } - // Our enum values match those in core/include/log.h g_cutoff = static_cast(verbosity); @@ -827,33 +1175,6 @@ Cdm::Status Cdm::initialize( host.storage = storage; host.clock = clock; host.timer = timer; - - device_certificate_request->needed = false; - - if (!host.provisioning_engine) { - host.provisioning_engine = new CdmEngine(); - } - bool has_cert = host.provisioning_engine->IsProvisioned( - kSecurityLevelL1, "" /* origin */); - - if (!has_cert) { - device_certificate_request->needed = true; - std::string empty_authority; - std::string empty_origin; - std::string base_url; - std::string signed_request; - CdmResponseType result = host.provisioning_engine->GetProvisioningRequest( - kCertificateWidevine, empty_authority, empty_origin, - &signed_request, &base_url); - if (result != NO_ERROR) { - LOGE("Unexpected error %d", result); - return kUnexpectedError; - } - device_certificate_request->url = base_url; - device_certificate_request->url.append("&signedRequest="); - device_certificate_request->url.append(signed_request); - } - host.initialized = true; return kSuccess; } @@ -865,6 +1186,7 @@ const char* Cdm::version() { // static Cdm* Cdm::create(IEventListener* listener, + IStorage* storage, bool privacy_mode) { if (!host.initialized) { LOGE("Not initialized!"); @@ -874,28 +1196,10 @@ Cdm* Cdm::create(IEventListener* listener, LOGE("No listener!"); return NULL; } - return new CdmImpl(listener, privacy_mode); -} + if (!storage) + storage = host.storage; -Cdm::Status Cdm::DeviceCertificateRequest::acceptReply( - const std::string& reply) { - if (!host.provisioning_engine) { - LOGE("Provisioning reply received while not in a provisioning state!"); - return kTypeError; - } - - std::string empty_origin; - std::string ignored_cert; - std::string ignored_wrapped_key; - - CdmResponseType result = - host.provisioning_engine->HandleProvisioningResponse( - empty_origin, reply, &ignored_cert, &ignored_wrapped_key); - if (result != NO_ERROR) { - LOGE("Unexpected error %d", result); - return kUnexpectedError; - } - return kSuccess; + return new CdmImpl(listener, storage, privacy_mode); } } // namespace widevine @@ -911,35 +1215,22 @@ int64_t Clock::GetCurrentTime() { class File::Impl { public: + Cdm::IStorage* storage; std::string name; bool read_only; bool truncate; }; -File::File() : impl_(NULL) {} +File::File(Impl* impl) : impl_(impl) {} -File::~File() { - Close(); -} - -bool File::Open(const std::string& file_path, int flags) { - if (!(flags & kCreate) && !host.storage->exists(file_path)) { - return false; - } - - impl_ = new Impl; - impl_->name = file_path; - impl_->read_only = (flags & kReadOnly); - impl_->truncate = (flags & kTruncate); - return true; -} +File::~File() {} ssize_t File::Read(char* buffer, size_t bytes) { if (!impl_) { return -1; } std::string data; - if (!host.storage->read(impl_->name, &data)) { + if (!impl_->storage->read(impl_->name, &data)) { return -1; } @@ -957,7 +1248,7 @@ ssize_t File::Write(const char* buffer, size_t bytes) { return -1; } std::string data(buffer, bytes); - if (!host.storage->write(impl_->name, data)) { + if (!impl_->storage->write(impl_->name, data)) { return -1; } return bytes; @@ -968,45 +1259,61 @@ void File::Close() { delete impl_; } impl_ = NULL; + delete this; } -bool File::Exists(const std::string& file_path) { - // An empty path is the "base directory" for CE CDM's file storage. - // Therefore, it should always be seen as existing. - // If it ever does not exist, CdmEngine detects this as a "factory reset" - // and wipes out all usage table data. - return file_path.empty() || host.storage->exists(file_path); +class FileSystem::Impl { + public: + widevine::Cdm::IStorage* storage; +}; + +FileSystem::FileSystem() : FileSystem("", NULL) {} + +FileSystem::FileSystem(const std::string& origin, void* extra_data) + : impl_(new Impl), origin_(origin) { + if (extra_data) + impl_->storage = (widevine::Cdm::IStorage*)extra_data; + else + impl_->storage = host.storage; } -bool File::Remove(const std::string& file_path) { - return host.storage->remove(file_path); +FileSystem::~FileSystem() { + delete impl_; + impl_ = NULL; } -bool File::Copy(const std::string& old_path, const std::string& new_path) { - std::string data; - bool read_ok = host.storage->read(old_path, &data); - if (!read_ok) return false; - return host.storage->write(new_path, data); +File* FileSystem::Open(const std::string& file_path, int flags) { + if (!impl_ || (!(flags & kCreate) && !impl_->storage->exists(file_path))) { + return NULL; + } + + File::Impl* file_impl = new File::Impl; + file_impl->storage = impl_->storage; + file_impl->name = file_path; + file_impl->read_only = (flags & kReadOnly); + file_impl->truncate = (flags & kTruncate); + return new File(file_impl); } -bool File::List(const std::string& path, std::vector* files) { - return false; +bool FileSystem::Exists(const std::string& file_path) { + if (!impl_) + return false; + + return file_path.empty() || impl_->storage->exists(file_path); } -bool File::CreateDirectory(const std::string dir_path) { - return true; +bool FileSystem::Remove(const std::string& file_path) { + if (!impl_) + return false; + + return impl_->storage->remove(file_path); } -bool File::IsDirectory(const std::string& dir_path) { - return false; -} +ssize_t FileSystem::FileSize(const std::string& file_path) { + if (!impl_) + return -1; -bool File::IsRegularFile(const std::string& file_path) { - return host.storage->exists(file_path); -} - -ssize_t File::FileSize(const std::string& file_path) { - return host.storage->size(file_path); + return impl_->storage->size(file_path); } } // namespace wvcdm diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index 58a1c030..ff98e7cf 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -56,7 +56,15 @@ const std::string kDefaultServerCertificate = a2bs_hex( "7C0011E0F5B38E4E298ED2CB301EB4564965F55C5D79757A250A4EB9C84AB3E6539F6B6FDF" "56899EA29914"); -const std::string kLicenseServer = "http://widevine-proxy.appspot.com/proxy"; +const std::string kProvisioningServerUrl = + "https://www.googleapis.com/" + "certificateprovisioning/v1/devicecertificates/create" + "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; + +const std::string kLicenseServerAppspot = + "http://widevine-proxy.appspot.com/proxy"; +const std::string kLicenseServerUat = "https://proxy.uat.widevine.com/proxy"; + const std::string kCencInitData = a2bs_hex( "00000042" // blob size "70737368" // "pssh" @@ -89,11 +97,35 @@ const std::string kNonWidevineCencInitData = a2bs_hex( const std::string kWebMInitData = a2bs_hex("deadbeefdeadbeefdeadbeefdeadbeef"); const std::string kKeyIdsInitData = "{\"kids\":[\"67ef0gd8pvfd0\",\"77ef0gd8pvfd0\"]}"; +const std::string kHlsInitData = + "#EXT-X-KEY:METHOD=SAMPLE-AES,KEYFORMAT=\"com.widevine\",KEYFORMATVERSIONS=" + "\"1\",URI=\"data:text/plain;base64,ew0KICAgInByb3ZpZGVyIjogIndpZGV2aW5lX3R" + "lc3QiLA0KICAgImNvbnRlbnRfaWQiOiAiWW1sblluVmphMkoxYm01NSIsDQogICAia2V5X2lkc" + "yI6IFsNCiAgICAgICI5Yjc1OTA0MDMyMWE0MDhhNWM3NzY4YjQ1MTEyODdhNiINCiAgIF0NCn0" + "=\",IV=0x75537a79fa41abc7b598ea72aba0c26f"; -// Dummy encrypted data. -const std::vector kKeyId1 = a2b_hex( +// This Key ID must match the key retrieved from kLicenseServerAppspot by +// kCencInitData. +const std::vector kKeyIdCtr = a2b_hex( "371ea35e1a985d75d198a7f41020dc23"); -const std::vector kInput1 = a2b_hex( +// This Key ID must match the key retrieved from kLicenseServerUat by +// kHlsInitData. +const std::vector kKeyIdCbc = a2b_hex( + "9b759040321a408a5c7768b4511287a6"); + +// A default pattern object disables patterns during decryption. +const Cdm::Pattern kPatternNone; +// The recommended pattern from CENC 3.0, which is also the pattern used by +// HLS. Encrypts 1 in every 10 crypto blocks. +const Cdm::Pattern kPatternRecommended(1, 9); +// The recommended pattern for HLS Audio, which should be decrypted in CENC 3.0 +// cbcs mode despite not using patterns. This pattern disables patterned +// decryption by having one encrypted block and no clear blocks. +const Cdm::Pattern kPatternHlsAudio(1, 0); + +// Dummy encrypted data using the CENC 3.0 "cenc" mode. Encrypted using the +// key matching kKeyIdCtr. +const std::vector kInputCenc = a2b_hex( "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" @@ -102,9 +134,9 @@ const std::vector kInput1 = a2b_hex( "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"); -const std::vector kIv1 = a2b_hex( +const std::vector kIvCenc = a2b_hex( "f6f4b1e600a5b67813ed2bded913ba9f"); -const std::vector kOutput1 = a2b_hex( +const std::vector kOutputCenc = a2b_hex( "217ce9bde99bd91e9733a1a00b9b557ac3a433dc92633546156817fae26b6e1c" "942ac20a89ff79f4c2f25fba99d6a44618a8c0420b27d54e3da17b77c9d43cca" "595d259a1e4a8b6d7744cd98c5d3f921adc252eb7d8af6b916044b676a574747" @@ -114,6 +146,75 @@ const std::vector kOutput1 = a2b_hex( "4884604c8da8a53ce33db9ff8f1c5bb6bb97f37b39906bf41596555c1bcce9ed" "08a899cd760ff0899a1170c2f224b9c52997a0785b7fe170805fd3e8b1127659"); +// Dummy encrypted data using the CENC 3.0 "cens" mode. Encrypted using the +// key matching kKeyIdCtr. +const std::vector kInputCens = a2b_hex( + "1660a777a301908b5e8c15b465ed7fa434793f65a8be816278f9479d741a78e0" + "b245e17629d63bbc2b15a5fa98b21daf62bdaf054113604ef19311adc5c3b74c" + "6167dc3160f27c4920d2f9ae4a7f8dfd029dde48bce29b2751f27f12503d369d" + "0ceb8b347e2884f51715f612badf15934aaa39db886e749afb8d8bdd29a18dd6" + "2b0c4355935c4dcc5ec0153307154ace5bfedcdaa2b670052660889f3d64c4b3" + "e363b16dc312d7e20373e873c760fae8b8bb39eccb6fe16e0198f6818ba24c30" + "39dec55ef91ddc47c320ec284e24d1c8cdd62515e8ce5c0cb01bea2fbf36ce99" + "246f5f8a2aca37719524dadffd4926a75a06402779a945d0b2c14a9c3f060a34"); +const std::vector kIvCens = a2b_hex( + "a891b8000af53049d7b24bdc19074839"); +const std::vector kOutputCens = a2b_hex( + "4bc4abcd79205e54188f04f99ea7e02534793f65a8be816278f9479d741a78e0" + "b245e17629d63bbc2b15a5fa98b21daf62bdaf054113604ef19311adc5c3b74c" + "6167dc3160f27c4920d2f9ae4a7f8dfd029dde48bce29b2751f27f12503d369d" + "0ceb8b347e2884f51715f612badf15934aaa39db886e749afb8d8bdd29a18dd6" + "2b0c4355935c4dcc5ec0153307154ace5bfedcdaa2b670052660889f3d64c4b3" + "f6104e15275ecb58324fb8f25ccde60db8bb39eccb6fe16e0198f6818ba24c30" + "39dec55ef91ddc47c320ec284e24d1c8cdd62515e8ce5c0cb01bea2fbf36ce99" + "246f5f8a2aca37719524dadffd4926a75a06402779a945d0b2c14a9c3f060a34"); + +// Dummy encrypted data using the CENC 3.0 "cbc1" mode. Encrypted using the +// key matching kKeyIdCbc. +const std::vector kInputCbc1 = a2b_hex( + "a69c76294ccd7e709fc3d11b1e0ccd4a74c9ffa8ce31ab92437c4da03b85822d" + "6f0da6d7935121cd585950ecc61efc83d2d86be9c32b3091cf546de987d9b480" + "fae8b8c35222f6fb7e2939b1af4c1445b6bd3ac22aeafc06ec016b011d465bf0" + "9d9a3a18865518bca314b1208830f0a18e6922b1d0a451df8f2c09efb416ca1d" + "0bdf93a7610f40da65fd23fc65531bb01373a85658043ed238e79d2b3f3c49e7" + "842ea0488a862932850153849f5ac20ce8181594240d16bb309d7523ffb9a7f0" + "edd976a6dcb0c90bf6895dad90b8f373b22162c397b0d0e3e49041dce4f7a34f" + "1dbe1e2c0f3f6be9d5bbc3e783743a70df89bf488de8dd97106c7fb9fdbbf662"); +const std::vector kIvCbc1 = a2b_hex( + "0111321322793b04f871aab28f6b066e"); +const std::vector kOutputCbc1 = a2b_hex( + "d5c7a71abfbfa2b490916d0e316c7b7e928b2cdaf9768b682b98f4087d664faa" + "c8f05bd97fede1c678dc4320df4ac65674ad63370616df3ee85acc145b4bc7a8" + "9169214197489350faa658ddff36959cf8dc2328bca5b1ccf26da4e1ce717595" + "a11ddf354a9811890afbb2207e90367bf007df42d99c682e6024cf7671273523" + "06d3e68a0fa2914640842759911bfdf90be7fc84742031989bb0b676d93a1904" + "4ba6811a032ddafd9e2d2caa44ec17363794b661d2460aa4517b1e349f0eeb23" + "9c2e83d31584f56a31b1688f89a4c64917e0037ae6aa7e483cd641dec38c3aba" + "195ca7942df98c124d4be96524edbda671aab2a52a2305637101f274e031bbc7"); + +// Dummy encrypted data using the CENC 3.0 "cbcs" mode. Encrypted using the +// key matching kKeyIdCbc. +const std::vector kInputCbcs = a2b_hex( + "7d8665445b3ac25fda29054e81626ed89f528f87315bdb07ba7fdad32835808f" + "6458893d28a247c37ec56c48f89ba6f941757edf22fea1b69980833746526dba" + "a8e9193125a4ac73893df76784aeb954124c59aa9771e5e5f91478ed720d2cfe" + "7421c5f7acbdfb762e75da0b48ba3f7bbf1dda3e8ff8ad1b625622438c100bd7" + "a063a0518b21bcbc6ebc2809478ebfd612e3400b515018b64fc1ba23c5b38b6d" + "45aaa4c0c394c3b75066390b646bf273fc4913f7e20b8c856d20561337fd4383" + "86938f189e0354ee9425974fc7153e61e006c3b472f21e2f3e33a1c94435d747" + "da153cd7f0cb8a7e4d1ee43acbf51027604cfe93808b42a2b6f30b4667d056e4"); +const std::vector kIvCbcs = a2b_hex( + "8e261c9660f5930ebe1734510cb9bc23"); +const std::vector kOutputCbcs = a2b_hex( + "bf7d1e9edc64a1782e884870edde98399f528f87315bdb07ba7fdad32835808f" + "6458893d28a247c37ec56c48f89ba6f941757edf22fea1b69980833746526dba" + "a8e9193125a4ac73893df76784aeb954124c59aa9771e5e5f91478ed720d2cfe" + "7421c5f7acbdfb762e75da0b48ba3f7bbf1dda3e8ff8ad1b625622438c100bd7" + "a063a0518b21bcbc6ebc2809478ebfd612e3400b515018b64fc1ba23c5b38b6d" + "d48cfbfc9e08ff501c62d5e85200dab0fc4913f7e20b8c856d20561337fd4383" + "86938f189e0354ee9425974fc7153e61e006c3b472f21e2f3e33a1c94435d747" + "da153cd7f0cb8a7e4d1ee43acbf51027604cfe93808b42a2b6f30b4667d056e4"); + const std::string kValue = "A Value"; const std::string kNewValue = "A New Value"; @@ -121,8 +222,7 @@ const std::string kParamName = "PARAM"; const std::string kParamName2 = "PARAM2"; -class CdmTest : public Test, - public Cdm::IEventListener { +class CdmTest : public Test, public Cdm::IEventListener { public: CdmTest() {} virtual ~CdmTest() {} @@ -136,6 +236,11 @@ class CdmTest : public Test, void(const std::string& session_id)); MOCK_METHOD1(onRemoveComplete, void(const std::string& session_id)); + MOCK_METHOD2(onDeferredComplete, + void(const std::string& session_id, Cdm::Status error_code)); + MOCK_METHOD2(onDirectIndividualizationRequest, + void(const std::string& session_id, + const std::string& message)); protected: virtual void SetUp() OVERRIDE { @@ -144,15 +249,17 @@ class CdmTest : public Test, // Clear anything stored by OEMCrypto. ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_DeleteUsageTable()); + int result = OEMCrypto_DeleteUsageTable(); + // Don't fault OEMCrypto implementations without usage tables: + if (result != OEMCrypto_ERROR_NOT_IMPLEMENTED) { + EXPECT_EQ(OEMCrypto_SUCCESS, result); + } ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); // Reinit the library. - Cdm::DeviceCertificateRequest cert_request; Cdm::Status status = Cdm::initialize( Cdm::kNoSecureOutput, PropertiesCE::GetClientInfo(), - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); ASSERT_EQ(Cdm::kSuccess, status); // Make a fresh CDM. @@ -172,7 +279,7 @@ class CdmTest : public Test, } void CreateAdditionalCdm(bool privacy_mode, scoped_ptr* cdm) { - cdm->reset(Cdm::create(this, privacy_mode)); + cdm->reset(Cdm::create(this, NULL, privacy_mode)); ASSERT_NE((Cdm*)0, cdm->get()); } @@ -226,10 +333,11 @@ class CdmTest : public Test, if (ok) ASSERT_EQ(kHttpOk, status_code); } - void FetchLicense(const std::string& message, + void FetchLicense(const std::string& license_server, + const std::string& message, std::string* response) { int status_code; - bool ok = Fetch(kLicenseServer, message, response, &status_code); + bool ok = Fetch(license_server, message, response, &status_code); ASSERT_TRUE(ok); if (ok) ASSERT_EQ(kHttpOk, status_code); } @@ -237,12 +345,13 @@ class CdmTest : public Test, void FetchLicenseFailure(const std::string& message, int expected_status_code) { int status_code; - bool ok = Fetch(kLicenseServer, message, NULL, &status_code); + bool ok = Fetch(kLicenseServerAppspot, message, NULL, &status_code); ASSERT_TRUE(ok); if (ok) ASSERT_EQ(expected_status_code, status_code); } void CreateSessionAndGenerateRequest(Cdm::SessionType session_type, + Cdm::InitDataType init_data_type, std::string* session_id, std::string* message) { Cdm::Status status = cdm_->createSession(session_type, session_id); @@ -250,32 +359,50 @@ class CdmTest : public Test, std::string init_data; if (session_type == Cdm::kTemporary) { - init_data = kCencInitData; - } else { - init_data = kCencPersistentInitData; + if (init_data_type == Cdm::kCenc) { + init_data = kCencInitData; + } else if (init_data_type == Cdm::kHls) { + init_data = kHlsInitData; + } + } else if (session_type == Cdm::kPersistentLicense || + session_type == Cdm::kPersistentUsageRecord) { + if (init_data_type == Cdm::kCenc) { + init_data = kCencPersistentInitData; + } } + ASSERT_FALSE(init_data.empty()); EXPECT_CALL(*this, onMessage(*session_id, Cdm::kLicenseRequest, _)). WillOnce(SaveArg<2>(message)); - status = cdm_->generateRequest(*session_id, Cdm::kCenc, init_data); + status = cdm_->generateRequest(*session_id, init_data_type, init_data); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); } void CreateSessionAndFetchLicense(Cdm::SessionType session_type, + Cdm::InitDataType init_data_type, std::string* session_id, std::string* response) { std::string message; ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( - session_type, session_id, &message)); - FetchLicense(message, response); + session_type, init_data_type, session_id, &message)); + + std::string license_server; + if (init_data_type == Cdm::kCenc) { + license_server = kLicenseServerAppspot; + } else if (init_data_type == Cdm::kHls) { + license_server = kLicenseServerUat; + } + ASSERT_FALSE(license_server.empty()); + FetchLicense(license_server, message, response); } void CreateSessionAndUpdate(Cdm::SessionType session_type, + Cdm::InitDataType init_data_type, std::string* session_id) { std::string response; ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( - session_type, session_id, &response)); + session_type, init_data_type, session_id, &response)); EXPECT_CALL(*this, onKeyStatusesChange(*session_id)); Cdm::Status status = cdm_->update(*session_id, response); ASSERT_EQ(Cdm::kSuccess, status); @@ -286,7 +413,8 @@ class CdmTest : public Test, const std::string& message) { // Acquire a license. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // This license should be accepted, but the keys are not expected to change. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -299,6 +427,44 @@ class CdmTest : public Test, }; +struct DecryptParam { + public: + DecryptParam( + const std::string& short_name_param, + Cdm::InitDataType init_data_type_param, + const std::vector& key_id_param, + const std::vector& iv_param, + Cdm::EncryptionScheme scheme_param, + const Cdm::Pattern& pattern_param, + const std::vector& input_param, + const std::vector& output_param) + : short_name(short_name_param), + init_data_type(init_data_type_param), + key_id(&key_id_param), + iv(&iv_param), + scheme(scheme_param), + pattern(&pattern_param), + input(&input_param), + output(&output_param) {} + + const std::string short_name; + const Cdm::InitDataType init_data_type; + const std::vector* const key_id; + const std::vector* const iv; + const Cdm::EncryptionScheme scheme; + const Cdm::Pattern* const pattern; + const std::vector* const input; + const std::vector* const output; +}; + +void PrintTo(const DecryptParam& value, ::std::ostream* os) { + *os << value.short_name << " DecryptParam"; +} + +class CdmTestWithDecryptParam : public CdmTest, + public WithParamInterface {}; + + class MockTimerClient : public Cdm::ITimer::IClient { public: MockTimerClient() {} @@ -335,14 +501,12 @@ TEST_F(CdmTest, TestHostTimer) { } TEST_F(CdmTest, Initialize) { - Cdm::DeviceCertificateRequest cert_request; Cdm::Status status; // Try with an invalid output type. status = Cdm::initialize( static_cast(-1), PropertiesCE::GetClientInfo(), - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); // Try with various client info properties missing. @@ -353,130 +517,64 @@ TEST_F(CdmTest, Initialize) { broken_client_info.product_name.clear(); status = Cdm::initialize( Cdm::kNoSecureOutput, broken_client_info, - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); broken_client_info = working_client_info; broken_client_info.company_name.clear(); status = Cdm::initialize( Cdm::kNoSecureOutput, broken_client_info, - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); broken_client_info = working_client_info; broken_client_info.device_name.clear(); // Not required status = Cdm::initialize( Cdm::kNoSecureOutput, broken_client_info, - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kSuccess, status); broken_client_info = working_client_info; broken_client_info.model_name.clear(); status = Cdm::initialize( Cdm::kNoSecureOutput, broken_client_info, - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); broken_client_info = working_client_info; broken_client_info.arch_name.clear(); // Not required status = Cdm::initialize( Cdm::kNoSecureOutput, broken_client_info, - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kSuccess, status); broken_client_info = working_client_info; broken_client_info.build_info.clear(); // Not required status = Cdm::initialize( Cdm::kNoSecureOutput, broken_client_info, - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kSuccess, status); // Try with various host interfaces missing. status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, - NULL, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + NULL, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, - g_host, NULL, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, NULL, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, - g_host, g_host, NULL, &cert_request, - static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kTypeError, status); - - status = Cdm::initialize( - Cdm::kNoSecureOutput, working_client_info, - g_host, g_host, g_host, NULL, - static_cast(g_cutoff)); + g_host, g_host, NULL, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); // One last init with everything correct and working. status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kSuccess, status); -} - -TEST_F(CdmTest, DeviceCertificateRequest) { - uint32_t nonce = 0; - uint8_t buffer[1]; - size_t size = 0; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); - int result = OEMCrypto_RewrapDeviceRSAKey( - 0, buffer, 0, buffer, 0, &nonce, buffer, 0, buffer, buffer, &size); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); - if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { - LOGW("WARNING: Skipping DeviceCertificateRequest because the device does " - "not support provisioning. If you are using a baked-in certificate, " - "this is expected. Otherwise, something is wrong."); - return; - } - - // Clear any existing certificates. - g_host->remove("cert.bin"); - - // Reinit the library without a device cert. - Cdm::DeviceCertificateRequest cert_request; - Cdm::Status status = Cdm::initialize( - Cdm::kNoSecureOutput, PropertiesCE::GetClientInfo(), - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); - - // This should succeed, but indicate that we need a cert still. - EXPECT_EQ(Cdm::kSuccess, status); - EXPECT_TRUE(cert_request.needed); - EXPECT_FALSE(cert_request.url.empty()); - - // Creating a session should fail. - std::string session_id; - status = cdm_->createSession(Cdm::kTemporary, &session_id); - EXPECT_EQ(Cdm::kNeedsDeviceCertificate, status); - - // Loading a session should fail. - status = cdm_->load(kBogusSessionId); - EXPECT_EQ(Cdm::kNeedsDeviceCertificate, status); - - // We should be able to provision. - std::string reply; - ASSERT_NO_FATAL_FAILURE(FetchCertificate(cert_request.url, &reply)); - status = cert_request.acceptReply(reply); - ASSERT_EQ(Cdm::kSuccess, status); - - // We should now be able to create a session. - status = cdm_->createSession(Cdm::kTemporary, &session_id); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kSuccess, status); } @@ -560,6 +658,14 @@ TEST_F(CdmTest, GenerateRequest) { EXPECT_EQ(Cdm::kNotSupported, status); Mock::VerifyAndClear(this); + // Create a new session and generate a license request for HLS. + status = cdm_->createSession(Cdm::kTemporary, &session_id); + ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); + status = cdm_->generateRequest(session_id, Cdm::kHls, kHlsInitData); + EXPECT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + // Create a new session and try a bogus init data type. status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); @@ -611,11 +717,12 @@ TEST_F(CdmTest, Update) { std::string session_id; std::string message; ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( - Cdm::kTemporary, &session_id, &message)); + Cdm::kTemporary, Cdm::kCenc, &session_id, &message)); // Acquire a license. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -667,7 +774,7 @@ TEST_F(CdmTest, LoadTemporary) { std::string session_id; std::string response; ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( - Cdm::kTemporary, &session_id, &response)); + Cdm::kTemporary, Cdm::kCenc, &session_id, &response)); // Update the temporary session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -688,7 +795,7 @@ TEST_F(CdmTest, LoadPersistent) { std::string session_id; std::string response; ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( - Cdm::kPersistentLicense, &session_id, &response)); + Cdm::kPersistentLicense, Cdm::kCenc, &session_id, &response)); // Update the persistent session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -722,11 +829,44 @@ TEST_F(CdmTest, LoadPersistent) { Mock::VerifyAndClear(this); } +TEST_F(CdmTest, PerOriginLoadPersistent) { + std::string session_id; + std::string response; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kPersistentLicense, Cdm::kCenc, &session_id, &response)); + + // Update and close the persistent session. + Cdm::Status status = cdm_->update(session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + status = cdm_->close(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + + // Should be able to load the session again after recreating the CDM. + ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); + ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); + status = cdm_->load(session_id); + EXPECT_EQ(Cdm::kSuccess, status); + + // Create another host to use its storage. This will simulate another origin. + TestHost other_host; + scoped_ptr other_cdm( + Cdm::create(this, &other_host, /* privacy_mode */ true)); + ASSERT_TRUE(other_cdm.get()); + status = other_cdm->setServerCertificate(kDefaultServerCertificate); + ASSERT_EQ(Cdm::kSuccess, status); + + // Should not be able to load from another origin. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + status = other_cdm->load(session_id); + EXPECT_EQ(Cdm::kSessionNotFound, status); + Mock::VerifyAndClear(this); +} + TEST_F(CdmTest, LoadUsageRecord) { std::string session_id; std::string response; ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( - Cdm::kPersistentUsageRecord, &session_id, &response)); + Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id, &response)); // Update the session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -740,7 +880,7 @@ TEST_F(CdmTest, LoadUsageRecord) { // There should be no usable keys after loading this session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0); status = cdm_->load(session_id); EXPECT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); @@ -749,7 +889,7 @@ TEST_F(CdmTest, LoadUsageRecord) { ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0); status = cdm_->load(session_id); EXPECT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); @@ -773,7 +913,8 @@ TEST_F(CdmTest, LoadBogus) { TEST_F(CdmTest, GetKeyStatuses) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, Cdm::kCenc, &session_id)); // We should be able to query status and see a usable key. Cdm::KeyStatusMap map; @@ -783,7 +924,7 @@ TEST_F(CdmTest, GetKeyStatuses) { // The key ID should be the one we are expecting. const std::string expected_key_id( - reinterpret_cast(kKeyId1.data()), kKeyId1.size()); + reinterpret_cast(kKeyIdCtr.data()), kKeyIdCtr.size()); EXPECT_EQ(expected_key_id, map.begin()->first); // Let the key expire. @@ -807,7 +948,8 @@ TEST_F(CdmTest, GetKeyStatuses) { TEST_F(CdmTest, GetExpiration) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, Cdm::kCenc, &session_id)); // We should be able to query expiration and get a value in the future. int64_t expiration; @@ -840,7 +982,7 @@ TEST_F(CdmTest, GetExpiration) { TEST_F(CdmTest, Remove) { std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentLicense, &session_id)); + Cdm::kPersistentLicense, Cdm::kCenc, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -859,7 +1001,8 @@ TEST_F(CdmTest, Remove) { // Post the release message to the license server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onRemoveComplete(session_id)); @@ -884,7 +1027,8 @@ TEST_F(CdmTest, Remove) { EXPECT_EQ(Cdm::kInvalidState, status); // Try a temporary session. - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, Cdm::kCenc, &session_id)); status = cdm_->remove(session_id); EXPECT_EQ(Cdm::kRangeError, status); } @@ -892,7 +1036,7 @@ TEST_F(CdmTest, Remove) { TEST_F(CdmTest, RemoveUsageRecord) { std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentUsageRecord, &session_id)); + Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -911,7 +1055,8 @@ TEST_F(CdmTest, RemoveUsageRecord) { // Post the release message to the license server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onRemoveComplete(session_id)); @@ -927,7 +1072,7 @@ TEST_F(CdmTest, RemoveUsageRecord) { TEST_F(CdmTest, RemoveIncomplete) { std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentLicense, &session_id)); + Cdm::kPersistentLicense, Cdm::kCenc, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -949,15 +1094,11 @@ TEST_F(CdmTest, RemoveIncomplete) { ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); - // Load the partially removed session, which will immediately generate a - // release message. - message.clear(); + // Load the partially removed session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce( - SaveArg<2>(&message)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0); status = cdm_->load(session_id); ASSERT_EQ(Cdm::kSuccess, status); - ASSERT_FALSE(message.empty()); Mock::VerifyAndClear(this); // This session has no keys. @@ -965,9 +1106,20 @@ TEST_F(CdmTest, RemoveIncomplete) { ASSERT_EQ(Cdm::kSuccess, status); EXPECT_TRUE(map.empty()); + // Remove the session again to fire the release message. + message.clear(); + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce( + SaveArg<2>(&message)); + status = cdm_->remove(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + ASSERT_FALSE(message.empty()); + Mock::VerifyAndClear(this); + // Post the release message to the license server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -984,7 +1136,7 @@ TEST_F(CdmTest, RemoveIncomplete) { TEST_F(CdmTest, RemoveUsageRecordIncomplete) { std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentUsageRecord, &session_id)); + Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -1006,13 +1158,19 @@ TEST_F(CdmTest, RemoveUsageRecordIncomplete) { ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); - // Load the partially removed session, which will immediately generate a - // release message. + // Load the partially removed session. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0); + status = cdm_->load(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Remove the session again to fire a release message. message.clear(); EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce( SaveArg<2>(&message)); - status = cdm_->load(session_id); + status = cdm_->remove(session_id); ASSERT_EQ(Cdm::kSuccess, status); ASSERT_FALSE(message.empty()); Mock::VerifyAndClear(this); @@ -1024,7 +1182,8 @@ TEST_F(CdmTest, RemoveUsageRecordIncomplete) { // Post the release message to the license server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -1042,7 +1201,7 @@ TEST_F(CdmTest, RemoveNotLoaded) { // Create a persistent session and then close it. std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentLicense, &session_id)); + Cdm::kPersistentLicense, Cdm::kCenc, &session_id)); Cdm::Status status = cdm_->close(session_id); ASSERT_EQ(Cdm::kSuccess, status); @@ -1052,37 +1211,6 @@ TEST_F(CdmTest, RemoveNotLoaded) { EXPECT_EQ(Cdm::kSessionNotFound, status); } -TEST_F(CdmTest, DecryptClear) { - Cdm::InputBuffer input; - Cdm::OutputBuffer output; - - input.key_id = kKeyId1.data(); - input.key_id_length = kKeyId1.size(); - input.iv = kIv1.data(); - input.iv_length = kIv1.size(); - input.data = kInput1.data(); - input.data_length = kInput1.size(); - input.is_encrypted = true; - - std::vector output_buffer(input.data_length); - output.data = &(output_buffer[0]); - output.data_length = output_buffer.size(); - - // Decrypt without keys loaded should fail. - Cdm::Status status = cdm_->decrypt(input, output); - ASSERT_EQ(Cdm::kNoKey, status); - - // Create a session with the right keys. - std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); - - // Decrypt should now succeed. - status = cdm_->decrypt(input, output); - ASSERT_EQ(Cdm::kSuccess, status); - EXPECT_EQ(kOutput1, output_buffer); -} -// TODO: add infrastructure to test secure buffer decrypt for some platforms - TEST_F(CdmTest, RequestPersistentLicenseWithWrongInitData) { // Generate a request for a persistent license without using the correct // persistent content init data. @@ -1118,7 +1246,8 @@ TEST_F(CdmTest, RequestTemporaryLicenseWithWrongInitData) { // Acquire a license. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // This license should not be accepted. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -1129,7 +1258,8 @@ TEST_F(CdmTest, RequestTemporaryLicenseWithWrongInitData) { TEST_F(CdmTest, Renewal) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, Cdm::kCenc, &session_id)); // We should have a timer. EXPECT_NE(0, g_host->NumTimers()); @@ -1169,15 +1299,16 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { // Expect a license request type message, but this is actually a server cert // provisioning request. std::string message; - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)). - WillOnce(SaveArg<2>(&message)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)) + .WillOnce(SaveArg<2>(&message)); status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); // Relay it to the server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // No keys will change, since this wasn't a license. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -1196,7 +1327,8 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { ASSERT_TRUE(map.empty()); // Relay the license request to the server. - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. The keys will change now. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1); @@ -1225,7 +1357,8 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { Mock::VerifyAndClear(this); // Relay it to the server. - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Keys will change, since this was an actual license. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1); @@ -1244,14 +1377,15 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { status = cdm2->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); message.clear(); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce( - SaveArg<2>(&message)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)) + .WillOnce(SaveArg<2>(&message)); status = cdm2->generateRequest(session_id, Cdm::kCenc, kCencInitData); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); // Relay it to the server. - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // No keys will change, since this wasn't a license. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -1275,7 +1409,8 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { Mock::VerifyAndClear(this); // Relay it to the server. - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Keys will change, since this was an actual license. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1); @@ -1364,4 +1499,334 @@ TEST_F(CdmTest, SetAppParameters) { ASSERT_EQ(Cdm::kTypeError, status); } +TEST_P(CdmTestWithDecryptParam, DecryptToClearBuffer) { + DecryptParam param = GetParam(); + + Cdm::InputBuffer input; + Cdm::OutputBuffer output; + + input.key_id = param.key_id->data(); + input.key_id_length = param.key_id->size(); + input.iv = param.iv->data(); + input.iv_length = param.iv->size(); + input.data = param.input->data(); + input.data_length = param.input->size(); + input.encryption_scheme = param.scheme; + input.pattern = *param.pattern; + + std::vector output_buffer(input.data_length); + output.data = &(output_buffer[0]); + output.data_length = output_buffer.size(); + + // Decrypt without keys loaded should fail. + Cdm::Status status = cdm_->decrypt(input, output); + ASSERT_EQ(Cdm::kNoKey, status); + + // Create a session with the right keys. + std::string session_id; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, param.init_data_type, &session_id)); + + // Decrypt should now succeed. + status = cdm_->decrypt(input, output); + ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_EQ(*param.output, output_buffer); +} + +INSTANTIATE_TEST_CASE_P(CdmDecryptTest, CdmTestWithDecryptParam, Values( + DecryptParam("CENC 3.0 cenc Mode", + Cdm::kCenc, kKeyIdCtr, kIvCenc, Cdm::kAesCtr, + kPatternNone, kInputCenc, kOutputCenc), + DecryptParam("CENC 3.0 cens Mode", + Cdm::kCenc, kKeyIdCtr, kIvCens, Cdm::kAesCtr, + kPatternRecommended, kInputCens, kOutputCens), + DecryptParam("CENC 3.0 cbc1 Mode", + Cdm::kHls, kKeyIdCbc, kIvCbc1, Cdm::kAesCbc, + kPatternNone, kInputCbc1, kOutputCbc1), + DecryptParam("CENC 3.0 cbcs Mode", + Cdm::kHls, kKeyIdCbc, kIvCbcs, Cdm::kAesCbc, + kPatternRecommended, kInputCbcs, kOutputCbcs), + DecryptParam("HLS Audio (CENC 3.0 cbcs Mode Without a Pattern)", + Cdm::kHls, kKeyIdCbc, kIvCbc1, Cdm::kAesCbc, + kPatternHlsAudio, kInputCbc1, kOutputCbc1) + )); + +// TODO: add infrastructure to test secure buffer decrypt for some platforms + +class CdmIndividualizationTest : public CdmTest { + protected: + bool CheckProvisioningSupport() { + uint32_t nonce = 0; + uint8_t buffer[1]; + size_t size = 0; + EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + int result = OEMCrypto_RewrapDeviceRSAKey(0, buffer, 0, buffer, 0, &nonce, + buffer, 0, buffer, buffer, &size); + EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); + if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + LOGW( + "WARNING: Skipping PerOriginDeviceProvisioning because the device " + "does not support provisioning. If you are using a baked-in " + "certificate, this is expected. Otherwise, something is wrong."); + return false; + } + return true; + } + + std::string GetProvisioningResponse(const std::string& message) { + std::string reply; + std::string uri = kProvisioningServerUrl; + uri += "&signedRequest=" + message; + FetchCertificate(uri, &reply); + if (HasFatalFailure()) + return ""; + return reply; + } +}; + +TEST_F(CdmIndividualizationTest, BasicFlow) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); + EXPECT_EQ(Cdm::kSuccess, status); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData); + EXPECT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Complete the provisioning request. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + status = cdm_->update(session_id, reply); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, WillNotSendRequestTwice) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Create a second session. + std::string session_id2; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id2)); + + // Should not get another individualization request. + EXPECT_CALL(*this, onMessage(_, _, _)).Times(0); + ASSERT_EQ(Cdm::kDeferred, + cdm_->generateRequest(session_id2, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Complete the provisioning request, should generate requests for both + // sessions. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onDeferredComplete(session_id2, Cdm::kSuccess)).Times(1); + EXPECT_EQ(Cdm::kSuccess, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, + WillNotSendMessageWhenGenerateRequestNotCalled) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Create a second session, don't call generateRequest for it. + std::string session_id2; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id2)); + + // Complete the provisioning request, should not get calls for the second + // session. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(0); + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + EXPECT_EQ(Cdm::kSuccess, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); + + // We should get a license message for the second session. + EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(1); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id2, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, PropagatesErrorsInUpdate) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, cdm_->generateRequest(session_id, Cdm::kCenc, + kInvalidCencInitData)); + Mock::VerifyAndClear(this); + + // Complete the provisioning request, should get an error call. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(_, _, _)).Times(0); + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + EXPECT_EQ(Cdm::kNotSupported, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, OnlyPropagatesErrorsForThisSession) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Create another session that will cause an error. + std::string session_id_2; + ASSERT_EQ(Cdm::kSuccess, + cdm_->createSession(Cdm::kTemporary, &session_id_2)); + ASSERT_EQ(Cdm::kDeferred, cdm_->generateRequest(session_id_2, Cdm::kCenc, + kInvalidCencInitData)); + + // Complete the provisioning request, should succeed, but get an error + // callback. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onMessage(session_id_2, _, _)).Times(0); + EXPECT_CALL(*this, onDeferredComplete(session_id_2, Cdm::kNotSupported)) + .Times(1); + EXPECT_EQ(Cdm::kSuccess, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, WorksWithLoad) { + if (!CheckProvisioningSupport()) return; + + // Create an offline session to load. + std::string session_id; + ASSERT_NO_FATAL_FAILURE( + CreateSessionAndUpdate(Cdm::kPersistentLicense, Cdm::kCenc, &session_id)); + EXPECT_EQ(Cdm::kSuccess, cdm_->close(session_id)); + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Loading a session should succeed, we should get an individualization + // request right away. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, cdm_->load(session_id)); + Mock::VerifyAndClear(this); + + // Complete the provisioning request. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + // Because we are now provisioned with a new key, we can't load the session, + // but we will still be provisioned. + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + EXPECT_EQ(Cdm::kUnexpectedError, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); + + // Create a second session, we should be previsioned at this point. + std::string session_id2; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id2)); + EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(1); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id2, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, WillResendOnProvisioningError) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Fail to provision the device. + EXPECT_CALL(*this, onMessage(_, _, _)).Times(0); + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + EXPECT_EQ(Cdm::kUnexpectedError, cdm_->update(session_id, "")); + Mock::VerifyAndClear(this); + + // Should get another individualization request. + std::string session_id_2; + ASSERT_EQ(Cdm::kSuccess, + cdm_->createSession(Cdm::kTemporary, &session_id_2)); + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id_2, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id_2, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); +} + } // namespace widevine diff --git a/cdm/test/cdm_test_main.cpp b/cdm/test/cdm_test_main.cpp index ed68408e..6d10571b 100644 --- a/cdm/test/cdm_test_main.cpp +++ b/cdm/test/cdm_test_main.cpp @@ -15,7 +15,7 @@ #include "test_host.h" #if defined(OEMCRYPTO_TESTS) -# include "oemcrypto_test.h" +# include "oec_device_features.h" #endif using namespace widevine; @@ -68,13 +68,11 @@ int main(int argc, char** argv) { #endif client_info.build_info = __DATE__; - Cdm::DeviceCertificateRequest cert_request; Cdm::Status status = Cdm::initialize( - Cdm::kNoSecureOutput, client_info, g_host, g_host, g_host, &cert_request, + Cdm::kNoSecureOutput, client_info, g_host, g_host, g_host, static_cast(verbosity)); (void)status; // status is now used when assertions are turned off. assert(status == Cdm::kSuccess); - assert(cert_request.needed == false); #if defined(OEMCRYPTO_TESTS) // Set up the OEMCrypto test harness. diff --git a/cdm/test/test_host.cpp b/cdm/test/test_host.cpp index 26faf0e8..deb1af3b 100644 --- a/cdm/test/test_host.cpp +++ b/cdm/test/test_host.cpp @@ -71,6 +71,7 @@ bool TestHost::exists(const std::string& name) { } bool TestHost::remove(const std::string& name) { + LOGD("remove: %s", name.c_str()); files_.erase(name); return true; } diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index 3b77b098..d4582df2 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -7,6 +7,7 @@ #include "certificate_provisioning.h" #include "crypto_session.h" +#include "file_store.h" #include "initialization_data.h" #include "lock.h" #include "oemcrypto_adapter.h" @@ -26,22 +27,24 @@ typedef std::map CdmReleaseKeySetMap; class CdmEngine { public: - CdmEngine(); + CdmEngine(FileSystem* file_system); virtual ~CdmEngine(); // Session related methods virtual CdmResponseType OpenSession(const CdmKeySystem& key_system, CdmClientPropertySet* property_set, - const std::string& origin, + const CdmSessionId& forced_session_id, + WvCdmEventListener* event_listener); + virtual CdmResponseType OpenSession(const CdmKeySystem& key_system, + CdmClientPropertySet* property_set, WvCdmEventListener* event_listener, - const CdmSessionId* forced_session_id, CdmSessionId* session_id); virtual CdmResponseType CloseSession(const CdmSessionId& session_id); virtual bool IsOpenSession(const CdmSessionId& session_id); - virtual CdmResponseType OpenKeySetSession( - const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set, - const std::string& origin, WvCdmEventListener* event_listener); + virtual CdmResponseType OpenKeySetSession(const CdmKeySetId& key_set_id, + CdmClientPropertySet* property_set, + WvCdmEventListener* event_listener); virtual CdmResponseType CloseKeySetSession(const CdmKeySetId& key_set_id); // License related methods @@ -58,27 +61,17 @@ class CdmEngine { // app_parameters: Additional, application-specific parameters that factor // into the request generation. This is ignored for release // and renewal requests. - // key_request: This must be non-null and point to a CdmKeyMessage. The buffer - // will have its contents replaced with the key request. - // key_request_type: May be null. If it is non-null, it will be filled with - // key request type, whether it is an initial request, - // renewal request or release request etc. - // server_url: This must be non-null and point to a string. The string will - // have its contents replaced with the default URL (if one is - // known) to send this key request to. - // key_set_id_out: May be null. If it is non-null, the CdmKeySetId pointed to - // will have its contents replaced with the key set ID of the - // session. Note that for non-offline license requests, the - // key set ID is empty, so the CdmKeySetId will be cleared. - // TODO(kqyang): Consider refactor GenerateKeyRequest to reduce the number of - // parameters. + // key_request: This must be non-null and point to a CdmKeyRequest. The + // message field will be filled with the key request, the + // type field will be filled with the key request type, + // whether it is an initial request, renewal request, + // release request, etc. The url field will be filled with + // the default URL (if one is known) to send this key + // request to. virtual CdmResponseType GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, const InitializationData& init_data, const CdmLicenseType license_type, - CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - CdmKeyRequestType* key_request_type, std::string* server_url, - CdmKeySetId* key_set_id_out); - + CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request); // Accept license response and extract key info. virtual CdmResponseType AddKey(const CdmSessionId& session_id, const CdmKeyResponse& key_data, @@ -90,9 +83,8 @@ class CdmEngine { virtual CdmResponseType RemoveKeys(const CdmSessionId& session_id); // Construct valid renewal request for the current session keys. - virtual CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id, - CdmKeyMessage* key_request, - std::string* server_url); + virtual CdmResponseType GenerateRenewalRequest( + const CdmSessionId& session_id, CdmKeyRequest* key_request); // Accept renewal response and update key info. virtual CdmResponseType RenewKey(const CdmSessionId& session_id, @@ -100,38 +92,48 @@ class CdmEngine { // Query system information virtual CdmResponseType QueryStatus(SecurityLevel security_level, - const std::string& key, - std::string* value); + const std::string& query_token, + std::string* query_response); // Query session information virtual CdmResponseType QuerySessionStatus(const CdmSessionId& session_id, - CdmQueryMap* key_info); + CdmQueryMap* query_response); virtual bool IsReleaseSession(const CdmSessionId& session_id); virtual bool IsOfflineSession(const CdmSessionId& session_id); // Query license information virtual CdmResponseType QueryKeyStatus(const CdmSessionId& session_id, - CdmQueryMap* key_info); + CdmQueryMap* query_response); - // Query session control information - virtual CdmResponseType QueryKeyControlInfo(const CdmSessionId& session_id, - CdmQueryMap* key_info); + // Query the types of usage permitted for the specified key. + virtual CdmResponseType QueryKeyAllowedUsage(const CdmSessionId& session_id, + const std::string& key_id, + CdmKeyAllowedUsage* key_usage); + + // Query the types of usage permitted for the specified key. + // Apply the query across all sessions. If the key is found in more than + // one session, return the allowed usage settings only if the usage settings + // are identical for each instance of the key. Otherwise, clear the settings + // and return KEY_CONFLICT_1. + virtual CdmResponseType QueryKeyAllowedUsage(const std::string& key_id, + CdmKeyAllowedUsage* key_usage); + + // Query OEMCrypto session ID + virtual CdmResponseType QueryOemCryptoSessionId( + const CdmSessionId& session_id, CdmQueryMap* query_response); // Provisioning related methods virtual CdmResponseType GetProvisioningRequest( CdmCertificateType cert_type, const std::string& cert_authority, - const std::string& origin, CdmProvisioningRequest* request, - std::string* default_url); + CdmProvisioningRequest* request, std::string* default_url); virtual CdmResponseType HandleProvisioningResponse( - const std::string& origin, const CdmProvisioningResponse& response, - std::string* cert, std::string* wrapped_key); + const CdmProvisioningResponse& response, std::string* cert, + std::string* wrapped_key); - virtual bool IsProvisioned(CdmSecurityLevel security_level, - const std::string& origin); + virtual bool IsProvisioned(CdmSecurityLevel security_level); - virtual CdmResponseType Unprovision(CdmSecurityLevel security_level, - const std::string& origin); + virtual CdmResponseType Unprovision(CdmSecurityLevel security_level); // Usage related methods for streaming licenses // Retrieve a random usage info from the list of all usage infos for this app @@ -154,6 +156,35 @@ class CdmEngine { virtual CdmResponseType Decrypt(const CdmSessionId& session_id, const CdmDecryptionParameters& parameters); + // Generic crypto operations - provides basic crypto operations that an + // application can use outside of content stream processing + + // Encrypts a buffer of app-level data. + virtual CdmResponseType GenericEncrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + CdmEncryptionAlgorithm algorithm, std::string* out_buffer); + + // Decrypts a buffer of app-level data. + virtual CdmResponseType GenericDecrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + CdmEncryptionAlgorithm algorithm, std::string* out_buffer); + + // Computes the signature for a message. + virtual CdmResponseType GenericSign(const std::string& session_id, + const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature); + + // Verifies the signature on a buffer of app-level data. + virtual CdmResponseType GenericVerify(const std::string& session_id, + const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature); + virtual size_t SessionSize() const { return sessions_.size(); } // Is the key known to any session? @@ -175,6 +206,12 @@ class CdmEngine { private: // private methods + CdmResponseType OpenSession(const CdmKeySystem& key_system, + CdmClientPropertySet* property_set, + WvCdmEventListener* event_listener, + const CdmSessionId* forced_session_id, + CdmSessionId* session_id); + void DeleteAllUsageReportsUponFactoryReset(); bool ValidateKeySystem(const CdmKeySystem& key_system); CdmResponseType GetUsageInfo(const std::string& app_id, @@ -190,6 +227,7 @@ class CdmEngine { CdmReleaseKeySetMap release_key_sets_; scoped_ptr cert_provisioning_; SecurityLevel cert_provisioning_requested_security_level_; + FileSystem* file_system_; static bool seeded_; diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index d473d16b..63c1908d 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -8,6 +8,7 @@ #include "crypto_session.h" #include "device_files.h" +#include "file_store.h" #include "initialization_data.h" #include "license.h" #include "oemcrypto_adapter.h" @@ -22,12 +23,13 @@ class WvCdmEventListener; class CdmSession { public: - CdmSession(CdmClientPropertySet* cdm_client_property_set, - const std::string& origin, WvCdmEventListener* event_listener, - const CdmSessionId* forced_session_id); + CdmSession(FileSystem* file_system); virtual ~CdmSession(); - virtual CdmResponseType Init(); + virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set); + virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set, + const CdmSessionId* forced_session_id, + WvCdmEventListener* event_listener); virtual CdmResponseType RestoreOfflineSession( const CdmKeySetId& key_set_id, const CdmLicenseType license_type); @@ -35,25 +37,27 @@ class CdmSession { const CdmKeyMessage& key_request, const CdmKeyResponse& key_response); virtual const CdmSessionId& session_id() { return session_id_; } + virtual const CdmKeySetId& key_set_id() { return key_set_id_; } virtual CdmResponseType GenerateKeyRequest( const InitializationData& init_data, CdmLicenseType license_type, - const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - CdmKeyRequestType* key_request_type, std::string* server_url, - CdmKeySetId* key_set_id); + const CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request); // AddKey() - Accept license response and extract key info. - virtual CdmResponseType AddKey(const CdmKeyResponse& key_response, - CdmKeySetId* key_set_id); + virtual CdmResponseType AddKey(const CdmKeyResponse& key_response); // Query session status - virtual CdmResponseType QueryStatus(CdmQueryMap* key_info); + virtual CdmResponseType QueryStatus(CdmQueryMap* query_response); // Query license information - virtual CdmResponseType QueryKeyStatus(CdmQueryMap* key_info); + virtual CdmResponseType QueryKeyStatus(CdmQueryMap* query_response); - // Query session control info - virtual CdmResponseType QueryKeyControlInfo(CdmQueryMap* key_info); + // Query allowed usages for key + virtual CdmResponseType QueryKeyAllowedUsage(const std::string& key_id, + CdmKeyAllowedUsage* key_usage); + + // Query OEMCrypto session ID + virtual CdmResponseType QueryOemCryptoSessionId(CdmQueryMap* query_response); // Decrypt() - Accept encrypted buffer and return decrypted data. virtual CdmResponseType Decrypt(const CdmDecryptionParameters& parameters); @@ -61,8 +65,7 @@ class CdmSession { // License renewal // GenerateRenewalRequest() - Construct valid renewal request for the current // session keys. - virtual CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request, - std::string* server_url); + virtual CdmResponseType GenerateRenewalRequest(CdmKeyRequest* key_request); // RenewKey() - Accept renewal response and update key info. virtual CdmResponseType RenewKey(const CdmKeyResponse& key_response); @@ -70,13 +73,13 @@ class CdmSession { // License release // GenerateReleaseRequest() - Construct valid release request for the current // session keys. - virtual CdmResponseType GenerateReleaseRequest(CdmKeyMessage* key_request, - std::string* server_url); + virtual CdmResponseType GenerateReleaseRequest(CdmKeyRequest* key_request); // ReleaseKey() - Accept response and release key. virtual CdmResponseType ReleaseKey(const CdmKeyResponse& key_response); virtual bool IsKeyLoaded(const KeyId& key_id); + virtual int64_t GetDurationRemaining(); // Used for notifying the Policy Engine of resolution changes virtual void NotifyResolution(uint32_t width, uint32_t height); @@ -116,11 +119,41 @@ class CdmSession { bool DeleteLicense(); + // Generate unique ID for each new session. + CdmSessionId GenerateSessionId(); + + // Generic crypto operations - provides basic crypto operations that an + // application can use outside of content stream processing + + // Encrypts a buffer of app-level data. + virtual CdmResponseType GenericEncrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer); + + // Decrypts a buffer of app-level data. + virtual CdmResponseType GenericDecrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer); + + // Computes the signature for a message. + virtual CdmResponseType GenericSign(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature); + + // Verifies the signature on a buffer of app-level data. + virtual CdmResponseType GenericVerify(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature); + private: friend class CdmSessionTest; - // Generate unique ID for each new session. - CdmSessionId GenerateSessionId(); bool GenerateKeySetId(CdmKeySetId* key_set_id); CdmResponseType StoreLicense(); @@ -135,7 +168,6 @@ class CdmSession { // instance variables bool initialized_; CdmSessionId session_id_; - const std::string origin_; scoped_ptr license_parser_; scoped_ptr crypto_session_; scoped_ptr policy_engine_; @@ -167,6 +199,9 @@ class CdmSession { // license type release and offline related information CdmKeySetId key_set_id_; + bool mock_license_parser_in_use_; + bool mock_policy_engine_in_use_; + CORE_DISALLOW_COPY_AND_ASSIGN(CdmSession); }; diff --git a/core/include/certificate_provisioning.h b/core/include/certificate_provisioning.h index dd222828..22460c21 100644 --- a/core/include/certificate_provisioning.h +++ b/core/include/certificate_provisioning.h @@ -12,6 +12,7 @@ namespace wvcdm { class CdmSession; +class FileSystem; class CertificateProvisioning { public: @@ -26,7 +27,7 @@ class CertificateProvisioning { CdmProvisioningRequest* request, std::string* default_url); CdmResponseType HandleProvisioningResponse( - const std::string& origin, + FileSystem* file_system, const CdmProvisioningResponse& response, std::string* cert, std::string* wrapped_key); diff --git a/core/include/crypto_key.h b/core/include/crypto_key.h index d973a021..921517e6 100644 --- a/core/include/crypto_key.h +++ b/core/include/crypto_key.h @@ -17,6 +17,7 @@ class CryptoKey { const std::string& key_data_iv() const { return key_data_iv_; } const std::string& key_control() const { return key_control_; } const std::string& key_control_iv() const { return key_control_iv_; } + CdmCipherMode cipher_mode() const { return cipher_mode_; } void set_key_id(const std::string& key_id) { key_id_ = key_id; } void set_key_data(const std::string& key_data) { key_data_ = key_data; } void set_key_data_iv(const std::string& iv) { key_data_iv_ = iv; } @@ -24,6 +25,9 @@ class CryptoKey { void set_key_control_iv(const std::string& ctl_iv) { key_control_iv_ = ctl_iv; } + void set_cipher_mode(CdmCipherMode cipher_mode) { + cipher_mode_ = cipher_mode; + } bool HasKeyControl() const { return key_control_.size() >= 16; } @@ -33,6 +37,7 @@ class CryptoKey { std::string key_data_; std::string key_control_; std::string key_control_iv_; + CdmCipherMode cipher_mode_; }; } // namespace wvcdm diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h index 1e8d52c2..9f1893c8 100644 --- a/core/include/crypto_session.h +++ b/core/include/crypto_session.h @@ -35,6 +35,7 @@ class CryptoSession { virtual bool GetApiVersion(uint32_t* version); virtual bool GetSystemId(uint32_t* system_id); virtual bool GetProvisioningId(std::string* provisioning_id); + virtual uint8_t GetSecurityPatchLevel(); virtual CdmResponseType Open() { return Open(kLevelDefault); } virtual CdmResponseType Open(SecurityLevel requested_security_level); @@ -102,6 +103,25 @@ class CryptoSession { virtual bool GetNumberOfOpenSessions(size_t* count); virtual bool GetMaxNumberOfSessions(size_t* max); + virtual CdmResponseType GenericEncrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer); + virtual CdmResponseType GenericDecrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer); + virtual CdmResponseType GenericSign(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature); + virtual CdmResponseType GenericVerify(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature); + private: void Init(); void Terminate(); @@ -113,9 +133,17 @@ class CryptoSession { bool GenerateRsaSignature(const std::string& message, std::string* signature); size_t GetOffset(std::string message, std::string field); bool SetDestinationBufferType(); - bool SelectKey(const std::string& key_id); + static const OEMCrypto_Algorithm kInvalidAlgorithm = + static_cast(-1); + + OEMCrypto_Algorithm GenericSigningAlgorithm(CdmSigningAlgorithm algorithm); + OEMCrypto_Algorithm GenericEncryptionAlgorithm( + CdmEncryptionAlgorithm algorithm); + size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm); + + static const size_t kAes128BlockSize = 16; // Block size for AES_CBC_128 static const size_t kSignatureSize = 32; // size for HMAC-SHA256 signature static Lock crypto_lock_; static bool initialized_; @@ -129,11 +157,13 @@ class CryptoSession { bool is_destination_buffer_type_valid_; SecurityLevel requested_security_level_; - KeyId key_id_; + KeyId cached_key_id_; uint64_t request_id_base_; static uint64_t request_id_index_; + CdmCipherMode cipher_mode_; + CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession); }; diff --git a/core/include/device_files.h b/core/include/device_files.h index 88dabae0..6eef4ee7 100644 --- a/core/include/device_files.h +++ b/core/include/device_files.h @@ -18,7 +18,7 @@ namespace wvcdm { -class File; +class FileSystem; class DeviceFiles { public: @@ -28,7 +28,7 @@ class DeviceFiles { kLicenseStateUnknown, } LicenseState; - DeviceFiles(); + DeviceFiles(FileSystem*); virtual ~DeviceFiles(); virtual bool Init(CdmSecurityLevel security_level); @@ -36,14 +36,12 @@ class DeviceFiles { return Init(security_level); } - virtual bool StoreCertificate(const std::string& origin, - const std::string& certificate, + virtual bool StoreCertificate(const std::string& certificate, const std::string& wrapped_private_key); - virtual bool RetrieveCertificate(const std::string& origin, - std::string* certificate, + virtual bool RetrieveCertificate(std::string* certificate, std::string* wrapped_private_key); - virtual bool HasCertificate(const std::string& origin); - virtual bool RemoveCertificate(const std::string& origin); + virtual bool HasCertificate(); + virtual bool RemoveCertificate(); virtual bool StoreLicense(const std::string& key_set_id, const LicenseState state, @@ -100,6 +98,13 @@ class DeviceFiles { CdmKeyMessage* license_request, CdmKeyResponse* license_response); + virtual bool StoreHlsAttributes(const std::string& key_set_id, + const CdmHlsMethod method, + const std::vector& media_segment_iv); + virtual bool RetrieveHlsAttributes(const std::string& key_set_id, + CdmHlsMethod* method, + std::vector* media_segment_iv); + virtual bool DeleteHlsAttributes(const std::string& key_set_id); private: // Helpers that wrap the File interface and automatically handle hashing, as // well as adding the device files base path to to the file name. @@ -113,29 +118,25 @@ class DeviceFiles { bool RemoveFile(const std::string& name); ssize_t GetFileSize(const std::string& name); - // Certificate and offline licenses are now stored in security - // level specific directories. In an earlier version they were - // stored in a common directory and need to be copied over. - virtual void SecurityLevelPathBackwardCompatibility(); - - static std::string GetCertificateFileName(const std::string& origin); + static std::string GetCertificateFileName(); + static std::string GetHlsAttributesFileNameExtension(); static std::string GetLicenseFileNameExtension(); static std::string GetUsageInfoFileName(const std::string& app_id); static std::string GetFileNameSafeHash(const std::string& input); - // For testing only: - void SetTestFile(File* file); #if defined(UNIT_TEST) FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel); FRIEND_TEST(DeviceCertificateStoreTest, StoreCertificate); FRIEND_TEST(DeviceCertificateTest, ReadCertificate); FRIEND_TEST(DeviceCertificateTest, HasCertificate); FRIEND_TEST(DeviceFilesStoreTest, StoreLicense); + FRIEND_TEST(DeviceFilesHlsAttributesTest, Delete); + FRIEND_TEST(DeviceFilesHlsAttributesTest, Read); + FRIEND_TEST(DeviceFilesHlsAttributesTest, Store); FRIEND_TEST(DeviceFilesTest, DeleteLicense); FRIEND_TEST(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem); FRIEND_TEST(DeviceFilesTest, RetrieveLicenses); FRIEND_TEST(DeviceFilesTest, AppParametersBackwardCompatibility); - FRIEND_TEST(DeviceFilesTest, SecurityLevelPathBackwardCompatibility); FRIEND_TEST(DeviceFilesTest, StoreLicenses); FRIEND_TEST(DeviceFilesTest, UpdateLicenseState); FRIEND_TEST(DeviceFilesUsageInfoTest, Delete); @@ -153,12 +154,10 @@ class DeviceFiles { static std::set reserved_license_ids_; - scoped_ptr file_; + FileSystem* file_system_; CdmSecurityLevel security_level_; bool initialized_; - bool test_file_; - CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles); }; diff --git a/core/include/file_store.h b/core/include/file_store.h index 8e77b4f1..6e28990f 100644 --- a/core/include/file_store.h +++ b/core/include/file_store.h @@ -15,39 +15,54 @@ namespace wvcdm { // File class. The implementation is platform dependent. class File { + public: + virtual ssize_t Read(char* buffer, size_t bytes); + virtual ssize_t Write(const char* buffer, size_t bytes); + virtual void Close(); + + protected: + class Impl; + + File(Impl*); + virtual ~File(); + + private: + Impl* impl_; + + friend class FileSystem; + CORE_DISALLOW_COPY_AND_ASSIGN(File); +}; + +class FileSystem { public: class Impl; // defines as bit flag enum OpenFlags { kNoFlags = 0, - kBinary = 1, - kCreate = 2, - kReadOnly = 4, // defaults to read and write access - kTruncate = 8 + kCreate = 1, + kReadOnly = 2, // defaults to read and write access + kTruncate = 4 }; - File(); - virtual ~File(); + FileSystem(); + FileSystem(const std::string& origin, void* extra_data); + virtual ~FileSystem(); - virtual bool Open(const std::string& file_path, int flags); - virtual ssize_t Read(char* buffer, size_t bytes); - virtual ssize_t Write(const char* buffer, size_t bytes); - virtual void Close(); + virtual File* Open(const std::string& file_path, int flags); virtual bool Exists(const std::string& file_path); virtual bool Remove(const std::string& file_path); - virtual bool Copy(const std::string& old_path, const std::string& new_path); - virtual bool List(const std::string& path, std::vector* files); - virtual bool CreateDirectory(const std::string dir_path); - virtual bool IsDirectory(const std::string& dir_path); - virtual bool IsRegularFile(const std::string& file_path); virtual ssize_t FileSize(const std::string& file_path); + const std::string& origin() const { return origin_; } + void SetOrigin(const std::string& origin); + private: Impl* impl_; + std::string origin_; - CORE_DISALLOW_COPY_AND_ASSIGN(File); + CORE_DISALLOW_COPY_AND_ASSIGN(FileSystem); }; } // namespace wvcdm diff --git a/core/include/initialization_data.h b/core/include/initialization_data.h index 80803c7b..c4edbd06 100644 --- a/core/include/initialization_data.h +++ b/core/include/initialization_data.h @@ -16,24 +16,62 @@ class InitializationData { InitializationData(const std::string& type = std::string(), const CdmInitData& data = CdmInitData()); - bool is_supported() const { return is_cenc_ || is_webm_; } + bool is_supported() const { return is_cenc_ || is_webm_ || is_hls_; } bool is_cenc() const { return is_cenc_; } + bool is_hls() const { return is_hls_; } bool is_webm() const { return is_webm_; } bool IsEmpty() const { return data_.empty(); } const std::string& type() const { return type_; } const CdmInitData& data() const { return data_; } + std::vector hls_iv() const { return hls_iv_; } + CdmHlsMethod hls_method() const { return hls_method_; } private: // Parse a blob of multiple concatenated PSSH atoms to extract the first // Widevine PSSH. bool ExtractWidevinePssh(const CdmInitData& init_data, CdmInitData* output); + bool ExtractHlsAttributes(const std::string& attribute_list, + CdmHlsMethod* method, std::vector* iv, + std::string* uri); + static bool ConstructWidevineInitData(CdmHlsMethod method, + const std::string& uri, + CdmInitData* output); + static bool ExtractQuotedAttribute(const std::string& attribute_list, + const std::string& key, + std::string* value); + static bool ExtractHexAttribute(const std::string& attribute_list, + const std::string& key, + std::vector* value); + static bool ExtractAttribute(const std::string& attribute_list, + const std::string& key, std::string* value); + + static std::vector ExtractKeyFormatVersions( + const std::string& key_format_versions); + +// For testing only: +#if defined(UNIT_TEST) + FRIEND_TEST(HlsAttributeExtractionTest, ExtractAttribute); + FRIEND_TEST(HlsConstructionTest, InitData); + FRIEND_TEST(HlsInitDataConstructionTest, InvalidUriDataFormat); + FRIEND_TEST(HlsInitDataConstructionTest, InvalidUriBase64Encode); + FRIEND_TEST(HlsHexAttributeExtractionTest, ExtractHexAttribute); + FRIEND_TEST(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions); + FRIEND_TEST(HlsParseTest, Parse); + FRIEND_TEST(HlsQuotedAttributeExtractionTest, ExtractQuotedAttribute); + FRIEND_TEST(HlsTest, ExtractHlsAttributes); +#endif + std::string type_; CdmInitData data_; bool is_cenc_; + bool is_hls_; bool is_webm_; + + std::vector hls_iv_; + CdmHlsMethod hls_method_; }; } // namespace wvcdm diff --git a/core/include/license_key_status.h b/core/include/license_key_status.h new file mode 100644 index 00000000..cc81dd5a --- /dev/null +++ b/core/include/license_key_status.h @@ -0,0 +1,139 @@ +// Copyright 2016 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_LICENSE_KEY_STATUS_H_ +#define WVCDM_CORE_LICENSE_KEY_STATUS_H_ + +#include + +#include "crypto_session.h" +#include "license_protocol.pb.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class LicenseKeyStatus; + +// Holds all content and operator session keys for a session. +class LicenseKeys { + public: + LicenseKeys() {} + virtual ~LicenseKeys() { Clear(); } + + virtual bool Empty() { return keys_.empty(); } + + // Returns true if the key is a content key (not an operator session key) + virtual bool IsContentKey(const KeyId& key_id); + + // Returns true if the key is currently usable for content decryption. + virtual bool CanDecryptContent(const KeyId& key_id); + + // Returns the allowed usages for a key. + virtual bool GetAllowedUsage(const KeyId& key_id, + CdmKeyAllowedUsage* allowed_usage); + + // Applies a new status to each content key. + // Returns true if any statuses changed, and sets new_usable_keys to + // true if the status changes resulted in keys becoming usable. + virtual bool ApplyStatusChange(CdmKeyStatus new_status, + bool* new_usable_keys); + + // Populates the CdmKeyStatusMap with the current content keys. + virtual void ExtractKeyStatuses(CdmKeyStatusMap* content_keys); + + // Determines whether the specified key can be used under the current + // resolution and/or hdcp constraints. If no constraints have been applied + // to the key, returns true. + virtual bool MeetsConstraints(const KeyId& key_id); + + // Applies a resolution and/or hdcp change to each key, updating their + // useability under their constraints. + virtual void ApplyConstraints(uint32_t new_resolution, + CryptoSession::HdcpCapability new_hdcp_level); + + // Extracts the keys from a license and makes them available for + // querying usage and constraint settings. + virtual void SetFromLicense( + const video_widevine_server::sdk::License& license); + + private: + typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; + typedef std::map::const_iterator + LicenseKeyStatusIterator; + + void Clear(); + + bool is_initialized_; + std::map keys_; + + CORE_DISALLOW_COPY_AND_ASSIGN(LicenseKeys); +}; + +// Holds the current license status of a key. +class LicenseKeyStatus { + friend class LicenseKeys; + + public: + // Returns true if the key is a content key (not an operator session key) + virtual bool IsContentKey() { return is_content_key_; } + + // Returns true if the key is currently usable for content decryption + virtual bool CanDecryptContent(); + + // Returns the usages allowed for this key. + virtual bool GetAllowedUsage(CdmKeyAllowedUsage* allowed_usage); + + // Returns the current status of the key. + virtual CdmKeyStatus GetKeyStatus() const { return key_status_; } + + // Applies a new status to this key. + // Returns true if the status changed, and sets new_usable_keys to + // true if the status changes resulted in the key becoming usable. + virtual bool ApplyStatusChange(CdmKeyStatus new_status, + bool* new_usable_keys); + + // Returns the current constraint status of this key. The result + // may change due to calls to ApplyConstraints(). + // Note: this will return true until the first call to ApplyConstraints(). + virtual bool MeetsConstraints() const { return meets_constraints_; } + + // Applies the given changes in resolution or HDCP settings. + virtual void ApplyConstraints( + uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level); + + protected: + typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; + typedef KeyContainer::OperatorSessionKeyPermissions + OperatorSessionKeyPermissions; + typedef KeyContainer::OutputProtection OutputProtection; + typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint; + typedef ::google::protobuf::RepeatedPtrField + ConstraintList; + + LicenseKeyStatus(const KeyContainer& key); + + virtual ~LicenseKeyStatus() {} + + private: + + void ParseContentKey(const KeyContainer& key); + void ParseOperatorSessionKey(const KeyContainer& key); + + bool HasConstraints() { + return is_content_key_ && constraints_.size() != 0; + } + + void SetConstraints(const ConstraintList& constraints); + + bool is_content_key_; + CdmKeyStatus key_status_; + bool meets_constraints_; + CdmKeyAllowedUsage allowed_usage_; + CryptoSession::HdcpCapability default_hdcp_level_; + ConstraintList constraints_; + + CORE_DISALLOW_COPY_AND_ASSIGN(LicenseKeyStatus); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_LICENSE_KEY_STATUS_H_ diff --git a/core/include/max_res_engine.h b/core/include/max_res_engine.h deleted file mode 100644 index 9a28d140..00000000 --- a/core/include/max_res_engine.h +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2014 Google Inc. All Rights Reserved. - -#ifndef WVCDM_CORE_MAX_RES_ENGINE_H_ -#define WVCDM_CORE_MAX_RES_ENGINE_H_ - -#include - -#include "crypto_session.h" -#include "license_protocol.pb.h" -#include "lock.h" -#include "scoped_ptr.h" -#include "wv_cdm_types.h" - -namespace wvcdm { - -class Clock; -class MaxResEngineTest; - -// Similar to the Policy Engine, this acts as an oracle that basically says -// "Yes(true) you may still decrypt or no(false) you may not decrypt this data -// anymore." -class MaxResEngine { - public: - explicit MaxResEngine(CryptoSession* crypto_session); - virtual ~MaxResEngine(); - - // The value returned is computed during the last call to SetLicense/ - // SetResolution/OnTimerEvent and may be out of sync depending on the amount - // of time elapsed. The current decryption status is not calculated when this - // function is called to avoid overhead in the decryption path. - virtual bool CanDecrypt(const KeyId& key_id); - - // SetLicense is used in handling the initial license response. It stores - // an exact copy of the key constraints from the license. - virtual void SetLicense(const video_widevine_server::sdk::License& license); - - // SetResolution is called when the current output resolution is updated by - // the decoder. The max-res engine will recalculate the current resolution - // constraints, (if any) which may affect the results for CanDecrypt(). - virtual void SetResolution(uint32_t width, uint32_t height); - - // OnTimerEvent is called when a timer fires. The max-res engine may check the - // current HDCP level using the crypto session, which may affect the results - // for CanDecrypt(). - virtual void OnTimerEvent(); - - private: - typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; - typedef ::video_widevine_server::sdk::License::KeyContainer::OutputProtection - OutputProtection; - typedef ::video_widevine_server::sdk::License::KeyContainer:: - VideoResolutionConstraint VideoResolutionConstraint; - typedef ::google::protobuf::RepeatedPtrField - ConstraintList; - - class KeyStatus { - public: - explicit KeyStatus(const ConstraintList& constraints); - KeyStatus(const ConstraintList& constraints, - const OutputProtection::HDCP& default_hdcp_level); - - bool can_decrypt() const { return can_decrypt_; } - - void Update(uint32_t res, - CryptoSession::HdcpCapability current_hdcp_level); - - private: - void Init(const ConstraintList& constraints); - - VideoResolutionConstraint* GetConstraintForRes(uint32_t res); - - static CryptoSession::HdcpCapability ProtobufHdcpToOemCryptoHdcp( - const OutputProtection::HDCP& input); - - bool can_decrypt_; - - CryptoSession::HdcpCapability default_hdcp_level_; - ConstraintList constraints_; - }; - - typedef std::map::const_iterator KeyIterator; - - void Init(CryptoSession* crypto_session, Clock* clock); - - void DeleteAllKeys(); - - Lock status_lock_; - std::map keys_; - uint32_t current_resolution_; - int64_t next_check_time_; - - scoped_ptr clock_; - CryptoSession* crypto_session_; - - // For testing - friend class MaxResEngineTest; - MaxResEngine(CryptoSession* crypto_session, Clock* clock); - - CORE_DISALLOW_COPY_AND_ASSIGN(MaxResEngine); -}; - -} // wvcdm - -#endif // WVCDM_CORE_MAX_RES_ENGINE_H_ diff --git a/core/include/oemcrypto_adapter.h b/core/include/oemcrypto_adapter.h index 5d7a648c..944cb66d 100644 --- a/core/include/oemcrypto_adapter.h +++ b/core/include/oemcrypto_adapter.h @@ -34,6 +34,7 @@ OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(SecurityLevel level, size_t* count); OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(SecurityLevel level, size_t* maximum); +uint8_t OEMCrypto_Security_Patch_Level(SecurityLevel level); } // namespace wvcdm #endif // WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ diff --git a/core/include/policy_engine.h b/core/include/policy_engine.h index aefe3c1e..ccef771d 100644 --- a/core/include/policy_engine.h +++ b/core/include/policy_engine.h @@ -6,8 +6,8 @@ #include #include +#include "license_key_status.h" #include "license_protocol.pb.h" -#include "max_res_engine.h" #include "scoped_ptr.h" #include "wv_cdm_types.h" @@ -67,7 +67,10 @@ class PolicyEngine { virtual void NotifySessionExpiration(); - virtual CdmResponseType Query(CdmQueryMap* key_info); + virtual CdmResponseType Query(CdmQueryMap* query_response); + + virtual CdmResponseType QueryKeyAllowedUsage(const KeyId& key_id, + CdmKeyAllowedUsage* key_usage); virtual const LicenseIdentification& license_id() { return license_id_; } @@ -84,11 +87,20 @@ class PolicyEngine { bool IsPlaybackStarted() { return playback_start_time_ > 0; } bool IsLicenseOrPlaybackDurationExpired(int64_t current_time); + int64_t GetLicenseOrPlaybackDurationRemaining(); bool CanRenew() { return policy_.can_renew(); } private: friend class PolicyEngineTest; + friend class PolicyEngineConstraintsTest; + + void InitDevice(CryptoSession* crypto_session); + void CheckDevice(int64_t current_time); + + void SetDeviceResolution(uint32_t width, uint32_t height) { + current_resolution_ = width * height; + } typedef enum { kLicenseStateInitial, @@ -119,9 +131,9 @@ class PolicyEngine { // expiry time changes. void NotifyExpirationUpdate(); - // These setters are for testing only. Takes ownership of the pointers. + // set_clock() is for testing only. It alters ownership of the + // passed-in pointer. void set_clock(Clock* clock); - void set_max_res_engine(MaxResEngine* max_res_engine); LicenseState license_state_; @@ -153,9 +165,14 @@ class PolicyEngine { CdmSessionId session_id_; WvCdmEventListener* event_listener_; - scoped_ptr max_res_engine_; + // Keys associated with license - holds allowed usage, usage constraints, + // and current status (CdmKeyStatus) + scoped_ptr license_keys_; - std::map keys_status_; + // Device checks + int64_t next_device_check_; + uint32_t current_resolution_; + CryptoSession* crypto_session_; scoped_ptr clock_; diff --git a/core/include/string_conversions.h b/core/include/string_conversions.h index f1f2ecb4..d261c804 100644 --- a/core/include/string_conversions.h +++ b/core/include/string_conversions.h @@ -15,12 +15,13 @@ std::vector a2b_hex(const std::string& label, const std::string& b); std::string a2bs_hex(const std::string& b); std::string b2a_hex(const std::vector& b); std::string b2a_hex(const std::string& b); +std::string Base64Encode(const std::vector& bin_input); +std::vector Base64Decode(const std::string& bin_input); std::string Base64SafeEncode(const std::vector& bin_input); std::string Base64SafeEncodeNoPad(const std::vector& bin_input); std::vector Base64SafeDecode(const std::string& bin_input); std::string HexEncode(const uint8_t* bytes, unsigned size); std::string IntToString(int value); -std::string UintToString(unsigned int value); int64_t htonll64(int64_t x); inline int64_t ntohll64(int64_t x) { return htonll64(x); } diff --git a/core/include/wv_cdm_constants.h b/core/include/wv_cdm_constants.h index c576d8a4..ba782ff8 100644 --- a/core/include/wv_cdm_constants.h +++ b/core/include/wv_cdm_constants.h @@ -80,8 +80,19 @@ static const std::string ISO_BMFF_AUDIO_MIME_TYPE = "audio/mp4"; static const std::string WEBM_VIDEO_MIME_TYPE = "video/webm"; static const std::string WEBM_AUDIO_MIME_TYPE = "audio/webm"; static const std::string CENC_INIT_DATA_FORMAT = "cenc"; +static const std::string HLS_INIT_DATA_FORMAT = "hls"; static const std::string WEBM_INIT_DATA_FORMAT = "webm"; +static const std::string HLS_KEYFORMAT_ATTRIBUTE = "KEYFORMAT"; +static const std::string HLS_KEYFORMAT_VERSIONS_ATTRIBUTE = "KEYFORMATVERSIONS"; +static const std::string HLS_KEYFORMAT_VERSION_VALUE_1 = "1"; +static const std::string HLS_METHOD_ATTRIBUTE = "METHOD"; +static const std::string HLS_METHOD_AES_128 = "AES-128"; +static const std::string HLS_METHOD_NONE = "NONE"; +static const std::string HLS_METHOD_SAMPLE_AES = "SAMPLE-AES"; +static const std::string HLS_IV_ATTRIBUTE = "IV"; +static const std::string HLS_URI_ATTRIBUTE = "URI"; + static const char EMPTY_ORIGIN[] = ""; } // namespace wvcdm diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index 06180447..c0ca5fd9 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -149,9 +149,9 @@ enum CdmResponseType { RENEW_KEY_ERROR_1, RENEW_KEY_ERROR_2, LICENSE_RENEWAL_SIGNING_ERROR, - RESTORE_OFFLINE_LICENSE_ERROR_1, + UNUSED_4, /* previously RESTORE_OFFLINE_LICENSE_ERROR_1 */ RESTORE_OFFLINE_LICENSE_ERROR_2, - SESSION_INIT_ERROR_1, + UNUSED_5, /* SESSION_INIT_ERROR_1 */ SESSION_INIT_ERROR_2, SESSION_INIT_GET_KEYBOX_ERROR, SESSION_NOT_FOUND_1, @@ -169,7 +169,7 @@ enum CdmResponseType { SIGNATURE_NOT_FOUND, STORE_LICENSE_ERROR_1, STORE_LICENSE_ERROR_2, - STORE_LICENSE_ERROR_3, + UNUSED_6, /* previously STORE_LICENSE_ERROR_3 */ STORE_USAGE_INFO_ERROR, UNPROVISION_ERROR_1, UNPROVISION_ERROR_2, @@ -213,6 +213,38 @@ enum CdmResponseType { SESSION_NOT_FOUND_11, LOAD_USAGE_INFO_FILE_ERROR, LOAD_USAGE_INFO_MISSING, + SESSION_FILE_HANDLE_INIT_ERROR, + INCORRECT_CRYPTO_MODE, + INVALID_PARAMETERS_ENG_5, + DECRYPT_ERROR, + INSUFFICIENT_OUTPUT_PROTECTION, + SESSION_NOT_FOUND_12, + KEY_NOT_FOUND_1, + KEY_NOT_FOUND_2, + KEY_CONFLICT_1, + INVALID_PARAMETERS_ENG_6, + INVALID_PARAMETERS_ENG_7, + INVALID_PARAMETERS_ENG_8, + INVALID_PARAMETERS_ENG_9, + INVALID_PARAMETERS_ENG_10, + INVALID_PARAMETERS_ENG_11, + INVALID_PARAMETERS_ENG_12, + SESSION_NOT_FOUND_13, + SESSION_NOT_FOUND_14, + SESSION_NOT_FOUND_15, + SESSION_NOT_FOUND_16, + KEY_NOT_FOUND_3, + KEY_NOT_FOUND_4, + KEY_NOT_FOUND_5, + KEY_NOT_FOUND_6, + KEY_ERROR_1, + KEY_ERROR_2, + KEY_ERROR_3, + KEY_ERROR_4, + INVALID_PARAMETERS_ENG_13, + INVALID_PARAMETERS_ENG_14, + INVALID_PARAMETERS_ENG_15, + INVALID_PARAMETERS_ENG_16, }; enum CdmKeyStatus { @@ -258,9 +290,89 @@ enum CdmCertificateType { kCertificateX509, }; +enum CdmHlsMethod { + kHlsMethodNone, + kHlsMethodAes128, + kHlsMethodSampleAes, +}; + +enum CdmCipherMode { + kCipherModeCtr, + kCipherModeCbc, +}; + +enum CdmEncryptionAlgorithm { + kEncryptionAlgorithmUnknown, + kEncryptionAlgorithmAesCbc128 +}; + +enum CdmSigningAlgorithm { + kSigningAlgorithmUnknown, + kSigningAlgorithmHmacSha256 +}; + +class CdmKeyAllowedUsage { + public: + CdmKeyAllowedUsage() { + Clear(); + } + + bool Valid() const { return valid_; } + void SetValid() { valid_ = true; } + + void Clear() { + decrypt_to_clear_buffer = false; + decrypt_to_secure_buffer = false; + generic_encrypt = false; + generic_decrypt = false; + generic_sign = false; + generic_verify = false; + valid_ = false; + } + + bool Equals(const CdmKeyAllowedUsage& other) { + if (!valid_ || !other.Valid() || + decrypt_to_clear_buffer != other.decrypt_to_clear_buffer || + decrypt_to_secure_buffer != other.decrypt_to_secure_buffer || + generic_encrypt != other.generic_encrypt || + generic_decrypt != other.generic_decrypt || + generic_sign != other.generic_sign || + generic_verify != other.generic_verify) { + return false; + } + return true; + } + + bool decrypt_to_clear_buffer; + bool decrypt_to_secure_buffer; + bool generic_encrypt; + bool generic_decrypt; + bool generic_sign; + bool generic_verify; + + private: + bool valid_; +}; + +// For schemes that do not use pattern encryption (cenc and cbc1), encrypt +// and skip should be set to 0. For those that do (cens and cbcs), it is +// recommended that encrypt+skip bytes sum to 10 and for cbcs that a 1:9 +// encrypt:skip ratio be used. See ISO/IEC DIS 23001-7, section 10.4.2 for +// more information. +struct CdmCencPatternEncryptionDescriptor { + size_t encrypt_blocks; // number of 16 byte blocks to decrypt + size_t skip_blocks; // number of 16 byte blocks to leave in clear + size_t offset_blocks; // offset into the pattern for this call, in blocks + CdmCencPatternEncryptionDescriptor() + : encrypt_blocks(0), + skip_blocks(0), + offset_blocks(0) {} +}; + struct CdmDecryptionParameters { bool is_encrypted; bool is_secure; + CdmCipherMode cipher_mode; const KeyId* key_id; const uint8_t* encrypt_buffer; size_t encrypt_length; @@ -271,9 +383,11 @@ struct CdmDecryptionParameters { size_t decrypt_buffer_offset; uint8_t subsample_flags; bool is_video; + CdmCencPatternEncryptionDescriptor pattern_descriptor; CdmDecryptionParameters() : is_encrypted(true), is_secure(true), + cipher_mode(kCipherModeCtr), key_id(NULL), encrypt_buffer(NULL), encrypt_length(0), @@ -290,6 +404,7 @@ struct CdmDecryptionParameters { size_t offset, void* decrypted_buffer) : is_encrypted(true), is_secure(true), + cipher_mode(kCipherModeCtr), key_id(key), encrypt_buffer(encrypted_buffer), encrypt_length(encrypted_length), @@ -302,6 +417,12 @@ struct CdmDecryptionParameters { is_video(true) {} }; +struct CdmKeyRequest { + CdmKeyMessage message; + CdmKeyRequestType type; + std::string url; +}; + // forward class references class KeyMessage; class Request; diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index 18bcdeb4..ba346cbe 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -15,7 +15,6 @@ #include "license_protocol.pb.h" #include "log.h" #include "properties.h" -#include "scoped_ptr.h" #include "string_conversions.h" #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" @@ -55,11 +54,13 @@ class UsagePropertySet : public CdmClientPropertySet { bool CdmEngine::seeded_ = false; -CdmEngine::CdmEngine() +CdmEngine::CdmEngine(FileSystem* file_system) : cert_provisioning_(NULL), cert_provisioning_requested_security_level_(kLevelDefault), + file_system_(file_system), usage_session_(NULL), last_usage_information_update_time_(0) { + assert(file_system); Properties::Init(); if (!seeded_) { Clock clock; @@ -79,7 +80,22 @@ CdmEngine::~CdmEngine() { CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, CdmClientPropertySet* property_set, - const std::string& origin, + const CdmSessionId& forced_session_id, + WvCdmEventListener* event_listener) { + return OpenSession(key_system, property_set, event_listener, + &forced_session_id, NULL); +} + +CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, + CdmClientPropertySet* property_set, + WvCdmEventListener* event_listener, + CdmSessionId* session_id) { + return OpenSession(key_system, property_set, event_listener, NULL, + session_id); +} + +CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, + CdmClientPropertySet* property_set, WvCdmEventListener* event_listener, const CdmSessionId* forced_session_id, CdmSessionId* session_id) { @@ -90,8 +106,8 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, return INVALID_KEY_SYSTEM; } - if (!session_id) { - LOGE("CdmEngine::OpenSession: no session ID destination provided"); + if (!session_id && !forced_session_id) { + LOGE("CdmEngine::OpenSession: no (forced/)session ID destination provided"); return INVALID_PARAMETERS_ENG_1; } @@ -101,32 +117,33 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, } } - scoped_ptr new_session( - new CdmSession(property_set, origin, event_listener, forced_session_id)); - if (new_session->session_id().empty()) { - LOGE("CdmEngine::OpenSession: failure to generate session ID"); - return EMPTY_SESSION_ID; - } + scoped_ptr new_session(new CdmSession(file_system_)); - CdmResponseType sts = new_session->Init(); + CdmResponseType sts = new_session->Init(property_set, forced_session_id, + event_listener); if (sts != NO_ERROR) { if (sts == NEED_PROVISIONING) { cert_provisioning_requested_security_level_ = new_session->GetRequestedSecurityLevel(); + // Reserve a session ID so the CDM can return success. + if (session_id) + *session_id = new_session->GenerateSessionId(); } else { LOGE("CdmEngine::OpenSession: bad session init: %d", sts); } return sts; } - *session_id = new_session->session_id(); + CdmSessionId id = new_session->session_id(); + AutoLock lock(session_list_lock_); - sessions_[*session_id] = new_session.release(); + sessions_[id] = new_session.release(); + if (session_id) *session_id = id; return NO_ERROR; } CdmResponseType CdmEngine::OpenKeySetSession( const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set, - const std::string& origin, WvCdmEventListener* event_listener) { + WvCdmEventListener* event_listener) { LOGI("CdmEngine::OpenKeySetSession"); if (key_set_id.empty()) { @@ -135,9 +152,8 @@ CdmResponseType CdmEngine::OpenKeySetSession( } CdmSessionId session_id; - CdmResponseType sts = - OpenSession(KEY_SYSTEM, property_set, origin, event_listener, - NULL /* forced_session_id */, &session_id); + CdmResponseType sts = OpenSession(KEY_SYSTEM, property_set, event_listener, + NULL /* forced_session_id */, &session_id); if (sts != NO_ERROR) return sts; @@ -182,9 +198,7 @@ bool CdmEngine::IsOpenSession(const CdmSessionId& session_id) { CdmResponseType CdmEngine::GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, const InitializationData& init_data, const CdmLicenseType license_type, - CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - CdmKeyRequestType* key_request_type, std::string* server_url, - CdmKeySetId* key_set_id_out) { + CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) { LOGI("CdmEngine::GenerateKeyRequest"); CdmSessionId id = session_id; @@ -223,11 +237,11 @@ CdmResponseType CdmEngine::GenerateKeyRequest( } if (!key_request) { - LOGE("CdmEngine::GenerateKeyRequest: no key request destination provided"); + LOGE("CdmEngine::GenerateKeyRequest: output destination provided"); return INVALID_PARAMETERS_ENG_2; } - key_request->clear(); + key_request->message.clear(); if (license_type == kLicenseTypeRelease && !iter->second->license_received()) { @@ -240,8 +254,7 @@ CdmResponseType CdmEngine::GenerateKeyRequest( } sts = iter->second->GenerateKeyRequest( - init_data, license_type, app_parameters, key_request, key_request_type, - server_url, key_set_id_out); + init_data, license_type, app_parameters, key_request); if (KEY_MESSAGE != sts) { if (sts == NEED_PROVISIONING) { @@ -300,7 +313,10 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, return EMPTY_KEY_DATA_1; } - CdmResponseType sts = iter->second->AddKey(key_data, key_set_id); + CdmResponseType sts = iter->second->AddKey(key_data); + if (key_set_id) { + *key_set_id = iter->second->key_set_id(); + } switch (sts) { case KEY_ADDED: @@ -359,8 +375,7 @@ CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) { } CdmResponseType CdmEngine::GenerateRenewalRequest( - const CdmSessionId& session_id, CdmKeyMessage* key_request, - std::string* server_url) { + const CdmSessionId& session_id, CdmKeyRequest* key_request) { LOGI("CdmEngine::GenerateRenewalRequest"); CdmSessionMap::iterator iter = sessions_.find(session_id); @@ -371,14 +386,13 @@ CdmResponseType CdmEngine::GenerateRenewalRequest( } if (!key_request) { - LOGE("CdmEngine::GenerateRenewalRequest: no key request destination"); + LOGE("CdmEngine::GenerateRenewalRequest: no request destination"); return INVALID_PARAMETERS_ENG_4; } - key_request->clear(); + key_request->message.clear(); - CdmResponseType sts = - iter->second->GenerateRenewalRequest(key_request, server_url); + CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request); if (KEY_MESSAGE != sts) { LOGE("CdmEngine::GenerateRenewalRequest: key request gen. failed, sts=%d", @@ -414,8 +428,8 @@ CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id, } CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, - const std::string& key, - std::string* value) { + const std::string& query_token, + std::string* query_response) { LOGI("CdmEngine::QueryStatus"); CryptoSession crypto_session; if (security_level == kLevel3) { @@ -423,36 +437,41 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, if (NO_ERROR != status) return INVALID_QUERY_STATUS; } - if (key == QUERY_KEY_SECURITY_LEVEL) { + if (!query_response) { + LOGE("CdmEngine::QueryStatus: no query response destination"); + return INVALID_PARAMETERS_ENG_6; + } + + if (query_token == QUERY_KEY_SECURITY_LEVEL) { CdmSecurityLevel security_level = crypto_session.GetSecurityLevel(); switch (security_level) { case kSecurityLevelL1: - *value = QUERY_VALUE_SECURITY_LEVEL_L1; + *query_response = QUERY_VALUE_SECURITY_LEVEL_L1; break; case kSecurityLevelL2: - *value = QUERY_VALUE_SECURITY_LEVEL_L2; + *query_response = QUERY_VALUE_SECURITY_LEVEL_L2; break; case kSecurityLevelL3: - *value = QUERY_VALUE_SECURITY_LEVEL_L3; + *query_response = QUERY_VALUE_SECURITY_LEVEL_L3; break; case kSecurityLevelUninitialized: case kSecurityLevelUnknown: - *value = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; + *query_response = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; break; default: LOGW("CdmEngine::QueryStatus: Unknown security level: %d", security_level); return UNKNOWN_ERROR; } - } else if (key == QUERY_KEY_DEVICE_ID) { + } else if (query_token == QUERY_KEY_DEVICE_ID) { std::string deviceId; if (!crypto_session.GetDeviceUniqueId(&deviceId)) { LOGW("CdmEngine::QueryStatus: GetDeviceUniqueId failed"); return UNKNOWN_ERROR; } - *value = deviceId; - } else if (key == QUERY_KEY_SYSTEM_ID) { + *query_response = deviceId; + } else if (query_token == QUERY_KEY_SYSTEM_ID) { uint32_t system_id; if (!crypto_session.GetSystemId(&system_id)) { LOGW("CdmEngine::QueryStatus: GetSystemId failed"); @@ -461,35 +480,36 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::ostringstream system_id_stream; system_id_stream << system_id; - *value = system_id_stream.str(); - } else if (key == QUERY_KEY_PROVISIONING_ID) { + *query_response = system_id_stream.str(); + } else if (query_token == QUERY_KEY_PROVISIONING_ID) { std::string provisioning_id; if (!crypto_session.GetProvisioningId(&provisioning_id)) { LOGW("CdmEngine::QueryStatus: GetProvisioningId failed"); return UNKNOWN_ERROR; } - *value = provisioning_id; - } else if (key == QUERY_KEY_CURRENT_HDCP_LEVEL || - key == QUERY_KEY_MAX_HDCP_LEVEL) { + *query_response = provisioning_id; + } else if (query_token == QUERY_KEY_CURRENT_HDCP_LEVEL || + query_token == QUERY_KEY_MAX_HDCP_LEVEL) { CryptoSession::HdcpCapability current_hdcp; CryptoSession::HdcpCapability max_hdcp; if (!crypto_session.GetHdcpCapabilities(¤t_hdcp, &max_hdcp)) { LOGW("CdmEngine::QueryStatus: GetHdcpCapabilities failed"); return UNKNOWN_ERROR; } - - *value = MapHdcpVersion(key == QUERY_KEY_CURRENT_HDCP_LEVEL ? current_hdcp - : max_hdcp); - } else if (key == QUERY_KEY_USAGE_SUPPORT) { + *query_response = + MapHdcpVersion(query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ? + current_hdcp : max_hdcp); + } else if (query_token == QUERY_KEY_USAGE_SUPPORT) { bool supports_usage_reporting; if (!crypto_session.UsageInformationSupport(&supports_usage_reporting)) { LOGW("CdmEngine::QueryStatus: UsageInformationSupport failed"); return UNKNOWN_ERROR; } - *value = supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; - } else if (key == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) { + *query_response = + supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + } else if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) { size_t number_of_open_sessions; if (!crypto_session.GetNumberOfOpenSessions(&number_of_open_sessions)) { LOGW("CdmEngine::QueryStatus: GetNumberOfOpenSessions failed"); @@ -498,8 +518,8 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::ostringstream open_sessions_stream; open_sessions_stream << number_of_open_sessions; - *value = open_sessions_stream.str(); - } else if (key == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) { + *query_response = open_sessions_stream.str(); + } else if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) { size_t maximum_number_of_sessions; if (!crypto_session.GetMaxNumberOfSessions(&maximum_number_of_sessions)) { LOGW("CdmEngine::QueryStatus: GetMaxNumberOfOpenSessions failed"); @@ -508,8 +528,8 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::ostringstream max_sessions_stream; max_sessions_stream << maximum_number_of_sessions; - *value = max_sessions_stream.str(); - } else if (key == QUERY_KEY_OEMCRYPTO_API_VERSION) { + *query_response = max_sessions_stream.str(); + } else if (query_token == QUERY_KEY_OEMCRYPTO_API_VERSION) { uint32_t api_version; if (!crypto_session.GetApiVersion(&api_version)) { LOGW("CdmEngine::QueryStatus: GetApiVersion failed"); @@ -518,10 +538,10 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::ostringstream api_version_stream; api_version_stream << api_version; - *value = api_version_stream.str(); + *query_response = api_version_stream.str(); } else { - LOGW("CdmEngine::QueryStatus: Unknown status requested, key = %s", - key.c_str()); + LOGW("CdmEngine::QueryStatus: Unknown status requested, token = %s", + query_token.c_str()); return INVALID_QUERY_KEY; } @@ -529,7 +549,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, } CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id, - CdmQueryMap* key_info) { + CdmQueryMap* query_response) { LOGI("CdmEngine::QuerySessionStatus"); CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { @@ -537,7 +557,7 @@ CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id, session_id.c_str()); return SESSION_NOT_FOUND_8; } - return iter->second->QueryStatus(key_info); + return iter->second->QueryStatus(query_response); } bool CdmEngine::IsReleaseSession(const CdmSessionId& session_id) { @@ -563,7 +583,7 @@ bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) { } CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id, - CdmQueryMap* key_info) { + CdmQueryMap* query_response) { LOGI("CdmEngine::QueryKeyStatus"); CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { @@ -571,19 +591,72 @@ CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id, session_id.c_str()); return SESSION_NOT_FOUND_9; } - return iter->second->QueryKeyStatus(key_info); + return iter->second->QueryKeyStatus(query_response); } -CdmResponseType CdmEngine::QueryKeyControlInfo(const CdmSessionId& session_id, - CdmQueryMap* key_info) { - LOGI("CdmEngine::QueryKeyControlInfo"); +CdmResponseType CdmEngine::QueryKeyAllowedUsage(const CdmSessionId& session_id, + const std::string& key_id, + CdmKeyAllowedUsage* key_usage) { + LOGI("CdmEngine::QueryKeyAllowedUsage"); + if (!key_usage) { + LOGE("CdmEngine::QueryKeyAllowedUsage: no response destination"); + return INVALID_PARAMETERS_ENG_12; + } CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { - LOGE("CdmEngine::QueryKeyControlInfo: session_id not found = %s", + LOGE("CdmEngine::QueryKeyAllowedUsage: session_id not found = %s", + session_id.c_str()); + return SESSION_NOT_FOUND_12; + } + return iter->second->QueryKeyAllowedUsage(key_id, key_usage); +} + +CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id, + CdmKeyAllowedUsage* key_usage) { + LOGI("CdmEngine::QueryKeyAllowedUsage (all sessions)"); + CdmResponseType session_sts; + CdmKeyAllowedUsage found_in_this_session; + bool found = false; + if (!key_usage) { + LOGE("CdmEngine::QueryKeyAllowedUsage: no response destination"); + return INVALID_PARAMETERS_ENG_7; + } + key_usage->Clear(); + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { + session_sts = iter->second->QueryKeyAllowedUsage(key_id, + &found_in_this_session); + if (session_sts == NO_ERROR) { + if (found) { + // Found another key. If usage settings do not match, fail. + if (!key_usage->Equals(found_in_this_session)) { + key_usage->Clear(); + return KEY_CONFLICT_1; + } + } else { + *key_usage = found_in_this_session; + found = true; + } + } else if (session_sts != KEY_NOT_FOUND_1) { + LOGE("CdmEngine::QueryKeyAllowedUsage (all sessions) FAILED = %d", + session_sts); + key_usage->Clear(); + return session_sts; + } + } + return (found) ? NO_ERROR : KEY_NOT_FOUND_2; +} + +CdmResponseType CdmEngine::QueryOemCryptoSessionId( + const CdmSessionId& session_id, CdmQueryMap* query_response) { + LOGI("CdmEngine::QueryOemCryptoSessionId"); + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::QueryOemCryptoSessionId: session_id not found = %s", session_id.c_str()); return SESSION_NOT_FOUND_10; } - return iter->second->QueryKeyControlInfo(key_info); + return iter->second->QueryOemCryptoSessionId(query_response); } /* @@ -595,8 +668,7 @@ CdmResponseType CdmEngine::QueryKeyControlInfo(const CdmSessionId& session_id, */ CdmResponseType CdmEngine::GetProvisioningRequest( CdmCertificateType cert_type, const std::string& cert_authority, - const std::string& origin, CdmProvisioningRequest* request, - std::string* default_url) { + CdmProvisioningRequest* request, std::string* default_url) { if (!request) { LOGE("CdmEngine::GetProvisioningRequest: invalid output parameters"); return INVALID_PROVISIONING_REQUEST_PARAM_1; @@ -613,7 +685,7 @@ CdmResponseType CdmEngine::GetProvisioningRequest( } CdmResponseType ret = cert_provisioning_->GetProvisioningRequest( cert_provisioning_requested_security_level_, cert_type, cert_authority, - origin, request, default_url); + file_system_->origin(), request, default_url); if (ret != NO_ERROR) { cert_provisioning_.reset(NULL); // Release resources. } @@ -628,8 +700,8 @@ CdmResponseType CdmEngine::GetProvisioningRequest( * Returns NO_ERROR for success and CdmResponseType error code if fails. */ CdmResponseType CdmEngine::HandleProvisioningResponse( - const std::string& origin, const CdmProvisioningResponse& response, - std::string* cert, std::string* wrapped_key) { + const CdmProvisioningResponse& response, std::string* cert, + std::string* wrapped_key) { if (response.empty()) { LOGE("CdmEngine::HandleProvisioningResponse: Empty provisioning response."); cert_provisioning_.reset(NULL); @@ -661,7 +733,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( return EMPTY_PROVISIONING_CERTIFICATE_2; } CdmSecurityLevel security_level = crypto_session.GetSecurityLevel(); - if (!IsProvisioned(security_level, origin)) { + if (!IsProvisioned(security_level)) { LOGE( "CdmEngine::HandleProvisioningResponse: provisioning object " "missing."); @@ -671,7 +743,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( } CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse( - origin, response, cert, wrapped_key); + file_system_, response, cert, wrapped_key); // Release resources only on success. It is possible that a provisioning // attempt was made after this one was requested but before the response was // received, which will cause this attempt to fail. Not releasing will @@ -680,28 +752,25 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( return ret; } -bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level, - const std::string& origin) { - DeviceFiles handle; +bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) { + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("CdmEngine::IsProvisioned: unable to initialize device files"); return false; } - return handle.HasCertificate(origin); + return handle.HasCertificate(); } -CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level, - const std::string& origin) { - DeviceFiles handle; +CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("CdmEngine::Unprovision: unable to initialize device files"); return UNPROVISION_ERROR_1; } - if (origin != EMPTY_ORIGIN) { - if (!handle.RemoveCertificate(origin)) { - LOGE("CdmEngine::Unprovision: unable to delete certificate for origin %s", - origin.c_str()); + if (!file_system_->origin().empty()) { + if (!handle.RemoveCertificate()) { + LOGE("CdmEngine::Unprovision: unable to delete certificate"); return UNPROVISION_ERROR_2; } return NO_ERROR; @@ -732,16 +801,19 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, if (NULL == usage_property_set_.get()) { usage_property_set_.reset(new UsagePropertySet()); } + if (!usage_info) { + LOGE("CdmEngine::GetUsageInfo: no usage info destination"); + return INVALID_PARAMETERS_ENG_8; + } usage_property_set_->set_security_level(kLevelDefault); usage_property_set_->set_app_id(app_id); - usage_session_.reset( - new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL)); - CdmResponseType status = usage_session_->Init(); + usage_session_.reset(new CdmSession(file_system_)); + CdmResponseType status = usage_session_->Init(usage_property_set_.get()); if (NO_ERROR != status) { LOGE("CdmEngine::GetUsageInfo: session init error"); return status; } - DeviceFiles handle; + DeviceFiles handle(file_system_); if (!handle.Init(usage_session_->GetSecurityLevel())) { LOGE("CdmEngine::GetUsageInfo: device file init error"); return GET_USAGE_INFO_ERROR_1; @@ -753,9 +825,8 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, &license_response)) { usage_property_set_->set_security_level(kLevel3); usage_property_set_->set_app_id(app_id); - usage_session_.reset( - new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL)); - status = usage_session_->Init(); + usage_session_.reset(new CdmSession(file_system_)); + status = usage_session_->Init(usage_property_set_.get()); if (NO_ERROR != status) { LOGE("CdmEngine::GetUsageInfo: session init error"); return status; @@ -771,18 +842,20 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, } } - std::string server_url; - usage_info->resize(1); status = - usage_session_->RestoreUsageSession(license_request, license_response); + usage_session_->RestoreUsageSession(license_request,license_response); + if (KEY_ADDED != status) { LOGE("CdmEngine::GetUsageInfo: restore usage session error %d", status); usage_info->clear(); return status; } - status = - usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url); + CdmKeyRequest request; + status = usage_session_->GenerateReleaseRequest(&request); + + usage_info->clear(); + usage_info->push_back(request.message); if (KEY_MESSAGE != status) { LOGE("CdmEngine::GetUsageInfo: generate release request error: %d", status); @@ -797,6 +870,10 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, // Return a random usage report from a random security level SecurityLevel security_level = ((rand() % 2) == 0) ? kLevelDefault : kLevel3; CdmResponseType status = UNKNOWN_ERROR; + if (!usage_info) { + LOGE("CdmEngine::GetUsageInfo: no usage info destination"); + return INVALID_PARAMETERS_ENG_9; + } do { status = GetUsageInfo(app_id, security_level, usage_info); @@ -822,16 +899,15 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, usage_property_set_->set_security_level(requested_security_level); usage_property_set_->set_app_id(app_id); - usage_session_.reset( - new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL)); + usage_session_.reset(new CdmSession(file_system_)); - CdmResponseType status = usage_session_->Init(); + CdmResponseType status = usage_session_->Init(usage_property_set_.get()); if (NO_ERROR != status) { LOGE("CdmEngine::GetUsageInfo: session init error"); return status; } - DeviceFiles handle; + DeviceFiles handle(file_system_); if (!handle.Init(usage_session_->GetSecurityLevel())) { LOGE("CdmEngine::GetUsageInfo: unable to initialize device files"); return GET_USAGE_INFO_ERROR_3; @@ -843,12 +919,15 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, return GET_USAGE_INFO_ERROR_4; } + if (!usage_info) { + LOGE("CdmEngine::GetUsageInfo: no usage info destination"); + return INVALID_PARAMETERS_ENG_10; + } if (0 == license_info.size()) { usage_info->resize(0); return NO_ERROR; } - std::string server_url; usage_info->resize(kUsageReportsPerRequest); uint32_t index = rand() % license_info.size(); @@ -861,8 +940,11 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, return status; } - status = - usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url); + CdmKeyRequest request; + status = usage_session_->GenerateReleaseRequest(&request); + + usage_info->clear(); + usage_info->push_back(request.message); switch (status) { case KEY_MESSAGE: @@ -888,7 +970,7 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) { CdmResponseType status = NO_ERROR; for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) { - DeviceFiles handle; + DeviceFiles handle(file_system_); if (handle.Init(static_cast(j))) { std::vector provider_session_tokens; if (!handle.DeleteAllUsageInfoForApp(app_id, &provider_session_tokens)) { @@ -901,9 +983,8 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) { ? kLevel3 : kLevelDefault; usage_property_set_->set_security_level(security_level); - usage_session_.reset( - new CdmSession(usage_property_set_.get(), - EMPTY_ORIGIN, NULL, NULL)); + usage_session_.reset(new CdmSession(file_system_)); + usage_session_->Init(usage_property_set_.get()); CdmResponseType status2 = usage_session_-> DeleteMultipleUsageInformation(provider_session_tokens); if (status2 != NO_ERROR) { @@ -954,7 +1035,12 @@ CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, return SESSION_NOT_FOUND_11; } - DeviceFiles handle; + if (!release_message) { + LOGE("CdmEngine::LoadUsageSession: no release message destination"); + return INVALID_PARAMETERS_ENG_11; + } + + DeviceFiles handle(file_system_); if (!handle.Init(iter->second->GetSecurityLevel())) { LOGE("CdmEngine::LoadUsageSession: unable to initialize device files"); return LOAD_USAGE_INFO_FILE_ERROR; @@ -978,8 +1064,9 @@ CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, return status; } - std::string server_url; - status = iter->second->GenerateReleaseRequest(release_message, &server_url); + CdmKeyRequest request; + status = iter->second->GenerateReleaseRequest(&request); + *release_message = request.message; switch (status) { case KEY_MESSAGE: @@ -1021,24 +1108,86 @@ CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id, // else we must be level 1 direct and we don't need to return a buffer. } - CdmSessionMap::iterator iter; + CdmSessionMap::iterator session_iter = sessions_.end(); if (session_id.empty()) { - // Loop through the sessions to find the session containing the key_id. - for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) { + // Loop through the sessions to find the session containing the key_id + // with the longest remaining license validity. + int64_t seconds_remaining = 0; + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { if (iter->second->IsKeyLoaded(*parameters.key_id)) { - break; + int64_t duration = iter->second->GetDurationRemaining(); + if (duration > seconds_remaining) { + session_iter = iter; + seconds_remaining = duration; + } } } } else { - iter = sessions_.find(session_id); + session_iter = sessions_.find(session_id); } - if (iter == sessions_.end()) { + if (session_iter == sessions_.end()) { LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d", session_id.c_str(), session_id.size()); return SESSION_NOT_FOUND_FOR_DECRYPT; } - return iter->second->Decrypt(parameters); + return session_iter->second->Decrypt(parameters); +} + +CdmResponseType CdmEngine::GenericEncrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenericEncrypt: session_id not found = %s ", + session_id.c_str()); + return SESSION_NOT_FOUND_13; + } + return iter->second->GenericEncrypt(in_buffer, key_id, iv, algorithm, + out_buffer); +} + +CdmResponseType CdmEngine::GenericDecrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenericDecrypt: session_id not found = %s ", + session_id.c_str()); + return SESSION_NOT_FOUND_14; + } + return iter->second->GenericDecrypt(in_buffer, key_id, iv, algorithm, + out_buffer); +} + +CdmResponseType CdmEngine::GenericSign( + const std::string& session_id, const std::string& message, + const std::string& key_id, CdmSigningAlgorithm algorithm, + std::string* signature) { + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenericSign: session_id not found = %s ", + session_id.c_str()); + return SESSION_NOT_FOUND_15; + } + return iter->second->GenericSign(message, key_id, algorithm, signature); +} + +CdmResponseType CdmEngine::GenericVerify( + const std::string& session_id, const std::string& message, + const std::string& key_id, CdmSigningAlgorithm algorithm, + const std::string& signature) { + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenericVerify: session_id not found = %s ", + session_id.c_str()); + return SESSION_NOT_FOUND_16; + } + return iter->second->GenericVerify(message, key_id, algorithm, signature); } bool CdmEngine::IsKeyLoaded(const KeyId& key_id) { @@ -1058,25 +1207,30 @@ bool CdmEngine::FindSessionForKey(const KeyId& key_id, return false; } - CdmSessionMap::iterator iter = sessions_.find(*session_id); - if (iter != sessions_.end()) { - if (iter->second->IsKeyLoaded(key_id)) { - return true; - } - } uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id); - for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) { + CdmSessionMap::iterator session_iter = sessions_.end(); + int64_t seconds_remaining = 0; + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { CdmSessionId local_session_id = iter->second->session_id(); if (Properties::GetSessionSharingId(local_session_id) == session_sharing_id) { if (iter->second->IsKeyLoaded(key_id)) { - *session_id = local_session_id; - return true; + int64_t duration = iter->second->GetDurationRemaining(); + if (duration > seconds_remaining) { + session_iter = iter; + seconds_remaining = duration; + } } } } + + if (session_iter != sessions_.end()) { + *session_id = session_iter->second->session_id(); + return true; + } return false; } @@ -1172,9 +1326,8 @@ void CdmEngine::DeleteAllUsageReportsUponFactoryReset() { Properties::GetDeviceFilesBasePath(kSecurityLevelL3, &device_base_path_level3); - File file; - if (!file.Exists(device_base_path_level1) && - !file.Exists(device_base_path_level3)) { + if (!file_system_->Exists(device_base_path_level1) && + !file_system_->Exists(device_base_path_level3)) { scoped_ptr crypto_session(new CryptoSession()); CdmResponseType status = crypto_session->Open( cert_provisioning_requested_security_level_); diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index 46c0767b..6d3b8b80 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -10,8 +10,6 @@ #include "cdm_engine.h" #include "clock.h" -#include "crypto_session.h" -#include "device_files.h" #include "file_store.h" #include "log.h" #include "properties.h" @@ -25,47 +23,22 @@ const size_t kKeySetIdLength = 14; namespace wvcdm { -CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set, - const std::string& origin, - WvCdmEventListener* event_listener, - const CdmSessionId* forced_session_id) - : initialized_(false), - session_id_(GenerateSessionId()), - origin_(origin), - crypto_session_(new CryptoSession), - file_handle_(new DeviceFiles), - license_received_(false), - is_offline_(false), - is_release_(false), - is_temporary_(false), - security_level_(kSecurityLevelUninitialized), - requested_security_level_(kLevelDefault), - is_initial_decryption_(true), - has_decrypted_since_last_report_(false), - is_initial_usage_update_(true), - is_usage_update_needed_(false) { - if (Properties::AlwaysUseKeySetIds()) { - if (forced_session_id) { - key_set_id_ = *forced_session_id; - } else { - bool ok = GenerateKeySetId(&key_set_id_); - (void)ok; // ok is now used when assertions are turned off. - assert(ok); - } - session_id_ = key_set_id_; - } - license_parser_.reset(new CdmLicense(session_id_)); - policy_engine_.reset(new PolicyEngine( - session_id_, event_listener, crypto_session_.get())); - if (cdm_client_property_set) { - if (cdm_client_property_set->security_level() == - QUERY_VALUE_SECURITY_LEVEL_L3) { - requested_security_level_ = kLevel3; - security_level_ = kSecurityLevelL3; - } - Properties::AddSessionPropertySet(session_id_, cdm_client_property_set); - } -} +CdmSession::CdmSession(FileSystem* file_system) : + initialized_(false), + crypto_session_(new CryptoSession), + file_handle_(new DeviceFiles(file_system)), + license_received_(false), + is_offline_(false), + is_release_(false), + is_temporary_(false), + security_level_(kSecurityLevelUninitialized), + requested_security_level_(kLevelDefault), + is_initial_decryption_(true), + has_decrypted_since_last_report_(false), + is_initial_usage_update_(true), + is_usage_update_needed_(false), + mock_license_parser_in_use_(false), + mock_policy_engine_in_use_(false) {} CdmSession::~CdmSession() { if (!key_set_id_.empty()) { @@ -75,24 +48,38 @@ CdmSession::~CdmSession() { Properties::RemoveSessionPropertySet(session_id_); } -CdmResponseType CdmSession::Init() { - if (session_id_.empty()) { - LOGE("CdmSession::Init: Failed, session not properly constructed"); - return SESSION_INIT_ERROR_1; - } +CdmResponseType CdmSession::Init( + CdmClientPropertySet* cdm_client_property_set) { + return Init(cdm_client_property_set, NULL, NULL); +} + +CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, + const CdmSessionId* forced_session_id, + WvCdmEventListener* event_listener) { if (initialized_) { LOGE("CdmSession::Init: Failed due to previous initialization"); return SESSION_INIT_ERROR_2; } + + if (cdm_client_property_set && + cdm_client_property_set->security_level() == + QUERY_VALUE_SECURITY_LEVEL_L3) { + requested_security_level_ = kLevel3; + security_level_ = kSecurityLevelL3; + } CdmResponseType sts = crypto_session_->Open(requested_security_level_); if (NO_ERROR != sts) return sts; security_level_ = crypto_session_->GetSecurityLevel(); + if (!file_handle_->Init(security_level_)) { + LOGE("CdmSession::Init: Unable to initialize file handle"); + return SESSION_FILE_HANDLE_INIT_ERROR; + } + std::string token; if (Properties::use_certificates_as_identification()) { std::string wrapped_key; - if (!file_handle_->Init(security_level_) || - !file_handle_->RetrieveCertificate(origin_, &token, &wrapped_key) || + if (!file_handle_->RetrieveCertificate(&token, &wrapped_key) || !crypto_session_->LoadCertificatePrivateKey(wrapped_key)) { return NEED_PROVISIONING; } @@ -101,6 +88,30 @@ CdmResponseType CdmSession::Init() { return SESSION_INIT_GET_KEYBOX_ERROR; } + if (forced_session_id) { + key_set_id_ = *forced_session_id; + } else { + bool ok = GenerateKeySetId(&key_set_id_); + (void)ok; // ok is now used when assertions are turned off. + assert(ok); + } + + session_id_ = + Properties::AlwaysUseKeySetIds() ? key_set_id_ : GenerateSessionId(); + + if (session_id_.empty()) { + LOGE("CdmSession::Init: empty session ID"); + return EMPTY_SESSION_ID; + } + if (cdm_client_property_set) + Properties::AddSessionPropertySet(session_id_, cdm_client_property_set); + + if (!mock_license_parser_in_use_) + license_parser_.reset(new CdmLicense(session_id_)); + if (!mock_policy_engine_in_use_) + policy_engine_.reset(new PolicyEngine( + session_id_, event_listener, crypto_session_.get())); + if (!license_parser_->Init(token, crypto_session_.get(), policy_engine_.get())) return LICENSE_PARSER_INIT_ERROR; @@ -115,10 +126,6 @@ CdmResponseType CdmSession::RestoreOfflineSession( const CdmKeySetId& key_set_id, const CdmLicenseType license_type) { key_set_id_ = key_set_id; - // Retrieve license information from persistent store - if (!file_handle_->Reset(security_level_)) - return RESTORE_OFFLINE_LICENSE_ERROR_1; - DeviceFiles::LicenseState license_state; int64_t playback_start_time; int64_t last_playback_time; @@ -177,9 +184,9 @@ CdmResponseType CdmSession::RestoreUsageSession( CdmResponseType CdmSession::GenerateKeyRequest( const InitializationData& init_data, CdmLicenseType license_type, - const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - CdmKeyRequestType* key_request_type, std::string* server_url, - CdmKeySetId* key_set_id) { + const CdmAppParameterMap& app_parameters, + CdmKeyRequest* key_request) { + if (crypto_session_.get() == NULL) { LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session"); return INVALID_CRYPTO_SESSION_1; @@ -190,6 +197,11 @@ CdmResponseType CdmSession::GenerateKeyRequest( return CRYPTO_SESSION_OPEN_ERROR_1; } + if (!key_request) { + LOGE("CdmSession::GenerateKeyRequest: No output destination provided"); + return INVALID_PARAMETERS_ENG_5; + } + switch (license_type) { case kLicenseTypeTemporary: is_temporary_ = true; @@ -230,13 +242,12 @@ CdmResponseType CdmSession::GenerateKeyRequest( } if (is_release_) { - if (key_request_type) *key_request_type = kKeyRequestTypeRelease; - return GenerateReleaseRequest(key_request, server_url); + return GenerateReleaseRequest(key_request); } else if (license_received_) { // renewal - if (key_request_type) *key_request_type = kKeyRequestTypeRenewal; - return GenerateRenewalRequest(key_request, server_url); + return GenerateRenewalRequest(key_request); } else { - if (key_request_type) *key_request_type = kKeyRequestTypeInitial; + key_request->type = kKeyRequestTypeInitial; + if (!license_parser_->HasInitData()) { if (!init_data.is_supported()) { LOGW("CdmSession::GenerateKeyRequest: unsupported init data type (%s)", @@ -248,8 +259,7 @@ CdmResponseType CdmSession::GenerateKeyRequest( return INIT_DATA_NOT_FOUND; } } - if (is_offline_ && key_set_id_.empty() && - !GenerateKeySetId(&key_set_id_)) { + if (is_offline_ && key_set_id_.empty()) { LOGE("CdmSession::GenerateKeyRequest: Unable to generate key set ID"); return KEY_REQUEST_ERROR_1; } @@ -257,24 +267,24 @@ CdmResponseType CdmSession::GenerateKeyRequest( app_parameters_ = app_parameters; CdmResponseType status = license_parser_->PrepareKeyRequest( init_data, license_type, - app_parameters, key_request, server_url); + app_parameters, &key_request->message, + &key_request->url); if (KEY_MESSAGE != status) return status; - key_request_ = *key_request; + key_request_ = key_request->message; if (is_offline_) { offline_init_data_ = init_data.data(); - offline_release_server_url_ = *server_url; + offline_release_server_url_ = key_request->url; + } - if (key_set_id) *key_set_id = key_set_id_; return KEY_MESSAGE; } } // AddKey() - Accept license response and extract key info. -CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response, - CdmKeySetId* key_set_id) { +CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { if (crypto_session_.get() == NULL) { LOGW("CdmSession::AddKey: Invalid crypto session"); return INVALID_CRYPTO_SESSION_2; @@ -303,12 +313,11 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response, if (sts != NO_ERROR) return sts; } - if (key_set_id) *key_set_id = key_set_id_; return KEY_ADDED; } } -CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) { +CdmResponseType CdmSession::QueryStatus(CdmQueryMap* query_response) { if (crypto_session_.get() == NULL) { LOGE("CdmSession::QueryStatus: Invalid crypto session"); return INVALID_CRYPTO_SESSION_3; @@ -321,17 +330,20 @@ CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) { switch (security_level_) { case kSecurityLevelL1: - (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1; + (*query_response)[QUERY_KEY_SECURITY_LEVEL] = + QUERY_VALUE_SECURITY_LEVEL_L1; break; case kSecurityLevelL2: - (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2; + (*query_response)[QUERY_KEY_SECURITY_LEVEL] = + QUERY_VALUE_SECURITY_LEVEL_L2; break; case kSecurityLevelL3: - (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3; + (*query_response)[QUERY_KEY_SECURITY_LEVEL] = + QUERY_VALUE_SECURITY_LEVEL_L3; break; case kSecurityLevelUninitialized: case kSecurityLevelUnknown: - (*key_info)[QUERY_KEY_SECURITY_LEVEL] = + (*query_response)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; break; default: @@ -340,24 +352,30 @@ CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) { return NO_ERROR; } -CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* key_info) { - return policy_engine_->Query(key_info); +CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* query_response) { + return policy_engine_->Query(query_response); } -CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) { +CdmResponseType CdmSession::QueryKeyAllowedUsage( + const std::string& key_id, CdmKeyAllowedUsage* key_usage) { + return policy_engine_->QueryKeyAllowedUsage(key_id, key_usage); +} + +CdmResponseType CdmSession::QueryOemCryptoSessionId( + CdmQueryMap* query_response) { if (crypto_session_.get() == NULL) { - LOGW("CdmSession::QueryKeyControlInfo: Invalid crypto session"); + LOGW("CdmSession::QueryOemCryptoSessionId: Invalid crypto session"); return INVALID_CRYPTO_SESSION_4; } if (!crypto_session_->IsOpen()) { - LOGW("CdmSession::QueryKeyControlInfo: Crypto session not open"); + LOGW("CdmSession::QueryOemCryptoSessionId: Crypto session not open"); return CRYPTO_SESSION_OPEN_ERROR_4; } std::stringstream ss; ss << crypto_session_->oec_session_id(); - (*key_info)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str(); + (*query_response)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str(); return NO_ERROR; } @@ -405,15 +423,17 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { // License renewal // GenerateRenewalRequest() - Construct valid renewal request for the current // session keys. -CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request, - std::string* server_url) { +CdmResponseType CdmSession::GenerateRenewalRequest( + CdmKeyRequest* key_request) { CdmResponseType status = license_parser_->PrepareKeyUpdateRequest( - true, app_parameters_, key_request, server_url); + true, app_parameters_, &key_request->message, &key_request->url); + + key_request->type = kKeyRequestTypeRenewal; if (KEY_MESSAGE != status) return status; if (is_offline_) { - offline_key_renewal_request_ = *key_request; + offline_key_renewal_request_ = key_request->message; } return KEY_MESSAGE; } @@ -432,11 +452,14 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { return KEY_ADDED; } -CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request, - std::string* server_url) { +CdmResponseType CdmSession::GenerateReleaseRequest( + CdmKeyRequest* key_request) { is_release_ = true; CdmResponseType status = license_parser_->PrepareKeyUpdateRequest( - false, app_parameters_, key_request, server_url); + false, app_parameters_, &key_request->message, + &key_request->url); + + key_request->type = kKeyRequestTypeRelease; if (KEY_MESSAGE != status) return status; @@ -463,6 +486,11 @@ bool CdmSession::IsKeyLoaded(const KeyId& key_id) { return license_parser_->IsKeyLoaded(key_id); } +int64_t CdmSession::GetDurationRemaining() { + if (policy_engine_->IsLicenseForFuture()) return 0; + return policy_engine_->GetLicenseOrPlaybackDurationRemaining(); +} + CdmSessionId CdmSession::GenerateSessionId() { static int session_num = 1; return SESSION_ID_PREFIX + IntToString(++session_num); @@ -477,8 +505,6 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) { std::vector random_data( (kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0); - if (!file_handle_->Reset(security_level_)) return false; - while (key_set_id->empty()) { if (!crypto_session_->GetRandom(random_data.size(), &random_data[0])) return false; @@ -514,13 +540,6 @@ CdmResponseType CdmSession::StoreLicense() { if (!StoreLicense(DeviceFiles::kLicenseStateActive)) { LOGE("CdmSession::StoreLicense: Unable to store license"); - CdmResponseType sts = Init(); - if (sts != NO_ERROR) { - LOGW("CdmSession::StoreLicense: Reinitialization failed"); - return sts; - } - - key_set_id_.clear(); return STORE_LICENSE_ERROR_1; } return NO_ERROR; @@ -533,11 +552,6 @@ CdmResponseType CdmSession::StoreLicense() { return STORE_LICENSE_ERROR_2; } - if (!file_handle_->Reset(security_level_)) { - LOGE("CdmSession::StoreLicense: Unable to initialize device files"); - return STORE_LICENSE_ERROR_3; - } - std::string app_id; GetApplicationId(&app_id); if (!file_handle_->StoreUsageInfo(provider_session_token, key_request_, @@ -549,8 +563,6 @@ CdmResponseType CdmSession::StoreLicense() { } bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { - if (!file_handle_->Reset(security_level_)) return false; - return file_handle_->StoreLicense( key_set_id_, state, offline_init_data_, key_request_, key_response_, offline_key_renewal_request_, offline_key_renewal_response_, @@ -558,14 +570,15 @@ bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { policy_engine_->GetLastPlaybackTime(), app_parameters_); } +CdmResponseType CdmSession::ReleaseCrypto() { + crypto_session_->Close(); + return NO_ERROR; +} + bool CdmSession::DeleteLicense() { if (!is_offline_ && license_parser_->provider_session_token().empty()) return false; - if (!file_handle_->Reset(security_level_)) { - LOGE("CdmSession::DeleteLicense: Unable to initialize device files"); - return false; - } if (is_offline_) { return file_handle_->DeleteLicense(key_set_id_); } else { @@ -613,13 +626,55 @@ CdmResponseType CdmSession::UpdateUsageInformation() { return crypto_session_->UpdateUsageInformation(); } -CdmResponseType CdmSession::ReleaseCrypto() { - crypto_session_->Close(); - return NO_ERROR; +CdmResponseType CdmSession::GenericEncrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + if (!out_buffer) { + LOGE("CdmSession::GenericEncrypt: No output destination provided"); + return INVALID_PARAMETERS_ENG_6; + } + return crypto_session_->GenericEncrypt(in_buffer, key_id, iv, algorithm, + out_buffer); } +CdmResponseType CdmSession::GenericDecrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + if (!out_buffer) { + LOGE("CdmSession::GenericDecrypt: No output destination provided"); + return INVALID_PARAMETERS_ENG_7; + } + return crypto_session_->GenericDecrypt(in_buffer, key_id, iv, algorithm, + out_buffer); +} + +CdmResponseType CdmSession::GenericSign(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature) { + if (!signature) { + LOGE("CdmSession::GenericSign: No output destination provided"); + return INVALID_PARAMETERS_ENG_8; + } + return crypto_session_->GenericSign(message, key_id, algorithm, signature); +} + +CdmResponseType CdmSession::GenericVerify(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature) { + return crypto_session_->GenericVerify(message, key_id, algorithm, signature); +} + +// For testing only - takes ownership of pointers + void CdmSession::set_license_parser(CdmLicense* license_parser) { license_parser_.reset(license_parser); + mock_license_parser_in_use_ = true; } void CdmSession::set_crypto_session(CryptoSession* crypto_session) { @@ -628,6 +683,7 @@ void CdmSession::set_crypto_session(CryptoSession* crypto_session) { void CdmSession::set_policy_engine(PolicyEngine* policy_engine) { policy_engine_.reset(policy_engine); + mock_policy_engine_in_use_ = true; } void CdmSession::set_file_handle(DeviceFiles* file_handle) { diff --git a/core/src/certificate_provisioning.cpp b/core/src/certificate_provisioning.cpp index 61c27077..a6b2c10a 100644 --- a/core/src/certificate_provisioning.cpp +++ b/core/src/certificate_provisioning.cpp @@ -189,7 +189,7 @@ bool CertificateProvisioning::ParseJsonResponse( * Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails. */ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( - const std::string& origin, const CdmProvisioningResponse& response, + FileSystem* file_system, const CdmProvisioningResponse& response, std::string* cert, std::string* wrapped_key) { // Extracts signed response from JSON string, decodes base64 signed response const std::string kMessageStart = "\"signedResponse\": \""; @@ -259,12 +259,12 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( const std::string& device_certificate = provisioning_response.device_certificate(); - DeviceFiles handle; + DeviceFiles handle(file_system); if (!handle.Init(crypto_session_.GetSecurityLevel())) { LOGE("HandleProvisioningResponse: failed to init DeviceFiles"); return CERT_PROVISIONING_RESPONSE_ERROR_7; } - if (!handle.StoreCertificate(origin, device_certificate, wrapped_rsa_key)) { + if (!handle.StoreCertificate(device_certificate, wrapped_rsa_key)) { LOGE("HandleProvisioningResponse: failed to save provisioning certificate"); return CERT_PROVISIONING_RESPONSE_ERROR_8; } diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp index ec255d20..cf5e96c3 100644 --- a/core/src/crypto_session.cpp +++ b/core/src/crypto_session.cpp @@ -40,7 +40,8 @@ CryptoSession::CryptoSession() update_usage_table_after_close_session_(false), is_destination_buffer_type_valid_(false), requested_security_level_(kLevelDefault), - request_id_base_(0) { + request_id_base_(0), + cipher_mode_(kCipherModeCtr) { Init(); } @@ -55,17 +56,19 @@ void CryptoSession::Init() { LOGV("CryptoSession::Init"); AutoLock auto_lock(crypto_lock_); session_count_ += 1; - if (initialized_) return; - OEMCryptoResult sts = OEMCrypto_Initialize(); - if (OEMCrypto_SUCCESS != sts) { - LOGE("OEMCrypto_Initialize failed: %d", sts); - return; + if (!initialized_) { + OEMCryptoResult sts = OEMCrypto_Initialize(); + if (OEMCrypto_SUCCESS != sts) { + LOGE("OEMCrypto_Initialize failed: %d", sts); + return; + } + initialized_ = true; } - initialized_ = true; } void CryptoSession::Terminate() { - LOGV("CryptoSession::Terminate"); + LOGE("CryptoSession::Terminate: initialized_=%d, session_count_=%d", + initialized_, session_count_); AutoLock auto_lock(crypto_lock_); if (session_count_ > 0) { session_count_ -= 1; @@ -233,6 +236,10 @@ bool CryptoSession::GetProvisioningId(std::string* provisioning_id) { return true; } +uint8_t CryptoSession::GetSecurityPatchLevel() { + return OEMCrypto_Security_Patch_Level(requested_security_level_); +} + CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { LOGV("CryptoSession::Open: Lock"); AutoLock auto_lock(crypto_lock_); @@ -247,16 +254,15 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { LOGV("OpenSession: id= %ld", (uint32_t)oec_session_id_); open_ = true; } else if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) { - LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", sts, - session_count_, (int)initialized_); + LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", + sts, session_count_, (int)initialized_); return INSUFFICIENT_CRYPTO_RESOURCES; } if (!open_) { - LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", sts, - session_count_, (int)initialized_); + LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", + sts, session_count_, (int)initialized_); return UNKNOWN_ERROR; } - OEMCrypto_GetRandom(reinterpret_cast(&request_id_base_), sizeof(request_id_base_)); ++request_id_index_; @@ -412,6 +418,10 @@ CdmResponseType CryptoSession::LoadKeys( ko->key_control_iv = NULL; ko->key_control = NULL; } + ko->cipher_mode = ki->cipher_mode() == kCipherModeCbc + ? OEMCrypto_CipherMode_CBC + : OEMCrypto_CipherMode_CTR; + cipher_mode_ = ki->cipher_mode(); } uint8_t* pst = NULL; if (!provider_session_token.empty()) { @@ -498,12 +508,22 @@ bool CryptoSession::RefreshKeys(const std::string& message, } bool CryptoSession::SelectKey(const std::string& key_id) { + // Crypto session lock already locked. + if (!cached_key_id_.empty() && cached_key_id_ == key_id) { + // Already using the desired key. + return true; + } + + cached_key_id_ = key_id; + const uint8_t* key_id_string = - reinterpret_cast(key_id.data()); + reinterpret_cast(cached_key_id_.data()); OEMCryptoResult sts = - OEMCrypto_SelectKey(oec_session_id_, key_id_string, key_id.size()); + OEMCrypto_SelectKey(oec_session_id_, key_id_string, + cached_key_id_.size()); if (OEMCrypto_SUCCESS != sts) { + cached_key_id_.clear(); return false; } return true; @@ -558,78 +578,76 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message, bool CryptoSession::GenerateSignature(const std::string& message, std::string* signature) { LOGV("GenerateSignature: id=%ld", (uint32_t)oec_session_id_); - if (!signature) return false; + if (!signature) { + LOGE("GenerateSignature: null signature string"); + return false; + } + OEMCryptoResult sts; size_t length = signature->size(); - OEMCryptoResult sts = OEMCrypto_GenerateSignature( - oec_session_id_, reinterpret_cast(message.data()), - message.size(), - reinterpret_cast(const_cast(signature->data())), - &length); - if (OEMCrypto_SUCCESS != sts) { - if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { - LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); - return false; - } - - // Retry with proper-sized signature buffer - signature->resize(length); + // At most two attempts. + // The first attempt may fail due to buffer too short + for (int i = 0; i < 2; ++i) { sts = OEMCrypto_GenerateSignature( oec_session_id_, reinterpret_cast(message.data()), message.size(), reinterpret_cast(const_cast(signature->data())), &length); - if (OEMCrypto_SUCCESS != sts) { - LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); - return false; + if (OEMCrypto_SUCCESS == sts) { + // Trim signature buffer and done + signature->resize(length); + return true; } + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + break; + } + + // Retry with proper-sized signature buffer + signature->resize(length); } - // Trim signature buffer - signature->resize(length); - - return true; + LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); + return false; } bool CryptoSession::GenerateRsaSignature(const std::string& message, std::string* signature) { LOGV("GenerateRsaSignature: id=%ld", (uint32_t)oec_session_id_); - if (!signature) return false; + if (!signature) { + LOGE("GenerateRsaSignature: null signature string"); + return false; + } + OEMCryptoResult sts; signature->resize(kRsaSignatureLength); size_t length = signature->size(); - OEMCryptoResult sts = OEMCrypto_GenerateRSASignature( - oec_session_id_, reinterpret_cast(message.data()), - message.size(), - reinterpret_cast(const_cast(signature->data())), &length, - kSign_RSASSA_PSS); - if (OEMCrypto_SUCCESS != sts) { - if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { - LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); - return false; - } - - // Retry with proper-sized signature buffer - signature->resize(length); + // At most two attempts. + // The first attempt may fail due to buffer too short + for (int i = 0; i < 2; ++i) { sts = OEMCrypto_GenerateRSASignature( oec_session_id_, reinterpret_cast(message.data()), message.size(), reinterpret_cast(const_cast(signature->data())), &length, kSign_RSASSA_PSS); - if (OEMCrypto_SUCCESS != sts) { - LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); - return false; + if (OEMCrypto_SUCCESS == sts) { + // Trim signature buffer and done + signature->resize(length); + return true; } + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + break; + } + + // Retry with proper-sized signature buffer + signature->resize(length); } - // Trim signature buffer - signature->resize(length); - - return true; + LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); + return false; } CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { @@ -666,27 +684,32 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { } OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED; - if (!params.is_encrypted) { + if (!params.is_encrypted && + params.subsample_flags == + (OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)) { sts = OEMCrypto_CopyBuffer(requested_security_level_, params.encrypt_buffer, params.encrypt_length, &buffer_descriptor, params.subsample_flags); } + if (params.is_encrypted && params.cipher_mode != cipher_mode_) { + return INCORRECT_CRYPTO_MODE; + } if (params.is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + OEMCrypto_CENCEncryptPatternDesc pattern_descriptor; + pattern_descriptor.encrypt = params.pattern_descriptor.encrypt_blocks; + pattern_descriptor.skip = params.pattern_descriptor.skip_blocks; + pattern_descriptor.offset = params.pattern_descriptor.offset_blocks; AutoLock auto_lock(crypto_lock_); // Check if key needs to be selected if (params.is_encrypted) { - if (key_id_ != *params.key_id) { - if (SelectKey(*params.key_id)) { - key_id_ = *params.key_id; - } else { - return NEED_KEY; - } + if (!SelectKey(*params.key_id)) { + return NEED_KEY; } } - sts = OEMCrypto_DecryptCTR( + sts = OEMCrypto_DecryptCENC( oec_session_id_, params.encrypt_buffer, params.encrypt_length, params.is_encrypted, &(*params.iv).front(), params.block_offset, - &buffer_descriptor, params.subsample_flags); + &buffer_descriptor, &pattern_descriptor, params.subsample_flags); } switch (sts) { @@ -696,6 +719,13 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { return INSUFFICIENT_CRYPTO_RESOURCES; case OEMCrypto_ERROR_KEY_EXPIRED: return NEED_KEY; + case OEMCrypto_ERROR_INVALID_SESSION: + return SESSION_NOT_FOUND_FOR_DECRYPT; + case OEMCrypto_ERROR_DECRYPT_FAILED: + case OEMCrypto_ERROR_UNKNOWN_FAILURE: + return DECRYPT_ERROR; + case OEMCrypto_ERROR_INSUFFICIENT_HDCP: + return INSUFFICIENT_OUTPUT_PROTECTION; default: return UNKNOWN_ERROR; } @@ -1076,4 +1106,200 @@ bool CryptoSession::GetMaxNumberOfSessions(size_t* max) { return true; } +CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + LOGV("GenericEncrypt: id=%ld", (uint32_t)oec_session_id_); + if (!out_buffer) return INVALID_PARAMETERS_ENG_9; + + OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm); + if (iv.size() != GenericEncryptionBlockSize(algorithm) || + oec_algorithm == kInvalidAlgorithm) { + return INVALID_PARAMETERS_ENG_13; + } + + if (out_buffer->size() < in_buffer.size()) { + out_buffer->resize(in_buffer.size()); + } + + AutoLock auto_lock(crypto_lock_); + if (!SelectKey(key_id)) { + return KEY_ERROR_1; + } + + OEMCryptoResult sts = OEMCrypto_Generic_Encrypt( + oec_session_id_, reinterpret_cast(in_buffer.data()), + in_buffer.size(), reinterpret_cast(iv.data()), + oec_algorithm, + reinterpret_cast(const_cast(out_buffer->data()))); + + if (OEMCrypto_SUCCESS != sts) { + LOGE("GenericEncrypt: OEMCrypto_Generic_Encrypt err=%d", sts); + if (OEMCrypto_ERROR_KEY_EXPIRED == sts || + OEMCrypto_ERROR_NO_CONTENT_KEY == sts) { + return KEY_NOT_FOUND_3; + } else { + return UNKNOWN_ERROR; + } + } + return NO_ERROR; +} + +CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + LOGV("GenericDecrypt: id=%ld", (uint32_t)oec_session_id_); + if (!out_buffer) return INVALID_PARAMETERS_ENG_10; + + OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm); + if (iv.size() != GenericEncryptionBlockSize(algorithm) || + oec_algorithm == kInvalidAlgorithm) { + return INVALID_PARAMETERS_ENG_14; + } + + if (out_buffer->size() < in_buffer.size()) { + out_buffer->resize(in_buffer.size()); + } + + AutoLock auto_lock(crypto_lock_); + if (!SelectKey(key_id)) { + return KEY_ERROR_2; + } + + OEMCryptoResult sts = OEMCrypto_Generic_Decrypt( + oec_session_id_, reinterpret_cast(in_buffer.data()), + in_buffer.size(), reinterpret_cast(iv.data()), + oec_algorithm, + reinterpret_cast(const_cast(out_buffer->data()))); + + if (OEMCrypto_SUCCESS != sts) { + LOGE("GenericDecrypt: OEMCrypto_Generic_Decrypt err=%d", sts); + if (OEMCrypto_ERROR_KEY_EXPIRED == sts || + OEMCrypto_ERROR_NO_CONTENT_KEY == sts) { + return KEY_NOT_FOUND_4; + } else { + return UNKNOWN_ERROR; + } + } + return NO_ERROR; +} + +CdmResponseType CryptoSession::GenericSign(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature) { + LOGV("GenericSign: id=%ld", (uint32_t)oec_session_id_); + if (!signature) { + LOGE("GenerateSign: null signature string"); + return INVALID_PARAMETERS_ENG_11; + } + + OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm); + if (oec_algorithm == kInvalidAlgorithm) { + return INVALID_PARAMETERS_ENG_15; + } + + OEMCryptoResult sts; + size_t length = signature->size(); + + AutoLock auto_lock(crypto_lock_); + if (!SelectKey(key_id)) { + return KEY_ERROR_3; + } + + // At most two attempts. + // The first attempt may fail due to buffer too short + for (int i = 0; i < 2; ++i) { + sts = OEMCrypto_Generic_Sign( + oec_session_id_, reinterpret_cast(message.data()), + message.size(), oec_algorithm, + reinterpret_cast(const_cast(signature->data())), + &length); + + if (OEMCrypto_SUCCESS == sts) { + // Trim signature buffer and done + signature->resize(length); + return NO_ERROR; + } + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + break; + } + + // Retry with proper-sized return buffer + signature->resize(length); + } + + LOGE("GenericSign: OEMCrypto_Generic_Sign err=%d", sts); + if (OEMCrypto_ERROR_KEY_EXPIRED == sts || + OEMCrypto_ERROR_NO_CONTENT_KEY == sts) { + return KEY_NOT_FOUND_5; + } else { + return UNKNOWN_ERROR; + } +} + +CdmResponseType CryptoSession::GenericVerify(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature) { + LOGV("GenericVerify: id=%ld", (uint32_t)oec_session_id_); + + OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm); + if (oec_algorithm == kInvalidAlgorithm) { + return INVALID_PARAMETERS_ENG_16; + } + + AutoLock auto_lock(crypto_lock_); + if (!SelectKey(key_id)) { + return KEY_ERROR_4; + } + + OEMCryptoResult sts = OEMCrypto_Generic_Verify( + oec_session_id_, reinterpret_cast(message.data()), + message.size(), oec_algorithm, + reinterpret_cast(signature.data()), signature.size()); + + if (OEMCrypto_SUCCESS != sts) { + LOGE("GenericVerify: OEMCrypto_Generic_Verify err=%d", sts); + if (OEMCrypto_ERROR_KEY_EXPIRED == sts || + OEMCrypto_ERROR_NO_CONTENT_KEY == sts) { + return KEY_NOT_FOUND_6; + } else { + return UNKNOWN_ERROR; + } + } + return NO_ERROR; +} + +OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm( + CdmSigningAlgorithm algorithm) { + if (kSigningAlgorithmHmacSha256 == algorithm) { + return OEMCrypto_HMAC_SHA256; + } else { + return kInvalidAlgorithm; + } +} + +OEMCrypto_Algorithm CryptoSession::GenericEncryptionAlgorithm( + CdmEncryptionAlgorithm algorithm) { + if (kEncryptionAlgorithmAesCbc128 == algorithm) { + return OEMCrypto_AES_CBC_128_NO_PADDING; + } else { + return kInvalidAlgorithm; + } +} + +size_t CryptoSession::GenericEncryptionBlockSize( + CdmEncryptionAlgorithm algorithm) { + if (kEncryptionAlgorithmAesCbc128 == algorithm) { + return kAes128BlockSize; + } else { + return 0; + } +} + } // namespace wvcdm diff --git a/core/src/device_files.cpp b/core/src/device_files.cpp index b2bdb210..ffebc70b 100644 --- a/core/src/device_files.cpp +++ b/core/src/device_files.cpp @@ -18,13 +18,16 @@ #define MD5 CC_MD5 #define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH #else -#include #include +#include #endif // Protobuf generated classes. using video_widevine_client::sdk::DeviceCertificate; using video_widevine_client::sdk::HashedFile; +using video_widevine_client::sdk::HlsAttributes; +using video_widevine_client::sdk::HlsAttributes_Method_AES_128; +using video_widevine_client::sdk::HlsAttributes_Method_SAMPLE_AES; using video_widevine_client::sdk::License; using video_widevine_client::sdk::License_LicenseState_ACTIVE; using video_widevine_client::sdk::License_LicenseState_RELEASING; @@ -34,19 +37,13 @@ using video_widevine_client::sdk::UsageInfo_ProviderSession; namespace { -const char kCertificateFileNamePrefix[] = "cert"; -const char kCertificateFileNameExt[] = ".bin"; +const char kCertificateFileName[] = "cert.bin"; +const char kHlsAttributesFileNameExt[] = ".hal"; const char kUsageInfoFileNamePrefix[] = "usage"; const char kUsageInfoFileNameExt[] = ".bin"; const char kLicenseFileNameExt[] = ".lic"; const char kEmptyFileName[] = ""; const char kWildcard[] = "*"; -const char kDirectoryDelimiter = '/'; -const char* kSecurityLevelPathCompatibilityExclusionList[] = { - "ay64.dat", "ay64.dat2", "ay64.dat3"}; -size_t kSecurityLevelPathCompatibilityExclusionListSize = - sizeof(kSecurityLevelPathCompatibilityExclusionList) / - sizeof(*kSecurityLevelPathCompatibilityExclusionList); bool Hash(const std::string& data, std::string* hash) { if (!hash) return false; @@ -66,30 +63,30 @@ namespace wvcdm { // static std::set DeviceFiles::reserved_license_ids_; -DeviceFiles::DeviceFiles() - : file_(NULL), +DeviceFiles::DeviceFiles(FileSystem* file_system) + : file_system_(file_system), security_level_(kSecurityLevelUninitialized), - initialized_(false), - test_file_(false) {} + initialized_(false) {} -DeviceFiles::~DeviceFiles() { - if (test_file_) file_.release(); -} +DeviceFiles::~DeviceFiles() {} bool DeviceFiles::Init(CdmSecurityLevel security_level) { + if (!file_system_) { + LOGD("DeviceFiles::Init: Invalid FileSystem given."); + return false; + } + std::string path; if (!Properties::GetDeviceFilesBasePath(security_level, &path)) { LOGW("DeviceFiles::Init: Unsupported security level %d", security_level); return false; } - if (!test_file_) file_.reset(new File()); security_level_ = security_level; initialized_ = true; return true; } -bool DeviceFiles::StoreCertificate(const std::string& origin, - const std::string& certificate, +bool DeviceFiles::StoreCertificate(const std::string& certificate, const std::string& wrapped_private_key) { if (!initialized_) { LOGW("DeviceFiles::StoreCertificate: not initialized"); @@ -109,23 +106,18 @@ bool DeviceFiles::StoreCertificate(const std::string& origin, std::string serialized_file; file.SerializeToString(&serialized_file); - return StoreFileWithHash(GetCertificateFileName(origin), serialized_file); + return StoreFileWithHash(GetCertificateFileName(), serialized_file); } -bool DeviceFiles::RetrieveCertificate(const std::string& origin, - std::string* certificate, +bool DeviceFiles::RetrieveCertificate(std::string* certificate, std::string* wrapped_private_key) { if (!initialized_) { LOGW("DeviceFiles::RetrieveCertificate: not initialized"); return false; } - if (Properties::security_level_path_backward_compatibility_support()) { - SecurityLevelPathBackwardCompatibility(); - } - video_widevine_client::sdk::File file; - if (!RetrieveHashedFile(GetCertificateFileName(origin), &file)) { + if (!RetrieveHashedFile(GetCertificateFileName(), &file)) { return false; } @@ -151,22 +143,22 @@ bool DeviceFiles::RetrieveCertificate(const std::string& origin, return true; } -bool DeviceFiles::HasCertificate(const std::string& origin) { +bool DeviceFiles::HasCertificate() { if (!initialized_) { LOGW("DeviceFiles::HasCertificate: not initialized"); return false; } - return FileExists(GetCertificateFileName(origin)); + return FileExists(GetCertificateFileName()); } -bool DeviceFiles::RemoveCertificate(const std::string& origin) { +bool DeviceFiles::RemoveCertificate() { if (!initialized_) { LOGW("DeviceFiles::RemoveCertificate: not initialized"); return false; } - return RemoveFile(GetCertificateFileName(origin)); + return RemoveFile(GetCertificateFileName()); } bool DeviceFiles::StoreLicense( @@ -534,13 +526,105 @@ bool DeviceFiles::RetrieveUsageInfoByKeySetId( return false; } -bool DeviceFiles::StoreFileWithHash(const std::string& name, - const std::string& serialized_file) { - if (!file_.get()) { - LOGW("DeviceFiles::StoreFileWithHash: Invalid file handle"); +bool DeviceFiles::StoreHlsAttributes( + const std::string& key_set_id, const CdmHlsMethod method, + const std::vector& media_segment_iv) { + if (!initialized_) { + LOGW("DeviceFiles::StoreHlsAttributes: not initialized"); return false; } + // Fill in file information + video_widevine_client::sdk::File file; + + file.set_type(video_widevine_client::sdk::File::HLS_ATTRIBUTES); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + + HlsAttributes* hls_attributes = file.mutable_hls_attributes(); + switch (method) { + case kHlsMethodAes128: + hls_attributes->set_method(HlsAttributes_Method_AES_128); + break; + case kHlsMethodSampleAes: + hls_attributes->set_method(HlsAttributes_Method_SAMPLE_AES); + break; + case kHlsMethodNone: + default: + LOGW("DeviceFiles::StoreHlsAttributeInfo: Unknown HLS method: %u", + method); + return false; + break; + } + hls_attributes->set_media_segment_iv(&media_segment_iv[0], + media_segment_iv.size()); + + std::string serialized_file; + file.SerializeToString(&serialized_file); + + return StoreFileWithHash(key_set_id + kHlsAttributesFileNameExt, + serialized_file); +} + +bool DeviceFiles::RetrieveHlsAttributes( + const std::string& key_set_id, CdmHlsMethod* method, + std::vector* media_segment_iv) { + if (!initialized_) { + LOGW("DeviceFiles::RetrieveHlsAttributes: not initialized"); + return false; + } + + video_widevine_client::sdk::File file; + if (!RetrieveHashedFile(key_set_id + kHlsAttributesFileNameExt, &file)) { + return false; + } + + if (file.type() != video_widevine_client::sdk::File::HLS_ATTRIBUTES) { + LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file type: %u", + file.type()); + return false; + } + + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file version: %u", + file.version()); + return false; + } + + if (!file.has_hls_attributes()) { + LOGW("DeviceFiles::RetrieveHlsAttributes: HLS attributes not present"); + return false; + } + + HlsAttributes attributes = file.hls_attributes(); + + switch (attributes.method()) { + case HlsAttributes_Method_AES_128: + *method = kHlsMethodAes128; + break; + case HlsAttributes_Method_SAMPLE_AES: + *method = kHlsMethodSampleAes; + break; + default: + LOGW("DeviceFiles::RetrieveHlsAttributes: Unrecognized HLS method: %u", + attributes.method()); + *method = kHlsMethodNone; + break; + } + media_segment_iv->assign(attributes.media_segment_iv().begin(), + attributes.media_segment_iv().end()); + return true; +} + +bool DeviceFiles::DeleteHlsAttributes(const std::string& key_set_id) { + if (!initialized_) { + LOGW("DeviceFiles::DeleteHlsAttributes: not initialized"); + return false; + } + return RemoveFile(key_set_id + kHlsAttributesFileNameExt); +} + +bool DeviceFiles::StoreFileWithHash(const std::string& name, + const std::string& serialized_file) { // calculate SHA hash std::string hash; if (!Hash(serialized_file, &hash)) { @@ -561,30 +645,23 @@ bool DeviceFiles::StoreFileWithHash(const std::string& name, bool DeviceFiles::StoreFileRaw(const std::string& name, const std::string& serialized_file) { - if (!file_.get()) { - LOGW("DeviceFiles::StoreFileRaw: Invalid file handle"); - return false; - } - std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::StoreFileRaw: Unable to get base path"); return false; } - if (!file_->IsDirectory(path)) { - if (!file_->CreateDirectory(path)) return false; - } - path += name; - if (!file_->Open(path, File::kCreate | File::kTruncate | File::kBinary)) { + File* file = + file_system_->Open(path, FileSystem::kCreate | FileSystem::kTruncate); + if (!file) { LOGW("DeviceFiles::StoreFileRaw: File open failed: %s", path.c_str()); return false; } - ssize_t bytes = file_->Write(serialized_file.data(), serialized_file.size()); - file_->Close(); + ssize_t bytes = file->Write(serialized_file.data(), serialized_file.size()); + file->Close(); if (bytes != static_cast(serialized_file.size())) { LOGW( @@ -599,16 +676,12 @@ bool DeviceFiles::StoreFileRaw(const std::string& name, return true; } -bool DeviceFiles::RetrieveHashedFile(const std::string& name, - video_widevine_client::sdk::File* file) { +bool DeviceFiles::RetrieveHashedFile( + const std::string& name, + video_widevine_client::sdk::File* deserialized_file) { std::string serialized_file; - if (!file_.get()) { - LOGW("DeviceFiles::RetrieveHashedFile: Invalid file handle"); - return false; - } - - if (!file) { + if (!deserialized_file) { LOGW("DeviceFiles::RetrieveHashedFile: Unspecified file parameter"); return false; } @@ -621,29 +694,30 @@ bool DeviceFiles::RetrieveHashedFile(const std::string& name, path += name; - if (!file_->Exists(path)) { + if (!file_system_->Exists(path)) { LOGW("DeviceFiles::RetrieveHashedFile: %s does not exist", path.c_str()); return false; } - ssize_t bytes = file_->FileSize(path); + ssize_t bytes = file_system_->FileSize(path); if (bytes <= 0) { LOGW("DeviceFiles::RetrieveHashedFile: File size invalid: %s", path.c_str()); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. - file_->Remove(path); + file_system_->Remove(path); return false; } - if (!file_->Open(path, File::kReadOnly | File::kBinary)) { + File* file = file_system_->Open(path, FileSystem::kReadOnly); + if (!file) { return false; } std::string serialized_hash_file; serialized_hash_file.resize(bytes); - bytes = file_->Read(&serialized_hash_file[0], serialized_hash_file.size()); - file_->Close(); + bytes = file->Read(&serialized_hash_file[0], serialized_hash_file.size()); + file->Close(); if (bytes != static_cast(serialized_hash_file.size())) { LOGW("DeviceFiles::RetrieveHashedFile: read failed"); @@ -669,11 +743,11 @@ bool DeviceFiles::RetrieveHashedFile(const std::string& name, LOGW("DeviceFiles::RetrieveHashedFile: Hash mismatch"); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. - file_->Remove(path); + file_system_->Remove(path); return false; } - if (!file->ParseFromString(hash_file.file())) { + if (!deserialized_file->ParseFromString(hash_file.file())) { LOGW("DeviceFiles::RetrieveHashedFile: Unable to parse file"); return false; } @@ -681,11 +755,6 @@ bool DeviceFiles::RetrieveHashedFile(const std::string& name, } bool DeviceFiles::FileExists(const std::string& name) { - if (!file_.get()) { - LOGW("DeviceFiles::FileExists: Invalid file handle"); - return false; - } - std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::FileExists: Unable to get base path"); @@ -693,15 +762,10 @@ bool DeviceFiles::FileExists(const std::string& name) { } path += name; - return file_->Exists(path); + return file_system_->Exists(path); } bool DeviceFiles::RemoveFile(const std::string& name) { - if (!file_.get()) { - LOGW("DeviceFiles::RemoveFile: Invalid file handle"); - return false; - } - std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::RemoveFile: Unable to get base path"); @@ -709,15 +773,10 @@ bool DeviceFiles::RemoveFile(const std::string& name) { } path += name; - return file_->Remove(path); + return file_system_->Remove(path); } ssize_t DeviceFiles::GetFileSize(const std::string& name) { - if (!file_.get()) { - LOGW("DeviceFiles::GetFileSize: Invalid file handle"); - return -1; - } - std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::GetFileSize: Unable to get base path"); @@ -725,79 +784,15 @@ ssize_t DeviceFiles::GetFileSize(const std::string& name) { } path += name; - return file_->FileSize(path); + return file_system_->FileSize(path); } -void DeviceFiles::SecurityLevelPathBackwardCompatibility() { - std::string path; - if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { - LOGW( - "DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to " - "get base path"); - return; - } - - std::vector security_dirs; - if (!Properties::GetSecurityLevelDirectories(&security_dirs)) { - LOGW( - "DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to " - "get security directories"); - return; - } - - size_t pos = std::string::npos; - for (size_t i = 0; i < security_dirs.size(); ++i) { - pos = path.find(security_dirs[i]); - if (pos != std::string::npos && pos > 0 && - pos == path.size() - security_dirs[i].size() && - path[pos - 1] == kDirectoryDelimiter) { - break; - } - } - - if (pos == std::string::npos) { - LOGV( - "DeviceFiles::SecurityLevelPathBackwardCompatibility: Security level " - "specific path not found. Check properties?"); - return; - } - - std::string from_dir(path, 0, pos); - - std::vector files; - if (!file_->List(from_dir, &files)) { - return; - } - - for (size_t i = 0; i < files.size(); ++i) { - std::string from = from_dir + files[i]; - bool exclude = false; - for (size_t j = 0; j < kSecurityLevelPathCompatibilityExclusionListSize; - ++j) { - if (files[i] == kSecurityLevelPathCompatibilityExclusionList[j]) { - exclude = true; - break; - } - } - if (exclude) continue; - if (!file_->IsRegularFile(from)) continue; - - for (size_t j = 0; j < security_dirs.size(); ++j) { - std::string to_dir = from_dir + security_dirs[j]; - if (!file_->Exists(to_dir)) file_->CreateDirectory(to_dir); - std::string to = to_dir + files[i]; - file_->Copy(from, to); - } - file_->Remove(from); - } +std::string DeviceFiles::GetCertificateFileName() { + return kCertificateFileName; } -std::string DeviceFiles::GetCertificateFileName(const std::string& origin) { - std::string hash; - if (origin != EMPTY_ORIGIN) { - hash = GetFileNameSafeHash(origin); - } - return kCertificateFileNamePrefix + hash + kCertificateFileNameExt; +std::string DeviceFiles::GetHlsAttributesFileNameExtension() { + return kHlsAttributesFileNameExt; } std::string DeviceFiles::GetLicenseFileNameExtension() { @@ -820,9 +815,4 @@ std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) { return wvcdm::Base64SafeEncode(hash); } -void DeviceFiles::SetTestFile(File* file) { - file_.reset(file); - test_file_ = true; -} - } // namespace wvcdm diff --git a/core/src/device_files.proto b/core/src/device_files.proto index e9b793fd..89bd0c5e 100644 --- a/core/src/device_files.proto +++ b/core/src/device_files.proto @@ -52,11 +52,21 @@ message UsageInfo { repeated ProviderSession sessions = 1; } +message HlsAttributes { + enum Method { + AES_128 = 1; + SAMPLE_AES = 2; + } + optional Method method = 1; + optional bytes media_segment_iv = 2; +} + message File { enum FileType { DEVICE_CERTIFICATE = 1; LICENSE = 2; USAGE_INFO = 3; + HLS_ATTRIBUTES = 4; } enum FileVersion { @@ -68,6 +78,7 @@ message File { optional DeviceCertificate device_certificate = 3; optional License license = 4; optional UsageInfo usage_info = 5; + optional HlsAttributes hls_attributes = 6; } message HashedFile { diff --git a/core/src/initialization_data.cpp b/core/src/initialization_data.cpp index a8378586..8227a42c 100644 --- a/core/src/initialization_data.cpp +++ b/core/src/initialization_data.cpp @@ -2,31 +2,70 @@ #include "initialization_data.h" +#include #include #include "buffer_reader.h" +#include "jsmn.h" +#include "license_protocol.pb.h" #include "log.h" #include "properties.h" +#include "string_conversions.h" #include "wv_cdm_constants.h" +namespace { +const char kKeyFormatVersionsSeparator = '/'; +const char kColon = ':'; +const char kDoubleQuote = '\"'; +const char kLeftBracket = '['; +const char kRightBracket = ']'; +const std::string kBase64String = "base64,"; +const uint32_t kFourCcCbc1 = 0x63626331; +const uint32_t kFourCcCbcs = 0x63626373; + +// json init data key values +const std::string kProvider = "provider"; +const std::string kContentId = "content_id"; +const std::string kKeyIds = "key_ids"; + +// Being conservative, usually we expect 6 + number of Key Ids +const int kDefaultNumJsonTokens = 128; +} // namespace + namespace wvcdm { +// Protobuf generated classes. +using video_widevine_server::sdk::WidevineCencHeader; +using video_widevine_server::sdk::WidevineCencHeader_Algorithm; +using video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR; + InitializationData::InitializationData(const std::string& type, const CdmInitData& data) - : type_(type), is_cenc_(false), is_webm_(false) { + : type_(type), + is_cenc_(false), + is_hls_(false), + is_webm_(false), + hls_method_(kHlsMethodNone) { if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE || type == CENC_INIT_DATA_FORMAT) { is_cenc_ = true; } else if (type == WEBM_VIDEO_MIME_TYPE || type == WEBM_AUDIO_MIME_TYPE || type == WEBM_INIT_DATA_FORMAT) { is_webm_ = true; + } else if (type == HLS_INIT_DATA_FORMAT) { + is_hls_ = true; } if (is_supported()) { if (is_cenc()) { ExtractWidevinePssh(data, &data_); - } else { + } else if (is_webm()) { data_ = data; + } else if (is_hls()) { + std::string uri; + if (ExtractHlsAttributes(data, &hls_method_, &hls_iv_, &uri)) { + ConstructWidevineInitData(hls_method_, uri, &data_); + } } } } @@ -61,19 +100,22 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // atom size, used for skipping. uint64_t size; if (!reader.Read4Into8(&size)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom size."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read atom size."); return false; } std::vector atom_type; if (!reader.ReadVec(&atom_type, 4)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom type."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read atom type."); return false; } if (size == 1) { if (!reader.Read8(&size)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read 64-bit atom " - "size."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read 64-bit " + "atom size."); return false; } } else if (size == 0) { @@ -82,10 +124,12 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // "pssh" if (memcmp(&atom_type[0], "pssh", 4)) { - LOGV("CdmEngine::ExtractWidevinePssh: PSSH literal not present."); + LOGV( + "InitializationData::ExtractWidevinePssh: PSSH literal not present."); if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of " - "the atom."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the rest " + "of the atom."); return false; } continue; @@ -94,15 +138,18 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // version uint8_t version; if (!reader.Read1(&version)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH version."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read PSSH " + "version."); return false; } if (version > 1) { // unrecognized version - skip. if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of " - "the atom."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the rest " + "of the atom."); return false; } continue; @@ -110,25 +157,31 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // flags if (!reader.SkipBytes(3)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the PSSH flags."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the PSSH " + "flags."); return false; } // system id std::vector system_id; if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read system ID."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read system ID."); return false; } if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) { // skip non-Widevine PSSH boxes. if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of " - "the atom."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the rest " + "of the atom."); return false; } - LOGV("CdmEngine::ExtractWidevinePssh: Skipping non-Widevine PSSH."); + LOGV( + "InitializationData::ExtractWidevinePssh: Skipping non-Widevine " + "PSSH."); continue; } @@ -136,11 +189,14 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // v1 has additional fields for key IDs. We can skip them. uint32_t num_key_ids; if (!reader.Read4(&num_key_ids)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read num key IDs."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read num key " + "IDs."); return false; } if (!reader.SkipBytes(num_key_ids * 16)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip key IDs."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip key IDs."); return false; } } @@ -148,13 +204,16 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // size of PSSH data uint32_t data_length; if (!reader.Read4(&data_length)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data size."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read PSSH data " + "size."); return false; } output->clear(); if (!reader.ReadString(output, data_length)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read PSSH data."); return false; } @@ -165,4 +224,357 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, return false; } +// Parse an EXT-X-KEY tag attribute list. Verify that Widevine supports it +// by validating KEYFORMAT and KEYFORMATVERSION attributes. Extract out +// method, IV, URI and WV init data. +// +// An example of a widevine supported attribute list from an HLS media playlist +// is, +// "EXT-X-KEY: METHOD=SAMPLE-AES, \" +// "URI=”data:text/plain;base64,eyANCiAgICJwcm92aWRlciI6Im1sYmFtaGJvIiwNCiAg" +// "ICJjb250ZW50X2lkIjoiMjAxNV9UZWFycyIsDQogICAia2V5X2lkcyI6DQogICBbDQo" +// "gICAgICAiMzcxZTEzNWUxYTk4NWQ3NWQxOThhN2Y0MTAyMGRjMjMiDQogICBdDQp9DQ" +// "o=, \" +// "IV=0x6df49213a781e338628d0e9c812d328e, \" +// "KEYFORMAT=”com.widevine”, \" +// "KEYFORMATVERSIONS=”1”" +bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list, + CdmHlsMethod* method, + std::vector* iv, + std::string* uri) { + std::string value; + if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_ATTRIBUTE, + &value)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS " + "keyformat value"); + return false; + } + + if (value.compare(0, sizeof(KEY_SYSTEM) - 1, KEY_SYSTEM) != 0) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: unrecognized HLS " + "keyformat value: %s", + value.c_str()); + return false; + } + + // KEYFORMATVERSIONS is an optional parameter. If absent its + // value defaults to "1" + if (ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, + &value)) { + std::vector versions = ExtractKeyFormatVersions(value); + bool supported = false; + for (size_t i = 0; i < versions.size(); ++i) { + if (versions[i].compare(HLS_KEYFORMAT_VERSION_VALUE_1) == 0) { + supported = true; + break; + } + } + if (!supported) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS keyformat " + "version is not supported: %s", + value.c_str()); + return false; + } + } + + if (!ExtractAttribute(attribute_list, HLS_METHOD_ATTRIBUTE, &value)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS " + "method"); + return false; + } + + if (value.compare(HLS_METHOD_AES_128) == 0) { + *method = kHlsMethodAes128; + } else if (value.compare(HLS_METHOD_SAMPLE_AES) == 0) { + *method = kHlsMethodSampleAes; + } else if (value.compare(HLS_METHOD_NONE) == 0) { + *method = kHlsMethodNone; + } else { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS method " + "unrecognized: %s", + value.c_str()); + return false; + } + + if (!ExtractHexAttribute(attribute_list, HLS_IV_ATTRIBUTE, iv)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS IV attribute " + "not present"); + return false; + } + + if (!ExtractQuotedAttribute(attribute_list, HLS_URI_ATTRIBUTE, uri)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS URI attribute " + "not present"); + return false; + } + + return true; +} + +// Extracts a base64 encoded string from URI data. This is then base64 decoded +// and the Json formatted init data is then parsed. The information is used +// to generate a Widevine init data protobuf (WidevineCencHeader). +// +// An example of a widevine supported json formatted init data string is, +// +// { +// "provider":"mlbamhbo", +// "content_id":"MjAxNV9UZWFycw==", +// "key_ids": +// [ +// "371e135e1a985d75d198a7f41020dc23" +// ] +// } +bool InitializationData::ConstructWidevineInitData( + CdmHlsMethod method, const std::string& uri, CdmInitData* init_data_proto) { + if (!init_data_proto) { + LOGV("InitializationData::ConstructWidevineInitData: Invalid parameter"); + return false; + } + if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) { + LOGV("InitializationData::ConstructWidevineInitData: Invalid method" + " parameter"); + return false; + } + + size_t pos = uri.find(kBase64String); + if (pos == std::string::npos) { + LOGV( + "InitializationData::ConstructWidevineInitData: URI attribute " + "unexpected format: %s", + uri.c_str()); + return false; + } + + std::vector json_init_data = + Base64Decode(uri.substr(pos + kBase64String.size())); + if (json_init_data.size() == 0) { + LOGV( + "InitializationData::ConstructWidevineInitData: Base64 decode of json " + "data failed"); + return false; + } + std::string json_string((const char*)(&json_init_data[0]), + json_init_data.size()); + + // Parse the Json string using jsmn + jsmn_parser parser; + jsmntok_t tokens[kDefaultNumJsonTokens]; + jsmn_init(&parser); + int num_of_tokens = + jsmn_parse(&parser, json_string.c_str(), json_string.size(), tokens, + kDefaultNumJsonTokens); + + if (num_of_tokens <= 0) { + LOGV( + "InitializationData::ConstructWidevineInitData: Json parsing failed: " + "%d", + num_of_tokens); + return false; + } + + std::string provider; + std::string content_id; + std::vector key_ids; + + enum JsmnParserState { + kParseState, + kProviderState, + kContentIdState, + kKeyIdsState, + } state = kParseState; + + int number_of_key_ids = 0; + + // Extract the provider, content_id and key_ids + for (int i = 0; i < num_of_tokens; ++i) { + if (tokens[i].start < 0 || tokens[i].end < 0) { + LOGV( + "InitializationData::ConstructWidevineInitData: Invalid start or end " + "of token"); + return false; + } + + switch (state) { + case kParseState: + if (tokens[i].type == JSMN_STRING) { + std::string token(json_string, tokens[i].start, + tokens[i].end - tokens[i].start); + if (token == kProvider) { + state = kProviderState; + } else if (token == kContentId) { + state = kContentIdState; + } else if (token == kKeyIds) { + state = kKeyIdsState; + } + } + break; + case kProviderState: + if (tokens[i].type == JSMN_STRING) { + provider.assign(json_string, tokens[i].start, + tokens[i].end - tokens[i].start); + } + state = kParseState; + break; + case kContentIdState: + if (tokens[i].type == JSMN_STRING) { + std::string base64_content_id(json_string, tokens[i].start, + tokens[i].end - tokens[i].start); + std::vector content_id_data = + Base64Decode(base64_content_id); + content_id.assign(reinterpret_cast(&content_id_data[0]), + content_id_data.size()); + } + state = kParseState; + break; + case kKeyIdsState: + if (tokens[i].type == JSMN_ARRAY) { + number_of_key_ids = tokens[i].size; + } else if (tokens[i].type == JSMN_STRING) { + std::string key_id(a2bs_hex(json_string.substr( + tokens[i].start, tokens[i].end - tokens[i].start))); + if (key_id.size() == 16) key_ids.push_back(key_id); + --number_of_key_ids; + } else { + state = kParseState; + } + if (number_of_key_ids <= 0) state = kParseState; + break; + } + } + + if (provider.size() == 0) { + LOGV("InitializationData::ConstructWidevineInitData: Invalid provider"); + return false; + } + + if (content_id.size() == 0) { + LOGV("InitializationData::ConstructWidevineInitData: Invalid content_id"); + return false; + } + + if (key_ids.size() == 0) { + LOGV("InitializationData::ConstructWidevineInitData: No key_ids present"); + return false; + } + + // Now format as Widevine init data protobuf + WidevineCencHeader cenc_header; + // TODO(rfrias): The algorithm is a deprecated field, but proto changes + // have not yet been pushed to production. Set until then. + cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR); + for (size_t i = 0; i < key_ids.size(); ++i) { + cenc_header.add_key_id(key_ids[i]); + } + cenc_header.set_provider(provider); + cenc_header.set_content_id(content_id); + if (method == kHlsMethodAes128) + cenc_header.set_protection_scheme(htonl(kFourCcCbc1)); + else + cenc_header.set_protection_scheme(htonl(kFourCcCbcs)); + cenc_header.SerializeToString(init_data_proto); + return true; +} + +bool InitializationData::ExtractQuotedAttribute( + const std::string& attribute_list, const std::string& key, + std::string* value) { + bool result = ExtractAttribute(attribute_list, key, value); + if (value->size() < 2 || value->at(0) != '\"' || + value->at(value->size() - 1) != '\"') + return false; + *value = value->substr(1, value->size() - 2); + if (value->find('\"') != std::string::npos) return false; + return result; +} + +bool InitializationData::ExtractHexAttribute(const std::string& attribute_list, + const std::string& key, + std::vector* value) { + std::string val; + bool result = ExtractAttribute(attribute_list, key, &val); + if (!result || val.size() <= 2 || val.size() % 2 != 0 || val[0] != '0' || + ((val[1] != 'x') && (val[1] != 'X'))) + return false; + for (size_t i = 2; i < val.size(); ++i) { + if (!isxdigit(val[i])) return false; + } + *value = a2b_hex(val.substr(2, val.size() - 2)); + return result; +} + +bool InitializationData::ExtractAttribute(const std::string& attribute_list, + const std::string& key, + std::string* value) { + // validate the key + for (size_t i = 0; i < key.size(); ++i) + if (!isupper(key[i]) && !isdigit(key[i]) && key[i] != '-') return false; + + bool found = false; + size_t pos = 0; + // Find the key followed by '=' + while (!found) { + pos = attribute_list.find(key, pos); + if (pos == std::string::npos) return false; + pos += key.size(); + if (attribute_list[pos] != '=') continue; + found = true; + } + + if (attribute_list.size() <= ++pos) return false; + + size_t end_pos = pos; + found = false; + // Find the next comma outside the quote or end of string + while (!found) { + end_pos = attribute_list.find(',', end_pos); + if (end_pos != std::string::npos && attribute_list[pos] == '\"' && + attribute_list[end_pos - 1] != '\"') { + ++end_pos; + continue; + } + + if (end_pos == std::string::npos) + end_pos = attribute_list.size() - 1; + else + --end_pos; + found = true; + } + + *value = attribute_list.substr(pos, end_pos - pos + 1); + + // validate the value + for (size_t i = 0; i < value->size(); ++i) + if (!isgraph(value->at(i))) return false; + + return true; +} + +// Key format versions are individual values or multiple versions +// separated by '/'. "1" or "1/2/5" +std::vector InitializationData::ExtractKeyFormatVersions( + const std::string& key_format_versions) { + std::vector versions; + size_t pos = 0; + while (pos < key_format_versions.size()) { + size_t next_pos = + key_format_versions.find(kKeyFormatVersionsSeparator, pos); + if (next_pos == std::string::npos) { + versions.push_back(key_format_versions.substr(pos)); + break; + } else { + versions.push_back(key_format_versions.substr(pos, next_pos - pos)); + pos = next_pos + 1; + } + } + return versions; +} + } // namespace wvcdm diff --git a/core/src/license.cpp b/core/src/license.cpp index 72af4db7..04603769 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -2,6 +2,8 @@ #include "license.h" +#include +#include #include #include "clock.h" @@ -24,6 +26,7 @@ std::string kProductNameKey = "product_name"; std::string kBuildInfoKey = "build_info"; std::string kDeviceIdKey = "device_id"; std::string kWVCdmVersionKey = "widevine_cdm_version"; +std::string kOemCryptoSecurityPatchLevelKey = "oem_crypto_security_patch_level"; const unsigned char kServiceCertificateCAPublicKey[] = { 0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xb4, 0xfe, 0x39, 0xc3, 0x65, 0x90, 0x03, 0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68, 0xcd, @@ -60,6 +63,10 @@ const unsigned char kServiceCertificateCAPublicKey[] = { 0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33, 0x5f, 0x91, 0x02, 0x03, 0x01, 0x00, 0x01}; } +const uint32_t kFourCcCbc1 = 0x63626331; +const uint32_t kFourCcCbcs = 0x63626373; +const uint32_t kFourCcCenc = 0x63656e63; +const uint32_t kFourCcCens = 0x63656e73; namespace wvcdm { @@ -91,7 +98,7 @@ static std::vector ExtractContentKeys(const License& license) { size_t length; switch (license.key(i).type()) { case License_KeyContainer::CONTENT: - case License_KeyContainer::OPERATOR_SESSION: + case License_KeyContainer::OPERATOR_SESSION: { key.set_key_id(license.key(i).id()); // Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes, // the padding will always be 16 bytes. @@ -106,8 +113,17 @@ static std::vector ExtractContentKeys(const License& license) { key.set_key_control(license.key(i).key_control().key_control_block()); key.set_key_control_iv(license.key(i).key_control().iv()); } + uint32_t four_cc = kFourCcCenc; + if (license.has_protection_scheme()) { + four_cc = ntohl(license.protection_scheme()); + } + if (four_cc == kFourCcCbc1 || four_cc == kFourCcCbcs) + key.set_cipher_mode(kCipherModeCbc); + else + key.set_cipher_mode(kCipherModeCtr); key_array.push_back(key); break; + } case License_KeyContainer::KEY_CONTROL: if (license.key(i).has_key_control()) { key.set_key_control(license.key(i).key_control().key_control_block()); @@ -232,7 +248,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest( LicenseRequest_ContentIdentification* content_id = license_request.mutable_content_id(); - if (init_data.is_cenc()) { + if (init_data.is_cenc() || init_data.is_hls()) { LicenseRequest_ContentIdentification_CENC* cenc_content_id = content_id->mutable_cenc_id(); @@ -535,8 +551,6 @@ CdmResponseType CdmLicense::HandleKeyResponse( server_url_ = license.policy().renewal_server_url(); } - policy_engine_->SetLicense(license); - if (license.policy().has_renew_with_client_id()) { renew_with_client_id_ = license.policy().renew_with_client_id(); } @@ -551,6 +565,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( it != key_array.end(); ++it) { loaded_keys_.insert(it->key_id()); } + policy_engine_->SetLicense(license); } return resp; } @@ -612,8 +627,6 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( return LICENSE_ID_NOT_FOUND; } - policy_engine_->UpdateLicense(license); - if (license.policy().has_renew_with_client_id()) { renew_with_client_id_ = license.policy().renew_with_client_id(); } @@ -637,6 +650,8 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( if (session_->RefreshKeys(signed_response.msg(), signed_response.signature(), key_array.size(), &key_array[0])) { + policy_engine_->UpdateLicense(license); + return KEY_ADDED; } else { return REFRESH_KEYS_ERROR; @@ -981,6 +996,11 @@ CdmResponseType CdmLicense::PrepareClientId( client_info->set_name(kWVCdmVersionKey); client_info->set_value(value); } + client_info = client_id->add_client_info(); + client_info->set_name(kOemCryptoSecurityPatchLevelKey); + std::stringstream ss; + ss << (uint32_t)session_->GetSecurityPatchLevel(); + client_info->set_value(ss.str()); ClientIdentification_ClientCapabilities* client_capabilities = client_id->mutable_client_capabilities(); diff --git a/core/src/license_key_status.cpp b/core/src/license_key_status.cpp new file mode 100644 index 00000000..d467687f --- /dev/null +++ b/core/src/license_key_status.cpp @@ -0,0 +1,285 @@ +// Copyright 2016 Google Inc. All Rights Reserved. + +#include "license_key_status.h" + +#include + +#include "log.h" + +namespace { +// License protocol aliases +typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; +typedef KeyContainer::OutputProtection OutputProtection; +typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint; +typedef ::google::protobuf::RepeatedPtrField + ConstraintList; + +// Map the HDCP protection associated with a key in the license to +// an equivalent OEMCrypto HDCP protection level +wvcdm::CryptoSession::HdcpCapability ProtobufHdcpToOemCryptoHdcp( + const OutputProtection::HDCP& input) { + switch (input) { + case OutputProtection::HDCP_NONE: + return HDCP_NONE; + case OutputProtection::HDCP_V1: + return HDCP_V1; + case OutputProtection::HDCP_V2: + return HDCP_V2; + case OutputProtection::HDCP_V2_1: + return HDCP_V2_1; + case OutputProtection::HDCP_V2_2: + return HDCP_V2_2; + case OutputProtection::HDCP_NO_DIGITAL_OUTPUT: + return HDCP_NO_DIGITAL_OUTPUT; + default: + LOGE("ContentKeyStatus::ProtobufHdcpToOemCryptoHdcp: " + "Unknown HDCP Level: input=%d, returning HDCP_NO_DIGITAL_OUTPUT", + input); + return HDCP_NO_DIGITAL_OUTPUT; + } +} + +// Returns the constraint from a set of constraints that matches the +// specified resolution, or null if none match +VideoResolutionConstraint* GetConstraintForRes( + uint32_t res, ConstraintList& constraints_from_key) { + typedef ConstraintList::pointer_iterator Iterator; + for (Iterator i = constraints_from_key.pointer_begin(); + i != constraints_from_key.pointer_end(); ++i) { + VideoResolutionConstraint* constraint = *i; + if (constraint->has_min_resolution_pixels() && + constraint->has_max_resolution_pixels() && + res >= constraint->min_resolution_pixels() && + res <= constraint->max_resolution_pixels()) { + return constraint; + } + } + return NULL; +} + +} // namespace + +namespace wvcdm { + +bool LicenseKeys::IsContentKey(const std::string& key_id) { + if (keys_.count(key_id) > 0) { + return keys_[key_id]->IsContentKey(); + } else { + return false; + } +} + +bool LicenseKeys::CanDecryptContent(const std::string& key_id) { + if (keys_.count(key_id) > 0) { + return keys_[key_id]->CanDecryptContent(); + } else { + return false; + } +} + +bool LicenseKeys::GetAllowedUsage(const KeyId& key_id, + CdmKeyAllowedUsage* allowed_usage) { + if (keys_.count(key_id) > 0) { + return keys_[key_id]->GetAllowedUsage(allowed_usage); + } else { + return false; + } +} + +bool LicenseKeys::ApplyStatusChange(CdmKeyStatus new_status, + bool* new_usable_keys) { + bool keys_changed = false; + bool newly_usable = false; + *new_usable_keys = false; + for (LicenseKeyStatusIterator it = keys_.begin(); it != keys_.end(); ++it) { + bool usable; + if (it->second->ApplyStatusChange(new_status, &usable)) { + newly_usable |= usable; + keys_changed = true; + } + } + *new_usable_keys = newly_usable; + return keys_changed; +} + +void LicenseKeys::ExtractKeyStatuses(CdmKeyStatusMap* content_keys) { + for (LicenseKeyStatusIterator it = keys_.begin(); it != keys_.end(); ++it) { + if (it->second->IsContentKey()) { + const KeyId key_id = it->first; + CdmKeyStatus key_status = it->second->GetKeyStatus(); + (*content_keys)[key_id] = key_status; + } + } +} + +bool LicenseKeys::MeetsConstraints(const KeyId& key_id) { + if (keys_.count(key_id) > 0) { + return keys_[key_id]->MeetsConstraints(); + } else { + // If a Key ID is unknown to us, we don't know of any constraints for it, + // so never block decryption. + return true; + } +} + +void LicenseKeys::ApplyConstraints( + uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level) { + for (LicenseKeyStatusIterator i = keys_.begin(); i != keys_.end(); ++i) { + i->second->ApplyConstraints(new_resolution, new_hdcp_level); + } +} + +void LicenseKeys::SetFromLicense( + const video_widevine_server::sdk::License& license) { + this->Clear(); + for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) { + const KeyContainer& key = license.key(key_index); + if (key.has_id() && (key.type() == KeyContainer::CONTENT || + key.type() == KeyContainer::OPERATOR_SESSION)) { + const KeyId& key_id = key.id(); + keys_[key_id] = new LicenseKeyStatus(key); + } + } +} + +LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key) : + is_content_key_(false), + key_status_(kKeyStatusInternalError), + meets_constraints_(true), + default_hdcp_level_(HDCP_NONE) { + + allowed_usage_.Clear(); + constraints_.Clear(); + + if (key.type() == KeyContainer::CONTENT) { + ParseContentKey(key); + } else if (key.type() == KeyContainer::OPERATOR_SESSION) { + ParseOperatorSessionKey(key); + } +} + +void LicenseKeyStatus::ParseContentKey(const KeyContainer& key) { + is_content_key_ = true; + if (key.has_level() && + ((key.level() == KeyContainer::HW_SECURE_DECODE) || + (key.level() == KeyContainer::HW_SECURE_ALL))) { + allowed_usage_.decrypt_to_clear_buffer = false; + allowed_usage_.decrypt_to_secure_buffer = true; + } else { + allowed_usage_.decrypt_to_clear_buffer = true; + allowed_usage_.decrypt_to_secure_buffer = true; + } + allowed_usage_.SetValid(); + + if (key.video_resolution_constraints_size() > 0) { + SetConstraints(key.video_resolution_constraints()); + } + + if (key.has_required_protection()) { + default_hdcp_level_ = + ProtobufHdcpToOemCryptoHdcp(key.required_protection().hdcp()); + } +} + +void LicenseKeyStatus::ParseOperatorSessionKey(const KeyContainer& key) { + is_content_key_ = false; + if (key.has_operator_session_key_permissions()) { + OperatorSessionKeyPermissions permissions = + key.operator_session_key_permissions(); + if (permissions.has_allow_encrypt()) + allowed_usage_.generic_encrypt = permissions.allow_encrypt(); + if (permissions.has_allow_decrypt()) + allowed_usage_.generic_decrypt = permissions.allow_decrypt(); + if (permissions.has_allow_sign()) + allowed_usage_.generic_sign = permissions.allow_sign(); + if (permissions.has_allow_signature_verify()) + allowed_usage_.generic_verify = permissions.allow_signature_verify(); + } else { + allowed_usage_.generic_encrypt = false; + allowed_usage_.generic_decrypt = false; + allowed_usage_.generic_sign = false; + allowed_usage_.generic_verify = false; + } + allowed_usage_.SetValid(); +} + +void LicenseKeys::Clear() { + for (LicenseKeyStatusIterator i = keys_.begin(); i != keys_.end(); ++i) { + delete i->second; + } + keys_.clear(); +} + +bool LicenseKeyStatus::CanDecryptContent() { + return is_content_key_ && key_status_ == kKeyStatusUsable; +} + +bool LicenseKeyStatus::GetAllowedUsage(CdmKeyAllowedUsage* allowed_usage) { + if (NULL == allowed_usage) + return false; + *allowed_usage = allowed_usage_; + return true; +} + +bool LicenseKeyStatus::ApplyStatusChange(CdmKeyStatus new_status, + bool* new_usable_key) { + *new_usable_key = false; + if (!is_content_key_) { + return false; + } + CdmKeyStatus updated_status = new_status; + if (updated_status == kKeyStatusUsable) { + if (!MeetsConstraints()) { + updated_status = kKeyStatusOutputNotAllowed; + } + } + if (key_status_ != updated_status) { + key_status_ = updated_status; + if (updated_status == kKeyStatusUsable) { + *new_usable_key = true; + } + return true; + } + return false; +} + +// If the key has constraints, find the constraint that applies. +// If none found, then the constraint test fails. +// If a constraint is found, verify that the device's current HDCP +// level is sufficient. If the constraint has an HDCP setting, use it, +// If the key has no constraints, or if the constraint has no HDCP +// requirement, use the key's default HDCP setting to check against the +// device's current HDCP level. +void LicenseKeyStatus::ApplyConstraints( + uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level) { + + VideoResolutionConstraint* current_constraint = NULL; + if (HasConstraints()) { + current_constraint = GetConstraintForRes(new_resolution, constraints_); + if (NULL == current_constraint) { + meets_constraints_ = false; + return; + } + } + + CryptoSession::HdcpCapability desired_hdcp_level; + if (current_constraint && current_constraint->has_required_protection()) { + desired_hdcp_level = ProtobufHdcpToOemCryptoHdcp( + current_constraint->required_protection().hdcp()); + } else { + desired_hdcp_level = default_hdcp_level_; + } + + meets_constraints_ = (new_hdcp_level >= desired_hdcp_level); +} + +void LicenseKeyStatus::SetConstraints(const ConstraintList& constraints) { + if (!is_content_key_) { + return; + } + constraints_.Clear(); + constraints_.MergeFrom(constraints); + meets_constraints_ = true; +} + +} // namespace wvcdm diff --git a/core/src/license_protocol.proto b/core/src/license_protocol.proto index 9079594e..f411b6b6 100644 --- a/core/src/license_protocol.proto +++ b/core/src/license_protocol.proto @@ -196,6 +196,10 @@ message License { optional bool remote_attestation_verified = 5 [default = false]; // Client token generated by the content provider. Optional. optional bytes provider_client_token = 6; + // Protection scheme identifying the encryption algorithm. Represented as one + // of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC), + // 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample). + optional uint32 protection_scheme = 7; } enum ProtocolVersion { @@ -268,6 +272,28 @@ message LicenseError { optional Error error_code = 1; } +message MetricData { + enum MetricType { + // The time spent in the 'stage', specified in microseconds. + LATENCY = 1; + // The UNIX epoch timestamp at which the 'stage' was first accessed in + // microseconds. + TIMESTAMP = 2; + } + + message TypeValue { + optional MetricType type = 1; + // The value associated with 'type'. For example if type == LATENCY, the + // value would be the time in microseconds spent in this 'stage'. + optional int64 value = 2 [default = 0]; + } + + // 'stage' that is currently processing the SignedMessage. Required. + optional string stage_name = 1; + // metric and associated value. + repeated TypeValue metric_data = 2; +} + message RemoteAttestation { // Encrypted ClientIdentification message containing the device remote // attestation certificate. Required. @@ -296,6 +322,14 @@ message SignedMessage { // request for ChromeOS client devices operating in verified mode. Remote // attestation challenge data is |msg| field above. Optional. optional RemoteAttestation remote_attestation = 5; + repeated MetricData metric_data = 6; +} + +message GroupKeys { + repeated License.KeyContainer key = 1; + // Byte string that identifies the group to which this license material + // belongs. + optional bytes group_id = 2; } // ---------------------------------------------------------------------------- @@ -427,7 +461,7 @@ message EncryptedClientIdentification { optional bytes encrypted_client_id = 3; // Initialization vector needed to decrypt encrypted_client_id. optional bytes encrypted_client_id_iv = 4; - // AES-128 privacy key, encrytped with the service public public key using + // AES-128 privacy key, encrypted with the service public public key using // RSA-OAEP. optional bytes encrypted_privacy_key = 5; } @@ -547,3 +581,48 @@ message SignedCertificateStatusList { // key using RSASSA-PSS. Required. optional bytes signature = 2; } + +// ---------------------------------------------------------------------------- +// widevine_header.proto +// ---------------------------------------------------------------------------- +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Description: +// Public protocol buffer definitions for Widevine Cenc Header +// protocol. +message WidevineCencHeader { + enum Algorithm { + UNENCRYPTED = 0; + AESCTR = 1; + }; + // Replaced with protection_scheme. + optional Algorithm algorithm = 1 [deprecated=true]; + repeated bytes key_id = 2; + + // Content provider name. + optional string provider = 3; + + // A content identifier, specified by content provider. + optional bytes content_id = 4; + + // Track type. Acceptable values are SD, HD and AUDIO. Used to differentiate + // content keys used by an asset. + // No longer adding track_type to the PSSH since the Widevine license server + // will return keys for all allowed track types in a single license. + optional string track_type_deprecated = 5; + + // The name of a registered policy to be used for this asset. + optional string policy = 6 [deprecated=true]; + + // Crypto period index, for media using key rotation. + optional uint32 crypto_period_index = 7; + + // Optional protected context for group content. The grouped_license is a + // serialized SignedMessage. + optional bytes grouped_license = 8; + + // Protection scheme identifying the encryption algorithm. Represented as one + // of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC), + // 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample). + optional uint32 protection_scheme = 9; +} diff --git a/core/src/max_res_engine.cpp b/core/src/max_res_engine.cpp deleted file mode 100644 index fbc28a81..00000000 --- a/core/src/max_res_engine.cpp +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2014 Google Inc. All Rights Reserved. - -#include "max_res_engine.h" - -#include "clock.h" -#include "log.h" - -namespace { - -const int64_t kHdcpCheckInterval = 10; -const uint32_t kNoResolution = 0; - -} // namespace - -namespace wvcdm { - -MaxResEngine::MaxResEngine(CryptoSession* crypto_session) { - Init(crypto_session, new Clock()); -} - -MaxResEngine::MaxResEngine(CryptoSession* crypto_session, Clock* clock) { - Init(crypto_session, clock); -} - -MaxResEngine::~MaxResEngine() { - AutoLock lock(status_lock_); - DeleteAllKeys(); -} - -bool MaxResEngine::CanDecrypt(const KeyId& key_id) { - AutoLock lock(status_lock_); - if (keys_.count(key_id) > 0) { - return keys_[key_id]->can_decrypt(); - } else { - // If a Key ID is unknown to us, we don't know of any constraints for it, - // so never block decryption. - return true; - } -} - -void MaxResEngine::Init(CryptoSession* crypto_session, Clock* clock) { - AutoLock lock(status_lock_); - current_resolution_ = kNoResolution; - clock_.reset(clock); - next_check_time_ = clock_->GetCurrentTime(); - crypto_session_ = crypto_session; -} - -void MaxResEngine::SetLicense( - const video_widevine_server::sdk::License& license) { - AutoLock lock(status_lock_); - DeleteAllKeys(); - for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) { - const KeyContainer& key = license.key(key_index); - if (key.type() == KeyContainer::CONTENT && key.has_id() && - key.video_resolution_constraints_size() > 0) { - const ConstraintList& constraints = key.video_resolution_constraints(); - const KeyId& key_id = key.id(); - if (key.has_required_protection()) { - keys_[key_id] = - new KeyStatus(constraints, key.required_protection().hdcp()); - } else { - keys_[key_id] = new KeyStatus(constraints); - } - } - } -} - -void MaxResEngine::SetResolution(uint32_t width, uint32_t height) { - AutoLock lock(status_lock_); - current_resolution_ = width * height; -} - -void MaxResEngine::OnTimerEvent() { - AutoLock lock(status_lock_); - int64_t current_time = clock_->GetCurrentTime(); - if (!keys_.empty() && current_resolution_ != kNoResolution && - current_time >= next_check_time_) { - CryptoSession::HdcpCapability current_hdcp_level; - CryptoSession::HdcpCapability ignored; - if (!crypto_session_->GetHdcpCapabilities(¤t_hdcp_level, &ignored)) { - current_hdcp_level = HDCP_NONE; - } - for (KeyIterator i = keys_.begin(); i != keys_.end(); ++i) { - i->second->Update(current_resolution_, current_hdcp_level); - } - next_check_time_ = current_time + kHdcpCheckInterval; - } -} - -void MaxResEngine::DeleteAllKeys() { - // This helper method assumes that status_lock_ is already held. - for (KeyIterator i = keys_.begin(); i != keys_.end(); ++i) delete i->second; - keys_.clear(); -} - -MaxResEngine::KeyStatus::KeyStatus(const ConstraintList& constraints) - : default_hdcp_level_(HDCP_NONE) { - Init(constraints); -} - -MaxResEngine::KeyStatus::KeyStatus( - const ConstraintList& constraints, - const OutputProtection::HDCP& default_hdcp_level) - : default_hdcp_level_(ProtobufHdcpToOemCryptoHdcp(default_hdcp_level)) { - Init(constraints); -} - -void MaxResEngine::KeyStatus::Init(const ConstraintList& constraints) { - constraints_.Clear(); - constraints_.MergeFrom(constraints); - can_decrypt_ = true; -} - -void MaxResEngine::KeyStatus::Update( - uint32_t res, CryptoSession::HdcpCapability current_hdcp_level) { - VideoResolutionConstraint* current_constraint = GetConstraintForRes(res); - - if (current_constraint == NULL) { - can_decrypt_ = false; - return; - } - - CryptoSession::HdcpCapability desired_hdcp_level; - if (current_constraint->has_required_protection()) { - desired_hdcp_level = ProtobufHdcpToOemCryptoHdcp( - current_constraint->required_protection().hdcp()); - } else { - desired_hdcp_level = default_hdcp_level_; - } - can_decrypt_ = (current_hdcp_level >= desired_hdcp_level); -} - -MaxResEngine::VideoResolutionConstraint* -MaxResEngine::KeyStatus::GetConstraintForRes(uint32_t res) { - typedef ConstraintList::pointer_iterator Iterator; - for (Iterator i = constraints_.pointer_begin(); - i != constraints_.pointer_end(); ++i) { - VideoResolutionConstraint* constraint = *i; - if (constraint->has_min_resolution_pixels() && - constraint->has_max_resolution_pixels() && - res >= constraint->min_resolution_pixels() && - res <= constraint->max_resolution_pixels()) { - return constraint; - } - } - return NULL; -} - -CryptoSession::HdcpCapability -MaxResEngine::KeyStatus::ProtobufHdcpToOemCryptoHdcp( - const OutputProtection::HDCP& input) { - switch (input) { - case OutputProtection::HDCP_NONE: - return HDCP_NONE; - case OutputProtection::HDCP_V1: - return HDCP_V1; - case OutputProtection::HDCP_V2: - return HDCP_V2; - case OutputProtection::HDCP_V2_1: - return HDCP_V2_1; - case OutputProtection::HDCP_V2_2: - return HDCP_V2_2; - case OutputProtection::HDCP_NO_DIGITAL_OUTPUT: - return HDCP_NO_DIGITAL_OUTPUT; - default: - LOGE("MaxResEngine::KeyStatus::ProtobufHdcpToOemCryptoHdcp: " - "Unknown HDCP Level"); - return HDCP_NO_DIGITAL_OUTPUT; - } -} - -} // wvcdm diff --git a/core/src/oemcrypto_adapter_static.cpp b/core/src/oemcrypto_adapter_static.cpp index f26ae6c6..eba92969 100644 --- a/core/src/oemcrypto_adapter_static.cpp +++ b/core/src/oemcrypto_adapter_static.cpp @@ -38,6 +38,10 @@ uint32_t OEMCrypto_APIVersion(SecurityLevel level) { return ::OEMCrypto_APIVersion(); } +uint8_t OEMCrypto_Security_Patch_Level(SecurityLevel level) { + return ::OEMCrypto_Security_Patch_Level(); +} + const char* OEMCrypto_SecurityLevel(SecurityLevel level) { return ::OEMCrypto_SecurityLevel(); } diff --git a/core/src/oemcrypto_adapter_static_v11.cpp b/core/src/oemcrypto_adapter_static_v11.cpp new file mode 100644 index 00000000..9cad8a1e --- /dev/null +++ b/core/src/oemcrypto_adapter_static_v11.cpp @@ -0,0 +1,67 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Wrapper of OEMCrypto APIs for platforms that support Level 1 only. +// This should be used when liboemcrypto.so is linked with the CDM code at +// compile time. +// +// Defines APIs introduced in newer version (v11) which is not available in v10 +// to allow an older oemcrypto implementation to be linked with CDM. + +#include "OEMCryptoCENC.h" + +extern "C" OEMCryptoResult OEMCrypto_DecryptCTR_V10( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + bool is_encrypted, const uint8_t* iv, size_t block_offset, + const OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags); + +typedef struct { + const uint8_t* key_id; + size_t key_id_length; + const uint8_t* key_data_iv; + const uint8_t* key_data; + size_t key_data_length; + const uint8_t* key_control_iv; + const uint8_t* key_control; +} OEMCrypto_KeyObject_V9; + +extern "C" OEMCryptoResult OEMCrypto_LoadKeys_V9_or_V10( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint8_t* enc_mac_keys_iv, const uint8_t* enc_mac_keys, + size_t num_keys, const OEMCrypto_KeyObject_V9* key_array, + const uint8_t* pst, size_t pst_length); + +extern "C" uint8_t OEMCrypto_Security_Patch_Level() { return 0; } + +extern "C" OEMCryptoResult OEMCrypto_DecryptCENC( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + bool is_encrypted, const uint8_t* iv, size_t block_offset, + OEMCrypto_DestBufferDesc* out_buffer, + const OEMCrypto_CENCEncryptPatternDesc* pattern, uint8_t subsample_flags) { + return OEMCrypto_DecryptCTR_V10(session, data_addr, data_length, is_encrypted, + iv, block_offset, out_buffer, + subsample_flags); +} + +extern "C" OEMCryptoResult OEMCrypto_LoadKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint8_t* enc_mac_keys_iv, const uint8_t* enc_mac_keys, + size_t num_keys, const OEMCrypto_KeyObject* key_array, const uint8_t* pst, + size_t pst_length) { + OEMCrypto_KeyObject_V9 key_array_v9[num_keys]; + for (size_t key_index = 0; key_index < num_keys; key_index++) { + key_array_v9[key_index].key_id = key_array[key_index].key_id; + key_array_v9[key_index].key_id_length = key_array[key_index].key_id_length; + key_array_v9[key_index].key_data_iv = key_array[key_index].key_data_iv; + key_array_v9[key_index].key_data = key_array[key_index].key_data; + key_array_v9[key_index].key_data_length = + key_array[key_index].key_data_length; + key_array_v9[key_index].key_control_iv = + key_array[key_index].key_control_iv; + key_array_v9[key_index].key_control = key_array[key_index].key_control; + } + return OEMCrypto_LoadKeys_V9_or_V10( + session, message, message_length, signature, signature_length, + enc_mac_keys_iv, enc_mac_keys, num_keys, key_array_v9, pst, pst_length); +} diff --git a/core/src/policy_engine.cpp b/core/src/policy_engine.cpp index 98a803a3..6d885a21 100644 --- a/core/src/policy_engine.cpp +++ b/core/src/policy_engine.cpp @@ -3,10 +3,7 @@ #include "policy_engine.h" #include - #include -#include -#include #include "clock.h" #include "log.h" @@ -17,6 +14,13 @@ using video_widevine_server::sdk::License; +namespace { + +const int64_t kHdcpCheckInterval = 10; +const uint32_t kNoResolution = 0; + +} // namespace + namespace wvcdm { PolicyEngine::PolicyEngine(CdmSessionId session_id, @@ -32,18 +36,43 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id, policy_max_duration_seconds_(0), session_id_(session_id), event_listener_(event_listener), - max_res_engine_(new MaxResEngine(crypto_session)), - clock_(new Clock) {} + license_keys_(new LicenseKeys), + clock_(new Clock) { + InitDevice(crypto_session); +} PolicyEngine::~PolicyEngine() {} bool PolicyEngine::CanDecrypt(const KeyId& key_id) { - if (keys_status_.find(key_id) == keys_status_.end()) { + if (license_keys_->IsContentKey(key_id)) { + return license_keys_->CanDecryptContent(key_id); + } else { LOGE("PolicyEngine::CanDecrypt Key '%s' not in license.", b2a_hex(key_id).c_str()); return false; } - return keys_status_[key_id] == kKeyStatusUsable; +} + +void PolicyEngine::InitDevice(CryptoSession* crypto_session) { + current_resolution_ = kNoResolution; + next_device_check_ = 0; + crypto_session_ = crypto_session; +} + +void PolicyEngine::CheckDevice(int64_t current_time) { + if (current_time < next_device_check_) { + return; + } + + if (!license_keys_->Empty() && current_resolution_ != kNoResolution) { + CryptoSession::HdcpCapability current_hdcp_level; + CryptoSession::HdcpCapability ignored; + if (!crypto_session_->GetHdcpCapabilities(¤t_hdcp_level, &ignored)) { + current_hdcp_level = HDCP_NONE; + } + license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level); + next_device_check_ = current_time + kHdcpCheckInterval; + } } void PolicyEngine::OnTimerEvent() { @@ -57,7 +86,8 @@ void PolicyEngine::OnTimerEvent() { return; } - max_res_engine_->OnTimerEvent(); + // Check device conditions that affect playability (HDCP, resolution) + CheckDevice(current_time); bool renewal_needed = false; @@ -110,17 +140,8 @@ void PolicyEngine::SetLicense(const License& license) { license_id_.Clear(); license_id_.CopyFrom(license.id()); policy_.Clear(); - - // Extract content key ids. - keys_status_.clear(); - for (int key_index = 0; key_index < license.key_size(); ++key_index) { - const License::KeyContainer& key = license.key(key_index); - if (key.type() == License::KeyContainer::CONTENT && key.has_id()) - keys_status_[key.id()] = kKeyStatusInternalError; - } - + license_keys_->SetFromLicense(license); UpdateLicense(license); - max_res_engine_->SetLicense(license); } void PolicyEngine::SetLicenseForRelease(const License& license) { @@ -130,7 +151,6 @@ void PolicyEngine::SetLicenseForRelease(const License& license) { // Expire any old keys. NotifyKeysChange(kKeyStatusExpired); - UpdateLicense(license); } @@ -220,7 +240,7 @@ void PolicyEngine::DecryptionEvent() { } void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) { - max_res_engine_->SetResolution(width, height); + SetDeviceResolution(width, height); } void PolicyEngine::NotifySessionExpiration() { @@ -228,35 +248,46 @@ void PolicyEngine::NotifySessionExpiration() { NotifyKeysChange(kKeyStatusExpired); } -CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) { +CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) { std::stringstream ss; int64_t current_time = clock_->GetCurrentTime(); if (license_state_ == kLicenseStateInitial) { - key_info->clear(); + query_response->clear(); return NO_ERROR; } - (*key_info)[QUERY_KEY_LICENSE_TYPE] = + (*query_response)[QUERY_KEY_LICENSE_TYPE] = license_id_.type() == video_widevine_server::sdk::STREAMING ? QUERY_VALUE_STREAMING : QUERY_VALUE_OFFLINE; - (*key_info)[QUERY_KEY_PLAY_ALLOWED] = + (*query_response)[QUERY_KEY_PLAY_ALLOWED] = policy_.can_play() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; - (*key_info)[QUERY_KEY_PERSIST_ALLOWED] = + (*query_response)[QUERY_KEY_PERSIST_ALLOWED] = policy_.can_persist() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; - (*key_info)[QUERY_KEY_RENEW_ALLOWED] = + (*query_response)[QUERY_KEY_RENEW_ALLOWED] = policy_.can_renew() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; ss << GetLicenseDurationRemaining(current_time); - (*key_info)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str(); + (*query_response)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str(); ss.str(""); ss << GetPlaybackDurationRemaining(current_time); - (*key_info)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str(); - (*key_info)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url(); + (*query_response)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str(); + (*query_response)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url(); return NO_ERROR; } +CdmResponseType PolicyEngine::QueryKeyAllowedUsage( + const KeyId& key_id, CdmKeyAllowedUsage* key_usage) { + if (NULL == key_usage) { + return INVALID_PARAMETERS_ENG_12; + } + if (license_keys_->GetAllowedUsage(key_id, key_usage)) { + return NO_ERROR; + } + return KEY_NOT_FOUND_1; +} + bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) { if (playback_start_time_ == 0) return false; @@ -272,6 +303,12 @@ bool PolicyEngine::GetSecondsSinceLastPlayed( return (*seconds_since_last_played >= 0) ? true : false; } +int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() { + int64_t current_time = clock_->GetCurrentTime(); + return std::min(GetLicenseDurationRemaining(current_time), + GetPlaybackDurationRemaining(current_time)); +} + void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time, int64_t last_playback_time) { playback_start_time_ = (playback_start_time > 0) ? playback_start_time : 0; @@ -345,25 +382,14 @@ bool PolicyEngine::IsRenewalRetryIntervalExpired(int64_t current_time) { } void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) { - bool keys_changed = false; + bool keys_changed; bool has_new_usable_key = false; - for (std::map::iterator it = keys_status_.begin(); - it != keys_status_.end(); ++it) { - const KeyId key_id = it->first; - CdmKeyStatus& key_status = it->second; - CdmKeyStatus updated_status = new_status; - if (updated_status == kKeyStatusUsable) { - if (!max_res_engine_->CanDecrypt(key_id)) - updated_status = kKeyStatusOutputNotAllowed; - } - if (key_status != updated_status) { - key_status = updated_status; - if (updated_status == kKeyStatusUsable) has_new_usable_key = true; - keys_changed = true; - } - } - if (keys_changed && event_listener_) { - event_listener_->OnSessionKeysChange(session_id_, keys_status_, + keys_changed = license_keys_->ApplyStatusChange(new_status, + &has_new_usable_key); + if (event_listener_ && keys_changed) { + CdmKeyStatusMap content_keys; + license_keys_->ExtractKeyStatuses(&content_keys); + event_listener_->OnSessionKeysChange(session_id_, content_keys, has_new_usable_key); } } @@ -381,8 +407,4 @@ void PolicyEngine::NotifyExpirationUpdate() { void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); } -void PolicyEngine::set_max_res_engine(MaxResEngine* max_res_engine) { - max_res_engine_.reset(max_res_engine); -} - -} // wvcdm +} // namespace wvcdm diff --git a/core/src/string_conversions.cpp b/core/src/string_conversions.cpp index 39541ff6..4b825d2a 100644 --- a/core/src/string_conversions.cpp +++ b/core/src/string_conversions.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include "log.h" @@ -78,6 +79,26 @@ std::string b2a_hex(const std::string& byte) { byte.length()); } +// Encode for standard base64 encoding (RFC4648). +std::string Base64Encode(const std::vector& bin_input) { + if (bin_input.empty()) { + return std::string(); + } + + int in_size = bin_input.size(); + std::string b64_output(modp_b64_encode_len(in_size), 0); + + int out_size = modp_b64_encode( + &b64_output[0], reinterpret_cast(&bin_input[0]), in_size); + if (out_size == -1) { + LOGE("Base64Encode failed"); + return std::string(); + } + + b64_output.resize(out_size); + return b64_output; +} + // Filename-friendly base64 encoding (RFC4648), commonly referred to // as Base64WebSafeEncode. // @@ -111,6 +132,25 @@ std::string Base64SafeEncodeNoPad(const std::vector& bin_input) { return b64_output; } +// Decode for standard base64 encoding (RFC4648). +std::vector Base64Decode(const std::string& b64_input) { + if (b64_input.empty()) { + return std::vector(); + } + + int in_size = b64_input.size(); + std::vector bin_output(modp_b64_decode_len(in_size), 0); + int out_size = modp_b64_decode(reinterpret_cast(&bin_output[0]), + b64_input.data(), in_size); + if (out_size == -1) { + LOGE("Base64Decode failed"); + return std::vector(0); + } + + bin_output.resize(out_size); + return bin_output; +} + // Decode for Filename-friendly base64 encoding (RFC4648), commonly referred // as Base64WebSafeDecode. std::vector Base64SafeDecode(const std::string& b64_input) { @@ -157,18 +197,6 @@ std::string IntToString(int value) { return out_string; } -std::string UintToString(unsigned int value) { - // log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4. - // So round up to allocate 3 output characters per byte. - const int kOutputBufSize = 3 * sizeof(unsigned int); - char buffer[kOutputBufSize]; - memset(buffer, 0, kOutputBufSize); - snprintf(buffer, kOutputBufSize, "%u", value); - - std::string out_string(buffer); - return out_string; -} - int64_t htonll64(int64_t x) { // Convert to big endian (network-byte-order) union { uint32_t array[2]; diff --git a/core/test/base64_test.cpp b/core/test/base64_test.cpp index fed4e3b7..44cefac4 100644 --- a/core/test/base64_test.cpp +++ b/core/test/base64_test.cpp @@ -38,34 +38,55 @@ const std::string kTestData = const std::string kMultipleOf24BitsB64Data("R29vZCBkYXkh"); const std::string kOneByteOverB64Data("SGVsbG8gRnJpZW5kIQ=="); const std::string kTwoBytesOverB64Data("SGVsbG8gRnJpZW5kISE="); -const std::string kB64TestData = "GPFc9rc-INmI8FwtyTrUrv6xnKHWZNZ_5uaT21nFjNg="; +const std::string kB64TestData = "GPFc9rc+INmI8FwtyTrUrv6xnKHWZNZ/5uaT21nFjNg="; -const std::pair kBase64TestVectors[] = { - make_pair(&kNullString, &kNullString), - make_pair(&kf, &kfB64), - make_pair(&kfo, &kfoB64), - make_pair(&kfoo, &kfooB64), - make_pair(&kfoob, &kfoobB64), - make_pair(&kfooba, &kfoobaB64), - make_pair(&kfoobar, &kfoobarB64), - make_pair(&kMultipleOf24BitsData, &kMultipleOf24BitsB64Data), - make_pair(&kOneByteOverData, &kOneByteOverB64Data), - make_pair(&kTwoBytesOverData, &kTwoBytesOverB64Data), - make_pair(&kTestData, &kB64TestData)}; +const std::pair kBase64TestVectors[] = + {make_pair(&kNullString, &kNullString), + make_pair(&kf, &kfB64), + make_pair(&kfo, &kfoB64), + make_pair(&kfoo, &kfooB64), + make_pair(&kfoob, &kfoobB64), + make_pair(&kfooba, &kfoobaB64), + make_pair(&kfoobar, &kfoobarB64), + make_pair(&kMultipleOf24BitsData, &kMultipleOf24BitsB64Data), + make_pair(&kOneByteOverData, &kOneByteOverB64Data), + make_pair(&kTwoBytesOverData, &kTwoBytesOverB64Data), + make_pair(&kTestData, &kB64TestData)}; + +std::string ConvertToBase64WebSafe(const std::string &std_base64_string) { + std::string str(std_base64_string); + for (size_t i = 0; i < str.size(); ++i) { + if (str[i] == '+') + str[i] = '-'; + else if (str[i] == '/') + str[i] = '_'; + } + return str; +} } // namespace class Base64EncodeDecodeTest : public ::testing::TestWithParam< - std::pair > {}; + std::pair > {}; TEST_P(Base64EncodeDecodeTest, EncodeDecodeTest) { std::pair values = GetParam(); - std::vector decoded_vector = Base64SafeDecode(values.second->data()); + std::vector decoded_vector = Base64Decode(values.second->data()); + std::string decoded_string(decoded_vector.begin(), decoded_vector.end()); + EXPECT_STREQ(values.first->data(), decoded_string.data()); + std::string b64_string = Base64Encode(decoded_vector); + EXPECT_STREQ(values.second->data(), b64_string.data()); +} + +TEST_P(Base64EncodeDecodeTest, WebSafeEncodeDecodeTest) { + std::pair values = GetParam(); + std::string encoded_string = ConvertToBase64WebSafe(*(values.second)); + std::vector decoded_vector = Base64SafeDecode(encoded_string); std::string decoded_string(decoded_vector.begin(), decoded_vector.end()); EXPECT_STREQ(values.first->data(), decoded_string.data()); std::string b64_string = Base64SafeEncode(decoded_vector); - EXPECT_STREQ(values.second->data(), b64_string.data()); + EXPECT_STREQ(encoded_string.data(), b64_string.data()); } INSTANTIATE_TEST_CASE_P(ExecutesBase64Test, Base64EncodeDecodeTest, diff --git a/core/test/cdm_engine_test.cpp b/core/test/cdm_engine_test.cpp index 84e6e71b..aa44c262 100644 --- a/core/test/cdm_engine_test.cpp +++ b/core/test/cdm_engine_test.cpp @@ -4,6 +4,7 @@ // This is because we need a valid RSA certificate, and will attempt to connect // to the provisioning server to request one if we don't. +#include #include #include @@ -43,6 +44,8 @@ const std::string kWebmMimeType = "video/webm"; class WvCdmEngineTest : public testing::Test { public: + WvCdmEngineTest() : cdm_engine_(&file_system_) {} + static void SetUpTestCase() { ConfigTestEnv config(kContentProtectionUatServer); g_client_auth.assign(config.client_auth()); @@ -52,20 +55,16 @@ class WvCdmEngineTest : public testing::Test { g_key_id_pssh.assign(a2bs_hex(config.key_id())); // Extract the key ID from the PSSH box. - InitializationData extractor(CENC_INIT_DATA_FORMAT, - g_key_id_pssh); + InitializationData extractor(CENC_INIT_DATA_FORMAT, g_key_id_pssh); g_key_id_unwrapped = extractor.data(); } virtual void SetUp() { CdmResponseType status = - cdm_engine_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, - NULL /* forced_session_id */, &session_id_); + cdm_engine_.OpenSession(g_key_system, NULL, NULL, &session_id_); if (status == NEED_PROVISIONING) { Provision(); - status = cdm_engine_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, - NULL /* forced_session_id */, - &session_id_); + status = cdm_engine_.OpenSession(g_key_system, NULL, NULL, &session_id_); } ASSERT_EQ(NO_ERROR, status); ASSERT_NE("", session_id_) << "Could not open CDM session."; @@ -82,37 +81,43 @@ class WvCdmEngineTest : public testing::Test { std::string cert_authority; std::string cert, wrapped_key; ASSERT_EQ(NO_ERROR, cdm_engine_.GetProvisioningRequest( - cert_type, cert_authority, EMPTY_ORIGIN, - &prov_request, &provisioning_server_url)); + cert_type, cert_authority, &prov_request, + &provisioning_server_url)); UrlRequest url_request(provisioning_server_url); + EXPECT_TRUE(url_request.is_connected()); url_request.PostCertRequestInQueryString(prov_request); std::string message; bool ok = url_request.GetResponse(&message); EXPECT_TRUE(ok); - ASSERT_EQ(NO_ERROR, cdm_engine_.HandleProvisioningResponse(EMPTY_ORIGIN, - message, &cert, + ASSERT_EQ(NO_ERROR, cdm_engine_.HandleProvisioningResponse(message, &cert, &wrapped_key)); } void GenerateKeyRequest(const std::string& key_id, const std::string& init_data_type_string) { CdmAppParameterMap app_parameters; - std::string server_url; CdmKeySetId key_set_id; InitializationData init_data(init_data_type_string, key_id); - CdmKeyRequestType key_request_type; + CdmKeyRequest key_request; + EXPECT_EQ(KEY_MESSAGE, cdm_engine_.GenerateKeyRequest( session_id_, key_set_id, init_data, - kLicenseTypeStreaming, app_parameters, &key_msg_, - &key_request_type, &server_url, NULL)); - EXPECT_EQ(kKeyRequestTypeInitial, key_request_type); + kLicenseTypeStreaming, app_parameters, + &key_request)); + + key_msg_ = key_request.message; + EXPECT_EQ(kKeyRequestTypeInitial, key_request.type); } void GenerateRenewalRequest() { - EXPECT_EQ(KEY_MESSAGE, cdm_engine_.GenerateRenewalRequest( - session_id_, &key_msg_, &server_url_)); + CdmKeyRequest request; + EXPECT_EQ(KEY_MESSAGE, + cdm_engine_.GenerateRenewalRequest(session_id_, &request)); + + key_msg_ = request.message; + server_url_ = request.url; } std::string GetKeyRequestResponse(const std::string& server_url, @@ -171,6 +176,7 @@ class WvCdmEngineTest : public testing::Test { EXPECT_EQ(KEY_ADDED, cdm_engine_.RenewKey(session_id_, resp)); } + FileSystem file_system_; CdmEngine cdm_engine_; std::string key_msg_; std::string session_id_; diff --git a/core/test/cdm_session_unittest.cpp b/core/test/cdm_session_unittest.cpp index 682be2a4..0a7390b9 100644 --- a/core/test/cdm_session_unittest.cpp +++ b/core/test/cdm_session_unittest.cpp @@ -88,13 +88,12 @@ const std::string kWrappedKey = a2bs_hex( "E74C92B44F9205D22262FB47948654229DE1920F8EDF96A19A88A1CA1552F8856FB4CBF83B" "AA3348419159D207F65FCE9C1A500C6818"); -const std::string kTestOrigin = "com.google"; - class MockDeviceFiles : public DeviceFiles { public: + MockDeviceFiles() : DeviceFiles(NULL) {} + MOCK_METHOD1(Init, bool(CdmSecurityLevel)); - MOCK_METHOD3(RetrieveCertificate, bool(const std::string&, std::string*, - std::string*)); + MOCK_METHOD2(RetrieveCertificate, bool(std::string*, std::string*)); }; class MockCryptoSession : public CryptoSession { @@ -136,7 +135,7 @@ using ::testing::StrEq; class CdmSessionTest : public ::testing::Test { protected: virtual void SetUp() { - cdm_session_.reset(new CdmSession(NULL, kTestOrigin, NULL, NULL)); + cdm_session_.reset(new CdmSession(NULL)); // Inject testing mocks. license_parser_ = new MockCdmLicense(cdm_session_->session_id()); cdm_session_->set_license_parser(license_parser_); @@ -165,9 +164,8 @@ TEST_F(CdmSessionTest, InitWithCertificate) { .InSequence(crypto_session_seq) .WillOnce(Return(level)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, RetrieveCertificate(StrEq(kTestOrigin), NotNull(), - NotNull())) - .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), + EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), Return(true))); EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) .InSequence(crypto_session_seq) @@ -178,7 +176,7 @@ TEST_F(CdmSessionTest, InitWithCertificate) { Properties::set_use_certificates_as_identification(true); - ASSERT_EQ(NO_ERROR, cdm_session_->Init()); + ASSERT_EQ(NO_ERROR, cdm_session_->Init(NULL)); } TEST_F(CdmSessionTest, InitWithKeybox) { @@ -193,13 +191,14 @@ TEST_F(CdmSessionTest, InitWithKeybox) { EXPECT_CALL(*crypto_session_, GetToken(NotNull())) .InSequence(crypto_session_seq) .WillOnce(DoAll(SetArgPointee<0>(kToken), Return(true))); + EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*license_parser_, Init(Eq(kToken), Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); Properties::set_use_certificates_as_identification(false); - ASSERT_EQ(NO_ERROR, cdm_session_->Init()); + ASSERT_EQ(NO_ERROR, cdm_session_->Init(NULL)); } TEST_F(CdmSessionTest, ReInitFail) { @@ -212,9 +211,8 @@ TEST_F(CdmSessionTest, ReInitFail) { .InSequence(crypto_session_seq) .WillOnce(Return(level)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, RetrieveCertificate(StrEq(kTestOrigin), NotNull(), - NotNull())) - .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), + EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), Return(true))); EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) .InSequence(crypto_session_seq) @@ -225,8 +223,8 @@ TEST_F(CdmSessionTest, ReInitFail) { Properties::set_use_certificates_as_identification(true); - ASSERT_EQ(NO_ERROR, cdm_session_->Init()); - ASSERT_NE(NO_ERROR, cdm_session_->Init()); + ASSERT_EQ(NO_ERROR, cdm_session_->Init(NULL)); + ASSERT_NE(NO_ERROR, cdm_session_->Init(NULL)); } TEST_F(CdmSessionTest, InitFailCryptoError) { @@ -235,7 +233,7 @@ TEST_F(CdmSessionTest, InitFailCryptoError) { Properties::set_use_certificates_as_identification(true); - ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init()); + ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init(NULL)); } TEST_F(CdmSessionTest, InitNeedsProvisioning) { @@ -248,13 +246,12 @@ TEST_F(CdmSessionTest, InitNeedsProvisioning) { .InSequence(crypto_session_seq) .WillOnce(Return(level)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, RetrieveCertificate(StrEq(kTestOrigin), NotNull(), - NotNull())) + EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull())) .WillOnce(Return(false)); Properties::set_use_certificates_as_identification(true); - ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init()); + ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init(NULL)); } } // namespace wvcdm diff --git a/core/test/device_files_unittest.cpp b/core/test/device_files_unittest.cpp index d246e13f..dacf0882 100644 --- a/core/test/device_files_unittest.cpp +++ b/core/test/device_files_unittest.cpp @@ -1408,23 +1408,49 @@ UsageInfo kUsageInfoTestData[] = { "0E12202FF1FBA9926A24A1F79970EC427DDF87B4421488F7952499BC33CEB282D9E48" "A")}}; -const std::string kTestOrigin = "com.google"; +struct HlsAttributesInfo { + std::string key_set_id; + CdmHlsMethod method; + std::string media_segment_iv; + std::string file_data; +}; + +HlsAttributesInfo kHlsAttributesTestData[] = { + { + "ksidC8EAA2579A282EB0", kHlsMethodAes128, // hls attributes 0 + a2bs_hex("F7C4D15BD466BF285E241A4E58638543"), + a2bs_hex("0A1A08041001321408011210F7C4D15BD466BF285E241A4E5863854312201" + "39114B0372FF80FADF92614106E27BE8BD1588B4CAE6E1AEFB7F9C34EA52E" + "CC"), + }, + { + "ksidE8C37662C88DC673", kHlsMethodSampleAes, // hls attributes 1 + a2bs_hex("16413F038088438B5D4CD99F03EBB3D8"), + a2bs_hex("0A1A0804100132140802121016413F038088438B5D4CD99F03EBB3D812205" + "9EA13188B75C55D1EB78B3A65DB3EA3F43BD1B16642266D988E3543943C5F" + "41"), + }}; class MockFile : public File { public: - MOCK_METHOD2(Open, bool(const std::string&, int flags)); - MOCK_METHOD0(Close, void()); + MockFile() : File(NULL) {} + ~MockFile() {} MOCK_METHOD2(Read, ssize_t(char*, size_t)); MOCK_METHOD2(Write, ssize_t(const char*, size_t)); + MOCK_METHOD0(Close, void()); +}; + +class MockFileSystem : public FileSystem { + public: + MockFileSystem() {} + ~MockFileSystem() {} + + MOCK_METHOD2(Open, File*(const std::string&, int flags)); + MOCK_METHOD0(IsFactoryReset, bool()); MOCK_METHOD1(Exists, bool(const std::string&)); MOCK_METHOD1(Remove, bool(const std::string&)); - MOCK_METHOD2(Copy, bool(const std::string&, const std::string&)); - MOCK_METHOD2(List, bool(const std::string&, std::vector*)); - MOCK_METHOD1(CreateDirectory, bool(const std::string)); - MOCK_METHOD1(IsDirectory, bool(const std::string&)); - MOCK_METHOD1(IsRegularFile, bool(const std::string&)); MOCK_METHOD1(FileSize, ssize_t(const std::string&)); }; @@ -1490,21 +1516,9 @@ class DeviceFilesTest : public ::testing::Test { class DeviceFilesStoreTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; -struct CertStorageVariant { - CertStorageVariant(bool dir_exists_value, const std::string& origin_value) - : dir_exists(dir_exists_value), origin(origin_value) {} +class DeviceCertificateStoreTest : public DeviceFilesTest {}; - const bool dir_exists; - const std::string origin; -}; - -class DeviceCertificateStoreTest - : public DeviceFilesTest, - public ::testing::WithParamInterface {}; - -class DeviceCertificateTest - : public DeviceFilesTest, - public ::testing::WithParamInterface {}; +class DeviceCertificateTest : public DeviceFilesTest {}; class DeviceFilesSecurityLevelTest : public DeviceFilesTest, @@ -1513,13 +1527,22 @@ class DeviceFilesSecurityLevelTest class DeviceFilesUsageInfoTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; -MATCHER(IsCreateFileFlagSet, "") { return File::kCreate & arg; } -MATCHER(IsBinaryFileFlagSet, "") { return File::kBinary & arg; } +class DeviceFilesHlsAttributesTest + : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + +MATCHER(IsCreateFileFlagSet, "") { return FileSystem::kCreate & arg; } MATCHER_P(IsStrEq, str, "") { // Estimating the length of data. We can have gmock provide length // as well as pointer to data but that will introduce a dependency on tr1 return memcmp(arg, str.c_str(), str.size()) == 0; } +MATCHER_P2(Contains, str1, size, "") { + // Estimating the length of data. We can have gmock provide length + // as well as pointer to data but that will introduce a dependency on tr1 + std::string data(arg, size + str1.size() + kProtobufEstimatedOverhead); + return (data.find(str1) != std::string::npos); +} MATCHER_P3(Contains, str1, str2, size, "") { // Estimating the length of data. We can have gmock provide length // as well as pointer to data but that will introduce a dependency on tr1 @@ -1586,105 +1609,78 @@ MATCHER_P7(Contains, str1, str2, str3, str4, str5, str6, map7, "") { data.find(str6) != std::string::npos && map7_entries_present); } -TEST_P(DeviceCertificateStoreTest, StoreCertificate) { - MockFile file; - CertStorageVariant params = GetParam(); +TEST_F(DeviceCertificateStoreTest, StoreCertificate) { + MockFileSystem file_system; std::string certificate(GenerateRandomData(kCertificateLen)); std::string wrapped_private_key(GenerateRandomData(kWrappedKeyLen)); std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(params.origin); + device_base_path_ + DeviceFiles::GetCertificateFileName(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillOnce(Return(params.dir_exists)); - if (params.dir_exists) { - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - } else { - EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path_))) - .WillOnce(Return(true)); - } - - EXPECT_CALL(file, Open(StrEq(device_certificate_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .WillOnce(Return(true)); + MockFile file; + EXPECT_CALL(file_system, + Open(StrEq(device_certificate_path), IsCreateFileFlagSet())) + .WillOnce(Return(&file)); EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0), Gt(certificate.size() + wrapped_private_key.size()))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); - EXPECT_TRUE(device_files.StoreCertificate(params.origin, certificate, - wrapped_private_key)); + EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); } -INSTANTIATE_TEST_CASE_P( - StoreCertificate, DeviceCertificateStoreTest, - ::testing::Values(CertStorageVariant(true, EMPTY_ORIGIN), - CertStorageVariant(true, kTestOrigin), - CertStorageVariant(false, EMPTY_ORIGIN), - CertStorageVariant(false, kTestOrigin))); - -TEST_P(DeviceCertificateTest, ReadCertificate) { - MockFile file; - std::string origin = GetParam(); +TEST_F(DeviceCertificateTest, ReadCertificate) { + MockFileSystem file_system; std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(origin); + device_base_path_ + DeviceFiles::GetCertificateFileName(); std::string data = a2bs_hex(kTestCertificateFileData); - EXPECT_CALL(file, Exists(StrEq(device_certificate_path))) + MockFile file; + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) .WillOnce(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(device_certificate_path))) + EXPECT_CALL(file_system, FileSize(StrEq(device_certificate_path))) .WillOnce(Return(data.size())); - EXPECT_CALL(file, Open(StrEq(device_certificate_path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(device_certificate_path), _)) + .WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Write(_, _)).Times(0); - if (Properties::security_level_path_backward_compatibility_support()) { - EXPECT_CALL(file, List(_, NotNull())).WillOnce(Return(false)); - } - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); std::string certificate, wrapped_private_key; - ASSERT_TRUE(device_files.RetrieveCertificate(origin, &certificate, - &wrapped_private_key)); + ASSERT_TRUE( + device_files.RetrieveCertificate(&certificate, &wrapped_private_key)); EXPECT_EQ(kTestCertificate, b2a_hex(certificate)); EXPECT_EQ(kTestWrappedPrivateKey, b2a_hex(wrapped_private_key)); } -TEST_P(DeviceCertificateTest, HasCertificate) { - MockFile file; - std::string origin = GetParam(); +TEST_F(DeviceCertificateTest, HasCertificate) { + MockFileSystem file_system; std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(origin); + device_base_path_ + DeviceFiles::GetCertificateFileName(); - EXPECT_CALL(file, Exists(StrEq(device_certificate_path))) + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) .WillOnce(Return(false)) .WillOnce(Return(true)); - EXPECT_CALL(file, Open(_, _)).Times(0); + EXPECT_CALL(file_system, Open(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); // MockFile returns false. - EXPECT_FALSE(device_files.HasCertificate(origin)); + EXPECT_FALSE(device_files.HasCertificate()); // MockFile returns true. - EXPECT_TRUE(device_files.HasCertificate(origin)); + EXPECT_TRUE(device_files.HasCertificate()); } -INSTANTIATE_TEST_CASE_P(CertificateUseTests, DeviceCertificateTest, - ::testing::Values(EMPTY_ORIGIN, kTestOrigin)); - TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) { - MockFile file; + MockFileSystem file_system; std::string certificate(GenerateRandomData(kCertificateLen)); std::string wrapped_private_key(GenerateRandomData(kWrappedKeyLen)); @@ -1693,55 +1689,39 @@ TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) { ASSERT_TRUE( Properties::GetDeviceFilesBasePath(security_level, &device_base_path)); std::string device_certificate_path = - device_base_path + DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN); + device_base_path + DeviceFiles::GetCertificateFileName(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path))) - .WillOnce(Return(false)); - EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path))) - .WillOnce(Return(true)); - - EXPECT_CALL(file, Open(StrEq(device_certificate_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .WillOnce(Return(true)); + MockFile file; + EXPECT_CALL(file_system, + Open(StrEq(device_certificate_path), IsCreateFileFlagSet())) + .WillOnce(Return(&file)); EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0), Gt(certificate.size() + wrapped_private_key.size()))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(security_level)); - device_files.SetTestFile(&file); - EXPECT_TRUE(device_files.StoreCertificate(EMPTY_ORIGIN, certificate, - wrapped_private_key)); + EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); } INSTANTIATE_TEST_CASE_P(SecurityLevel, DeviceFilesSecurityLevelTest, ::testing::Values(kSecurityLevelL1, kSecurityLevelL3)); TEST_P(DeviceFilesStoreTest, StoreLicense) { - MockFile file; + MockFileSystem file_system; size_t license_num = 0; std::string license_path = device_base_path_ + license_test_data[license_num].key_set_id + DeviceFiles::GetLicenseFileNameExtension(); - bool dir_exists = GetParam(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillOnce(Return(dir_exists)); - if (dir_exists) { - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - } else { - EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path_))) - .WillOnce(Return(true)); - } - CdmAppParameterMap app_parameters = GetAppParameters(license_test_data[license_num].app_parameters); - EXPECT_CALL(file, Open(StrEq(license_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .WillOnce(Return(true)); + MockFile file; + EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) + .WillOnce(Return(&file)); EXPECT_CALL( file, Write(Contains(license_test_data[license_num].pssh_data, license_test_data[license_num].key_request, @@ -1755,9 +1735,8 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); EXPECT_TRUE(device_files.StoreLicense( license_test_data[license_num].key_set_id, license_test_data[license_num].license_state, @@ -1774,11 +1753,8 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { INSTANTIATE_TEST_CASE_P(StoreLicense, DeviceFilesStoreTest, ::testing::Bool()); TEST_F(DeviceFilesTest, StoreLicenses) { + MockFileSystem file_system; MockFile file; - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .Times(kNumberOfLicenses) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); for (size_t i = 0; i < kNumberOfLicenses; ++i) { std::string license_path = device_base_path_ + @@ -1788,9 +1764,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) { CdmAppParameterMap app_parameters = GetAppParameters(license_test_data[i].app_parameters); - EXPECT_CALL(file, Open(StrEq(license_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) + .WillOnce(Return(&file)); EXPECT_CALL(file, Write(Contains(license_test_data[i].pssh_data, license_test_data[i].key_request, @@ -1805,9 +1780,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) { EXPECT_CALL(file, Close()).Times(kNumberOfLicenses); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); for (size_t i = 0; i < kNumberOfLicenses; i++) { CdmAppParameterMap app_parameters = GetAppParameters(license_test_data[i].app_parameters); @@ -1825,6 +1799,7 @@ TEST_F(DeviceFilesTest, StoreLicenses) { } TEST_F(DeviceFilesTest, RetrieveLicenses) { + MockFileSystem file_system; MockFile file; for (size_t i = 0; i < kNumberOfLicenses; ++i) { @@ -1834,10 +1809,12 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { size_t size = license_test_data[i].file_data.size(); - EXPECT_CALL(file, Exists(StrEq(license_path))).WillOnce(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); - EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) + EXPECT_CALL(file_system, Exists(StrEq(license_path))) .WillOnce(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(license_path))) + .WillOnce(Return(size)); + EXPECT_CALL(file_system, Open(StrEq(license_path), _)) + .WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(size))) .WillOnce( DoAll(SetArrayArgument<0>(license_test_data[i].file_data.begin(), @@ -1847,9 +1824,8 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { EXPECT_CALL(file, Close()).Times(kNumberOfLicenses); EXPECT_CALL(file, Write(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); CdmInitData pssh_data; CdmKeyMessage key_request; CdmKeyResponse key_response; @@ -1886,7 +1862,7 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { } TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { - MockFile file; + MockFileSystem file_system; LicenseInfo* test_data = &license_app_parameters_backwards_compatibility_test_data; @@ -1895,10 +1871,12 @@ TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { size_t size = test_data->file_data.size(); - EXPECT_CALL(file, Exists(StrEq(license_path))).WillOnce(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); - EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + MockFile file; + EXPECT_CALL(file_system, Exists(StrEq(license_path))).WillOnce(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(license_path))) + .WillOnce(Return(size)); + EXPECT_CALL(file_system, Open(StrEq(license_path), _)) + .WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(size))) .WillOnce(DoAll(SetArrayArgument<0>(test_data->file_data.begin(), test_data->file_data.end()), @@ -1907,9 +1885,8 @@ TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Write(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); DeviceFiles::LicenseState license_state; CdmInitData pssh_data; CdmKeyMessage key_request; @@ -1936,102 +1913,16 @@ TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { EXPECT_EQ(0u, app_parameters.size()); } -TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) { - if (!Properties::security_level_path_backward_compatibility_support()) { - return; - } - - MockFile file; - std::vector security_dirs; - EXPECT_TRUE(Properties::GetSecurityLevelDirectories(&security_dirs)); - - size_t pos = std::string::npos; - for (size_t i = 0; i < security_dirs.size(); ++i) { - pos = device_base_path_.rfind(security_dirs[i]); - if (std::string::npos != pos) break; - } - - EXPECT_NE(std::string::npos, pos); - - std::string base_path(device_base_path_, 0, pos); - std::vector old_files; - std::string new_path; - for (size_t i = 0; i < security_dirs.size(); ++i) { - old_files.push_back(security_dirs[i]); - new_path = base_path + security_dirs[i]; - EXPECT_CALL(file, IsRegularFile(StrEq(new_path))).WillOnce(Return(false)); - EXPECT_CALL(file, Exists(StrEq(new_path))) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(StrEq(new_path))).WillOnce(Return(true)); - } - - std::string old_path = - base_path + DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN); - old_files.push_back(DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN)); - EXPECT_CALL(file, IsRegularFile(StrEq(old_path))).WillOnce(Return(true)); - EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true)); - for (size_t i = 0; i < security_dirs.size(); ++i) { - new_path = base_path + security_dirs[i] + - DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN); - EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path))) - .WillOnce(Return(true)); - } - - for (size_t j = 0; j < kNumberOfLicenses; ++j) { - std::string file_name = license_test_data[j].key_set_id + - DeviceFiles::GetLicenseFileNameExtension(); - old_path = base_path + file_name; - old_files.push_back(file_name); - EXPECT_CALL(file, IsRegularFile(StrEq(old_path))).WillOnce(Return(true)); - EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true)); - for (size_t i = 0; i < security_dirs.size(); ++i) { - new_path = base_path + security_dirs[i] + file_name; - EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path))) - .WillOnce(Return(true)); - } - } - - EXPECT_CALL(file, List(StrEq(base_path), NotNull())) - .WillOnce(DoAll(SetArgPointee<1>(old_files), Return(true))); - - std::string data = a2bs_hex(kTestCertificateFileData); - - new_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN); - EXPECT_CALL(file, Exists(StrEq(new_path))).WillOnce(Return(true)); - EXPECT_CALL(file, FileSize(_)).WillOnce(Return(data.size())); - EXPECT_CALL(file, Open(_, _)).WillOnce(Return(true)); - EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) - .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), - Return(data.size()))); - EXPECT_CALL(file, Close()).Times(1); - EXPECT_CALL(file, Write(_, _)).Times(0); - - DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); - - Properties::Init(); - std::string certificate, wrapped_private_key; - ASSERT_TRUE(device_files.RetrieveCertificate(EMPTY_ORIGIN, &certificate, - &wrapped_private_key)); -} - TEST_F(DeviceFilesTest, UpdateLicenseState) { - MockFile file; + MockFileSystem file_system; std::string license_path = device_base_path_ + license_update_test_data[0].key_set_id + DeviceFiles::GetLicenseFileNameExtension(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) + MockFile file; + EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) .Times(2) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - EXPECT_CALL(file, Open(StrEq(license_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .Times(2) - .WillRepeatedly(Return(true)); + .WillRepeatedly(Return(&file)); EXPECT_CALL(file, Write(IsStrEq(license_update_test_data[0].file_data), Eq(license_update_test_data[0].file_data.size()))) .WillOnce(ReturnArg<1>()); @@ -2041,9 +1932,8 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) { EXPECT_CALL(file, Close()).Times(2); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); EXPECT_TRUE(device_files.StoreLicense( license_update_test_data[0].key_set_id, license_update_test_data[0].license_state, @@ -2072,32 +1962,33 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) { } TEST_F(DeviceFilesTest, DeleteLicense) { - MockFile file; + MockFileSystem file_system; std::string license_path = device_base_path_ + license_test_data[0].key_set_id + DeviceFiles::GetLicenseFileNameExtension(); size_t size = license_test_data[0].file_data.size(); - EXPECT_CALL(file, Exists(StrEq(license_path))) + MockFile file; + EXPECT_CALL(file_system, Exists(StrEq(license_path))) .Times(2) .WillOnce(Return(true)) .WillOnce(Return(false)); - EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); - EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(license_path))) + .WillOnce(Return(size)); + EXPECT_CALL(file_system, Open(StrEq(license_path), _)) + .WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(size))) .WillOnce( DoAll(SetArrayArgument<0>(license_test_data[0].file_data.begin(), license_test_data[0].file_data.end()), Return(size))); - EXPECT_CALL(file, Remove(StrEq(license_path))).WillOnce(Return(true)); + EXPECT_CALL(file_system, Remove(StrEq(license_path))).WillOnce(Return(true)); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Write(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); DeviceFiles::LicenseState license_state; CdmInitData pssh_data; CdmKeyMessage key_request; @@ -2135,17 +2026,11 @@ TEST_F(DeviceFilesTest, DeleteLicense) { TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { // Validate that ReserveLicenseIds does not touch the file system. - MockFile file; - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))).Times(0); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - EXPECT_CALL(file, Open(_, _)).Times(0); - EXPECT_CALL(file, Write(_, _)).Times(0); - EXPECT_CALL(file, Close()).Times(0); - EXPECT_CALL(file, Read(_, _)).Times(0); + MockFileSystem file_system; + EXPECT_CALL(file_system, Open(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); for (size_t i = 0; i < kNumberOfLicenses; i++) { EXPECT_TRUE(device_files.ReserveLicenseId(license_test_data[i].key_set_id)); // Validate that the license IDs are actually reserved. @@ -2156,6 +2041,7 @@ TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { } TEST_P(DeviceFilesUsageInfoTest, Read) { + MockFileSystem file_system; MockFile file; std::string app_id; // TODO(fredgc): add tests with multiple app_ids. std::string path = @@ -2167,28 +2053,24 @@ TEST_P(DeviceFilesUsageInfoTest, Read) { data = kUsageInfoTestData[index].file_data; } if (index >= 0) { - EXPECT_CALL(file, Exists(StrEq(path))).WillRepeatedly(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(path))) + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) .WillRepeatedly(Return(data.size())); - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); EXPECT_CALL(file, Close()).Times(1); } else { - EXPECT_CALL(file, Exists(StrEq(path))) - .WillRepeatedly(Return(false)); - EXPECT_CALL(file, FileSize(_)).Times(0); - EXPECT_CALL(file, Open(_, _)).Times(0); - EXPECT_CALL(file, Close()).Times(0); + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(false)); + EXPECT_CALL(file_system, FileSize(_)).Times(0); + EXPECT_CALL(file_system, Open(_, _)).Times(0); } EXPECT_CALL(file, Write(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); std::vector > license_info; ASSERT_TRUE(device_files.RetrieveUsageInfo(app_id, &license_info)); @@ -2212,7 +2094,7 @@ TEST_P(DeviceFilesUsageInfoTest, Read) { } TEST_P(DeviceFilesUsageInfoTest, Store) { - MockFile file; + MockFileSystem file_system; std::string app_id; // TODO(fredgc): multiple app ids. std::string pst(GenerateRandomData(kProviderSessionTokenLen)); std::string license_request(GenerateRandomData(kLicenseRequestLen)); @@ -2226,24 +2108,24 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { if (index >= 0) { data = kUsageInfoTestData[index].file_data; } - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - EXPECT_CALL(file, Exists(StrEq(path))).WillRepeatedly(Return(index >= 0)); + MockFile file; + EXPECT_CALL(file_system, Exists(StrEq(path))) + .WillRepeatedly(Return(index >= 0)); if (index >= 0) { - EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size())); - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(path), _)) .Times(2) - .WillRepeatedly(Return(true)); + .WillRepeatedly(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); EXPECT_CALL(file, Close()).Times(2); } else { - EXPECT_CALL(file, FileSize(_)).Times(0); - EXPECT_CALL(file, Open(_, _)).Times(1).WillOnce(Return(true)); - EXPECT_CALL(file, Close()).Times(1); + EXPECT_CALL(file_system, FileSize(_)).Times(0); + EXPECT_CALL(file_system, Open(_, _)).Times(1).WillOnce(Return(&file)); + EXPECT_CALL(file, Close()); } EXPECT_CALL(file, @@ -2253,9 +2135,8 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { key_set_id.size()))) .WillOnce(ReturnArg<1>()); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); ASSERT_TRUE( device_files.StoreUsageInfo(pst, license_request, license, app_id, @@ -2263,6 +2144,7 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { } TEST_P(DeviceFilesUsageInfoTest, Delete) { + MockFileSystem file_system; MockFile file; std::string app_id; // TODO(fredgc): expand tests. std::string path = @@ -2282,24 +2164,19 @@ TEST_P(DeviceFilesUsageInfoTest, Delete) { } } - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); + EXPECT_CALL(file_system, Exists(StrEq(path))).WillOnce(Return(index >= 0)); - EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(index >= 0)); - - EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size())); + EXPECT_CALL(file_system, FileSize(StrEq(path))).WillOnce(Return(data.size())); if (index >= 1) { - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) + EXPECT_CALL(file_system, Open(StrEq(path), _)) .Times(2) - .WillRepeatedly(Return(true)); + .WillRepeatedly(Return(&file)); EXPECT_CALL(file, Write(Contains(prev_pst, prev_license, prev_data.size()), Gt(prev_pst.size() + prev_license.size()))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(file, Close()).Times(2); } else { - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); EXPECT_CALL(file, Write(_, _)).Times(0); EXPECT_CALL(file, Close()).Times(1); } @@ -2307,9 +2184,8 @@ TEST_P(DeviceFilesUsageInfoTest, Delete) { .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); if (index >= 1) { ASSERT_TRUE(device_files.DeleteUsageInfo(app_id, pst)); @@ -2319,36 +2195,33 @@ TEST_P(DeviceFilesUsageInfoTest, Delete) { } TEST_P(DeviceFilesUsageInfoTest, DeleteAll) { + MockFileSystem file_system; MockFile file; std::string app_id; // TODO(fredgc): expand tests. std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName(app_id); int index = GetParam(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); EXPECT_CALL(file, Write(_, _)).Times(0); std::string data; if (index < 0) { - EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(false)); + EXPECT_CALL(file_system, Exists(StrEq(path))).WillOnce(Return(false)); } else { data = kUsageInfoTestData[index].file_data; - EXPECT_CALL(file, Exists(StrEq(path))).WillRepeatedly(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size())); - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); EXPECT_CALL(file, Close()).Times(1); - EXPECT_CALL(file, Remove(StrEq(path))).WillOnce(Return(true)); + EXPECT_CALL(file_system, Remove(StrEq(path))).WillOnce(Return(true)); } - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); std::vector psts; ASSERT_TRUE(device_files.DeleteAllUsageInfoForApp(app_id, &psts)); @@ -2367,4 +2240,75 @@ TEST_P(DeviceFilesUsageInfoTest, DeleteAll) { INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoTest, ::testing::Range(-1, 4)); +TEST_P(DeviceFilesHlsAttributesTest, Read) { + MockFileSystem file_system; + MockFile file; + HlsAttributesInfo* param = GetParam(); + std::string path = device_base_path_ + param->key_set_id + + DeviceFiles::GetHlsAttributesFileNameExtension(); + + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .WillRepeatedly(Return(param->file_data.size())); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); + EXPECT_CALL(file, Read(NotNull(), Eq(param->file_data.size()))) + .WillOnce(DoAll( + SetArrayArgument<0>(param->file_data.begin(), param->file_data.end()), + Return(param->file_data.size()))); + EXPECT_CALL(file, Close()).Times(1); + + EXPECT_CALL(file, Write(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + CdmHlsMethod method; + std::vector media_segment_iv; + ASSERT_TRUE(device_files.RetrieveHlsAttributes(param->key_set_id, &method, + &media_segment_iv)); + EXPECT_EQ(param->method, method); + EXPECT_EQ(b2a_hex(param->media_segment_iv), b2a_hex(media_segment_iv)); +} + +TEST_P(DeviceFilesHlsAttributesTest, Store) { + MockFileSystem file_system; + MockFile file; + HlsAttributesInfo* param = GetParam(); + std::string path = device_base_path_ + param->key_set_id + + DeviceFiles::GetHlsAttributesFileNameExtension(); + + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); + EXPECT_CALL(file, Write(Contains(param->media_segment_iv, 0), + Gt(param->media_segment_iv.size()))) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(file, Read(_, _)).Times(0); + EXPECT_CALL(file, Close()).Times(1); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + std::vector iv(param->media_segment_iv.begin(), + param->media_segment_iv.end()); + ASSERT_TRUE( + device_files.StoreHlsAttributes(param->key_set_id, param->method, iv)); +} + +TEST_P(DeviceFilesHlsAttributesTest, Delete) { + MockFileSystem file_system; + MockFile file; + HlsAttributesInfo* param = GetParam(); + std::string path = device_base_path_ + param->key_set_id + + DeviceFiles::GetHlsAttributesFileNameExtension(); + + EXPECT_CALL(file_system, Remove(StrEq(path))).WillOnce(Return(true)); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + ASSERT_TRUE(device_files.DeleteHlsAttributes(param->key_set_id)); +} + +INSTANTIATE_TEST_CASE_P(HlsAttributes, DeviceFilesHlsAttributesTest, + ::testing::Range(&kHlsAttributesTestData[0], + &kHlsAttributesTestData[2])); + } // namespace wvcdm diff --git a/core/test/file_store_unittest.cpp b/core/test/file_store_unittest.cpp index 3eee8251..48bfcd17 100644 --- a/core/test/file_store_unittest.cpp +++ b/core/test/file_store_unittest.cpp @@ -1,9 +1,8 @@ // Copyright 2013 Google Inc. All Rights Reserved. #include -#include "device_files.h" + #include "file_store.h" -#include "properties.h" #include "test_vectors.h" namespace wvcdm { @@ -18,20 +17,12 @@ const std::string kWildcard = "*"; class FileTest : public testing::Test { protected: - virtual void SetUp() { CreateTestDir(); } + FileTest() {} + virtual void TearDown() { RemoveTestDir(); } - void CreateTestDir() { - File file; - if (!file.Exists(test_vectors::kTestDir)) { - EXPECT_TRUE(file.CreateDirectory(test_vectors::kTestDir)); - } - EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); - } - void RemoveTestDir() { - File file; - EXPECT_TRUE(file.Remove(test_vectors::kTestDir)); + EXPECT_TRUE(file_system.Remove(test_vectors::kTestDir)); } std::string GenerateRandomData(uint32_t len) { @@ -41,64 +32,52 @@ class FileTest : public testing::Test { } return data; } + + FileSystem file_system; }; TEST_F(FileTest, FileExists) { - File file; - EXPECT_TRUE(file.Exists(test_vectors::kExistentFile)); - EXPECT_TRUE(file.Exists(test_vectors::kExistentDir)); - EXPECT_FALSE(file.Exists(test_vectors::kNonExistentFile)); - EXPECT_FALSE(file.Exists(test_vectors::kNonExistentDir)); -} - -TEST_F(FileTest, CreateDirectory) { - File file; - std::string dir_wo_delimiter = - test_vectors::kTestDir.substr(0, test_vectors::kTestDir.size() - 1); - if (file.Exists(dir_wo_delimiter)) EXPECT_TRUE(file.Remove(dir_wo_delimiter)); - EXPECT_FALSE(file.Exists(dir_wo_delimiter)); - EXPECT_TRUE(file.CreateDirectory(dir_wo_delimiter)); - EXPECT_TRUE(file.Exists(dir_wo_delimiter)); - EXPECT_TRUE(file.Remove(dir_wo_delimiter)); - EXPECT_TRUE(file.CreateDirectory(test_vectors::kTestDir)); - EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); - EXPECT_TRUE(file.Remove(test_vectors::kTestDir)); + EXPECT_TRUE(file_system.Exists(test_vectors::kExistentFile)); + EXPECT_TRUE(file_system.Exists(test_vectors::kExistentDir)); + EXPECT_FALSE(file_system.Exists(test_vectors::kNonExistentFile)); + EXPECT_FALSE(file_system.Exists(test_vectors::kNonExistentDir)); } TEST_F(FileTest, RemoveDir) { - File file; - EXPECT_TRUE(file.Remove(test_vectors::kTestDir)); - EXPECT_FALSE(file.Exists(test_vectors::kTestDir)); + EXPECT_TRUE(file_system.Remove(test_vectors::kTestDir)); + EXPECT_FALSE(file_system.Exists(test_vectors::kTestDir)); } TEST_F(FileTest, OpenFile) { std::string path = test_vectors::kTestDir + kTestFileName; - File handle; - EXPECT_TRUE(handle.Remove(path)); + EXPECT_TRUE(file_system.Remove(path)); - File file; - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); + File* file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); - EXPECT_TRUE(handle.Exists(path)); + EXPECT_TRUE(file_system.Exists(path)); } TEST_F(FileTest, RemoveDirAndFile) { std::string path = test_vectors::kTestDir + kTestFileName; - File file; - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); - EXPECT_TRUE(file.Exists(path)); - EXPECT_TRUE(file.Remove(path)); - EXPECT_FALSE(file.Exists(path)); + File* file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); - EXPECT_TRUE(file.Exists(path)); + EXPECT_TRUE(file_system.Exists(path)); + EXPECT_TRUE(file_system.Remove(path)); + EXPECT_FALSE(file_system.Exists(path)); + + file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); + + EXPECT_TRUE(file_system.Exists(path)); RemoveTestDir(); - EXPECT_FALSE(file.Exists(test_vectors::kTestDir)); - EXPECT_FALSE(file.Exists(path)); + EXPECT_FALSE(file_system.Exists(test_vectors::kTestDir)); + EXPECT_FALSE(file_system.Exists(path)); } TEST_F(FileTest, RemoveWildcardFiles) { @@ -107,159 +86,53 @@ TEST_F(FileTest, RemoveWildcardFiles) { std::string wildcard_path = test_vectors::kTestDir + kWildcard + kTestFileNameExt; - File file; - EXPECT_TRUE(file.Open(path1, File::kCreate)); - file.Close(); - EXPECT_TRUE(file.Open(path2, File::kCreate)); - file.Close(); - EXPECT_TRUE(file.Exists(path1)); - EXPECT_TRUE(file.Exists(path2)); - EXPECT_TRUE(file.Remove(wildcard_path)); - EXPECT_FALSE(file.Exists(path1)); - EXPECT_FALSE(file.Exists(path2)); -} + File* file = file_system.Open(path1, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); + file = file_system.Open(path2, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); -TEST_F(FileTest, IsDir) { - std::string path = test_vectors::kTestDir + kTestFileName; - File file; - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); - - EXPECT_TRUE(file.Exists(path)); - EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); - EXPECT_FALSE(file.IsDirectory(path)); - EXPECT_TRUE(file.IsDirectory(test_vectors::kTestDir)); -} - -TEST_F(FileTest, IsRegularFile) { - std::string path = test_vectors::kTestDir + kTestFileName; - File file; - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); - - EXPECT_TRUE(file.Exists(path)); - EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); - EXPECT_TRUE(file.IsRegularFile(path)); - EXPECT_FALSE(file.IsRegularFile(test_vectors::kTestDir)); + EXPECT_TRUE(file_system.Exists(path1)); + EXPECT_TRUE(file_system.Exists(path2)); + EXPECT_TRUE(file_system.Remove(wildcard_path)); + EXPECT_FALSE(file_system.Exists(path1)); + EXPECT_FALSE(file_system.Exists(path2)); } TEST_F(FileTest, FileSize) { std::string path = test_vectors::kTestDir + kTestFileName; - File file; - file.Remove(path); + file_system.Remove(path); std::string write_data = GenerateRandomData(600); - File wr_file; - EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); - wr_file.Close(); - EXPECT_TRUE(file.Exists(path)); + File* file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + EXPECT_TRUE(file->Write(write_data.data(), write_data.size())); + file->Close(); + EXPECT_TRUE(file_system.Exists(path)); - EXPECT_EQ(static_cast(write_data.size()), file.FileSize(path)); -} - -TEST_F(FileTest, WriteReadTextFile) { - std::string path = test_vectors::kTestDir + kTestFileName; - File file; - file.Remove(path); - - std::string write_data = "This is a test"; - File wr_file; - EXPECT_TRUE(wr_file.Open(path, File::kCreate)); - EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); - wr_file.Close(); - EXPECT_TRUE(file.Exists(path)); - - std::string read_data; - read_data.resize(file.FileSize(path)); - File rd_file; - EXPECT_TRUE(rd_file.Open(path, File::kReadOnly)); - EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size())); - rd_file.Close(); - EXPECT_EQ(write_data, read_data); + EXPECT_EQ(static_cast(write_data.size()), + file_system.FileSize(path)); } TEST_F(FileTest, WriteReadBinaryFile) { std::string path = test_vectors::kTestDir + kTestFileName; - File file; - file.Remove(path); + file_system.Remove(path); std::string write_data = GenerateRandomData(600); - File wr_file; - EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); - wr_file.Close(); - EXPECT_TRUE(file.Exists(path)); + File* file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + EXPECT_TRUE(file->Write(write_data.data(), write_data.size())); + file->Close(); + EXPECT_TRUE(file_system.Exists(path)); std::string read_data; - read_data.resize(file.FileSize(path)); - File rd_file; - EXPECT_TRUE(rd_file.Open(path, File::kReadOnly)); - EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size())); - rd_file.Close(); + read_data.resize(file_system.FileSize(path)); + file = file_system.Open(path, FileSystem::kReadOnly); + ASSERT_TRUE(file); + EXPECT_TRUE(file->Read(&read_data[0], read_data.size())); + file->Close(); EXPECT_EQ(write_data, read_data); } -TEST_F(FileTest, CopyFile) { - std::string path = test_vectors::kTestDir + kTestFileName; - File file; - file.Remove(path); - - std::string write_data = GenerateRandomData(600); - File wr_file; - EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); - wr_file.Close(); - ASSERT_TRUE(file.Exists(path)); - - std::string path_copy = test_vectors::kTestDir + kTestFileName2; - EXPECT_FALSE(file.Exists(path_copy)); - EXPECT_TRUE(file.Copy(path, path_copy)); - - std::string read_data; - read_data.resize(file.FileSize(path_copy)); - File rd_file; - EXPECT_TRUE(rd_file.Open(path_copy, File::kReadOnly)); - EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size())); - rd_file.Close(); - EXPECT_EQ(write_data, read_data); - EXPECT_EQ(file.FileSize(path), file.FileSize(path_copy)); -} - -TEST_F(FileTest, ListEmptyDirectory) { - std::vector files; - File file; - EXPECT_TRUE(file.List(test_vectors::kTestDir, &files)); - EXPECT_EQ(0u, files.size()); -} - -TEST_F(FileTest, ListFiles) { - File file; - std::string path = test_vectors::kTestDir + kTestDirName; - EXPECT_TRUE(file.CreateDirectory(path)); - - path = test_vectors::kTestDir + kTestFileName; - std::string write_data = GenerateRandomData(600); - EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(file.Write(write_data.data(), write_data.size())); - file.Close(); - EXPECT_TRUE(file.Exists(path)); - - path = test_vectors::kTestDir + kTestFileName2; - write_data = GenerateRandomData(600); - EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(file.Write(write_data.data(), write_data.size())); - file.Close(); - EXPECT_TRUE(file.Exists(path)); - - std::vector files; - EXPECT_TRUE(file.List(test_vectors::kTestDir, &files)); - EXPECT_EQ(3u, files.size()); - - for (size_t i = 0; i < files.size(); ++i) { - EXPECT_TRUE(files[i] == kTestDirName || files[i] == kTestFileName || - files[i] == kTestFileName2); - } -} - } // namespace wvcdm diff --git a/core/test/generic_crypto_unittest.cpp b/core/test/generic_crypto_unittest.cpp new file mode 100644 index 00000000..571a2af4 --- /dev/null +++ b/core/test/generic_crypto_unittest.cpp @@ -0,0 +1,416 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// These tests are for the generic crypto operations. They call on the +// CdmEngine class and exercise the classes below it as well. In +// particular, we assume that the OEMCrypo layer works, and has a valid keybox. +// This is because we need a valid RSA certificate, and will attempt to connect +// to the provisioning server to request one if we don't. + +#include +#include +#include + +#include "cdm_engine.h" + +#include "license_request.h" +#include "log.h" +#include "oec_session_util.h" +#include "../../oemcrypto/mock/src/oemcrypto_key_mock.h" +#include "string_conversions.h" +#include "url_request.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_types.h" + +namespace { + +const std::string kKeySystem = "com.widevine.alpha"; + +} // namespace + +namespace wvcdm { + +class WvGenericOperationsTest : public testing::Test { + public: + virtual void SetUp() { + ::testing::Test::SetUp(); + + // Load test keybox. This keybox will be used by any CryptoSession + // created by the CDM under test. + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestKeybox()); + + // Perform CdmEngine setup + cdm_engine_ = new CdmEngine(&file_system_); + + CdmResponseType status = + cdm_engine_->OpenSession(kKeySystem, NULL, NULL, &session_id_); + if (status == NEED_PROVISIONING) { + Provision(); + status = cdm_engine_->OpenSession(kKeySystem, NULL, NULL, &session_id_); + } + ASSERT_EQ(NO_ERROR, status); + ASSERT_NE("", session_id_) << "Could not open CDM session."; + ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_)); + + // Get OEMCrypto session ID from the CDM + CdmQueryMap query; + cdm_engine_->QueryOemCryptoSessionId(session_id_, &query); + std::istringstream parse_int; + parse_int.str(query[QUERY_KEY_OEMCRYPTO_SESSION_ID]); + parse_int >> oec_session_id_; + + // Construct and install keys into the CDM's OEMCrypto session. + OecSessionSetup(oec_session_id_); + EncryptAndLoadKeys(); + } + + virtual void TearDown() { + oec_util_session_.close(); + cdm_engine_->CloseSession(session_id_); + // OEMCrypto_Terminate() will be performed during the test class's + // destruction (specifically by the CryptoSession destructor) + } + + void OecSessionSetup(uint32_t oec_session_id) { + buffer_size_ = 160; + oec_util_session_.SetSessionId(oec_session_id); + oec_util_session_.GenerateTestSessionKeys(); + MakeFourKeys(); + } + + enum GenericKeyType { + kGenericEncrypt = 0, + kGenericDecrypt = 1, + kGenericSign = 2, + kGenericVerify = 3 + }; + + virtual void MakeFourKeys( + uint32_t duration = wvoec::kDuration, uint32_t control = 0, + uint32_t nonce = 0, const std::string& pst = "") { + ASSERT_NO_FATAL_FAILURE( + oec_util_session_.FillSimpleMessage(duration, control, nonce, pst)); + oec_util_session_.license().keys[kGenericEncrypt].control.control_bits |= + htonl(wvoec_mock::kControlAllowEncrypt); + oec_util_session_.license().keys[kGenericDecrypt].control.control_bits |= + htonl(wvoec_mock::kControlAllowDecrypt); + oec_util_session_.license().keys[kGenericSign].control.control_bits |= + htonl(wvoec_mock::kControlAllowSign); + oec_util_session_.license().keys[kGenericVerify].control.control_bits |= + htonl(wvoec_mock::kControlAllowVerify); + + oec_util_session_.license().keys[kGenericSign].key_data_length = + wvcdm::MAC_KEY_SIZE; + oec_util_session_.license().keys[kGenericVerify].key_data_length = + wvcdm::MAC_KEY_SIZE; + + clear_buffer_.assign(buffer_size_, 0); + for (size_t i = 0; i < clear_buffer_.size(); i++) { + clear_buffer_[i] = 1 + i % 250; + } + for (size_t i = 0; i < wvcdm::KEY_IV_SIZE; i++) { + iv_[i] = i; + } + } + + std::string GetKeyId(GenericKeyType type) { + std::string key_id; + size_t key_id_length = oec_util_session_.license().keys[0].key_id_length; + key_id.assign( + &(oec_util_session_.license().keys[type].key_id[0]), + &(oec_util_session_.license().keys[type].key_id[key_id_length])); + return key_id; + } + + std::string GetClearBuffer() { + std::string buffer; + size_t buffer_length = clear_buffer_.size(); + buffer.assign(&clear_buffer_[0], &clear_buffer_[buffer_length]); + return buffer; + } + + std::string GetEncryptedBuffer() { + std::string buffer; + size_t buffer_length = encrypted_buffer_.size(); + buffer.assign(&encrypted_buffer_[0], &encrypted_buffer_[buffer_length]); + return buffer; + } + + std::string GetIvBlock() { + std::string buffer; + size_t buffer_length = wvcdm::KEY_IV_SIZE; + buffer.assign(&iv_[0], &iv_[buffer_length]); + return buffer; + } + + std::string GetSignatureBuffer() { + std::string buffer; + buffer.resize(SHA256_DIGEST_LENGTH); + return buffer; + } + + void EncryptAndLoadKeys() { + ASSERT_NO_FATAL_FAILURE(oec_util_session_.EncryptAndSign()); + oec_util_session_.LoadTestKeys(); + } + + protected: + void Provision() { + CdmProvisioningRequest prov_request; + std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority; + std::string cert, wrapped_key; + ASSERT_EQ(NO_ERROR, + cdm_engine_->GetProvisioningRequest( + cert_type, cert_authority, &prov_request, + &provisioning_server_url)); + UrlRequest url_request(provisioning_server_url); + EXPECT_TRUE(url_request.is_connected()); + url_request.PostCertRequestInQueryString(prov_request); + std::string message; + bool ok = url_request.GetResponse(&message); + EXPECT_TRUE(ok); + ASSERT_EQ(NO_ERROR, + cdm_engine_->HandleProvisioningResponse(message, &cert, + &wrapped_key)); + } + + // This CryptoSession object handles Initialization and Termination + // calls on OEMCrypto for the duration of the test. CryptoSessions + // created by the CDM will share the OEMCrypto state of this CryptoSession, + // including, for example, a test keybox. + CryptoSession crypto_session_; + + FileSystem file_system_; + CdmEngine* cdm_engine_; + std::string key_msg_; + std::string session_id_; + std::string server_url_; + uint32_t oec_session_id_; + wvoec::Session oec_util_session_; + size_t buffer_size_; + vector clear_buffer_; + vector encrypted_buffer_; + uint8_t iv_[wvcdm::KEY_IV_SIZE]; +}; + +TEST_F(WvGenericOperationsTest, NormalSessionOpenClose) { + wvoec::Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.close()); +} + +TEST_F(WvGenericOperationsTest, GenerateSessionKeys) { + wvoec::Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.close()); +} + +TEST_F(WvGenericOperationsTest, GenericEncryptNoKey) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // No key + KeyId key_id("xyz"); + cdm_sts = cdm_engine_->GenericEncrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(KEY_ERROR_1, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericEncryptKeyNotAllowed) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Wrong key + std::string key_id = GetKeyId(kGenericDecrypt); + + cdm_sts = cdm_engine_->GenericEncrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericEncryptGood) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Good key + std::string key_id = GetKeyId(kGenericEncrypt); + + cdm_sts = cdm_engine_->GenericEncrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericDecryptKeyNotAllowed) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Wrong key + std::string key_id = GetKeyId(kGenericEncrypt); + + cdm_sts = cdm_engine_->GenericDecrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericDecryptGood) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Good key + std::string key_id = GetKeyId(kGenericDecrypt); + + cdm_sts = cdm_engine_->GenericDecrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericSignKeyNotAllowed) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer; + + // Wrong key + std::string key_id = GetKeyId(kGenericVerify); + + cdm_sts = cdm_engine_->GenericSign(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + &signature_buffer); + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericSignGood) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer; + + // Good key + std::string key_id = GetKeyId(kGenericSign); + + cdm_sts = cdm_engine_->GenericSign(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + &signature_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericVerifyKeyNotAllowed) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer = GetSignatureBuffer(); + + // Wrong key + std::string key_id = GetKeyId(kGenericSign); + + cdm_sts = cdm_engine_->GenericVerify(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + signature_buffer); + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericVerifyGood) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer = GetSignatureBuffer(); + + // Good key - signature not set. + std::string key_id = GetKeyId(kGenericVerify); + + cdm_sts = cdm_engine_->GenericVerify(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + signature_buffer); + // OEMCrypto error is OEMCrypto_ERROR_SIGNATURE_FAILURE + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +class WvGenericOperationsDataTest : public WvGenericOperationsTest { + public: + // Construct keys for encrypt/decrypt and for sign/verify + virtual void MakeFourKeys( + uint32_t duration = wvoec::kDuration, uint32_t control = 0, + uint32_t nonce = 0, const std::string& pst = "") { + ASSERT_NO_FATAL_FAILURE( + oec_util_session_.FillSimpleMessage(duration, control, nonce, pst)); + oec_util_session_.license().keys[kGenericEncrypt].control.control_bits |= + htonl(wvoec_mock::kControlAllowEncrypt | + wvoec_mock::kControlAllowDecrypt); + oec_util_session_.license().keys[kGenericSign].control.control_bits |= + htonl(wvoec_mock::kControlAllowSign | wvoec_mock::kControlAllowVerify); + + oec_util_session_.license().keys[kGenericSign].key_data_length = + wvcdm::MAC_KEY_SIZE; + + clear_buffer_.assign(buffer_size_, 0); + for (size_t i = 0; i < clear_buffer_.size(); i++) { + clear_buffer_[i] = 1 + i % 250; + } + for (size_t i = 0; i < wvcdm::KEY_IV_SIZE; i++) { + iv_[i] = i; + } + } +}; + +TEST_F(WvGenericOperationsDataTest, GenericEncryptDecrypt) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string encrypted_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Encrypt + std::string key_id = GetKeyId(kGenericEncrypt); + + cdm_sts = cdm_engine_->GenericEncrypt( + session_id_, in_buffer, key_id, iv, wvcdm::kEncryptionAlgorithmAesCbc128, + &encrypted_buffer); + + EXPECT_EQ(NO_ERROR, cdm_sts); + + // Decrypt, use same key as encrypt. + key_id = GetKeyId(kGenericEncrypt); + + std::string final_buffer; + final_buffer.resize(in_buffer.size()); + + cdm_sts = cdm_engine_->GenericDecrypt( + session_id_, encrypted_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, &final_buffer); + + EXPECT_EQ(NO_ERROR, cdm_sts); + EXPECT_EQ(0, in_buffer.compare(final_buffer)); +} + +TEST_F(WvGenericOperationsDataTest, GenericSignVerify) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer = GetSignatureBuffer(); + + // Signing key + std::string key_id = GetKeyId(kGenericSign); + cdm_sts = cdm_engine_->GenericSign(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + &signature_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); + + // Verify signature, use same key as sign. + key_id = GetKeyId(kGenericSign); + cdm_sts = cdm_engine_->GenericVerify(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + signature_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); +} + +} // namespace wvcdm diff --git a/core/test/http_socket.cpp b/core/test/http_socket.cpp index 2a8eb7e6..f1d20daf 100644 --- a/core/test/http_socket.cpp +++ b/core/test/http_socket.cpp @@ -41,9 +41,12 @@ SSL_CTX* InitSslContext() { OpenSSL_add_all_algorithms(); SSL_load_error_strings(); - method = SSLv3_client_method(); + method = TLSv1_2_client_method(); ctx = SSL_CTX_new(method); if (!ctx) LOGE("failed to create SSL context"); + int ret = SSL_CTX_set_cipher_list( + ctx, "ALL:!RC4-MD5:!RC4-SHA:!ECDHE-ECDSA-RC4-SHA:!ECDHE-RSA-RC4-SHA"); + if (0 != ret) LOGE("error disabling vulnerable ciphers"); return ctx; } @@ -294,6 +297,10 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) { int total_read = 0; int to_read = len; + if (socket_fd_ == -1) { + LOGE("Socket to %s not open. Cannot read.", domain_name_.c_str()); + return -1; + } while (to_read > 0) { if (!SocketWait(socket_fd_, /* for_read */ true, timeout_in_ms)) { LOGE("unable to read from %s", domain_name_.c_str()); @@ -329,6 +336,10 @@ int HttpSocket::Write(const char* data, int len, int timeout_in_ms) { int total_sent = 0; int to_send = len; + if (socket_fd_ == -1) { + LOGE("Socket to %s not open. Cannot write.", domain_name_.c_str()); + return -1; + } while (to_send > 0) { int sent; if (secure_connect_) diff --git a/core/test/initialization_data_unittest.cpp b/core/test/initialization_data_unittest.cpp index a739803f..e8de2ea2 100644 --- a/core/test/initialization_data_unittest.cpp +++ b/core/test/initialization_data_unittest.cpp @@ -1,19 +1,44 @@ // Copyright 2015 Google Inc. All Rights Reserved. +#include #include #include + #include "initialization_data.h" +#include "license_protocol.pb.h" #include "string_conversions.h" #include "wv_cdm_constants.h" // References: // [1] http://dashif.org/identifiers/content-protection/ // [2] http://www.w3.org/TR/encrypted-media/cenc-format.html#common-system +// [3] https://tools.ietf.org/html/draft-pantos-http-live-streaming-18 namespace wvcdm { +// Protobuf generated classes. +using video_widevine_server::sdk::WidevineCencHeader; + namespace { +// Constants for JSON formatting +const std::string kLeftBrace = "{"; +const std::string kRightBrace = "}"; +const std::string kLeftBracket = "["; +const std::string kRightBracket = "]"; +const std::string kComma = ","; +const std::string kColon = ":"; +const std::string kDoubleQuote = "\""; +const std::string kNewline = "\n"; +const std::string kFourSpaceIndent = " "; + +const std::string kJsonProvider = "provider"; +const std::string kJsonContentId = "content_id"; +const std::string kJsonKeyIds = "key_ids"; + +const uint32_t kFourCcCbc1 = 0x63626331; +const uint32_t kFourCcCbcs = 0x63626373; + const std::string kWidevinePssh = a2bs_hex( // Widevine PSSH box "00000042" // atom size @@ -139,8 +164,245 @@ const std::string kZeroSizedPsshBox = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); +// HLS test attribute key and values +const std::string kHlsIvHexValue = "6DF49213A781E338628D0E9C812D328E"; +const std::string kHlsIvValue = "0x" + kHlsIvHexValue; +const std::string kHlsKeyFormatValue = "com.widevine.alpha"; +const std::string kHlsKeyFormatValueOther = "com.example"; +const std::string kHlsTestKey1 = "TESTKEY1"; +const std::string kHlsTestValue1 = "testvalue1"; +const std::string kHlsTestKey2 = "TESTKEY2"; +const std::string kHlsTestValue2 = "testvalue2"; +const std::string kHlsTestInvalidLowercaseKey = "testkey3"; +const std::string kHlsTestKeyWithDash = "TEST-KEY4"; +const std::string kHlsTestInvalidNonAlphanumKey = "TEST;KEY4"; +const std::string kHlsTestValueWithEmbeddedQuote = "test\"value1"; +const std::string kHlsTestEmptyHexValue = ""; +const std::string kHlsTestNoHexValue = "0x"; +const std::string kHlsTestHexValueWithOddBytes = kHlsIvHexValue + "7"; +const std::string kHlsTestInvalidHexValue = kHlsIvHexValue + "g7"; +char kHlsTestKeyFormatVersionsSeparator = '/'; +const std::string kHlsTestUriDataFormat = "data:text/plain;base64,"; +const std::string kHlsTestProvider = "youtube"; +const std::string kHlsTestContentId = "MjAxNV9UZWFycw=="; +const std::string kHlsTestKeyId1 = "371E135E1A985D75D198A7F41020DC23"; +const std::string kHlsTestKeyId2 = "E670D9B60AE61583E01BC9253FA19261"; +const std::string kHlsTestKeyId3 = "78094E72165DF39721B8A354D6A71390"; +const std::string kHlsTestInvalidKeyId = "B8A354D6A71390"; +const std::string kHlsTestKeyFormatVersion1 = "1"; +const std::string kHlsTestKeyFormatVersion3 = "3"; +const std::string kHlsTestKeyFormatVersion5 = "5"; +const std::string kHlsTestKeyFormatVersion13 = "13"; +const std::string kHlsTestKeyFormatVersion21 = "21"; +const std::string kHlsTestKeyFormatVersion37 = "37"; + +// HLS attribute helper functions +std::string QuoteString(const std::string& value) { + return "\"" + value + "\""; +} + +std::string GenerateJsonInitData(const std::string& provider, + const std::string& content_id, + const std::vector& key_ids) { + std::string json = kLeftBrace + kNewline; + if (provider.size() > 0) { + json += kFourSpaceIndent + kDoubleQuote + kJsonProvider + kDoubleQuote + + kColon + kDoubleQuote + provider + kDoubleQuote + kComma + kNewline; + } + if (content_id.size() > 0) { + json += kFourSpaceIndent + kDoubleQuote + kJsonContentId + kDoubleQuote + + kColon + kDoubleQuote + content_id + kDoubleQuote + kComma + + kNewline; + } + if (key_ids.size() > 0) { + json += kFourSpaceIndent + kDoubleQuote + kJsonKeyIds + kDoubleQuote + + kColon + kNewline; + json += kFourSpaceIndent + kLeftBracket + kNewline; + for (size_t i = 0; i < key_ids.size(); ++i) { + json += kFourSpaceIndent + kFourSpaceIndent + kDoubleQuote + key_ids[i] + + kDoubleQuote; + if (i != key_ids.size() - 1) { + json += kComma; + } + json += kNewline; + } + json += kFourSpaceIndent + kRightBracket + kNewline; + } + json += kRightBrace + kNewline; + return json; +} + +class VectorOfStrings { + public: + VectorOfStrings(const std::string& str) { vec_.push_back(str); } + VectorOfStrings& Add(const std::string& str) { + vec_.push_back(str); + return *this; + } + const std::vector Generate() { return vec_; } + + private: + std::vector vec_; +}; + +std::string GenerateHlsUriData(const std::string& provider, + const std::string& content_id, + const std::vector& key_ids) { + std::string json = GenerateJsonInitData(provider, content_id, key_ids); + std::vector json_init_data( + reinterpret_cast(json.data()), + reinterpret_cast(json.data() + json.size())); + return kHlsTestUriDataFormat + Base64Encode(json_init_data); +} + +std::string CreateHlsAttributeList(const std::string& method, + const std::string& provider, + const std::string& content_id, + const std::vector& key_ids, + const std::string& iv, + const std::string& key_format, + const std::string& key_format_version) { + return "EXT-X-KEY: " + HLS_METHOD_ATTRIBUTE + "=" + method + "," + + HLS_URI_ATTRIBUTE + "=" + + QuoteString(GenerateHlsUriData(provider, content_id, key_ids)) + "," + + HLS_IV_ATTRIBUTE + "=" + iv + "," + HLS_KEYFORMAT_ATTRIBUTE + "=" + + QuoteString(key_format) + "," + HLS_KEYFORMAT_VERSIONS_ATTRIBUTE + + "=" + QuoteString(key_format_version); +} + +// HLS attribute list for testing +const std::string kHlsAttributeList = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListKeyFormatUnknown = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, + kHlsKeyFormatValueOther, HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListKeyFormatVersionUnsupported = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsTestProvider, + kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), + kHlsIvValue, kHlsKeyFormatValue, "2"); + +const std::string kHlsAttributeListKeyFormatVersionMultiple = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsTestProvider, + kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), + kHlsIvValue, kHlsKeyFormatValue, "1/2/5"); + +const std::string kHlsAttributeListMethodAes128 = CreateHlsAttributeList( + HLS_METHOD_AES_128, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListMethodNone = CreateHlsAttributeList( + HLS_METHOD_NONE, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListMethodInvalid = CreateHlsAttributeList( + kHlsTestValue1, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListInvalidUriNoProvider = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, "", kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), + kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListInvalidUriNoContentId = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsTestProvider, "", + VectorOfStrings(kHlsTestKeyId1).Generate(), + kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListInvalidUriNoKeyId = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings("").Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListValidUriThreeKeyIds = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1) + .Add(kHlsTestKeyId2) + .Add(kHlsTestKeyId3) + .Generate(), + kHlsIvValue, kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListNoIv = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsTestNoHexValue, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListInvalidIv = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsTestHexValueWithOddBytes, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); + +std::string InsertHlsAttributeInList(const std::string key, + const std::string& value) { + return kHlsAttributeList + "," + key + "=" + value + "," + kHlsTestKey2 + + "=" + kHlsTestValue2; +} + +struct HlsInitDataVariant { + HlsInitDataVariant(CdmHlsMethod method, const std::string& provider, + const std::string& content_id, const std::string& key_id, + bool success) + : method_(method), provider_(provider), content_id_(content_id), + success_(success) { + if (key_id.size() > 0) key_ids_.push_back(key_id); + } + HlsInitDataVariant& AddKeyId(const std::string& key_id) { + key_ids_.push_back(key_id); + return *this; + } + const CdmHlsMethod method_; + const std::string provider_; + const std::string content_id_; + std::vector key_ids_; + const bool success_; +}; + +struct HlsAttributeVariant { + HlsAttributeVariant(const std::string& attribute_list, const std::string& key, + const std::string& value, bool success) + : attribute_list_(attribute_list), + key_(key), + value_(value), + success_(success) {} + const std::string attribute_list_; + const std::string key_; + const std::string value_; + const bool success_; +}; + class InitializationDataTest : public ::testing::TestWithParam {}; +class HlsAttributeExtractionTest + : public ::testing::TestWithParam {}; + +class HlsHexAttributeExtractionTest + : public ::testing::TestWithParam {}; + +class HlsQuotedAttributeExtractionTest + : public ::testing::TestWithParam {}; + +class HlsKeyFormatVersionsExtractionTest + : public ::testing::TestWithParam > {}; + +class HlsConstructionTest + : public ::testing::TestWithParam {}; + +class HlsInitDataConstructionTest : public ::testing::Test {}; + +class HlsParseTest : public ::testing::TestWithParam {}; + +class HlsTest : public ::testing::Test {}; } // namespace TEST_P(InitializationDataTest, Parse) { @@ -148,17 +410,343 @@ TEST_P(InitializationDataTest, Parse) { EXPECT_FALSE(init_data.IsEmpty()); } +INSTANTIATE_TEST_CASE_P(ParsePssh, InitializationDataTest, + ::testing::Values(kWidevinePssh, kWidevinePsshFirst, + kWidevinePsshAfterV0Pssh, + kWidevinePsshAfterNonZeroFlags, + kWidevinePsshAfterV1Pssh, + kWidevineV1Pssh, kOtherBoxFirst, + kZeroSizedPsshBox)); + +TEST_P(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions) { + std::vector versions = GetParam(); + std::string key_format_versions; + for (size_t i = 0; i < versions.size(); ++i) { + key_format_versions += versions[i] + kHlsTestKeyFormatVersionsSeparator; + } + key_format_versions.resize(key_format_versions.size() - + sizeof(kHlsTestKeyFormatVersionsSeparator)); + std::vector extracted_versions = + InitializationData::ExtractKeyFormatVersions(key_format_versions); + EXPECT_EQ(versions.size(), extracted_versions.size()); + for (size_t i = 0; i < versions.size(); ++i) { + bool found = false; + for (size_t j = 0; j < extracted_versions.size(); ++j) { + if (versions[i] == extracted_versions[j]) { + found = true; + break; + } + } + EXPECT_TRUE(found); + } +} + INSTANTIATE_TEST_CASE_P( - ParsePssh, InitializationDataTest, + HlsTest, HlsKeyFormatVersionsExtractionTest, + ::testing::Values(VectorOfStrings(kHlsTestKeyFormatVersion1).Generate(), + VectorOfStrings(kHlsTestKeyFormatVersion21).Generate(), + VectorOfStrings(kHlsTestKeyFormatVersion1) + .Add(kHlsTestKeyFormatVersion3) + .Generate(), + VectorOfStrings(kHlsTestKeyFormatVersion1) + .Add(kHlsTestKeyFormatVersion3) + .Add(kHlsTestKeyFormatVersion13) + .Generate(), + VectorOfStrings(kHlsTestKeyFormatVersion13) + .Add(kHlsTestKeyFormatVersion5) + .Add(kHlsTestKeyFormatVersion21) + .Add(kHlsTestKeyFormatVersion37) + .Generate())); + +TEST_P(HlsAttributeExtractionTest, ExtractAttribute) { + HlsAttributeVariant param = GetParam(); + std::string value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ExtractAttribute(param.attribute_list_, + param.key_, &value)); + EXPECT_EQ(param.value_, value); + } else { + EXPECT_FALSE(InitializationData::ExtractAttribute(param.attribute_list_, + param.key_, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsAttributeExtractionTest, ::testing::Values( - kWidevinePssh, - kWidevinePsshFirst, - kWidevinePsshAfterV0Pssh, - kWidevinePsshAfterNonZeroFlags, - kWidevinePsshAfterV1Pssh, - kWidevineV1Pssh, - kOtherBoxFirst, - kZeroSizedPsshBox - )); + HlsAttributeVariant(kHlsAttributeList, HLS_METHOD_ATTRIBUTE, + HLS_METHOD_SAMPLE_AES, true), + HlsAttributeVariant(kHlsAttributeList, HLS_URI_ATTRIBUTE, + QuoteString(GenerateHlsUriData( + kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate())), + true), + HlsAttributeVariant(kHlsAttributeList, HLS_IV_ATTRIBUTE, kHlsIvValue, + true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_ATTRIBUTE, + QuoteString(kHlsKeyFormatValue), true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, + QuoteString(HLS_KEYFORMAT_VERSION_VALUE_1), true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestValue1), + kHlsTestKey2, kHlsTestValue2, true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1 + "\t", + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + " " + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestValue1 + " "), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1 + "3", + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, ""), + kHlsTestKey1, "", true), + HlsAttributeVariant(InsertHlsAttributeInList( + kHlsTestInvalidLowercaseKey, kHlsTestValue1), + kHlsTestInvalidLowercaseKey, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKeyWithDash, + kHlsTestValue1), + kHlsTestKeyWithDash, kHlsTestValue1, true), + HlsAttributeVariant(InsertHlsAttributeInList( + kHlsTestInvalidNonAlphanumKey, kHlsTestValue1), + kHlsTestInvalidNonAlphanumKey, kHlsTestValue1, + false), + HlsAttributeVariant( + InsertHlsAttributeInList(kHlsTestKey1, QuoteString(kHlsTestValue1)), + kHlsTestKey1, QuoteString(kHlsTestValue1), true), + HlsAttributeVariant( + InsertHlsAttributeInList( + kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote)), + kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote), true))); + +TEST_P(HlsHexAttributeExtractionTest, ExtractHexAttribute) { + HlsAttributeVariant param = GetParam(); + std::vector value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ExtractHexAttribute(param.attribute_list_, + param.key_, &value)); + EXPECT_EQ(param.value_, b2a_hex(value)); + } else { + EXPECT_FALSE(InitializationData::ExtractHexAttribute(param.attribute_list_, + param.key_, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsHexAttributeExtractionTest, + ::testing::Values( + HlsAttributeVariant(kHlsAttributeList, HLS_IV_ATTRIBUTE, kHlsIvHexValue, + true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestEmptyHexValue), + kHlsTestKey1, kHlsTestEmptyHexValue, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestNoHexValue), + kHlsTestKey1, kHlsTestNoHexValue, false), + HlsAttributeVariant(InsertHlsAttributeInList( + kHlsTestKey1, kHlsTestHexValueWithOddBytes), + kHlsTestKey1, kHlsTestHexValueWithOddBytes, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestInvalidHexValue), + kHlsTestKey1, kHlsTestInvalidHexValue, false))); + +TEST_P(HlsQuotedAttributeExtractionTest, ExtractQuotedAttribute) { + HlsAttributeVariant param = GetParam(); + std::string value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ExtractQuotedAttribute( + param.attribute_list_, param.key_, &value)); + EXPECT_EQ(param.value_, value); + } else { + EXPECT_FALSE(InitializationData::ExtractQuotedAttribute( + param.attribute_list_, param.key_, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsQuotedAttributeExtractionTest, + ::testing::Values( + HlsAttributeVariant( + kHlsAttributeList, HLS_URI_ATTRIBUTE, + GenerateHlsUriData(kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate()), + true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_ATTRIBUTE, + kHlsKeyFormatValue, true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, + HLS_KEYFORMAT_VERSION_VALUE_1, true), + HlsAttributeVariant( + InsertHlsAttributeInList(kHlsTestKey1, QuoteString(kHlsTestValue1)), + kHlsTestKey1, kHlsTestValue1, true), + HlsAttributeVariant( + InsertHlsAttributeInList( + kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote)), + kHlsTestKey1, kHlsTestValueWithEmbeddedQuote, false))); + +TEST_P(HlsConstructionTest, InitData) { + HlsInitDataVariant param = GetParam(); + + std::string uri = + GenerateHlsUriData(param.provider_, param.content_id_, param.key_ids_); + std::string value; + EXPECT_EQ(param.success_, InitializationData::ConstructWidevineInitData( + param.method_, uri, &value)); + if (param.success_) { + WidevineCencHeader cenc_header; + EXPECT_TRUE(cenc_header.ParseFromString(value)); + EXPECT_EQ(video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR, + cenc_header.algorithm()); + for (size_t i = 0; i < param.key_ids_.size(); ++i) { + bool key_id_found = false; + if (param.key_ids_[i].size() != 32) continue; + for (int j = 0; j < cenc_header.key_id_size(); ++j) { + if (param.key_ids_[i] == b2a_hex(cenc_header.key_id(j))) { + key_id_found = true; + break; + } + } + EXPECT_TRUE(key_id_found); + } + EXPECT_EQ(param.provider_, cenc_header.provider()); + std::vector param_content_id_vec(Base64Decode(param.content_id_)); + EXPECT_EQ( + std::string(param_content_id_vec.begin(), param_content_id_vec.end()), + cenc_header.content_id()); + uint32_t protection_scheme = 0; + switch (param.method_) { + case kHlsMethodAes128: protection_scheme = kFourCcCbc1; break; + case kHlsMethodSampleAes: protection_scheme = kFourCcCbcs; break; + default: break; + } + EXPECT_EQ(protection_scheme, ntohl(cenc_header.protection_scheme())); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsConstructionTest, + ::testing::Values( + HlsInitDataVariant(kHlsMethodAes128, "", kHlsTestContentId, + kHlsTestKeyId1, false), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + "", kHlsTestKeyId1, false), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, "", false), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, kHlsTestInvalidKeyId, false), + HlsInitDataVariant(kHlsMethodNone, kHlsTestProvider, kHlsTestContentId, + kHlsTestKeyId1, false), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, kHlsTestKeyId1, true), + HlsInitDataVariant(kHlsMethodSampleAes, kHlsTestProvider, + kHlsTestContentId, kHlsTestKeyId1, true), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, kHlsTestKeyId1, true) + .AddKeyId(kHlsTestKeyId2) + .AddKeyId(kHlsTestKeyId3), + HlsInitDataVariant(kHlsMethodSampleAes, kHlsTestProvider, + kHlsTestContentId, kHlsTestKeyId1, true) + .AddKeyId(kHlsTestKeyId2) + .AddKeyId(kHlsTestKeyId3), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, kHlsTestInvalidKeyId, true) + .AddKeyId(kHlsTestKeyId1), + HlsInitDataVariant(kHlsMethodSampleAes, kHlsTestProvider, + kHlsTestContentId, kHlsTestInvalidKeyId, true) + .AddKeyId(kHlsTestKeyId1))); + +TEST_F(HlsInitDataConstructionTest, InvalidUriDataFormat) { + std::string json = + GenerateJsonInitData(kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate()); + std::vector json_init_data( + reinterpret_cast(json.data()), + reinterpret_cast(json.data() + json.size())); + std::string value; + EXPECT_FALSE(InitializationData::ConstructWidevineInitData( + kHlsMethodAes128, Base64Encode(json_init_data), &value)); +} + +TEST_F(HlsInitDataConstructionTest, InvalidUriBase64Encode) { + std::string json = + GenerateJsonInitData(kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate()); + std::string value; + EXPECT_FALSE(InitializationData::ConstructWidevineInitData( + kHlsMethodSampleAes, kHlsTestUriDataFormat + json, &value)); +} + +TEST_P(HlsParseTest, Parse) { + HlsAttributeVariant param = GetParam(); + InitializationData init_data(HLS_INIT_DATA_FORMAT, param.attribute_list_); + if (param.success_) { + EXPECT_TRUE(init_data.is_hls()); + EXPECT_FALSE(init_data.IsEmpty()); + if (param.key_.compare(HLS_METHOD_ATTRIBUTE) == 0) { + if (param.value_.compare(HLS_METHOD_SAMPLE_AES) == 0) { + EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_method()); + } else if (param.value_.compare(HLS_METHOD_AES_128) == 0) { + EXPECT_EQ(kHlsMethodAes128, init_data.hls_method()); + } else if (param.value_.compare(HLS_METHOD_NONE) == 0) { + EXPECT_EQ(kHlsMethodNone, init_data.hls_method()); + } + } else { + EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_method()); + } + + WidevineCencHeader cenc_header; + EXPECT_TRUE(cenc_header.ParseFromString(init_data.data())); + EXPECT_EQ(video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR, + cenc_header.algorithm()); + if (param.key_.compare(kJsonProvider) == 0) { + EXPECT_EQ(param.value_, cenc_header.provider()); + } else if (param.key_.compare(kJsonContentId) == 0) { + EXPECT_EQ(param.value_, cenc_header.content_id()); + } else if (param.key_.compare(kJsonKeyIds) == 0) { + EXPECT_EQ(param.value_, b2a_hex(cenc_header.key_id(0))); + } + + EXPECT_EQ(kHlsIvHexValue, b2a_hex(init_data.hls_iv())); + } else { + EXPECT_TRUE(init_data.is_hls()); + EXPECT_TRUE(init_data.IsEmpty()); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsParseTest, + ::testing::Values( + HlsAttributeVariant(kHlsAttributeList, "", "", true), + HlsAttributeVariant(kHlsAttributeListKeyFormatUnknown, + HLS_KEYFORMAT_ATTRIBUTE, kHlsKeyFormatValueOther, + false), + HlsAttributeVariant(kHlsAttributeListKeyFormatVersionUnsupported, + HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, "2", false), + HlsAttributeVariant(kHlsAttributeListMethodAes128, HLS_METHOD_ATTRIBUTE, + HLS_METHOD_AES_128, true), + HlsAttributeVariant(kHlsAttributeListMethodNone, HLS_METHOD_ATTRIBUTE, + HLS_METHOD_NONE, false), + HlsAttributeVariant(kHlsAttributeListKeyFormatVersionMultiple, + HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, + HLS_KEYFORMAT_VERSION_VALUE_1, true), + HlsAttributeVariant(kHlsAttributeListMethodInvalid, + HLS_METHOD_ATTRIBUTE, kHlsTestValue1, false), + HlsAttributeVariant(kHlsAttributeListInvalidUriNoProvider, + kJsonProvider, kHlsTestProvider, false), + HlsAttributeVariant(kHlsAttributeListInvalidUriNoContentId, + kJsonContentId, kHlsTestContentId, false), + HlsAttributeVariant(kHlsAttributeListInvalidUriNoKeyId, kJsonKeyIds, + kHlsTestKeyId1, false), + HlsAttributeVariant(kHlsAttributeListValidUriThreeKeyIds, kJsonKeyIds, + kHlsTestKeyId1, true), + HlsAttributeVariant(kHlsAttributeListNoIv, HLS_IV_ATTRIBUTE, + kHlsTestNoHexValue, false), + HlsAttributeVariant(kHlsAttributeListInvalidIv, HLS_IV_ATTRIBUTE, + kHlsTestHexValueWithOddBytes, false))); } // namespace wvcdm diff --git a/core/test/license_keys_unittest.cpp b/core/test/license_keys_unittest.cpp new file mode 100644 index 00000000..34ecf62b --- /dev/null +++ b/core/test/license_keys_unittest.cpp @@ -0,0 +1,888 @@ +// Copyright 2016 Google Inc. All Rights Reserved. + +#include +#include +#include +#include "license.h" +#include "license_key_status.h" + +namespace wvcdm { + +namespace { + +static const uint32_t dev_lo_res = 200; +static const uint32_t dev_hi_res = 400; +static const uint32_t dev_top_res = 800; + +static const uint32_t key_lo_res_min = 151; +static const uint32_t key_lo_res_max = 300; +static const uint32_t key_hi_res_min = 301; +static const uint32_t key_hi_res_max = 450; +static const uint32_t key_top_res_min = 451; +static const uint32_t key_top_res_max = 650; + +// Content Keys +static const KeyId ck_sw_crypto = "c_key_SW_SECURE_CRYPTO"; +static const KeyId ck_sw_decode = "c_key_SW_SECURE_DECODE"; +static const KeyId ck_hw_crypto = "c_key_HW_SECURE_CRYPTO"; +static const KeyId ck_hw_decode = "c_key_HW_SECURE_DECODE"; +static const KeyId ck_hw_secure = "c_key_HW_SECURE_ALL"; + +// Operator Session Keys +static const KeyId osk_decrypt = "os_key_generic_decrypt"; +static const KeyId osk_encrypt = "os_key_generic_encrypt"; +static const KeyId osk_sign = "os_key_generic_sign"; +static const KeyId osk_verify = "os_key_generic_verify"; +static const KeyId osk_encrypt_decrypt = "os_key_generic_encrypt_decrypt"; +static const KeyId osk_sign_verify = "os_key_generic_sign_verify"; +static const KeyId osk_all = "os_key_generic_all"; + +// HDCP test keys +static const KeyId ck_sw_crypto_NO_HDCP = "ck_sw_crypto_NO_HDCP"; +static const KeyId ck_hw_secure_NO_HDCP = "ck_hw_secure_NO_HDCP"; +static const KeyId ck_sw_crypto_HDCP_V2_1 = "ck_sw_crypto_HDCP_V2_1"; +static const KeyId ck_hw_secure_HDCP_V2_1 = "ck_hw_secure_HDCP_V2_1"; +static const KeyId ck_sw_crypto_HDCP_NO_OUTPUT = "ck_sw_crypto_HDCP_NO_OUT"; +static const KeyId ck_hw_secure_HDCP_NO_OUTPUT = "ck_hw_secure_HDCP_NO_OUT"; + +// Constraint test keys +static const KeyId ck_NO_HDCP_lo_res = "ck_NO_HDCP_lo_res"; +static const KeyId ck_HDCP_NO_OUTPUT_hi_res = "ck_HDCP_NO_OUTPUT_hi_res"; +static const KeyId ck_HDCP_V2_1_max_res = "ck_HDCP_V2_1_max_res"; +static const KeyId ck_NO_HDCP_dual_res = "ck_NO_HDCP_dual_res"; + +} // namespace + +// protobuf generated classes. +using video_widevine_server::sdk::License; +using video_widevine_server::sdk::LicenseIdentification; +using video_widevine_server::sdk::STREAMING; +using video_widevine_server::sdk::OFFLINE; + +typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; +typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint; + +class LicenseKeysTest : public ::testing::Test { + protected: + + enum KeyFlag { + kKeyFlagNull, + kKeyFlagFalse, + kKeyFlagTrue + }; + + static const KeyFlag kEncryptNull = kKeyFlagNull; + static const KeyFlag kEncryptFalse = kKeyFlagFalse; + static const KeyFlag kEncryptTrue = kKeyFlagTrue; + static const KeyFlag kDecryptNull = kKeyFlagNull; + static const KeyFlag kDecryptFalse = kKeyFlagFalse; + static const KeyFlag kDecryptTrue = kKeyFlagTrue; + static const KeyFlag kSignNull = kKeyFlagNull; + static const KeyFlag kSignFalse = kKeyFlagFalse; + static const KeyFlag kSignTrue = kKeyFlagTrue; + static const KeyFlag kVerifyNull = kKeyFlagNull; + static const KeyFlag kVerifyFalse = kKeyFlagFalse; + static const KeyFlag kVerifyTrue = kKeyFlagTrue; + + static const KeyFlag kContentSecureFalse = kKeyFlagFalse; + static const KeyFlag kContentSecureTrue = kKeyFlagTrue; + static const KeyFlag kContentClearFalse = kKeyFlagFalse; + static const KeyFlag kContentClearTrue = kKeyFlagTrue; + + virtual void SetUp() { + LicenseIdentification* id = license_.mutable_id(); + id->set_version(1); + id->set_type(STREAMING); + } + + virtual void AddContentKey( + const KeyId& key_id, bool set_level = false, + KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO, + bool set_hdcp = false, KeyContainer::OutputProtection::HDCP hdcp_value = + KeyContainer::OutputProtection::HDCP_NONE, + bool set_constraints = false, + std::vector* constraints = NULL) { + KeyContainer* key = license_.add_key(); + key->set_type(KeyContainer::CONTENT); + if (set_level) { + key->set_level(level); + } + if (set_hdcp) { + KeyContainer::OutputProtection* pro = key->mutable_required_protection(); + pro->set_hdcp(hdcp_value); + } + if (set_constraints) { + for (std::vector::iterator + it = constraints->begin(); it != constraints->end(); ++it) { + VideoResolutionConstraint* constraint = + key->add_video_resolution_constraints(); + constraint->set_min_resolution_pixels(it->min_resolution_pixels()); + constraint->set_max_resolution_pixels(it->max_resolution_pixels()); + constraint->mutable_required_protection()-> + set_hdcp(it->required_protection().hdcp()); + } + } + key->set_id(key_id); + } + + virtual void AddOperatorSessionKey( + const KeyId& key_id, bool set_perms = false, + KeyFlag encrypt = kKeyFlagNull, KeyFlag decrypt = kKeyFlagNull, + KeyFlag sign = kKeyFlagNull, KeyFlag verify = kKeyFlagNull) { + KeyContainer* non_content_key = license_.add_key(); + non_content_key->set_type(KeyContainer::OPERATOR_SESSION); + non_content_key->set_id(key_id); + if (set_perms) { + KeyContainer::OperatorSessionKeyPermissions* permissions = + non_content_key->mutable_operator_session_key_permissions(); + if (encrypt != kKeyFlagNull) { + permissions->set_allow_encrypt(encrypt == kKeyFlagTrue); + } + if (decrypt != kKeyFlagNull) { + permissions->set_allow_decrypt(decrypt == kKeyFlagTrue); + } + if (sign != kKeyFlagNull) { + permissions->set_allow_sign(sign == kKeyFlagTrue); + } + if (verify != kKeyFlagNull) { + permissions->set_allow_signature_verify(verify == kKeyFlagTrue); + } + } + } + + virtual void AddSigningKey(const KeyId& key_id) { + KeyContainer* key = license_.add_key(); + key->set_type(KeyContainer::SIGNING); + key->set_id(key_id); + } + + virtual void ExpectAllowedUsageContent( + const CdmKeyAllowedUsage& key_usage, KeyFlag secure, KeyFlag clear) { + EXPECT_EQ(key_usage.decrypt_to_secure_buffer, secure == kKeyFlagTrue); + EXPECT_EQ(key_usage.decrypt_to_clear_buffer, clear == kKeyFlagTrue); + EXPECT_FALSE(key_usage.generic_encrypt); + EXPECT_FALSE(key_usage.generic_decrypt); + EXPECT_FALSE(key_usage.generic_sign); + EXPECT_FALSE(key_usage.generic_verify); + } + + virtual void ExpectAllowedUsageOperator( + const CdmKeyAllowedUsage& key_usage, KeyFlag encrypt, KeyFlag decrypt, + KeyFlag sign, KeyFlag verify) { + EXPECT_FALSE(key_usage.decrypt_to_secure_buffer); + EXPECT_FALSE(key_usage.decrypt_to_clear_buffer); + EXPECT_EQ(key_usage.generic_encrypt, encrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_decrypt, decrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_sign, sign == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_verify, verify == kKeyFlagTrue); + } + + virtual int NumContentKeys() { + return content_key_count_; + } + + virtual void StageContentKeys() { + content_key_count_ = 0; + AddContentKey(ck_sw_crypto, true, KeyContainer::SW_SECURE_CRYPTO); + content_key_count_++; + AddContentKey(ck_sw_decode, true, KeyContainer::SW_SECURE_DECODE); + content_key_count_++; + AddContentKey(ck_hw_crypto, true, KeyContainer::HW_SECURE_CRYPTO); + content_key_count_++; + AddContentKey(ck_hw_decode, true, KeyContainer::HW_SECURE_DECODE); + content_key_count_++; + AddContentKey(ck_hw_secure, true, KeyContainer::HW_SECURE_ALL); + content_key_count_++; + license_keys_.SetFromLicense(license_); + } + + virtual void StageOperatorSessionKeys() { + AddOperatorSessionKey(osk_decrypt, true, + kEncryptNull, kDecryptTrue, kSignNull, kVerifyNull); + AddOperatorSessionKey(osk_encrypt, true, + kEncryptTrue, kDecryptNull, kSignNull, kVerifyNull); + AddOperatorSessionKey(osk_sign, true, + kEncryptNull, kDecryptNull, kSignTrue, kVerifyNull); + AddOperatorSessionKey(osk_verify, true, + kEncryptNull, kDecryptNull, kSignNull, kVerifyTrue); + AddOperatorSessionKey(osk_encrypt_decrypt, true, + kEncryptTrue, kDecryptTrue, kSignNull, kVerifyNull); + AddOperatorSessionKey(osk_sign_verify, true, + kEncryptNull, kDecryptNull, kSignTrue, kVerifyTrue); + AddOperatorSessionKey(osk_all, true, + kEncryptTrue, kDecryptTrue, kSignTrue, kVerifyTrue); + license_keys_.SetFromLicense(license_); + } + + virtual void StageHdcpKeys() { + content_key_count_ = 0; + AddContentKey(ck_sw_crypto_NO_HDCP, true, KeyContainer::SW_SECURE_CRYPTO, + true, KeyContainer::OutputProtection::HDCP_NONE); + content_key_count_++; + AddContentKey(ck_hw_secure_NO_HDCP, true, KeyContainer::HW_SECURE_ALL, true, + KeyContainer::OutputProtection::HDCP_NONE); + content_key_count_++; + + AddContentKey(ck_sw_crypto_HDCP_V2_1, true, KeyContainer::SW_SECURE_CRYPTO, + true, KeyContainer::OutputProtection::HDCP_V2_1); + content_key_count_++; + AddContentKey(ck_hw_secure_HDCP_V2_1, true, KeyContainer::HW_SECURE_ALL, + true, KeyContainer::OutputProtection::HDCP_V2_1); + content_key_count_++; + + AddContentKey(ck_sw_crypto_HDCP_NO_OUTPUT, true, + KeyContainer::SW_SECURE_CRYPTO, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + content_key_count_++; + AddContentKey(ck_hw_secure_HDCP_NO_OUTPUT, true, + KeyContainer::HW_SECURE_ALL, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + content_key_count_++; + license_keys_.SetFromLicense(license_); + } + + virtual void AddConstraint( + std::vector& constraints, uint32_t min_res, + uint32_t max_res, bool set_hdcp = false, + KeyContainer::OutputProtection::HDCP hdcp = + KeyContainer::OutputProtection::HDCP_NONE) { + VideoResolutionConstraint constraint; + constraint.set_min_resolution_pixels(min_res); + constraint.set_max_resolution_pixels(max_res); + if (set_hdcp) { + constraint.mutable_required_protection()->set_hdcp(hdcp); + } + constraints.push_back(constraint); + } + + virtual void StageConstraintKeys() { + content_key_count_ = 0; + + std::vector constraints; + + AddConstraint(constraints, key_lo_res_min, key_lo_res_max); + + AddContentKey(ck_NO_HDCP_lo_res, true, KeyContainer::SW_SECURE_CRYPTO, true, + KeyContainer::OutputProtection::HDCP_NONE, true, + &constraints); + content_key_count_++; + + constraints.clear(); + AddConstraint(constraints, key_hi_res_min, key_hi_res_max); + + AddContentKey( + ck_HDCP_NO_OUTPUT_hi_res, true, KeyContainer::SW_SECURE_CRYPTO, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT, true, + &constraints); + content_key_count_++; + + constraints.clear(); + AddConstraint(constraints, key_top_res_min, key_top_res_max); + + AddContentKey(ck_HDCP_V2_1_max_res, true, KeyContainer::SW_SECURE_CRYPTO, + true, KeyContainer::OutputProtection::HDCP_V2_1, true, + &constraints); + content_key_count_++; + + constraints.clear(); + AddConstraint(constraints, key_lo_res_min, key_lo_res_max); + AddConstraint(constraints, key_hi_res_min, key_hi_res_max, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + + AddContentKey(ck_NO_HDCP_dual_res, true, KeyContainer::HW_SECURE_ALL, true, + KeyContainer::OutputProtection::HDCP_NONE, true, + &constraints); + content_key_count_++; + + license_keys_.SetFromLicense(license_); + } + + virtual void ExpectKeyStatusesEqual(CdmKeyStatusMap& key_status_map, + CdmKeyStatus expected_status) { + for (CdmKeyStatusMap::iterator it = key_status_map.begin(); + it != key_status_map.end(); ++it) { + EXPECT_TRUE(it->second == expected_status); + } + } + + virtual void ExpectKeyStatusEqual(CdmKeyStatusMap& key_status_map, + const KeyId& key_id, + CdmKeyStatus expected_status) { + for (CdmKeyStatusMap::iterator it = key_status_map.begin(); + it != key_status_map.end(); ++it) { + if (key_id == it->first) { + EXPECT_TRUE(it->second == expected_status); + } + } + } + + + int content_key_count_; + LicenseKeys license_keys_; + License license_; +}; + +TEST_F(LicenseKeysTest, Empty) { + EXPECT_TRUE(license_keys_.Empty()); +} + +TEST_F(LicenseKeysTest, NotEmpty) { + const KeyId c_key = "content_key"; + AddContentKey(c_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.Empty()); +} + +TEST_F(LicenseKeysTest, BadKeyId) { + const KeyId c_key = "content_key"; + const KeyId os_key = "op_sess_key"; + const KeyId unk_key = "unknown_key"; + CdmKeyAllowedUsage allowed_usage; + AddContentKey(c_key); + AddOperatorSessionKey(os_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.IsContentKey(unk_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(unk_key)); + EXPECT_TRUE(license_keys_.MeetsConstraints(unk_key)); + EXPECT_FALSE(license_keys_.GetAllowedUsage(unk_key, &allowed_usage)); +} + +TEST_F(LicenseKeysTest, SigningKey) { + const KeyId c_key = "content_key"; + const KeyId os_key = "op_sess_key"; + const KeyId sign_key = "signing_key"; + CdmKeyAllowedUsage allowed_usage; + AddSigningKey(sign_key); + AddContentKey(c_key); + AddOperatorSessionKey(os_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.IsContentKey(sign_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(sign_key)); + EXPECT_TRUE(license_keys_.MeetsConstraints(sign_key)); + EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &allowed_usage)); +} + +TEST_F(LicenseKeysTest, ContentKey) { + const KeyId c_key = "content_key"; + AddContentKey(c_key); + EXPECT_FALSE(license_keys_.IsContentKey(c_key)); + + license_keys_.SetFromLicense(license_); + EXPECT_TRUE(license_keys_.IsContentKey(c_key)); +} + +TEST_F(LicenseKeysTest, OperatorSessionKey) { + const KeyId os_key = "op_sess_key"; + EXPECT_FALSE(license_keys_.IsContentKey(os_key)); + AddOperatorSessionKey(os_key); + + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.IsContentKey(os_key)); +} + +TEST_F(LicenseKeysTest, CanDecrypt) { + const KeyId os_key = "op_sess_key"; + const KeyId c_key = "content_key"; + EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + AddOperatorSessionKey(os_key); + AddContentKey(c_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + bool new_usable_keys = false; + bool any_change = false; + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + + any_change = license_keys_.ApplyStatusChange(kKeyStatusExpired, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); +} + +TEST_F(LicenseKeysTest, AllowedUsageNull) { + const KeyId os_key = "op_sess_key"; + const KeyId c_key = "content_key"; + const KeyId sign_key = "signing_key"; + AddOperatorSessionKey(os_key); + AddContentKey(c_key); + AddSigningKey(sign_key); + license_keys_.SetFromLicense(license_); + CdmKeyAllowedUsage usage_1; + EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &usage_1)); + + CdmKeyAllowedUsage usage_2; + EXPECT_TRUE(license_keys_.GetAllowedUsage(c_key, &usage_2)); + ExpectAllowedUsageContent(usage_2, kContentClearTrue, kContentSecureTrue); + + CdmKeyAllowedUsage usage_3; + EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_3)); + ExpectAllowedUsageContent(usage_3, kContentClearFalse, kContentSecureFalse); +} + +TEST_F(LicenseKeysTest, AllowedUsageContent) { + StageContentKeys(); + CdmKeyAllowedUsage u_sw_crypto; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_crypto, &u_sw_crypto)); + ExpectAllowedUsageContent(u_sw_crypto, kContentSecureTrue, kContentClearTrue); + + CdmKeyAllowedUsage u_sw_decode; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_decode, &u_sw_decode)); + ExpectAllowedUsageContent(u_sw_decode, kContentSecureTrue, kContentClearTrue); + + CdmKeyAllowedUsage u_hw_crypto; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_crypto, &u_hw_crypto)); + ExpectAllowedUsageContent(u_hw_crypto, kContentSecureTrue, kContentClearTrue); + + CdmKeyAllowedUsage u_hw_decode; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_decode, &u_hw_decode)); + ExpectAllowedUsageContent(u_hw_decode, kContentSecureTrue, + kContentClearFalse); + + CdmKeyAllowedUsage u_hw_secure; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_secure, &u_hw_secure)); + ExpectAllowedUsageContent(u_hw_secure, kContentSecureTrue, + kContentClearFalse); +} + +TEST_F(LicenseKeysTest, AllowedUsageOperatorSession) { + StageOperatorSessionKeys(); + CdmKeyAllowedUsage u_encrypt; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_encrypt, &u_encrypt)); + ExpectAllowedUsageOperator(u_encrypt, kEncryptTrue, kDecryptFalse, kSignFalse, + kVerifyFalse); + + CdmKeyAllowedUsage u_decrypt; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_decrypt, &u_decrypt)); + ExpectAllowedUsageOperator(u_decrypt, kEncryptFalse, kDecryptTrue, kSignFalse, + kVerifyFalse); + + CdmKeyAllowedUsage u_sign; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign, &u_sign)); + ExpectAllowedUsageOperator(u_sign, kEncryptFalse, kDecryptFalse, kSignTrue, + kVerifyFalse); + + CdmKeyAllowedUsage u_verify; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_verify, &u_verify)); + ExpectAllowedUsageOperator(u_verify, kEncryptFalse, kDecryptFalse, kSignFalse, + kVerifyTrue); + + CdmKeyAllowedUsage u_encrypt_decrypt; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_encrypt_decrypt, + &u_encrypt_decrypt)); + ExpectAllowedUsageOperator(u_encrypt_decrypt, kEncryptTrue, kDecryptTrue, + kSignFalse, kVerifyFalse); + + CdmKeyAllowedUsage u_sign_verify; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign_verify, &u_sign_verify)); + ExpectAllowedUsageOperator(u_sign_verify, kEncryptFalse, kDecryptFalse, + kSignTrue, kVerifyTrue); + + CdmKeyAllowedUsage u_all; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_all, &u_all)); + ExpectAllowedUsageOperator(u_all, kEncryptTrue, kDecryptTrue, kSignTrue, + kVerifyTrue); +} + +TEST_F(LicenseKeysTest, ExtractKeyStatuses) { + CdmKeyStatusMap key_status_map; + StageOperatorSessionKeys(); + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(0, key_status_map.size()); + StageContentKeys(); + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError); +} + +TEST_F(LicenseKeysTest, KeyStatusChanges) { + bool new_usable_keys = false; + bool any_change = false; + CdmKeyStatusMap key_status_map; + StageOperatorSessionKeys(); + StageContentKeys(); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError); + + // change to pending + any_change = license_keys_.ApplyStatusChange(kKeyStatusPending, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending); + + // change to pending (again) + any_change = license_keys_.ApplyStatusChange(kKeyStatusPending, + &new_usable_keys); + EXPECT_FALSE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending); + + // change to usable + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable); + + // change to usable (again) + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_FALSE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable); + + // change to expired + any_change = license_keys_.ApplyStatusChange(kKeyStatusExpired, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusExpired); +} + +TEST_F(LicenseKeysTest, HdcpChanges) { + bool new_usable_keys = false; + bool any_change = false; + CdmKeyStatusMap key_status_map; + StageHdcpKeys(); + + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ApplyConstraints(100, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + license_keys_.ApplyConstraints(100, HDCP_V1); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_FALSE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + license_keys_.ApplyConstraints(100, HDCP_V2_2); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); + + license_keys_.ApplyConstraints(100, HDCP_NO_DIGITAL_OUTPUT); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusUsable); + + license_keys_.ApplyConstraints(100, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); +} + +TEST_F(LicenseKeysTest, ConstraintChanges) { + bool new_usable_keys = false; + bool any_change = false; + CdmKeyStatusMap key_status_map; + StageConstraintKeys(); + + // No constraints set by device + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + // Low-res device, no HDCP support + license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + // Hi-res device, HDCP_V1 support + license_keys_.ApplyConstraints(dev_hi_res, HDCP_V1); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + // Lo-res device, HDCP V2.2 support + license_keys_.ApplyConstraints(dev_lo_res, HDCP_V2_2); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); + + // Hi-res device, Maximal HDCP support + license_keys_.ApplyConstraints(dev_hi_res, HDCP_NO_DIGITAL_OUTPUT); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusUsable); + + // Lo-res device, no HDCP support + license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); + + // Too-high-res -- all keys rejected + license_keys_.ApplyConstraints(dev_top_res, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); +} + +} // namespace wvcdm diff --git a/core/test/license_request.cpp b/core/test/license_request.cpp index 3c8020ce..0b104998 100644 --- a/core/test/license_request.cpp +++ b/core/test/license_request.cpp @@ -55,30 +55,4 @@ void LicenseRequest::GetDrmMessage(const std::string& response, } } -// Returns heartbeat url in heartbeat_url. -// The heartbeat url is stored as meta data in the response message. -void LicenseRequest::GetHeartbeatUrl(const std::string& response, - std::string& heartbeat_url) { - if (response.empty()) { - heartbeat_url.clear(); - return; - } - - size_t header_end_pos = FindHeaderEndPosition(response); - if (header_end_pos != std::string::npos) { - header_end_pos += kTwoBlankLines.size(); // points to response body - - heartbeat_url.clear(); - size_t heartbeat_url_pos = response.find("Heartbeat-Url: ", header_end_pos); - if (heartbeat_url_pos != std::string::npos) { - heartbeat_url_pos += sizeof("Heartbeat-Url: "); - heartbeat_url.assign(response.substr(heartbeat_url_pos)); - } else { - LOGE("heartbeat url not found"); - } - } else { - LOGE("response body not found"); - } -} - } // namespace wvcdm diff --git a/core/test/license_request.h b/core/test/license_request.h index 8e228dd7..a023d1b1 100644 --- a/core/test/license_request.h +++ b/core/test/license_request.h @@ -17,7 +17,6 @@ class LicenseRequest { ~LicenseRequest() {}; void GetDrmMessage(const std::string& response, std::string& drm_msg); - void GetHeartbeatUrl(const std::string& response, std::string& heartbeat_url); private: size_t FindHeaderEndPosition(const std::string& response) const; diff --git a/core/test/max_res_engine_unittest.cpp b/core/test/max_res_engine_unittest.cpp deleted file mode 100644 index 5d03a659..00000000 --- a/core/test/max_res_engine_unittest.cpp +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. - -#include -#include -#include "crypto_session.h" -#include "license.h" -#include "max_res_engine.h" -#include "mock_clock.h" -#include "scoped_ptr.h" -#include "wv_cdm_types.h" - -namespace wvcdm { - -typedef ::video_widevine_server::sdk::License License; -typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; -typedef ::video_widevine_server::sdk::License::KeyContainer::OutputProtection - OutputProtection; -typedef ::video_widevine_server::sdk::License::KeyContainer:: - VideoResolutionConstraint VideoResolutionConstraint; -typedef ::google::protobuf::RepeatedPtrField KeyList; -typedef ::google::protobuf::RepeatedPtrField - ConstraintList; - -using namespace testing; - -namespace { - -const KeyId kKeyId1 = "357adc89f1673433c36c621f1b5c41ee"; -const KeyId kKeyId2 = "3d25f819250789ecfc9ed48cc99af164"; -const KeyId kKeyId3 = "fe3cf6b69e76c9a1c877922e1a661707"; -const KeyId kKeyId4 = "29a321b9886658078f916fdd41d6f570"; -const KeyId kKeyId5 = "cc5b031bcde371031c06822d935b9a63"; -const KeyId kKeyId6 = "90ac1332e4efc8acbaf929c8d321f50c"; - -const uint32_t kMinRes1 = 0; -const uint32_t kMaxRes1 = 2000; -const uint32_t kTargetRes1 = (kMinRes1 + kMaxRes1) / 2; -const uint32_t kMinRes2 = kMaxRes1; -const uint32_t kMaxRes2 = 4000; -const uint32_t kTargetRes2 = (kMinRes2 + kMaxRes2) / 2; -const uint32_t kTargetRes3 = kMaxRes2 + 1000; - -const OutputProtection::HDCP kHdcpDefault = OutputProtection::HDCP_V2; -const OutputProtection::HDCP kHdcpConstraint = OutputProtection::HDCP_V2_1; - -const int64_t kHdcpInterval = 10; - -class HdcpOnlyMockCryptoSession : public CryptoSession { - public: - MOCK_METHOD2(GetHdcpCapabilities, bool(HdcpCapability*, HdcpCapability*)); -}; - -} // namespace - -ACTION_P2(IncrementAndReturnPointee, p, a) { - *p += a; - return *p; -} - -class MaxResEngineTest : public Test { - protected: - virtual void SetUp() { - mock_clock_ = new NiceMock(); - current_time_ = 0; - - ON_CALL(*mock_clock_, GetCurrentTime()) - .WillByDefault( - IncrementAndReturnPointee(¤t_time_, kHdcpInterval)); - - max_res_engine_.reset(new MaxResEngine(&crypto_session_, mock_clock_)); - - KeyList* keys = license_.mutable_key(); - - // Key 1 - Content key w/ ID, no HDCP, no constraints - { - KeyContainer* key1 = keys->Add(); - key1->set_type(KeyContainer::CONTENT); - key1->set_id(kKeyId1); - } - - // Key 2 - Content key w/ ID, HDCP, no constraints - { - KeyContainer* key2 = keys->Add(); - key2->set_type(KeyContainer::CONTENT); - key2->set_id(kKeyId2); - key2->mutable_required_protection()->set_hdcp(kHdcpDefault); - } - - // Key 3 - Content key w/ ID, no HDCP, constraints - { - KeyContainer* key3 = keys->Add(); - key3->set_type(KeyContainer::CONTENT); - key3->set_id(kKeyId3); - AddConstraints(key3->mutable_video_resolution_constraints()); - } - - // Key 4 - Content key w/ ID, HDCP, constraints - { - KeyContainer* key4 = keys->Add(); - key4->set_type(KeyContainer::CONTENT); - key4->set_id(kKeyId4); - key4->mutable_required_protection()->set_hdcp(kHdcpDefault); - AddConstraints(key4->mutable_video_resolution_constraints()); - } - - // Key 5 - Content key w/o ID, HDCP, constraints - { - KeyContainer* key5 = keys->Add(); - key5->set_type(KeyContainer::CONTENT); - key5->mutable_required_protection()->set_hdcp(kHdcpDefault); - AddConstraints(key5->mutable_video_resolution_constraints()); - } - - // Key 6 - Non-content key - { - KeyContainer* key6 = keys->Add(); - key6->set_type(KeyContainer::OPERATOR_SESSION); - } - } - - void AddConstraints(ConstraintList* constraints) { - // Constraint 1 - Low-res and no HDCP - { - VideoResolutionConstraint* constraint1 = constraints->Add(); - constraint1->set_min_resolution_pixels(kMinRes1); - constraint1->set_max_resolution_pixels(kMaxRes1); - } - - // Constraint 2 - High-res and stricter HDCP - { - VideoResolutionConstraint* constraint2 = constraints->Add(); - constraint2->set_min_resolution_pixels(kMinRes2); - constraint2->set_max_resolution_pixels(kMaxRes2); - constraint2->mutable_required_protection()->set_hdcp(kHdcpConstraint); - } - } - - MockClock* mock_clock_; - int64_t current_time_; - StrictMock crypto_session_; - scoped_ptr max_res_engine_; - License license_; -}; - -TEST_F(MaxResEngineTest, IsPermissiveByDefault) { - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->OnTimerEvent(); - - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, IsPermissiveWithoutALicense) { - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, IsPermissiveWithoutAResolution) { - max_res_engine_->SetLicense(license_); - max_res_engine_->OnTimerEvent(); - - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, HandlesResolutionsBasedOnConstraints) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), - Return(true))); - - max_res_engine_->SetLicense(license_); - - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->SetResolution(1, kTargetRes2); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->SetResolution(1, kTargetRes3); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, RequestsHdcpImmediatelyAndOnlyAfterInterval) { - int64_t start_time = current_time_; - - { - InSequence calls; - EXPECT_CALL(*mock_clock_, GetCurrentTime()).WillOnce(Return(start_time)); - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillOnce( - DoAll(SetArgPointee<0>(HDCP_V2_2), - Return(true))); - EXPECT_CALL(*mock_clock_, GetCurrentTime()) - .WillOnce(Return(start_time + kHdcpInterval / 2)) - .WillOnce(Return(start_time + kHdcpInterval)); - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillOnce( - DoAll(SetArgPointee<0>(HDCP_V2_2), - Return(true))); - } - - max_res_engine_->SetLicense(license_); - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - max_res_engine_->OnTimerEvent(); - max_res_engine_->OnTimerEvent(); -} - -TEST_F(MaxResEngineTest, DoesNotRequestHdcpWithoutALicense) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)).Times(0); - - max_res_engine_->OnTimerEvent(); -} - -TEST_F(MaxResEngineTest, HandlesConstraintOverridingHdcp) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(HDCP_V2), - Return(true))); - - max_res_engine_->SetLicense(license_); - - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->SetResolution(1, kTargetRes2); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, HandlesNoHdcp) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(HDCP_NONE), - Return(true))); - - max_res_engine_->SetLicense(license_); - - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->SetResolution(1, kTargetRes2); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, IgnoresHdcpWithoutAResolution) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)).Times(0); - - max_res_engine_->SetLicense(license_); - max_res_engine_->OnTimerEvent(); - - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -} // wvcdm diff --git a/core/test/policy_engine_constraints_unittest.cpp b/core/test/policy_engine_constraints_unittest.cpp new file mode 100644 index 00000000..44b1956b --- /dev/null +++ b/core/test/policy_engine_constraints_unittest.cpp @@ -0,0 +1,430 @@ +// Copyright 2016 Google Inc. All Rights Reserved. + +#include +#include +#include "crypto_session.h" +#include "license.h" +#include "policy_engine.h" +#include "mock_clock.h" +#include "scoped_ptr.h" +#include "wv_cdm_event_listener.h" +#include "wv_cdm_types.h" + +// protobuf generated classes. +using video_widevine_server::sdk::License; +using video_widevine_server::sdk::License_Policy; +using video_widevine_server::sdk::STREAMING; + +namespace wvcdm { + +typedef ::video_widevine_server::sdk::License License; +typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; +typedef ::video_widevine_server::sdk::License::KeyContainer::OutputProtection + OutputProtection; +typedef ::video_widevine_server::sdk::License::KeyContainer:: + VideoResolutionConstraint VideoResolutionConstraint; +typedef ::google::protobuf::RepeatedPtrField KeyList; +typedef ::google::protobuf::RepeatedPtrField + ConstraintList; + +using namespace testing; + +namespace { + +const CdmSessionId kSessionId = "mock_session_id"; + +const KeyId kKeyId1 = "1111kKeyId1"; +const KeyId kKeyId2 = "2222kKeyId2"; +const KeyId kKeyId3 = "3333kKeyId3"; +const KeyId kKeyId4 = "4444kKeyId4"; +const KeyId kKeyId5 = "5555kKeyId5"; +const KeyId kKeyId6 = "6666kKeyId6"; + +const uint32_t kMinRes1 = 0; +const uint32_t kMaxRes1 = 2000; +const uint32_t kMinRes2 = kMaxRes1; +const uint32_t kMaxRes2 = 4000; +const uint32_t kTargetRes1 = (kMinRes1 + kMaxRes1) / 2; +const uint32_t kTargetRes2 = (kMinRes2 + kMaxRes2) / 2; +const uint32_t kTargetRes3 = kMaxRes2 + 1000; + +const int64_t kRentalDuration = 604800; // 7 days +const int64_t kPlaybackDuration = 172800; // 48 hours +const int64_t kStreamingLicenseDuration = 300; + +const OutputProtection::HDCP kHdcpV2 = OutputProtection::HDCP_V2; +const OutputProtection::HDCP kHdcpV2_1 = OutputProtection::HDCP_V2_1; + +// should match kHdcpCheckInterval in policy_engine.cpp +const int64_t kHdcpInterval = 10; + +class HdcpOnlyMockCryptoSession : public CryptoSession { + public: + MOCK_METHOD2(GetHdcpCapabilities, bool(HdcpCapability*, HdcpCapability*)); +}; + +class MockCdmEventListener : public WvCdmEventListener { + public: + MOCK_METHOD1(OnSessionRenewalNeeded, void(const CdmSessionId& session_id)); + MOCK_METHOD3(OnSessionKeysChange, void(const CdmSessionId& session_id, + const CdmKeyStatusMap& keys_status, + bool has_new_usable_key)); + MOCK_METHOD2(OnExpirationUpdate, void(const CdmSessionId& session_id, + int64_t new_expiry_time_seconds)); +}; + +} // namespace + +class PolicyEngineConstraintsTest : public Test { + protected: + virtual void SetUp() { + mock_clock_ = new NiceMock(); + current_time_ = 0; + + policy_engine_.reset(new PolicyEngine(kSessionId, &mock_event_listener_, + &crypto_session_)); + InjectMockClock(); + + SetupLicense(); + } + + void AddConstraints(ConstraintList* constraints) { + // Constraint 1 - Low-res and no HDCP + { + VideoResolutionConstraint* c_lo_res_no_hdcp = constraints->Add(); + c_lo_res_no_hdcp->set_min_resolution_pixels(kMinRes1); + c_lo_res_no_hdcp->set_max_resolution_pixels(kMaxRes1); + } + + // Constraint 2 - High-res and stricter HDCP + { + VideoResolutionConstraint* c_hi_res_hdcp_v2 = constraints->Add(); + c_hi_res_hdcp_v2->set_min_resolution_pixels(kMinRes2); + c_hi_res_hdcp_v2->set_max_resolution_pixels(kMaxRes2); + c_hi_res_hdcp_v2->mutable_required_protection()->set_hdcp(kHdcpV2_1); + } + } + + void SetupLicense() { + license_.set_license_start_time(current_time_); + + LicenseIdentification* id = license_.mutable_id(); + id->set_version(1); + id->set_type(STREAMING); + + License_Policy* policy = license_.mutable_policy(); + policy = license_.mutable_policy(); + policy->set_can_play(true); + policy->set_can_persist(false); + policy->set_rental_duration_seconds(kRentalDuration); + policy->set_playback_duration_seconds(kPlaybackDuration); + policy->set_license_duration_seconds(kStreamingLicenseDuration); + + KeyList* keys = license_.mutable_key(); + + // Key 1 - Content key w/ ID, no HDCP, no constraints + { + KeyContainer* key1 = keys->Add(); + key1->set_type(KeyContainer::CONTENT); + key1->set_id(kKeyId1); + } + + // Key 2 - Content key w/ ID, HDCP, no constraints + { + KeyContainer* key2 = keys->Add(); + key2->set_type(KeyContainer::CONTENT); + key2->set_id(kKeyId2); + key2->mutable_required_protection()->set_hdcp(kHdcpV2); + } + + // Key 3 - Content key w/ ID, no HDCP, constraints + { + KeyContainer* key3 = keys->Add(); + key3->set_type(KeyContainer::CONTENT); + key3->set_id(kKeyId3); + AddConstraints(key3->mutable_video_resolution_constraints()); + } + + // Key 4 - Content key w/ ID, HDCP, constraints + { + KeyContainer* key4 = keys->Add(); + key4->set_type(KeyContainer::CONTENT); + key4->set_id(kKeyId4); + key4->mutable_required_protection()->set_hdcp(kHdcpV2); + AddConstraints(key4->mutable_video_resolution_constraints()); + } + + // Key 5 - Content key w/o ID, HDCP, constraints + { + KeyContainer* key5 = keys->Add(); + key5->set_type(KeyContainer::CONTENT); + key5->mutable_required_protection()->set_hdcp(kHdcpV2); + AddConstraints(key5->mutable_video_resolution_constraints()); + } + + // Key 6 - Non-content key + { + KeyContainer* key6 = keys->Add(); + key6->set_type(KeyContainer::OPERATOR_SESSION); + } + } + + void InjectMockClock() { + mock_clock_ = new MockClock(); + policy_engine_->set_clock(mock_clock_); + } + + void ExpectSessionKeysChange(CdmKeyStatus expected_key_status, + bool expected_has_new_usable_key) { + EXPECT_CALL(mock_event_listener_, + OnSessionKeysChange( + kSessionId, UnorderedElementsAre( + Pair(kKeyId1, expected_key_status), + Pair(kKeyId2, expected_key_status), + Pair(kKeyId3, expected_key_status), + Pair(kKeyId4, expected_key_status)), + expected_has_new_usable_key)); + } + + void ExpectSessionKeysChanges(const KeyId& k1, CdmKeyStatus expected_1, + const KeyId& k2, CdmKeyStatus expected_2, + const KeyId& k3, CdmKeyStatus expected_3, + const KeyId& k4, CdmKeyStatus expected_4, + bool expected_has_new_usable_key) { + EXPECT_CALL(mock_event_listener_, + OnSessionKeysChange( + kSessionId, UnorderedElementsAre( + Pair(k1, expected_1), + Pair(k2, expected_2), + Pair(k3, expected_3), + Pair(k4, expected_4)), + expected_has_new_usable_key)); + } + + scoped_ptr policy_engine_; + MockClock* mock_clock_; + int64_t current_time_; + StrictMock crypto_session_; + StrictMock mock_event_listener_; + License license_; +}; + +TEST_F(PolicyEngineConstraintsTest, IsPermissiveWithoutAResolution) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).Times(2); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + ExpectSessionKeysChange(kKeyStatusUsable, true); + + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +TEST_F(PolicyEngineConstraintsTest, HandlesResolutionsBasedOnConstraints) { + { + Sequence time; + for (int i=0; i<4; ++i) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).InSequence(time) + .WillOnce(Return(i * 10)); + } + } + { + Sequence key_change; + ExpectSessionKeysChange(kKeyStatusUsable, true); + ExpectSessionKeysChanges(kKeyId1, kKeyStatusUsable, + kKeyId2, kKeyStatusUsable, + kKeyId3, kKeyStatusOutputNotAllowed, + kKeyId4, kKeyStatusOutputNotAllowed, false); + } + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), + Return(true))); + + + policy_engine_->SetLicense(license_); + policy_engine_->NotifyResolution(1, kTargetRes1); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); + + policy_engine_->NotifyResolution(1, kTargetRes2); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); + + policy_engine_->NotifyResolution(1, kTargetRes3); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +TEST_F(PolicyEngineConstraintsTest, + RequestsHdcpImmediatelyAndOnlyAfterInterval) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(0)) + .WillOnce(Return(5)); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + ExpectSessionKeysChange(kKeyStatusUsable, true); + + int64_t start_time = current_time_ + 5; + { + InSequence calls; + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillOnce( + DoAll(SetArgPointee<0>(HDCP_V2_2), + Return(true))); + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(start_time + kHdcpInterval / 2)) + .WillOnce(Return(start_time + kHdcpInterval)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillOnce( + DoAll(SetArgPointee<0>(HDCP_V2_2), + Return(true))); + } + + policy_engine_->NotifyResolution(1, kTargetRes1); + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + policy_engine_->OnTimerEvent(); + policy_engine_->OnTimerEvent(); +} + +TEST_F(PolicyEngineConstraintsTest, DoesNotRequestHdcpWithoutALicense) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(0)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)).Times(0); + + policy_engine_->OnTimerEvent(); +} + +TEST_F(PolicyEngineConstraintsTest, HandlesConstraintOverridingHdcp) { + { + Sequence time; + for (int i=0; i<3; ++i) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).InSequence(time) + .WillOnce(Return(i * 10)); + } + } + { + Sequence key_change; + ExpectSessionKeysChange(kKeyStatusUsable, true); + ExpectSessionKeysChanges(kKeyId1, kKeyStatusUsable, + kKeyId2, kKeyStatusUsable, + kKeyId3, kKeyStatusOutputNotAllowed, + kKeyId4, kKeyStatusOutputNotAllowed, false); + } + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_V2), + Return(true))); + + policy_engine_->SetLicense(license_); + policy_engine_->NotifyResolution(1, kTargetRes1); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); + + policy_engine_->NotifyResolution(1, kTargetRes2); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +TEST_F(PolicyEngineConstraintsTest, HandlesNoHdcp) { + { + Sequence time; + for (int i=0; i<3; ++i) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).InSequence(time) + .WillOnce(Return(i * 10)); + } + } + { + Sequence key_change; + ExpectSessionKeysChanges(kKeyId1, kKeyStatusUsable, + kKeyId2, kKeyStatusOutputNotAllowed, + kKeyId3, kKeyStatusUsable, + kKeyId4, kKeyStatusOutputNotAllowed, false); + ExpectSessionKeysChanges(kKeyId1, kKeyStatusUsable, + kKeyId2, kKeyStatusOutputNotAllowed, + kKeyId3, kKeyStatusOutputNotAllowed, + kKeyId4, kKeyStatusOutputNotAllowed, false); + } + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_NONE), + Return(true))); + + policy_engine_->SetLicense(license_); + + policy_engine_->NotifyResolution(1, kTargetRes1); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); + + policy_engine_->NotifyResolution(1, kTargetRes2); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +TEST_F(PolicyEngineConstraintsTest, IgnoresHdcpWithoutAResolution) { + { + Sequence time; + for (int i=0; i<2; ++i) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).InSequence(time) + .WillOnce(Return(i * 10)); + } + } + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)).Times(0); + + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +} // namespace wvcdm diff --git a/core/test/policy_engine_unittest.cpp b/core/test/policy_engine_unittest.cpp index 68b30b15..a74dcc3e 100644 --- a/core/test/policy_engine_unittest.cpp +++ b/core/test/policy_engine_unittest.cpp @@ -42,6 +42,7 @@ const char* kRenewalServerUrl = const KeyId kKeyId = "357adc89f1673433c36c621f1b5c41ee"; const KeyId kAnotherKeyId = "another_key_id"; const KeyId kSomeRandomKeyId = "some_random_key_id"; +const KeyId kUnknownKeyId = "some_random_unknown_key_id"; const CdmSessionId kSessionId = "mock_session_id"; int64_t GetLicenseRenewalDelay(int64_t license_duration) { @@ -60,13 +61,6 @@ class MockCdmEventListener : public WvCdmEventListener { int64_t new_expiry_time_seconds)); }; -class MockMaxResEngine : public MaxResEngine { - public: - MockMaxResEngine() : MaxResEngine(NULL) {} - - MOCK_METHOD1(CanDecrypt, bool(const KeyId& key_id)); -}; - } // namespace // protobuf generated classes. @@ -138,11 +132,6 @@ class PolicyEngineTest : public ::testing::Test { policy_engine_->set_clock(mock_clock_); } - void InjectMockMaxResEngine() { - mock_max_res_engine_ = new MockMaxResEngine(); - policy_engine_->set_max_res_engine(mock_max_res_engine_); - } - void ExpectSessionKeysChange(CdmKeyStatus expected_key_status, bool expected_has_new_usable_key) { EXPECT_CALL(mock_event_listener_, @@ -165,7 +154,6 @@ class PolicyEngineTest : public ::testing::Test { StrictMock mock_event_listener_; MockClock* mock_clock_; - MockMaxResEngine* mock_max_res_engine_; scoped_ptr policy_engine_; License license_; MockFunction check_; @@ -1036,56 +1024,204 @@ TEST_F(PolicyEngineTest, MultipleKeysInLicense) { EXPECT_FALSE(policy_engine_->CanDecrypt(kSomeRandomKeyId)); } -TEST_F(PolicyEngineTest, KeysOutputNotAllowedByMaxResEngine) { +class PolicyEngineKeyAllowedUsageTest : public PolicyEngineTest { + protected: + enum KeyFlag { + kKeyFlagNull, + kKeyFlagFalse, + kKeyFlagTrue + }; + + static const KeyFlag kEncryptNull = kKeyFlagNull; + static const KeyFlag kEncryptFalse = kKeyFlagFalse; + static const KeyFlag kEncryptTrue = kKeyFlagTrue; + static const KeyFlag kDecryptNull = kKeyFlagNull; + static const KeyFlag kDecryptFalse = kKeyFlagFalse; + static const KeyFlag kDecryptTrue = kKeyFlagTrue; + static const KeyFlag kSignNull = kKeyFlagNull; + static const KeyFlag kSignFalse = kKeyFlagFalse; + static const KeyFlag kSignTrue = kKeyFlagTrue; + static const KeyFlag kVerifyNull = kKeyFlagNull; + static const KeyFlag kVerifyFalse = kKeyFlagFalse; + static const KeyFlag kVerifyTrue = kKeyFlagTrue; + + static const KeyFlag kContentSecureFalse = kKeyFlagFalse; + static const KeyFlag kContentSecureTrue = kKeyFlagTrue; + static const KeyFlag kContentClearFalse = kKeyFlagFalse; + static const KeyFlag kContentClearTrue = kKeyFlagTrue; + + + void ExpectAllowedContentKeySettings(const CdmKeyAllowedUsage& key_usage, + KeyFlag secure, KeyFlag clear) { + EXPECT_EQ(key_usage.decrypt_to_secure_buffer, secure == kKeyFlagTrue); + EXPECT_EQ(key_usage.decrypt_to_clear_buffer, clear == kKeyFlagTrue); + EXPECT_FALSE(key_usage.generic_encrypt); + EXPECT_FALSE(key_usage.generic_decrypt); + EXPECT_FALSE(key_usage.generic_sign); + EXPECT_FALSE(key_usage.generic_verify); + } + + void ExpectAllowedOperatorKeySettings(const CdmKeyAllowedUsage& key_usage, + KeyFlag encrypt, KeyFlag decrypt, + KeyFlag sign, KeyFlag verify) { + EXPECT_FALSE(key_usage.decrypt_to_secure_buffer); + EXPECT_FALSE(key_usage.decrypt_to_clear_buffer); + EXPECT_EQ(key_usage.generic_encrypt, encrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_decrypt, decrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_sign, sign == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_verify, verify == kKeyFlagTrue); + } + + void ExpectSecureContentKey(const KeyId& key_id) { + CdmKeyAllowedUsage key_usage; + EXPECT_EQ(NO_ERROR, + policy_engine_->QueryKeyAllowedUsage(key_id, &key_usage)); + + ExpectAllowedContentKeySettings(key_usage, kContentSecureTrue, + kContentSecureFalse); + } + + void ExpectLessSecureContentKey(const KeyId& key_id) { + CdmKeyAllowedUsage key_usage; + EXPECT_EQ(NO_ERROR, + policy_engine_->QueryKeyAllowedUsage(key_id, &key_usage)); + + ExpectAllowedContentKeySettings(key_usage, kContentSecureTrue, + kContentSecureTrue); + } + + void ExpectOperatorSessionKey(const KeyId& key_id, KeyFlag encrypt, + KeyFlag decrypt, KeyFlag sign, KeyFlag verify) { + CdmKeyAllowedUsage key_usage; + EXPECT_EQ(NO_ERROR, + policy_engine_->QueryKeyAllowedUsage(key_id, &key_usage)); + + ExpectAllowedOperatorKeySettings(key_usage, encrypt, decrypt, sign, verify); + } + + void AddOperatorSessionKey(const KeyId& key_id, KeyFlag encrypt, + KeyFlag decrypt, KeyFlag sign, KeyFlag verify) { + License::KeyContainer* non_content_key = license_.add_key(); + non_content_key->set_type(License::KeyContainer::OPERATOR_SESSION); + non_content_key->set_id(key_id); + License::KeyContainer::OperatorSessionKeyPermissions* permissions = + non_content_key->mutable_operator_session_key_permissions(); + if (encrypt != kKeyFlagNull) { + permissions->set_allow_encrypt(encrypt == kKeyFlagTrue); + } + if (decrypt != kKeyFlagNull) { + permissions->set_allow_decrypt(decrypt == kKeyFlagTrue); + } + if (sign != kKeyFlagNull) { + permissions->set_allow_sign(sign == kKeyFlagTrue); + } + if (verify != kKeyFlagNull) { + permissions->set_allow_signature_verify(verify == kKeyFlagTrue); + } + } +}; + +TEST_F(PolicyEngineKeyAllowedUsageTest, AllowedUsageBasic) { + + const KeyId kGenericKeyId = "oper_session_key"; + license_.clear_key(); + + // most secure License::KeyContainer* content_key = license_.add_key(); content_key->set_type(License::KeyContainer::CONTENT); content_key->set_id(kKeyId); + content_key->set_level(License::KeyContainer::HW_SECURE_ALL); + + // generic operator session key (sign) + AddOperatorSessionKey(kGenericKeyId, kEncryptNull, kDecryptNull, kSignTrue, + kVerifyNull); + + License::KeyContainer* content_key_without_id = license_.add_key(); + content_key_without_id->set_type(License::KeyContainer::CONTENT); + + // default level - less secure License::KeyContainer* another_content_key = license_.add_key(); another_content_key->set_type(License::KeyContainer::CONTENT); another_content_key->set_id(kAnotherKeyId); EXPECT_CALL(*mock_clock_, GetCurrentTime()) - .WillOnce(Return(kLicenseStartTime + 1)) - .WillOnce(Return(kLicenseStartTime + 5)) - .WillOnce(Return(kLicenseStartTime + 8)) - .WillOnce(Return(kLicenseStartTime + 10)); + .WillOnce(Return(kLicenseStartTime + 1)); - InjectMockMaxResEngine(); - EXPECT_CALL(*mock_max_res_engine_, CanDecrypt(kKeyId)) - .WillOnce(Return(false)) - .WillOnce(Return(true)) - .WillOnce(Return(false)) - .WillOnce(Return(true)); - EXPECT_CALL(*mock_max_res_engine_, CanDecrypt(kAnotherKeyId)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(true)) - .WillOnce(Return(true)); - - InSequence s; - ExpectSessionKeysChange(kKeyStatusOutputNotAllowed, - kKeyStatusOutputNotAllowed, false); - EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, _)); - ExpectSessionKeysChange(kKeyStatusUsable, kKeyStatusOutputNotAllowed, true); - ExpectSessionKeysChange(kKeyStatusOutputNotAllowed, kKeyStatusUsable, true); ExpectSessionKeysChange(kKeyStatusUsable, kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, _)); policy_engine_->SetLicense(license_); - EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId)); - EXPECT_FALSE(policy_engine_->CanDecrypt(kAnotherKeyId)); - policy_engine_->OnTimerEvent(); - EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); - EXPECT_FALSE(policy_engine_->CanDecrypt(kAnotherKeyId)); + ExpectSecureContentKey(kKeyId); + ExpectLessSecureContentKey(kAnotherKeyId); + ExpectOperatorSessionKey(kGenericKeyId, kEncryptNull, kDecryptNull, + kSignTrue, kVerifyNull); - policy_engine_->OnTimerEvent(); - EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId)); - EXPECT_TRUE(policy_engine_->CanDecrypt(kAnotherKeyId)); + CdmKeyAllowedUsage key_usage; + EXPECT_EQ(KEY_NOT_FOUND_1, + policy_engine_->QueryKeyAllowedUsage(kUnknownKeyId, &key_usage)); +} - policy_engine_->OnTimerEvent(); - EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); - EXPECT_TRUE(policy_engine_->CanDecrypt(kAnotherKeyId)); +TEST_F(PolicyEngineKeyAllowedUsageTest, AllowedUsageGeneric) { + + const KeyId kGenericEncryptKeyId = "oper_session_key_1"; + const KeyId kGenericDecryptKeyId = "oper_session_key_2"; + const KeyId kGenericSignKeyId = "oper_session_key_3"; + const KeyId kGenericVerifyKeyId = "oper_session_key_4"; + const KeyId kGenericFullKeyId = "oper_session_key_5"; + const KeyId kGenericExplicitKeyId = "oper_session_key_6"; + + license_.clear_key(); + + // more secure + License::KeyContainer* content_key = license_.add_key(); + content_key->set_type(License::KeyContainer::CONTENT); + content_key->set_id(kKeyId); + content_key->set_level(License::KeyContainer::HW_SECURE_DECODE); + + // less secure + License::KeyContainer* another_content_key = license_.add_key(); + another_content_key->set_type(License::KeyContainer::CONTENT); + another_content_key->set_id(kAnotherKeyId); + another_content_key->set_level(License::KeyContainer::HW_SECURE_CRYPTO); + + // generic operator session keys + AddOperatorSessionKey(kGenericSignKeyId, kEncryptNull, kDecryptNull, + kSignTrue, kVerifyNull); + AddOperatorSessionKey(kGenericEncryptKeyId, kEncryptTrue, kDecryptNull, + kSignNull, kVerifyNull); + AddOperatorSessionKey(kGenericDecryptKeyId, kEncryptNull, kDecryptTrue, + kSignNull, kVerifyNull); + AddOperatorSessionKey(kGenericVerifyKeyId, kEncryptNull, kDecryptNull, + kSignNull, kVerifyTrue); + AddOperatorSessionKey(kGenericFullKeyId, kEncryptTrue, kDecryptTrue, + kSignTrue, kVerifyTrue); + AddOperatorSessionKey(kGenericExplicitKeyId, kEncryptFalse, kDecryptTrue, + kSignFalse, kVerifyTrue); + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)); + + ExpectSessionKeysChange(kKeyStatusUsable, kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, _)); + + policy_engine_->SetLicense(license_); + + ExpectSecureContentKey(kKeyId); + ExpectLessSecureContentKey(kAnotherKeyId); + ExpectOperatorSessionKey(kGenericEncryptKeyId, kEncryptTrue, kDecryptFalse, + kSignFalse, kVerifyFalse); + ExpectOperatorSessionKey(kGenericDecryptKeyId, kEncryptFalse, kDecryptTrue, + kSignFalse, kVerifyFalse); + ExpectOperatorSessionKey(kGenericSignKeyId, kEncryptFalse, kDecryptFalse, + kSignTrue, kVerifyFalse); + ExpectOperatorSessionKey(kGenericVerifyKeyId, kEncryptFalse, kDecryptFalse, + kSignFalse, kVerifyTrue); + ExpectOperatorSessionKey(kGenericFullKeyId, kEncryptTrue, kDecryptTrue, + kSignTrue, kVerifyTrue); + ExpectOperatorSessionKey(kGenericExplicitKeyId, kEncryptFalse, kDecryptTrue, + kSignFalse, kVerifyTrue); } class PolicyEngineQueryTest : public PolicyEngineTest { diff --git a/core/test/test_printers.cpp b/core/test/test_printers.cpp index d77d984a..2d470d44 100644 --- a/core/test/test_printers.cpp +++ b/core/test/test_printers.cpp @@ -26,59 +26,74 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case DEVICE_REVOKED: *os << "DEVICE_REVOKED"; break; - case INSUFFICIENT_CRYPTO_RESOURCES: *os << "INSUFFICIENT_CRYPTO_RESOURCES"; + case INSUFFICIENT_CRYPTO_RESOURCES: *os << "INSUFFICIENT_CRYPTO_RESOURCES"; break; case ADD_KEY_ERROR: *os << "ADD_KEY_ERROR"; break; - case CERT_PROVISIONING_GET_KEYBOX_ERROR_1: *os << "CERT_PROVISIONING_GET_KEYBOX_ERROR_1"; + case CERT_PROVISIONING_GET_KEYBOX_ERROR_1: + *os << "CERT_PROVISIONING_GET_KEYBOX_ERROR_1"; break; - case CERT_PROVISIONING_GET_KEYBOX_ERROR_2: *os << "CERT_PROVISIONING_GET_KEYBOX_ERROR_2"; + case CERT_PROVISIONING_GET_KEYBOX_ERROR_2: + *os << "CERT_PROVISIONING_GET_KEYBOX_ERROR_2"; break; - case CERT_PROVISIONING_INVALID_CERT_TYPE: *os << "CERT_PROVISIONING_INVALID_CERT_TYPE"; + case CERT_PROVISIONING_INVALID_CERT_TYPE: + *os << "CERT_PROVISIONING_INVALID_CERT_TYPE"; break; - case CERT_PROVISIONING_REQUEST_ERROR_1: *os << "CERT_PROVISIONING_REQUEST_ERROR_1"; + case CERT_PROVISIONING_REQUEST_ERROR_1: + *os << "CERT_PROVISIONING_REQUEST_ERROR_1"; break; - case CERT_PROVISIONING_REQUEST_ERROR_2: *os << "CERT_PROVISIONING_REQUEST_ERROR_2"; + case CERT_PROVISIONING_REQUEST_ERROR_2: + *os << "CERT_PROVISIONING_REQUEST_ERROR_2"; break; - case CERT_PROVISIONING_REQUEST_ERROR_3: *os << "CERT_PROVISIONING_REQUEST_ERROR_3"; + case CERT_PROVISIONING_REQUEST_ERROR_3: + *os << "CERT_PROVISIONING_REQUEST_ERROR_3"; break; - case CERT_PROVISIONING_REQUEST_ERROR_4: *os << "CERT_PROVISIONING_REQUEST_ERROR_4"; + case CERT_PROVISIONING_REQUEST_ERROR_4: + *os << "CERT_PROVISIONING_REQUEST_ERROR_4"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_1: *os << "CERT_PROVISIONING_RESPONSE_ERROR_1"; + case CERT_PROVISIONING_RESPONSE_ERROR_1: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_1"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_2: *os << "CERT_PROVISIONING_RESPONSE_ERROR_2"; + case CERT_PROVISIONING_RESPONSE_ERROR_2: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_2"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_3: *os << "CERT_PROVISIONING_RESPONSE_ERROR_3"; + case CERT_PROVISIONING_RESPONSE_ERROR_3: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_3"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_4: *os << "CERT_PROVISIONING_RESPONSE_ERROR_4"; + case CERT_PROVISIONING_RESPONSE_ERROR_4: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_4"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_5: *os << "CERT_PROVISIONING_RESPONSE_ERROR_5"; + case CERT_PROVISIONING_RESPONSE_ERROR_5: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_5"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_6: *os << "CERT_PROVISIONING_RESPONSE_ERROR_6"; + case CERT_PROVISIONING_RESPONSE_ERROR_6: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_6"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_7: *os << "CERT_PROVISIONING_RESPONSE_ERROR_7"; + case CERT_PROVISIONING_RESPONSE_ERROR_7: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_7"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_8: *os << "CERT_PROVISIONING_RESPONSE_ERROR_8"; + case CERT_PROVISIONING_RESPONSE_ERROR_8: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_8"; break; - case CRYPTO_SESSION_OPEN_ERROR_1: *os << "CRYPTO_SESSION_OPEN_ERROR_1"; + case CRYPTO_SESSION_OPEN_ERROR_1: *os << "CRYPTO_SESSION_OPEN_ERROR_1"; break; - case CRYPTO_SESSION_OPEN_ERROR_2: *os << "CRYPTO_SESSION_OPEN_ERROR_2"; + case CRYPTO_SESSION_OPEN_ERROR_2: *os << "CRYPTO_SESSION_OPEN_ERROR_2"; break; - case CRYPTO_SESSION_OPEN_ERROR_3: *os << "CRYPTO_SESSION_OPEN_ERROR_3"; + case CRYPTO_SESSION_OPEN_ERROR_3: *os << "CRYPTO_SESSION_OPEN_ERROR_3"; break; - case CRYPTO_SESSION_OPEN_ERROR_4: *os << "CRYPTO_SESSION_OPEN_ERROR_4"; + case CRYPTO_SESSION_OPEN_ERROR_4: *os << "CRYPTO_SESSION_OPEN_ERROR_4"; break; - case CRYPTO_SESSION_OPEN_ERROR_5: *os << "CRYPTO_SESSION_OPEN_ERROR_5"; + case CRYPTO_SESSION_OPEN_ERROR_5: *os << "CRYPTO_SESSION_OPEN_ERROR_5"; break; case DECRYPT_NOT_READY: *os << "DECRYPT_NOT_READY"; break; - case DEVICE_CERTIFICATE_ERROR_1: *os << "DEVICE_CERTIFICATE_ERROR_1"; + case DEVICE_CERTIFICATE_ERROR_1: *os << "DEVICE_CERTIFICATE_ERROR_1"; break; - case DEVICE_CERTIFICATE_ERROR_2: *os << "DEVICE_CERTIFICATE_ERROR_2"; + case DEVICE_CERTIFICATE_ERROR_2: *os << "DEVICE_CERTIFICATE_ERROR_2"; break; - case DEVICE_CERTIFICATE_ERROR_3: *os << "DEVICE_CERTIFICATE_ERROR_3"; + case DEVICE_CERTIFICATE_ERROR_3: *os << "DEVICE_CERTIFICATE_ERROR_3"; break; - case DEVICE_CERTIFICATE_ERROR_4: *os << "DEVICE_CERTIFICATE_ERROR_4"; + case DEVICE_CERTIFICATE_ERROR_4: *os << "DEVICE_CERTIFICATE_ERROR_4"; break; case EMPTY_KEY_DATA_1: *os << "EMPTY_KEY_DATA_1"; break; @@ -96,25 +111,27 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case EMPTY_LICENSE_RENEWAL: *os << "EMPTY_LICENSE_RENEWAL"; break; - case EMPTY_LICENSE_RESPONSE_1: *os << "EMPTY_LICENSE_RESPONSE_1"; + case EMPTY_LICENSE_RESPONSE_1: *os << "EMPTY_LICENSE_RESPONSE_1"; break; - case EMPTY_LICENSE_RESPONSE_2: *os << "EMPTY_LICENSE_RESPONSE_2"; + case EMPTY_LICENSE_RESPONSE_2: *os << "EMPTY_LICENSE_RESPONSE_2"; break; - case EMPTY_PROVISIONING_CERTIFICATE_1: *os << "EMPTY_PROVISIONING_CERTIFICATE_1"; + case EMPTY_PROVISIONING_CERTIFICATE_1: + *os << "EMPTY_PROVISIONING_CERTIFICATE_1"; break; - case EMPTY_PROVISIONING_RESPONSE: *os << "EMPTY_PROVISIONING_RESPONSE"; + case EMPTY_PROVISIONING_RESPONSE: *os << "EMPTY_PROVISIONING_RESPONSE"; break; case EMPTY_SESSION_ID: *os << "EMPTY_SESSION_ID"; break; - case GENERATE_DERIVED_KEYS_ERROR: *os << "GENERATE_DERIVED_KEYS_ERROR"; + case GENERATE_DERIVED_KEYS_ERROR: *os << "GENERATE_DERIVED_KEYS_ERROR"; break; - case LICENSE_RENEWAL_NONCE_GENERATION_ERROR: *os << "LICENSE_RENEWAL_NONCE_GENERATION_ERROR"; + case LICENSE_RENEWAL_NONCE_GENERATION_ERROR: + *os << "LICENSE_RENEWAL_NONCE_GENERATION_ERROR"; break; - case GENERATE_USAGE_REPORT_ERROR: *os << "GENERATE_USAGE_REPORT_ERROR"; + case GENERATE_USAGE_REPORT_ERROR: *os << "GENERATE_USAGE_REPORT_ERROR"; break; case GET_LICENSE_ERROR: *os << "GET_LICENSE_ERROR"; break; - case GET_RELEASED_LICENSE_ERROR: *os << "GET_RELEASED_LICENSE_ERROR"; + case GET_RELEASED_LICENSE_ERROR: *os << "GET_RELEASED_LICENSE_ERROR"; break; case GET_USAGE_INFO_ERROR_1: *os << "GET_USAGE_INFO_ERROR_1"; break; @@ -126,51 +143,60 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case INIT_DATA_NOT_FOUND: *os << "INIT_DATA_NOT_FOUND"; break; - case INVALID_CRYPTO_SESSION_1: *os << "INVALID_CRYPTO_SESSION_1"; + case INVALID_CRYPTO_SESSION_1: *os << "INVALID_CRYPTO_SESSION_1"; break; - case INVALID_CRYPTO_SESSION_2: *os << "INVALID_CRYPTO_SESSION_2"; + case INVALID_CRYPTO_SESSION_2: *os << "INVALID_CRYPTO_SESSION_2"; break; - case INVALID_CRYPTO_SESSION_3: *os << "INVALID_CRYPTO_SESSION_3"; + case INVALID_CRYPTO_SESSION_3: *os << "INVALID_CRYPTO_SESSION_3"; break; - case INVALID_CRYPTO_SESSION_4: *os << "INVALID_CRYPTO_SESSION_4"; + case INVALID_CRYPTO_SESSION_4: *os << "INVALID_CRYPTO_SESSION_4"; break; - case INVALID_CRYPTO_SESSION_5: *os << "INVALID_CRYPTO_SESSION_5"; + case INVALID_CRYPTO_SESSION_5: *os << "INVALID_CRYPTO_SESSION_5"; break; - case INVALID_DECRYPT_PARAMETERS_ENG_1: *os << "INVALID_DECRYPT_PARAMETERS_ENG_1"; + case INVALID_DECRYPT_PARAMETERS_ENG_1: + *os << "INVALID_DECRYPT_PARAMETERS_ENG_1"; break; - case INVALID_DECRYPT_PARAMETERS_ENG_2: *os << "INVALID_DECRYPT_PARAMETERS_ENG_2"; + case INVALID_DECRYPT_PARAMETERS_ENG_2: + *os << "INVALID_DECRYPT_PARAMETERS_ENG_2"; break; - case INVALID_DECRYPT_PARAMETERS_ENG_3: *os << "INVALID_DECRYPT_PARAMETERS_ENG_3"; + case INVALID_DECRYPT_PARAMETERS_ENG_3: + *os << "INVALID_DECRYPT_PARAMETERS_ENG_3"; break; - case INVALID_DECRYPT_PARAMETERS_ENG_4: *os << "INVALID_DECRYPT_PARAMETERS_ENG_4"; + case INVALID_DECRYPT_PARAMETERS_ENG_4: + *os << "INVALID_DECRYPT_PARAMETERS_ENG_4"; break; - case INVALID_DEVICE_CERTIFICATE_TYPE: *os << "INVALID_DEVICE_CERTIFICATE_TYPE"; + case INVALID_DEVICE_CERTIFICATE_TYPE: + *os << "INVALID_DEVICE_CERTIFICATE_TYPE"; break; case INVALID_KEY_SYSTEM: *os << "INVALID_KEY_SYSTEM"; break; - case INVALID_LICENSE_RESPONSE: *os << "INVALID_LICENSE_RESPONSE"; + case INVALID_LICENSE_RESPONSE: *os << "INVALID_LICENSE_RESPONSE"; break; case INVALID_LICENSE_TYPE: *os << "INVALID_LICENSE_TYPE"; break; - case INVALID_PARAMETERS_ENG_1: *os << "INVALID_PARAMETERS_ENG_1"; + case INVALID_PARAMETERS_ENG_1: *os << "INVALID_PARAMETERS_ENG_1"; break; - case INVALID_PARAMETERS_ENG_2: *os << "INVALID_PARAMETERS_ENG_2"; + case INVALID_PARAMETERS_ENG_2: *os << "INVALID_PARAMETERS_ENG_2"; break; - case INVALID_PARAMETERS_ENG_3: *os << "INVALID_PARAMETERS_ENG_3"; + case INVALID_PARAMETERS_ENG_3: *os << "INVALID_PARAMETERS_ENG_3"; break; - case INVALID_PARAMETERS_ENG_4: *os << "INVALID_PARAMETERS_ENG_4"; + case INVALID_PARAMETERS_ENG_4: *os << "INVALID_PARAMETERS_ENG_4"; break; - case INVALID_PARAMETERS_LIC_1: *os << "INVALID_PARAMETERS_LIC_1"; + case INVALID_PARAMETERS_LIC_1: *os << "INVALID_PARAMETERS_LIC_1"; break; - case INVALID_PARAMETERS_LIC_2: *os << "INVALID_PARAMETERS_LIC_2"; + case INVALID_PARAMETERS_LIC_2: *os << "INVALID_PARAMETERS_LIC_2"; break; - case INVALID_PROVISIONING_PARAMETERS_1: *os << "INVALID_PROVISIONING_PARAMETERS_1"; + case INVALID_PROVISIONING_PARAMETERS_1: + *os << "INVALID_PROVISIONING_PARAMETERS_1"; break; - case INVALID_PROVISIONING_PARAMETERS_2: *os << "INVALID_PROVISIONING_PARAMETERS_2"; + case INVALID_PROVISIONING_PARAMETERS_2: + *os << "INVALID_PROVISIONING_PARAMETERS_2"; break; - case INVALID_PROVISIONING_REQUEST_PARAM_1: *os << "INVALID_PROVISIONING_REQUEST_PARAM_1"; + case INVALID_PROVISIONING_REQUEST_PARAM_1: + *os << "INVALID_PROVISIONING_REQUEST_PARAM_1"; break; - case INVALID_PROVISIONING_REQUEST_PARAM_2: *os << "INVALID_PROVISIONING_REQUEST_PARAM_2"; + case INVALID_PROVISIONING_REQUEST_PARAM_2: + *os << "INVALID_PROVISIONING_REQUEST_PARAM_2"; break; case INVALID_QUERY_KEY: *os << "INVALID_QUERY_KEY"; break; @@ -188,22 +214,28 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case LICENSE_ID_NOT_FOUND: *os << "LICENSE_ID_NOT_FOUND"; break; - case LICENSE_PARSER_INIT_ERROR: *os << "LICENSE_PARSER_INIT_ERROR"; + case LICENSE_PARSER_INIT_ERROR: *os << "LICENSE_PARSER_INIT_ERROR"; break; - case LICENSE_PARSER_NOT_INITIALIZED_1: *os << "LICENSE_PARSER_NOT_INITIALIZED_1"; + case LICENSE_PARSER_NOT_INITIALIZED_1: + *os << "LICENSE_PARSER_NOT_INITIALIZED_1"; break; - case LICENSE_PARSER_NOT_INITIALIZED_2: *os << "LICENSE_PARSER_NOT_INITIALIZED_2"; + case LICENSE_PARSER_NOT_INITIALIZED_2: + *os << "LICENSE_PARSER_NOT_INITIALIZED_2"; break; - case LICENSE_PARSER_NOT_INITIALIZED_3: *os << "LICENSE_PARSER_NOT_INITIALIZED_3"; + case LICENSE_PARSER_NOT_INITIALIZED_3: + *os << "LICENSE_PARSER_NOT_INITIALIZED_3"; break; - case LICENSE_RESPONSE_NOT_SIGNED: *os << "LICENSE_RESPONSE_NOT_SIGNED"; + case LICENSE_RESPONSE_NOT_SIGNED: *os << "LICENSE_RESPONSE_NOT_SIGNED"; break; break; - case LICENSE_RESPONSE_PARSE_ERROR_1: *os << "LICENSE_RESPONSE_PARSE_ERROR_1"; + case LICENSE_RESPONSE_PARSE_ERROR_1: + *os << "LICENSE_RESPONSE_PARSE_ERROR_1"; break; - case LICENSE_RESPONSE_PARSE_ERROR_2: *os << "LICENSE_RESPONSE_PARSE_ERROR_2"; + case LICENSE_RESPONSE_PARSE_ERROR_2: + *os << "LICENSE_RESPONSE_PARSE_ERROR_2"; break; - case LICENSE_RESPONSE_PARSE_ERROR_3: *os << "LICENSE_RESPONSE_PARSE_ERROR_3"; + case LICENSE_RESPONSE_PARSE_ERROR_3: + *os << "LICENSE_RESPONSE_PARSE_ERROR_3"; break; case LOAD_KEY_ERROR: *os << "LOAD_KEY_ERROR"; break; @@ -211,35 +243,34 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case REFRESH_KEYS_ERROR: *os << "REFRESH_KEYS_ERROR"; break; - case RELEASE_ALL_USAGE_INFO_ERROR_1: *os << "RELEASE_ALL_USAGE_INFO_ERROR_1"; + case RELEASE_ALL_USAGE_INFO_ERROR_1: + *os << "RELEASE_ALL_USAGE_INFO_ERROR_1"; break; - case RELEASE_ALL_USAGE_INFO_ERROR_2: *os << "RELEASE_ALL_USAGE_INFO_ERROR_2"; + case RELEASE_ALL_USAGE_INFO_ERROR_2: + *os << "RELEASE_ALL_USAGE_INFO_ERROR_2"; break; case RELEASE_KEY_ERROR: *os << "RELEASE_KEY_ERROR"; break; - case RELEASE_KEY_REQUEST_ERROR: *os << "RELEASE_KEY_REQUEST_ERROR"; + case RELEASE_KEY_REQUEST_ERROR: *os << "RELEASE_KEY_REQUEST_ERROR"; break; case RELEASE_LICENSE_ERROR_1: *os << "RELEASE_LICENSE_ERROR_1"; break; case RELEASE_LICENSE_ERROR_2: *os << "RELEASE_LICENSE_ERROR_2"; break; - case RELEASE_USAGE_INFO_ERROR: *os << "RELEASE_USAGE_INFO_ERROR"; + case RELEASE_USAGE_INFO_ERROR: *os << "RELEASE_USAGE_INFO_ERROR"; break; case RENEW_KEY_ERROR_1: *os << "RENEW_KEY_ERROR_1"; break; case RENEW_KEY_ERROR_2: *os << "RENEW_KEY_ERROR_2"; break; - case LICENSE_RENEWAL_SIGNING_ERROR: *os << "LICENSE_RENEWAL_SIGNING_ERROR"; + case LICENSE_RENEWAL_SIGNING_ERROR: *os << "LICENSE_RENEWAL_SIGNING_ERROR"; break; - case RESTORE_OFFLINE_LICENSE_ERROR_1: *os << "RESTORE_OFFLINE_LICENSE_ERROR_1"; - break; - case RESTORE_OFFLINE_LICENSE_ERROR_2: *os << "RESTORE_OFFLINE_LICENSE_ERROR_2"; - break; - case SESSION_INIT_ERROR_1: *os << "SESSION_INIT_ERROR_1"; + case RESTORE_OFFLINE_LICENSE_ERROR_2: + *os << "RESTORE_OFFLINE_LICENSE_ERROR_2"; break; case SESSION_INIT_ERROR_2: *os << "SESSION_INIT_ERROR_2"; break; - case SESSION_INIT_GET_KEYBOX_ERROR: *os << "SESSION_INIT_GET_KEYBOX_ERROR"; + case SESSION_INIT_GET_KEYBOX_ERROR: *os << "SESSION_INIT_GET_KEYBOX_ERROR"; break; case SESSION_NOT_FOUND_1: *os << "SESSION_NOT_FOUND_1"; break; @@ -261,7 +292,7 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case SESSION_NOT_FOUND_10: *os << "SESSION_NOT_FOUND_10"; break; - case SESSION_NOT_FOUND_FOR_DECRYPT: *os << "SESSION_NOT_FOUND_FOR_DECRYPT"; + case SESSION_NOT_FOUND_FOR_DECRYPT: *os << "SESSION_NOT_FOUND_FOR_DECRYPT"; break; case SESSION_KEYS_NOT_FOUND: *os << "SESSION_KEYS_NOT_FOUND"; break; @@ -271,8 +302,6 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case STORE_LICENSE_ERROR_2: *os << "STORE_LICENSE_ERROR_2"; break; - case STORE_LICENSE_ERROR_3: *os << "STORE_LICENSE_ERROR_3"; - break; case STORE_USAGE_INFO_ERROR: *os << "STORE_USAGE_INFO_ERROR"; break; case UNPROVISION_ERROR_1: *os << "UNPROVISION_ERROR_1"; @@ -290,58 +319,134 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case LICENSE_RENEWAL_SERVICE_CERTIFICATE_GENERATION_ERROR: *os << "LICENSE_RENEWAL_SERVICE_CERTIFICATE_GENERATION_ERROR"; break; - case EMPTY_PROVISIONING_CERTIFICATE_2: *os << "EMPTY_PROVISIONING_CERTIFICATE_2"; + case EMPTY_PROVISIONING_CERTIFICATE_2: + *os << "EMPTY_PROVISIONING_CERTIFICATE_2"; break; - case PARSE_SERVICE_CERTIFICATE_ERROR: *os << "PARSE_SERVICE_CERTIFICATE_ERROR"; + case PARSE_SERVICE_CERTIFICATE_ERROR: + *os << "PARSE_SERVICE_CERTIFICATE_ERROR"; break; - case SERVICE_CERTIFICATE_TYPE_ERROR: *os << "SERVICE_CERTIFICATE_TYPE_ERROR"; + case SERVICE_CERTIFICATE_TYPE_ERROR: + *os << "SERVICE_CERTIFICATE_TYPE_ERROR"; break; - case CLIENT_ID_GENERATE_RANDOM_ERROR: *os << "CLIENT_ID_GENERATE_RANDOM_ERROR"; + case CLIENT_ID_GENERATE_RANDOM_ERROR: + *os << "CLIENT_ID_GENERATE_RANDOM_ERROR"; break; - case CLIENT_ID_AES_INIT_ERROR: *os << "CLIENT_ID_AES_INIT_ERROR"; + case CLIENT_ID_AES_INIT_ERROR: *os << "CLIENT_ID_AES_INIT_ERROR"; break; - case CLIENT_ID_AES_ENCRYPT_ERROR: *os << "CLIENT_ID_AES_ENCRYPT_ERROR"; + case CLIENT_ID_AES_ENCRYPT_ERROR: *os << "CLIENT_ID_AES_ENCRYPT_ERROR"; break; - case CLIENT_ID_RSA_INIT_ERROR: *os << "CLIENT_ID_RSA_INIT_ERROR"; + case CLIENT_ID_RSA_INIT_ERROR: *os << "CLIENT_ID_RSA_INIT_ERROR"; break; - case CLIENT_ID_RSA_ENCRYPT_ERROR: *os << "CLIENT_ID_RSA_ENCRYPT_ERROR"; + case CLIENT_ID_RSA_ENCRYPT_ERROR: *os << "CLIENT_ID_RSA_ENCRYPT_ERROR"; break; case INVALID_QUERY_STATUS: *os << "INVALID_QUERY_STATUS"; break; - case LICENSE_PARSER_NOT_INITIALIZED_4: *os << "LICENSE_PARSER_NOT_INITIALIZED_4"; + case LICENSE_PARSER_NOT_INITIALIZED_4: + *os << "LICENSE_PARSER_NOT_INITIALIZED_4"; break; - case INVALID_PARAMETERS_LIC_3: *os << "INVALID_PARAMETERS_LIC_3"; + case INVALID_PARAMETERS_LIC_3: *os << "INVALID_PARAMETERS_LIC_3"; break; - case INVALID_PARAMETERS_LIC_4: *os << "INVALID_PARAMETERS_LIC_4"; + case INVALID_PARAMETERS_LIC_4: *os << "INVALID_PARAMETERS_LIC_4"; break; - case INVALID_PARAMETERS_LIC_6: *os << "INVALID_PARAMETERS_LIC_6"; + case INVALID_PARAMETERS_LIC_6: *os << "INVALID_PARAMETERS_LIC_6"; break; - case INVALID_PARAMETERS_LIC_7: *os << "INVALID_PARAMETERS_LIC_7"; + case INVALID_PARAMETERS_LIC_7: *os << "INVALID_PARAMETERS_LIC_7"; break; case LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR: *os << "LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR"; break; - case CENC_INIT_DATA_UNAVAILABLE: *os << "CENC_INIT_DATA_UNAVAILABLE"; + case CENC_INIT_DATA_UNAVAILABLE: *os << "CENC_INIT_DATA_UNAVAILABLE"; break; - case PREPARE_CENC_CONTENT_ID_FAILED: *os << "PREPARE_CENC_CONTENT_ID_FAILED"; + case PREPARE_CENC_CONTENT_ID_FAILED: + *os << "PREPARE_CENC_CONTENT_ID_FAILED"; break; - case WEBM_INIT_DATA_UNAVAILABLE: *os << "WEBM_INIT_DATA_UNAVAILABLE"; + case WEBM_INIT_DATA_UNAVAILABLE: *os << "WEBM_INIT_DATA_UNAVAILABLE"; break; - case PREPARE_WEBM_CONTENT_ID_FAILED: *os << "PREPARE_WEBM_CONTENT_ID_FAILED"; + case PREPARE_WEBM_CONTENT_ID_FAILED: + *os << "PREPARE_WEBM_CONTENT_ID_FAILED"; break; - case UNSUPPORTED_INIT_DATA_FORMAT: *os << "UNSUPPORTED_INIT_DATA_FORMAT"; + case UNSUPPORTED_INIT_DATA_FORMAT: *os << "UNSUPPORTED_INIT_DATA_FORMAT"; break; - case LICENSE_REQUEST_NONCE_GENERATION_ERROR: *os << "LICENSE_REQUEST_NONCE_GENERATION_ERROR"; + case LICENSE_REQUEST_NONCE_GENERATION_ERROR: + *os << "LICENSE_REQUEST_NONCE_GENERATION_ERROR"; break; - case LICENSE_REQUEST_SIGNING_ERROR: *os << "LICENSE_REQUEST_SIGNING_ERROR"; + case LICENSE_REQUEST_SIGNING_ERROR: *os << "LICENSE_REQUEST_SIGNING_ERROR"; break; case EMPTY_LICENSE_REQUEST: *os << "EMPTY_LICENSE_REQUEST"; break; - case DUPLICATE_SESSION_ID_SPECIFIED: *os << "DUPLICATE_SESSION_ID_SPECIFIED"; - case LICENSE_RENEWAL_PROHIBITED: *os << "LICENSE_RENEWAL_PROHIBITED"; + case DUPLICATE_SESSION_ID_SPECIFIED: + *os << "DUPLICATE_SESSION_ID_SPECIFIED"; + break; + case LICENSE_RENEWAL_PROHIBITED: *os << "LICENSE_RENEWAL_PROHIBITED"; + break; + case SESSION_FILE_HANDLE_INIT_ERROR: + *os << "SESSION_FILE_HANDLE_INIT_ERROR"; + break; + case INCORRECT_CRYPTO_MODE: *os << "INCORRECT_CRYPTO_MODE"; + break; + case INVALID_PARAMETERS_ENG_5: *os << "INVALID_PARAMETERS_ENG_5"; + break; + case DECRYPT_ERROR: *os << "DECRYPT_ERROR"; + break; + case INSUFFICIENT_OUTPUT_PROTECTION: + *os << "INSUFFICIENT_OUTPUT_PROTECTION"; + break; + case SESSION_NOT_FOUND_12: *os << "SESSION_NOT_FOUND_12"; + break; + case KEY_NOT_FOUND_1: *os << "KEY_NOT_FOUND_1"; + break; + case KEY_NOT_FOUND_2: *os << "KEY_NOT_FOUND_2"; + break; + case KEY_CONFLICT_1: *os << "KEY_CONFLICT_1"; + break; + case INVALID_PARAMETERS_ENG_6: *os << "INVALID_PARAMETERS_ENG_6"; + break; + case INVALID_PARAMETERS_ENG_7: *os << "INVALID_PARAMETERS_ENG_7"; + break; + case INVALID_PARAMETERS_ENG_8: *os << "INVALID_PARAMETERS_ENG_8"; + break; + case INVALID_PARAMETERS_ENG_9: *os << "INVALID_PARAMETERS_ENG_9"; + break; + case INVALID_PARAMETERS_ENG_10: *os << "INVALID_PARAMETERS_ENG_10"; + break; + case INVALID_PARAMETERS_ENG_11: *os << "INVALID_PARAMETERS_ENG_11"; + break; + case INVALID_PARAMETERS_ENG_12: *os << "INVALID_PARAMETERS_ENG_12"; + break; + case SESSION_NOT_FOUND_13: *os << "SESSION_NOT_FOUND_13"; + break; + case SESSION_NOT_FOUND_14: *os << "SESSION_NOT_FOUND_14"; + break; + case SESSION_NOT_FOUND_15: *os << "SESSION_NOT_FOUND_15"; + break; + case SESSION_NOT_FOUND_16: *os << "SESSION_NOT_FOUND_16"; + break; + case KEY_NOT_FOUND_3: *os << "KEY_NOT_FOUND_3"; + break; + case KEY_NOT_FOUND_4: *os << "KEY_NOT_FOUND_4"; + break; + case KEY_NOT_FOUND_5: *os << "KEY_NOT_FOUND_5"; + break; + case KEY_NOT_FOUND_6: *os << "KEY_NOT_FOUND_6"; + break; + case KEY_ERROR_1: *os << "KEY_ERROR_1"; + break; + case KEY_ERROR_2: *os << "KEY_ERROR_2"; + break; + case KEY_ERROR_3: *os << "KEY_ERROR_3"; + break; + case KEY_ERROR_4: *os << "KEY_ERROR_4"; + break; + case INVALID_PARAMETERS_ENG_13: *os << "INVALID_PARAMETERS_ENG_13"; + break; + case INVALID_PARAMETERS_ENG_14: *os << "INVALID_PARAMETERS_ENG_14"; + break; + case INVALID_PARAMETERS_ENG_15: *os << "INVALID_PARAMETERS_ENG_15"; + break; + case INVALID_PARAMETERS_ENG_16: *os << "INVALID_PARAMETERS_ENG_16"; break; default: - *os << "Unknown CdmResponseType"; + *os << "Unknown CdmResponseType"; break; } } @@ -364,7 +469,7 @@ void PrintTo(const enum CdmLicenseType& value, ::std::ostream* os) { void PrintTo(const enum CdmSecurityLevel& value, ::std::ostream* os) { switch (value) { - case kSecurityLevelUninitialized: *os << "kSecurityLevelUninitialized"; + case kSecurityLevelUninitialized: *os << "kSecurityLevelUninitialized"; break; case kSecurityLevelL1: *os << "kSecurityLevelL1"; break; @@ -387,7 +492,7 @@ void PrintTo(const enum CdmCertificateType& value, ::std::ostream* os) { case kCertificateX509: *os << "kCertificateX509"; break; default: - *os << "Unknown CdmCertificateType"; + *os << "Unknown CdmCertificateType"; break; } }; diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index 1056d691..ae23c5c9 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -121,6 +121,14 @@ typedef struct { } buffer; } OEMCrypto_DestBufferDesc; +/** OEMCryptoCipherMode is used in LoadKeys to prepare a key for either CTR + * decryption or CBC decryption. + */ +typedef enum OEMCryptoCipherMode { + OEMCrypto_CipherMode_CTR, + OEMCrypto_CipherMode_CBC, +} OEMCryptoCipherMode; + /* * OEMCrypto_KeyObject * Points to the relevant fields for a content key. The fields are extracted @@ -137,6 +145,8 @@ typedef struct { * key_control field. * key_control - the key control block. It is encrypted (AES-128-CBC) with * the content key from the key_data field. + * cipher_mode - whether the key should be prepared for CTR mode or CBC mode + * when used in later calls to DecryptCENC. * * The memory for the OEMCrypto_KeyObject fields is allocated and freed * by the caller of OEMCrypto_LoadKeys(). @@ -149,6 +159,7 @@ typedef struct { size_t key_data_length; const uint8_t* key_control_iv; const uint8_t* key_control; + OEMCryptoCipherMode cipher_mode; } OEMCrypto_KeyObject; /* @@ -188,11 +199,21 @@ typedef enum OEMCrypto_Algorithm { } OEMCrypto_Algorithm; /* - * Flags indicating data endpoints in OEMCrypto_DecryptCTR. + * Flags indicating data endpoints in OEMCrypto_DecryptCENC. */ #define OEMCrypto_FirstSubsample 1 #define OEMCrypto_LastSubsample 2 +/* OEMCrypto_CENCEncryptPatternDesc + * This is used in OEMCrypto_DecryptCENC to indicate the encrypt/skip pattern + * used, as specified in the CENC standard. + */ +typedef struct { + size_t encrypt; // number of 16 byte blocks to decrypt. + size_t skip; // number of 16 byte blocks to leave in clear. + size_t offset; // offset into the pattern in blocks for this call. +} OEMCrypto_CENCEncryptPatternDesc; + /* * OEMCrypto_Usage_Entry_Status. * Valid values for status in the usage table. @@ -260,7 +281,7 @@ typedef enum OEMCrypto_HDCP_Capability { #define OEMCrypto_WrapKeybox _oecc08 #define OEMCrypto_OpenSession _oecc09 #define OEMCrypto_CloseSession _oecc10 -#define OEMCrypto_DecryptCTR _oecc11 +#define OEMCrypto_DecryptCTR_V10 _oecc11 #define OEMCrypto_GenerateDerivedKeys _oecc12 #define OEMCrypto_GenerateSignature _oecc13 #define OEMCrypto_GenerateNonce _oecc14 @@ -284,7 +305,7 @@ typedef enum OEMCrypto_HDCP_Capability { #define OEMCrypto_ReportUsage _oecc32 #define OEMCrypto_DeleteUsageEntry _oecc33 #define OEMCrypto_DeleteUsageTable _oecc34 -#define OEMCrypto_LoadKeys _oecc35 +#define OEMCrypto_LoadKeys_V9_or_V10 _oecc35 #define OEMCrypto_GenerateRSASignature _oecc36 #define OEMCrypto_GetMaxNumberOfSessions _oecc37 #define OEMCrypto_GetNumberOfOpenSessions _oecc38 @@ -295,6 +316,9 @@ typedef enum OEMCrypto_HDCP_Capability { #define OEMCrypto_ForceDeleteUsageEntry _oecc43 #define OEMCrypto_GetHDCPCapability _oecc44 #define OEMCrypto_LoadTestRSAKey _oecc45 +#define OEMCrypto_Security_Patch_Level _oecc46 +#define OEMCrypto_LoadKeys _oecc47 +#define OEMCrypto_DecryptCENC _oecc48 /* @@ -373,7 +397,8 @@ OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION *session); * OEMCrypto_CloseSession * * Description: - * Closes the crypto security engine session and frees any associated resources. + * Closes the crypto security engine session and frees any associated + * resources. * * Parameters: * session (in) - handle for the session to be closed. @@ -565,10 +590,12 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * the previous call to OEMCrypto_GenerateNonce(). * * This session’s elapsed time clock is started at 0. The clock will be used - * in OEMCrypto_DecryptCTR(). + * in OEMCrypto_DecryptCENC(). * - * NOTE: OEMCrypto_GenerateDerivedKeys() must be called first to establish the - * mac_key and encrypt_key. + * NOTE: The calling software must have previously established the mac_keys + * and encrypt_key with a call to OEMCrypto_GenerateDerivedKeys(), + * OEMCrypto_DeriveKeysFromSessionKey(), or a previous call to + * OEMCrypto_LoadKeys(). * * Refer to document "Widevine Modular DRM Security Integration Guide for * CENC" for details. @@ -604,9 +631,15 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * the cache. Note that all the key control blocks in a particular call shall * have the same nonce value. * - * 6. If the key control block has a nonzero Replay_Control, then the + * 6. If any key control block has the Require_AntiRollback_Hardware bit set, + * and the device does not protect the usage table from rollback, then do not + * load the keys and return OEMCrypto_ERROR_UNKNOWN_FAILURE. + * + * 7. If the key control block has a nonzero Replay_Control, then the * verification described below is also performed. * + * 8. If num_keys == 0, then return OEMCrypto_ERROR_INVALID_CONTEXT. + * * Usage Table and Provider Session Token (pst): * * If a key control block has a nonzero value for Replay_Control, then all keys @@ -652,6 +685,10 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * Devices that do not support the Usage Table will return * OEMCrypto_ERROR_INVALID_CONTEXT if the Replay_Control is nonzero. * + * Note: If LoadKeys creates a new entry in the usage table, OEMCrypto will + * increment the Usage Table’s generation number, and then sign, encrypt, and + * save the Usage Table. + * * Parameters: * session (in) - crypto session identifier. * message (in) - pointer to memory containing message to be verified. @@ -681,7 +718,7 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * OEMCrypto_ERROR_UNKNOWN_FAILURE * * Version: - * This method changed in API version 9. + * This method changed in API version 11. */ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session, const uint8_t* message, @@ -714,7 +751,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session, * first to establish the mac_key[server]. * * This session’s elapsed time clock is reset to 0 when this function is called. - * The elapsed time clock is used in OEMCrypto_DecryptCTR(). + * The elapsed time clock is used in OEMCrypto_DecryptCENC(). * * This function does not add keys to the key table. It is only used to update a * key control block license duration. Refer to the License Signing and @@ -849,7 +886,7 @@ OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * * Description: * Select a content key and install it in the hardware key ladder for - * subsequent decryption operations (OEMCrypto_DecryptCTR()) for this session. + * subsequent decryption operations (OEMCrypto_DecryptCENC()) for this session. * The specified key must have been previously "installed" via * OEMCrypto_LoadKeys() or OEMCrypto_RefreshKeys(). * @@ -864,7 +901,7 @@ OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * permission flags and timers based on the key's control block. * * Step 3: use the latched content key to decrypt (AES-128-CTR) buffers passed in - * via OEMCrypto_DecryptCTR(). If the key is 256 bits it will be used for + * via OEMCrypto_DecryptCENC(). If the key is 256 bits it will be used for * OEMCrypto_Generic_Sign or OEMCrypto_Generic_Verify as specified in the key * control block. Continue to use this key until OEMCrypto_SelectKey() is called * again, or until OEMCrypto_CloseSession() is called. @@ -898,15 +935,17 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, size_t key_id_length); /* - * OEMCrypto_DecryptCTR + * OEMCrypto_DecryptCENC * * Description: - * Decrypts (AES-128-CTR) or copies the payload in the buffer referenced by the - * data_addr parameter into the buffer referenced by the out_buffer parameter, - * using the session context indicated by the session parameter. If is_encrypted - * is true, the content key associated with the session is latched in the active - * hardware key ladder and is used for the decryption operation. If is_encrypted - * is false, the data is simply copied. + * Decrypts or copies the payload in the buffer referenced by the *data_addr + * parameter into the buffer referenced by the out_buffer parameter, using + * the session context indicated by the session parameter. Decryption mode + * is AES-128-CTR or AES-128-CBC depending on the value of cipher_mode set in + * the OEMCrypto_KeyObject passed in to OEMCrypto_LoadKeys. If is_encrypted + * is true, the content key associated with the session is latched in the + * active hardware key ladder and is used for the decryption operation. If + * is_encrypted is false, the data is simply copied. * * After decryption, the data_length bytes are copied to the location described * by out_buffer. This could be one of @@ -921,13 +960,13 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * the decoder and rendered. * * NOTES: - * IV points to the counter value to be used for the initial - * encrypted block of the input buffer. The IV length is the AES - * block size. For subsequent encrypted AES blocks the IV is - * calculated by incrementing the lower 64 bits (byte 8-15) of the - * IV value used for the previous block. The counter rolls over to - * zero when it reaches its maximum value (0xFFFFFFFFFFFFFFFF). - * The upper 64 bits (byte 0-7) of the IV do not change. + * For CTR mode, IV points to the counter value to be used for the initial + * encrypted block of the input buffer. The IV length is the AES block + * size. For subsequent encrypted AES blocks the IV is calculated by + * incrementing the lower 64 bits (byte 8-15) of the IV value used for the + * previous block. The counter rolls over to zero when it reaches its maximum + * value (0xFFFFFFFFFFFFFFFF). The upper 64 bits (byte 0-7) of the IV do not + * change. * * This method may be called several times before the decrypted data is used. * For this reason, the parameter subsample_flags may be used to optimize @@ -938,7 +977,7 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * OEMCrypto_LastSubsample has been set. If an implementation decrypts data * immediately, it may ignore subsample_flags. * - * If the destination buffer is secure, an offset may be specified. DecryptCTR + * If the destination buffer is secure, an offset may be specified. DecryptCENC * begins storing data out_buffer->secure.offset bytes after the beginning of the * secure buffer. * @@ -946,6 +985,13 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * time_of_last_decrypt. If the status of the entry is "unused", then change the * status to "active" and set the time_of_first_decrypt. * + * The decryption mode, either OEMCrypto_CipherMode_CTR or + * OEMCrypto_CipherMode_CBC, was specified in the call to OEMCrypto_LoadKeys. + * The encryption pattern is specified in by the parameter pattern. A + * description of partial encryption patterns can be found in the document + * Draft International Standard ISO/IEC DIS 23001-7. Search for the codes + * "cenc", "cbc1", "cens" or "cbcs". + * * * Verification: * The following checks should be performed if is_encrypted is true. If any @@ -992,9 +1038,12 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * decryption block start address and data_addr are discarded * after decryption. It does not adjust the beginning of the * source or destination data. This parameter satisfies - * 0 <= block_offset < 16. + * 0 <= block_offset < 16. This paramater is only used + * for CTR mode. * out_buffer (in) - A caller-owned descriptor that specifies the handling of the * decrypted byte stream. See OEMCrypto_DestbufferDesc for details. + * pattern (in) - A caller-owned structure indicating the encrypt/skip + * pattern as specified in the CENC standard. * subsample_flags (in) - bitwise flags indicating if this is the first, middle, * or last subsample in a chunk of data. * 0 = neither first nor last subsample, @@ -1018,16 +1067,18 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * OEMCrypto_ERROR_UNKNOWN_FAILURE * * Version: - * This method changed in API version 9. + * This method changed in API version 11. + * This method changed its name in API version 11. */ -OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session, - const uint8_t *data_addr, - size_t data_length, - bool is_encrypted, - const uint8_t *iv, - size_t block_offset, - OEMCrypto_DestBufferDesc* out_buffer, - uint8_t subsample_flags); +OEMCryptoResult OEMCrypto_DecryptCENC(OEMCrypto_SESSION session, + const uint8_t *data_addr, + size_t data_length, + bool is_encrypted, + const uint8_t *iv, + size_t block_offset, + OEMCrypto_DestBufferDesc* out_buffer, + const OEMCrypto_CENCEncryptPatternDesc* pattern, + uint8_t subsample_flags); /* @@ -1037,9 +1088,9 @@ OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session, * Copies the payload in the buffer referenced by the *data_addr parameter into * the buffer referenced by the out_buffer parameter. The data is simply * copied. The definition of OEMCrypto_DestBufferDesc and subsample_flags are - * the same as in OEMCrypto_DecryptCTR, above. + * the same as in OEMCrypto_DecryptCENC, above. * - * The main difference between this and DecryptCTR is that this function does + * The main difference between this and DecryptCENC is that this function does * not need an open session, and it may be called concurrently with other * session functions on a multithreaded system. In particular, an application * will use this to copy the clear leader of a video to a secure buffer while @@ -1648,9 +1699,6 @@ OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(OEMCrypto_SESSION session, * There is no plan to introduce forward-compatibility. Applications will reject * a library with a newer version of the API. * - * The version specified in this document is 9. Any OEM that returns this - * version number guarantees it passes all unit tests associated this version. - * * Parameters: * none * @@ -1665,6 +1713,28 @@ OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(OEMCrypto_SESSION session, */ uint32_t OEMCrypto_APIVersion(); +/** + * OEMCrypto_Security_Patch_Level() + * + * Description: + * This function returns the current patch level of the software running in + * the trusted environment. The patch level is defined by the OEM, and is + * only incremented when a security update has been added. + * + * Parameters: + * none + * + * Returns: + * The OEM defined version number. + * + * Threading: + * This function may be called simultaneously with any other functions. + * + * Version: + * This method was introduced in API version 11. + */ +uint8_t OEMCrypto_Security_Patch_Level(); + /* * OEMCrypto_SecurityLevel() * diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp new file mode 100644 index 00000000..d1c639b4 --- /dev/null +++ b/oemcrypto/test/oec_device_features.cpp @@ -0,0 +1,144 @@ +#include "oec_device_features.h" + +#include + +#include + +#include "oec_test_data.h" + +namespace wvoec { + +DeviceFeatures global_features; + +void DeviceFeatures::Initialize(bool is_cast_receiver, + bool force_load_test_keybox) { + cast_receiver = is_cast_receiver; + uses_keybox = false; + uses_certificate = false; + loads_certificate = false; + generic_crypto = false; + usage_table = false; + api_version = 0; + derive_key_method = NO_METHOD; + if (OEMCrypto_SUCCESS != OEMCrypto_Initialize()) { + printf("OEMCrypto_Initialze failed. All tests will fail.\n"); + return; + } + uint32_t nonce = 0; + uint8_t buffer[1]; + size_t size = 0; + uses_keybox = + (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_GetKeyData(buffer, &size)); + printf("uses_keybox = %s.\n", uses_keybox ? "true" : "false"); + loads_certificate = uses_keybox && (OEMCrypto_ERROR_NOT_IMPLEMENTED != + OEMCrypto_RewrapDeviceRSAKey( + 0, buffer, 0, buffer, 0, &nonce, + buffer, 0, buffer, buffer, &size)); + printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); + uses_certificate = (OEMCrypto_ERROR_NOT_IMPLEMENTED != + OEMCrypto_GenerateRSASignature(0, buffer, 0, buffer, + &size, kSign_RSASSA_PSS)); + printf("uses_certificate = %s.\n", uses_certificate ? "true" : "false"); + generic_crypto = + (OEMCrypto_ERROR_NOT_IMPLEMENTED != + OEMCrypto_Generic_Encrypt(0, buffer, 0, buffer, + OEMCrypto_AES_CBC_128_NO_PADDING, buffer)); + printf("generic_crypto = %s.\n", generic_crypto ? "true" : "false"); + api_version = OEMCrypto_APIVersion(); + printf("api_version = %d.\n", api_version); + usage_table = OEMCrypto_SupportsUsageTable(); + printf("usage_table = %s.\n", usage_table ? "true" : "false"); + if (force_load_test_keybox) { + derive_key_method = FORCE_TEST_KEYBOX; + } else { + PickDerivedKey(); + } + printf("cast_receiver = %s.\n", cast_receiver ? "true" : "false"); + switch (derive_key_method) { + case NO_METHOD: + printf("NO_METHOD: Cannot derive known session keys.\n"); + // Note: cast_receiver left unchanged because set by user on command line. + uses_keybox = false; + uses_certificate = false; + loads_certificate = false; + generic_crypto = false; + usage_table = false; + break; + case LOAD_TEST_KEYBOX: + printf("LOAD_TEST_KEYBOX: Call LoadTestKeybox before deriving keys.\n"); + break; + case LOAD_TEST_RSA_KEY: + printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n"); + break; + case EXISTING_TEST_KEYBOX: + printf("EXISTING_TEST_KEYBOX: Keybox is already the test keybox.\n"); + break; + case FORCE_TEST_KEYBOX: + printf("FORCE_TEST_KEYBOX: User requested calling InstallKeybox.\n"); + break; + } + OEMCrypto_Terminate(); +} + +std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { + std::string filter = initial_filter; + if (!uses_keybox) FilterOut(&filter, "*KeyboxTest*"); + if (derive_key_method + != FORCE_TEST_KEYBOX) FilterOut(&filter, "*ForceKeybox*"); + if (!uses_certificate) FilterOut(&filter, "OEMCrypto*Cert*"); + if (!loads_certificate) FilterOut(&filter, "OEMCryptoLoadsCert*"); + if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); + if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); + if (!usage_table) FilterOut(&filter, "*UsageTable*"); + if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*"); + if (api_version < 10) FilterOut(&filter, "*API10*"); + if (api_version < 11) FilterOut(&filter, "*API11*"); + // Performance tests take a long time. Filter them out if they are not + // specifically requested. + if (filter.find("Performance") == std::string::npos) { + FilterOut(&filter, "*Performance*"); + } + return filter; +} + +void DeviceFeatures::PickDerivedKey() { + if (uses_keybox) { + // If device uses a keybox, try to load the test keybox. + if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestKeybox()) { + derive_key_method = LOAD_TEST_KEYBOX; + } else if (IsTestKeyboxInstalled()) { + derive_key_method = EXISTING_TEST_KEYBOX; + } + } else if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) { + derive_key_method = LOAD_TEST_RSA_KEY; + } +} + +bool DeviceFeatures::IsTestKeyboxInstalled() { + uint8_t key_data[256]; + size_t key_data_len = sizeof(key_data); + if (OEMCrypto_GetKeyData(key_data, &key_data_len) != OEMCrypto_SUCCESS) + return false; + if (key_data_len != sizeof(kTestKeybox.data_)) return false; + if (memcmp(key_data, kTestKeybox.data_, key_data_len)) return false; + uint8_t dev_id[128] = {0}; + size_t dev_id_len = 128; + if (OEMCrypto_GetDeviceID(dev_id, &dev_id_len) != OEMCrypto_SUCCESS) + return false; + // We use strncmp instead of memcmp because we don't really care about the + // multiple '\0' characters at the end of the device id. + return 0 == strncmp(reinterpret_cast(dev_id), + reinterpret_cast(kTestKeybox.device_id_), + sizeof(kTestKeybox.device_id_)); +} + +void DeviceFeatures::FilterOut(std::string* current_filter, + const std::string& new_filter) { + if (current_filter->find('-') == std::string::npos) { + *current_filter += "-" + new_filter; + } else { + *current_filter += ":" + new_filter; + } +} + +} // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test.h b/oemcrypto/test/oec_device_features.h similarity index 91% rename from oemcrypto/test/oemcrypto_test.h rename to oemcrypto/test/oec_device_features.h index 4a3988c7..76a3fc03 100644 --- a/oemcrypto/test/oemcrypto_test.h +++ b/oemcrypto/test/oec_device_features.h @@ -1,5 +1,7 @@ -#ifndef CDM_OEMCRYPTO_TEST_H_ -#define CDM_OEMCRYPTO_TEST_H_ +#ifndef CDM_OEC_DEVICE_FEATURES_H_ +#define CDM_OEC_DEVICE_FEATURES_H_ + +#include #include "OEMCryptoCENC.h" #include "wv_keybox.h" @@ -38,4 +40,4 @@ extern DeviceFeatures global_features; } // namespace wvoec -#endif // CDM_OEMCRYPTO_TEST_H_ +#endif // CDM_OEC_DEVICE_FEATURES_H_ diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp new file mode 100644 index 00000000..79495426 --- /dev/null +++ b/oemcrypto/test/oec_session_util.cpp @@ -0,0 +1,802 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// OEMCrypto unit tests +// + +#include "oec_session_util.h" + +#include // needed for ntoh() +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "log.h" +#include "oec_device_features.h" +#include "oec_test_data.h" +#include "oemcrypto_key_mock.h" +#include "OEMCryptoCENC.h" +#include "string_conversions.h" +#include "wv_cdm_constants.h" +#include "wv_keybox.h" + +using namespace std; + +// GTest requires PrintTo to be in the same namespace as the thing it prints, +// which is std::vector in this case. +namespace std { + +void PrintTo(const vector& value, ostream* os) { + *os << wvcdm::b2a_hex(value); +} + +void PrintTo(const PatternTestVariant& param, ostream* os) { + *os << ((param.mode == OEMCrypto_CipherMode_CTR) ? "CTR mode" : "CBC mode") + << ", encrypt=" << param.pattern.encrypt + << ", skip=" << param.pattern.skip; +} + +} // namespace std + +namespace wvoec { + +// Increment counter for AES-CTR. The CENC spec specifies we increment only +// the low 64 bits of the IV counter, and leave the high 64 bits alone. This +// is different from the OpenSSL implementation, so we implement the CTR loop +// ourselves. +void ctr128_inc64(int64_t increaseBy, uint8_t* iv) { + ASSERT_NE(static_cast(NULL), iv); + uint64_t* counterBuffer = reinterpret_cast(&iv[8]); + (*counterBuffer) = + wvcdm::htonll64(wvcdm::ntohll64(*counterBuffer) + increaseBy); +} + +// Some compilers don't like the macro htonl within an ASSERT_EQ. +uint32_t htonl_fnc(uint32_t x) { return htonl(x); } + +void dump_openssl_error() { + while (unsigned long err = ERR_get_error()) { + char buffer[120]; + ERR_error_string_n(err, buffer, sizeof(buffer)); + cout << "openssl error -- " << buffer << "\n"; + } +} + +Session::Session() + : open_(false), + forced_session_id_(false), + session_id_(0), + mac_key_server_(wvcdm::MAC_KEY_SIZE), + mac_key_client_(wvcdm::MAC_KEY_SIZE), + enc_key_(wvcdm::KEY_SIZE), + public_rsa_(0) {} + +Session::~Session() { + if (!forced_session_id_ && open_) close(); + if (public_rsa_) RSA_free(public_rsa_); +} + +void Session::open() { + EXPECT_FALSE(forced_session_id_); + EXPECT_FALSE(open_); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_OpenSession(&session_id_)); + open_ = true; +} + +void Session::SetSessionId(uint32_t session_id) { + EXPECT_FALSE(open_); + session_id_ = session_id; + forced_session_id_ = true; +} + +void Session::close() { + EXPECT_TRUE(open_ || forced_session_id_); + if (open_) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CloseSession(session_id_)); + } + forced_session_id_ = false; + open_ = false; +} + +void Session::GenerateNonce(uint32_t* nonce, int* error_counter) { + if (OEMCrypto_SUCCESS == OEMCrypto_GenerateNonce(session_id(), nonce)) { + return; + } + if (error_counter) { + (*error_counter)++; + } else { + sleep(1); // wait a second, then try again. + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateNonce(session_id(), nonce)); + } +} + +void Session::FillDefaultContext(vector* mac_context, + vector* enc_context) { + /* Context strings + * These context strings are normally created by the CDM layer + * from a license request message. + * They are used to test MAC and ENC key generation. + */ + *mac_context = wvcdm::a2b_hex( + "41555448454e5449434154494f4e000a4c08001248000000020000101907d9ff" + "de13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e5873" + "4930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a" + "230a14080112100915007caa9b5931b76a3a85f046523e10011a093938373635" + "34333231180120002a0c31383836373837343035000000000200"); + *enc_context = wvcdm::a2b_hex( + "454e4352595054494f4e000a4c08001248000000020000101907d9ffde13aa95" + "c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930aceb" + "e899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a1408" + "0112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231" + "180120002a0c31383836373837343035000000000080"); +} + +void Session::GenerateDerivedKeysFromKeybox() { + GenerateNonce(&nonce_); + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateDerivedKeys(session_id(), &mac_context[0], + mac_context.size(), &enc_context[0], + enc_context.size())); + + // Expected MAC and ENC keys generated from context strings + // with test keybox "installed". + mac_key_server_ = wvcdm::a2b_hex( + "3CFD60254786AF350B353B4FBB700AB382558400356866BA16C256BCD8C502BF"); + mac_key_client_ = wvcdm::a2b_hex( + "A9DE7B3E4E199ED8D1FBC29CD6B4C772CC4538C8B0D3E208B3E76F2EC0FD6F47"); + enc_key_ = wvcdm::a2b_hex("D0BFC35DA9E33436E81C4229E78CB9F4"); +} + +void Session::GenerateDerivedKeysFromSessionKey() { + // Uses test certificate. + GenerateNonce(&nonce_); + vector enc_session_key; + PreparePublicKey(); + ASSERT_TRUE(GenerateRSASessionKey(&enc_session_key)); + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_DeriveKeysFromSessionKey( + session_id(), &enc_session_key[0], enc_session_key.size(), + &mac_context[0], mac_context.size(), &enc_context[0], + enc_context.size())); + + // Expected MAC and ENC keys generated from context strings + // with RSA certificate "installed". + mac_key_server_ = wvcdm::a2b_hex( + "1E451E59CB663DA1646194DD28880788ED8ED2EFF913CBD6A0D535D1D5A90381"); + mac_key_client_ = wvcdm::a2b_hex( + "F9AAE74690909F2207B53B13307FCA096CA8C49CC6DFE3659873CB952889A74B"); + enc_key_ = wvcdm::a2b_hex("CB477D09014D72C9B8DCE76C33EA43B3"); +} + +void Session::GenerateTestSessionKeys() { + if (global_features.derive_key_method == DeviceFeatures::LOAD_TEST_RSA_KEY) { + GenerateDerivedKeysFromSessionKey(); + } else { + GenerateDerivedKeysFromKeybox(); + } +} + +void Session::LoadTestKeys(const std::string& pst, bool new_mac_keys) { + uint8_t* pst_ptr = NULL; + if (pst.length() > 0) { + pst_ptr = encrypted_license_.pst; + } + if (new_mac_keys) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys( + session_id(), message_ptr(), sizeof(MessageData), + &signature_[0], signature_.size(), + encrypted_license_.mac_key_iv, encrypted_license_.mac_keys, + kNumKeys, key_array_, pst_ptr, pst.length())); + // Update new generated keys. + memcpy(&mac_key_server_[0], license_.mac_keys, wvcdm::MAC_KEY_SIZE); + memcpy(&mac_key_client_[0], license_.mac_keys + wvcdm::MAC_KEY_SIZE, + wvcdm::MAC_KEY_SIZE); + } else { + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(session_id(), message_ptr(), sizeof(MessageData), + &signature_[0], signature_.size(), NULL, NULL, + kNumKeys, key_array_, pst_ptr, pst.length())); + } + VerifyTestKeys(); +} + +void Session::VerifyTestKeys() { + for (unsigned int i = 0; i < kNumKeys; i++) { + KeyControlBlock block; + size_t size = sizeof(block); + OEMCryptoResult sts = OEMCrypto_QueryKeyControl( + session_id(), license_.keys[i].key_id, license_.keys[i].key_id_length, + reinterpret_cast(&block), &size); + if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(sizeof(block), size); + // control duration and bits stored in network byte order. For printing + // we change to host byte order. + ASSERT_EQ((htonl_fnc(license_.keys[i].control.duration)), + (htonl_fnc(block.duration))) << "For key " << i; + ASSERT_EQ(htonl_fnc(license_.keys[i].control.control_bits), + htonl_fnc(block.control_bits)) << "For key " << i; + } + } +} + +void Session::RefreshTestKeys(const size_t key_count, + uint32_t control_bits, uint32_t nonce, + OEMCryptoResult expected_result) { + // Note: we store the message in encrypted_license_, but the refresh key + // message is not actually encrypted. It is, however, signed. + FillRefreshMessage(key_count, control_bits, nonce); + ServerSignMessage(encrypted_license_, &signature_); + OEMCrypto_KeyRefreshObject key_array[key_count]; + FillRefreshArray(key_array, key_count); + OEMCryptoResult sts = OEMCrypto_RefreshKeys( + session_id(), message_ptr(), sizeof(MessageData), &signature_[0], + signature_.size(), key_count, key_array); + ASSERT_EQ(expected_result, sts); + + ASSERT_NO_FATAL_FAILURE(TestDecryptCTR()); + sleep(kShortSleep); // Should still be valid key. + ASSERT_NO_FATAL_FAILURE(TestDecryptCTR(false)); + sleep(kShortSleep + kLongSleep); // Should be after first expiration. + if (expected_result == OEMCrypto_SUCCESS) { + ASSERT_NO_FATAL_FAILURE(TestDecryptCTR(false, OEMCrypto_SUCCESS)); + } else { + ASSERT_NO_FATAL_FAILURE( + TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + } +} + +void Session::SetKeyId(int index, const string& key_id) { + MessageKeyData& key = license_.keys[index]; + key.key_id_length = key_id.length(); + ASSERT_LE(key.key_id_length, kTestKeyIdMaxLength); + memcpy(key.key_id, key_id.data(), key.key_id_length); +} + +void Session::FillSimpleMessage( + uint32_t duration, uint32_t control, uint32_t nonce, + const std::string& pst) { + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.mac_key_iv, + sizeof(license_.mac_key_iv))); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.mac_keys, sizeof(license_.mac_keys))); + for (unsigned int i = 0; i < kNumKeys; i++) { + memset(license_.keys[i].key_id, 0, kTestKeyIdMaxLength); + license_.keys[i].key_id_length = kDefaultKeyIdLength; + memset(license_.keys[i].key_id, i, license_.keys[i].key_id_length); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.keys[i].key_data, + sizeof(license_.keys[i].key_data))); + license_.keys[i].key_data_length = wvcdm::KEY_SIZE; + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.keys[i].key_iv, + sizeof(license_.keys[i].key_iv))); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.keys[i].control_iv, + sizeof(license_.keys[i].control_iv))); + if (control & wvoec_mock::kControlSecurityPatchLevelMask) { + memcpy(license_.keys[i].control.verification, "kc11", 4); + } else if (control & wvoec_mock::kControlRequireAntiRollbackHardware) { + memcpy(license_.keys[i].control.verification, "kc10", 4); + } else if (control & (wvoec_mock::kControlHDCPVersionMask | + wvoec_mock::kControlReplayMask)) { + memcpy(license_.keys[i].control.verification, "kc09", 4); + } else { + memcpy(license_.keys[i].control.verification, "kctl", 4); + } + license_.keys[i].control.duration = htonl(duration); + license_.keys[i].control.nonce = htonl(nonce); + license_.keys[i].control.control_bits = htonl(control); + license_.keys[i].cipher_mode = OEMCrypto_CipherMode_CTR; + } + memcpy(license_.pst, pst.c_str(), min(sizeof(license_.pst), pst.length())); +} + +void Session::FillRefreshMessage(size_t key_count, uint32_t control_bits, + uint32_t nonce) { + for (unsigned int i = 0; i < key_count; i++) { + encrypted_license_.keys[i].key_id_length = license_.keys[i].key_id_length; + memcpy(encrypted_license_.keys[i].key_id, license_.keys[i].key_id, + encrypted_license_.keys[i].key_id_length); + memcpy(encrypted_license_.keys[i].control.verification, "kctl", 4); + encrypted_license_.keys[i].control.duration = htonl(kLongDuration); + encrypted_license_.keys[i].control.nonce = htonl(nonce); + encrypted_license_.keys[i].control.control_bits = htonl(control_bits); + } +} + +void Session::EncryptAndSign() { + encrypted_license_ = license_; + + uint8_t iv_buffer[16]; + memcpy(iv_buffer, &license_.mac_key_iv[0], wvcdm::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); + AES_cbc_encrypt(&license_.mac_keys[0], &encrypted_license_.mac_keys[0], + 2 * wvcdm::MAC_KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); + + for (unsigned int i = 0; i < kNumKeys; i++) { + memcpy(iv_buffer, &license_.keys[i].control_iv[0], wvcdm::KEY_IV_SIZE); + AES_set_encrypt_key(&license_.keys[i].key_data[0], 128, &aes_key); + AES_cbc_encrypt( + reinterpret_cast(&license_.keys[i].control), + reinterpret_cast(&encrypted_license_.keys[i].control), + wvcdm::KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); + + memcpy(iv_buffer, &license_.keys[i].key_iv[0], wvcdm::KEY_IV_SIZE); + AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); + AES_cbc_encrypt(&license_.keys[i].key_data[0], + &encrypted_license_.keys[i].key_data[0], + license_.keys[i].key_data_length, &aes_key, iv_buffer, + AES_ENCRYPT); + } + memcpy(encrypted_license_.pst, license_.pst, sizeof(license_.pst)); + ServerSignMessage(encrypted_license_, &signature_); + FillKeyArray(encrypted_license_, key_array_); +} + +void Session::EncryptMessage(RSAPrivateKeyMessage* data, + RSAPrivateKeyMessage* encrypted) { + *encrypted = *data; + size_t padding = wvcdm::KEY_SIZE - (data->rsa_key_length % wvcdm::KEY_SIZE); + memset(data->rsa_key + data->rsa_key_length, static_cast(padding), + padding); + encrypted->rsa_key_length = data->rsa_key_length + padding; + uint8_t iv_buffer[16]; + memcpy(iv_buffer, &data->rsa_key_iv[0], wvcdm::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); + AES_cbc_encrypt(&data->rsa_key[0], &encrypted->rsa_key[0], + encrypted->rsa_key_length, &aes_key, iv_buffer, + AES_ENCRYPT); +} + +template +void Session::ServerSignMessage(const T& data, + std::vector* signature) { + signature->assign(SHA256_DIGEST_LENGTH, 0); + unsigned int md_len = SHA256_DIGEST_LENGTH; + HMAC(EVP_sha256(), &mac_key_server_[0], mac_key_server_.size(), + reinterpret_cast(&data), sizeof(data), + &(signature->front()), &md_len); +} + +void Session::ClientSignMessage(const vector& data, + std::vector* signature) { + signature->assign(SHA256_DIGEST_LENGTH, 0); + unsigned int md_len = SHA256_DIGEST_LENGTH; + HMAC(EVP_sha256(), &mac_key_client_[0], mac_key_client_.size(), + &(data.front()), data.size(), &(signature->front()), &md_len); +} + +void Session::FillKeyArray(const MessageData& data, + OEMCrypto_KeyObject* key_array) { + for (unsigned int i = 0; i < kNumKeys; i++) { + key_array[i].key_id = data.keys[i].key_id; + key_array[i].key_id_length = data.keys[i].key_id_length; + key_array[i].key_data_iv = data.keys[i].key_iv; + key_array[i].key_data = data.keys[i].key_data; + key_array[i].key_data_length = data.keys[i].key_data_length; + key_array[i].key_control_iv = data.keys[i].control_iv; + key_array[i].key_control = + reinterpret_cast(&data.keys[i].control); + key_array[i].cipher_mode = data.keys[i].cipher_mode; + } +} + +void Session::FillRefreshArray(OEMCrypto_KeyRefreshObject* key_array, + size_t key_count) { + for (size_t i = 0; i < key_count; i++) { + if (key_count > 1) { + key_array[i].key_id = encrypted_license_.keys[i].key_id; + key_array[i].key_id_length = encrypted_license_.keys[i].key_id_length; + } else { + key_array[i].key_id = NULL; + key_array[i].key_id_length = 0; + } + key_array[i].key_control_iv = NULL; + key_array[i].key_control = + reinterpret_cast(&encrypted_license_.keys[i].control); + } +} + +void Session::EncryptCTR( + const vector& in_buffer, const uint8_t *key, + const uint8_t* starting_iv, vector* out_buffer) { + ASSERT_NE(static_cast(NULL), key); + ASSERT_NE(static_cast(NULL), starting_iv); + ASSERT_NE(static_cast(NULL), out_buffer); + AES_KEY aes_key; + AES_set_encrypt_key(key, AES_BLOCK_SIZE * 8, &aes_key); + out_buffer->resize(in_buffer.size()); + + uint8_t iv[AES_BLOCK_SIZE]; // Current iv. + + memcpy(iv, &starting_iv[0], AES_BLOCK_SIZE); + size_t l = 0; // byte index into encrypted subsample. + while (l < in_buffer.size()) { + uint8_t aes_output[AES_BLOCK_SIZE]; + AES_encrypt(iv, aes_output, &aes_key); + for (size_t n = 0; n < AES_BLOCK_SIZE && l < in_buffer.size(); n++, l++) { + (*out_buffer)[l] = aes_output[n] ^ in_buffer[l]; + } + ctr128_inc64(1, iv); + } +} + +void Session::TestDecryptCTR(bool select_key_first, + OEMCryptoResult expected_result) { + OEMCryptoResult sts; + if (select_key_first) { + // Select the key (from FillSimpleMessage) + sts = OEMCrypto_SelectKey(session_id(), license_.keys[0].key_id, + license_.keys[0].key_id_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + } + + vector unencryptedData(256); + for(size_t i=0; i < unencryptedData.size(); i++) unencryptedData[i] = i % 256; + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&unencryptedData[0], unencryptedData.size())); + vector encryptionIv(wvcdm::KEY_IV_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], wvcdm::KEY_IV_SIZE)); + vector encryptedData(unencryptedData.size()); + EncryptCTR(unencryptedData, license_.keys[0].key_data, &encryptionIv[0], + &encryptedData); + + // Describe the output + vector outputBuffer(256); + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer.data(); + destBuffer.buffer.clear.max_length = outputBuffer.size(); + OEMCrypto_CENCEncryptPatternDesc pattern; + pattern.encrypt = 0; + pattern.skip = 0; + pattern.offset = 0; + // Decrypt the data + sts = OEMCrypto_DecryptCENC( + session_id(), &encryptedData[0], encryptedData.size(), true, + &encryptionIv[0], 0, &destBuffer, &pattern, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + // We only have a few errors that we test are reported. + if (expected_result == OEMCrypto_SUCCESS) { // No error. + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(unencryptedData, outputBuffer); + } else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED) { + // Report stale keys. + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, sts); + ASSERT_NE(unencryptedData, outputBuffer); + } else if (expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP) { + // Report HDCP errors. + ASSERT_EQ(OEMCrypto_ERROR_INSUFFICIENT_HDCP, sts); + ASSERT_NE(unencryptedData, outputBuffer); + } else { + // OEM's can fine tune other error codes for debugging. + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(unencryptedData, outputBuffer); + } +} + +void Session::MakeRSACertificate( + struct RSAPrivateKeyMessage* encrypted, std::vector* signature, + uint32_t allowed_schemes, const vector& rsa_key) { + // Dummy context for testing signature generation. + vector context = wvcdm::a2b_hex( + "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" + "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" + "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" + "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" + "38373430350000"); + + OEMCryptoResult sts; + + // Generate signature + size_t gen_signature_length = 0; + sts = OEMCrypto_GenerateSignature(session_id(), &context[0], context.size(), + NULL, &gen_signature_length); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_EQ(static_cast(32), gen_signature_length); + vector gen_signature(gen_signature_length); + sts = OEMCrypto_GenerateSignature(session_id(), &context[0], context.size(), + &gen_signature[0], &gen_signature_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + std::vector expected_signature; + ClientSignMessage(context, &expected_signature); + ASSERT_EQ(expected_signature, gen_signature); + + // Rewrap Canned Response + + // In the real world, the signature above would just have been used to + // contact the certificate provisioning server to get this response. + + struct RSAPrivateKeyMessage message; + if (allowed_schemes != kSign_RSASSA_PSS) { + uint32_t algorithm_n = htonl(allowed_schemes); + memcpy(message.rsa_key, "SIGN", 4); + memcpy(message.rsa_key + 4, &algorithm_n, 4); + memcpy(message.rsa_key + 8, rsa_key.data(), rsa_key.size()); + message.rsa_key_length = 8 + rsa_key.size(); + } else { + memcpy(message.rsa_key, rsa_key.data(), rsa_key.size()); + message.rsa_key_length = rsa_key.size(); + } + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(message.rsa_key_iv, wvcdm::KEY_IV_SIZE)); + message.nonce = nonce_; + + EncryptMessage(&message, encrypted); + ServerSignMessage(*encrypted, signature); +} + +void Session::RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, + const std::vector& signature, + vector* wrapped_key, bool force) { + size_t wrapped_key_length = 0; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_RewrapDeviceRSAKey( + session_id(), message_ptr, sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, encrypted.rsa_key, + encrypted.rsa_key_length, encrypted.rsa_key_iv, NULL, + &wrapped_key_length)); + wrapped_key->clear(); + wrapped_key->assign(wrapped_key_length, 0); + OEMCryptoResult sts = OEMCrypto_RewrapDeviceRSAKey( + session_id(), message_ptr, sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, encrypted.rsa_key, + encrypted.rsa_key_length, encrypted.rsa_key_iv, &(wrapped_key->front()), + &wrapped_key_length); + if (force) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + } + if (OEMCrypto_SUCCESS != sts) { + wrapped_key->clear(); + } +} + +void Session::PreparePublicKey(const uint8_t* rsa_key, + size_t rsa_key_length) { + if (rsa_key == NULL) { + rsa_key = kTestRSAPKCS8PrivateKeyInfo2_2048; + rsa_key_length = sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048); + } + uint8_t* p = const_cast(rsa_key); + BIO* bio = BIO_new_mem_buf(p, rsa_key_length); + ASSERT_TRUE(NULL != bio); + PKCS8_PRIV_KEY_INFO* pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL); + ASSERT_TRUE(NULL != pkcs8_pki); + EVP_PKEY* evp = NULL; + evp = EVP_PKCS82PKEY(pkcs8_pki); + ASSERT_TRUE(NULL != evp); + if (public_rsa_) RSA_free(public_rsa_); + public_rsa_ = EVP_PKEY_get1_RSA(evp); + EVP_PKEY_free(evp); + PKCS8_PRIV_KEY_INFO_free(pkcs8_pki); + BIO_free(bio); + if (!public_rsa_) { + cout << "d2i_RSAPrivateKey failed. "; + dump_openssl_error(); + ASSERT_TRUE(false); + } + switch (RSA_check_key(public_rsa_)) { + case 1: // valid. + ASSERT_TRUE(true); + return; + case 0: // not valid. + cout << "[rsa key not valid] "; + dump_openssl_error(); + ASSERT_TRUE(false); + default: // -1 == check failed. + cout << "[error checking rsa key] "; + dump_openssl_error(); + ASSERT_TRUE(false); + } +} + +bool Session::VerifyPSSSignature( + EVP_PKEY* pkey, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length) { + EVP_MD_CTX ctx; + EVP_MD_CTX_init(&ctx); + EVP_PKEY_CTX* pctx = NULL; + + if (EVP_DigestVerifyInit(&ctx, &pctx, EVP_sha1(), NULL /* no ENGINE */, + pkey) != 1) { + LOGE("EVP_DigestVerifyInit failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_sha1()) != 1) { + LOGE("EVP_PKEY_CTX_set_signature_md failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) != 1) { + LOGE("EVP_PKEY_CTX_set_rsa_padding failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, SHA_DIGEST_LENGTH) != 1) { + LOGE("EVP_PKEY_CTX_set_rsa_pss_saltlen failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_DigestVerifyUpdate(&ctx, message, message_length) != 1) { + LOGE("EVP_DigestVerifyUpdate failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_DigestVerifyFinal(&ctx, const_cast(signature), + signature_length) != 1) { + LOGE( + "EVP_DigestVerifyFinal failed in VerifyPSSSignature. (Probably a bad " + "signature.)"); + goto err; + } + + EVP_MD_CTX_cleanup(&ctx); + return true; + + err: + dump_openssl_error(); + EVP_MD_CTX_cleanup(&ctx); + return false; +} + +void Session::VerifyRSASignature( + const vector& message, const uint8_t* signature, + size_t signature_length, RSA_Padding_Scheme padding_scheme) { + EXPECT_TRUE(NULL != public_rsa_) + << "No public RSA key loaded in test code.\n"; + EXPECT_EQ(static_cast(RSA_size(public_rsa_)), signature_length) + << "Signature size is wrong. " << signature_length << ", should be " + << RSA_size(public_rsa_) << "\n"; + + if (padding_scheme == kSign_RSASSA_PSS) { + EVP_PKEY* pkey = EVP_PKEY_new(); + ASSERT_TRUE(EVP_PKEY_set1_RSA(pkey, public_rsa_) == 1); + + const bool ok = VerifyPSSSignature(pkey, &message[0], message.size(), + signature, signature_length); + EVP_PKEY_free(pkey); + EXPECT_TRUE(ok) << "PSS signature check failed."; + } else if (padding_scheme == kSign_PKCS1_Block1) { + vector padded_digest(signature_length); + int size; + // RSA_public_decrypt decrypts the signature, and then verifies that + // it was padded with RSA PKCS1 padding. + size = RSA_public_decrypt(signature_length, signature, &padded_digest[0], + public_rsa_, RSA_PKCS1_PADDING); + EXPECT_GT(size, 0); + padded_digest.resize(size); + EXPECT_EQ(message, padded_digest); + } else { + EXPECT_TRUE(false) << "Padding scheme not supported."; + } +} + +bool Session::GenerateRSASessionKey(vector* enc_session_key) { + if (!public_rsa_) { + cout << "No public RSA key loaded in test code.\n"; + return false; + } + vector session_key = + wvcdm::a2b_hex("6fa479c731d2770b6a61a5d1420bb9d1"); + enc_session_key->assign(RSA_size(public_rsa_), 0); + int status = RSA_public_encrypt(session_key.size(), &session_key[0], + &(enc_session_key->front()), public_rsa_, + RSA_PKCS1_OAEP_PADDING); + int size = static_cast(RSA_size(public_rsa_)); + if (status != size) { + cout << "GenerateRSASessionKey error encrypting session key. "; + dump_openssl_error(); + return false; + } + return true; +} + +void Session::InstallRSASessionTestKey( + const vector& wrapped_rsa_key) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadDeviceRSAKey(session_id(), &wrapped_rsa_key[0], + wrapped_rsa_key.size())); + GenerateDerivedKeysFromSessionKey(); +} + +void Session::DisallowDeriveKeys() { + GenerateNonce(&nonce_); + vector enc_session_key; + PreparePublicKey(); + ASSERT_TRUE(GenerateRSASessionKey(&enc_session_key)); + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_DeriveKeysFromSessionKey( + session_id(), &enc_session_key[0], enc_session_key.size(), + &mac_context[0], mac_context.size(), &enc_context[0], + enc_context.size())); +} + +void Session::GenerateReport( + const std::string& pst, bool expect_success, Session* other) { + if (other) { // If other is specified, copy mac keys. + mac_key_server_ = other->mac_key_server_; + mac_key_client_ = other->mac_key_client_; + } + size_t length = 0; + OEMCryptoResult sts = OEMCrypto_ReportUsage( + session_id(), reinterpret_cast(pst.c_str()), pst.length(), + pst_report(), &length); + if (expect_success) { + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + } + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + ASSERT_LE(sizeof(OEMCrypto_PST_Report), length); + pst_report_buffer_.resize(length); + } + sts = OEMCrypto_ReportUsage(session_id(), + reinterpret_cast(pst.c_str()), + pst.length(), pst_report(), &length); + if (!expect_success) { + ASSERT_NE(OEMCrypto_SUCCESS, sts); + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector computed_signature(SHA_DIGEST_LENGTH); + unsigned int sig_len = SHA_DIGEST_LENGTH; + HMAC(EVP_sha1(), &mac_key_client_[0], mac_key_client_.size(), + reinterpret_cast(pst_report()) + SHA_DIGEST_LENGTH, + length - SHA_DIGEST_LENGTH, &computed_signature[0], &sig_len); + EXPECT_EQ(0, memcmp(&computed_signature[0], pst_report()->signature, + SHA_DIGEST_LENGTH)); + EXPECT_GE(kInactive, pst_report()->status); + EXPECT_GE(kHardwareSecureClock, pst_report()->clock_security_level); + EXPECT_EQ(pst.length(), pst_report()->pst_length); + EXPECT_EQ(0, memcmp(pst.c_str(), pst_report()->pst, pst.length())); +} + +OEMCrypto_PST_Report* Session::pst_report() { + return reinterpret_cast(&pst_report_buffer_[0]); +} + +void Session::DeleteEntry(const std::string& pst) { + uint8_t* pst_ptr = encrypted_license_.pst; + memcpy(pst_ptr, pst.c_str(), min(sizeof(license_.pst), pst.length())); + ServerSignMessage(encrypted_license_, &signature_); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_DeleteUsageEntry(session_id(), pst_ptr, pst.length(), + message_ptr(), sizeof(MessageData), + &signature_[0], signature_.size())); +} + +void Session::ForceDeleteEntry(const std::string& pst) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_ForceDeleteUsageEntry( + reinterpret_cast(pst.c_str()), pst.length())); +} + +const uint8_t* Session::message_ptr() { + return reinterpret_cast(&encrypted_license_); +} + +} // namespace wvoec diff --git a/oemcrypto/test/oec_session_util.h b/oemcrypto/test/oec_session_util.h new file mode 100644 index 00000000..4a2bcc10 --- /dev/null +++ b/oemcrypto/test/oec_session_util.h @@ -0,0 +1,207 @@ +#ifndef CDM_OEC_SESSION_UTIL_H_ +#define CDM_OEC_SESSION_UTIL_H_ + +// Copyright 2016 Google Inc. All Rights Reserved. +// +// OEMCrypto unit tests +// +#include +#include +#include + +#include "oec_device_features.h" +#include "wv_cdm_constants.h" + +using namespace std; + +// GTest requires PrintTo to be in the same namespace as the thing it prints, +// which is std::vector in this case. +namespace std { + +struct PatternTestVariant { + PatternTestVariant(size_t encrypt, size_t skip, OEMCryptoCipherMode mode) { + this->pattern.encrypt = encrypt; + this->pattern.skip = skip; + this->pattern.offset = 0; + this->mode = mode; + } + OEMCrypto_CENCEncryptPatternDesc pattern; + OEMCryptoCipherMode mode; +}; + +void PrintTo(const vector& value, ostream* os); +void PrintTo(const PatternTestVariant& param, ostream* os); + +} // namespace std + +namespace wvoec { + +const size_t kNumKeys = 4; + +namespace { +#if defined(TEST_SPEED_MULTIPLIER) // Can slow test time limits when + // debugging is slowing everything. +const int kSpeedMultiplier = TEST_SPEED_MULTIPLIER; +#else +const int kSpeedMultiplier = 1; +#endif +const int kShortSleep = 1 * kSpeedMultiplier; +const int kLongSleep = 2 * kSpeedMultiplier; +const uint32_t kDuration = 2 * kSpeedMultiplier; +const uint32_t kLongDuration = 5 * kSpeedMultiplier; +const int32_t kTimeTolerance = 3 * kSpeedMultiplier; +} // namespace + +typedef struct { + uint8_t verification[4]; + uint32_t duration; + uint32_t nonce; + uint32_t control_bits; +} KeyControlBlock; + +// Note: The API does not specify a maximum key id length. We specify a +// maximum just for these tests, so that we have a fixed message size. +const size_t kTestKeyIdMaxLength = 16; + +// Most content will use a key id that is 16 bytes long. +const int kDefaultKeyIdLength = 16; + +const size_t kMaxTestRSAKeyLength = 2000; // Rough estimate. + +typedef struct { + uint8_t key_id[kTestKeyIdMaxLength]; + size_t key_id_length; + uint8_t key_data[wvcdm::MAC_KEY_SIZE]; + size_t key_data_length; + uint8_t key_iv[wvcdm::KEY_IV_SIZE]; + uint8_t control_iv[wvcdm::KEY_IV_SIZE]; + KeyControlBlock control; + // Note: cipher_mode may not be part of a real signed message. For these + // tests, it is convenient to keep it in this structure anyway. + OEMCryptoCipherMode cipher_mode; +} MessageKeyData; + +// This structure will be signed to simulate a message from the server. +struct MessageData { + MessageKeyData keys[kNumKeys]; + uint8_t mac_key_iv[wvcdm::KEY_IV_SIZE]; + uint8_t mac_keys[2 * wvcdm::MAC_KEY_SIZE]; + uint8_t pst[kTestKeyIdMaxLength]; +}; + +struct RSAPrivateKeyMessage { + uint8_t rsa_key[kMaxTestRSAKeyLength]; + uint8_t rsa_key_iv[wvcdm::KEY_IV_SIZE]; + size_t rsa_key_length; + uint32_t nonce; +}; + +// Increment counter for AES-CTR. The CENC spec specifies we increment only +// the low 64 bits of the IV counter, and leave the high 64 bits alone. This +// is different from the OpenSSL implementation, so we implement the CTR loop +// ourselves. +void ctr128_inc64(int64_t increaseBy, uint8_t* iv); + +// Some compilers don't like the macro htonl within an ASSERT_EQ. +uint32_t htonl_fnc(uint32_t x); + +// Prints error string from openSSL +void dump_openssl_error(); + +class Session { + public: + Session(); + ~Session(); + + uint32_t get_nonce() { return nonce_; } + + uint32_t session_id() { return (uint32_t)session_id_; } + + void open(); + void close(); + void SetSessionId(uint32_t session_id); + + uint32_t GetOecSessionId() { return session_id_; } + void GenerateNonce(uint32_t* nonce, int* error_counter = NULL); + void FillDefaultContext(vector* mac_context, + vector* enc_context); + void GenerateDerivedKeysFromKeybox(); + void GenerateDerivedKeysFromSessionKey(); + void GenerateTestSessionKeys(); + void LoadTestKeys(const std::string& pst = "", bool new_mac_keys = true); + void VerifyTestKeys(); + void RefreshTestKeys(const size_t key_count, uint32_t control_bits, + uint32_t nonce, OEMCryptoResult expected_result); + void SetKeyId(int index, const string& key_id); + void FillSimpleMessage(uint32_t duration, uint32_t control, uint32_t nonce, + const std::string& pst = ""); + + void FillRefreshMessage(size_t key_count, uint32_t control_bits, + uint32_t nonce); + void EncryptAndSign(); + void EncryptMessage(RSAPrivateKeyMessage* data, + RSAPrivateKeyMessage* encrypted); + + template + void ServerSignMessage(const T& data, std::vector* signature); + + void ClientSignMessage(const vector& data, + std::vector* signature); + void FillKeyArray(const MessageData& data, OEMCrypto_KeyObject* key_array); + void FillRefreshArray(OEMCrypto_KeyRefreshObject* key_array, + size_t key_count); + void EncryptCTR( + const vector& in_buffer, const uint8_t *key, + const uint8_t* starting_iv, vector* out_buffer); + void TestDecryptCTR(bool select_key_first = true, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS); + void MakeRSACertificate( + struct RSAPrivateKeyMessage* encrypted, std::vector* signature, + uint32_t allowed_schemes, const vector& rsa_key); + void RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, + const std::vector& signature, + vector* wrapped_key, bool force); + void PreparePublicKey(const uint8_t* rsa_key = NULL, + size_t rsa_key_length = 0); + static bool VerifyPSSSignature( + EVP_PKEY* pkey, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length); + void VerifyRSASignature( + const vector& message, const uint8_t* signature, + size_t signature_length, RSA_Padding_Scheme padding_scheme); + bool GenerateRSASessionKey(vector* enc_session_key); + void InstallRSASessionTestKey(const vector& wrapped_rsa_key); + void DisallowDeriveKeys(); + void GenerateReport(const std::string& pst, bool expect_success = true, + Session* other = 0); + OEMCrypto_PST_Report* pst_report(); + void DeleteEntry(const std::string& pst); + void ForceDeleteEntry(const std::string& pst); + + MessageData& license() { return license_; } + MessageData& encrypted_license() { return encrypted_license_; } + + const uint8_t* message_ptr(); + + OEMCrypto_KeyObject* key_array() { return key_array_; } + std::vector& signature() { return signature_; } + + private: + bool open_; + bool forced_session_id_; + OEMCrypto_SESSION session_id_; + vector mac_key_server_; + vector mac_key_client_; + vector enc_key_; + uint32_t nonce_; + RSA* public_rsa_; + vector pst_report_buffer_; + MessageData license_; + MessageData encrypted_license_; + OEMCrypto_KeyObject key_array_[kNumKeys]; + std::vector signature_; +}; + +} // namespace wvoec + +#endif // CDM_OEC_SESSION_UTIL_H_ diff --git a/oemcrypto/test/oec_test_data.h b/oemcrypto/test/oec_test_data.h new file mode 100644 index 00000000..120e536b --- /dev/null +++ b/oemcrypto/test/oec_test_data.h @@ -0,0 +1,273 @@ +#ifndef CDM_OEC_TEST_DATA_H_ +#define CDM_OEC_TEST_DATA_H_ + +#include + +#if 0 +#include "OEMCryptoCENC.h" +#include "wv_keybox.h" +#endif + +namespace wvoec { + +// These are test keyboxes. They will not be accepted by production systems. +// By using known keyboxes for these tests, the results for a given set of +// inputs to a test are predictable and can be compared to the actual results. +// The first keybox, kTestKeybox, with deviceID "TestKey01" is used for most of +// the tests. It should be loaded by OEMCrypto when OEMCrypto_LoadTestKeybox +// is called. +const wvoec_mock::WidevineKeybox kTestKeybox = { + // Sample keybox used for test vectors + { + // deviceID + 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey01 + 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + }, { + // key + 0xfb, 0xda, 0x04, 0x89, 0xa1, 0x58, 0x16, 0x0e, + 0xa4, 0x02, 0xe9, 0x29, 0xe3, 0xb6, 0x8f, 0x04, + }, { + // data + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, + 0x07, 0xd9, 0xff, 0xde, 0x13, 0xaa, 0x95, 0xc1, + 0x22, 0x67, 0x80, 0x53, 0x36, 0x21, 0x36, 0xbd, + 0xf8, 0x40, 0x8f, 0x82, 0x76, 0xe4, 0xc2, 0xd8, + 0x7e, 0xc5, 0x2b, 0x61, 0xaa, 0x1b, 0x9f, 0x64, + 0x6e, 0x58, 0x73, 0x49, 0x30, 0xac, 0xeb, 0xe8, + 0x99, 0xb3, 0xe4, 0x64, 0x18, 0x9a, 0x14, 0xa8, + 0x72, 0x02, 0xfb, 0x02, 0x57, 0x4e, 0x70, 0x64, + 0x0b, 0xd2, 0x2e, 0xf4, 0x4b, 0x2d, 0x7e, 0x39, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0x0a, 0x7a, 0x2c, 0x35, + } +}; + +static wvoec_mock::WidevineKeybox kValidKeybox02 = { + // Sample keybox used for test vectors + { + // deviceID + 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey02 + 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + }, { + // key + 0x76, 0x5d, 0xce, 0x01, 0x04, 0x89, 0xb3, 0xd0, + 0xdf, 0xce, 0x54, 0x8a, 0x49, 0xda, 0xdc, 0xb6, + }, { + // data + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, + 0x92, 0x27, 0x0b, 0x1f, 0x1a, 0xd5, 0xc6, 0x93, + 0x19, 0x3f, 0xaa, 0x74, 0x1f, 0xdd, 0x5f, 0xb4, + 0xe9, 0x40, 0x2f, 0x34, 0xa4, 0x92, 0xf4, 0xae, + 0x9a, 0x52, 0x39, 0xbc, 0xb7, 0x24, 0x38, 0x13, + 0xab, 0xf4, 0x92, 0x96, 0xc4, 0x81, 0x60, 0x33, + 0xd8, 0xb8, 0x09, 0xc7, 0x55, 0x0e, 0x12, 0xfa, + 0xa8, 0x98, 0x62, 0x8a, 0xec, 0xea, 0x74, 0x8a, + 0x4b, 0xfa, 0x5a, 0x9e, 0xb6, 0x49, 0x0d, 0x80, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0x2a, 0x3b, 0x3e, 0xe4, + } +}; + +static wvoec_mock::WidevineKeybox kValidKeybox03 = { + // Sample keybox used for test vectors + { + // deviceID + 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey03 + 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + }, { + // key + 0x25, 0xe5, 0x2a, 0x02, 0x29, 0x68, 0x04, 0xa2, + 0x92, 0xfd, 0x7c, 0x67, 0x0b, 0x67, 0x1f, 0x31, + }, { + // data + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, + 0xf4, 0x0a, 0x0e, 0xa2, 0x0a, 0x71, 0xd5, 0x92, + 0xfa, 0xa3, 0x25, 0xc6, 0x4b, 0x76, 0xf1, 0x64, + 0xf4, 0x60, 0xa0, 0x30, 0x72, 0x23, 0xbe, 0x03, + 0xcd, 0xde, 0x7a, 0x06, 0xd4, 0x01, 0xeb, 0xdc, + 0xe0, 0x50, 0xc0, 0x53, 0x0a, 0x50, 0xb0, 0x37, + 0xe5, 0x05, 0x25, 0x0e, 0xa4, 0xc8, 0x5a, 0xff, + 0x46, 0x6e, 0xa5, 0x31, 0xf3, 0xdd, 0x94, 0xb7, + 0xe0, 0xd3, 0xf9, 0x04, 0xb2, 0x54, 0xb1, 0x64, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0xa1, 0x99, 0x5f, 0x46, + } +}; + +// A 2048 bit RSA key in PKCS#8 PrivateKeyInfo format +// Used to verify the functions that manipulate RSA keys. +static const uint8_t kTestRSAPKCS8PrivateKeyInfo2_2048[] = { + 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, + 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, + 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, + 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, + 0x94, 0x58, 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, + 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, + 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, + 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, + 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, + 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, + 0x28, 0xda, 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, + 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, + 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, + 0x29, 0xf2, 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, + 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, + 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, + 0xcd, 0x9a, 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, + 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, + 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, + 0x98, 0x56, 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, + 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, + 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, + 0xc9, 0x83, 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, + 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, + 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, + 0x2d, 0x5f, 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, + 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, + 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, + 0x82, 0x46, 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, + 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, + 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, + 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, + 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, + 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, + 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x5e, + 0x79, 0x65, 0x49, 0xa5, 0x76, 0x79, 0xf9, 0x05, + 0x45, 0x0f, 0xf4, 0x03, 0xbd, 0xa4, 0x7d, 0x29, + 0xd5, 0xde, 0x33, 0x63, 0xd8, 0xb8, 0xac, 0x97, + 0xeb, 0x3f, 0x5e, 0x55, 0xe8, 0x7d, 0xf3, 0xe7, + 0x3b, 0x5c, 0x2d, 0x54, 0x67, 0x36, 0xd6, 0x1d, + 0x46, 0xf5, 0xca, 0x2d, 0x8b, 0x3a, 0x7e, 0xdc, + 0x45, 0x38, 0x79, 0x7e, 0x65, 0x71, 0x5f, 0x1c, + 0x5e, 0x79, 0xb1, 0x40, 0xcd, 0xfe, 0xc5, 0xe1, + 0xc1, 0x6b, 0x78, 0x04, 0x4e, 0x8e, 0x79, 0xf9, + 0x0a, 0xfc, 0x79, 0xb1, 0x5e, 0xb3, 0x60, 0xe3, + 0x68, 0x7b, 0xc6, 0xef, 0xcb, 0x71, 0x4c, 0xba, + 0xa7, 0x79, 0x5c, 0x7a, 0x81, 0xd1, 0x71, 0xe7, + 0x00, 0x21, 0x13, 0xe2, 0x55, 0x69, 0x0e, 0x75, + 0xbe, 0x09, 0xc3, 0x4f, 0xa9, 0xc9, 0x68, 0x22, + 0x0e, 0x97, 0x8d, 0x89, 0x6e, 0xf1, 0xe8, 0x88, + 0x7a, 0xd1, 0xd9, 0x09, 0x5d, 0xd3, 0x28, 0x78, + 0x25, 0x0b, 0x1c, 0x47, 0x73, 0x25, 0xcc, 0x21, + 0xb6, 0xda, 0xc6, 0x24, 0x5a, 0xd0, 0x37, 0x14, + 0x46, 0xc7, 0x94, 0x69, 0xe4, 0x43, 0x6f, 0x47, + 0xde, 0x00, 0x33, 0x4d, 0x8f, 0x95, 0x72, 0xfa, + 0x68, 0x71, 0x17, 0x66, 0x12, 0x1a, 0x87, 0x27, + 0xf7, 0xef, 0x7e, 0xe0, 0x35, 0x58, 0xf2, 0x4d, + 0x6f, 0x35, 0x01, 0xaa, 0x96, 0xe2, 0x3d, 0x51, + 0x13, 0x86, 0x9c, 0x79, 0xd0, 0xb7, 0xb6, 0x64, + 0xe8, 0x86, 0x65, 0x50, 0xbf, 0xcc, 0x27, 0x53, + 0x1f, 0x51, 0xd4, 0xca, 0xbe, 0xf5, 0xdd, 0x77, + 0x70, 0x98, 0x0f, 0xee, 0xa8, 0x96, 0x07, 0x5f, + 0x45, 0x6a, 0x7a, 0x0d, 0x03, 0x9c, 0x4f, 0x29, + 0xf6, 0x06, 0xf3, 0x5d, 0x58, 0x6c, 0x47, 0xd0, + 0x96, 0xa9, 0x03, 0x17, 0xbb, 0x4e, 0xc9, 0x21, + 0xe0, 0xac, 0xcd, 0x78, 0x78, 0xb2, 0xfe, 0x81, + 0xb2, 0x51, 0x53, 0xa6, 0x1f, 0x98, 0x45, 0x02, + 0x81, 0x81, 0x00, 0xcf, 0x73, 0x8c, 0xbe, 0x6d, + 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, + 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, + 0x64, 0x60, 0x8c, 0x43, 0xeb, 0x85, 0xab, 0x04, + 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, + 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, + 0xde, 0x51, 0x4b, 0xb6, 0x51, 0x86, 0x7b, 0xd0, + 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, + 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, + 0x93, 0xd7, 0x9c, 0x27, 0x80, 0xb7, 0x1e, 0x64, + 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, + 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, + 0x90, 0x8f, 0xa2, 0x16, 0x22, 0x6a, 0xcc, 0x48, + 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, + 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, + 0x33, 0x85, 0x26, 0x60, 0x48, 0x16, 0xcb, 0xef, + 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, + 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, + 0xc3, 0x41, 0x45, 0x97, 0xb1, 0x49, 0xc2, 0x19, + 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, + 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, + 0xbb, 0xdb, 0xfd, 0x11, 0x9d, 0x17, 0x68, 0x78, + 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, + 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, + 0x91, 0x99, 0xe5, 0x91, 0x32, 0x2d, 0xeb, 0x3f, + 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, + 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, + 0x58, 0xf4, 0xd0, 0xb2, 0x74, 0x1d, 0x8e, 0x87, + 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, + 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, + 0xda, 0xfb, 0xed, 0x83, 0x51, 0x67, 0xa9, 0x55, + 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, + 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, + 0x81, 0x80, 0x67, 0x9c, 0x32, 0x83, 0x39, 0x57, + 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, + 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, + 0xf3, 0x90, 0x9a, 0xab, 0x9b, 0x0b, 0x1b, 0x43, + 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, + 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, + 0xb8, 0xe1, 0xb3, 0xa1, 0x6c, 0x25, 0x92, 0xe4, + 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, + 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, + 0x20, 0x82, 0xa1, 0x48, 0x2c, 0x2d, 0x45, 0xdc, + 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, + 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, + 0xc8, 0x28, 0x10, 0x1e, 0x08, 0x71, 0x16, 0xd8, + 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, + 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, + 0x35, 0x95, 0x41, 0x29, 0x40, 0x19, 0x83, 0x35, + 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, + 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, + 0x50, 0x76, 0x63, 0x94, 0x49, 0x4c, 0xad, 0x10, + 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, + 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, + 0x8a, 0xa2, 0xb9, 0x8f, 0xce, 0xec, 0x5e, 0x61, + 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, + 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, + 0xe4, 0x8b, 0x01, 0x06, 0x59, 0x22, 0xfa, 0x34, + 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, + 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, + 0x6c, 0xfa, 0x2b, 0xd9, 0x83, 0x5a, 0x2d, 0x0c, + 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, + 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, + 0x66, 0xff, 0x1c, 0x1b, 0xc8, 0x96, 0x76, 0xe8, + 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, + 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, + 0x2c, 0x7c, 0xad, 0x1e, 0x75, 0xf6, 0x69, 0x1d, + 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, + 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, + 0xae, 0xb7, 0x65, 0x2c, 0x52, 0xf9, 0xe4, 0xc7, + 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, + 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, + 0x75, 0x61, 0x96, 0x86, 0x4b, 0xb6, 0x2b, 0xad, + 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, + 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, + 0xd3, 0x29, 0x23, 0x02, 0x60, 0xf7, 0xab, 0x30, + 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, + 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, + 0xf4, 0xd4, 0x52, 0x95, 0x00, 0xae, 0x84, 0x6b, + 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, + 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, + 0x56, 0xfe, 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03 }; + +} // namespace wvoec + +#endif // CDM_OEC_TEST_DATA_H_ diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 6f53fdc8..734c573e 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -5,18 +5,6 @@ #include // needed for ntoh() #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include #include #include #include @@ -24,10 +12,24 @@ #include #include #include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include #include "log.h" +#include "oec_session_util.h" +#include "oec_test_data.h" #include "oemcrypto_key_mock.h" -#include "oemcrypto_test.h" +#include "oec_device_features.h" #include "OEMCryptoCENC.h" #include "properties.h" #include "string_conversions.h" @@ -39,1201 +41,7 @@ using ::testing::WithParamInterface; using ::testing::Range; using ::testing::Values; -// GTest requires PrintTo to be in the same namespace as the thing it prints, -// which is std::vector in this case. -namespace std { -void PrintTo(const vector& value, std::ostream* os) { - *os << wvcdm::b2a_hex(value); -} -} // namespace std - namespace wvoec { -namespace { -const size_t kNumKeys = 4; -#if defined(TEST_SPEED_MULTIPLIER) // Can slow test time limits when - // debugging is slowing everything. -const int kSpeedMultiplier = TEST_SPEED_MULTIPLIER; -#else -const int kSpeedMultiplier = 1; -#endif -const int kShortSleep = 1 * kSpeedMultiplier; -const int kLongSleep = 2 * kSpeedMultiplier; -const uint32_t kDuration = 2 * kSpeedMultiplier; -const uint32_t kLongDuration = 5 * kSpeedMultiplier; -const int32_t kAlmostRange = 3 * kSpeedMultiplier; -} // namespace - -typedef struct { - uint8_t verification[4]; - uint32_t duration; - uint32_t nonce; - uint32_t control_bits; -} KeyControlBlock; - -// Note: The API does not specify a maximum key id length. We specify a -// maximum just for these tests, so that we have a fixed message size. -const size_t kTestKeyIdMaxLength = 16; -// Most content will use a key id that is 16 bytes long. -const int kDefaultKeyIdLength = 16; -typedef struct { - uint8_t key_id[kTestKeyIdMaxLength]; - size_t key_id_length; - uint8_t key_data[wvcdm::MAC_KEY_SIZE]; - size_t key_data_length; - uint8_t key_iv[wvcdm::KEY_IV_SIZE]; - uint8_t control_iv[wvcdm::KEY_IV_SIZE]; - KeyControlBlock control; -} MessageKeyData; - -// This structure will be signed to simulate a message from the server. -struct MessageData { - MessageKeyData keys[kNumKeys]; - uint8_t mac_key_iv[wvcdm::KEY_IV_SIZE]; - uint8_t mac_keys[2 * wvcdm::MAC_KEY_SIZE]; - uint8_t pst[kTestKeyIdMaxLength]; -}; - -const size_t kMaxTestRSAKeyLength = 2000; // Rough estimate. -struct RSAPrivateKeyMessage { - uint8_t rsa_key[kMaxTestRSAKeyLength]; - uint8_t rsa_key_iv[wvcdm::KEY_IV_SIZE]; - size_t rsa_key_length; - uint32_t nonce; -}; - -// These are test keyboxes. They will not be accepted by production systems. -// By using known keyboxes for these tests, the results for a given set of -// inputs to a test are predictable and can be compared to the actual results. -// The first keybox, kTestKeybox, with deviceID "TestKey01" is used for most of -// the tests. It should be loaded by OEMCrypto when OEMCrypto_LoadTestKeybox -// is called. -const wvoec_mock::WidevineKeybox kTestKeybox = { - // Sample keybox used for test vectors - { - // deviceID - 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey01 - 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - }, { - // key - 0xfb, 0xda, 0x04, 0x89, 0xa1, 0x58, 0x16, 0x0e, - 0xa4, 0x02, 0xe9, 0x29, 0xe3, 0xb6, 0x8f, 0x04, - }, { - // data - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, - 0x07, 0xd9, 0xff, 0xde, 0x13, 0xaa, 0x95, 0xc1, - 0x22, 0x67, 0x80, 0x53, 0x36, 0x21, 0x36, 0xbd, - 0xf8, 0x40, 0x8f, 0x82, 0x76, 0xe4, 0xc2, 0xd8, - 0x7e, 0xc5, 0x2b, 0x61, 0xaa, 0x1b, 0x9f, 0x64, - 0x6e, 0x58, 0x73, 0x49, 0x30, 0xac, 0xeb, 0xe8, - 0x99, 0xb3, 0xe4, 0x64, 0x18, 0x9a, 0x14, 0xa8, - 0x72, 0x02, 0xfb, 0x02, 0x57, 0x4e, 0x70, 0x64, - 0x0b, 0xd2, 0x2e, 0xf4, 0x4b, 0x2d, 0x7e, 0x39, - }, { - // magic - 0x6b, 0x62, 0x6f, 0x78, - }, { - // Crc - 0x0a, 0x7a, 0x2c, 0x35, - } -}; - -static wvoec_mock::WidevineKeybox kValidKeybox02 = { - // Sample keybox used for test vectors - { - // deviceID - 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey02 - 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - }, { - // key - 0x76, 0x5d, 0xce, 0x01, 0x04, 0x89, 0xb3, 0xd0, - 0xdf, 0xce, 0x54, 0x8a, 0x49, 0xda, 0xdc, 0xb6, - }, { - // data - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, - 0x92, 0x27, 0x0b, 0x1f, 0x1a, 0xd5, 0xc6, 0x93, - 0x19, 0x3f, 0xaa, 0x74, 0x1f, 0xdd, 0x5f, 0xb4, - 0xe9, 0x40, 0x2f, 0x34, 0xa4, 0x92, 0xf4, 0xae, - 0x9a, 0x52, 0x39, 0xbc, 0xb7, 0x24, 0x38, 0x13, - 0xab, 0xf4, 0x92, 0x96, 0xc4, 0x81, 0x60, 0x33, - 0xd8, 0xb8, 0x09, 0xc7, 0x55, 0x0e, 0x12, 0xfa, - 0xa8, 0x98, 0x62, 0x8a, 0xec, 0xea, 0x74, 0x8a, - 0x4b, 0xfa, 0x5a, 0x9e, 0xb6, 0x49, 0x0d, 0x80, - }, { - // magic - 0x6b, 0x62, 0x6f, 0x78, - }, { - // Crc - 0x2a, 0x3b, 0x3e, 0xe4, - } -}; - -static wvoec_mock::WidevineKeybox kValidKeybox03 = { - // Sample keybox used for test vectors - { - // deviceID - 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey03 - 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - }, { - // key - 0x25, 0xe5, 0x2a, 0x02, 0x29, 0x68, 0x04, 0xa2, - 0x92, 0xfd, 0x7c, 0x67, 0x0b, 0x67, 0x1f, 0x31, - }, { - // data - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, - 0xf4, 0x0a, 0x0e, 0xa2, 0x0a, 0x71, 0xd5, 0x92, - 0xfa, 0xa3, 0x25, 0xc6, 0x4b, 0x76, 0xf1, 0x64, - 0xf4, 0x60, 0xa0, 0x30, 0x72, 0x23, 0xbe, 0x03, - 0xcd, 0xde, 0x7a, 0x06, 0xd4, 0x01, 0xeb, 0xdc, - 0xe0, 0x50, 0xc0, 0x53, 0x0a, 0x50, 0xb0, 0x37, - 0xe5, 0x05, 0x25, 0x0e, 0xa4, 0xc8, 0x5a, 0xff, - 0x46, 0x6e, 0xa5, 0x31, 0xf3, 0xdd, 0x94, 0xb7, - 0xe0, 0xd3, 0xf9, 0x04, 0xb2, 0x54, 0xb1, 0x64, - }, { - // magic - 0x6b, 0x62, 0x6f, 0x78, - }, { - // Crc - 0xa1, 0x99, 0x5f, 0x46, - } -}; - -// A 2048 bit RSA key in PKCS#8 PrivateKeyInfo format -// Used to verify the functions that manipulate RSA keys. -static const uint8_t kTestRSAPKCS8PrivateKeyInfo2_2048[] = { - 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, - 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, - 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, - 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, - 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, - 0x94, 0x58, 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, - 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, - 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, - 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, - 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, - 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, - 0x28, 0xda, 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, - 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, - 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, - 0x29, 0xf2, 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, - 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, - 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, - 0xcd, 0x9a, 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, - 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, - 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, - 0x98, 0x56, 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, - 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, - 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, - 0xc9, 0x83, 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, - 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, - 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, - 0x2d, 0x5f, 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, - 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, - 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, - 0x82, 0x46, 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, - 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, - 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, - 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, - 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, - 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, - 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x5e, - 0x79, 0x65, 0x49, 0xa5, 0x76, 0x79, 0xf9, 0x05, - 0x45, 0x0f, 0xf4, 0x03, 0xbd, 0xa4, 0x7d, 0x29, - 0xd5, 0xde, 0x33, 0x63, 0xd8, 0xb8, 0xac, 0x97, - 0xeb, 0x3f, 0x5e, 0x55, 0xe8, 0x7d, 0xf3, 0xe7, - 0x3b, 0x5c, 0x2d, 0x54, 0x67, 0x36, 0xd6, 0x1d, - 0x46, 0xf5, 0xca, 0x2d, 0x8b, 0x3a, 0x7e, 0xdc, - 0x45, 0x38, 0x79, 0x7e, 0x65, 0x71, 0x5f, 0x1c, - 0x5e, 0x79, 0xb1, 0x40, 0xcd, 0xfe, 0xc5, 0xe1, - 0xc1, 0x6b, 0x78, 0x04, 0x4e, 0x8e, 0x79, 0xf9, - 0x0a, 0xfc, 0x79, 0xb1, 0x5e, 0xb3, 0x60, 0xe3, - 0x68, 0x7b, 0xc6, 0xef, 0xcb, 0x71, 0x4c, 0xba, - 0xa7, 0x79, 0x5c, 0x7a, 0x81, 0xd1, 0x71, 0xe7, - 0x00, 0x21, 0x13, 0xe2, 0x55, 0x69, 0x0e, 0x75, - 0xbe, 0x09, 0xc3, 0x4f, 0xa9, 0xc9, 0x68, 0x22, - 0x0e, 0x97, 0x8d, 0x89, 0x6e, 0xf1, 0xe8, 0x88, - 0x7a, 0xd1, 0xd9, 0x09, 0x5d, 0xd3, 0x28, 0x78, - 0x25, 0x0b, 0x1c, 0x47, 0x73, 0x25, 0xcc, 0x21, - 0xb6, 0xda, 0xc6, 0x24, 0x5a, 0xd0, 0x37, 0x14, - 0x46, 0xc7, 0x94, 0x69, 0xe4, 0x43, 0x6f, 0x47, - 0xde, 0x00, 0x33, 0x4d, 0x8f, 0x95, 0x72, 0xfa, - 0x68, 0x71, 0x17, 0x66, 0x12, 0x1a, 0x87, 0x27, - 0xf7, 0xef, 0x7e, 0xe0, 0x35, 0x58, 0xf2, 0x4d, - 0x6f, 0x35, 0x01, 0xaa, 0x96, 0xe2, 0x3d, 0x51, - 0x13, 0x86, 0x9c, 0x79, 0xd0, 0xb7, 0xb6, 0x64, - 0xe8, 0x86, 0x65, 0x50, 0xbf, 0xcc, 0x27, 0x53, - 0x1f, 0x51, 0xd4, 0xca, 0xbe, 0xf5, 0xdd, 0x77, - 0x70, 0x98, 0x0f, 0xee, 0xa8, 0x96, 0x07, 0x5f, - 0x45, 0x6a, 0x7a, 0x0d, 0x03, 0x9c, 0x4f, 0x29, - 0xf6, 0x06, 0xf3, 0x5d, 0x58, 0x6c, 0x47, 0xd0, - 0x96, 0xa9, 0x03, 0x17, 0xbb, 0x4e, 0xc9, 0x21, - 0xe0, 0xac, 0xcd, 0x78, 0x78, 0xb2, 0xfe, 0x81, - 0xb2, 0x51, 0x53, 0xa6, 0x1f, 0x98, 0x45, 0x02, - 0x81, 0x81, 0x00, 0xcf, 0x73, 0x8c, 0xbe, 0x6d, - 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, - 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, - 0x64, 0x60, 0x8c, 0x43, 0xeb, 0x85, 0xab, 0x04, - 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, - 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, - 0xde, 0x51, 0x4b, 0xb6, 0x51, 0x86, 0x7b, 0xd0, - 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, - 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, - 0x93, 0xd7, 0x9c, 0x27, 0x80, 0xb7, 0x1e, 0x64, - 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, - 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, - 0x90, 0x8f, 0xa2, 0x16, 0x22, 0x6a, 0xcc, 0x48, - 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, - 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, - 0x33, 0x85, 0x26, 0x60, 0x48, 0x16, 0xcb, 0xef, - 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, - 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, - 0xc3, 0x41, 0x45, 0x97, 0xb1, 0x49, 0xc2, 0x19, - 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, - 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, - 0xbb, 0xdb, 0xfd, 0x11, 0x9d, 0x17, 0x68, 0x78, - 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, - 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, - 0x91, 0x99, 0xe5, 0x91, 0x32, 0x2d, 0xeb, 0x3f, - 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, - 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, - 0x58, 0xf4, 0xd0, 0xb2, 0x74, 0x1d, 0x8e, 0x87, - 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, - 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, - 0xda, 0xfb, 0xed, 0x83, 0x51, 0x67, 0xa9, 0x55, - 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, - 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, - 0x81, 0x80, 0x67, 0x9c, 0x32, 0x83, 0x39, 0x57, - 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, - 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, - 0xf3, 0x90, 0x9a, 0xab, 0x9b, 0x0b, 0x1b, 0x43, - 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, - 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, - 0xb8, 0xe1, 0xb3, 0xa1, 0x6c, 0x25, 0x92, 0xe4, - 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, - 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, - 0x20, 0x82, 0xa1, 0x48, 0x2c, 0x2d, 0x45, 0xdc, - 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, - 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, - 0xc8, 0x28, 0x10, 0x1e, 0x08, 0x71, 0x16, 0xd8, - 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, - 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, - 0x35, 0x95, 0x41, 0x29, 0x40, 0x19, 0x83, 0x35, - 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, - 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, - 0x50, 0x76, 0x63, 0x94, 0x49, 0x4c, 0xad, 0x10, - 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, - 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, - 0x8a, 0xa2, 0xb9, 0x8f, 0xce, 0xec, 0x5e, 0x61, - 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, - 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, - 0xe4, 0x8b, 0x01, 0x06, 0x59, 0x22, 0xfa, 0x34, - 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, - 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, - 0x6c, 0xfa, 0x2b, 0xd9, 0x83, 0x5a, 0x2d, 0x0c, - 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, - 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, - 0x66, 0xff, 0x1c, 0x1b, 0xc8, 0x96, 0x76, 0xe8, - 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, - 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, - 0x2c, 0x7c, 0xad, 0x1e, 0x75, 0xf6, 0x69, 0x1d, - 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, - 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, - 0xae, 0xb7, 0x65, 0x2c, 0x52, 0xf9, 0xe4, 0xc7, - 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, - 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, - 0x75, 0x61, 0x96, 0x86, 0x4b, 0xb6, 0x2b, 0xad, - 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, - 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, - 0xd3, 0x29, 0x23, 0x02, 0x60, 0xf7, 0xab, 0x30, - 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, - 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, - 0xf4, 0xd4, 0x52, 0x95, 0x00, 0xae, 0x84, 0x6b, - 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, - 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, - 0x56, 0xfe, 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03 }; - -DeviceFeatures global_features; - -void DeviceFeatures::Initialize(bool is_cast_receiver, bool force_load_test_keybox) { - cast_receiver = is_cast_receiver; - uses_keybox = false; - uses_certificate = false; - loads_certificate = false; - generic_crypto = false; - usage_table = false; - api_version = 0; - derive_key_method = NO_METHOD; - if (OEMCrypto_SUCCESS != OEMCrypto_Initialize()) { - printf("OEMCrypto_Initialze failed. All tests will fail.\n"); - return; - } - uint32_t nonce = 0; - uint8_t buffer[1]; - size_t size = 0; - uses_keybox = - (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_GetKeyData(buffer, &size)); - printf("uses_keybox = %s.\n", uses_keybox ? "true" : "false"); - loads_certificate = uses_keybox && - (OEMCrypto_ERROR_NOT_IMPLEMENTED != - OEMCrypto_RewrapDeviceRSAKey(0, buffer, 0, buffer, 0, &nonce, - buffer, 0, buffer, - buffer, &size)); - printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); - uses_certificate = - (OEMCrypto_ERROR_NOT_IMPLEMENTED - != OEMCrypto_GenerateRSASignature(0, buffer, 0, buffer, &size, - kSign_RSASSA_PSS)); - printf("uses_certificate = %s.\n", uses_certificate ? "true" : "false"); - generic_crypto = - (OEMCrypto_ERROR_NOT_IMPLEMENTED != - OEMCrypto_Generic_Encrypt(0, buffer, 0, buffer, - OEMCrypto_AES_CBC_128_NO_PADDING, buffer)); - printf("generic_crypto = %s.\n", generic_crypto ? "true" : "false"); - api_version = OEMCrypto_APIVersion(); - printf("api_version = %d.\n", api_version); - usage_table = OEMCrypto_SupportsUsageTable(); - printf("usage_table = %s.\n", usage_table ? "true" : "false"); - if (force_load_test_keybox) { - derive_key_method = FORCE_TEST_KEYBOX; - } else { - PickDerivedKey(); - } - printf("cast_receiver = %s.\n", cast_receiver ? "true" : "false"); - switch(derive_key_method) { - case NO_METHOD: - printf("NO_METHOD: Cannot derive known session keys.\n"); - // Note: cast_receiver left unchanged because set by user on command line. - uses_keybox = false; - uses_certificate = false; - loads_certificate = false; - generic_crypto = false; - usage_table = false; - break; - case LOAD_TEST_KEYBOX: - printf("LOAD_TEST_KEYBOX: Call LoadTestKeybox before deriving keys.\n"); - break; - case LOAD_TEST_RSA_KEY: - printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n"); - break; - case EXISTING_TEST_KEYBOX: - printf("EXISTING_TEST_KEYBOX: Keybox is already the test keybox.\n"); - break; - case FORCE_TEST_KEYBOX: - printf("FORCE_TEST_KEYBOX: User requested calling InstallKeybox.\n"); - break; - } - OEMCrypto_Terminate(); -} - -std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { - std::string filter = initial_filter; - if (!uses_keybox) FilterOut(&filter, "*KeyboxTest*"); - if (derive_key_method - != FORCE_TEST_KEYBOX) FilterOut(&filter, "*ForceKeybox*"); - if (!uses_certificate) FilterOut(&filter, "*Certificate*"); - if (!loads_certificate) FilterOut(&filter, "*LoadsCert*"); - if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); - if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); - if (!usage_table) FilterOut(&filter, "*UsageTable*"); - if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*"); - if (api_version < 10) FilterOut(&filter, "*API10*"); - // Performance tests take a long time. Filter them out if they are not - // specifically requested. - if (filter.find("Performance") == std::string::npos) { - FilterOut(&filter, "*Performance*"); - } - return filter; -} - -void DeviceFeatures::PickDerivedKey() { - if (uses_keybox) { - // If device uses a keybox, try to load the test keybox. - if(OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestKeybox()) { - derive_key_method = LOAD_TEST_KEYBOX; - } else if(IsTestKeyboxInstalled()) { - derive_key_method = EXISTING_TEST_KEYBOX; - } - } else if(OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) { - derive_key_method = LOAD_TEST_RSA_KEY; - } -} - -bool DeviceFeatures::IsTestKeyboxInstalled() { - uint8_t key_data[256]; - size_t key_data_len = sizeof(key_data); - if (OEMCrypto_GetKeyData(key_data, &key_data_len) != OEMCrypto_SUCCESS) - return false; - if (key_data_len != sizeof(kTestKeybox.data_)) return false; - if (memcmp(key_data, kTestKeybox.data_, key_data_len)) return false; - uint8_t dev_id[128] = {0}; - size_t dev_id_len = 128; - if (OEMCrypto_GetDeviceID(dev_id, &dev_id_len) != OEMCrypto_SUCCESS) - return false; - // We use strncmp instead of memcmp because we don't really care about the - // multiple '\0' characters at the end of the device id. - return 0 == - strncmp(reinterpret_cast(dev_id), - reinterpret_cast(kTestKeybox.device_id_), - sizeof(kTestKeybox.device_id_)); -} - -void DeviceFeatures::FilterOut(std::string* current_filter, - const std::string& new_filter) { - if (current_filter->find('-') == std::string::npos) { - *current_filter += "-" + new_filter; - } else { - *current_filter += ":" + new_filter; - } -} - -static void dump_openssl_error() { - while (unsigned long err = ERR_get_error()) { - char buffer[120]; - ERR_error_string_n(err, buffer, sizeof(buffer)); - cout << "openssl error -- " << buffer << "\n"; - } -} - -// We don't expect exact timing. -#define EXPECT_ALMOST(A, B) \ - EXPECT_GE(A + kAlmostRange, B); \ - EXPECT_LE(A - kAlmostRange, B); - -class Session { - public: - Session() - : open_(false), - session_id_(0), - mac_key_server_(wvcdm::MAC_KEY_SIZE), - mac_key_client_(wvcdm::MAC_KEY_SIZE), - enc_key_(wvcdm::KEY_SIZE), - public_rsa_(0) {} - - ~Session() { - if (open_) close(); - if (public_rsa_) RSA_free(public_rsa_); - } - - bool isOpen() { return open_; } - OEMCryptoResult getStatus() { return session_status_; } - uint32_t get_nonce() { return nonce_; } - - uint32_t session_id() { return (uint32_t)session_id_; } - void set_session_id(uint32_t newsession) { - session_id_ = (OEMCrypto_SESSION)newsession; - } - - void open() { - EXPECT_FALSE(open_); - session_status_ = OEMCrypto_OpenSession(&session_id_); - if (OEMCrypto_SUCCESS == session_status_) { - open_ = true; - } - } - - void close() { - session_status_ = OEMCrypto_CloseSession(session_id_); - if (OEMCrypto_SUCCESS == session_status_) { - open_ = false; - } - } - - void GenerateNonce(uint32_t* nonce, int* error_counter = NULL) { - if (OEMCrypto_SUCCESS == OEMCrypto_GenerateNonce(session_id(), nonce)) { - return; - } - if (error_counter) { - (*error_counter)++; - } else { - sleep(1); // wait a second, then try again. - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_GenerateNonce(session_id(), nonce)); - } - } - - void FillDefaultContext(vector* mac_context, - vector* enc_context) { - /* Context strings - * These context strings are normally created by the CDM layer - * from a license request message. - * They are used to test MAC and ENC key generation. - */ - *mac_context = wvcdm::a2b_hex( - "41555448454e5449434154494f4e000a4c08001248000000020000101907d9ff" - "de13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e5873" - "4930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a" - "230a14080112100915007caa9b5931b76a3a85f046523e10011a093938373635" - "34333231180120002a0c31383836373837343035000000000200"); - *enc_context = wvcdm::a2b_hex( - "454e4352595054494f4e000a4c08001248000000020000101907d9ffde13aa95" - "c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930aceb" - "e899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a1408" - "0112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231" - "180120002a0c31383836373837343035000000000080"); - } - - void GenerateDerivedKeysFromKeybox() { - GenerateNonce(&nonce_); - vector mac_context; - vector enc_context; - FillDefaultContext(&mac_context, &enc_context); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_GenerateDerivedKeys(session_id(), &mac_context[0], - mac_context.size(), &enc_context[0], - enc_context.size())); - - // Expected MAC and ENC keys generated from context strings - // with test keybox "installed". - mac_key_server_ = wvcdm::a2b_hex( - "3CFD60254786AF350B353B4FBB700AB382558400356866BA16C256BCD8C502BF"); - mac_key_client_ = wvcdm::a2b_hex( - "A9DE7B3E4E199ED8D1FBC29CD6B4C772CC4538C8B0D3E208B3E76F2EC0FD6F47"); - enc_key_ = wvcdm::a2b_hex("D0BFC35DA9E33436E81C4229E78CB9F4"); - } - - void GenerateDerivedKeysFromSessionKey() { // Uses test certificate. - GenerateNonce(&nonce_); - vector enc_session_key; - PreparePublicKey(); - ASSERT_TRUE(GenerateRSASessionKey(&enc_session_key)); - vector mac_context; - vector enc_context; - FillDefaultContext(&mac_context, &enc_context); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DeriveKeysFromSessionKey( - session_id(), &enc_session_key[0], enc_session_key.size(), - &mac_context[0], mac_context.size(), &enc_context[0], - enc_context.size())); - - // Expected MAC and ENC keys generated from context strings - // with RSA certificate "installed". - mac_key_server_ = wvcdm::a2b_hex( - "1E451E59CB663DA1646194DD28880788ED8ED2EFF913CBD6A0D535D1D5A90381"); - mac_key_client_ = wvcdm::a2b_hex( - "F9AAE74690909F2207B53B13307FCA096CA8C49CC6DFE3659873CB952889A74B"); - enc_key_ = wvcdm::a2b_hex("CB477D09014D72C9B8DCE76C33EA43B3"); - - } - - void GenerateTestSessionKeys() { - if (global_features.derive_key_method - == DeviceFeatures::LOAD_TEST_RSA_KEY) { - GenerateDerivedKeysFromSessionKey(); - } else { - GenerateDerivedKeysFromKeybox(); - } - } - - void LoadTestKeys(const std::string& pst = "", bool new_mac_keys = true) { - uint8_t* pst_ptr = NULL; - if (pst.length() > 0) { - pst_ptr = encrypted_license_.pst; - } - if (new_mac_keys) { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys( - session_id(), message_ptr(), sizeof(MessageData), - &signature_[0], signature_.size(), - encrypted_license_.mac_key_iv, encrypted_license_.mac_keys, - kNumKeys, key_array_, pst_ptr, pst.length())); - // Update new generated keys. - memcpy(&mac_key_server_[0], license_.mac_keys, wvcdm::MAC_KEY_SIZE); - memcpy(&mac_key_client_[0], license_.mac_keys + wvcdm::MAC_KEY_SIZE, - wvcdm::MAC_KEY_SIZE); - } else { - ASSERT_EQ( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(session_id(), message_ptr(), sizeof(MessageData), - &signature_[0], signature_.size(), NULL, NULL, - kNumKeys, key_array_, pst_ptr, pst.length())); - } - VerifyTestKeys(); - } - - void VerifyTestKeys() { - for (unsigned int i = 0; i < kNumKeys; i++) { - KeyControlBlock block; - size_t size = sizeof(block); - OEMCryptoResult sts = OEMCrypto_QueryKeyControl( - session_id(), license_.keys[i].key_id, license_.keys[i].key_id_length, - reinterpret_cast(&block), &size); - if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(sizeof(block), size); - // control duration and bits stored in network byte order. For printing - // we change to host byte order. - ASSERT_EQ(htonl(license_.keys[i].control.duration), - htonl(block.duration)) << "For key " << i; - ASSERT_EQ(htonl(license_.keys[i].control.control_bits), - htonl(block.control_bits)) << "For key " << i; - } - } - } - - void RefreshTestKeys(const size_t key_count, uint32_t control_bits, - uint32_t nonce, OEMCryptoResult expected_result) { - // Note: we store the message in encrypted_license_, but the refresh key - // message is not actually encrypted. It is, however, signed. - FillRefreshMessage(key_count, control_bits, nonce); - ServerSignMessage(encrypted_license_, &signature_); - OEMCrypto_KeyRefreshObject key_array[key_count]; - FillRefreshArray(key_array, key_count); - OEMCryptoResult sts = OEMCrypto_RefreshKeys( - session_id(), message_ptr(), sizeof(MessageData), &signature_[0], - signature_.size(), key_count, key_array); - ASSERT_EQ(expected_result, sts); - - TestDecryptCTR(); - sleep(kShortSleep); // Should still be valid key. - TestDecryptCTR(false); - sleep(kShortSleep + kLongSleep); // Should be after first expiration. - if (expected_result == OEMCrypto_SUCCESS) { - TestDecryptCTR(false, OEMCrypto_SUCCESS); - } else { - TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE); - } - } - - void SetKeyId(int index, const string& key_id) { - MessageKeyData &key = license_.keys[index]; - key.key_id_length = key_id.length(); - ASSERT_LE(key.key_id_length, kTestKeyIdMaxLength); - memcpy(key.key_id, key_id.data(), key.key_id_length); - } - - void FillSimpleMessage(uint32_t duration, uint32_t control, uint32_t nonce, - const std::string& pst = "") { - OEMCrypto_GetRandom(license_.mac_key_iv, sizeof(license_.mac_key_iv)); - OEMCrypto_GetRandom(license_.mac_keys, sizeof(license_.mac_keys)); - for (unsigned int i = 0; i < kNumKeys; i++) { - memset(license_.keys[i].key_id, 0, kTestKeyIdMaxLength); - license_.keys[i].key_id_length = kDefaultKeyIdLength; - memset(license_.keys[i].key_id, i, license_.keys[i].key_id_length); - OEMCrypto_GetRandom(license_.keys[i].key_data, - sizeof(license_.keys[i].key_data)); - license_.keys[i].key_data_length = wvcdm::KEY_SIZE; - OEMCrypto_GetRandom(license_.keys[i].key_iv, - sizeof(license_.keys[i].key_iv)); - OEMCrypto_GetRandom(license_.keys[i].control_iv, - sizeof(license_.keys[i].control_iv)); - if (control & wvoec_mock::kControlRequireAntiRollbackHardware) { - memcpy(license_.keys[i].control.verification, "kc10", 4); - } else if (control & (wvoec_mock::kControlHDCPVersionMask | - wvoec_mock::kControlReplayMask)) { - memcpy(license_.keys[i].control.verification, "kc09", 4); - } else { - memcpy(license_.keys[i].control.verification, "kctl", 4); - } - license_.keys[i].control.duration = htonl(duration); - license_.keys[i].control.nonce = htonl(nonce); - license_.keys[i].control.control_bits = htonl(control); - } - memcpy(license_.pst, pst.c_str(), min(sizeof(license_.pst), pst.length())); - - // The first key for the canned decryption content. - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - memcpy(license_.keys[0].key_data, &key[0], key.size()); - } - - void FillRefreshMessage(size_t key_count, uint32_t control_bits, - uint32_t nonce) { - for (unsigned int i = 0; i < key_count; i++) { - encrypted_license_.keys[i].key_id_length = license_.keys[i].key_id_length; - memcpy(encrypted_license_.keys[i].key_id, license_.keys[i].key_id, - encrypted_license_.keys[i].key_id_length); - memcpy(encrypted_license_.keys[i].control.verification, "kctl", 4); - encrypted_license_.keys[i].control.duration = htonl(kLongDuration); - encrypted_license_.keys[i].control.nonce = htonl(nonce); - encrypted_license_.keys[i].control.control_bits = htonl(control_bits); - } - } - - void EncryptAndSign() { - encrypted_license_ = license_; - - uint8_t iv_buffer[16]; - memcpy(iv_buffer, &license_.mac_key_iv[0], wvcdm::KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); - AES_cbc_encrypt(&license_.mac_keys[0], &encrypted_license_.mac_keys[0], - 2 * wvcdm::MAC_KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); - - for (unsigned int i = 0; i < kNumKeys; i++) { - memcpy(iv_buffer, &license_.keys[i].control_iv[0], wvcdm::KEY_IV_SIZE); - AES_set_encrypt_key(&license_.keys[i].key_data[0], 128, &aes_key); - AES_cbc_encrypt( - reinterpret_cast(&license_.keys[i].control), - reinterpret_cast(&encrypted_license_.keys[i].control), - wvcdm::KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); - - memcpy(iv_buffer, &license_.keys[i].key_iv[0], wvcdm::KEY_IV_SIZE); - AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); - AES_cbc_encrypt(&license_.keys[i].key_data[0], - &encrypted_license_.keys[i].key_data[0], - license_.keys[i].key_data_length, &aes_key, iv_buffer, - AES_ENCRYPT); - } - memcpy(encrypted_license_.pst, license_.pst, sizeof(license_.pst)); - ServerSignMessage(encrypted_license_, &signature_); - FillKeyArray(encrypted_license_, key_array_); - } - - void EncryptMessage(RSAPrivateKeyMessage* data, - RSAPrivateKeyMessage* encrypted) { - *encrypted = *data; - size_t padding = wvcdm::KEY_SIZE - (data->rsa_key_length % wvcdm::KEY_SIZE); - memset(data->rsa_key + data->rsa_key_length, static_cast(padding), - padding); - encrypted->rsa_key_length = data->rsa_key_length + padding; - uint8_t iv_buffer[16]; - memcpy(iv_buffer, &data->rsa_key_iv[0], wvcdm::KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); - AES_cbc_encrypt(&data->rsa_key[0], &encrypted->rsa_key[0], - encrypted->rsa_key_length, &aes_key, iv_buffer, - AES_ENCRYPT); - } - - template - void ServerSignMessage(const T& data, std::vector* signature) { - signature->assign(SHA256_DIGEST_LENGTH, 0); - unsigned int md_len = SHA256_DIGEST_LENGTH; - HMAC(EVP_sha256(), &mac_key_server_[0], mac_key_server_.size(), - reinterpret_cast(&data), sizeof(data), - &(signature->front()), &md_len); - } - - void ClientSignMessage(const vector& data, - std::vector* signature) { - signature->assign(SHA256_DIGEST_LENGTH, 0); - unsigned int md_len = SHA256_DIGEST_LENGTH; - HMAC(EVP_sha256(), &mac_key_client_[0], mac_key_client_.size(), - &(data.front()), data.size(), &(signature->front()), &md_len); - } - - void FillKeyArray(const MessageData& data, OEMCrypto_KeyObject* key_array) { - for (unsigned int i = 0; i < kNumKeys; i++) { - key_array[i].key_id = data.keys[i].key_id; - key_array[i].key_id_length = data.keys[i].key_id_length; - key_array[i].key_data_iv = data.keys[i].key_iv; - key_array[i].key_data = data.keys[i].key_data; - key_array[i].key_data_length = data.keys[i].key_data_length; - key_array[i].key_control_iv = data.keys[i].control_iv; - key_array[i].key_control = - reinterpret_cast(&data.keys[i].control); - } - } - - void FillRefreshArray(OEMCrypto_KeyRefreshObject* key_array, - size_t key_count) { - for (size_t i = 0; i < key_count; i++) { - if (key_count > 1) { - key_array[i].key_id = encrypted_license_.keys[i].key_id; - key_array[i].key_id_length = encrypted_license_.keys[i].key_id_length; - } else { - key_array[i].key_id = NULL; - key_array[i].key_id_length = 0; - } - key_array[i].key_control_iv = NULL; - key_array[i].key_control = - reinterpret_cast(&encrypted_license_.keys[i].control); - } - } - - void TestDecryptCTR(bool select_key_first = true, - OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { - OEMCryptoResult sts; - if (select_key_first) { - // Select the key (from FillSimpleMessage) - sts = OEMCrypto_SelectKey(session_id(), license_.keys[0].key_id, - license_.keys[0].key_id_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - } - - // Set up our expected input and output - // This is dummy encrypted data. - vector encryptedData = wvcdm::a2b_hex( - "ec261c115f9d5cda1d5cc7d33c4e37362d1397c89efdd1da5f0065c4848b0462" - "337ba14693735203c9b4184e362439c0cea5e5d1a628425eddf8a6bf9ba901ca" - "46f5a9fd973cffbbe3c276af9919e2e8f6f3f420538b7a0d6dc41487874d96b8" - "efaedb45a689b91beb8c20d36140ad467d9d620b19a5fc6f223b57e0e6a7f913" - "00fd899e5e1b89963e83067ca0912aa5b79df683e2530b55a9645be341bc5f07" - "cffc724790af635c959e2644e51ba7f23bae710eb55a1f2f4e060c3c1dd1387c" - "74415dc880492dd1d5b9ecf3f01de48a44baeb4d3ea5cc4f8d561d0865afcabb" - "fc14a9ab9647e6e31adabb72d792f0c9ba99dc3e9205657d28fc7771d64e6d4b"); - vector encryptionIv = - wvcdm::a2b_hex("719dbcb253b2ec702bb8c1b1bc2f3bc6"); - // This is the expected decrypted data. - vector unencryptedData = wvcdm::a2b_hex( - "19ef4361e16e6825b336e2012ad8ffc9ce176ab2256e1b98aa15b7877bd8c626" - "fa40b2e88373457cbcf4f1b4b9793434a8ac03a708f85974cff01bddcbdd7a8e" - "e33fd160c1d5573bfd8104efd23237edcf28205c3673920553f8dd5e916604b0" - "1082345181dceeae5ea39d829c7f49e1850c460645de33c288723b7ae3d91a17" - "a3f04195cd1945ba7b0f37fef7e82368be30f04365d877766f6d56f67d22a244" - "ef2596d3053f657c1b5d90b64e11797edf1c198a23a7bfc20e4d44c74ae41280" - "a8317f443255f4020eda850ff0954e308f53a634cbce799ae58911bc59ccd6a5" - "de2ac53ee0fa7ea15fc692cc892acc0090865dc57becacddf362a092dfd3040b"); - - // Describe the output - vector outputBuffer(256); - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = outputBuffer.data(); - destBuffer.buffer.clear.max_length = outputBuffer.size(); - // Decrypt the data - sts = OEMCrypto_DecryptCTR( - session_id(), &encryptedData[0], encryptedData.size(), true, - &encryptionIv[0], 0, &destBuffer, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); - // We only have a few errors that we test are reported. - if (expected_result == OEMCrypto_SUCCESS) { // No error. - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(unencryptedData, outputBuffer); - } else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED) { - // Report stale keys. - ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, sts); - ASSERT_NE(unencryptedData, outputBuffer); - } else if (expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP) { - // Report HDCP errors. - ASSERT_EQ(OEMCrypto_ERROR_INSUFFICIENT_HDCP, sts); - ASSERT_NE(unencryptedData, outputBuffer); - } else { - // OEM's can fine tune other error codes for debugging. - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NE(unencryptedData, outputBuffer); - } - } - - void MakeRSACertificate(struct RSAPrivateKeyMessage* encrypted, - std::vector* signature, - uint32_t allowed_schemes, - const vector& rsa_key) { - // Dummy context for testing signature generation. - vector context = wvcdm::a2b_hex( - "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" - "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" - "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" - "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" - "38373430350000"); - - OEMCryptoResult sts; - - // Generate signature - size_t gen_signature_length = 0; - sts = OEMCrypto_GenerateSignature(session_id(), &context[0], context.size(), - NULL, &gen_signature_length); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_EQ(static_cast(32), gen_signature_length); - vector gen_signature(gen_signature_length); - sts = OEMCrypto_GenerateSignature(session_id(), &context[0], context.size(), - &gen_signature[0], &gen_signature_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - std::vector expected_signature; - ClientSignMessage(context, &expected_signature); - ASSERT_EQ(expected_signature, gen_signature); - - // Rewrap Canned Response - - // In the real world, the signature above would just have been used to - // contact the certificate provisioning server to get this response. - - struct RSAPrivateKeyMessage message; - if (allowed_schemes != kSign_RSASSA_PSS) { - uint32_t algorithm_n = htonl(allowed_schemes); - memcpy(message.rsa_key, "SIGN", 4); - memcpy(message.rsa_key + 4, &algorithm_n, 4); - memcpy(message.rsa_key + 8, rsa_key.data(), rsa_key.size()); - message.rsa_key_length = 8 + rsa_key.size(); - } else { - memcpy(message.rsa_key, rsa_key.data(), rsa_key.size()); - message.rsa_key_length = rsa_key.size(); - } - OEMCrypto_GetRandom(message.rsa_key_iv, wvcdm::KEY_IV_SIZE); - message.nonce = nonce_; - - EncryptMessage(&message, encrypted); - ServerSignMessage(*encrypted, signature); - } - - void RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, - const std::vector& signature, - vector* wrapped_key, bool force) { - size_t wrapped_key_length = 0; - const uint8_t* message_ptr = reinterpret_cast(&encrypted); - - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey( - session_id(), message_ptr, sizeof(encrypted), &signature[0], - signature.size(), &encrypted.nonce, encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, NULL, - &wrapped_key_length)); - wrapped_key->clear(); - wrapped_key->assign(wrapped_key_length, 0); - OEMCryptoResult sts = OEMCrypto_RewrapDeviceRSAKey( - session_id(), message_ptr, sizeof(encrypted), &signature[0], - signature.size(), &encrypted.nonce, encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, &(wrapped_key->front()), - &wrapped_key_length); - if (force) { - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - } - if (OEMCrypto_SUCCESS != sts) { - wrapped_key->clear(); - } - } - - void PreparePublicKey(const uint8_t* rsa_key = NULL, - size_t rsa_key_length = 0) { - if (rsa_key == NULL) { - rsa_key = kTestRSAPKCS8PrivateKeyInfo2_2048; - rsa_key_length = sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048); - } - uint8_t* p = const_cast(rsa_key); - BIO* bio = BIO_new_mem_buf(p, rsa_key_length); - ASSERT_TRUE(NULL != bio); - PKCS8_PRIV_KEY_INFO* pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL); - ASSERT_TRUE(NULL != pkcs8_pki); - EVP_PKEY* evp = NULL; - evp = EVP_PKCS82PKEY(pkcs8_pki); - ASSERT_TRUE(NULL != evp); - if (public_rsa_) RSA_free(public_rsa_); - public_rsa_ = EVP_PKEY_get1_RSA(evp); - EVP_PKEY_free(evp); - PKCS8_PRIV_KEY_INFO_free(pkcs8_pki); - BIO_free(bio); - if (!public_rsa_) { - cout << "d2i_RSAPrivateKey failed. "; - dump_openssl_error(); - ASSERT_TRUE(false); - } - switch (RSA_check_key(public_rsa_)) { - case 1: // valid. - ASSERT_TRUE(true); - return; - case 0: // not valid. - cout << "[rsa key not valid] "; - dump_openssl_error(); - ASSERT_TRUE(false); - default: // -1 == check failed. - cout << "[error checking rsa key] "; - dump_openssl_error(); - ASSERT_TRUE(false); - } - } - - static bool VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message, - size_t message_length, - const uint8_t* signature, - size_t signature_length) { - EVP_MD_CTX ctx; - EVP_MD_CTX_init(&ctx); - EVP_PKEY_CTX* pctx = NULL; - - if (EVP_DigestVerifyInit(&ctx, &pctx, EVP_sha1(), NULL /* no ENGINE */, - pkey) != 1) { - LOGE("EVP_DigestVerifyInit failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_sha1()) != 1) { - LOGE("EVP_PKEY_CTX_set_signature_md failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) != 1) { - LOGE("EVP_PKEY_CTX_set_rsa_padding failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, SHA_DIGEST_LENGTH) != 1) { - LOGE("EVP_PKEY_CTX_set_rsa_pss_saltlen failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_DigestVerifyUpdate(&ctx, message, message_length) != 1) { - LOGE("EVP_DigestVerifyUpdate failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_DigestVerifyFinal(&ctx, const_cast(signature), - signature_length) != 1) { - LOGE( - "EVP_DigestVerifyFinal failed in VerifyPSSSignature. (Probably a bad " - "signature.)"); - goto err; - } - - EVP_MD_CTX_cleanup(&ctx); - return true; - - err: - dump_openssl_error(); - EVP_MD_CTX_cleanup(&ctx); - return false; - } - - void VerifyRSASignature(const vector& message, - const uint8_t* signature, size_t signature_length, - RSA_Padding_Scheme padding_scheme) { - EXPECT_TRUE(NULL != public_rsa_) - << "No public RSA key loaded in test code.\n"; - EXPECT_EQ(static_cast(RSA_size(public_rsa_)), signature_length) - << "Signature size is wrong. " << signature_length << ", should be " - << RSA_size(public_rsa_) << "\n"; - - if (padding_scheme == kSign_RSASSA_PSS) { - EVP_PKEY *pkey = EVP_PKEY_new(); - ASSERT_TRUE(EVP_PKEY_set1_RSA(pkey, public_rsa_) == 1); - - const bool ok = VerifyPSSSignature(pkey, &message[0], message.size(), - signature, signature_length); - EVP_PKEY_free(pkey); - EXPECT_TRUE(ok) << "PSS signature check failed."; - } else if (padding_scheme == kSign_PKCS1_Block1) { - vector padded_digest(signature_length); - int size; - // RSA_public_decrypt decrypts the signature, and then verifies that - // it was padded with RSA PKCS1 padding. - size = RSA_public_decrypt(signature_length, signature, &padded_digest[0], - public_rsa_, RSA_PKCS1_PADDING); - EXPECT_GT(size, 0); - padded_digest.resize(size); - EXPECT_EQ(message, padded_digest); - } else { - EXPECT_TRUE(false) << "Padding scheme not supported."; - } - } - - bool GenerateRSASessionKey(vector* enc_session_key) { - if (!public_rsa_) { - cout << "No public RSA key loaded in test code.\n"; - return false; - } - vector session_key = - wvcdm::a2b_hex("6fa479c731d2770b6a61a5d1420bb9d1"); - enc_session_key->assign(RSA_size(public_rsa_), 0); - int status = RSA_public_encrypt(session_key.size(), &session_key[0], - &(enc_session_key->front()), public_rsa_, - RSA_PKCS1_OAEP_PADDING); - int size = static_cast(RSA_size(public_rsa_)); - if (status != size) { - cout << "GenerateRSASessionKey error encrypting session key. "; - dump_openssl_error(); - return false; - } - return true; - } - - void InstallRSASessionTestKey(const vector& wrapped_rsa_key) { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadDeviceRSAKey(session_id(), &wrapped_rsa_key[0], - wrapped_rsa_key.size())); - GenerateDerivedKeysFromSessionKey(); - } - - void DisallowDeriveKeys() { - GenerateNonce(&nonce_); - vector enc_session_key; - PreparePublicKey(); - ASSERT_TRUE(GenerateRSASessionKey(&enc_session_key)); - vector mac_context; - vector enc_context; - FillDefaultContext(&mac_context, &enc_context); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_DeriveKeysFromSessionKey( - session_id(), &enc_session_key[0], enc_session_key.size(), - &mac_context[0], mac_context.size(), &enc_context[0], - enc_context.size())); - } - - void GenerateReport(const std::string& pst, bool expect_success = true, - Session* other = 0) { - if (other) { // If other is specified, copy mac keys. - mac_key_server_ = other->mac_key_server_; - mac_key_client_ = other->mac_key_client_; - } - size_t length = 0; - OEMCryptoResult sts = OEMCrypto_ReportUsage( - session_id(), reinterpret_cast(pst.c_str()), - pst.length(), pst_report(), &length); - if (expect_success) { - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - } - if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { - ASSERT_LE(sizeof(OEMCrypto_PST_Report), length); - pst_report_buffer_.resize(length); - } - sts = OEMCrypto_ReportUsage(session_id(), - reinterpret_cast(pst.c_str()), - pst.length(), pst_report(), &length); - if (!expect_success) { - ASSERT_NE(OEMCrypto_SUCCESS, sts); - return; - } - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - vector computed_signature(SHA_DIGEST_LENGTH); - unsigned int sig_len = SHA_DIGEST_LENGTH; - HMAC(EVP_sha1(), &mac_key_client_[0], mac_key_client_.size(), - reinterpret_cast(pst_report()) + SHA_DIGEST_LENGTH, - length - SHA_DIGEST_LENGTH, &computed_signature[0], &sig_len); - EXPECT_EQ(0, memcmp(&computed_signature[0], pst_report()->signature, - SHA_DIGEST_LENGTH)); - EXPECT_GE(kInactive, pst_report()->status); - EXPECT_GE(kHardwareSecureClock, pst_report()->clock_security_level); - EXPECT_EQ(pst.length(), pst_report()->pst_length); - EXPECT_EQ(0, memcmp(pst.c_str(), pst_report()->pst, pst.length())); - } - - OEMCrypto_PST_Report* pst_report() { - return reinterpret_cast(&pst_report_buffer_[0]); - } - - void DeleteEntry(const std::string& pst) { - uint8_t* pst_ptr = encrypted_license_.pst; - memcpy(pst_ptr, pst.c_str(), min(sizeof(license_.pst), pst.length())); - ServerSignMessage(encrypted_license_, &signature_); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DeleteUsageEntry(session_id(), pst_ptr, pst.length(), - message_ptr(), sizeof(MessageData), - &signature_[0], signature_.size())); - } - - void ForceDeleteEntry(const std::string& pst) { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_ForceDeleteUsageEntry( - reinterpret_cast(pst.c_str()), pst.length())); - } - - MessageData& license() { return license_; } - MessageData& encrypted_license() { return encrypted_license_; } - const uint8_t* message_ptr() { - return reinterpret_cast(&encrypted_license_); - } - OEMCrypto_KeyObject* key_array() { return key_array_; } - std::vector& signature() { return signature_; } - - private: - bool open_; - OEMCrypto_SESSION session_id_; - OEMCryptoResult session_status_; - vector mac_key_server_; - vector mac_key_client_; - vector enc_key_; - uint32_t nonce_; - RSA* public_rsa_; - vector pst_report_buffer_; - MessageData license_; - MessageData encrypted_license_; - OEMCrypto_KeyObject key_array_[kNumKeys]; - std::vector signature_; -}; class OEMCryptoClientTest : public ::testing::Test { protected: @@ -1246,8 +54,7 @@ class OEMCryptoClientTest : public ::testing::Test { wvcdm::g_cutoff = wvcdm::LOG_INFO; const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); - LOGD("Running test %s.%s", test_info->name(), - test_info->test_case_name()); + LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); } virtual void TearDown() { @@ -1283,7 +90,7 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { cout << " OEMCrypto does not support usage tables." << endl; } ASSERT_GE(version, 8u); - ASSERT_LE(version, 10u); + ASSERT_LE(version, 11u); } const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) { @@ -1372,33 +179,17 @@ TEST_F(OEMCryptoClientTest, NormalInitTermination) { // TEST_F(OEMCryptoClientTest, NormalSessionOpenClose) { Session s; - s.open(); - ASSERT_EQ(OEMCrypto_SUCCESS, s.getStatus()); - ASSERT_TRUE(s.isOpen()); - s.close(); - ASSERT_EQ(OEMCrypto_SUCCESS, s.getStatus()); - ASSERT_FALSE(s.isOpen()); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.close()); } TEST_F(OEMCryptoClientTest, TwoSessionsOpenClose) { Session s1; Session s2; - - s1.open(); - ASSERT_EQ(OEMCrypto_SUCCESS, s1.getStatus()); - ASSERT_TRUE(s1.isOpen()); - - s2.open(); - ASSERT_EQ(OEMCrypto_SUCCESS, s2.getStatus()); - ASSERT_TRUE(s2.isOpen()); - - s1.close(); - ASSERT_EQ(OEMCrypto_SUCCESS, s1.getStatus()); - ASSERT_FALSE(s1.isOpen()); - - s2.close(); - ASSERT_EQ(OEMCrypto_SUCCESS, s2.getStatus()); - ASSERT_FALSE(s2.isOpen()); + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(s2.close()); } // This test should still pass for API v9. A better test is below, but it only @@ -1406,14 +197,10 @@ TEST_F(OEMCryptoClientTest, TwoSessionsOpenClose) { TEST_F(OEMCryptoClientTest, EightSessionsOpenClose) { vector s(8); for (int i = 0; i < 8; i++) { - s[i].open(); - ASSERT_EQ(OEMCrypto_SUCCESS, s[i].getStatus()); - ASSERT_TRUE(s[i].isOpen()); + ASSERT_NO_FATAL_FAILURE(s[i].open()); } for (int i = 0; i < 8; i++) { - s[i].close(); - ASSERT_EQ(OEMCrypto_SUCCESS, s[i].getStatus()); - ASSERT_FALSE(s[i].isOpen()); + ASSERT_NO_FATAL_FAILURE(s[i].close()); } } @@ -1429,8 +216,8 @@ TEST_F(OEMCryptoClientTest, MaxSessionsOpenCloseAPI10) { ASSERT_GE(max_sessions, kMinimumSupportedMaxNumberOfSessions); // We allow GetMaxNumberOfSessions to return an estimate. This tests with a // pad of 5%. Even if it's just an estimate, we still require 8 sessions. - size_t max_sessions_with_pad = max(max_sessions * 19/20, - kMinimumSupportedMaxNumberOfSessions); + size_t max_sessions_with_pad = + max(max_sessions * 19 / 20, kMinimumSupportedMaxNumberOfSessions); vector sessions; // Limit the number of sessions for testing. const size_t kMaxNumberOfSessionsForTesting = 0x100u; @@ -1469,17 +256,16 @@ TEST_F(OEMCryptoClientTest, MaxSessionsOpenCloseAPI10) { TEST_F(OEMCryptoClientTest, GenerateNonce) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); uint32_t nonce; s.GenerateNonce(&nonce); } TEST_F(OEMCryptoClientTest, GenerateTwoNonces) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); uint32_t nonce1; uint32_t nonce2; - s.GenerateNonce(&nonce1); s.GenerateNonce(&nonce2); ASSERT_TRUE(nonce1 != nonce2); @@ -1487,7 +273,7 @@ TEST_F(OEMCryptoClientTest, GenerateTwoNonces) { TEST_F(OEMCryptoClientTest, PreventNonceFlood) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); int error_counter = 0; uint32_t nonce; time_t test_start = time(NULL); @@ -1520,8 +306,7 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood2) { const int kFloodCount = 80; for (int i = 0; i < kFloodCount; i++) { Session s; - s.open(); - EXPECT_TRUE(s.isOpen()); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(&nonce, &error_counter); } time_t test_end = time(NULL); @@ -1534,7 +319,7 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood2) { error_counter = 0; sleep(2); // After a pause, we should be able to regenerate nonces. Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(&nonce, &error_counter); EXPECT_EQ(0, error_counter); } @@ -1552,8 +337,7 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood3) { // To allow for some slop, we actually test for more. Session s[8]; for (int i = 0; i < 8; i++) { - s[i].open(); - EXPECT_TRUE(s[i].isOpen()); + ASSERT_NO_FATAL_FAILURE(s[i].open()); for (int j = 0; j < 10; j++) { request_counter++; s[i].GenerateNonce(&nonce, &error_counter); @@ -1568,7 +352,6 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood3) { EXPECT_LE(valid_counter, 20 * (test_end - test_start + 2)); error_counter = 0; sleep(2); // After a pause, we should be able to regenerate nonces. - EXPECT_TRUE(s[0].isOpen()); s[0].GenerateNonce(&nonce, &error_counter); EXPECT_EQ(0, error_counter); } @@ -1582,30 +365,30 @@ TEST_F(OEMCryptoClientTest, ClearCopyTestAPI10) { dest_buffer.type = OEMCrypto_BufferType_Clear; dest_buffer.buffer.clear.address = &output_buffer[0]; dest_buffer.buffer.clear.max_length = output_buffer.size(); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), - &dest_buffer, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), &dest_buffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); ASSERT_EQ(input_buffer, output_buffer); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, - OEMCrypto_CopyBuffer(NULL, input_buffer.size(), - &dest_buffer, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, - OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), - NULL, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer(NULL, input_buffer.size(), &dest_buffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), NULL, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); dest_buffer.buffer.clear.address = NULL; - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, - OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), - &dest_buffer, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), &dest_buffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); dest_buffer.buffer.clear.address = &output_buffer[0]; dest_buffer.buffer.clear.max_length = output_buffer.size() - 1; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), - &dest_buffer, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), &dest_buffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); } TEST_F(OEMCryptoClientTest, CanLoadTestKeys) { @@ -1627,13 +410,20 @@ TEST_F(OEMCryptoKeyboxTest, NormalGetKeyData) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); } +TEST_F(OEMCryptoKeyboxTest, GetKeyDataNullPointer) { + OEMCryptoResult sts; + uint8_t key_data[256]; + sts = OEMCrypto_GetKeyData(key_data, NULL); + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + TEST_F(OEMCryptoKeyboxTest, ProductionKeyboxValid) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); } TEST_F(OEMCryptoKeyboxTest, GenerateDerivedKeysFromKeybox) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); } @@ -1653,7 +443,7 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest { } void EnsureTestKeys() { - switch(global_features.derive_key_method) { + switch (global_features.derive_key_method) { case DeviceFeatures::LOAD_TEST_KEYBOX: ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestKeybox()); break; @@ -1693,8 +483,7 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest { } }; -class OEMCryptoSessionTestKeyboxTest : public OEMCryptoSessionTests {}; - +class OEMCryptoSessionTestKeyboxTest : public OEMCryptoSessionTests {}; TEST_F(OEMCryptoSessionTestKeyboxTest, TestKeyboxIsValid) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); @@ -1754,9 +543,9 @@ TEST_F(OEMCryptoSessionTestKeyboxTest, BadDataForceKeybox) { TEST_F(OEMCryptoSessionTestKeyboxTest, GenerateSignature) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); - s.GenerateDerivedKeysFromKeybox(); + ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox()); // Dummy context for testing signature generation. vector context = wvcdm::a2b_hex( @@ -1776,7 +565,7 @@ TEST_F(OEMCryptoSessionTestKeyboxTest, GenerateSignature) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); static const uint32_t SignatureExpectedLength = 32; - ASSERT_EQ(signature_length, SignatureExpectedLength); + ASSERT_EQ(SignatureExpectedLength, signature_length); signature.resize(signature_length); std::vector expected_signature; @@ -1786,29 +575,30 @@ TEST_F(OEMCryptoSessionTestKeyboxTest, GenerateSignature) { TEST_F(OEMCryptoSessionTests, LoadKeyNoNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 42); - s.EncryptAndSign(); - s.LoadTestKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 42)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); } TEST_F(OEMCryptoSessionTests, LoadKeyWithNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); } TEST_F(OEMCryptoSessionTests, LoadKeyWithNoMAC) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys("", false); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", false)); vector context = wvcdm::a2b_hex( "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" @@ -1828,7 +618,7 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithNoMAC) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); static const uint32_t SignatureExpectedLength = 32; - ASSERT_EQ(signature_length, SignatureExpectedLength); + ASSERT_EQ(SignatureExpectedLength, signature_length); signature.resize(signature_length); std::vector expected_signature; @@ -1841,10 +631,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithNoMAC) { not point into the message buffer */ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange1) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector mac_keys( s.encrypted_license().mac_keys, s.encrypted_license().mac_keys + sizeof(s.encrypted_license().mac_keys)); @@ -1859,10 +649,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange1) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange2) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector mac_key_iv(s.encrypted_license().mac_key_iv, s.encrypted_license().mac_key_iv + sizeof(s.encrypted_license().mac_key_iv)); @@ -1877,13 +667,13 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange2) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange3) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer(s.encrypted_license().keys[0].key_id, s.encrypted_license().keys[0].key_id + - s.encrypted_license().keys[0].key_id_length); + s.encrypted_license().keys[0].key_id_length); s.key_array()[0].key_id = &bad_buffer[0]; OEMCryptoResult sts = OEMCrypto_LoadKeys( @@ -1895,10 +685,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange3) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange4) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer( s.encrypted_license().keys[1].key_data, @@ -1914,10 +704,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange4) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange5) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer(s.encrypted_license().keys[1].key_iv, s.encrypted_license().keys[1].key_iv + sizeof(s.encrypted_license().keys[1].key_iv)); @@ -1931,11 +721,11 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange5) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange6) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer(s.key_array()[2].key_control, s.key_array()[2].key_control + @@ -1951,11 +741,11 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange6) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange7) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer( s.key_array()[2].key_control_iv, s.key_array()[2].key_control_iv + @@ -1971,10 +761,12 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange7) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, 42); // bad nonce. - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, + wvoec_mock::kControlNonceEnabled, + 42)); // bad nonce. + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -1985,19 +777,21 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadNonce) { TEST_F(OEMCryptoSessionTests, LoadKeyWithRepeatNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); uint32_t nonce = s.get_nonce(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, nonce); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.close(); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, nonce)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.close()); - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, - nonce); // same old nonce. - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, + wvoec_mock::kControlNonceEnabled, + nonce)); // same old nonce. + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -2008,12 +802,12 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithRepeatNonce) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadVerification) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); s.license().keys[1].control.verification[2] = 'Z'; - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -2022,12 +816,28 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadVerification) { ASSERT_NE(OEMCrypto_SUCCESS, sts); } +TEST_F(OEMCryptoSessionTests, LoadKeyWithFutureVerification) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + // OEMCrypto should reject API12 until the spec has been defined. + memcpy(s.license().keys[1].control.verification, "kc12", 4); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + OEMCryptoResult sts = OEMCrypto_LoadKeys( + s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], + s.signature().size(), s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, s.key_array(), NULL, 0); + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + TEST_F(OEMCryptoSessionTests, LoadKeysBadSignature) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); s.signature()[0] ^= 42; // Bad signature. OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], @@ -2038,10 +848,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeysBadSignature) { TEST_F(OEMCryptoSessionTests, LoadKeysWithNoDerivedKeys) { Session s; - s.open(); - // s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + // ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys())); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -2049,25 +859,57 @@ TEST_F(OEMCryptoSessionTests, LoadKeysWithNoDerivedKeys) { ASSERT_NE(OEMCrypto_SUCCESS, sts); } +// To prevent initial loading shared licenses without usage table or nonce, +// LoadKeys should reject an empty list of keys. +TEST_F(OEMCryptoSessionTests, LoadKeyNoKeys) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 42)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + int kNoKeys = 0; + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), NULL, NULL, + kNoKeys, s.key_array(), NULL, 0)); +} + +TEST_F(OEMCryptoSessionTests, LoadKeyNoKeyWithNonce) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + int kNoKeys = 0; + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), NULL, NULL, + kNoKeys, s.key_array(), NULL, 0)); +} + TEST_F(OEMCryptoSessionTests, QueryKeyControl) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); // Note: successful cases are tested in VerifyTestKeys. KeyControlBlock block; size_t size = sizeof(block) - 1; - OEMCryptoResult sts = OEMCrypto_QueryKeyControl( - s.session_id(), s.license().keys[0].key_id, - s.license().keys[0].key_id_length, reinterpret_cast(&block), - &size); + OEMCryptoResult sts = + OEMCrypto_QueryKeyControl(s.session_id(), s.license().keys[0].key_id, + s.license().keys[0].key_id_length, + reinterpret_cast(&block), &size); if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { return; } ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - const char *key_id = "no_key"; + const char* key_id = "no_key"; size = sizeof(block); ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_QueryKeyControl( @@ -2077,10 +919,11 @@ TEST_F(OEMCryptoSessionTests, QueryKeyControl) { TEST_F(OEMCryptoSessionTests, AntiRollbackHardwareRequired) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlRequireAntiRollbackHardware, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, wvoec_mock::kControlRequireAntiRollbackHardware, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -2092,8 +935,58 @@ TEST_F(OEMCryptoSessionTests, AntiRollbackHardwareRequired) { } } +TEST_F(OEMCryptoSessionTests, CheckMinimumPatchLevel) { + uint8_t patch_level = OEMCrypto_Security_Patch_Level(); + printf(" Current Patch Level: %u.\n", patch_level); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, patch_level << wvoec_mock::kControlSecurityPatchLevelShift, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), NULL, 0)); + if (patch_level < 0x3F) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, (patch_level + 1) << wvoec_mock::kControlSecurityPatchLevelShift, + 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_EQ( + OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), NULL, 0)); + } + if (patch_level > 0) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, (patch_level - 1) << wvoec_mock::kControlSecurityPatchLevelShift, + 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), NULL, 0)); + } +} + class SessionTestDecryptWithHDCP : public OEMCryptoSessionTests, - public WithParamInterface { + public WithParamInterface { public: void DecryptWithHDCP(OEMCrypto_HDCP_Capability version) { OEMCryptoResult sts; @@ -2101,19 +994,21 @@ class SessionTestDecryptWithHDCP : public OEMCryptoSessionTests, sts = OEMCrypto_GetHDCPCapability(¤t, &maximum); ASSERT_EQ(OEMCrypto_SUCCESS, sts); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, (version << wvoec_mock::kControlHDCPVersionShift) | - wvoec_mock::kControlObserveHDCP | - wvoec_mock::kControlHDCPRequired, - 0); - s.EncryptAndSign(); - s.LoadTestKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, + (version << wvoec_mock::kControlHDCPVersionShift) | + wvoec_mock::kControlObserveHDCP | wvoec_mock::kControlHDCPRequired, + 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); if (version > current) { - s.TestDecryptCTR(true, OEMCrypto_ERROR_INSUFFICIENT_HDCP); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(true, OEMCrypto_ERROR_INSUFFICIENT_HDCP)); } else { - s.TestDecryptCTR(true, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true, OEMCrypto_SUCCESS)); } } }; @@ -2122,7 +1017,7 @@ TEST_P(SessionTestDecryptWithHDCP, Decrypt) { // Test parameterized by HDCP version. DecryptWithHDCP(static_cast(GetParam())); } -INSTANTIATE_TEST_CASE_P(TestHDCP, SessionTestDecryptWithHDCP, Range(1, 5)); +INSTANTIATE_TEST_CASE_P(TestHDCP, SessionTestDecryptWithHDCP, Range(1, 5)); // // Load, Refresh Keys Test @@ -2145,56 +1040,59 @@ class SessionTestRefreshKeyTest TEST_P(SessionTestRefreshKeyTest, RefreshWithNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlNonceEnabled, - s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys("", new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); uint32_t nonce; s.GenerateNonce(&nonce); - s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, - OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.RefreshTestKeys( + num_keys_, wvoec_mock::kControlNonceEnabled, nonce, OEMCrypto_SUCCESS)); } TEST_P(SessionTestRefreshKeyTest, RefreshNoNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys("", new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); uint32_t nonce; s.GenerateNonce(&nonce); - s.RefreshTestKeys(num_keys_, 0, 0, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE( + s.RefreshTestKeys(num_keys_, 0, 0, OEMCrypto_SUCCESS)); } TEST_P(SessionTestRefreshKeyTest, RefreshOldNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlNonceEnabled, - s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys("", new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); uint32_t nonce = s.get_nonce(); - s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, - OEMCrypto_ERROR_INVALID_NONCE); + ASSERT_NO_FATAL_FAILURE( + s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, + OEMCrypto_ERROR_INVALID_NONCE)); } TEST_P(SessionTestRefreshKeyTest, RefreshBadNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlNonceEnabled, - s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys("", new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); uint32_t nonce; s.GenerateNonce(&nonce); nonce ^= 42; - s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, - OEMCrypto_ERROR_INVALID_NONCE); + ASSERT_NO_FATAL_FAILURE( + s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, + OEMCrypto_ERROR_INVALID_NONCE)); } // Of only one key control block in the refesh, we update all the keys. @@ -2212,37 +1110,517 @@ INSTANTIATE_TEST_CASE_P(TestRefreshEachKeys, SessionTestRefreshKeyTest, // TEST_F(OEMCryptoSessionTests, Decrypt) { Session s; - s.open(); - - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); } -TEST_F(OEMCryptoSessionTests, DecryptPerformance) { +TEST_F(OEMCryptoSessionTests, DecryptZeroDuration) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); +} + +TEST_F(OEMCryptoSessionTests, SimultaneousDecrypt) { + vector s(8); + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].open()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s[i].FillSimpleMessage(kLongDuration, 0, + s[i].get_nonce())); + ASSERT_NO_FATAL_FAILURE(s[i].EncryptAndSign()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].LoadTestKeys()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); + } + // Second call to decrypt for each session. + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); + } +} + +TEST_F(OEMCryptoSessionTests, SimultaneousDecryptWithLostMessage) { + vector s(8); + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].open()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s[i].FillSimpleMessage(kLongDuration, 0, + s[i].get_nonce())); + ASSERT_NO_FATAL_FAILURE(s[i].EncryptAndSign()); + } + // First set of messages are lost. Generate second set. + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s[i].FillSimpleMessage(kLongDuration, 0, + s[i].get_nonce())); + ASSERT_NO_FATAL_FAILURE(s[i].EncryptAndSign()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].LoadTestKeys()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); + } + // Second call to decrypt for each session. + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); + } +} + +struct SampleSize { + size_t clear_size; + size_t encrypted_size; + SampleSize(size_t clear, size_t encrypted) + : clear_size(clear), encrypted_size(encrypted) {} +}; + +struct SampleInitData { + uint8_t iv[AES_BLOCK_SIZE]; + size_t block_offset; +}; + +class OEMCryptoSessionTestsDecryptTests + : public OEMCryptoSessionTests, + public WithParamInterface { + protected: + virtual void SetUp() { + OEMCryptoSessionTests::SetUp(); + pattern_ = GetParam().pattern; + cipher_mode_ = GetParam().mode; + } + + void FindTotalSize() { + total_size_ = 0; + for (size_t i = 0; i < subsample_size_.size(); i++) { + total_size_ += + subsample_size_[i].clear_size + subsample_size_[i].encrypted_size; + } + } + + void EncryptData(const vector& key, + const vector& starting_iv, + const vector& in_buffer, + vector* out_buffer) { + AES_KEY aes_key; + AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); + out_buffer->resize(in_buffer.size()); + + uint8_t iv[AES_BLOCK_SIZE]; // Current iv. + + memcpy(iv, &starting_iv[0], AES_BLOCK_SIZE); + + size_t buffer_index = 0; // byte index into in and out. + size_t block_offset = 0; // byte index into current block. + for (size_t i = 0; i < subsample_size_.size(); i++) { + // Copy clear content. + if (subsample_size_[i].clear_size > 0) { + memcpy(&(*out_buffer)[buffer_index], &in_buffer[buffer_index], + subsample_size_[i].clear_size); + buffer_index += subsample_size_[i].clear_size; + } + // Save the current iv and offsets for call to DecryptCENC. + sample_init_data_.push_back(SampleInitData()); + memcpy(sample_init_data_[i].iv, iv, AES_BLOCK_SIZE); + // Note: final CENC spec specifies the pattern_offset = 0 at the + // start of each subsample. + size_t pattern_offset = 0; + sample_init_data_[i].block_offset = block_offset; + + size_t subsample_end = buffer_index + subsample_size_[i].encrypted_size; + while (buffer_index < subsample_end) { + size_t size = min(subsample_end - buffer_index, + AES_BLOCK_SIZE - block_offset); + size_t pattern_length = pattern_.encrypt + pattern_.skip; + bool skip_block = (pattern_offset >= pattern_.encrypt) + && (pattern_length>0); + if (pattern_length > 0) { + pattern_offset = (pattern_offset + 1) % pattern_length; + } + // CBC mode should just copy a partial block at the end. If there + // is a partial block at the beginning, an error is returned, so we + // can put whatever we want in the output buffer. + if (skip_block || ((cipher_mode_ == OEMCrypto_CipherMode_CBC) && + (size < AES_BLOCK_SIZE))) { + memcpy(&(*out_buffer)[buffer_index], &in_buffer[buffer_index], + size); + } else { + if (cipher_mode_ == OEMCrypto_CipherMode_CTR) { + uint8_t aes_output[AES_BLOCK_SIZE]; + AES_encrypt(iv, aes_output, &aes_key); + for (size_t n = 0; n < size; n++) { + (*out_buffer)[buffer_index + n] = + aes_output[n + block_offset] ^ + in_buffer[buffer_index + n]; + } + ctr128_inc64(1, iv); + } else { + uint8_t aes_input[AES_BLOCK_SIZE]; + for (size_t n = 0; n < size; n++) { + aes_input[n] = in_buffer[buffer_index + n] ^ iv[n]; + } + AES_encrypt(aes_input, &(*out_buffer)[buffer_index], &aes_key); + memcpy(iv, &(*out_buffer)[buffer_index], AES_BLOCK_SIZE); + } + } + buffer_index += size; + block_offset = 0; + } + block_offset = + (block_offset + subsample_size_[i].encrypted_size) % AES_BLOCK_SIZE; + } + } + + void TestDecryptCENC(const vector& key, + const vector& /* encryptionIv */, + const vector& encryptedData, + const vector& unencryptedData) { + OEMCryptoResult sts; + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); + memcpy(s.license().keys[0].key_data, &key[0], key.size()); + s.license().keys[0].cipher_mode = cipher_mode_; + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[0].key_id, + s.license().keys[0].key_id_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // We decrypt each subsample. + vector outputBuffer(total_size_ + 16, 0xaa); + size_t buffer_offset = 0; + for (size_t i = 0; i < subsample_size_.size(); i++) { + OEMCrypto_CENCEncryptPatternDesc pattern = pattern_; + pattern.offset = 0; // Final CENC spec says pattern offset always 0. + bool is_encrypted = false; + OEMCrypto_DestBufferDesc destBuffer; + size_t block_offset = 0; + uint8_t subsample_flags = 0; + if (subsample_size_[i].clear_size > 0) { + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = &outputBuffer[buffer_offset]; + destBuffer.buffer.clear.max_length = total_size_ - buffer_offset; + if (i == 0) subsample_flags |= OEMCrypto_FirstSubsample; + if ((i == subsample_size_.size() - 1) && + (subsample_size_[i].encrypted_size == 0)) { + subsample_flags |= OEMCrypto_LastSubsample; + } + sts = + OEMCrypto_DecryptCENC(s.session_id(), &encryptedData[buffer_offset], + subsample_size_[i].clear_size, is_encrypted, + sample_init_data_[i].iv, block_offset, + &destBuffer, &pattern, subsample_flags); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + buffer_offset += subsample_size_[i].clear_size; + } + if (subsample_size_[i].encrypted_size > 0) { + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = &outputBuffer[buffer_offset]; + destBuffer.buffer.clear.max_length = total_size_ - buffer_offset; + is_encrypted = true; + block_offset = sample_init_data_[i].block_offset; + subsample_flags = 0; + if ((i == 0) && (subsample_size_[i].clear_size == 0)) { + subsample_flags |= OEMCrypto_FirstSubsample; + } + if (i == subsample_size_.size() - 1) { + subsample_flags |= OEMCrypto_LastSubsample; + } + sts = OEMCrypto_DecryptCENC( + s.session_id(), &encryptedData[buffer_offset], + subsample_size_[i].encrypted_size, is_encrypted, + sample_init_data_[i].iv, block_offset, &destBuffer, &pattern, + subsample_flags); + // CBC mode should not accept a block offset. + if ((block_offset > 0) && (cipher_mode_ == OEMCrypto_CipherMode_CBC)) { + ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts) + << "CBC Mode should reject a non-zero block offset."; + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + buffer_offset += subsample_size_[i].encrypted_size; + } + } + EXPECT_EQ(0xaa, outputBuffer[total_size_]) << "Buffer overrun."; + outputBuffer.resize(total_size_); + EXPECT_EQ(unencryptedData, outputBuffer); + } + + OEMCrypto_CENCEncryptPatternDesc pattern_; + OEMCryptoCipherMode cipher_mode_; + vector subsample_size_; + size_t total_size_; + vector sample_init_data_; +}; + +// Tests that generate partial ending blocks. These tests should not be used +// with CTR mode and pattern encrypt. +class OEMCryptoSessionTestsPartialBlockTests : + public OEMCryptoSessionTestsDecryptTests {}; + +TEST_P(OEMCryptoSessionTestsDecryptTests, SingleLargeSubsample) { + // This subsample size should be larger a few encrypt/skip patterns. Most + // test cases use a pattern length of 160, so we'll run through at least two + // full patterns. + subsample_size_.push_back(SampleSize(0, 400)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, PatternPlusOneBlock) { + // When the pattern length is 10 blocks, there is a discrepancy between the + // HLS and the CENC standards for samples of size 160*N+16, for N = 1, 2, 3 ... + // We require the CENC standard for OEMCrypto, and let a layer above us break + // samples into pieces if they wish to use the HLS standard. + subsample_size_.push_back(SampleSize(0, 160+16)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, OneBlock) { + subsample_size_.push_back(SampleSize(0, 16)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// This tests the ability to decrypt multiple subsamples with no offset. +// There is no offset within the block, used by CTR mode. However, there might +// be an offset in the encrypt/skip pattern. +TEST_P(OEMCryptoSessionTestsDecryptTests, NoOffset) { + subsample_size_.push_back(SampleSize(25, 160)); + subsample_size_.push_back(SampleSize(50, 256)); + subsample_size_.push_back(SampleSize(25, 160)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// This tests an offset into the block for the second encrypted subsample. +// This should only work for CTR mode, for CBC mode an error is expected in +// the decrypt step. +// If this test fails for CTR mode, then it is probably handleing the +// block_offset incorrectly. +TEST_P(OEMCryptoSessionTestsPartialBlockTests, EvenOffset) { + subsample_size_.push_back(SampleSize(25, 8)); + subsample_size_.push_back(SampleSize(25, 32)); + subsample_size_.push_back(SampleSize(25, 50)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + // CTR Mode is self-inverse -- i.e. We can pick the encrypted data and + // compute the unencrypted data. By picking the encrypted data to be all 0, + // it is easier to re-encrypt the data and debug problems. Similarly, we + // pick an iv = 0. + EncryptData(key, encryptionIv, encryptedData, &unencryptedData); + // Run EncryptData again to correctly compute intermediate IV vectors. + // For CBC mode, this also computes the real encrypted data. + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// If the EvenOffset test passes, but this one doesn't, then DecryptCTR might +// be using the wrong definition of block offset. Adding the block offset to +// the block boundary should give you the beginning of the encrypted data. +// This should only work for CTR mode, for CBC mode, the block offset must be +// 0, so an error is expected in the decrypt step. +// Another way to view the block offset is with the formula: +// block_boundary + block_offset = beginning of subsample. +TEST_P(OEMCryptoSessionTestsPartialBlockTests, OddOffset) { + subsample_size_.push_back(SampleSize(10, 50)); + subsample_size_.push_back(SampleSize(10, 75)); + subsample_size_.push_back(SampleSize(10, 25)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// This tests that the algorithm used to increment the counter for +// AES-CTR mode is correct. There are two possible implementations: +// 1) increment the counter as if it were a 128 bit number, +// 2) increment the low 64 bits as a 64 bit number and leave the high bits +// alone. +// For CENC, the algorithm we should use is the second one. OpenSSL defaults to +// the first. If this test is not passing, you should look at the way you +// increment the counter. Look at the example code in ctr128_inc64 above. +// If you start with an IV of 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, after you +// increment twice, you should get 0xFFFFFFFFFFFFFFFF0000000000000000. +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptWithNearWrap) { + subsample_size_.push_back(SampleSize(0, 256)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + encryptionIv = wvcdm::a2b_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// This tests the case where an encrypted sample is not an even number of +// blocks. For CTR mode, the partial block is encrypted. For CBC mode the +// partial block should be a copy of the clear data. +TEST_P(OEMCryptoSessionTestsPartialBlockTests, PartialBlock) { + // Note: for more complete test coverage, we want a sample size that is in + // the encrypted range for some tests, e.g. (3,7), and in the skip range for + // other tests, e.g. (7, 3). 3*16 < 50 and 7*16 > 50. + subsample_size_.push_back(SampleSize(0, 50)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptUnencrypted) { + subsample_size_.push_back(SampleSize(256, 0)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +TEST_F(OEMCryptoSessionTests, DecryptUnencryptedNoKey) { OEMCryptoResult sts; Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Clear data should be copied even if there is no key selected. + // Set up our expected input and output + // This is dummy decrypted data. + vector in_buffer(256); + for (size_t i = 0; i < in_buffer.size(); i++) in_buffer[i] = i % 256; + vector encryptionIv(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + // Describe the output + vector out_buffer(in_buffer.size()); + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = &out_buffer[0]; + destBuffer.buffer.clear.max_length = out_buffer.size(); + OEMCrypto_CENCEncryptPatternDesc pattern; + pattern.encrypt = 0; + pattern.skip = 0; + pattern.offset = 0; + + // Decrypt the data + sts = OEMCrypto_DecryptCENC( + s.session_id(), &in_buffer[0], in_buffer.size(), false, + &encryptionIv[0], 0, &destBuffer, &pattern, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(in_buffer, out_buffer); +} + +// This test is not run by default, because it takes a long time and +// is used to measure decrypt performance, not test functionality. +// TODO(fredgc): Verify this test runs correctly. +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptCENCPerformance) { + OEMCryptoResult sts; + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); const time_t TestDuration = 5; - s.FillSimpleMessage(600, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - vector keyId = wvcdm::a2b_hex("000000000000000000000000"); - sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(600, 0, 0)); + s.license().keys[0].cipher_mode = GetParam().mode; + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[0].key_id, + s.license().keys[0].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector encryptionIv = wvcdm::a2b_hex("719dbcb253b2ec702bb8c1b1bc2f3bc6"); - const size_t max_length = 250*1000; + const size_t max_length = 250 * 1024; vector input(max_length); printf("Size of input is %zd\n", input.size()); - for(unsigned int i=0; i < max_length; i++) input[i] = i % 256; + for (unsigned int i = 0; i < max_length; i++) input[i] = i % 256; vector output(max_length); OEMCrypto_DestBufferDesc destBuffer; destBuffer.type = OEMCrypto_BufferType_Clear; destBuffer.buffer.clear.address = &output[0]; + OEMCrypto_CENCEncryptPatternDesc pattern = GetParam().pattern; const char* level = OEMCrypto_SecurityLevel(); const int n = 10; @@ -2251,10 +1629,11 @@ TEST_F(OEMCryptoSessionTests, DecryptPerformance) { double ysum = 0.0; double xysum = 0.0; double xsqsum = 0.0; - printf("PERF:head, security, bytes, bytes/frame, time(ms)/frame, bandwidth\n"); + printf( + "PERF:head, security, bytes, bytes/frame, time(ms)/frame, bandwidth\n"); - for(int i=0; i < n; i++) { - size_t length = 1000 + i*1000; + for (int i = 0; i < n; i++) { + size_t length = 1024 + i * 1024; destBuffer.buffer.clear.max_length = length; time_t test_start = time(NULL); time_t test_end = time(NULL); @@ -2262,328 +1641,93 @@ TEST_F(OEMCryptoSessionTests, DecryptPerformance) { size_t total = 0; do { ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DecryptCTR( - s.session_id(), &input[0], length, true, - &encryptionIv[0], 0, &destBuffer, + OEMCrypto_DecryptCENC( + s.session_id(), &input[0], length, true, &encryptionIv[0], + 0, &destBuffer, &pattern, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); count++; total += length; test_end = time(NULL); - } while(test_end - test_start < TestDuration); + } while (test_end - test_start < TestDuration); x[i] = length; - y[i] = 1000*(test_end-test_start)/((double)count); + y[i] = 1024 * (test_end - test_start) / ((double)count); xsum += x[i]; ysum += y[i]; - xysum += x[i]*y[i]; - xsqsum += x[i]*x[i]; - printf("PERF:stat, %s, %12zd, %12g, %12g, %12g\n", level, total, - x[i], y[i], - ((double)total)/((double)(test_end-test_start)) - ); + xysum += x[i] * y[i]; + xsqsum += x[i] * x[i]; + printf("PERF:stat, %s, %12zd, %12g, %12g, %12g\n", level, total, x[i], + y[i], ((double)total) / ((double)(test_end - test_start))); } - double b = (n*xysum - xsum*ysum) / (n*xsqsum - xsum*xsum); - double a = (ysum - b*xsum)/n; + double b = (n * xysum - xsum * ysum) / (n * xsqsum - xsum * xsum); + double a = (ysum - b * xsum) / n; printf("PERF-FIT, security=%s fit time(ms)/frame = %g + %g * buffer_size\n", - level, a, b); - for(int i=0; i < n; i++) { - printf("PERF-FIT, %12g, %12g, %12g\n", x[i], y[i], a + b*x[i]); + level, a, b); + for (int i = 0; i < n; i++) { + printf("PERF-FIT, %12g, %12g, %12g\n", x[i], y[i], a + b * x[i]); } } -TEST_F(OEMCryptoSessionTests, DecryptZeroDuration) { - Session s; - s.open(); - - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(); -} - -class OEMCryptoSessionTestsDecryptEdgeCases : public OEMCryptoSessionTests { - public: - // Increment counter for AES-CTR. The CENC spec specifies we increment only - // the low 64 bits of the IV counter, and leave the high 64 bits alone. This - // is different from the OpenSSL implementation, so we implement the CTR loop - // ourselves. - void ctr128_inc64(int64_t increaseBy, uint8_t* iv) { - uint64_t* counterBuffer = reinterpret_cast(&iv[8]); - (*counterBuffer) = wvcdm::htonll64(wvcdm::ntohll64(*counterBuffer) + - increaseBy); - } - - size_t FindTotalSize(const vector& subsample_size) { - size_t total_size = 0; - for(size_t i=0; i < subsample_size.size(); i++) - total_size += subsample_size[i]; - return total_size; - } - - void EncryptCTR(const vector& key, const vector& iv, - const vector& in, vector* out) { - AES_KEY aes_key; - AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); - - uint8_t aes_iv[AES_BLOCK_SIZE]; - memcpy(aes_iv, &iv[0], AES_BLOCK_SIZE); - - // Encrypt the IV. - uint8_t ecount_buf[AES_BLOCK_SIZE]; - - out->resize(in.size()); - - size_t cipher_data_length = in.size(); - size_t l = 0; - while (l < cipher_data_length) { - AES_encrypt(aes_iv, ecount_buf, &aes_key); - for (int n = 0; n < AES_BLOCK_SIZE && l < cipher_data_length; - ++n, ++l) { - (*out)[l] = in[l] ^ ecount_buf[n]; - } - ctr128_inc64(1, aes_iv); - } - } - - void TestDecrypt(const vector& unencryptedData, - const vector& encryptedData, - const vector& encryptionIv, - size_t total_size, const vector subsample_size) { - OEMCryptoResult sts; - Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - sts = OEMCrypto_SelectKey(s.session_id(), - s.license().keys[0].key_id, - s.license().keys[0].key_id_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - // We decrypt three subsamples. each with a block offset. - vector outputBuffer(total_size, 0xaa); - size_t buffer_offset = 0; - for(size_t i=0; i < subsample_size.size(); i++) { - const size_t block_offset = buffer_offset % AES_BLOCK_SIZE; - uint8_t subsample_flags = 0; - if (i == 0) subsample_flags |= OEMCrypto_FirstSubsample; - if (i == subsample_size.size()-1) { - subsample_flags |= OEMCrypto_LastSubsample; - } - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = &outputBuffer[buffer_offset]; - destBuffer.buffer.clear.max_length = total_size-buffer_offset; - uint8_t aes_iv[AES_BLOCK_SIZE]; - memcpy(aes_iv, &encryptionIv[0], AES_BLOCK_SIZE); - size_t iv_increment = buffer_offset / AES_BLOCK_SIZE; - ctr128_inc64(iv_increment, aes_iv); - sts = OEMCrypto_DecryptCTR( - s.session_id(), &encryptedData[buffer_offset], subsample_size[i], - true, aes_iv, block_offset, &destBuffer, subsample_flags); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - buffer_offset += subsample_size[i]; - } - EXPECT_EQ(unencryptedData, outputBuffer); - // If there was a problem, compare the outputBuffer at the offset with the - // correct data at 0. A common error is to ignore the offset when - // decrypting. - if (unencryptedData != outputBuffer && 2*subsample_size[0] < total_size - && 0 == memcmp(&unencryptedData[0], &outputBuffer[subsample_size[0]], - subsample_size[0])){ - printf("The first %zd bytes are repeating. This is an indication \n", - subsample_size[0]); - printf("that DecryptCTR is ignoring the offset.\n"); - } - } -}; - -TEST_F(OEMCryptoSessionTestsDecryptEdgeCases, EvenOffset) { - vector subsample_size; - subsample_size.push_back(8); - subsample_size.push_back(32); - subsample_size.push_back(50); - const size_t total_size = FindTotalSize(subsample_size); - vector unencryptedData(total_size); - vector encryptedData(total_size); - vector encryptionIv(AES_BLOCK_SIZE); - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - // Note: DecryptCTR is self-inverse -- ie it's the same as EncryptCTR. - // So we can pick the encrypted data and compute the unencrypted data if we - // want. By picking the encrypted data to be all 0, it is easier to - // re-encrypt the data and debug problems. - EncryptCTR(key, encryptionIv, encryptedData, &unencryptedData); - TestDecrypt(unencryptedData, encryptedData, encryptionIv, total_size, - subsample_size); -} - -// This tests the ability to decrypt multiple subsamples with no offset. -TEST_F(OEMCryptoSessionTestsDecryptEdgeCases, NoOffset) { - vector subsample_size; - subsample_size.push_back(64); - subsample_size.push_back(64); - subsample_size.push_back(64); - const size_t total_size = FindTotalSize(subsample_size); - vector unencryptedData(total_size); - vector encryptedData(total_size); - vector encryptionIv(AES_BLOCK_SIZE); - encryptionIv = wvcdm::a2b_hex("c09454479a280829c946df3c22f25539"); - for(size_t i=0; i < total_size; i++) unencryptedData[i] = i % 256; - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - EncryptCTR(key, encryptionIv, unencryptedData, &encryptedData); - TestDecrypt(unencryptedData, encryptedData, encryptionIv, total_size, - subsample_size); -} - -// If the EvenOffset test passes, but this one doesn't, then DecryptCTR might -// be using the wrong definition of offset. Adding the offset to the block -// boundary should give you the beginning of the encrypted data. -TEST_F(OEMCryptoSessionTestsDecryptEdgeCases, OddOffset) { - vector subsample_size; - subsample_size.push_back(50); - subsample_size.push_back(75); - subsample_size.push_back(25); - const size_t total_size = FindTotalSize(subsample_size); - vector unencryptedData(total_size); - vector encryptedData(total_size); - vector encryptionIv(AES_BLOCK_SIZE); - encryptionIv = wvcdm::a2b_hex("c09454479a280829c946df3c22f25539"); - for(size_t i=0; i < total_size; i++) unencryptedData[i] = i % 256; - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - EncryptCTR(key, encryptionIv, unencryptedData, &encryptedData); - TestDecrypt(unencryptedData, encryptedData, encryptionIv, total_size, - subsample_size); -} - -// This tests that the algorithm used to increment the counter for -// AES-CTR mode is correct. There are two possible implementations: -// 1) increment the counter as if it were a 128 bit number, -// 2) increment the low 64 bits as a 64 bit number and leave the high bits alone. -// For CENC, the algorithm we should use is the second one. OpenSSL defaults to -// the first. If this test is not passing, you should look at the way you -// increment the counter. Look at the example code in ctr128_inc64 above. -// If you start with an IV of 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, after you -// increment twice, you should get 0xFFFFFFFFFFFFFFFF0000000000000000. -TEST_F(OEMCryptoSessionTestsDecryptEdgeCases, DecryptWithNearWrap) { - vector subsample_size; - subsample_size.push_back(150); - const size_t total_size = FindTotalSize(subsample_size); - vector unencryptedData(total_size); - vector encryptedData(total_size); - vector encryptionIv(AES_BLOCK_SIZE); - encryptionIv = wvcdm::a2b_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"); - for(size_t i=0; i < total_size; i++) unencryptedData[i] = i % 256; - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - EncryptCTR(key, encryptionIv, unencryptedData, &encryptedData); - TestDecrypt(unencryptedData, encryptedData, encryptionIv, total_size, - subsample_size); -} - -TEST_F(OEMCryptoSessionTests, DecryptUnencrypted) { - OEMCryptoResult sts; - Session s; - s.open(); - - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - - // Select the key (from FillSimpleMessage) - sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[0].key_id, - s.license().keys[0].key_id_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - // Set up our expected input and output - // This is dummy decrypted data. - vector unencryptedData = wvcdm::a2b_hex( - "1558497b6d994be343ed1c6d6313e0537b843e9a9c0836d1e83fe33154191ce9" - "a14d8d95bebaddc03bd471827170f527c0a166b9068b273d1bc57fbb13975ee4" - "f6b9a31743da6c447acbb712e81b13eddfd4e96c76010ac9b8aa1b6b3152b0fc" - "39ad33e5719656069f9ede9ebba7a77dd2e2074eec5c1b7ffc427a6f1be168f0" - "b5857713a44623862c903284bc53417e23c65602b52c1cb699e8352453eb9698" - "0b31459b90c26c907b549c1ab293725e414d4e45f5b30af7a55f95499a7dc89f" - "7d13ba90b34aef6b49484b0701bf96ea8b660c24bb4e92a2d1c43beb434fa386" - "1071380388799ac31d79285f5817687ed3e2eeb73a30744e77b757686c9ba5ad"); - vector encryptionIv = wvcdm::a2b_hex( - "49fc3efaaf614ed81d595847b928edd0"); - - // Describe the output - vector outputBuffer(256); - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = &outputBuffer[0]; - destBuffer.buffer.clear.max_length = outputBuffer.size(); - - // Decrypt the data - sts = OEMCrypto_DecryptCTR( - s.session_id(), &unencryptedData[0], unencryptedData.size(), false, - &encryptionIv[0], 0, &destBuffer, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(unencryptedData, outputBuffer); -} - -TEST_F(OEMCryptoSessionTests, DecryptUnencryptedNoKey) { - OEMCryptoResult sts; - Session s; - s.open(); - // Clear data should be copied even if there is no key selected. - // Set up our expected input and output - // This is dummy decrypted data. - vector unencryptedData = wvcdm::a2b_hex( - "1558497b6d994be343ed1c6d6313e0537b843e9a9c0836d1e83fe33154191ce9" - "a14d8d95bebaddc03bd471827170f527c0a166b9068b273d1bc57fbb13975ee4" - "f6b9a31743da6c447acbb712e81b13eddfd4e96c76010ac9b8aa1b6b3152b0fc" - "39ad33e5719656069f9ede9ebba7a77dd2e2074eec5c1b7ffc427a6f1be168f0" - "b5857713a44623862c903284bc53417e23c65602b52c1cb699e8352453eb9698" - "0b31459b90c26c907b549c1ab293725e414d4e45f5b30af7a55f95499a7dc89f" - "7d13ba90b34aef6b49484b0701bf96ea8b660c24bb4e92a2d1c43beb434fa386" - "1071380388799ac31d79285f5817687ed3e2eeb73a30744e77b757686c9ba5ad"); - vector encryptionIv = wvcdm::a2b_hex( - "49fc3efaaf614ed81d595847b928edd0"); - - // Describe the output - vector outputBuffer(256); - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = &outputBuffer[0]; - destBuffer.buffer.clear.max_length = outputBuffer.size(); - - // Decrypt the data - sts = OEMCrypto_DecryptCTR( - s.session_id(), &unencryptedData[0], unencryptedData.size(), false, - &encryptionIv[0], 0, &destBuffer, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(unencryptedData, outputBuffer); -} +INSTANTIATE_TEST_CASE_P( + CTRTests, OEMCryptoSessionTestsPartialBlockTests, + Values(PatternTestVariant(0, 0, OEMCrypto_CipherMode_CTR) + )); +INSTANTIATE_TEST_CASE_P( + CBCTests, OEMCryptoSessionTestsPartialBlockTests, + Values(PatternTestVariant(0, 0, OEMCrypto_CipherMode_CBC), + PatternTestVariant(3, 7, OEMCrypto_CipherMode_CBC), + // HLS Edge case. We should follow the CENC spec, not HLS spec. + PatternTestVariant(9, 1, OEMCrypto_CipherMode_CBC), + PatternTestVariant(1, 9, OEMCrypto_CipherMode_CBC), + PatternTestVariant(1, 3, OEMCrypto_CipherMode_CBC), + PatternTestVariant(2, 1, OEMCrypto_CipherMode_CBC) + )); +INSTANTIATE_TEST_CASE_P( + CTRTests, OEMCryptoSessionTestsDecryptTests, + Values(PatternTestVariant(0, 0, OEMCrypto_CipherMode_CTR), + PatternTestVariant(3, 7, OEMCrypto_CipherMode_CTR), + // Pattern length should be 10, but that is not guaranteed. + PatternTestVariant(1, 3, OEMCrypto_CipherMode_CTR), + PatternTestVariant(2, 1, OEMCrypto_CipherMode_CTR) + )); +INSTANTIATE_TEST_CASE_P( + CBCTests, OEMCryptoSessionTestsDecryptTests, + Values(PatternTestVariant(0, 0, OEMCrypto_CipherMode_CBC), + PatternTestVariant(3, 7, OEMCrypto_CipherMode_CBC), + // HLS Edge case. We should follow the CENC spec, not HLS spec. + PatternTestVariant(9, 1, OEMCrypto_CipherMode_CBC), + PatternTestVariant(1, 9, OEMCrypto_CipherMode_CBC), + // Pattern length should be 10, but that is not guaranteed. + PatternTestVariant(1, 3, OEMCrypto_CipherMode_CBC), + PatternTestVariant(2, 1, OEMCrypto_CipherMode_CBC) + )); TEST_F(OEMCryptoSessionTests, DecryptSecureToClear) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlObserveDataPath | - wvoec_mock::kControlDataPathSecure, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, + wvoec_mock::kControlObserveDataPath | wvoec_mock::kControlDataPathSecure, + 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); } TEST_F(OEMCryptoSessionTests, KeyDuration) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlNonceEnabled, - s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(true, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true, OEMCrypto_SUCCESS)); sleep(kShortSleep); // Should still be valid key. - s.TestDecryptCTR(false, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false, OEMCrypto_SUCCESS)); sleep(kLongSleep); // Should be expired key. - s.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); } // @@ -2591,21 +1735,22 @@ TEST_F(OEMCryptoSessionTests, KeyDuration) { // class OEMCryptoLoadsCertificate : public OEMCryptoSessionTestKeyboxTest { protected: - OEMCryptoLoadsCertificate() : - encoded_rsa_key_(kTestRSAPKCS8PrivateKeyInfo2_2048, - kTestRSAPKCS8PrivateKeyInfo2_2048 + - sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)) {} + OEMCryptoLoadsCertificate() + : encoded_rsa_key_(kTestRSAPKCS8PrivateKeyInfo2_2048, + kTestRSAPKCS8PrivateKeyInfo2_2048 + + sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)) {} void CreateWrappedRSAKey(vector* wrapped_key, uint32_t allowed_schemes, bool force) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; - s.MakeRSACertificate(&encrypted, &signature, allowed_schemes, - encoded_rsa_key_); - s.RewrapRSAKey(encrypted, signature, wrapped_key, force); + ASSERT_NO_FATAL_FAILURE(s.MakeRSACertificate( + &encrypted, &signature, allowed_schemes, encoded_rsa_key_)); + ASSERT_NO_FATAL_FAILURE( + s.RewrapRSAKey(encrypted, signature, wrapped_key, force)); // Verify that the clear key is not contained in the wrapped key. // It should be encrypted. ASSERT_EQ(NULL, find(*wrapped_key, encoded_rsa_key_)); @@ -2618,8 +1763,8 @@ TEST_F(OEMCryptoLoadsCertificate, LoadRSASessionKey) { std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); Session s; - s.open(); - s.InstallRSASessionTestKey(wrapped_rsa_key); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key)); } TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) { @@ -2632,12 +1777,12 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; - s.MakeRSACertificate(&encrypted, &signature, kSign_RSASSA_PSS, - encoded_rsa_key_); + ASSERT_NO_FATAL_FAILURE(s.MakeRSACertificate( + &encrypted, &signature, kSign_RSASSA_PSS, encoded_rsa_key_)); vector wrapped_key; const uint8_t* message_ptr = reinterpret_cast(&encrypted); size_t wrapped_key_length = 0; @@ -2660,7 +1805,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2690,7 +1835,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2721,7 +1866,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadSignature) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2750,7 +1895,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadSignature) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2779,7 +1924,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRSAKey) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2812,7 +1957,7 @@ TEST_F(OEMCryptoLoadsCertificate, LoadWrappedRSAKey) { CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key[0], wrapped_rsa_key.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -2824,27 +1969,27 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateDecrypt) { std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); Session s; - s.open(); - s.InstallRSASessionTestKey(wrapped_rsa_key); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key)); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); } class OEMCryptoUsesCertificate : public OEMCryptoLoadsCertificate { protected: virtual void SetUp() { OEMCryptoLoadsCertificate::SetUp(); - session_.open(); - if (global_features.derive_key_method - != DeviceFeatures::LOAD_TEST_RSA_KEY) { + ASSERT_NO_FATAL_FAILURE(session_.open()); + if (global_features.derive_key_method != + DeviceFeatures::LOAD_TEST_RSA_KEY) { std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadDeviceRSAKey(session_.session_id(), - &wrapped_rsa_key[0], - wrapped_rsa_key.size())); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadDeviceRSAKey(session_.session_id(), &wrapped_rsa_key[0], + wrapped_rsa_key.size())); } } @@ -2856,22 +2001,24 @@ class OEMCryptoUsesCertificate : public OEMCryptoLoadsCertificate { Session session_; }; -// Test performance -TEST_F( OEMCryptoLoadsCertificate, RSAPerformance) { +// This test is not run by default, because it takes a long time and +// is used to measure RSA performance, not test functionality. +TEST_F(OEMCryptoLoadsCertificate, RSAPerformance) { OEMCryptoResult sts; - sleep(2); // Make sure are not nonce limited. - const uint32_t TestDuration = 5000; // milliseconds. + sleep(2); // Make sure are not nonce limited. + + const uint32_t TestDuration = 5000; // milliseconds. struct timeval start_time, end_time; gettimeofday(&start_time, NULL); gettimeofday(&end_time, NULL); double mtime = 0; long count = 0; - for(int i=0; i< 15; i++) { // Only 20 nonce available. + for (int i = 0; i < 15; i++) { // Only 20 nonce available. vector wrapped_key; CreateWrappedRSAKey(&wrapped_key, kSign_RSASSA_PSS, true); count++; gettimeofday(&end_time, NULL); - long seconds = end_time.tv_sec - start_time.tv_sec; + long seconds = end_time.tv_sec - start_time.tv_sec; long useconds = end_time.tv_usec - start_time.tv_usec; mtime = seconds * 1e3 + useconds * 1e-3; } @@ -2886,7 +2033,7 @@ TEST_F( OEMCryptoLoadsCertificate, RSAPerformance) { count = 0; do { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key[0], wrapped_rsa_key.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -2906,14 +2053,14 @@ TEST_F( OEMCryptoLoadsCertificate, RSAPerformance) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); count++; gettimeofday(&end_time, NULL); - long seconds = end_time.tv_sec - start_time.tv_sec; + long seconds = end_time.tv_sec - start_time.tv_sec; long useconds = end_time.tv_usec - start_time.tv_usec; mtime = seconds * 1e3 + useconds * 1e-3; - } while(mtime < TestDuration); + } while (mtime < TestDuration); double license_request_time = mtime / count; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key[0], wrapped_rsa_key.size())); @@ -2954,16 +2101,16 @@ TEST_F( OEMCryptoLoadsCertificate, RSAPerformance) { enc_context.size())); count++; gettimeofday(&end_time, NULL); - long seconds = end_time.tv_sec - start_time.tv_sec; + long seconds = end_time.tv_sec - start_time.tv_sec; long useconds = end_time.tv_usec - start_time.tv_usec; mtime = seconds * 1e3 + useconds * 1e-3; - } while(mtime < TestDuration); + } while (mtime < TestDuration); double derive_keys_time = mtime / count; const char* level = OEMCrypto_SecurityLevel(); printf("PERF:head, security, provision (ms), lic req(ms), derive keys(ms)\n"); printf("PERF:stat, %s, %8.3f, %8.3f, %8.3f\n", level, provision_time, - license_request_time, derive_keys_time); + license_request_time, derive_keys_time); } TEST_F(OEMCryptoUsesCertificate, RSASignature) { @@ -2974,25 +2121,24 @@ TEST_F(OEMCryptoUsesCertificate, RSASignature) { size_t signature_length = 0; uint8_t signature[500]; - sts = OEMCrypto_GenerateRSASignature(session_.session_id(), - &licenseRequest[0], - licenseRequest.size(), signature, - &signature_length, kSign_RSASSA_PSS); + sts = OEMCrypto_GenerateRSASignature( + session_.session_id(), &licenseRequest[0], licenseRequest.size(), + signature, &signature_length, kSign_RSASSA_PSS); ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); ASSERT_NE(static_cast(0), signature_length); ASSERT_GE(sizeof(signature), signature_length); - sts = OEMCrypto_GenerateRSASignature(session_.session_id(), &licenseRequest[0], - licenseRequest.size(), signature, - &signature_length, kSign_RSASSA_PSS); + sts = OEMCrypto_GenerateRSASignature( + session_.session_id(), &licenseRequest[0], licenseRequest.size(), + signature, &signature_length, kSign_RSASSA_PSS); ASSERT_EQ(OEMCrypto_SUCCESS, sts); // In the real world, the signature above would just have been used to contact // the license server to get this response. session_.PreparePublicKey(); - session_.VerifyRSASignature(licenseRequest, signature, signature_length, - kSign_RSASSA_PSS); + ASSERT_NO_FATAL_FAILURE(session_.VerifyRSASignature( + licenseRequest, signature, signature_length, kSign_RSASSA_PSS)); } // This test attempts to use alternate algorithms for loaded device certs. @@ -3001,7 +2147,7 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { void DisallowForbiddenPadding(RSA_Padding_Scheme scheme, size_t size) { OEMCryptoResult sts; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key_[0], wrapped_rsa_key_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -3023,7 +2169,7 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { &signature_length, scheme); } - ASSERT_NE(OEMCrypto_SUCCESS, sts) + EXPECT_NE(OEMCrypto_SUCCESS, sts) << "Signed with forbidden padding scheme=" << (int)scheme << ", size=" << (int)size; vector zero(signature_length, 0); @@ -3033,7 +2179,7 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { void TestSignature(RSA_Padding_Scheme scheme, size_t size) { OEMCryptoResult sts; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key_[0], wrapped_rsa_key_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -3056,14 +2202,15 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { << "Failed to sign with padding scheme=" << (int)scheme << ", size=" << (int)size; s.PreparePublicKey(); - s.VerifyRSASignature(licenseRequest, signature, signature_length, scheme); + ASSERT_NO_FATAL_FAILURE(s.VerifyRSASignature(licenseRequest, signature, + signature_length, scheme)); delete[] signature; } void DisallowDeriveKeys() { OEMCryptoResult sts; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key_[0], wrapped_rsa_key_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -3165,96 +2312,89 @@ class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { // This encodes the RSA key used in the PKCS#1 signing tests below. void BuildRSAKey() { vector field_n = - encode(0x02, wvcdm::a2b_hex( - "00" - "df271fd25f8644496b0c81be4bd50297" - "ef099b002a6fd67727eb449cea566ed6" - "a3981a71312a141cabc9815c1209e320" - "a25b32464e9999f18ca13a9fd3892558" - "f9e0adefdd3650dd23a3f036d60fe398" - "843706a40b0b8462c8bee3bce12f1f28" - "60c2444cdc6a44476a75ff4aa24273cc" - "be3bf80248465f8ff8c3a7f3367dfc0d" - "f5b6509a4f82811cedd81cdaaa73c491" - "da412170d544d4ba96b97f0afc806549" - "8d3a49fd910992a1f0725be24f465cfe" - "7e0eabf678996c50bc5e7524abf73f15" - "e5bef7d518394e3138ce4944506aaaaf" - "3f9b236dcab8fc00f87af596fdc3d9d6" - "c75cd508362fae2cbeddcc4c7450b17b" - "776c079ecca1f256351a43b97dbe2153")); + encode(0x02, wvcdm::a2b_hex("00" + "df271fd25f8644496b0c81be4bd50297" + "ef099b002a6fd67727eb449cea566ed6" + "a3981a71312a141cabc9815c1209e320" + "a25b32464e9999f18ca13a9fd3892558" + "f9e0adefdd3650dd23a3f036d60fe398" + "843706a40b0b8462c8bee3bce12f1f28" + "60c2444cdc6a44476a75ff4aa24273cc" + "be3bf80248465f8ff8c3a7f3367dfc0d" + "f5b6509a4f82811cedd81cdaaa73c491" + "da412170d544d4ba96b97f0afc806549" + "8d3a49fd910992a1f0725be24f465cfe" + "7e0eabf678996c50bc5e7524abf73f15" + "e5bef7d518394e3138ce4944506aaaaf" + "3f9b236dcab8fc00f87af596fdc3d9d6" + "c75cd508362fae2cbeddcc4c7450b17b" + "776c079ecca1f256351a43b97dbe2153")); vector field_e = encode(0x02, wvcdm::a2b_hex("010001")); vector field_d = - encode(0x02, wvcdm::a2b_hex( - "5bd910257830dce17520b03441a51a8c" - "ab94020ac6ecc252c808f3743c95b7c8" - "3b8c8af1a5014346ebc4242cdfb5d718" - "e30a733e71f291e4d473b61bfba6daca" - "ed0a77bd1f0950ae3c91a8f901118825" - "89e1d62765ee671e7baeea309f64d447" - "bbcfa9ea12dce05e9ea8939bc5fe6108" - "581279c982b308794b3448e7f7b95229" - "2df88c80cb40142c4b5cf5f8ddaa0891" - "678d610e582fcb880f0d707caf47d09a" - "84e14ca65841e5a3abc5e9dba94075a9" - "084341f0edad9b68e3b8e082b80b6e6e" - "8a0547b44fb5061b6a9131603a5537dd" - "abd01d8e863d8922e9aa3e4bfaea0b39" - "d79283ad2cbc8a59cce7a6ecf4e4c81e" - "d4c6591c807defd71ab06866bb5e7745")); + encode(0x02, wvcdm::a2b_hex("5bd910257830dce17520b03441a51a8c" + "ab94020ac6ecc252c808f3743c95b7c8" + "3b8c8af1a5014346ebc4242cdfb5d718" + "e30a733e71f291e4d473b61bfba6daca" + "ed0a77bd1f0950ae3c91a8f901118825" + "89e1d62765ee671e7baeea309f64d447" + "bbcfa9ea12dce05e9ea8939bc5fe6108" + "581279c982b308794b3448e7f7b95229" + "2df88c80cb40142c4b5cf5f8ddaa0891" + "678d610e582fcb880f0d707caf47d09a" + "84e14ca65841e5a3abc5e9dba94075a9" + "084341f0edad9b68e3b8e082b80b6e6e" + "8a0547b44fb5061b6a9131603a5537dd" + "abd01d8e863d8922e9aa3e4bfaea0b39" + "d79283ad2cbc8a59cce7a6ecf4e4c81e" + "d4c6591c807defd71ab06866bb5e7745")); vector field_p = - encode(0x02, wvcdm::a2b_hex( - "00" - "f44f5e4246391f482b2f5296e3602eb3" - "4aa136427710f7c0416d403fd69d4b29" - "130cfebef34e885abdb1a8a0a5f0e9b5" - "c33e1fc3bfc285b1ae17e40cc67a1913" - "dd563719815ebaf8514c2a7aa0018e63" - "b6c631dc315a46235716423d11ff5803" - "4e610645703606919f5c7ce2660cd148" - "bd9efc123d9c54b6705590d006cfcf3f")); + encode(0x02, wvcdm::a2b_hex("00" + "f44f5e4246391f482b2f5296e3602eb3" + "4aa136427710f7c0416d403fd69d4b29" + "130cfebef34e885abdb1a8a0a5f0e9b5" + "c33e1fc3bfc285b1ae17e40cc67a1913" + "dd563719815ebaf8514c2a7aa0018e63" + "b6c631dc315a46235716423d11ff5803" + "4e610645703606919f5c7ce2660cd148" + "bd9efc123d9c54b6705590d006cfcf3f")); vector field_q = - encode(0x02, wvcdm::a2b_hex( - "00" - "e9d49841e0e0a6ad0d517857133e36dc" - "72c1bdd90f9174b52e26570f373640f1" - "c185e7ea8e2ed7f1e4ebb951f70a5802" - "3633b0097aec67c6dcb800fc1a67f9bb" - "0563610f08ebc8746ad129772136eb1d" - "daf46436450d318332a84982fe5d28db" - "e5b3e912407c3e0e03100d87d436ee40" - "9eec1cf85e80aba079b2e6106b97bced")); + encode(0x02, wvcdm::a2b_hex("00" + "e9d49841e0e0a6ad0d517857133e36dc" + "72c1bdd90f9174b52e26570f373640f1" + "c185e7ea8e2ed7f1e4ebb951f70a5802" + "3633b0097aec67c6dcb800fc1a67f9bb" + "0563610f08ebc8746ad129772136eb1d" + "daf46436450d318332a84982fe5d28db" + "e5b3e912407c3e0e03100d87d436ee40" + "9eec1cf85e80aba079b2e6106b97bced")); vector field_exp1 = - encode(0x02, wvcdm::a2b_hex( - "00" - "ed102acdb26871534d1c414ecad9a4d7" - "32fe95b10eea370da62f05de2c393b1a" - "633303ea741b6b3269c97f704b352702" - "c9ae79922f7be8d10db67f026a8145de" - "41b30c0a42bf923bac5f7504c248604b" - "9faa57ed6b3246c6ba158e36c644f8b9" - "548fcf4f07e054a56f768674054440bc" - "0dcbbc9b528f64a01706e05b0b91106f")); + encode(0x02, wvcdm::a2b_hex("00" + "ed102acdb26871534d1c414ecad9a4d7" + "32fe95b10eea370da62f05de2c393b1a" + "633303ea741b6b3269c97f704b352702" + "c9ae79922f7be8d10db67f026a8145de" + "41b30c0a42bf923bac5f7504c248604b" + "9faa57ed6b3246c6ba158e36c644f8b9" + "548fcf4f07e054a56f768674054440bc" + "0dcbbc9b528f64a01706e05b0b91106f")); vector field_exp2 = - encode(0x02, wvcdm::a2b_hex( - "6827924a85e88b55ba00f8219128bd37" - "24c6b7d1dfe5629ef197925fecaff5ed" - "b9cdf3a7befd8ea2e8dd3707138b3ff8" - "7c3c39c57f439e562e2aa805a39d7cd7" - "9966d2ece7845f1dbc16bee99999e4d0" - "bf9eeca45fcda8a8500035fe6b5f03bc" - "2f6d1bfc4d4d0a3723961af0cdce4a01" - "eec82d7f5458ec19e71b90eeef7dff61")); + encode(0x02, wvcdm::a2b_hex("6827924a85e88b55ba00f8219128bd37" + "24c6b7d1dfe5629ef197925fecaff5ed" + "b9cdf3a7befd8ea2e8dd3707138b3ff8" + "7c3c39c57f439e562e2aa805a39d7cd7" + "9966d2ece7845f1dbc16bee99999e4d0" + "bf9eeca45fcda8a8500035fe6b5f03bc" + "2f6d1bfc4d4d0a3723961af0cdce4a01" + "eec82d7f5458ec19e71b90eeef7dff61")); vector field_invq = - encode(0x02, wvcdm::a2b_hex( - "57b73888d183a99a6307422277551a3d" - "9e18adf06a91e8b55ceffef9077c8496" - "948ecb3b16b78155cb2a3a57c119d379" - "951c010aa635edcf62d84c5a122a8d67" - "ab5fa9e5a4a8772a1e943bafc70ae3a4" - "c1f0f3a4ddffaefd1892c8cb33bb0d0b" - "9590e963a69110fb34db7b906fc4ba28" - "36995aac7e527490ac952a02268a4f18")); + encode(0x02, wvcdm::a2b_hex("57b73888d183a99a6307422277551a3d" + "9e18adf06a91e8b55ceffef9077c8496" + "948ecb3b16b78155cb2a3a57c119d379" + "951c010aa635edcf62d84c5a122a8d67" + "ab5fa9e5a4a8772a1e943bafc70ae3a4" + "c1f0f3a4ddffaefd1892c8cb33bb0d0b" + "9590e963a69110fb34db7b906fc4ba28" + "36995aac7e527490ac952a02268a4f18")); // Header of rsa key is constant. encoded_rsa_key_ = wvcdm::a2b_hex( @@ -3298,7 +2438,7 @@ class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { const vector& correct_signature) { OEMCryptoResult sts; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key_[0], wrapped_rsa_key_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -3341,9 +2481,10 @@ class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { // Also verify that our verification algorithm agrees. This is not needed // to test OEMCrypto, but it does verify that this test is valid. - s.VerifyRSASignature(digest, &signature[0], signature_length, scheme); - s.VerifyRSASignature(digest, &correct_signature[0], - correct_signature.size(), scheme); + ASSERT_NO_FATAL_FAILURE( + s.VerifyRSASignature(digest, &signature[0], signature_length, scheme)); + ASSERT_NO_FATAL_FAILURE(s.VerifyRSASignature( + digest, &correct_signature[0], correct_signature.size(), scheme)); } }; @@ -4042,11 +3183,14 @@ TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_20) { class GenericCryptoTest : public OEMCryptoSessionTests { protected: + GenericCryptoTest() : buffer_size_(160) {} + virtual void SetUp() { OEMCryptoSessionTests::SetUp(); - session_.open(); - session_.GenerateTestSessionKeys(); - MakeFourKeys(); + buffer_size_ = 160; + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(session_.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(MakeFourKeys()); } virtual void TearDown() { @@ -4056,7 +3200,8 @@ class GenericCryptoTest : public OEMCryptoSessionTests { void MakeFourKeys(uint32_t duration = kDuration, uint32_t control = 0, uint32_t nonce = 0, const std::string& pst = "") { - session_.FillSimpleMessage(duration, control, nonce, pst); + ASSERT_NO_FATAL_FAILURE( + session_.FillSimpleMessage(duration, control, nonce, pst)); session_.license().keys[0].control.control_bits |= htonl(wvoec_mock::kControlAllowEncrypt); session_.license().keys[1].control.control_bits |= @@ -4069,7 +3214,7 @@ class GenericCryptoTest : public OEMCryptoSessionTests { session_.license().keys[2].key_data_length = wvcdm::MAC_KEY_SIZE; session_.license().keys[3].key_data_length = wvcdm::MAC_KEY_SIZE; - clear_buffer_.assign(kBufferSize, 0); + clear_buffer_.assign(buffer_size_, 0); for (size_t i = 0; i < clear_buffer_.size(); i++) { clear_buffer_[i] = 1 + i % 250; } @@ -4079,15 +3224,16 @@ class GenericCryptoTest : public OEMCryptoSessionTests { } void EncryptAndLoadKeys() { - session_.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); session_.LoadTestKeys(); } void EncryptBuffer(unsigned int key_index, const vector& in_buffer, vector* out_buffer) { AES_KEY aes_key; - ASSERT_EQ(0, AES_set_encrypt_key(session_.license().keys[key_index].key_data, - AES_BLOCK_SIZE * 8, &aes_key)); + ASSERT_EQ(0, + AES_set_encrypt_key(session_.license().keys[key_index].key_data, + AES_BLOCK_SIZE * 8, &aes_key)); uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; memcpy(iv_buffer, iv_, wvcdm::KEY_IV_SIZE); out_buffer->resize(in_buffer.size()); @@ -4098,8 +3244,7 @@ class GenericCryptoTest : public OEMCryptoSessionTests { } // Sign the buffer with the specified key. - void SignBuffer(unsigned int key_index, - const vector& in_buffer, + void SignBuffer(unsigned int key_index, const vector& in_buffer, vector* signature) { unsigned int md_len = SHA256_DIGEST_LENGTH; signature->resize(SHA256_DIGEST_LENGTH); @@ -4113,16 +3258,17 @@ class GenericCryptoTest : public OEMCryptoSessionTests { OEMCryptoResult sts; vector expected_encrypted; EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); - sts = OEMCrypto_SelectKey( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector encrypted(buffer_length); - sts = OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - buffer_length, iv_, algorithm, &encrypted[0]); - ASSERT_NE(OEMCrypto_SUCCESS, sts); + sts = + OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], + buffer_length, iv_, algorithm, &encrypted[0]); + EXPECT_NE(OEMCrypto_SUCCESS, sts); expected_encrypted.resize(buffer_length); - ASSERT_NE(encrypted, expected_encrypted); + EXPECT_NE(encrypted, expected_encrypted); } void BadDecrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, @@ -4130,16 +3276,16 @@ class GenericCryptoTest : public OEMCryptoSessionTests { OEMCryptoResult sts; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - sts = OEMCrypto_SelectKey( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector resultant(encrypted.size()); - sts = OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - buffer_length, iv_, algorithm, - &resultant[0]); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NE(clear_buffer_, resultant); + sts = + OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], + buffer_length, iv_, algorithm, &resultant[0]); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(clear_buffer_, resultant); } void BadSign(unsigned int key_index, OEMCrypto_Algorithm algorithm) { @@ -4147,17 +3293,17 @@ class GenericCryptoTest : public OEMCryptoSessionTests { vector expected_signature; SignBuffer(key_index, clear_buffer_, &expected_signature); - sts = OEMCrypto_SelectKey( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); size_t signature_length = (size_t)SHA256_DIGEST_LENGTH; vector signature(SHA256_DIGEST_LENGTH); sts = OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), algorithm, - &signature[0], &signature_length); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NE(signature, expected_signature); + clear_buffer_.size(), algorithm, &signature[0], + &signature_length); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(signature, expected_signature); } void BadVerify(unsigned int key_index, OEMCrypto_Algorithm algorithm, @@ -4169,52 +3315,52 @@ class GenericCryptoTest : public OEMCryptoSessionTests { signature[0] ^= 42; } - sts = OEMCrypto_SelectKey( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); sts = OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), algorithm, &signature[0], signature_size); - ASSERT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(OEMCrypto_SUCCESS, sts); } - static const size_t kBufferSize = 160; // multiple of encryption block size. + // This must be a multiple of encryption block size. + size_t buffer_size_; vector clear_buffer_; vector encrypted_buffer_; uint8_t iv_[wvcdm::KEY_IV_SIZE]; Session session_; }; -TEST_F(GenericCryptoTest, GenericKeyLoad) { - EncryptAndLoadKeys(); -} +TEST_F(GenericCryptoTest, GenericKeyLoad) { EncryptAndLoadKeys(); } TEST_F(GenericCryptoTest, GenericKeyEncrypt) { EncryptAndLoadKeys(); unsigned int key_index = 0; vector expected_encrypted; EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); vector encrypted(clear_buffer_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &encrypted[0])); - ASSERT_EQ(encrypted, expected_encrypted); + OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); + ASSERT_EQ(expected_encrypted, encrypted); } TEST_F(GenericCryptoTest, GenericKeyBadEncrypt) { EncryptAndLoadKeys(); - BadEncrypt(0, OEMCrypto_HMAC_SHA256, kBufferSize); - BadEncrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize - 10); - BadEncrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); - BadEncrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); - BadEncrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + BadEncrypt(0, OEMCrypto_HMAC_SHA256, buffer_size_); + // The buffer size must be a multiple of 16, so subtracting 10 is bad. + BadEncrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_ - 10); + BadEncrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadEncrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadEncrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); } TEST_F(GenericCryptoTest, GenericKeyDecrypt) { @@ -4222,16 +3368,16 @@ TEST_F(GenericCryptoTest, GenericKeyDecrypt) { unsigned int key_index = 1; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); vector resultant(encrypted.size()); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), - iv_, OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_EQ(clear_buffer_, resultant); } @@ -4242,26 +3388,27 @@ TEST_F(GenericCryptoTest, GenericSecureToClear) { unsigned int key_index = 1; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); vector resultant(encrypted.size()); ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_NE(clear_buffer_, resultant); } TEST_F(GenericCryptoTest, GenericKeyBadDecrypt) { EncryptAndLoadKeys(); - BadDecrypt(1, OEMCrypto_HMAC_SHA256, kBufferSize); - BadDecrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize - 10); - BadDecrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); - BadDecrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); - BadDecrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + BadDecrypt(1, OEMCrypto_HMAC_SHA256, buffer_size_); + // The buffer size must be a multiple of 16, so subtracting 10 is bad. + BadDecrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_ - 10); + BadDecrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadDecrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadDecrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); } TEST_F(GenericCryptoTest, GenericKeySign) { @@ -4270,10 +3417,11 @@ TEST_F(GenericCryptoTest, GenericKeySign) { vector expected_signature; SignBuffer(key_index, clear_buffer_, &expected_signature); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); size_t gen_signature_length = 0; ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], @@ -4285,7 +3433,7 @@ TEST_F(GenericCryptoTest, GenericKeySign) { OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), OEMCrypto_HMAC_SHA256, &signature[0], &gen_signature_length)); - ASSERT_EQ(signature, expected_signature); + ASSERT_EQ(expected_signature, signature); } TEST_F(GenericCryptoTest, GenericKeyBadSign) { @@ -4302,14 +3450,15 @@ TEST_F(GenericCryptoTest, GenericKeyVerify) { vector signature; SignBuffer(key_index, clear_buffer_, &signature); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - &signature[0], signature.size())); + OEMCrypto_Generic_Verify( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, &signature[0], signature.size())); } TEST_F(GenericCryptoTest, GenericKeyBadVerify) { @@ -4323,6 +3472,91 @@ TEST_F(GenericCryptoTest, GenericKeyBadVerify) { BadVerify(3, OEMCrypto_AES_CBC_128_NO_PADDING, SHA256_DIGEST_LENGTH, false); } +TEST_F(GenericCryptoTest, GenericKeyEncryptLargeBufferAPI11) { + // Some applications are known to pass in a block that is almost 400k, but + // the layer above oemcrypto can break it into 100k chunks. + buffer_size_ = 100 * 1024; + EncryptAndLoadKeys(); + unsigned int key_index = 0; + vector expected_encrypted; + EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); + vector encrypted(clear_buffer_.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); + ASSERT_EQ(expected_encrypted, encrypted); +} + +TEST_F(GenericCryptoTest, GenericKeyDecryptLargeBufferAPI11) { + // Some applications are known to pass in a block that is almost 400k. + buffer_size_ = 400 * 1024; + EncryptAndLoadKeys(); + unsigned int key_index = 1; + vector encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); + vector resultant(encrypted.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); + ASSERT_EQ(clear_buffer_, resultant); +} + +TEST_F(GenericCryptoTest, GenericKeySignLargeBufferAPI11) { + buffer_size_ = 100 * 1024; + EncryptAndLoadKeys(); + unsigned int key_index = 2; + vector expected_signature; + SignBuffer(key_index, clear_buffer_, &expected_signature); + + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); + size_t gen_signature_length = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + NULL, &gen_signature_length)); + ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); + vector signature(SHA256_DIGEST_LENGTH); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + &signature[0], &gen_signature_length)); + ASSERT_EQ(expected_signature, signature); +} + +TEST_F(GenericCryptoTest, GenericKeyVerifyLargeBufferAPI11) { + buffer_size_ = 100 * 1024; + EncryptAndLoadKeys(); + unsigned int key_index = 3; + vector signature; + SignBuffer(key_index, clear_buffer_, &signature); + + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Verify( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, &signature[0], signature.size())); +} + TEST_F(GenericCryptoTest, KeyDurationEncrypt) { EncryptAndLoadKeys(); vector expected_encrypted; @@ -4332,23 +3566,24 @@ TEST_F(GenericCryptoTest, KeyDurationEncrypt) { sleep(kShortSleep); // Should still be valid key. + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); - ASSERT_EQ(encrypted, expected_encrypted); + OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); + ASSERT_EQ(expected_encrypted, encrypted); sleep(kLongSleep); // Should be expired key. encrypted.assign(clear_buffer_.size(), 0); ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, - OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); + OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); ASSERT_NE(encrypted, expected_encrypted); } @@ -4358,29 +3593,28 @@ TEST_F(GenericCryptoTest, KeyDurationDecrypt) { unsigned int key_index = 1; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); sleep(kShortSleep); // Should still be valid key. vector resultant(encrypted.size()); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_EQ(clear_buffer_, resultant); sleep(kLongSleep); // Should be expired key. resultant.assign(encrypted.size(), 0); ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_NE(clear_buffer_, resultant); } @@ -4393,10 +3627,11 @@ TEST_F(GenericCryptoTest, KeyDurationSign) { size_t signature_length = signature.size(); SignBuffer(key_index, clear_buffer_, &expected_signature); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); sleep(kShortSleep); // Should still be valid key. @@ -4404,7 +3639,7 @@ TEST_F(GenericCryptoTest, KeyDurationSign) { OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), OEMCrypto_HMAC_SHA256, &signature[0], &signature_length)); - ASSERT_EQ(signature, expected_signature); + ASSERT_EQ(expected_signature, signature); sleep(kLongSleep); // Should be expired key. @@ -4413,7 +3648,7 @@ TEST_F(GenericCryptoTest, KeyDurationSign) { OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), OEMCrypto_HMAC_SHA256, &signature[0], &signature_length)); - ASSERT_NE(signature, expected_signature); + ASSERT_NE(expected_signature, signature); } TEST_F(GenericCryptoTest, KeyDurationVerify) { @@ -4423,24 +3658,25 @@ TEST_F(GenericCryptoTest, KeyDurationVerify) { vector signature; SignBuffer(key_index, clear_buffer_, &signature); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); sleep(kShortSleep); // Should still be valid key. ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - &signature[0], signature.size())); + OEMCrypto_Generic_Verify( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, &signature[0], signature.size())); sleep(kLongSleep); // Should be expired key. ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, - OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - &signature[0], signature.size())); + OEMCrypto_Generic_Verify( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, &signature[0], signature.size())); } const unsigned int kLongKeyId = 2; @@ -4450,11 +3686,11 @@ class GenericCryptoKeyIdLengthTest : public GenericCryptoTest { virtual void SetUp() { GenericCryptoTest::SetUp(); const uint32_t kNoNonce = 0; - session_.FillSimpleMessage(kDuration, wvoec_mock::kControlAllowDecrypt, - kNoNonce); + ASSERT_NO_FATAL_FAILURE(session_.FillSimpleMessage( + kDuration, wvoec_mock::kControlAllowDecrypt, kNoNonce)); // We are testing that the key ids do not have to have the same length. - session_.SetKeyId(0, "123456789012"); // 12 bytes (common key id length). - session_.SetKeyId(1, "12345"); // short key id. + session_.SetKeyId(0, "123456789012"); // 12 bytes (common key id length). + session_.SetKeyId(1, "12345"); // short key id. session_.SetKeyId(2, "1234567890123456"); // 16 byte key id. (default) session_.SetKeyId(3, "12345678901234"); // 14 byte. (uncommon) ASSERT_EQ(2u, kLongKeyId); @@ -4462,9 +3698,9 @@ class GenericCryptoKeyIdLengthTest : public GenericCryptoTest { // Make all four keys have the same length. void SetUniformKeyIdLength(size_t key_id_length) { - for(unsigned int i = 0; i < 4; i++) { + for (unsigned int i = 0; i < 4; i++) { string key_id; - key_id.resize(key_id_length, i + 'a'); + key_id.resize(key_id_length, i + 'a'); session_.SetKeyId(i, key_id); } } @@ -4484,30 +3720,24 @@ class GenericCryptoKeyIdLengthTest : public GenericCryptoTest { memcpy(key_id_buffer.data(), session_.license().keys[kLongKeyId].key_id, session_.license().keys[kLongKeyId].key_id_length); EncryptBuffer(key_index, clear_buffer_, &encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), key_id_buffer.data(), - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), key_id_buffer.data(), + session_.license().keys[key_index].key_id_length)); vector resultant(encrypted.size()); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_EQ(clear_buffer_, resultant); } }; -TEST_F(GenericCryptoKeyIdLengthTest, MediumKeyId) { - TestWithKey(0); -} +TEST_F(GenericCryptoKeyIdLengthTest, MediumKeyId) { TestWithKey(0); } -TEST_F(GenericCryptoKeyIdLengthTest, ShortKeyId) { - TestWithKey(1); -} +TEST_F(GenericCryptoKeyIdLengthTest, ShortKeyId) { TestWithKey(1); } -TEST_F(GenericCryptoKeyIdLengthTest, LongKeyId) { - TestWithKey(2); -} +TEST_F(GenericCryptoKeyIdLengthTest, LongKeyId) { TestWithKey(2); } TEST_F(GenericCryptoKeyIdLengthTest, UniformShortKeyId) { SetUniformKeyIdLength(5); @@ -4537,18 +3767,20 @@ class UsageTableTest : public GenericCryptoTest { } void LoadOfflineLicense(Session& s, const std::string& pst) { - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); s.GenerateReport(pst); s.GenerateReport(pst); EXPECT_EQ(kUnused, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.close(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.close()); } void PrintDotsWhileSleep(time_t total_seconds, time_t interval_seconds) { @@ -4587,20 +3819,20 @@ TEST_F(UsageTableTest, PSTReportSizes) { OEMCrypto_PST_Report report; uint8_t* location = reinterpret_cast(&report); EXPECT_EQ(48u, sizeof(report)); - uint8_t *field; - field = reinterpret_cast(&report.status); + uint8_t* field; + field = reinterpret_cast(&report.status); EXPECT_EQ(20, field - location); - field = reinterpret_cast(&report.clock_security_level); + field = reinterpret_cast(&report.clock_security_level); EXPECT_EQ(21, field - location); - field = reinterpret_cast(&report.pst_length); + field = reinterpret_cast(&report.pst_length); EXPECT_EQ(22, field - location); - field = reinterpret_cast(&report.seconds_since_license_received); + field = reinterpret_cast(&report.seconds_since_license_received); EXPECT_EQ(24, field - location); - field = reinterpret_cast(&report.seconds_since_first_decrypt); + field = reinterpret_cast(&report.seconds_since_first_decrypt); EXPECT_EQ(32, field - location); - field = reinterpret_cast(&report.seconds_since_last_decrypt); + field = reinterpret_cast(&report.seconds_since_last_decrypt); EXPECT_EQ(40, field - location); - field = reinterpret_cast(&report.pst); + field = reinterpret_cast(&report.pst); EXPECT_EQ(48, field - location); } @@ -4608,13 +3840,13 @@ TEST_P(UsageTableTestWithMAC, OnlineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); s.GenerateReport(pst); @@ -4622,297 +3854,301 @@ TEST_P(UsageTableTestWithMAC, OnlineLicense) { s.GenerateReport(pst); s.GenerateReport(pst); EXPECT_EQ(kUnused, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.TestDecryptCTR(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); EXPECT_EQ(kActive, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR(0, wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR(0, wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); s.GenerateReport(pst); EXPECT_EQ(kInactive, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); } TEST_F(UsageTableTest, RepeatOnlineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); Session s2; - s2.open(); - s2.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); uint8_t* pst_ptr = s.encrypted_license().pst; // Trying to reuse a PST is bad. We use session ID for s2, everything else // reused from s. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), - sizeof(MessageData), &s.signature()[0], - s.signature().size(), - s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, - s.key_array(), pst_ptr, pst.length())); - s2.close(); + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), pst_ptr, pst.length())); + ASSERT_NO_FATAL_FAILURE(s2.close()); } // A license with non-zero replay control bits needs a valid pst.. TEST_F(UsageTableTest, OnlineEmptyPST) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce()); - s.EncryptAndSign(); + s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, s.key_array(), - NULL, 0); + s.encrypted_license().mac_keys, kNumKeys, s.key_array(), NULL, 0); ASSERT_NE(OEMCrypto_SUCCESS, sts); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } TEST_F(UsageTableTest, EmptyTable) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "no_pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); OEMCrypto_DeleteUsageTable(); Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, false); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); } TEST_F(UsageTableTest, FiftyEntries) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s1; - s1.open(); - s1.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s1.GenerateTestSessionKeys()); std::string pst1 = "pst saved"; - s1.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s1.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s1.get_nonce(), pst1); - s1.EncryptAndSign(); - s1.LoadTestKeys(pst1, new_mac_keys_); + s1.get_nonce(), pst1)); + ASSERT_NO_FATAL_FAILURE(s1.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst1, new_mac_keys_)); sleep(kShortSleep); - const size_t ENTRY_COUNT = 49;// API says should hold at least 50 entries. + const size_t ENTRY_COUNT = 49; // API says should hold at least 50 entries. vector sessions(ENTRY_COUNT); - for (size_t i=0; i < ENTRY_COUNT; i++) { - sessions[i].open(); - sessions[i].GenerateTestSessionKeys(); + for (size_t i = 0; i < ENTRY_COUNT; i++) { + ASSERT_NO_FATAL_FAILURE(sessions[i].open()); + ASSERT_NO_FATAL_FAILURE(sessions[i].GenerateTestSessionKeys()); std::string pst = "pst "; char c = 'A' + i; pst = pst + c; - sessions[i].FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(sessions[i].FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - sessions[i].get_nonce(), pst); - sessions[i].EncryptAndSign(); - sessions[i].LoadTestKeys(pst, new_mac_keys_); + sessions[i].get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(sessions[i].EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)); sessions[i].GenerateReport(pst); - sessions[i].close(); + ASSERT_NO_FATAL_FAILURE(sessions[i].close()); } - for (size_t i=0; istatus); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } sleep(kShortSleep); // If I add too many entries, it can delete the older ones first, except // it shouldn't delete the one attached to an open session. (s1) - for (size_t i=0; i < ENTRY_COUNT; i++) { - sessions[i].open(); - sessions[i].GenerateTestSessionKeys(); + for (size_t i = 0; i < ENTRY_COUNT; i++) { + ASSERT_NO_FATAL_FAILURE(sessions[i].open()); + ASSERT_NO_FATAL_FAILURE(sessions[i].GenerateTestSessionKeys()); std::string pst = "newer pst "; char c = 'A' + i; pst = pst + c; - sessions[i].FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(sessions[i].FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - sessions[i].get_nonce(), pst); - sessions[i].EncryptAndSign(); - sessions[i].LoadTestKeys(pst, new_mac_keys_); + sessions[i].get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(sessions[i].EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)); sessions[i].GenerateReport(pst); - sessions[i].close(); + ASSERT_NO_FATAL_FAILURE(sessions[i].close()); } - for (int i=0; i<49; i++) { + for (int i = 0; i < 49; i++) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); std::string pst = "newer pst "; char c = 'A' + i; pst = pst + c; s.GenerateReport(pst, true, &sessions[i]); EXPECT_EQ(kUnused, s.pst_report()->status); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } - s1.close(); - s1.open(); // Make sure s1's entry is still in the table. + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE( + s1.open()); // Make sure s1's entry is still in the table. s1.GenerateReport(pst1); EXPECT_EQ(kUnused, s1.pst_report()->status); - s1.close(); + ASSERT_NO_FATAL_FAILURE(s1.close()); } TEST_P(UsageTableTestWithMAC, DeleteUnusedEntry) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should be able to generate report and copy mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); EXPECT_EQ(kUnused, s2.pst_report()->status); s2.DeleteEntry(pst); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // Now that session is deleted, we can't generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, false); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteActiveEntry) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); - s.TestDecryptCTR(); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should be able to generate report and copy mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); EXPECT_EQ(kActive, s2.pst_report()->status); s2.DeleteEntry(pst); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // Now that session is deleted, we can't generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, false); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, ForceDeleteActiveEntry) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); - s.TestDecryptCTR(); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); s.ForceDeleteEntry(pst); // Now that session is deleted, we can't generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, false); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteInactiveEntry) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); DeactivatePST(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should be able to generate report and copy mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); EXPECT_EQ(kInactive, s2.pst_report()->status); s2.DeleteEntry(pst); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // Now that session is deleted, we can't generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, false); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteEntryBadSignature) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should be able to generate report and copy mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); uint8_t* pst_ptr = s2.encrypted_license().pst; memcpy(pst_ptr, pst.c_str(), min(sizeof(s2.license().pst), pst.length())); @@ -4923,34 +4159,34 @@ TEST_P(UsageTableTestWithMAC, DeleteEntryBadSignature) { OEMCrypto_DeleteUsageEntry(s2.session_id(), pst_ptr, pst.length(), s2.message_ptr(), sizeof(MessageData), &signature[0], signature.size())); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // The session is not deleted, we can still generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, true, &s); EXPECT_EQ(kUnused, s3.pst_report()->status); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteEntryWrongSession) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should not be able to delete without using GenerateReport // to load mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); // s2.GenerateReport(pst, true, &s); uint8_t* pst_ptr = s2.encrypted_license().pst; memcpy(pst_ptr, pst.c_str(), min(sizeof(s2.license().pst), pst.length())); @@ -4960,34 +4196,34 @@ TEST_P(UsageTableTestWithMAC, DeleteEntryWrongSession) { OEMCrypto_DeleteUsageEntry(s2.session_id(), pst_ptr, pst.length(), s2.message_ptr(), sizeof(MessageData), &signature[0], signature.size())); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // The session is not deleted, we can still generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, true, &s); EXPECT_EQ(kUnused, s3.pst_report()->status); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteEntryBadRange) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should not be able to delete if pst doesn't point into // message. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); uint8_t* pst_ptr = s2.license().pst; memcpy(pst_ptr, pst.c_str(), min(sizeof(s2.license().pst), pst.length())); @@ -4997,14 +4233,14 @@ TEST_P(UsageTableTestWithMAC, DeleteEntryBadRange) { OEMCrypto_DeleteUsageEntry(s2.session_id(), pst_ptr, pst.length(), s2.message_ptr(), sizeof(MessageData), &signature[0], signature.size())); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // The session is not deleted, we can still generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, true, &s); EXPECT_EQ(kUnused, s3.pst_report()->status); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeactivateBadPST) { @@ -5023,41 +4259,46 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoEncrypt) { std::string pst = "A PST"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); - MakeFourKeys(0, wvoec_mock::kControlNonceEnabled | - wvoec_mock::kControlNonceRequired, - nonce, pst); - session_.EncryptAndSign(); - session_.LoadTestKeys(pst, new_mac_keys_); + MakeFourKeys( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + nonce, pst); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 0; vector expected_encrypted; EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); - sts = OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector encrypted(clear_buffer_.size()); - sts = OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0]); + sts = OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0]); ASSERT_EQ(OEMCrypto_SUCCESS, sts); - EXPECT_EQ(encrypted, expected_encrypted); + EXPECT_EQ(expected_encrypted, encrypted); session_.GenerateReport(pst); EXPECT_EQ(kActive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); session_.GenerateReport(pst); EXPECT_EQ(kInactive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); encrypted.assign(clear_buffer_.size(), 0); - sts = OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0]); + sts = OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0]); ASSERT_NE(OEMCrypto_SUCCESS, sts); EXPECT_NE(encrypted, expected_encrypted); } @@ -5066,41 +4307,46 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoDecrypt) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); - MakeFourKeys(0, wvoec_mock::kControlNonceEnabled | - wvoec_mock::kControlNonceRequired, - nonce, pst); - session_.EncryptAndSign(); - session_.LoadTestKeys(pst, new_mac_keys_); + MakeFourKeys( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + nonce, pst); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 1; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - sts = OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector resultant(encrypted.size()); - sts = OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0]); + sts = OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0]); ASSERT_EQ(OEMCrypto_SUCCESS, sts); EXPECT_EQ(clear_buffer_, resultant); session_.GenerateReport(pst); EXPECT_EQ(kActive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); session_.GenerateReport(pst); EXPECT_EQ(kInactive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); resultant.assign(encrypted.size(), 0); - sts = OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0]); + sts = OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0]); ASSERT_NE(OEMCrypto_SUCCESS, sts); EXPECT_NE(clear_buffer_, resultant); } @@ -5109,18 +4355,19 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoSign) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); - MakeFourKeys(0, wvoec_mock::kControlNonceEnabled | - wvoec_mock::kControlNonceRequired, - nonce, pst); - session_.EncryptAndSign(); - session_.LoadTestKeys(pst, new_mac_keys_); + MakeFourKeys( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + nonce, pst); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 2; vector expected_signature; SignBuffer(key_index, clear_buffer_, &expected_signature); - sts = OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); size_t gen_signature_length = 0; @@ -5134,21 +4381,25 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoSign) { clear_buffer_.size(), OEMCrypto_HMAC_SHA256, &signature[0], &gen_signature_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(signature, expected_signature); + ASSERT_EQ(expected_signature, signature); session_.GenerateReport(pst); EXPECT_EQ(kActive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); session_.GenerateReport(pst); EXPECT_EQ(kInactive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); signature.assign(SHA256_DIGEST_LENGTH, 0); gen_signature_length = SHA256_DIGEST_LENGTH; @@ -5163,18 +4414,19 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoVerify) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); - MakeFourKeys(0, wvoec_mock::kControlNonceEnabled | - wvoec_mock::kControlNonceRequired, - nonce, pst); - session_.EncryptAndSign(); - session_.LoadTestKeys(pst, new_mac_keys_); + MakeFourKeys( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + nonce, pst); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 3; vector signature; SignBuffer(key_index, clear_buffer_, &signature); - sts = OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); sts = OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], @@ -5184,17 +4436,21 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoVerify) { session_.GenerateReport(pst); EXPECT_EQ(kActive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); session_.GenerateReport(pst); EXPECT_EQ(kInactive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); sts = OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), OEMCrypto_HMAC_SHA256, @@ -5206,74 +4462,66 @@ TEST_P(UsageTableTestWithMAC, OfflineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - LoadOfflineLicense(s, pst); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); } TEST_P(UsageTableTestWithMAC, ReloadOfflineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - LoadOfflineLicense(s, pst); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - // If there are errors in LoadOfflineLicense, that function will exit but this - // test will continue. The session will be left open and in an unknown state. - // Best just to abort in that case. - ASSERT_FALSE(s.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - - s.open(); // Offline license can be reused. - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); // Offline license can be reused. + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); // We will reuse the encrypted and signed message, so we don't call // FillSimpleMessage again. - s.LoadTestKeys(pst, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); s.GenerateReport(pst); EXPECT_EQ(kUnused, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.TestDecryptCTR(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); EXPECT_EQ(kActive, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt)); - s.close(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR(0, wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR(0, wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.close()); } TEST_P(UsageTableTestWithMAC, BadReloadOfflineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - LoadOfflineLicense(s, pst); - - // If there are errors in LoadOfflineLicense, that function will exit but this - // test will continue. The session will be left open and in an unknown state. - // Best just to abort in that case. - ASSERT_FALSE(s.isOpen()) << "LoadOfflineLicense() failed. Aborting."; + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); // Offline license with new mac keys should fail. Session s2; - s2.open(); - s2.GenerateTestSessionKeys(); - s2.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, - s2.get_nonce(), pst); - s2.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s2.FillSimpleMessage( + 0, wvoec_mock::kControlNonceOrEntry, s2.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s2.EncryptAndSign()); uint8_t* pst_ptr = s2.encrypted_license().pst; - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s2.message_ptr(), - sizeof(MessageData), &s2.signature()[0], - s2.signature().size(), - s2.encrypted_license().mac_key_iv, - s2.encrypted_license().mac_keys, kNumKeys, - s2.key_array(), pst_ptr, pst.length())); - s2.close(); + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s2.session_id(), s2.message_ptr(), sizeof(MessageData), + &s2.signature()[0], s2.signature().size(), + s2.encrypted_license().mac_key_iv, + s2.encrypted_license().mac_keys, kNumKeys, + s2.key_array(), pst_ptr, pst.length())); + ASSERT_NO_FATAL_FAILURE(s2.close()); // Offline license with same mac keys should still be OK. - s.open(); - s.GenerateTestSessionKeys(); - s.LoadTestKeys(pst, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); EXPECT_EQ(kUnused, s.pst_report()->status); } @@ -5283,76 +4531,75 @@ TEST_P(UsageTableTestWithMAC, OfflineBadNonce) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, 42, pst); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, 42, pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); uint8_t* pst_ptr = s.encrypted_license().pst; OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, s.key_array(), - pst_ptr, pst.length()); + s.encrypted_license().mac_keys, kNumKeys, s.key_array(), pst_ptr, + pst.length()); ASSERT_NE(OEMCrypto_SUCCESS, sts); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } // An offline license needs a valid pst. TEST_P(UsageTableTestWithMAC, OfflineEmptyPST) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce()); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, s.key_array(), - NULL, 0); + s.encrypted_license().mac_keys, kNumKeys, s.key_array(), NULL, 0); ASSERT_NE(OEMCrypto_SUCCESS, sts); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } TEST_P(UsageTableTestWithMAC, DeactivateOfflineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - LoadOfflineLicense(s, pst); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - // If there are errors in LoadOfflineLicense, that function will exit but this - // test will continue. The session will be left open and in an unknown state. - // Best just to abort in that case. - ASSERT_FALSE(s.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - - s.open(); - s.GenerateTestSessionKeys(); - s.LoadTestKeys(pst, new_mac_keys_); // Reload the license - s.TestDecryptCTR(); // Should be able to decrypt. - DeactivatePST(pst); // Then deactivate. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.LoadTestKeys(pst, new_mac_keys_)); // Reload the license + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); // Should be able to decrypt. + DeactivatePST(pst); // Then deactivate. // After deactivate, should not be able to decrypt. - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); s.GenerateReport(pst); EXPECT_EQ(kInactive, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.close(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.close()); Session s2; - s2.open(); - s2.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); // Offile license can not be reused if it has been deactivated. uint8_t* pst_ptr = s.encrypted_license().pst; - EXPECT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), - sizeof(MessageData), &s.signature()[0], - s.signature().size(), - s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, - s.key_array(), pst_ptr, pst.length())); + EXPECT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), pst_ptr, pst.length())); // But we can still generate a report. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, true, &s); EXPECT_EQ(kInactive, s3.pst_report()->status); } @@ -5361,10 +4608,11 @@ TEST_P(UsageTableTestWithMAC, BadRange) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry,s.get_nonce(), pst); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); uint8_t* pst_ptr = s.license().pst; // Bad: not in encrypted_license. ASSERT_NE( OEMCrypto_SUCCESS, @@ -5383,62 +4631,58 @@ TEST_F(UsageTableTest, TimingTest) { Session s1; Session s2; Session s3; - LoadOfflineLicense(s1, pst1); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s1, pst1)); time_t loaded1 = time(NULL); - LoadOfflineLicense(s2, pst2); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s2, pst2)); time_t loaded2 = time(NULL); - LoadOfflineLicense(s3, pst3); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s3, pst3)); time_t loaded3 = time(NULL); - // If there are errors in LoadOfflineLicense, that function will exit but this - // test will continue. The sessions will be left open and in an unknown state. - // Best just to abort in that case. - ASSERT_FALSE(s1.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - ASSERT_FALSE(s2.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - ASSERT_FALSE(s3.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - sleep(kLongSleep); - s1.open(); - s1.GenerateTestSessionKeys(); - s1.LoadTestKeys(pst1, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s1.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst1, new_mac_keys_)); time_t first_decrypt1 = time(NULL); - s1.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); - s2.open(); - s2.GenerateTestSessionKeys(); - s2.LoadTestKeys(pst2, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s2.LoadTestKeys(pst2, new_mac_keys_)); time_t first_decrypt2 = time(NULL); - s2.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); sleep(kLongSleep); time_t second_decrypt = time(NULL); - s1.TestDecryptCTR(); - s2.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); sleep(kLongSleep); DeactivatePST(pst1); - s1.close(); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(s2.close()); sleep(kLongSleep); // This is as close to reboot as we can simulate in code. + ASSERT_NO_FATAL_FAILURE(session_.close()); // Close sessions before terminate. OEMCrypto_Terminate(); sleep(kShortSleep); OEMCrypto_Initialize(); EnsureTestKeys(); + // Test teardown expects session_ to be open. + ASSERT_NO_FATAL_FAILURE(session_.open()); // After a reboot, we should be able to reload keys, and generate reports. sleep(kLongSleep); time_t third_decrypt = time(NULL); - s2.open(); - s2.GenerateTestSessionKeys(); - s2.LoadTestKeys(pst2, new_mac_keys_); - s2.TestDecryptCTR(); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s2.LoadTestKeys(pst2, new_mac_keys_)); + ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s2.close()); - s1.open(); - s2.open(); - s3.open(); + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s3.open()); sleep(kLongSleep); time_t report_generated1 = time(NULL); s1.GenerateReport(pst1); @@ -5448,29 +4692,31 @@ TEST_F(UsageTableTest, TimingTest) { s3.GenerateReport(pst3); EXPECT_EQ(kInactive, s1.pst_report()->status); - EXPECT_ALMOST( - report_generated1 - loaded1, - wvcdm::htonll64(s1.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST( - report_generated1 - first_decrypt1, - wvcdm::htonll64(s1.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(report_generated1 - second_decrypt, - wvcdm::htonll64(s1.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR(report_generated1 - loaded1, + wvcdm::htonll64(s1.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR(report_generated1 - first_decrypt1, + wvcdm::htonll64(s1.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR(report_generated1 - second_decrypt, + wvcdm::htonll64(s1.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); EXPECT_EQ(kActive, s2.pst_report()->status); - EXPECT_ALMOST( - report_generated2 - loaded2, - wvcdm::htonll64(s2.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST( - report_generated2 - first_decrypt2, - wvcdm::htonll64(s2.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(report_generated2 - third_decrypt, - wvcdm::htonll64(s2.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR(report_generated2 - loaded2, + wvcdm::htonll64(s2.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR(report_generated2 - first_decrypt2, + wvcdm::htonll64(s2.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR(report_generated2 - third_decrypt, + wvcdm::htonll64(s2.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); EXPECT_EQ(kUnused, s3.pst_report()->status); - EXPECT_ALMOST( - report_generated3 - loaded3, - wvcdm::htonll64(s3.pst_report()->seconds_since_license_received)); + EXPECT_NEAR(report_generated3 - loaded3, + wvcdm::htonll64(s3.pst_report()->seconds_since_license_received), + kTimeTolerance); // We don't expect first or last decrypt for unused report. } @@ -5478,13 +4724,13 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); const int kLicenseReceivedTimeTolerance = kSpeedMultiplier; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); @@ -5499,11 +4745,12 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { const time_t kUsageTableTimeTolerance = 10; cout << "This test verifies the elapsed time reported in the usage table " - "for a 2 minute simulated playback." << endl; - cout << "The total time for this test is about " << - kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl; - cout << "Wait " << kIdleInSeconds << - " seconds to verify usage table time before playback." << endl; + "for a 2 minute simulated playback." + << endl; + cout << "The total time for this test is about " + << kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl; + cout << "Wait " << kIdleInSeconds + << " seconds to verify usage table time before playback." << endl; PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); @@ -5518,7 +4765,7 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { time_t playback_time = 0; time_t start_time = time(NULL); do { - s.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); EXPECT_EQ(kActive, s.pst_report()->status); playback_time = time(NULL) - start_time; @@ -5537,15 +4784,16 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { playback_time + kIdleInSeconds, kLicenseReceivedTimeTolerance); EXPECT_NEAR(wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt), playback_time, kUsageTableTimeTolerance); - EXPECT_NEAR(wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), - 0, kUsageTableTimeTolerance); - EXPECT_NEAR( - wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt) - - wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), - playback_time, kUsageTableTimeTolerance); + EXPECT_NEAR(wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), 0, + kUsageTableTimeTolerance); + EXPECT_NEAR(wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt) - + wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), + playback_time, kUsageTableTimeTolerance); - cout << "Wait another " << kIdleInSeconds << " seconds " - "to verify usage table time since playback ended." << endl; + cout << "Wait another " << kIdleInSeconds + << " seconds " + "to verify usage table time since playback ended." + << endl; PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); // At this point, this is what we expect: @@ -5567,10 +4815,42 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { DeactivatePST(pst); s.GenerateReport(pst); EXPECT_EQ(kInactive, s.pst_report()->status); - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// This is a special case where a collection of licenses can be shared with +// multiple devices. In order for this to work, a single session must first +// load a device specific license, and then a shared content license. +TEST_F(UsageTableTest, LoadSharedLicense) { + // session_.generatersasignature. + // session_.GenerateNonce + // DeriveKeysFromSessionKey - (specify enc/mac keys. + // LoadKeys replay control = 2. loads new mac keys. + // LoadKeys replay control = 0. uses same mac key. + // check second loadkeys without first fails. + std::string pst = "my_pst"; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); + Session s; + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); + + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, true)); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + // The second set of keys are not loaded. + for (unsigned int i = 0; i < kNumKeys; i++) { + memset(s.license().keys[i].key_id, 'A' + i, + s.license().keys[i].key_id_length); + } + // TODO(fredgc,jfore): Decide if first set of keys need to stay loaded, or if + // they are replaced. + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.close()); } INSTANTIATE_TEST_CASE_P(TestUsageTables, UsageTableTestWithMAC, Values(true, false)); // With and without new_mac_keys. - } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test_android.cpp b/oemcrypto/test/oemcrypto_test_android.cpp index 1002939b..d08f3eb6 100644 --- a/oemcrypto/test/oemcrypto_test_android.cpp +++ b/oemcrypto/test/oemcrypto_test_android.cpp @@ -88,7 +88,7 @@ TEST_F(OEMCryptoAndroidLMPTest, Level1Required) { } // These tests are required for M Android devices. -class OEMCryptoAndroidMNCTest : public OEMCryptoAndroidLMPTest {}; +class OEMCryptoAndroidMNCTest : public OEMCryptoAndroidLMPTest {}; TEST_F(OEMCryptoAndroidMNCTest, MinVersionNumber10) { uint32_t version = OEMCrypto_APIVersion(); @@ -111,4 +111,12 @@ TEST_F(OEMCryptoAndroidMNCTest, QueryKeyControlImplemented) { OEMCrypto_QueryKeyControl(0, NULL, 0, NULL, NULL)); } +// These tests are required for N Android devices. +class OEMCryptoAndroidNYCTest : public OEMCryptoAndroidMNCTest {}; + +TEST_F(OEMCryptoAndroidNYCTest, MinVersionNumber11) { + uint32_t version = OEMCrypto_APIVersion(); + ASSERT_GE(version, 11u); +} + } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test_main.cpp b/oemcrypto/test/oemcrypto_test_main.cpp index a6d14959..e989bd90 100644 --- a/oemcrypto/test/oemcrypto_test_main.cpp +++ b/oemcrypto/test/oemcrypto_test_main.cpp @@ -2,7 +2,7 @@ #include #include "log.h" -#include "oemcrypto_test.h" +#include "oec_device_features.h" #include "OEMCryptoCENC.h" #include "properties.h" diff --git a/third_party/gyp/MSVSNew.py b/third_party/gyp/MSVSNew.py index 845dcb06..593f0e5b 100644 --- a/third_party/gyp/MSVSNew.py +++ b/third_party/gyp/MSVSNew.py @@ -172,7 +172,7 @@ class MSVSProject(MSVSSolutionEntry): #------------------------------------------------------------------------------ -class MSVSSolution: +class MSVSSolution(object): """Visual Studio solution.""" def __init__(self, path, version, entries=None, variants=None, diff --git a/third_party/gyp/MSVSSettings.py b/third_party/gyp/MSVSSettings.py index 773b74e9..8ae19180 100644 --- a/third_party/gyp/MSVSSettings.py +++ b/third_party/gyp/MSVSSettings.py @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -"""Code to validate and convert settings of the Microsoft build tools. +r"""Code to validate and convert settings of the Microsoft build tools. This file contains code to validate and convert settings of the Microsoft build tools. The function ConvertToMSBuildSettings(), ValidateMSVSSettings(), @@ -314,7 +314,14 @@ def _MSBuildOnly(tool, name, setting_type): name: the name of the setting. setting_type: the type of this setting. """ + + def _Translate(value, msbuild_settings): + # Let msbuild-only properties get translated as-is from msvs_settings. + tool_settings = msbuild_settings.setdefault(tool.msbuild_name, {}) + tool_settings[name] = value + _msbuild_validators[tool.msbuild_name][name] = setting_type.ValidateMSBuild + _msvs_to_msbuild_converters[tool.msvs_name][name] = _Translate def _ConvertedToAdditionalOption(tool, msvs_name, flag): @@ -367,6 +374,35 @@ fix_vc_macro_slashes_regex = re.compile( r'(\$\((?:%s)\))(?:[\\/]+)' % "|".join(fix_vc_macro_slashes_regex_list) ) +# Regular expression to detect keys that were generated by exclusion lists +_EXCLUDED_SUFFIX_RE = re.compile('^(.*)_excluded$') + + +def _ValidateExclusionSetting(setting, settings, error_msg, stderr=sys.stderr): + """Verify that 'setting' is valid if it is generated from an exclusion list. + + If the setting appears to be generated from an exclusion list, the root name + is checked. + + Args: + setting: A string that is the setting name to validate + settings: A dictionary where the keys are valid settings + error_msg: The message to emit in the event of error + stderr: The stream receiving the error messages. + """ + # This may be unrecognized because it's an exclusion list. If the + # setting name has the _excluded suffix, then check the root name. + unrecognized = True + m = re.match(_EXCLUDED_SUFFIX_RE, setting) + if m: + root_setting = m.group(1) + unrecognized = root_setting not in settings + + if unrecognized: + # We don't know this setting. Give a warning. + print >> stderr, error_msg + + def FixVCMacroSlashes(s): """Replace macros which have excessive following slashes. @@ -388,11 +424,11 @@ def ConvertVCMacrosToMSBuild(s): if '$' in s: replace_map = { '$(ConfigurationName)': '$(Configuration)', - '$(InputDir)': '%(RootDir)%(Directory)', + '$(InputDir)': '%(RelativeDir)', '$(InputExt)': '%(Extension)', '$(InputFileName)': '%(Filename)%(Extension)', '$(InputName)': '%(Filename)', - '$(InputPath)': '%(FullPath)', + '$(InputPath)': '%(Identity)', '$(ParentName)': '$(ProjectFileName)', '$(PlatformName)': '$(Platform)', '$(SafeInputName)': '%(Filename)', @@ -403,9 +439,6 @@ def ConvertVCMacrosToMSBuild(s): return s -_EXCLUDED_SUFFIX_RE = re.compile('^(.*)_excluded$') - - def ConvertToMSBuildSettings(msvs_settings, stderr=sys.stderr): """Converts MSVS settings (VS2008 and earlier) to MSBuild settings (VS2010+). @@ -432,19 +465,12 @@ def ConvertToMSBuildSettings(msvs_settings, stderr=sys.stderr): print >> stderr, ('Warning: while converting %s/%s to MSBuild, ' '%s' % (msvs_tool_name, msvs_setting, e)) else: - # This may be unrecognized because it's an exclusion list. If the - # setting name has the _excluded suffix, then check the root name. - unrecognized = True - m = re.match(_EXCLUDED_SUFFIX_RE, msvs_setting) - if m: - root_msvs_setting = m.group(1) - unrecognized = root_msvs_setting not in msvs_tool - - if unrecognized: - # We don't know this setting. Give a warning. - print >> stderr, ('Warning: unrecognized setting %s/%s ' - 'while converting to MSBuild.' % - (msvs_tool_name, msvs_setting)) + _ValidateExclusionSetting(msvs_setting, + msvs_tool, + ('Warning: unrecognized setting %s/%s ' + 'while converting to MSBuild.' % + (msvs_tool_name, msvs_setting)), + stderr) else: print >> stderr, ('Warning: unrecognized tool %s while converting to ' 'MSBuild.' % msvs_tool_name) @@ -495,8 +521,12 @@ def _ValidateSettings(validators, settings, stderr): print >> stderr, ('Warning: for %s/%s, %s' % (tool_name, setting, e)) else: - print >> stderr, ('Warning: unrecognized setting %s/%s' % - (tool_name, setting)) + _ValidateExclusionSetting(setting, + tool_validators, + ('Warning: unrecognized setting %s/%s' % + (tool_name, setting)), + stderr) + else: print >> stderr, ('Warning: unrecognized tool %s' % tool_name) @@ -508,6 +538,7 @@ _midl = _Tool('VCMIDLTool', 'Midl') _rc = _Tool('VCResourceCompilerTool', 'ResourceCompile') _lib = _Tool('VCLibrarianTool', 'Lib') _manifest = _Tool('VCManifestTool', 'Manifest') +_masm = _Tool('MASM', 'MASM') _AddTool(_compile) @@ -516,6 +547,7 @@ _AddTool(_midl) _AddTool(_rc) _AddTool(_lib) _AddTool(_manifest) +_AddTool(_masm) # Add sections only found in the MSBuild settings. _msbuild_validators[''] = {} _msbuild_validators['ProjectReference'] = {} @@ -560,6 +592,7 @@ _Same(_compile, 'UndefinePreprocessorDefinitions', _string_list) # /U _Same(_compile, 'UseFullPaths', _boolean) # /FC _Same(_compile, 'WholeProgramOptimization', _boolean) # /GL _Same(_compile, 'XMLDocumentationFileName', _file_name) +_Same(_compile, 'CompileAsWinRT', _boolean) # /ZW _Same(_compile, 'AssemblerOutput', _Enumeration(['NoListing', @@ -579,7 +612,8 @@ _Same(_compile, 'BrowseInformation', _Same(_compile, 'CallingConvention', _Enumeration(['Cdecl', # /Gd 'FastCall', # /Gr - 'StdCall'])) # /Gz + 'StdCall', # /Gz + 'VectorCall'])) # /Gv _Same(_compile, 'CompileAs', _Enumeration(['Default', 'CompileAsC', # /TC @@ -593,7 +627,12 @@ _Same(_compile, 'DebugInformationFormat', _Same(_compile, 'EnableEnhancedInstructionSet', _Enumeration(['NotSet', 'StreamingSIMDExtensions', # /arch:SSE - 'StreamingSIMDExtensions2'])) # /arch:SSE2 + 'StreamingSIMDExtensions2', # /arch:SSE2 + 'AdvancedVectorExtensions', # /arch:AVX (vs2012+) + 'NoExtensions', # /arch:IA32 (vs2012+) + # This one only exists in the new msbuild format. + 'AdvancedVectorExtensions2', # /arch:AVX2 (vs2013r2+) + ])) _Same(_compile, 'ErrorReporting', _Enumeration(['None', # /errorReport:none 'Prompt', # /errorReport:prompt @@ -670,10 +709,7 @@ _MSVSOnly(_compile, 'UseUnicodeResponseFiles', _boolean) _MSBuildOnly(_compile, 'BuildingInIDE', _boolean) _MSBuildOnly(_compile, 'CompileAsManaged', _Enumeration([], new=['false', - 'true', # /clr - 'Pure', # /clr:pure - 'Safe', # /clr:safe - 'OldSyntax'])) # /clr:oldSyntax + 'true'])) # /clr _MSBuildOnly(_compile, 'CreateHotpatchableImage', _boolean) # /hotpatch _MSBuildOnly(_compile, 'MultiProcessorCompilation', _boolean) # /MP _MSBuildOnly(_compile, 'PreprocessOutputPath', _string) # /Fi @@ -848,13 +884,6 @@ _Moved(_link, 'UseLibraryDependencyInputs', 'ProjectReference', _boolean) # MSVS options not found in MSBuild. _MSVSOnly(_link, 'OptimizeForWindows98', _newly_boolean) _MSVSOnly(_link, 'UseUnicodeResponseFiles', _boolean) -# These settings generate correctly in the MSVS output files when using -# e.g. DelayLoadDLLs! or AdditionalDependencies! to exclude files from -# configuration entries, but result in spurious artifacts which can be -# safely ignored here. See crbug.com/246570 -_MSVSOnly(_link, 'AdditionalLibraryDirectories_excluded', _folder_list) -_MSVSOnly(_link, 'DelayLoadDLLs_excluded', _file_list) -_MSVSOnly(_link, 'AdditionalDependencies_excluded', _file_list) # MSBuild options not found in MSVS. _MSBuildOnly(_link, 'BuildingInIDE', _boolean) @@ -1003,9 +1032,6 @@ _Same(_lib, 'TargetMachine', _target_machine_enumeration) # ProjectReference. We may want to validate that they are consistent. _Moved(_lib, 'LinkLibraryDependencies', 'ProjectReference', _boolean) -# TODO(jeanluc) I don't think these are genuine settings but byproducts of Gyp. -_MSVSOnly(_lib, 'AdditionalLibraryDirectories_excluded', _folder_list) - _MSBuildOnly(_lib, 'DisplayLibrary', _string) # /LIST Visible='false' _MSBuildOnly(_lib, 'ErrorReporting', _Enumeration([], new=['PromptImmediately', # /ERRORREPORT:PROMPT @@ -1061,3 +1087,11 @@ _MSBuildOnly(_manifest, 'ManifestFromManagedAssembly', _MSBuildOnly(_manifest, 'OutputResourceManifests', _string) # /outputresource _MSBuildOnly(_manifest, 'SuppressDependencyElement', _boolean) # /nodependency _MSBuildOnly(_manifest, 'TrackerLogDirectory', _folder_name) + + +# Directives for MASM. +# See "$(VCTargetsPath)\BuildCustomizations\masm.xml" for the schema of the +# MSBuild MASM settings. + +# Options that have the same name in MSVS and MSBuild. +_Same(_masm, 'UseSafeExceptionHandlers', _boolean) # /safeseh diff --git a/third_party/gyp/MSVSSettings_test.py b/third_party/gyp/MSVSSettings_test.py index 9bd37ec3..bf6ea6b8 100755 --- a/third_party/gyp/MSVSSettings_test.py +++ b/third_party/gyp/MSVSSettings_test.py @@ -267,7 +267,7 @@ class TestSequenceFunctions(unittest.TestCase): 'Warning: for VCCLCompilerTool/BrowseInformation, ' "invalid literal for int() with base 10: 'fdkslj'", 'Warning: for VCCLCompilerTool/CallingConvention, ' - 'index value (-1) not in expected range [0, 3)', + 'index value (-1) not in expected range [0, 4)', 'Warning: for VCCLCompilerTool/DebugInformationFormat, ' 'converted value for 2 not specified.', 'Warning: unrecognized setting VCCLCompilerTool/Enableprefast', @@ -296,7 +296,7 @@ class TestSequenceFunctions(unittest.TestCase): 'BuildingInIDE': 'true', 'CallingConvention': 'Cdecl', 'CompileAs': 'CompileAsC', - 'CompileAsManaged': 'Pure', + 'CompileAsManaged': 'true', 'CreateHotpatchableImage': 'true', 'DebugInformationFormat': 'ProgramDatabase', 'DisableLanguageExtensions': 'true', diff --git a/third_party/gyp/MSVSUtil.py b/third_party/gyp/MSVSUtil.py index 62e8d260..f5e0c1d8 100644 --- a/third_party/gyp/MSVSUtil.py +++ b/third_party/gyp/MSVSUtil.py @@ -8,10 +8,12 @@ import copy import os -_TARGET_TYPE_EXT = { - 'executable': '.exe', - 'loadable_module': '.dll', - 'shared_library': '.dll', +# A dictionary mapping supported target types to extensions. +TARGET_TYPE_EXT = { + 'executable': 'exe', + 'loadable_module': 'dll', + 'shared_library': 'dll', + 'static_library': 'lib', } @@ -108,16 +110,17 @@ def ShardTargets(target_list, target_dicts): else: new_target_dicts[t] = target_dicts[t] # Shard dependencies. - for t in new_target_dicts: - dependencies = copy.copy(new_target_dicts[t].get('dependencies', [])) - new_dependencies = [] - for d in dependencies: - if d in targets_to_shard: - for i in range(targets_to_shard[d]): - new_dependencies.append(_ShardName(d, i)) - else: - new_dependencies.append(d) - new_target_dicts[t]['dependencies'] = new_dependencies + for t in sorted(new_target_dicts): + for deptype in ('dependencies', 'dependencies_original'): + dependencies = copy.copy(new_target_dicts[t].get(deptype, [])) + new_dependencies = [] + for d in dependencies: + if d in targets_to_shard: + for i in range(targets_to_shard[d]): + new_dependencies.append(_ShardName(d, i)) + else: + new_dependencies.append(d) + new_target_dicts[t][deptype] = new_dependencies return (new_target_list, new_target_dicts) @@ -156,7 +159,7 @@ def _GetPdbPath(target_dict, config_name, vars): pdb_base = target_dict.get('product_name', target_dict['target_name']) - pdb_base = '%s%s.pdb' % (pdb_base, _TARGET_TYPE_EXT[target_dict['type']]) + pdb_base = '%s.%s.pdb' % (pdb_base, TARGET_TYPE_EXT[target_dict['type']]) pdb_path = vars['PRODUCT_DIR'] + '/' + pdb_base return pdb_path @@ -264,4 +267,4 @@ def InsertLargePdbShims(target_list, target_dicts, vars): # Update the original target to depend on the shim target. target_dict.setdefault('dependencies', []).append(full_shim_target_name) - return (target_list, target_dicts) \ No newline at end of file + return (target_list, target_dicts) diff --git a/third_party/gyp/MSVSVersion.py b/third_party/gyp/MSVSVersion.py index 03b6d8ad..edaf6eed 100644 --- a/third_party/gyp/MSVSVersion.py +++ b/third_party/gyp/MSVSVersion.py @@ -68,26 +68,29 @@ class VisualStudioVersion(object): of a user override.""" return self.default_toolset - def SetupScript(self, target_arch): + def _SetupScriptInternal(self, target_arch): """Returns a command (with arguments) to be used to set up the environment.""" - # Check if we are running in the SDK command line environment and use - # the setup script from the SDK if so. |target_arch| should be either - # 'x86' or 'x64'. + # If WindowsSDKDir is set and SetEnv.Cmd exists then we are using the + # depot_tools build tools and should run SetEnv.Cmd to set up the + # environment. The check for WindowsSDKDir alone is not sufficient because + # this is set by running vcvarsall.bat. assert target_arch in ('x86', 'x64') sdk_dir = os.environ.get('WindowsSDKDir') - if self.sdk_based and sdk_dir: - return [os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.Cmd')), - '/' + target_arch] + if sdk_dir: + setup_path = os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.Cmd')) + if self.sdk_based and sdk_dir and os.path.exists(setup_path): + return [setup_path, '/' + target_arch] else: # We don't use VC/vcvarsall.bat for x86 because vcvarsall calls # vcvars32, which it can only find if VS??COMNTOOLS is set, which it # isn't always. if target_arch == 'x86': - if self.short_name == '2013' and ( + if self.short_name >= '2013' and self.short_name[-1] != 'e' and ( os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64'): - # VS2013 non-Express has a x64-x86 cross that we want to prefer. + # VS2013 and later, non-Express have a x64-x86 cross that we want + # to prefer. return [os.path.normpath( os.path.join(self.path, 'VC/vcvarsall.bat')), 'amd64_x86'] # Otherwise, the standard x86 compiler. @@ -105,6 +108,14 @@ class VisualStudioVersion(object): return [os.path.normpath( os.path.join(self.path, 'VC/vcvarsall.bat')), arg] + def SetupScript(self, target_arch): + script_data = self._SetupScriptInternal(target_arch) + script_path = script_data[0] + if not os.path.exists(script_path): + raise Exception('%s is missing - make sure VC++ tools are installed.' % + script_path) + return script_data + def _RegistryQueryBase(sysdir, key, value): """Use reg.exe to read a particular key. @@ -138,7 +149,7 @@ def _RegistryQueryBase(sysdir, key, value): def _RegistryQuery(key, value=None): - """Use reg.exe to read a particular key through _RegistryQueryBase. + r"""Use reg.exe to read a particular key through _RegistryQueryBase. First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If that fails, it falls back to System32. Sysnative is available on Vista and @@ -165,8 +176,33 @@ def _RegistryQuery(key, value=None): return text +def _RegistryGetValueUsingWinReg(key, value): + """Use the _winreg module to obtain the value of a registry key. + + Args: + key: The registry key. + value: The particular registry value to read. + Return: + contents of the registry key's value, or None on failure. Throws + ImportError if _winreg is unavailable. + """ + import _winreg + try: + root, subkey = key.split('\\', 1) + assert root == 'HKLM' # Only need HKLM for now. + with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey: + return _winreg.QueryValueEx(hkey, value)[0] + except WindowsError: + return None + + def _RegistryGetValue(key, value): - """Use reg.exe to obtain the value of a registry key. + """Use _winreg or reg.exe to obtain the value of a registry key. + + Using _winreg is preferable because it solves an issue on some corporate + environments where access to reg.exe is locked down. However, we still need + to fallback to reg.exe for the case where the _winreg module is not available + (for example in cygwin python). Args: key: The registry key. @@ -174,6 +210,12 @@ def _RegistryGetValue(key, value): Return: contents of the registry key's value, or None on failure. """ + try: + return _RegistryGetValueUsingWinReg(key, value) + except ImportError: + pass + + # Fallback to reg.exe if we fail to import _winreg. text = _RegistryQuery(key, value) if not text: return None @@ -184,19 +226,6 @@ def _RegistryGetValue(key, value): return match.group(1) -def _RegistryKeyExists(key): - """Use reg.exe to see if a key exists. - - Args: - key: The registry key to check. - Return: - True if the key exists - """ - if not _RegistryQuery(key): - return False - return True - - def _CreateVersion(name, path, sdk_based=False): """Sets up MSVS project generation. @@ -207,6 +236,15 @@ def _CreateVersion(name, path, sdk_based=False): if path: path = os.path.normpath(path) versions = { + '2015': VisualStudioVersion('2015', + 'Visual Studio 2015', + solution_version='12.00', + project_version='14.0', + flat_sln=False, + uses_vcxproj=True, + path=path, + sdk_based=sdk_based, + default_toolset='v140'), '2013': VisualStudioVersion('2013', 'Visual Studio 2013', solution_version='13.00', @@ -316,7 +354,8 @@ def _DetectVisualStudioVersions(versions_to_check, force_express): 2008(e) - Visual Studio 2008 (9) 2010(e) - Visual Studio 2010 (10) 2012(e) - Visual Studio 2012 (11) - 2013(e) - Visual Studio 2013 (11) + 2013(e) - Visual Studio 2013 (12) + 2015 - Visual Studio 2015 (14) Where (e) is e for express editions of MSVS and blank otherwise. """ version_to_year = { @@ -325,6 +364,7 @@ def _DetectVisualStudioVersions(versions_to_check, force_express): '10.0': '2010', '11.0': '2012', '12.0': '2013', + '14.0': '2015', } versions = [] for version in versions_to_check: @@ -361,13 +401,14 @@ def _DetectVisualStudioVersions(versions_to_check, force_express): if not path: continue path = _ConvertToCygpath(path) - versions.append(_CreateVersion(version_to_year[version] + 'e', - os.path.join(path, '..'), sdk_based=True)) + if version != '14.0': # There is no Express edition for 2015. + versions.append(_CreateVersion(version_to_year[version] + 'e', + os.path.join(path, '..'), sdk_based=True)) return versions -def SelectVisualStudioVersion(version='auto'): +def SelectVisualStudioVersion(version='auto', allow_fallback=True): """Select which version of Visual Studio projects to generate. Arguments: @@ -379,7 +420,7 @@ def SelectVisualStudioVersion(version='auto'): if version == 'auto': version = os.environ.get('GYP_MSVS_VERSION', 'auto') version_map = { - 'auto': ('10.0', '12.0', '9.0', '8.0', '11.0'), + 'auto': ('14.0', '12.0', '10.0', '9.0', '8.0', '11.0'), '2005': ('8.0',), '2005e': ('8.0',), '2008': ('9.0',), @@ -390,6 +431,7 @@ def SelectVisualStudioVersion(version='auto'): '2012e': ('11.0',), '2013': ('12.0',), '2013e': ('12.0',), + '2015': ('14.0',), } override_path = os.environ.get('GYP_MSVS_OVERRIDE_PATH') if override_path: @@ -401,6 +443,8 @@ def SelectVisualStudioVersion(version='auto'): version = str(version) versions = _DetectVisualStudioVersions(version_map[version], 'e' in version) if not versions: + if not allow_fallback: + raise ValueError('Could not locate Visual Studio installation.') if version == 'auto': # Default to 2005 if we couldn't find anything return _CreateVersion('2005', None) diff --git a/third_party/gyp/__init__.py b/third_party/gyp/__init__.py index 30edea56..668f38b6 100755 --- a/third_party/gyp/__init__.py +++ b/third_party/gyp/__init__.py @@ -49,7 +49,7 @@ def FindBuildFiles(): def Load(build_files, format, default_variables={}, includes=[], depth='.', params=None, check=False, - circular_check=True): + circular_check=True, duplicate_basename_check=True): """ Loads one or more specified build files. default_variables and includes will be copied before use. @@ -59,7 +59,6 @@ def Load(build_files, format, default_variables={}, if params is None: params = {} - flavor = None if '-' in format: format, params['flavor'] = format.split('-', 1) @@ -69,6 +68,7 @@ def Load(build_files, format, default_variables={}, # named WITH_CAPITAL_LETTERS to provide a distinct "best practice" namespace, # avoiding collisions with user and automatic variables. default_variables['GENERATOR'] = format + default_variables['GENERATOR_FLAVOR'] = params.get('flavor', '') # Format can be a custom python file, or by default the name of a module # within gyp.generator. @@ -126,6 +126,7 @@ def Load(build_files, format, default_variables={}, # Process the input specific to this generator. result = gyp.input.Load(build_files, default_variables, includes[:], depth, generator_input_info, check, circular_check, + duplicate_basename_check, params['parallel'], params['root_targets']) return [generator] + result @@ -324,6 +325,16 @@ def gyp_main(args): parser.add_option('--no-circular-check', dest='circular_check', action='store_false', default=True, regenerate=False, help="don't check for circular relationships between files") + # --no-duplicate-basename-check disables the check for duplicate basenames + # in a static_library/shared_library project. Visual C++ 2008 generator + # doesn't support this configuration. Libtool on Mac also generates warnings + # when duplicate basenames are passed into Make generator on Mac. + # TODO(yukawa): Remove this option when these legacy generators are + # deprecated. + parser.add_option('--no-duplicate-basename-check', + dest='duplicate_basename_check', action='store_false', + default=True, regenerate=False, + help="don't check for duplicate basenames") parser.add_option('--no-parallel', action='store_true', default=False, help='Disable multiprocessing') parser.add_option('-S', '--suffix', dest='suffix', default='', @@ -371,7 +382,7 @@ def gyp_main(args): if options.use_environment: generate_formats = os.environ.get('GYP_GENERATORS', []) if generate_formats: - generate_formats = re.split('[\s,]', generate_formats) + generate_formats = re.split(r'[\s,]', generate_formats) if generate_formats: options.formats = generate_formats else: @@ -493,14 +504,14 @@ def gyp_main(args): 'gyp_binary': sys.argv[0], 'home_dot_gyp': home_dot_gyp, 'parallel': options.parallel, - 'root_targets': options.root_targets} + 'root_targets': options.root_targets, + 'target_arch': cmdline_default_variables.get('target_arch', '')} # Start with the default variables from the command line. - [generator, flat_list, targets, data] = Load(build_files, format, - cmdline_default_variables, - includes, options.depth, - params, options.check, - options.circular_check) + [generator, flat_list, targets, data] = Load( + build_files, format, cmdline_default_variables, includes, options.depth, + params, options.check, options.circular_check, + options.duplicate_basename_check) # TODO(mark): Pass |data| for now because the generator needs a list of # build files that came in. In the future, maybe it should just accept diff --git a/third_party/gyp/common.py b/third_party/gyp/common.py index f9c6c6f3..a1e1db5f 100644 --- a/third_party/gyp/common.py +++ b/third_party/gyp/common.py @@ -4,6 +4,7 @@ from __future__ import with_statement +import collections import errno import filecmp import os.path @@ -130,13 +131,20 @@ def QualifiedTarget(build_file, target, toolset): @memoize -def RelativePath(path, relative_to): +def RelativePath(path, relative_to, follow_path_symlink=True): # Assuming both |path| and |relative_to| are relative to the current # directory, returns a relative path that identifies path relative to # relative_to. + # If |follow_symlink_path| is true (default) and |path| is a symlink, then + # this method returns a path to the real file represented by |path|. If it is + # false, this method returns a path to the symlink. If |path| is not a + # symlink, this option has no effect. # Convert to normalized (and therefore absolute paths). - path = os.path.realpath(path) + if follow_path_symlink: + path = os.path.realpath(path) + else: + path = os.path.abspath(path) relative_to = os.path.realpath(relative_to) # On Windows, we can't create a relative path to a different drive, so just @@ -328,7 +336,7 @@ def WriteOnDiff(filename): the target if it differs (on close). """ - class Writer: + class Writer(object): """Wrapper around file which only covers the target if it differs.""" def __init__(self): # Pick temporary file. @@ -417,13 +425,15 @@ def GetFlavor(params): return 'freebsd' if sys.platform.startswith('openbsd'): return 'openbsd' + if sys.platform.startswith('netbsd'): + return 'netbsd' if sys.platform.startswith('aix'): return 'aix' return 'linux' -def CopyTool(flavor, out_path): +def CopyTool(flavor, out_path, generator_flags={}): """Finds (flock|mac|win)_tool.gyp in the gyp directory and copies it to |out_path|.""" # aix and solaris just need flock emulation. mac and win use more complicated @@ -443,11 +453,18 @@ def CopyTool(flavor, out_path): with open(source_path) as source_file: source = source_file.readlines() + # Set custom header flags. + header = '# Generated by gyp. Do not edit.\n' + mac_toolchain_dir = generator_flags.get('mac_toolchain_dir', None) + if flavor == 'mac' and mac_toolchain_dir: + header += "import os;\nos.environ['DEVELOPER_DIR']='%s'\n" \ + % mac_toolchain_dir + # Add header and write it out. tool_path = os.path.join(out_path, 'gyp-%s-tool' % prefix) with open(tool_path, 'w') as tool_file: tool_file.write( - ''.join([source[0], '# Generated by gyp. Do not edit.\n'] + source[1:])) + ''.join([source[0], header] + source[1:])) # Make file executable. os.chmod(tool_path, 0755) @@ -472,6 +489,72 @@ def uniquer(seq, idfun=None): return result +# Based on http://code.activestate.com/recipes/576694/. +class OrderedSet(collections.MutableSet): + def __init__(self, iterable=None): + self.end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.map = {} # key --> [key, prev, next] + if iterable is not None: + self |= iterable + + def __len__(self): + return len(self.map) + + def __contains__(self, key): + return key in self.map + + def add(self, key): + if key not in self.map: + end = self.end + curr = end[1] + curr[2] = end[1] = self.map[key] = [key, curr, end] + + def discard(self, key): + if key in self.map: + key, prev_item, next_item = self.map.pop(key) + prev_item[2] = next_item + next_item[1] = prev_item + + def __iter__(self): + end = self.end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + # The second argument is an addition that causes a pylint warning. + def pop(self, last=True): # pylint: disable=W0221 + if not self: + raise KeyError('set is empty') + key = self.end[1][0] if last else self.end[2][0] + self.discard(key) + return key + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + if isinstance(other, OrderedSet): + return len(self) == len(other) and list(self) == list(other) + return set(self) == set(other) + + # Extensions to the recipe. + def update(self, iterable): + for i in iterable: + if i not in self: + self.add(i) + + class CycleError(Exception): """An exception raised when an unexpected cycle is detected.""" def __init__(self, nodes): @@ -481,7 +564,7 @@ class CycleError(Exception): def TopologicallySorted(graph, get_edges): - """Topologically sort based on a user provided edge definition. + r"""Topologically sort based on a user provided edge definition. Args: graph: A list of node names. @@ -519,3 +602,14 @@ def TopologicallySorted(graph, get_edges): for node in sorted(graph): Visit(node) return ordered_nodes + +def CrossCompileRequested(): + # TODO: figure out how to not build extra host objects in the + # non-cross-compile case when this is enabled, and enable unconditionally. + return (os.environ.get('GYP_CROSSCOMPILE') or + os.environ.get('AR_host') or + os.environ.get('CC_host') or + os.environ.get('CXX_host') or + os.environ.get('AR_target') or + os.environ.get('CC_target') or + os.environ.get('CXX_target')) diff --git a/third_party/gyp/flock_tool.py b/third_party/gyp/flock_tool.py index 3e7efff2..b38d8660 100755 --- a/third_party/gyp/flock_tool.py +++ b/third_party/gyp/flock_tool.py @@ -40,7 +40,12 @@ class FlockTool(object): # with EBADF, that's why we use this F_SETLK # hack instead. fd = os.open(lockfile, os.O_WRONLY|os.O_NOCTTY|os.O_CREAT, 0666) - op = struct.pack('hhllhhl', fcntl.F_WRLCK, 0, 0, 0, 0, 0, 0) + if sys.platform.startswith('aix'): + # Python on AIX is compiled with LARGEFILE support, which changes the + # struct size. + op = struct.pack('hhIllqq', fcntl.F_WRLCK, 0, 0, 0, 0, 0, 0) + else: + op = struct.pack('hhllhhl', fcntl.F_WRLCK, 0, 0, 0, 0, 0, 0) fcntl.fcntl(fd, fcntl.F_SETLK, op) return subprocess.call(cmd_list) diff --git a/third_party/gyp/generator/analyzer.py b/third_party/gyp/generator/analyzer.py new file mode 100644 index 00000000..921c1a6b --- /dev/null +++ b/third_party/gyp/generator/analyzer.py @@ -0,0 +1,741 @@ +# Copyright (c) 2014 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +This script is intended for use as a GYP_GENERATOR. It takes as input (by way of +the generator flag config_path) the path of a json file that dictates the files +and targets to search for. The following keys are supported: +files: list of paths (relative) of the files to search for. +test_targets: unqualified target names to search for. Any target in this list +that depends upon a file in |files| is output regardless of the type of target +or chain of dependencies. +additional_compile_targets: Unqualified targets to search for in addition to +test_targets. Targets in the combined list that depend upon a file in |files| +are not necessarily output. For example, if the target is of type none then the +target is not output (but one of the descendants of the target will be). + +The following is output: +error: only supplied if there is an error. +compile_targets: minimal set of targets that directly or indirectly (for + targets of type none) depend on the files in |files| and is one of the + supplied targets or a target that one of the supplied targets depends on. + The expectation is this set of targets is passed into a build step. This list + always contains the output of test_targets as well. +test_targets: set of targets from the supplied |test_targets| that either + directly or indirectly depend upon a file in |files|. This list if useful + if additional processing needs to be done for certain targets after the + build, such as running tests. +status: outputs one of three values: none of the supplied files were found, + one of the include files changed so that it should be assumed everything + changed (in this case test_targets and compile_targets are not output) or at + least one file was found. +invalid_targets: list of supplied targets that were not found. + +Example: +Consider a graph like the following: + A D + / \ +B C +A depends upon both B and C, A is of type none and B and C are executables. +D is an executable, has no dependencies and nothing depends on it. +If |additional_compile_targets| = ["A"], |test_targets| = ["B", "C"] and +files = ["b.cc", "d.cc"] (B depends upon b.cc and D depends upon d.cc), then +the following is output: +|compile_targets| = ["B"] B must built as it depends upon the changed file b.cc +and the supplied target A depends upon it. A is not output as a build_target +as it is of type none with no rules and actions. +|test_targets| = ["B"] B directly depends upon the change file b.cc. + +Even though the file d.cc, which D depends upon, has changed D is not output +as it was not supplied by way of |additional_compile_targets| or |test_targets|. + +If the generator flag analyzer_output_path is specified, output is written +there. Otherwise output is written to stdout. + +In Gyp the "all" target is shorthand for the root targets in the files passed +to gyp. For example, if file "a.gyp" contains targets "a1" and +"a2", and file "b.gyp" contains targets "b1" and "b2" and "a2" has a dependency +on "b2" and gyp is supplied "a.gyp" then "all" consists of "a1" and "a2". +Notice that "b1" and "b2" are not in the "all" target as "b.gyp" was not +directly supplied to gyp. OTOH if both "a.gyp" and "b.gyp" are supplied to gyp +then the "all" target includes "b1" and "b2". +""" + +import gyp.common +import gyp.ninja_syntax as ninja_syntax +import json +import os +import posixpath +import sys + +debug = False + +found_dependency_string = 'Found dependency' +no_dependency_string = 'No dependencies' +# Status when it should be assumed that everything has changed. +all_changed_string = 'Found dependency (all)' + +# MatchStatus is used indicate if and how a target depends upon the supplied +# sources. +# The target's sources contain one of the supplied paths. +MATCH_STATUS_MATCHES = 1 +# The target has a dependency on another target that contains one of the +# supplied paths. +MATCH_STATUS_MATCHES_BY_DEPENDENCY = 2 +# The target's sources weren't in the supplied paths and none of the target's +# dependencies depend upon a target that matched. +MATCH_STATUS_DOESNT_MATCH = 3 +# The target doesn't contain the source, but the dependent targets have not yet +# been visited to determine a more specific status yet. +MATCH_STATUS_TBD = 4 + +generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested() + +generator_wants_static_library_dependencies_adjusted = False + +generator_default_variables = { +} +for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR', + 'LIB_DIR', 'SHARED_LIB_DIR']: + generator_default_variables[dirname] = '!!!' + +for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', + 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT', + 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX', + 'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX', + 'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX', + 'CONFIGURATION_NAME']: + generator_default_variables[unused] = '' + + +def _ToGypPath(path): + """Converts a path to the format used by gyp.""" + if os.sep == '\\' and os.altsep == '/': + return path.replace('\\', '/') + return path + + +def _ResolveParent(path, base_path_components): + """Resolves |path|, which starts with at least one '../'. Returns an empty + string if the path shouldn't be considered. See _AddSources() for a + description of |base_path_components|.""" + depth = 0 + while path.startswith('../'): + depth += 1 + path = path[3:] + # Relative includes may go outside the source tree. For example, an action may + # have inputs in /usr/include, which are not in the source tree. + if depth > len(base_path_components): + return '' + if depth == len(base_path_components): + return path + return '/'.join(base_path_components[0:len(base_path_components) - depth]) + \ + '/' + path + + +def _AddSources(sources, base_path, base_path_components, result): + """Extracts valid sources from |sources| and adds them to |result|. Each + source file is relative to |base_path|, but may contain '..'. To make + resolving '..' easier |base_path_components| contains each of the + directories in |base_path|. Additionally each source may contain variables. + Such sources are ignored as it is assumed dependencies on them are expressed + and tracked in some other means.""" + # NOTE: gyp paths are always posix style. + for source in sources: + if not len(source) or source.startswith('!!!') or source.startswith('$'): + continue + # variable expansion may lead to //. + org_source = source + source = source[0] + source[1:].replace('//', '/') + if source.startswith('../'): + source = _ResolveParent(source, base_path_components) + if len(source): + result.append(source) + continue + result.append(base_path + source) + if debug: + print 'AddSource', org_source, result[len(result) - 1] + + +def _ExtractSourcesFromAction(action, base_path, base_path_components, + results): + if 'inputs' in action: + _AddSources(action['inputs'], base_path, base_path_components, results) + + +def _ToLocalPath(toplevel_dir, path): + """Converts |path| to a path relative to |toplevel_dir|.""" + if path == toplevel_dir: + return '' + if path.startswith(toplevel_dir + '/'): + return path[len(toplevel_dir) + len('/'):] + return path + + +def _ExtractSources(target, target_dict, toplevel_dir): + # |target| is either absolute or relative and in the format of the OS. Gyp + # source paths are always posix. Convert |target| to a posix path relative to + # |toplevel_dir_|. This is done to make it easy to build source paths. + base_path = posixpath.dirname(_ToLocalPath(toplevel_dir, _ToGypPath(target))) + base_path_components = base_path.split('/') + + # Add a trailing '/' so that _AddSources() can easily build paths. + if len(base_path): + base_path += '/' + + if debug: + print 'ExtractSources', target, base_path + + results = [] + if 'sources' in target_dict: + _AddSources(target_dict['sources'], base_path, base_path_components, + results) + # Include the inputs from any actions. Any changes to these affect the + # resulting output. + if 'actions' in target_dict: + for action in target_dict['actions']: + _ExtractSourcesFromAction(action, base_path, base_path_components, + results) + if 'rules' in target_dict: + for rule in target_dict['rules']: + _ExtractSourcesFromAction(rule, base_path, base_path_components, results) + + return results + + +class Target(object): + """Holds information about a particular target: + deps: set of Targets this Target depends upon. This is not recursive, only the + direct dependent Targets. + match_status: one of the MatchStatus values. + back_deps: set of Targets that have a dependency on this Target. + visited: used during iteration to indicate whether we've visited this target. + This is used for two iterations, once in building the set of Targets and + again in _GetBuildTargets(). + name: fully qualified name of the target. + requires_build: True if the target type is such that it needs to be built. + See _DoesTargetTypeRequireBuild for details. + added_to_compile_targets: used when determining if the target was added to the + set of targets that needs to be built. + in_roots: true if this target is a descendant of one of the root nodes. + is_executable: true if the type of target is executable. + is_static_library: true if the type of target is static_library. + is_or_has_linked_ancestor: true if the target does a link (eg executable), or + if there is a target in back_deps that does a link.""" + def __init__(self, name): + self.deps = set() + self.match_status = MATCH_STATUS_TBD + self.back_deps = set() + self.name = name + # TODO(sky): I don't like hanging this off Target. This state is specific + # to certain functions and should be isolated there. + self.visited = False + self.requires_build = False + self.added_to_compile_targets = False + self.in_roots = False + self.is_executable = False + self.is_static_library = False + self.is_or_has_linked_ancestor = False + + +class Config(object): + """Details what we're looking for + files: set of files to search for + targets: see file description for details.""" + def __init__(self): + self.files = [] + self.targets = set() + self.additional_compile_target_names = set() + self.test_target_names = set() + + def Init(self, params): + """Initializes Config. This is a separate method as it raises an exception + if there is a parse error.""" + generator_flags = params.get('generator_flags', {}) + config_path = generator_flags.get('config_path', None) + if not config_path: + return + try: + f = open(config_path, 'r') + config = json.load(f) + f.close() + except IOError: + raise Exception('Unable to open file ' + config_path) + except ValueError as e: + raise Exception('Unable to parse config file ' + config_path + str(e)) + if not isinstance(config, dict): + raise Exception('config_path must be a JSON file containing a dictionary') + self.files = config.get('files', []) + self.additional_compile_target_names = set( + config.get('additional_compile_targets', [])) + self.test_target_names = set(config.get('test_targets', [])) + + +def _WasBuildFileModified(build_file, data, files, toplevel_dir): + """Returns true if the build file |build_file| is either in |files| or + one of the files included by |build_file| is in |files|. |toplevel_dir| is + the root of the source tree.""" + if _ToLocalPath(toplevel_dir, _ToGypPath(build_file)) in files: + if debug: + print 'gyp file modified', build_file + return True + + # First element of included_files is the file itself. + if len(data[build_file]['included_files']) <= 1: + return False + + for include_file in data[build_file]['included_files'][1:]: + # |included_files| are relative to the directory of the |build_file|. + rel_include_file = \ + _ToGypPath(gyp.common.UnrelativePath(include_file, build_file)) + if _ToLocalPath(toplevel_dir, rel_include_file) in files: + if debug: + print 'included gyp file modified, gyp_file=', build_file, \ + 'included file=', rel_include_file + return True + return False + + +def _GetOrCreateTargetByName(targets, target_name): + """Creates or returns the Target at targets[target_name]. If there is no + Target for |target_name| one is created. Returns a tuple of whether a new + Target was created and the Target.""" + if target_name in targets: + return False, targets[target_name] + target = Target(target_name) + targets[target_name] = target + return True, target + + +def _DoesTargetTypeRequireBuild(target_dict): + """Returns true if the target type is such that it needs to be built.""" + # If a 'none' target has rules or actions we assume it requires a build. + return bool(target_dict['type'] != 'none' or + target_dict.get('actions') or target_dict.get('rules')) + + +def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, + build_files): + """Returns a tuple of the following: + . A dictionary mapping from fully qualified name to Target. + . A list of the targets that have a source file in |files|. + . Targets that constitute the 'all' target. See description at top of file + for details on the 'all' target. + This sets the |match_status| of the targets that contain any of the source + files in |files| to MATCH_STATUS_MATCHES. + |toplevel_dir| is the root of the source tree.""" + # Maps from target name to Target. + name_to_target = {} + + # Targets that matched. + matching_targets = [] + + # Queue of targets to visit. + targets_to_visit = target_list[:] + + # Maps from build file to a boolean indicating whether the build file is in + # |files|. + build_file_in_files = {} + + # Root targets across all files. + roots = set() + + # Set of Targets in |build_files|. + build_file_targets = set() + + while len(targets_to_visit) > 0: + target_name = targets_to_visit.pop() + created_target, target = _GetOrCreateTargetByName(name_to_target, + target_name) + if created_target: + roots.add(target) + elif target.visited: + continue + + target.visited = True + target.requires_build = _DoesTargetTypeRequireBuild( + target_dicts[target_name]) + target_type = target_dicts[target_name]['type'] + target.is_executable = target_type == 'executable' + target.is_static_library = target_type == 'static_library' + target.is_or_has_linked_ancestor = (target_type == 'executable' or + target_type == 'shared_library') + + build_file = gyp.common.ParseQualifiedTarget(target_name)[0] + if not build_file in build_file_in_files: + build_file_in_files[build_file] = \ + _WasBuildFileModified(build_file, data, files, toplevel_dir) + + if build_file in build_files: + build_file_targets.add(target) + + # If a build file (or any of its included files) is modified we assume all + # targets in the file are modified. + if build_file_in_files[build_file]: + print 'matching target from modified build file', target_name + target.match_status = MATCH_STATUS_MATCHES + matching_targets.append(target) + else: + sources = _ExtractSources(target_name, target_dicts[target_name], + toplevel_dir) + for source in sources: + if _ToGypPath(os.path.normpath(source)) in files: + print 'target', target_name, 'matches', source + target.match_status = MATCH_STATUS_MATCHES + matching_targets.append(target) + break + + # Add dependencies to visit as well as updating back pointers for deps. + for dep in target_dicts[target_name].get('dependencies', []): + targets_to_visit.append(dep) + + created_dep_target, dep_target = _GetOrCreateTargetByName(name_to_target, + dep) + if not created_dep_target: + roots.discard(dep_target) + + target.deps.add(dep_target) + dep_target.back_deps.add(target) + + return name_to_target, matching_targets, roots & build_file_targets + + +def _GetUnqualifiedToTargetMapping(all_targets, to_find): + """Returns a tuple of the following: + . mapping (dictionary) from unqualified name to Target for all the + Targets in |to_find|. + . any target names not found. If this is empty all targets were found.""" + result = {} + if not to_find: + return {}, [] + to_find = set(to_find) + for target_name in all_targets.keys(): + extracted = gyp.common.ParseQualifiedTarget(target_name) + if len(extracted) > 1 and extracted[1] in to_find: + to_find.remove(extracted[1]) + result[extracted[1]] = all_targets[target_name] + if not to_find: + return result, [] + return result, [x for x in to_find] + + +def _DoesTargetDependOnMatchingTargets(target): + """Returns true if |target| or any of its dependencies is one of the + targets containing the files supplied as input to analyzer. This updates + |matches| of the Targets as it recurses. + target: the Target to look for.""" + if target.match_status == MATCH_STATUS_DOESNT_MATCH: + return False + if target.match_status == MATCH_STATUS_MATCHES or \ + target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: + return True + for dep in target.deps: + if _DoesTargetDependOnMatchingTargets(dep): + target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY + print '\t', target.name, 'matches by dep', dep.name + return True + target.match_status = MATCH_STATUS_DOESNT_MATCH + return False + + +def _GetTargetsDependingOnMatchingTargets(possible_targets): + """Returns the list of Targets in |possible_targets| that depend (either + directly on indirectly) on at least one of the targets containing the files + supplied as input to analyzer. + possible_targets: targets to search from.""" + found = [] + print 'Targets that matched by dependency:' + for target in possible_targets: + if _DoesTargetDependOnMatchingTargets(target): + found.append(target) + return found + + +def _AddCompileTargets(target, roots, add_if_no_ancestor, result): + """Recurses through all targets that depend on |target|, adding all targets + that need to be built (and are in |roots|) to |result|. + roots: set of root targets. + add_if_no_ancestor: If true and there are no ancestors of |target| then add + |target| to |result|. |target| must still be in |roots|. + result: targets that need to be built are added here.""" + if target.visited: + return + + target.visited = True + target.in_roots = target in roots + + for back_dep_target in target.back_deps: + _AddCompileTargets(back_dep_target, roots, False, result) + target.added_to_compile_targets |= back_dep_target.added_to_compile_targets + target.in_roots |= back_dep_target.in_roots + target.is_or_has_linked_ancestor |= ( + back_dep_target.is_or_has_linked_ancestor) + + # Always add 'executable' targets. Even though they may be built by other + # targets that depend upon them it makes detection of what is going to be + # built easier. + # And always add static_libraries that have no dependencies on them from + # linkables. This is necessary as the other dependencies on them may be + # static libraries themselves, which are not compile time dependencies. + if target.in_roots and \ + (target.is_executable or + (not target.added_to_compile_targets and + (add_if_no_ancestor or target.requires_build)) or + (target.is_static_library and add_if_no_ancestor and + not target.is_or_has_linked_ancestor)): + print '\t\tadding to compile targets', target.name, 'executable', \ + target.is_executable, 'added_to_compile_targets', \ + target.added_to_compile_targets, 'add_if_no_ancestor', \ + add_if_no_ancestor, 'requires_build', target.requires_build, \ + 'is_static_library', target.is_static_library, \ + 'is_or_has_linked_ancestor', target.is_or_has_linked_ancestor + result.add(target) + target.added_to_compile_targets = True + + +def _GetCompileTargets(matching_targets, supplied_targets): + """Returns the set of Targets that require a build. + matching_targets: targets that changed and need to be built. + supplied_targets: set of targets supplied to analyzer to search from.""" + result = set() + for target in matching_targets: + print 'finding compile targets for match', target.name + _AddCompileTargets(target, supplied_targets, True, result) + return result + + +def _WriteOutput(params, **values): + """Writes the output, either to stdout or a file is specified.""" + if 'error' in values: + print 'Error:', values['error'] + if 'status' in values: + print values['status'] + if 'targets' in values: + values['targets'].sort() + print 'Supplied targets that depend on changed files:' + for target in values['targets']: + print '\t', target + if 'invalid_targets' in values: + values['invalid_targets'].sort() + print 'The following targets were not found:' + for target in values['invalid_targets']: + print '\t', target + if 'build_targets' in values: + values['build_targets'].sort() + print 'Targets that require a build:' + for target in values['build_targets']: + print '\t', target + if 'compile_targets' in values: + values['compile_targets'].sort() + print 'Targets that need to be built:' + for target in values['compile_targets']: + print '\t', target + if 'test_targets' in values: + values['test_targets'].sort() + print 'Test targets:' + for target in values['test_targets']: + print '\t', target + + output_path = params.get('generator_flags', {}).get( + 'analyzer_output_path', None) + if not output_path: + print json.dumps(values) + return + try: + f = open(output_path, 'w') + f.write(json.dumps(values) + '\n') + f.close() + except IOError as e: + print 'Error writing to output file', output_path, str(e) + + +def _WasGypIncludeFileModified(params, files): + """Returns true if one of the files in |files| is in the set of included + files.""" + if params['options'].includes: + for include in params['options'].includes: + if _ToGypPath(os.path.normpath(include)) in files: + print 'Include file modified, assuming all changed', include + return True + return False + + +def _NamesNotIn(names, mapping): + """Returns a list of the values in |names| that are not in |mapping|.""" + return [name for name in names if name not in mapping] + + +def _LookupTargets(names, mapping): + """Returns a list of the mapping[name] for each value in |names| that is in + |mapping|.""" + return [mapping[name] for name in names if name in mapping] + + +def CalculateVariables(default_variables, params): + """Calculate additional variables for use in the build (called by gyp).""" + flavor = gyp.common.GetFlavor(params) + if flavor == 'mac': + default_variables.setdefault('OS', 'mac') + elif flavor == 'win': + default_variables.setdefault('OS', 'win') + # Copy additional generator configuration data from VS, which is shared + # by the Windows Ninja generator. + import gyp.generator.msvs as msvs_generator + generator_additional_non_configuration_keys = getattr(msvs_generator, + 'generator_additional_non_configuration_keys', []) + generator_additional_path_sections = getattr(msvs_generator, + 'generator_additional_path_sections', []) + + gyp.msvs_emulation.CalculateCommonVariables(default_variables, params) + else: + operating_system = flavor + if flavor == 'android': + operating_system = 'linux' # Keep this legacy behavior for now. + default_variables.setdefault('OS', operating_system) + + +class TargetCalculator(object): + """Calculates the matching test_targets and matching compile_targets.""" + def __init__(self, files, additional_compile_target_names, test_target_names, + data, target_list, target_dicts, toplevel_dir, build_files): + self._additional_compile_target_names = set(additional_compile_target_names) + self._test_target_names = set(test_target_names) + self._name_to_target, self._changed_targets, self._root_targets = ( + _GenerateTargets(data, target_list, target_dicts, toplevel_dir, + frozenset(files), build_files)) + self._unqualified_mapping, self.invalid_targets = ( + _GetUnqualifiedToTargetMapping(self._name_to_target, + self._supplied_target_names_no_all())) + + def _supplied_target_names(self): + return self._additional_compile_target_names | self._test_target_names + + def _supplied_target_names_no_all(self): + """Returns the supplied test targets without 'all'.""" + result = self._supplied_target_names(); + result.discard('all') + return result + + def is_build_impacted(self): + """Returns true if the supplied files impact the build at all.""" + return self._changed_targets + + def find_matching_test_target_names(self): + """Returns the set of output test targets.""" + assert self.is_build_impacted() + # Find the test targets first. 'all' is special cased to mean all the + # root targets. To deal with all the supplied |test_targets| are expanded + # to include the root targets during lookup. If any of the root targets + # match, we remove it and replace it with 'all'. + test_target_names_no_all = set(self._test_target_names) + test_target_names_no_all.discard('all') + test_targets_no_all = _LookupTargets(test_target_names_no_all, + self._unqualified_mapping) + test_target_names_contains_all = 'all' in self._test_target_names + if test_target_names_contains_all: + test_targets = [x for x in (set(test_targets_no_all) | + set(self._root_targets))] + else: + test_targets = [x for x in test_targets_no_all] + print 'supplied test_targets' + for target_name in self._test_target_names: + print '\t', target_name + print 'found test_targets' + for target in test_targets: + print '\t', target.name + print 'searching for matching test targets' + matching_test_targets = _GetTargetsDependingOnMatchingTargets(test_targets) + matching_test_targets_contains_all = (test_target_names_contains_all and + set(matching_test_targets) & + set(self._root_targets)) + if matching_test_targets_contains_all: + # Remove any of the targets for all that were not explicitly supplied, + # 'all' is subsequentely added to the matching names below. + matching_test_targets = [x for x in (set(matching_test_targets) & + set(test_targets_no_all))] + print 'matched test_targets' + for target in matching_test_targets: + print '\t', target.name + matching_target_names = [gyp.common.ParseQualifiedTarget(target.name)[1] + for target in matching_test_targets] + if matching_test_targets_contains_all: + matching_target_names.append('all') + print '\tall' + return matching_target_names + + def find_matching_compile_target_names(self): + """Returns the set of output compile targets.""" + assert self.is_build_impacted(); + # Compile targets are found by searching up from changed targets. + # Reset the visited status for _GetBuildTargets. + for target in self._name_to_target.itervalues(): + target.visited = False + + supplied_targets = _LookupTargets(self._supplied_target_names_no_all(), + self._unqualified_mapping) + if 'all' in self._supplied_target_names(): + supplied_targets = [x for x in (set(supplied_targets) | + set(self._root_targets))] + print 'Supplied test_targets & compile_targets' + for target in supplied_targets: + print '\t', target.name + print 'Finding compile targets' + compile_targets = _GetCompileTargets(self._changed_targets, + supplied_targets) + return [gyp.common.ParseQualifiedTarget(target.name)[1] + for target in compile_targets] + + +def GenerateOutput(target_list, target_dicts, data, params): + """Called by gyp as the final stage. Outputs results.""" + config = Config() + try: + config.Init(params) + + if not config.files: + raise Exception('Must specify files to analyze via config_path generator ' + 'flag') + + toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir)) + if debug: + print 'toplevel_dir', toplevel_dir + + if _WasGypIncludeFileModified(params, config.files): + result_dict = { 'status': all_changed_string, + 'test_targets': list(config.test_target_names), + 'compile_targets': list( + config.additional_compile_target_names | + config.test_target_names) } + _WriteOutput(params, **result_dict) + return + + calculator = TargetCalculator(config.files, + config.additional_compile_target_names, + config.test_target_names, data, + target_list, target_dicts, toplevel_dir, + params['build_files']) + if not calculator.is_build_impacted(): + result_dict = { 'status': no_dependency_string, + 'test_targets': [], + 'compile_targets': [] } + if calculator.invalid_targets: + result_dict['invalid_targets'] = calculator.invalid_targets + _WriteOutput(params, **result_dict) + return + + test_target_names = calculator.find_matching_test_target_names() + compile_target_names = calculator.find_matching_compile_target_names() + found_at_least_one_target = compile_target_names or test_target_names + result_dict = { 'test_targets': test_target_names, + 'status': found_dependency_string if + found_at_least_one_target else no_dependency_string, + 'compile_targets': list( + set(compile_target_names) | + set(test_target_names)) } + if calculator.invalid_targets: + result_dict['invalid_targets'] = calculator.invalid_targets + _WriteOutput(params, **result_dict) + + except Exception as e: + _WriteOutput(params, error=str(e)) diff --git a/third_party/gyp/generator/android.py b/third_party/gyp/generator/android.py deleted file mode 100644 index 41346e2b..00000000 --- a/third_party/gyp/generator/android.py +++ /dev/null @@ -1,1069 +0,0 @@ -# Copyright (c) 2012 Google Inc. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -# Notes: -# -# This generates makefiles suitable for inclusion into the Android build system -# via an Android.mk file. It is based on make.py, the standard makefile -# generator. -# -# The code below generates a separate .mk file for each target, but -# all are sourced by the top-level GypAndroid.mk. This means that all -# variables in .mk-files clobber one another, and furthermore that any -# variables set potentially clash with other Android build system variables. -# Try to avoid setting global variables where possible. - -import gyp -import gyp.common -import gyp.generator.make as make # Reuse global functions from make backend. -import os -import re -import subprocess - -generator_default_variables = { - 'OS': 'android', - 'EXECUTABLE_PREFIX': '', - 'EXECUTABLE_SUFFIX': '', - 'STATIC_LIB_PREFIX': 'lib', - 'SHARED_LIB_PREFIX': 'lib', - 'STATIC_LIB_SUFFIX': '.a', - 'SHARED_LIB_SUFFIX': '.so', - 'INTERMEDIATE_DIR': '$(gyp_intermediate_dir)', - 'SHARED_INTERMEDIATE_DIR': '$(gyp_shared_intermediate_dir)', - 'PRODUCT_DIR': '$(gyp_shared_intermediate_dir)', - 'SHARED_LIB_DIR': '$(builddir)/lib.$(TOOLSET)', - 'LIB_DIR': '$(obj).$(TOOLSET)', - 'RULE_INPUT_ROOT': '%(INPUT_ROOT)s', # This gets expanded by Python. - 'RULE_INPUT_DIRNAME': '%(INPUT_DIRNAME)s', # This gets expanded by Python. - 'RULE_INPUT_PATH': '$(RULE_SOURCES)', - 'RULE_INPUT_EXT': '$(suffix $<)', - 'RULE_INPUT_NAME': '$(notdir $<)', - 'CONFIGURATION_NAME': '$(GYP_CONFIGURATION)', -} - -# Make supports multiple toolsets -generator_supports_multiple_toolsets = True - - -# Generator-specific gyp specs. -generator_additional_non_configuration_keys = [ - # Boolean to declare that this target does not want its name mangled. - 'android_unmangled_name', -] -generator_additional_path_sections = [] -generator_extra_sources_for_rules = [] - - -SHARED_FOOTER = """\ -# "gyp_all_modules" is a concatenation of the "gyp_all_modules" targets from -# all the included sub-makefiles. This is just here to clarify. -gyp_all_modules: -""" - -header = """\ -# This file is generated by gyp; do not edit. - -""" - -android_standard_include_paths = set([ - # JNI_H_INCLUDE in build/core/binary.mk - 'dalvik/libnativehelper/include/nativehelper', - # from SRC_HEADERS in build/core/config.mk - 'system/core/include', - 'hardware/libhardware/include', - 'hardware/libhardware_legacy/include', - 'hardware/ril/include', - 'dalvik/libnativehelper/include', - 'frameworks/native/include', - 'frameworks/native/opengl/include', - 'frameworks/base/include', - 'frameworks/base/opengl/include', - 'frameworks/base/native/include', - 'external/skia/include', - # TARGET_C_INCLUDES in build/core/combo/TARGET_linux-arm.mk - 'bionic/libc/arch-arm/include', - 'bionic/libc/include', - 'bionic/libstdc++/include', - 'bionic/libc/kernel/common', - 'bionic/libc/kernel/arch-arm', - 'bionic/libm/include', - 'bionic/libm/include/arm', - 'bionic/libthread_db/include', - ]) - - -# Map gyp target types to Android module classes. -MODULE_CLASSES = { - 'static_library': 'STATIC_LIBRARIES', - 'shared_library': 'SHARED_LIBRARIES', - 'executable': 'EXECUTABLES', -} - - -def IsCPPExtension(ext): - return make.COMPILABLE_EXTENSIONS.get(ext) == 'cxx' - - -def Sourceify(path): - """Convert a path to its source directory form. The Android backend does not - support options.generator_output, so this function is a noop.""" - return path - - -# Map from qualified target to path to output. -# For Android, the target of these maps is a tuple ('static', 'modulename'), -# ('dynamic', 'modulename'), or ('path', 'some/path') instead of a string, -# since we link by module. -target_outputs = {} -# Map from qualified target to any linkable output. A subset -# of target_outputs. E.g. when mybinary depends on liba, we want to -# include liba in the linker line; when otherbinary depends on -# mybinary, we just want to build mybinary first. -target_link_deps = {} - - -class AndroidMkWriter(object): - """AndroidMkWriter packages up the writing of one target-specific Android.mk. - - Its only real entry point is Write(), and is mostly used for namespacing. - """ - - def __init__(self, android_top_dir): - self.android_top_dir = android_top_dir - - def Write(self, qualified_target, relative_target, base_path, output_filename, - spec, configs, part_of_all): - """The main entry point: writes a .mk file for a single target. - - Arguments: - qualified_target: target we're generating - relative_target: qualified target name relative to the root - base_path: path relative to source root we're building in, used to resolve - target-relative paths - output_filename: output .mk file name to write - spec, configs: gyp info - part_of_all: flag indicating this target is part of 'all' - """ - gyp.common.EnsureDirExists(output_filename) - - self.fp = open(output_filename, 'w') - - self.fp.write(header) - - self.qualified_target = qualified_target - self.relative_target = relative_target - self.path = base_path - self.target = spec['target_name'] - self.type = spec['type'] - self.toolset = spec['toolset'] - - deps, link_deps = self.ComputeDeps(spec) - - # Some of the generation below can add extra output, sources, or - # link dependencies. All of the out params of the functions that - # follow use names like extra_foo. - extra_outputs = [] - extra_sources = [] - - self.android_class = MODULE_CLASSES.get(self.type, 'GYP') - self.android_module = self.ComputeAndroidModule(spec) - (self.android_stem, self.android_suffix) = self.ComputeOutputParts(spec) - self.output = self.output_binary = self.ComputeOutput(spec) - - # Standard header. - self.WriteLn('include $(CLEAR_VARS)\n') - - # Module class and name. - self.WriteLn('LOCAL_MODULE_CLASS := ' + self.android_class) - self.WriteLn('LOCAL_MODULE := ' + self.android_module) - # Only emit LOCAL_MODULE_STEM if it's different to LOCAL_MODULE. - # The library module classes fail if the stem is set. ComputeOutputParts - # makes sure that stem == modulename in these cases. - if self.android_stem != self.android_module: - self.WriteLn('LOCAL_MODULE_STEM := ' + self.android_stem) - self.WriteLn('LOCAL_MODULE_SUFFIX := ' + self.android_suffix) - self.WriteLn('LOCAL_MODULE_TAGS := optional') - if self.toolset == 'host': - self.WriteLn('LOCAL_IS_HOST_MODULE := true') - - # Grab output directories; needed for Actions and Rules. - self.WriteLn('gyp_intermediate_dir := $(call local-intermediates-dir)') - self.WriteLn('gyp_shared_intermediate_dir := ' - '$(call intermediates-dir-for,GYP,shared)') - self.WriteLn() - - # List files this target depends on so that actions/rules/copies/sources - # can depend on the list. - # TODO: doesn't pull in things through transitive link deps; needed? - target_dependencies = [x[1] for x in deps if x[0] == 'path'] - self.WriteLn('# Make sure our deps are built first.') - self.WriteList(target_dependencies, 'GYP_TARGET_DEPENDENCIES', - local_pathify=True) - - # Actions must come first, since they can generate more OBJs for use below. - if 'actions' in spec: - self.WriteActions(spec['actions'], extra_sources, extra_outputs) - - # Rules must be early like actions. - if 'rules' in spec: - self.WriteRules(spec['rules'], extra_sources, extra_outputs) - - if 'copies' in spec: - self.WriteCopies(spec['copies'], extra_outputs) - - # GYP generated outputs. - self.WriteList(extra_outputs, 'GYP_GENERATED_OUTPUTS', local_pathify=True) - - # Set LOCAL_ADDITIONAL_DEPENDENCIES so that Android's build rules depend - # on both our dependency targets and our generated files. - self.WriteLn('# Make sure our deps and generated files are built first.') - self.WriteLn('LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) ' - '$(GYP_GENERATED_OUTPUTS)') - self.WriteLn() - - # Sources. - if spec.get('sources', []) or extra_sources: - self.WriteSources(spec, configs, extra_sources) - - self.WriteTarget(spec, configs, deps, link_deps, part_of_all) - - # Update global list of target outputs, used in dependency tracking. - target_outputs[qualified_target] = ('path', self.output_binary) - - # Update global list of link dependencies. - if self.type == 'static_library': - target_link_deps[qualified_target] = ('static', self.android_module) - elif self.type == 'shared_library': - target_link_deps[qualified_target] = ('shared', self.android_module) - - self.fp.close() - return self.android_module - - - def WriteActions(self, actions, extra_sources, extra_outputs): - """Write Makefile code for any 'actions' from the gyp input. - - extra_sources: a list that will be filled in with newly generated source - files, if any - extra_outputs: a list that will be filled in with any outputs of these - actions (used to make other pieces dependent on these - actions) - """ - for action in actions: - name = make.StringToMakefileVariable('%s_%s' % (self.relative_target, - action['action_name'])) - self.WriteLn('### Rules for action "%s":' % action['action_name']) - inputs = action['inputs'] - outputs = action['outputs'] - - # Build up a list of outputs. - # Collect the output dirs we'll need. - dirs = set() - for out in outputs: - if not out.startswith('$'): - print ('WARNING: Action for target "%s" writes output to local path ' - '"%s".' % (self.target, out)) - dir = os.path.split(out)[0] - if dir: - dirs.add(dir) - if int(action.get('process_outputs_as_sources', False)): - extra_sources += outputs - - # Prepare the actual command. - command = gyp.common.EncodePOSIXShellList(action['action']) - if 'message' in action: - quiet_cmd = 'Gyp action: %s ($@)' % action['message'] - else: - quiet_cmd = 'Gyp action: %s ($@)' % name - if len(dirs) > 0: - command = 'mkdir -p %s' % ' '.join(dirs) + '; ' + command - - cd_action = 'cd $(gyp_local_path)/%s; ' % self.path - command = cd_action + command - - # The makefile rules are all relative to the top dir, but the gyp actions - # are defined relative to their containing dir. This replaces the gyp_* - # variables for the action rule with an absolute version so that the - # output goes in the right place. - # Only write the gyp_* rules for the "primary" output (:1); - # it's superfluous for the "extra outputs", and this avoids accidentally - # writing duplicate dummy rules for those outputs. - main_output = make.QuoteSpaces(self.LocalPathify(outputs[0])) - self.WriteLn('%s: gyp_local_path := $(LOCAL_PATH)' % main_output) - self.WriteLn('%s: gyp_intermediate_dir := ' - '$(abspath $(gyp_intermediate_dir))' % main_output) - self.WriteLn('%s: gyp_shared_intermediate_dir := ' - '$(abspath $(gyp_shared_intermediate_dir))' % main_output) - - # Android's envsetup.sh adds a number of directories to the path including - # the built host binary directory. This causes actions/rules invoked by - # gyp to sometimes use these instead of system versions, e.g. bison. - # The built host binaries may not be suitable, and can cause errors. - # So, we remove them from the PATH using the ANDROID_BUILD_PATHS variable - # set by envsetup. - self.WriteLn('%s: export PATH := $(subst $(ANDROID_BUILD_PATHS),,$(PATH))' - % main_output) - - for input in inputs: - assert ' ' not in input, ( - "Spaces in action input filenames not supported (%s)" % input) - for output in outputs: - assert ' ' not in output, ( - "Spaces in action output filenames not supported (%s)" % output) - - self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES)' % - (main_output, ' '.join(map(self.LocalPathify, inputs)))) - self.WriteLn('\t@echo "%s"' % quiet_cmd) - self.WriteLn('\t$(hide)%s\n' % command) - for output in outputs[1:]: - # Make each output depend on the main output, with an empty command - # to force make to notice that the mtime has changed. - self.WriteLn('%s: %s ;' % (self.LocalPathify(output), main_output)) - - extra_outputs += outputs - self.WriteLn() - - self.WriteLn() - - - def WriteRules(self, rules, extra_sources, extra_outputs): - """Write Makefile code for any 'rules' from the gyp input. - - extra_sources: a list that will be filled in with newly generated source - files, if any - extra_outputs: a list that will be filled in with any outputs of these - rules (used to make other pieces dependent on these rules) - """ - if len(rules) == 0: - return - rule_trigger = '%s_rule_trigger' % self.android_module - - did_write_rule = False - for rule in rules: - if len(rule.get('rule_sources', [])) == 0: - continue - did_write_rule = True - name = make.StringToMakefileVariable('%s_%s' % (self.relative_target, - rule['rule_name'])) - self.WriteLn('\n### Generated for rule "%s":' % name) - self.WriteLn('# "%s":' % rule) - - inputs = rule.get('inputs') - for rule_source in rule.get('rule_sources', []): - (rule_source_dirname, rule_source_basename) = os.path.split(rule_source) - (rule_source_root, rule_source_ext) = \ - os.path.splitext(rule_source_basename) - - outputs = [self.ExpandInputRoot(out, rule_source_root, - rule_source_dirname) - for out in rule['outputs']] - - dirs = set() - for out in outputs: - if not out.startswith('$'): - print ('WARNING: Rule for target %s writes output to local path %s' - % (self.target, out)) - dir = os.path.dirname(out) - if dir: - dirs.add(dir) - extra_outputs += outputs - if int(rule.get('process_outputs_as_sources', False)): - extra_sources.extend(outputs) - - components = [] - for component in rule['action']: - component = self.ExpandInputRoot(component, rule_source_root, - rule_source_dirname) - if '$(RULE_SOURCES)' in component: - component = component.replace('$(RULE_SOURCES)', - rule_source) - components.append(component) - - command = gyp.common.EncodePOSIXShellList(components) - cd_action = 'cd $(gyp_local_path)/%s; ' % self.path - command = cd_action + command - if dirs: - command = 'mkdir -p %s' % ' '.join(dirs) + '; ' + command - - # We set up a rule to build the first output, and then set up - # a rule for each additional output to depend on the first. - outputs = map(self.LocalPathify, outputs) - main_output = outputs[0] - self.WriteLn('%s: gyp_local_path := $(LOCAL_PATH)' % main_output) - self.WriteLn('%s: gyp_intermediate_dir := ' - '$(abspath $(gyp_intermediate_dir))' % main_output) - self.WriteLn('%s: gyp_shared_intermediate_dir := ' - '$(abspath $(gyp_shared_intermediate_dir))' % main_output) - - # See explanation in WriteActions. - self.WriteLn('%s: export PATH := ' - '$(subst $(ANDROID_BUILD_PATHS),,$(PATH))' % main_output) - - main_output_deps = self.LocalPathify(rule_source) - if inputs: - main_output_deps += ' ' - main_output_deps += ' '.join([self.LocalPathify(f) for f in inputs]) - - self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES)' % - (main_output, main_output_deps)) - self.WriteLn('\t%s\n' % command) - for output in outputs[1:]: - # Make each output depend on the main output, with an empty command - # to force make to notice that the mtime has changed. - self.WriteLn('%s: %s ;' % (output, main_output)) - self.WriteLn('.PHONY: %s' % (rule_trigger)) - self.WriteLn('%s: %s' % (rule_trigger, main_output)) - self.WriteLn('') - if did_write_rule: - extra_sources.append(rule_trigger) # Force all rules to run. - self.WriteLn('### Finished generating for all rules') - self.WriteLn('') - - - def WriteCopies(self, copies, extra_outputs): - """Write Makefile code for any 'copies' from the gyp input. - - extra_outputs: a list that will be filled in with any outputs of this action - (used to make other pieces dependent on this action) - """ - self.WriteLn('### Generated for copy rule.') - - variable = make.StringToMakefileVariable(self.relative_target + '_copies') - outputs = [] - for copy in copies: - for path in copy['files']: - # The Android build system does not allow generation of files into the - # source tree. The destination should start with a variable, which will - # typically be $(gyp_intermediate_dir) or - # $(gyp_shared_intermediate_dir). Note that we can't use an assertion - # because some of the gyp tests depend on this. - if not copy['destination'].startswith('$'): - print ('WARNING: Copy rule for target %s writes output to ' - 'local path %s' % (self.target, copy['destination'])) - - # LocalPathify() calls normpath, stripping trailing slashes. - path = Sourceify(self.LocalPathify(path)) - filename = os.path.split(path)[1] - output = Sourceify(self.LocalPathify(os.path.join(copy['destination'], - filename))) - - self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES) | $(ACP)' % - (output, path)) - self.WriteLn('\t@echo Copying: $@') - self.WriteLn('\t$(hide) mkdir -p $(dir $@)') - self.WriteLn('\t$(hide) $(ACP) -rpf $< $@') - self.WriteLn() - outputs.append(output) - self.WriteLn('%s = %s' % (variable, - ' '.join(map(make.QuoteSpaces, outputs)))) - extra_outputs.append('$(%s)' % variable) - self.WriteLn() - - - def WriteSourceFlags(self, spec, configs): - """Write out the flags and include paths used to compile source files for - the current target. - - Args: - spec, configs: input from gyp. - """ - for configname, config in sorted(configs.iteritems()): - extracted_includes = [] - - self.WriteLn('\n# Flags passed to both C and C++ files.') - cflags, includes_from_cflags = self.ExtractIncludesFromCFlags( - config.get('cflags', []) + config.get('cflags_c', [])) - extracted_includes.extend(includes_from_cflags) - self.WriteList(cflags, 'MY_CFLAGS_%s' % configname) - - self.WriteList(config.get('defines'), 'MY_DEFS_%s' % configname, - prefix='-D', quoter=make.EscapeCppDefine) - - self.WriteLn('\n# Include paths placed before CFLAGS/CPPFLAGS') - includes = list(config.get('include_dirs', [])) - includes.extend(extracted_includes) - includes = map(Sourceify, map(self.LocalPathify, includes)) - includes = self.NormalizeIncludePaths(includes) - self.WriteList(includes, 'LOCAL_C_INCLUDES_%s' % configname) - - self.WriteLn('\n# Flags passed to only C++ (and not C) files.') - self.WriteList(config.get('cflags_cc'), 'LOCAL_CPPFLAGS_%s' % configname) - - self.WriteLn('\nLOCAL_CFLAGS := $(MY_CFLAGS_$(GYP_CONFIGURATION)) ' - '$(MY_DEFS_$(GYP_CONFIGURATION))') - # Undefine ANDROID for host modules - # TODO: the source code should not use macro ANDROID to tell if it's host - # or target module. - if self.toolset == 'host': - self.WriteLn('# Undefine ANDROID for host modules') - self.WriteLn('LOCAL_CFLAGS += -UANDROID') - self.WriteLn('LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) ' - '$(LOCAL_C_INCLUDES_$(GYP_CONFIGURATION))') - self.WriteLn('LOCAL_CPPFLAGS := $(LOCAL_CPPFLAGS_$(GYP_CONFIGURATION))') - - - def WriteSources(self, spec, configs, extra_sources): - """Write Makefile code for any 'sources' from the gyp input. - These are source files necessary to build the current target. - We need to handle shared_intermediate directory source files as - a special case by copying them to the intermediate directory and - treating them as a genereated sources. Otherwise the Android build - rules won't pick them up. - - Args: - spec, configs: input from gyp. - extra_sources: Sources generated from Actions or Rules. - """ - sources = filter(make.Compilable, spec.get('sources', [])) - generated_not_sources = [x for x in extra_sources if not make.Compilable(x)] - extra_sources = filter(make.Compilable, extra_sources) - - # Determine and output the C++ extension used by these sources. - # We simply find the first C++ file and use that extension. - all_sources = sources + extra_sources - local_cpp_extension = '.cpp' - for source in all_sources: - (root, ext) = os.path.splitext(source) - if IsCPPExtension(ext): - local_cpp_extension = ext - break - if local_cpp_extension != '.cpp': - self.WriteLn('LOCAL_CPP_EXTENSION := %s' % local_cpp_extension) - - # We need to move any non-generated sources that are coming from the - # shared intermediate directory out of LOCAL_SRC_FILES and put them - # into LOCAL_GENERATED_SOURCES. We also need to move over any C++ files - # that don't match our local_cpp_extension, since Android will only - # generate Makefile rules for a single LOCAL_CPP_EXTENSION. - local_files = [] - for source in sources: - (root, ext) = os.path.splitext(source) - if '$(gyp_shared_intermediate_dir)' in source: - extra_sources.append(source) - elif '$(gyp_intermediate_dir)' in source: - extra_sources.append(source) - elif IsCPPExtension(ext) and ext != local_cpp_extension: - extra_sources.append(source) - else: - local_files.append(os.path.normpath(os.path.join(self.path, source))) - - # For any generated source, if it is coming from the shared intermediate - # directory then we add a Make rule to copy them to the local intermediate - # directory first. This is because the Android LOCAL_GENERATED_SOURCES - # must be in the local module intermediate directory for the compile rules - # to work properly. If the file has the wrong C++ extension, then we add - # a rule to copy that to intermediates and use the new version. - final_generated_sources = [] - # If a source file gets copied, we still need to add the orginal source - # directory as header search path, for GCC searches headers in the - # directory that contains the source file by default. - origin_src_dirs = [] - for source in extra_sources: - local_file = source - if not '$(gyp_intermediate_dir)/' in local_file: - basename = os.path.basename(local_file) - local_file = '$(gyp_intermediate_dir)/' + basename - (root, ext) = os.path.splitext(local_file) - if IsCPPExtension(ext) and ext != local_cpp_extension: - local_file = root + local_cpp_extension - if local_file != source: - self.WriteLn('%s: %s' % (local_file, self.LocalPathify(source))) - self.WriteLn('\tmkdir -p $(@D); cp $< $@') - origin_src_dirs.append(os.path.dirname(source)) - final_generated_sources.append(local_file) - - # We add back in all of the non-compilable stuff to make sure that the - # make rules have dependencies on them. - final_generated_sources.extend(generated_not_sources) - self.WriteList(final_generated_sources, 'LOCAL_GENERATED_SOURCES') - - origin_src_dirs = gyp.common.uniquer(origin_src_dirs) - origin_src_dirs = map(Sourceify, map(self.LocalPathify, origin_src_dirs)) - self.WriteList(origin_src_dirs, 'GYP_COPIED_SOURCE_ORIGIN_DIRS') - - self.WriteList(local_files, 'LOCAL_SRC_FILES') - - # Write out the flags used to compile the source; this must be done last - # so that GYP_COPIED_SOURCE_ORIGIN_DIRS can be used as an include path. - self.WriteSourceFlags(spec, configs) - - - def ComputeAndroidModule(self, spec): - """Return the Android module name used for a gyp spec. - - We use the complete qualified target name to avoid collisions between - duplicate targets in different directories. We also add a suffix to - distinguish gyp-generated module names. - """ - - if int(spec.get('android_unmangled_name', 0)): - assert self.type != 'shared_library' or self.target.startswith('lib') - return self.target - - if self.type == 'shared_library': - # For reasons of convention, the Android build system requires that all - # shared library modules are named 'libfoo' when generating -l flags. - prefix = 'lib_' - else: - prefix = '' - - if spec['toolset'] == 'host': - suffix = '_host_gyp' - else: - suffix = '_gyp' - - if self.path: - name = '%s%s_%s%s' % (prefix, self.path, self.target, suffix) - else: - name = '%s%s%s' % (prefix, self.target, suffix) - - return make.StringToMakefileVariable(name) - - - def ComputeOutputParts(self, spec): - """Return the 'output basename' of a gyp spec, split into filename + ext. - - Android libraries must be named the same thing as their module name, - otherwise the linker can't find them, so product_name and so on must be - ignored if we are building a library, and the "lib" prepending is - not done for Android. - """ - assert self.type != 'loadable_module' # TODO: not supported? - - target = spec['target_name'] - target_prefix = '' - target_ext = '' - if self.type == 'static_library': - target = self.ComputeAndroidModule(spec) - target_ext = '.a' - elif self.type == 'shared_library': - target = self.ComputeAndroidModule(spec) - target_ext = '.so' - elif self.type == 'none': - target_ext = '.stamp' - elif self.type != 'executable': - print ("ERROR: What output file should be generated?", - "type", self.type, "target", target) - - if self.type != 'static_library' and self.type != 'shared_library': - target_prefix = spec.get('product_prefix', target_prefix) - target = spec.get('product_name', target) - product_ext = spec.get('product_extension') - if product_ext: - target_ext = '.' + product_ext - - target_stem = target_prefix + target - return (target_stem, target_ext) - - - def ComputeOutputBasename(self, spec): - """Return the 'output basename' of a gyp spec. - - E.g., the loadable module 'foobar' in directory 'baz' will produce - 'libfoobar.so' - """ - return ''.join(self.ComputeOutputParts(spec)) - - - def ComputeOutput(self, spec): - """Return the 'output' (full output path) of a gyp spec. - - E.g., the loadable module 'foobar' in directory 'baz' will produce - '$(obj)/baz/libfoobar.so' - """ - if self.type == 'executable' and self.toolset == 'host': - # We install host executables into shared_intermediate_dir so they can be - # run by gyp rules that refer to PRODUCT_DIR. - path = '$(gyp_shared_intermediate_dir)' - elif self.type == 'shared_library': - if self.toolset == 'host': - path = '$(HOST_OUT_INTERMEDIATE_LIBRARIES)' - else: - path = '$(TARGET_OUT_INTERMEDIATE_LIBRARIES)' - else: - # Other targets just get built into their intermediate dir. - if self.toolset == 'host': - path = '$(call intermediates-dir-for,%s,%s,true)' % (self.android_class, - self.android_module) - else: - path = '$(call intermediates-dir-for,%s,%s)' % (self.android_class, - self.android_module) - - assert spec.get('product_dir') is None # TODO: not supported? - return os.path.join(path, self.ComputeOutputBasename(spec)) - - def NormalizeIncludePaths(self, include_paths): - """ Normalize include_paths. - Convert absolute paths to relative to the Android top directory; - filter out include paths that are already brought in by the Android build - system. - - Args: - include_paths: A list of unprocessed include paths. - Returns: - A list of normalized include paths. - """ - normalized = [] - for path in include_paths: - if path[0] == '/': - path = gyp.common.RelativePath(path, self.android_top_dir) - - # Filter out the Android standard search path. - if path not in android_standard_include_paths: - normalized.append(path) - return normalized - - def ExtractIncludesFromCFlags(self, cflags): - """Extract includes "-I..." out from cflags - - Args: - cflags: A list of compiler flags, which may be mixed with "-I.." - Returns: - A tuple of lists: (clean_clfags, include_paths). "-I.." is trimmed. - """ - clean_cflags = [] - include_paths = [] - for flag in cflags: - if flag.startswith('-I'): - include_paths.append(flag[2:]) - else: - clean_cflags.append(flag) - - return (clean_cflags, include_paths) - - def ComputeAndroidLibraryModuleNames(self, libraries): - """Compute the Android module names from libraries, ie spec.get('libraries') - - Args: - libraries: the value of spec.get('libraries') - Returns: - A tuple (static_lib_modules, dynamic_lib_modules) - """ - static_lib_modules = [] - dynamic_lib_modules = [] - for libs in libraries: - # Libs can have multiple words. - for lib in libs.split(): - # Filter the system libraries, which are added by default by the Android - # build system. - if (lib == '-lc' or lib == '-lstdc++' or lib == '-lm' or - lib.endswith('libgcc.a')): - continue - match = re.search(r'([^/]+)\.a$', lib) - if match: - static_lib_modules.append(match.group(1)) - continue - match = re.search(r'([^/]+)\.so$', lib) - if match: - dynamic_lib_modules.append(match.group(1)) - continue - # "-lstlport" -> libstlport - if lib.startswith('-l'): - if lib.endswith('_static'): - static_lib_modules.append('lib' + lib[2:]) - else: - dynamic_lib_modules.append('lib' + lib[2:]) - return (static_lib_modules, dynamic_lib_modules) - - - def ComputeDeps(self, spec): - """Compute the dependencies of a gyp spec. - - Returns a tuple (deps, link_deps), where each is a list of - filenames that will need to be put in front of make for either - building (deps) or linking (link_deps). - """ - deps = [] - link_deps = [] - if 'dependencies' in spec: - deps.extend([target_outputs[dep] for dep in spec['dependencies'] - if target_outputs[dep]]) - for dep in spec['dependencies']: - if dep in target_link_deps: - link_deps.append(target_link_deps[dep]) - deps.extend(link_deps) - return (gyp.common.uniquer(deps), gyp.common.uniquer(link_deps)) - - - def WriteTargetFlags(self, spec, configs, link_deps): - """Write Makefile code to specify the link flags and library dependencies. - - spec, configs: input from gyp. - link_deps: link dependency list; see ComputeDeps() - """ - for configname, config in sorted(configs.iteritems()): - ldflags = list(config.get('ldflags', [])) - self.WriteLn('') - self.WriteList(ldflags, 'LOCAL_LDFLAGS_%s' % configname) - self.WriteLn('\nLOCAL_LDFLAGS := $(LOCAL_LDFLAGS_$(GYP_CONFIGURATION))') - - # Libraries (i.e. -lfoo) - libraries = gyp.common.uniquer(spec.get('libraries', [])) - static_libs, dynamic_libs = self.ComputeAndroidLibraryModuleNames( - libraries) - - # Link dependencies (i.e. libfoo.a, libfoo.so) - static_link_deps = [x[1] for x in link_deps if x[0] == 'static'] - shared_link_deps = [x[1] for x in link_deps if x[0] == 'shared'] - self.WriteLn('') - self.WriteList(static_libs + static_link_deps, - 'LOCAL_STATIC_LIBRARIES') - self.WriteLn('# Enable grouping to fix circular references') - self.WriteLn('LOCAL_GROUP_STATIC_LIBRARIES := true') - self.WriteLn('') - self.WriteList(dynamic_libs + shared_link_deps, - 'LOCAL_SHARED_LIBRARIES') - - - def WriteTarget(self, spec, configs, deps, link_deps, part_of_all): - """Write Makefile code to produce the final target of the gyp spec. - - spec, configs: input from gyp. - deps, link_deps: dependency lists; see ComputeDeps() - part_of_all: flag indicating this target is part of 'all' - """ - self.WriteLn('### Rules for final target.') - - if self.type != 'none': - self.WriteTargetFlags(spec, configs, link_deps) - - # Add to the set of targets which represent the gyp 'all' target. We use the - # name 'gyp_all_modules' as the Android build system doesn't allow the use - # of the Make target 'all' and because 'all_modules' is the equivalent of - # the Make target 'all' on Android. - if part_of_all: - self.WriteLn('# Add target alias to "gyp_all_modules" target.') - self.WriteLn('.PHONY: gyp_all_modules') - self.WriteLn('gyp_all_modules: %s' % self.android_module) - self.WriteLn('') - - # Add an alias from the gyp target name to the Android module name. This - # simplifies manual builds of the target, and is required by the test - # framework. - if self.target != self.android_module: - self.WriteLn('# Alias gyp target name.') - self.WriteLn('.PHONY: %s' % self.target) - self.WriteLn('%s: %s' % (self.target, self.android_module)) - self.WriteLn('') - - # Add the command to trigger build of the target type depending - # on the toolset. Ex: BUILD_STATIC_LIBRARY vs. BUILD_HOST_STATIC_LIBRARY - # NOTE: This has to come last! - modifier = '' - if self.toolset == 'host': - modifier = 'HOST_' - if self.type == 'static_library': - self.WriteLn('include $(BUILD_%sSTATIC_LIBRARY)' % modifier) - elif self.type == 'shared_library': - self.WriteLn('LOCAL_PRELINK_MODULE := false') - self.WriteLn('include $(BUILD_%sSHARED_LIBRARY)' % modifier) - elif self.type == 'executable': - if self.toolset == 'host': - self.WriteLn('LOCAL_MODULE_PATH := $(gyp_shared_intermediate_dir)') - else: - # Don't install target executables for now, as it results in them being - # included in ROM. This can be revisited if there's a reason to install - # them later. - self.WriteLn('LOCAL_UNINSTALLABLE_MODULE := true') - self.WriteLn('include $(BUILD_%sEXECUTABLE)' % modifier) - else: - self.WriteLn('LOCAL_MODULE_PATH := $(PRODUCT_OUT)/gyp_stamp') - self.WriteLn('LOCAL_UNINSTALLABLE_MODULE := true') - self.WriteLn() - self.WriteLn('include $(BUILD_SYSTEM)/base_rules.mk') - self.WriteLn() - self.WriteLn('$(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES)') - self.WriteLn('\t$(hide) echo "Gyp timestamp: $@"') - self.WriteLn('\t$(hide) mkdir -p $(dir $@)') - self.WriteLn('\t$(hide) touch $@') - - - def WriteList(self, value_list, variable=None, prefix='', - quoter=make.QuoteIfNecessary, local_pathify=False): - """Write a variable definition that is a list of values. - - E.g. WriteList(['a','b'], 'foo', prefix='blah') writes out - foo = blaha blahb - but in a pretty-printed style. - """ - values = '' - if value_list: - value_list = [quoter(prefix + l) for l in value_list] - if local_pathify: - value_list = [self.LocalPathify(l) for l in value_list] - values = ' \\\n\t' + ' \\\n\t'.join(value_list) - self.fp.write('%s :=%s\n\n' % (variable, values)) - - - def WriteLn(self, text=''): - self.fp.write(text + '\n') - - - def LocalPathify(self, path): - """Convert a subdirectory-relative path into a normalized path which starts - with the make variable $(LOCAL_PATH) (i.e. the top of the project tree). - Absolute paths, or paths that contain variables, are just normalized.""" - if '$(' in path or os.path.isabs(path): - # path is not a file in the project tree in this case, but calling - # normpath is still important for trimming trailing slashes. - return os.path.normpath(path) - local_path = os.path.join('$(LOCAL_PATH)', self.path, path) - local_path = os.path.normpath(local_path) - # Check that normalizing the path didn't ../ itself out of $(LOCAL_PATH) - # - i.e. that the resulting path is still inside the project tree. The - # path may legitimately have ended up containing just $(LOCAL_PATH), though, - # so we don't look for a slash. - assert local_path.startswith('$(LOCAL_PATH)'), ( - 'Path %s attempts to escape from gyp path %s !)' % (path, self.path)) - return local_path - - - def ExpandInputRoot(self, template, expansion, dirname): - if '%(INPUT_ROOT)s' not in template and '%(INPUT_DIRNAME)s' not in template: - return template - path = template % { - 'INPUT_ROOT': expansion, - 'INPUT_DIRNAME': dirname, - } - return path - - -def PerformBuild(data, configurations, params): - # The android backend only supports the default configuration. - options = params['options'] - makefile = os.path.abspath(os.path.join(options.toplevel_dir, - 'GypAndroid.mk')) - env = dict(os.environ) - env['ONE_SHOT_MAKEFILE'] = makefile - arguments = ['make', '-C', os.environ['ANDROID_BUILD_TOP'], 'gyp_all_modules'] - print 'Building: %s' % arguments - subprocess.check_call(arguments, env=env) - - -def GenerateOutput(target_list, target_dicts, data, params): - options = params['options'] - generator_flags = params.get('generator_flags', {}) - builddir_name = generator_flags.get('output_dir', 'out') - limit_to_target_all = generator_flags.get('limit_to_target_all', False) - android_top_dir = os.environ.get('ANDROID_BUILD_TOP') - assert android_top_dir, '$ANDROID_BUILD_TOP not set; you need to run lunch.' - - def CalculateMakefilePath(build_file, base_name): - """Determine where to write a Makefile for a given gyp file.""" - # Paths in gyp files are relative to the .gyp file, but we want - # paths relative to the source root for the master makefile. Grab - # the path of the .gyp file as the base to relativize against. - # E.g. "foo/bar" when we're constructing targets for "foo/bar/baz.gyp". - base_path = gyp.common.RelativePath(os.path.dirname(build_file), - options.depth) - # We write the file in the base_path directory. - output_file = os.path.join(options.depth, base_path, base_name) - assert not options.generator_output, ( - 'The Android backend does not support options.generator_output.') - base_path = gyp.common.RelativePath(os.path.dirname(build_file), - options.toplevel_dir) - return base_path, output_file - - # TODO: search for the first non-'Default' target. This can go - # away when we add verification that all targets have the - # necessary configurations. - default_configuration = None - toolsets = set([target_dicts[target]['toolset'] for target in target_list]) - for target in target_list: - spec = target_dicts[target] - if spec['default_configuration'] != 'Default': - default_configuration = spec['default_configuration'] - break - if not default_configuration: - default_configuration = 'Default' - - srcdir = '.' - makefile_name = 'GypAndroid' + options.suffix + '.mk' - makefile_path = os.path.join(options.toplevel_dir, makefile_name) - assert not options.generator_output, ( - 'The Android backend does not support options.generator_output.') - gyp.common.EnsureDirExists(makefile_path) - root_makefile = open(makefile_path, 'w') - - root_makefile.write(header) - - # We set LOCAL_PATH just once, here, to the top of the project tree. This - # allows all the other paths we use to be relative to the Android.mk file, - # as the Android build system expects. - root_makefile.write('\nLOCAL_PATH := $(call my-dir)\n') - - # Find the list of targets that derive from the gyp file(s) being built. - needed_targets = set() - for build_file in params['build_files']: - for target in gyp.common.AllTargets(target_list, target_dicts, build_file): - needed_targets.add(target) - - build_files = set() - include_list = set() - android_modules = {} - for qualified_target in target_list: - build_file, target, toolset = gyp.common.ParseQualifiedTarget( - qualified_target) - relative_build_file = gyp.common.RelativePath(build_file, - options.toplevel_dir) - build_files.add(relative_build_file) - included_files = data[build_file]['included_files'] - for included_file in included_files: - # The included_files entries are relative to the dir of the build file - # that included them, so we have to undo that and then make them relative - # to the root dir. - relative_include_file = gyp.common.RelativePath( - gyp.common.UnrelativePath(included_file, build_file), - options.toplevel_dir) - abs_include_file = os.path.abspath(relative_include_file) - # If the include file is from the ~/.gyp dir, we should use absolute path - # so that relocating the src dir doesn't break the path. - if (params['home_dot_gyp'] and - abs_include_file.startswith(params['home_dot_gyp'])): - build_files.add(abs_include_file) - else: - build_files.add(relative_include_file) - - base_path, output_file = CalculateMakefilePath(build_file, - target + '.' + toolset + options.suffix + '.mk') - - spec = target_dicts[qualified_target] - configs = spec['configurations'] - - part_of_all = (qualified_target in needed_targets and - not int(spec.get('suppress_wildcard', False))) - if limit_to_target_all and not part_of_all: - continue - - relative_target = gyp.common.QualifiedTarget(relative_build_file, target, - toolset) - writer = AndroidMkWriter(android_top_dir) - android_module = writer.Write(qualified_target, relative_target, base_path, - output_file, spec, configs, - part_of_all=part_of_all) - if android_module in android_modules: - print ('ERROR: Android module names must be unique. The following ' - 'targets both generate Android module name %s.\n %s\n %s' % - (android_module, android_modules[android_module], - qualified_target)) - return - android_modules[android_module] = qualified_target - - # Our root_makefile lives at the source root. Compute the relative path - # from there to the output_file for including. - mkfile_rel_path = gyp.common.RelativePath(output_file, - os.path.dirname(makefile_path)) - include_list.add(mkfile_rel_path) - - root_makefile.write('GYP_CONFIGURATION ?= %s\n' % default_configuration) - - # Write out the sorted list of includes. - root_makefile.write('\n') - for include_file in sorted(include_list): - root_makefile.write('include $(LOCAL_PATH)/' + include_file + '\n') - root_makefile.write('\n') - - root_makefile.write(SHARED_FOOTER) - - root_makefile.close() diff --git a/third_party/gyp/generator/cmake.py b/third_party/gyp/generator/cmake.py index 10d015ee..a2b96291 100644 --- a/third_party/gyp/generator/cmake.py +++ b/third_party/gyp/generator/cmake.py @@ -34,6 +34,7 @@ import signal import string import subprocess import gyp.common +import gyp.xcode_emulation generator_default_variables = { 'EXECUTABLE_PREFIX': '', @@ -55,7 +56,7 @@ generator_default_variables = { 'CONFIGURATION_NAME': '${configuration}', } -FULL_PATH_VARS = ('${CMAKE_SOURCE_DIR}', '${builddir}', '${obj}') +FULL_PATH_VARS = ('${CMAKE_CURRENT_LIST_DIR}', '${builddir}', '${obj}') generator_supports_multiple_toolsets = True generator_wants_static_library_dependencies_adjusted = True @@ -103,7 +104,7 @@ def NormjoinPathForceCMakeSource(base_path, rel_path): if any([rel_path.startswith(var) for var in FULL_PATH_VARS]): return rel_path # TODO: do we need to check base_path for absolute variables as well? - return os.path.join('${CMAKE_SOURCE_DIR}', + return os.path.join('${CMAKE_CURRENT_LIST_DIR}', os.path.normpath(os.path.join(base_path, rel_path))) @@ -150,20 +151,17 @@ def SetFileProperty(output, source_name, property_name, values, sep): output.write('")\n') -def SetFilesProperty(output, source_names, property_name, values, sep): +def SetFilesProperty(output, variable, property_name, values, sep): """Given a set of source files, sets the given property on them.""" - output.write('set_source_files_properties(\n') - for source_name in source_names: - output.write(' ') - output.write(source_name) - output.write('\n') - output.write(' PROPERTIES\n ') + output.write('set_source_files_properties(') + WriteVariable(output, variable) + output.write(' PROPERTIES ') output.write(property_name) output.write(' "') for value in values: output.write(CMakeStringEscape(value)) output.write(sep) - output.write('"\n)\n') + output.write('")\n') def SetTargetProperty(output, target_name, property_name, values, sep=''): @@ -216,7 +214,7 @@ def WriteVariable(output, variable_name, prepend=None): output.write('}') -class CMakeTargetType: +class CMakeTargetType(object): def __init__(self, command, modifier, property_modifier): self.command = command self.modifier = modifier @@ -236,11 +234,11 @@ def StringToCMakeTargetName(a): """Converts the given string 'a' to a valid CMake target name. All invalid characters are replaced by '_'. - Invalid for cmake: ' ', '/', '(', ')' + Invalid for cmake: ' ', '/', '(', ')', '"' Invalid for make: ':' Invalid for unknown reasons but cause failures: '.' """ - return a.translate(string.maketrans(' /():.', '______')) + return a.translate(string.maketrans(' /():."', '_______')) def WriteActions(target_name, actions, extra_sources, extra_deps, @@ -296,7 +294,7 @@ def WriteActions(target_name, actions, extra_sources, extra_deps, WriteVariable(output, inputs_name) output.write('\n') - output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') + output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') output.write(path_to_gyp) output.write('\n') @@ -401,9 +399,9 @@ def WriteRules(target_name, rules, extra_sources, extra_deps, output.write(NormjoinPath(path_to_gyp, rule_source)) output.write('\n') - # CMAKE_SOURCE_DIR is where the CMakeLists.txt lives. + # CMAKE_CURRENT_LIST_DIR is where the CMakeLists.txt lives. # The cwd is the current build directory. - output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') + output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') output.write(path_to_gyp) output.write('\n') @@ -464,7 +462,7 @@ def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output): extra_deps.append(copy_name) return - class Copy: + class Copy(object): def __init__(self, ext, command): self.cmake_inputs = [] self.cmake_outputs = [] @@ -488,7 +486,7 @@ def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output): copy = file_copy if os.path.basename(src) else dir_copy - copy.cmake_inputs.append(NormjoinPath(path_to_gyp, src)) + copy.cmake_inputs.append(NormjoinPathForceCMakeSource(path_to_gyp, src)) copy.cmake_outputs.append(NormjoinPathForceCMakeSource(path_to_gyp, dst)) copy.gyp_inputs.append(src) copy.gyp_outputs.append(dst) @@ -525,7 +523,7 @@ def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output): WriteVariable(output, copy.inputs_name, ' ') output.write('\n') - output.write('WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') + output.write('WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') output.write(path_to_gyp) output.write('\n') @@ -611,8 +609,8 @@ class CMakeNamer(object): def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, - options, generator_flags, all_qualified_targets, output): - + options, generator_flags, all_qualified_targets, flavor, + output): # The make generator does this always. # TODO: It would be nice to be able to tell CMake all dependencies. circular_libs = generator_flags.get('circular', True) @@ -636,10 +634,20 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, spec = target_dicts.get(qualified_target, {}) config = spec.get('configurations', {}).get(config_to_use, {}) + xcode_settings = None + if flavor == 'mac': + xcode_settings = gyp.xcode_emulation.XcodeSettings(spec) + target_name = spec.get('target_name', '') target_type = spec.get('type', '') target_toolset = spec.get('toolset') + cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type) + if cmake_target_type is None: + print ('Target %s has unknown target type %s, skipping.' % + ( target_name, target_type ) ) + return + SetVariable(output, 'TARGET', target_name) SetVariable(output, 'TOOLSET', target_toolset) @@ -667,27 +675,89 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, srcs = spec.get('sources', []) # Gyp separates the sheep from the goats based on file extensions. - def partition(l, p): - return reduce(lambda x, e: x[not p(e)].append(e) or x, l, ([], [])) - compilable_srcs, other_srcs = partition(srcs, Compilable) + # A full separation is done here because of flag handing (see below). + s_sources = [] + c_sources = [] + cxx_sources = [] + linkable_sources = [] + other_sources = [] + for src in srcs: + _, ext = os.path.splitext(src) + src_type = COMPILABLE_EXTENSIONS.get(ext, None) + src_norm_path = NormjoinPath(path_from_cmakelists_to_gyp, src); + + if src_type == 's': + s_sources.append(src_norm_path) + elif src_type == 'cc': + c_sources.append(src_norm_path) + elif src_type == 'cxx': + cxx_sources.append(src_norm_path) + elif Linkable(ext): + linkable_sources.append(src_norm_path) + else: + other_sources.append(src_norm_path) + + for extra_source in extra_sources: + src, real_source = extra_source + _, ext = os.path.splitext(real_source) + src_type = COMPILABLE_EXTENSIONS.get(ext, None) + + if src_type == 's': + s_sources.append(src) + elif src_type == 'cc': + c_sources.append(src) + elif src_type == 'cxx': + cxx_sources.append(src) + elif Linkable(ext): + linkable_sources.append(src) + else: + other_sources.append(src) + + s_sources_name = None + if s_sources: + s_sources_name = cmake_target_name + '__asm_srcs' + SetVariableList(output, s_sources_name, s_sources) + + c_sources_name = None + if c_sources: + c_sources_name = cmake_target_name + '__c_srcs' + SetVariableList(output, c_sources_name, c_sources) + + cxx_sources_name = None + if cxx_sources: + cxx_sources_name = cmake_target_name + '__cxx_srcs' + SetVariableList(output, cxx_sources_name, cxx_sources) + + linkable_sources_name = None + if linkable_sources: + linkable_sources_name = cmake_target_name + '__linkable_srcs' + SetVariableList(output, linkable_sources_name, linkable_sources) + + other_sources_name = None + if other_sources: + other_sources_name = cmake_target_name + '__other_srcs' + SetVariableList(output, other_sources_name, other_sources) # CMake gets upset when executable targets provide no sources. - if target_type == 'executable' and not compilable_srcs and not extra_sources: - print ('Executable %s has no complilable sources, treating as "none".' % - target_name ) - target_type = 'none' + # http://www.cmake.org/pipermail/cmake/2010-July/038461.html + dummy_sources_name = None + has_sources = (s_sources_name or + c_sources_name or + cxx_sources_name or + linkable_sources_name or + other_sources_name) + if target_type == 'executable' and not has_sources: + dummy_sources_name = cmake_target_name + '__dummy_srcs' + SetVariable(output, dummy_sources_name, + "${obj}.${TOOLSET}/${TARGET}/genc/dummy.c") + output.write('if(NOT EXISTS "') + WriteVariable(output, dummy_sources_name) + output.write('")\n') + output.write(' file(WRITE "') + WriteVariable(output, dummy_sources_name) + output.write('" "")\n') + output.write("endif()\n") - cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type) - if cmake_target_type is None: - print ('Target %s has unknown target type %s, skipping.' % - ( target_name, target_type ) ) - return - - other_srcs_name = None - if other_srcs: - other_srcs_name = cmake_target_name + '__other_srcs' - SetVariableList(output, other_srcs_name, - [NormjoinPath(path_from_cmakelists_to_gyp, src) for src in other_srcs]) # CMake is opposed to setting linker directories and considers the practice # of setting linker directories dangerous. Instead, it favors the use of @@ -713,37 +783,54 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, output.write(' ') output.write(cmake_target_type.modifier) - if other_srcs_name: - WriteVariable(output, other_srcs_name, ' ') - - output.write('\n') - - for src in compilable_srcs: - output.write(' ') - output.write(NormjoinPath(path_from_cmakelists_to_gyp, src)) - output.write('\n') - for extra_source in extra_sources: - output.write(' ') - src, _ = extra_source - output.write(NormjoinPath(path_from_cmakelists_to_gyp, src)) - output.write('\n') + if s_sources_name: + WriteVariable(output, s_sources_name, ' ') + if c_sources_name: + WriteVariable(output, c_sources_name, ' ') + if cxx_sources_name: + WriteVariable(output, cxx_sources_name, ' ') + if linkable_sources_name: + WriteVariable(output, linkable_sources_name, ' ') + if other_sources_name: + WriteVariable(output, other_sources_name, ' ') + if dummy_sources_name: + WriteVariable(output, dummy_sources_name, ' ') output.write(')\n') + # Let CMake know if the 'all' target should depend on this target. + exclude_from_all = ('TRUE' if qualified_target not in all_qualified_targets + else 'FALSE') + SetTargetProperty(output, cmake_target_name, + 'EXCLUDE_FROM_ALL', exclude_from_all) + for extra_target_name in extra_deps: + SetTargetProperty(output, extra_target_name, + 'EXCLUDE_FROM_ALL', exclude_from_all) + # Output name and location. if target_type != 'none': + # Link as 'C' if there are no other files + if not c_sources and not cxx_sources: + SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C']) + # Mark uncompiled sources as uncompiled. - if other_srcs_name: + if other_sources_name: output.write('set_source_files_properties(') - WriteVariable(output, other_srcs_name, '') + WriteVariable(output, other_sources_name, '') output.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n') + # Mark object sources as linkable. + if linkable_sources_name: + output.write('set_source_files_properties(') + WriteVariable(output, other_sources_name, '') + output.write(' PROPERTIES EXTERNAL_OBJECT "TRUE")\n') + # Output directory target_output_directory = spec.get('product_dir') if target_output_directory is None: if target_type in ('executable', 'loadable_module'): target_output_directory = generator_default_variables['PRODUCT_DIR'] - elif target_type in ('shared_library'): + elif target_type == 'shared_library': target_output_directory = '${builddir}/lib.${TOOLSET}' elif spec.get('standalone_static_library', False): target_output_directory = generator_default_variables['PRODUCT_DIR'] @@ -804,122 +891,98 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, cmake_target_output_basename) SetFileProperty(output, cmake_target_output, 'GENERATED', ['TRUE'], '') - # Let CMake know if the 'all' target should depend on this target. - exclude_from_all = ('TRUE' if qualified_target not in all_qualified_targets - else 'FALSE') - SetTargetProperty(output, cmake_target_name, - 'EXCLUDE_FROM_ALL', exclude_from_all) - for extra_target_name in extra_deps: - SetTargetProperty(output, extra_target_name, - 'EXCLUDE_FROM_ALL', exclude_from_all) + # Includes + includes = config.get('include_dirs') + if includes: + # This (target include directories) is what requires CMake 2.8.8 + includes_name = cmake_target_name + '__include_dirs' + SetVariableList(output, includes_name, + [NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include) + for include in includes]) + output.write('set_property(TARGET ') + output.write(cmake_target_name) + output.write(' APPEND PROPERTY INCLUDE_DIRECTORIES ') + WriteVariable(output, includes_name, '') + output.write(')\n') - # Includes - includes = config.get('include_dirs') - if includes: - # This (target include directories) is what requires CMake 2.8.8 - includes_name = cmake_target_name + '__include_dirs' - SetVariableList(output, includes_name, - [NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include) - for include in includes]) - output.write('set_property(TARGET ') - output.write(cmake_target_name) - output.write(' APPEND PROPERTY INCLUDE_DIRECTORIES ') - WriteVariable(output, includes_name, '') - output.write(')\n') - - # Defines - defines = config.get('defines') - if defines is not None: - SetTargetProperty(output, + # Defines + defines = config.get('defines') + if defines is not None: + SetTargetProperty(output, cmake_target_name, 'COMPILE_DEFINITIONS', defines, ';') - # Compile Flags - http://www.cmake.org/Bug/view.php?id=6493 - # CMake currently does not have target C and CXX flags. - # So, instead of doing... + # Compile Flags - http://www.cmake.org/Bug/view.php?id=6493 + # CMake currently does not have target C and CXX flags. + # So, instead of doing... - # cflags_c = config.get('cflags_c') - # if cflags_c is not None: - # SetTargetProperty(output, cmake_target_name, - # 'C_COMPILE_FLAGS', cflags_c, ' ') + # cflags_c = config.get('cflags_c') + # if cflags_c is not None: + # SetTargetProperty(output, cmake_target_name, + # 'C_COMPILE_FLAGS', cflags_c, ' ') - # cflags_cc = config.get('cflags_cc') - # if cflags_cc is not None: - # SetTargetProperty(output, cmake_target_name, - # 'CXX_COMPILE_FLAGS', cflags_cc, ' ') + # cflags_cc = config.get('cflags_cc') + # if cflags_cc is not None: + # SetTargetProperty(output, cmake_target_name, + # 'CXX_COMPILE_FLAGS', cflags_cc, ' ') - # Instead we must... - s_sources = [] - c_sources = [] - cxx_sources = [] - for src in srcs: - _, ext = os.path.splitext(src) - src_type = COMPILABLE_EXTENSIONS.get(ext, None) + # Instead we must... + cflags = config.get('cflags', []) + cflags_c = config.get('cflags_c', []) + cflags_cxx = config.get('cflags_cc', []) + if xcode_settings: + cflags = xcode_settings.GetCflags(config_to_use) + cflags_c = xcode_settings.GetCflagsC(config_to_use) + cflags_cxx = xcode_settings.GetCflagsCC(config_to_use) + #cflags_objc = xcode_settings.GetCflagsObjC(config_to_use) + #cflags_objcc = xcode_settings.GetCflagsObjCC(config_to_use) - if src_type == 's': - s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) + if (not cflags_c or not c_sources) and (not cflags_cxx or not cxx_sources): + SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', cflags, ' ') - if src_type == 'cc': - c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - if src_type == 'cxx': - cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - for extra_source in extra_sources: - src, real_source = extra_source - _, ext = os.path.splitext(real_source) - src_type = COMPILABLE_EXTENSIONS.get(ext, None) - - if src_type == 's': - s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - if src_type == 'cc': - c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - if src_type == 'cxx': - cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - cflags = config.get('cflags', []) - cflags_c = config.get('cflags_c', []) - cflags_cxx = config.get('cflags_cc', []) - if c_sources and not (s_sources or cxx_sources): - flags = [] - flags.extend(cflags) - flags.extend(cflags_c) - SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') - - elif cxx_sources and not (s_sources or c_sources): - flags = [] - flags.extend(cflags) - flags.extend(cflags_cxx) - SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') - - else: - if s_sources and cflags: - SetFilesProperty(output, s_sources, 'COMPILE_FLAGS', cflags, ' ') - - if c_sources and (cflags or cflags_c): + elif c_sources and not (s_sources or cxx_sources): flags = [] flags.extend(cflags) flags.extend(cflags_c) - SetFilesProperty(output, c_sources, 'COMPILE_FLAGS', flags, ' ') + SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') - if cxx_sources and (cflags or cflags_cxx): + elif cxx_sources and not (s_sources or c_sources): flags = [] flags.extend(cflags) flags.extend(cflags_cxx) - SetFilesProperty(output, cxx_sources, 'COMPILE_FLAGS', flags, ' ') + SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') - # Have assembly link as c if there are no other files - if not c_sources and not cxx_sources and s_sources: - SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C']) + else: + # TODO: This is broken, one cannot generally set properties on files, + # as other targets may require different properties on the same files. + if s_sources and cflags: + SetFilesProperty(output, s_sources_name, 'COMPILE_FLAGS', cflags, ' ') - # Linker flags - ldflags = config.get('ldflags') - if ldflags is not None: - SetTargetProperty(output, cmake_target_name, 'LINK_FLAGS', ldflags, ' ') + if c_sources and (cflags or cflags_c): + flags = [] + flags.extend(cflags) + flags.extend(cflags_c) + SetFilesProperty(output, c_sources_name, 'COMPILE_FLAGS', flags, ' ') + + if cxx_sources and (cflags or cflags_cxx): + flags = [] + flags.extend(cflags) + flags.extend(cflags_cxx) + SetFilesProperty(output, cxx_sources_name, 'COMPILE_FLAGS', flags, ' ') + + # Linker flags + ldflags = config.get('ldflags') + if ldflags is not None: + SetTargetProperty(output, cmake_target_name, 'LINK_FLAGS', ldflags, ' ') + + # XCode settings + xcode_settings = config.get('xcode_settings', {}) + for xcode_setting, xcode_value in xcode_settings.viewitems(): + SetTargetProperty(output, cmake_target_name, + "XCODE_ATTRIBUTE_%s" % xcode_setting, xcode_value, + '' if isinstance(xcode_value, str) else ' ') # Note on Dependencies and Libraries: # CMake wants to handle link order, resolving the link line up front. @@ -985,7 +1048,7 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, output.write(cmake_target_name) output.write('\n') if static_deps: - write_group = circular_libs and len(static_deps) > 1 + write_group = circular_libs and len(static_deps) > 1 and flavor != 'mac' if write_group: output.write('-Wl,--start-group\n') for dep in gyp.common.uniquer(static_deps): @@ -1001,9 +1064,9 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, output.write('\n') if external_libs: for lib in gyp.common.uniquer(external_libs): - output.write(' ') - output.write(lib) - output.write('\n') + output.write(' "') + output.write(RemovePrefix(lib, "$(SDKROOT)")) + output.write('"\n') output.write(')\n') @@ -1015,6 +1078,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_to_use): options = params['options'] generator_flags = params['generator_flags'] + flavor = gyp.common.GetFlavor(params) # generator_dir: relative path from pwd to where make puts build files. # Makes migrating from make to cmake easier, cmake doesn't put anything here. @@ -1040,20 +1104,49 @@ def GenerateOutputForConfig(target_list, target_dicts, data, output.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n') output.write('cmake_policy(VERSION 2.8.8)\n') - _, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1]) + gyp_file, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1]) output.write('project(') output.write(project_target) output.write(')\n') SetVariable(output, 'configuration', config_to_use) + ar = None + cc = None + cxx = None + + make_global_settings = data[gyp_file].get('make_global_settings', []) + build_to_top = gyp.common.InvertRelativePath(build_dir, + options.toplevel_dir) + for key, value in make_global_settings: + if key == 'AR': + ar = os.path.join(build_to_top, value) + if key == 'CC': + cc = os.path.join(build_to_top, value) + if key == 'CXX': + cxx = os.path.join(build_to_top, value) + + ar = gyp.common.GetEnvironFallback(['AR_target', 'AR'], ar) + cc = gyp.common.GetEnvironFallback(['CC_target', 'CC'], cc) + cxx = gyp.common.GetEnvironFallback(['CXX_target', 'CXX'], cxx) + + if ar: + SetVariable(output, 'CMAKE_AR', ar) + if cc: + SetVariable(output, 'CMAKE_C_COMPILER', cc) + if cxx: + SetVariable(output, 'CMAKE_CXX_COMPILER', cxx) + # The following appears to be as-yet undocumented. # http://public.kitware.com/Bug/view.php?id=8392 output.write('enable_language(ASM)\n') # ASM-ATT does not support .S files. # output.write('enable_language(ASM-ATT)\n') - SetVariable(output, 'builddir', '${CMAKE_BINARY_DIR}') + if cc: + SetVariable(output, 'CMAKE_ASM_COMPILER', cc) + + SetVariable(output, 'builddir', '${CMAKE_CURRENT_BINARY_DIR}') SetVariable(output, 'obj', '${builddir}/obj') output.write('\n') @@ -1066,6 +1159,13 @@ def GenerateOutputForConfig(target_list, target_dicts, data, output.write('set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)\n') output.write('\n') + # Force ninja to use rsp files. Otherwise link and ar lines can get too long, + # resulting in 'Argument list too long' errors. + # However, rsp files don't work correctly on Mac. + if flavor != 'mac': + output.write('set(CMAKE_NINJA_FORCE_RESPONSE_FILE 1)\n') + output.write('\n') + namer = CMakeNamer(target_list) # The list of targets upon which the 'all' target should depend. @@ -1078,8 +1178,13 @@ def GenerateOutputForConfig(target_list, target_dicts, data, all_qualified_targets.add(qualified_target) for qualified_target in target_list: + if flavor == 'mac': + gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target) + spec = target_dicts[qualified_target] + gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[gyp_file], spec) + WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, - options, generator_flags, all_qualified_targets, output) + options, generator_flags, all_qualified_targets, flavor, output) output.close() diff --git a/third_party/gyp/generator/dump_dependency_json.py b/third_party/gyp/generator/dump_dependency_json.py index 927ba6eb..160eafe2 100644 --- a/third_party/gyp/generator/dump_dependency_json.py +++ b/third_party/gyp/generator/dump_dependency_json.py @@ -14,6 +14,9 @@ generator_supports_multiple_toolsets = True generator_wants_static_library_dependencies_adjusted = False +generator_filelist_paths = { +} + generator_default_variables = { } for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR', @@ -56,6 +59,17 @@ def CalculateGeneratorInputInfo(params): global generator_wants_static_library_dependencies_adjusted generator_wants_static_library_dependencies_adjusted = True + toplevel = params['options'].toplevel_dir + generator_dir = os.path.relpath(params['options'].generator_output or '.') + # output_dir: relative path from generator_dir to the build directory. + output_dir = generator_flags.get('output_dir', 'out') + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, generator_dir, output_dir, 'gypfiles')) + global generator_filelist_paths + generator_filelist_paths = { + 'toplevel': toplevel, + 'qualified_out_dir': qualified_out_dir, + } def GenerateOutput(target_list, target_dicts, data, params): # Map of target -> list of targets it depends on. @@ -74,7 +88,11 @@ def GenerateOutput(target_list, target_dicts, data, params): edges[target].append(dep) targets_to_visit.append(dep) - filename = 'dump.json' + try: + filepath = params['generator_flags']['output_dir'] + except KeyError: + filepath = '.' + filename = os.path.join(filepath, 'dump.json') f = open(filename, 'w') json.dump(edges, f) f.close() diff --git a/third_party/gyp/generator/eclipse.py b/third_party/gyp/generator/eclipse.py index 8d08f57e..3544347b 100644 --- a/third_party/gyp/generator/eclipse.py +++ b/third_party/gyp/generator/eclipse.py @@ -24,6 +24,7 @@ import gyp import gyp.common import gyp.msvs_emulation import shlex +import xml.etree.cElementTree as ET generator_wants_static_library_dependencies_adjusted = False @@ -31,8 +32,8 @@ generator_default_variables = { } for dirname in ['INTERMEDIATE_DIR', 'PRODUCT_DIR', 'LIB_DIR', 'SHARED_LIB_DIR']: - # Some gyp steps fail if these are empty(!). - generator_default_variables[dirname] = 'dir' + # Some gyp steps fail if these are empty(!), so we convert them to variables + generator_default_variables[dirname] = '$' + dirname for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT', @@ -165,7 +166,7 @@ def GetAllIncludeDirectories(target_list, target_dicts, return all_includes_list -def GetCompilerPath(target_list, data): +def GetCompilerPath(target_list, data, options): """Determine a command that can be used to invoke the compiler. Returns: @@ -173,13 +174,12 @@ def GetCompilerPath(target_list, data): the compiler from that. Otherwise, see if a compiler was specified via the CC_target environment variable. """ - # First, see if the compiler is configured in make's settings. build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) make_global_settings_dict = data[build_file].get('make_global_settings', {}) for key, value in make_global_settings_dict: if key in ['CC', 'CXX']: - return value + return os.path.join(options.toplevel_dir, value) # Check to see if the compiler was specified as an environment variable. for key in ['CC_target', 'CC', 'CXX']: @@ -295,33 +295,123 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, shared_intermediate_dirs = [os.path.join(toplevel_build, 'obj', 'gen'), os.path.join(toplevel_build, 'gen')] - out_name = os.path.join(toplevel_build, 'eclipse-cdt-settings.xml') + GenerateCdtSettingsFile(target_list, + target_dicts, + data, + params, + config_name, + os.path.join(toplevel_build, + 'eclipse-cdt-settings.xml'), + options, + shared_intermediate_dirs) + GenerateClasspathFile(target_list, + target_dicts, + options.toplevel_dir, + toplevel_build, + os.path.join(toplevel_build, + 'eclipse-classpath.xml')) + + +def GenerateCdtSettingsFile(target_list, target_dicts, data, params, + config_name, out_name, options, + shared_intermediate_dirs): gyp.common.EnsureDirExists(out_name) - out = open(out_name, 'w') + with open(out_name, 'w') as out: + out.write('\n') + out.write('\n') - out.write('\n') - out.write('\n') + eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File', + 'GNU C++', 'GNU C', 'Assembly'] + compiler_path = GetCompilerPath(target_list, data, options) + include_dirs = GetAllIncludeDirectories(target_list, target_dicts, + shared_intermediate_dirs, + config_name, params, compiler_path) + WriteIncludePaths(out, eclipse_langs, include_dirs) + defines = GetAllDefines(target_list, target_dicts, data, config_name, + params, compiler_path) + WriteMacros(out, eclipse_langs, defines) - eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File', - 'GNU C++', 'GNU C', 'Assembly'] - compiler_path = GetCompilerPath(target_list, data) - include_dirs = GetAllIncludeDirectories(target_list, target_dicts, - shared_intermediate_dirs, config_name, - params, compiler_path) - WriteIncludePaths(out, eclipse_langs, include_dirs) - defines = GetAllDefines(target_list, target_dicts, data, config_name, params, - compiler_path) - WriteMacros(out, eclipse_langs, defines) + out.write('\n') - out.write('\n') - out.close() + +def GenerateClasspathFile(target_list, target_dicts, toplevel_dir, + toplevel_build, out_name): + '''Generates a classpath file suitable for symbol navigation and code + completion of Java code (such as in Android projects) by finding all + .java and .jar files used as action inputs.''' + gyp.common.EnsureDirExists(out_name) + result = ET.Element('classpath') + + def AddElements(kind, paths): + # First, we need to normalize the paths so they are all relative to the + # toplevel dir. + rel_paths = set() + for path in paths: + if os.path.isabs(path): + rel_paths.add(os.path.relpath(path, toplevel_dir)) + else: + rel_paths.add(path) + + for path in sorted(rel_paths): + entry_element = ET.SubElement(result, 'classpathentry') + entry_element.set('kind', kind) + entry_element.set('path', path) + + AddElements('lib', GetJavaJars(target_list, target_dicts, toplevel_dir)) + AddElements('src', GetJavaSourceDirs(target_list, target_dicts, toplevel_dir)) + # Include the standard JRE container and a dummy out folder + AddElements('con', ['org.eclipse.jdt.launching.JRE_CONTAINER']) + # Include a dummy out folder so that Eclipse doesn't use the default /bin + # folder in the root of the project. + AddElements('output', [os.path.join(toplevel_build, '.eclipse-java-build')]) + + ET.ElementTree(result).write(out_name) + + +def GetJavaJars(target_list, target_dicts, toplevel_dir): + '''Generates a sequence of all .jars used as inputs.''' + for target_name in target_list: + target = target_dicts[target_name] + for action in target.get('actions', []): + for input_ in action['inputs']: + if os.path.splitext(input_)[1] == '.jar' and not input_.startswith('$'): + if os.path.isabs(input_): + yield input_ + else: + yield os.path.join(os.path.dirname(target_name), input_) + + +def GetJavaSourceDirs(target_list, target_dicts, toplevel_dir): + '''Generates a sequence of all likely java package root directories.''' + for target_name in target_list: + target = target_dicts[target_name] + for action in target.get('actions', []): + for input_ in action['inputs']: + if (os.path.splitext(input_)[1] == '.java' and + not input_.startswith('$')): + dir_ = os.path.dirname(os.path.join(os.path.dirname(target_name), + input_)) + # If there is a parent 'src' or 'java' folder, navigate up to it - + # these are canonical package root names in Chromium. This will + # break if 'src' or 'java' exists in the package structure. This + # could be further improved by inspecting the java file for the + # package name if this proves to be too fragile in practice. + parent_search = dir_ + while os.path.basename(parent_search) not in ['src', 'java']: + parent_search, _ = os.path.split(parent_search) + if not parent_search or parent_search == toplevel_dir: + # Didn't find a known root, just return the original path + yield dir_ + break + else: + yield parent_search def GenerateOutput(target_list, target_dicts, data, params): """Generate an XML settings file that can be imported into a CDT project.""" if params['options'].generator_output: - raise NotImplementedError, "--generator_output not implemented for eclipse" + raise NotImplementedError("--generator_output not implemented for eclipse") user_config = params.get('generator_flags', {}).get('config', None) if user_config: diff --git a/third_party/gyp/generator/gypd.py b/third_party/gyp/generator/gypd.py index 22ef57f8..3efdb996 100644 --- a/third_party/gyp/generator/gypd.py +++ b/third_party/gyp/generator/gypd.py @@ -39,9 +39,11 @@ import pprint # These variables should just be spit back out as variable references. _generator_identity_variables = [ + 'CONFIGURATION_NAME', 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX', 'INTERMEDIATE_DIR', + 'LIB_DIR', 'PRODUCT_DIR', 'RULE_INPUT_ROOT', 'RULE_INPUT_DIRNAME', @@ -49,6 +51,11 @@ _generator_identity_variables = [ 'RULE_INPUT_NAME', 'RULE_INPUT_PATH', 'SHARED_INTERMEDIATE_DIR', + 'SHARED_LIB_DIR', + 'SHARED_LIB_PREFIX', + 'SHARED_LIB_SUFFIX', + 'STATIC_LIB_PREFIX', + 'STATIC_LIB_SUFFIX', ] # gypd doesn't define a default value for OS like many other generator diff --git a/third_party/gyp/generator/make.py b/third_party/gyp/generator/make.py index b88a433d..b7da768f 100644 --- a/third_party/gyp/generator/make.py +++ b/third_party/gyp/generator/make.py @@ -29,6 +29,7 @@ import gyp import gyp.common import gyp.xcode_emulation from gyp.common import GetEnvironFallback +from gyp.common import GypError generator_default_variables = { 'EXECUTABLE_PREFIX': '', @@ -210,10 +211,10 @@ cmd_solink_module_host = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(T LINK_COMMANDS_AIX = """\ quiet_cmd_alink = AR($(TOOLSET)) $@ -cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^) +cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) -X32_64 crs $@ $(filter %.o,$^) quiet_cmd_alink_thin = AR($(TOOLSET)) $@ -cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^) +cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) -X32_64 crs $@ $(filter %.o,$^) quiet_cmd_link = LINK($(TOOLSET)) $@ cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(LD_INPUTS) $(LIBS) @@ -272,30 +273,22 @@ all_deps := %(make_global_settings)s CC.target ?= %(CC.target)s -CFLAGS.target ?= $(CFLAGS) +CFLAGS.target ?= $(CPPFLAGS) $(CFLAGS) CXX.target ?= %(CXX.target)s -CXXFLAGS.target ?= $(CXXFLAGS) +CXXFLAGS.target ?= $(CPPFLAGS) $(CXXFLAGS) LINK.target ?= %(LINK.target)s LDFLAGS.target ?= $(LDFLAGS) AR.target ?= $(AR) # C++ apps need to be linked with g++. -# -# Note: flock is used to seralize linking. Linking is a memory-intensive -# process so running parallel links can often lead to thrashing. To disable -# the serialization, override LINK via an envrionment variable as follows: -# -# export LINK=g++ -# -# This will allow make to invoke N linker processes as specified in -jN. -LINK ?= %(flock)s $(builddir)/linker.lock $(CXX.target) +LINK ?= $(CXX.target) # TODO(evan): move all cross-compilation logic to gyp-time so we don't need # to replicate this environment fallback in make as well. CC.host ?= %(CC.host)s -CFLAGS.host ?= +CFLAGS.host ?= $(CPPFLAGS_host) $(CFLAGS_host) CXX.host ?= %(CXX.host)s -CXXFLAGS.host ?= +CXXFLAGS.host ?= $(CPPFLAGS_host) $(CXXFLAGS_host) LINK.host ?= %(LINK.host)s LDFLAGS.host ?= AR.host ?= %(AR.host)s @@ -372,7 +365,7 @@ cmd_touch = touch $@ quiet_cmd_copy = COPY $@ # send stderr to /dev/null to ignore messages when linking directories. -cmd_copy = ln -f "$<" "$@" 2>/dev/null || (rm -rf "$@" && cp -af "$<" "$@") +cmd_copy = ln -f "$<" "$@" 2>/dev/null || (rm -rf "$@" && cp %(copy_archive_args)s "$<" "$@") %(link_commands)s """ @@ -631,6 +624,38 @@ def QuoteSpaces(s, quote=r'\ '): return s.replace(' ', quote) +# TODO: Avoid code duplication with _ValidateSourcesForMSVSProject in msvs.py. +def _ValidateSourcesForOSX(spec, all_sources): + """Makes sure if duplicate basenames are not specified in the source list. + + Arguments: + spec: The target dictionary containing the properties of the target. + """ + if spec.get('type', None) != 'static_library': + return + + basenames = {} + for source in all_sources: + name, ext = os.path.splitext(source) + is_compiled_file = ext in [ + '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S'] + if not is_compiled_file: + continue + basename = os.path.basename(name) # Don't include extension. + basenames.setdefault(basename, []).append(source) + + error = '' + for basename, files in basenames.iteritems(): + if len(files) > 1: + error += ' %s: %s\n' % (basename, ' '.join(files)) + + if error: + print('static library %s has several files with the same basename:\n' % + spec['target_name'] + error + 'libtool on OS X will generate' + + ' warnings for them.') + raise GypError('Duplicate basenames in sources section, see list above') + + # Map from qualified target to path to output. target_outputs = {} # Map from qualified target to any linkable output. A subset @@ -640,7 +665,7 @@ target_outputs = {} target_link_deps = {} -class MakefileWriter: +class MakefileWriter(object): """MakefileWriter packages up the writing of one target-specific foobar.mk. Its only real entry point is Write(), and is mostly used for namespacing. @@ -758,6 +783,10 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # Sources. all_sources = spec.get('sources', []) + extra_sources if all_sources: + if self.flavor == 'mac': + # libtool on OS X generates warnings for duplicate basenames in the same + # target. + _ValidateSourcesForOSX(spec, all_sources) self.WriteSources( configs, deps, all_sources, extra_outputs, extra_link_deps, part_of_all, @@ -990,7 +1019,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # accidentally writing duplicate dummy rules for those outputs. self.WriteLn('%s: obj := $(abs_obj)' % outputs[0]) self.WriteLn('%s: builddir := $(abs_builddir)' % outputs[0]) - self.WriteMakeRule(outputs, inputs + ['FORCE_DO_CMD'], actions) + self.WriteMakeRule(outputs, inputs, actions, + command="%s_%d" % (name, count)) # Spaces in rule filenames are not supported, but rule variables have # spaces in them (e.g. RULE_INPUT_PATH expands to '$(abspath $<)'). # The spaces within the variables are valid, so remove the variables @@ -1101,9 +1131,12 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD for output, res in gyp.xcode_emulation.GetMacBundleResources( generator_default_variables['PRODUCT_DIR'], self.xcode_settings, map(Sourceify, map(self.Absolutify, resources))): - self.WriteDoCmd([output], [res], 'mac_tool,,,copy-bundle-resource', - part_of_all=True) - bundle_deps.append(output) + _, ext = os.path.splitext(output) + if ext != '.xcassets': + # Make does not supports '.xcassets' emulation. + self.WriteDoCmd([output], [res], 'mac_tool,,,copy-bundle-resource', + part_of_all=True) + bundle_deps.append(output) def WriteMacInfoPlist(self, bundle_deps): @@ -1546,7 +1579,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD for link_dep in link_deps: assert ' ' not in link_dep, ( "Spaces in alink input filenames not supported (%s)" % link_dep) - if (self.flavor not in ('mac', 'openbsd', 'win') and not + if (self.flavor not in ('mac', 'openbsd', 'netbsd', 'win') and not self.is_standalone_static_library): self.WriteDoCmd([self.output_binary], link_deps, 'alink_thin', part_of_all, postbuilds=postbuilds) @@ -1656,6 +1689,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.WriteMakeRule(outputs, inputs, actions = ['$(call do_cmd,%s%s)' % (command, suffix)], comment = comment, + command = command, force = True) # Add our outputs to the list of targets we read depfiles from. # all_deps is only used for deps file reading, and for deps files we replace @@ -1666,7 +1700,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD def WriteMakeRule(self, outputs, inputs, actions=None, comment=None, - order_only=False, force=False, phony=False): + order_only=False, force=False, phony=False, command=None): """Write a Makefile rule, with some extra tricks. outputs: a list of outputs for the rule (note: this is not directly @@ -1679,6 +1713,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD force: if true, include FORCE_DO_CMD as an order-only dep phony: if true, the rule does not actually generate the named output, the output is just a name to run the rule + command: (optional) command name to generate unambiguous labels """ outputs = map(QuoteSpaces, outputs) inputs = map(QuoteSpaces, inputs) @@ -1687,44 +1722,38 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.WriteLn('# ' + comment) if phony: self.WriteLn('.PHONY: ' + ' '.join(outputs)) - # TODO(evanm): just make order_only a list of deps instead of these hacks. - if order_only: - order_insert = '| ' - pick_output = ' '.join(outputs) - else: - order_insert = '' - pick_output = outputs[0] - if force: - force_append = ' FORCE_DO_CMD' - else: - force_append = '' if actions: self.WriteLn("%s: TOOLSET := $(TOOLSET)" % outputs[0]) - self.WriteLn('%s: %s%s%s' % (pick_output, order_insert, ' '.join(inputs), - force_append)) + force_append = ' FORCE_DO_CMD' if force else '' + + if order_only: + # Order only rule: Just write a simple rule. + # TODO(evanm): just make order_only a list of deps instead of this hack. + self.WriteLn('%s: | %s%s' % + (' '.join(outputs), ' '.join(inputs), force_append)) + elif len(outputs) == 1: + # Regular rule, one output: Just write a simple rule. + self.WriteLn('%s: %s%s' % (outputs[0], ' '.join(inputs), force_append)) + else: + # Regular rule, more than one output: Multiple outputs are tricky in + # make. We will write three rules: + # - All outputs depend on an intermediate file. + # - Make .INTERMEDIATE depend on the intermediate. + # - The intermediate file depends on the inputs and executes the + # actual command. + # - The intermediate recipe will 'touch' the intermediate file. + # - The multi-output rule will have an do-nothing recipe. + intermediate = "%s.intermediate" % (command if command else self.target) + self.WriteLn('%s: %s' % (' '.join(outputs), intermediate)) + self.WriteLn('\t%s' % '@:'); + self.WriteLn('%s: %s' % ('.INTERMEDIATE', intermediate)) + self.WriteLn('%s: %s%s' % + (intermediate, ' '.join(inputs), force_append)) + actions.insert(0, '$(call do_cmd,touch)') + if actions: for action in actions: self.WriteLn('\t%s' % action) - if not order_only and len(outputs) > 1: - # If we have more than one output, a rule like - # foo bar: baz - # that for *each* output we must run the action, potentially - # in parallel. That is not what we're trying to write -- what - # we want is that we run the action once and it generates all - # the files. - # http://www.gnu.org/software/hello/manual/automake/Multiple-Outputs.html - # discusses this problem and has this solution: - # 1) Write the naive rule that would produce parallel runs of - # the action. - # 2) Make the outputs seralized on each other, so we won't start - # a parallel run until the first run finishes, at which point - # we'll have generated all the outputs and we're done. - self.WriteLn('%s: %s' % (' '.join(outputs[1:]), outputs[0])) - # Add a dummy command to the "extra outputs" rule, otherwise make seems to - # think these outputs haven't (couldn't have?) changed, and thus doesn't - # flag them as changed (i.e. include in '$?') when evaluating dependent - # rules, which in turn causes do_cmd() to skip running dependent commands. - self.WriteLn('%s: ;' % (' '.join(outputs[1:]))) self.WriteLn() @@ -1981,6 +2010,7 @@ def GenerateOutput(target_list, target_dicts, data, params): srcdir_prefix = '$(srcdir)/' flock_command= 'flock' + copy_archive_arguments = '-af' header_params = { 'default_target': default_target, 'builddir': builddir_name, @@ -1990,6 +2020,7 @@ def GenerateOutput(target_list, target_dicts, data, params): 'link_commands': LINK_COMMANDS_LINUX, 'extra_commands': '', 'srcdir': srcdir, + 'copy_archive_args': copy_archive_arguments, } if flavor == 'mac': flock_command = './gyp-mac-tool flock' @@ -2013,8 +2044,15 @@ def GenerateOutput(target_list, target_dicts, data, params): header_params.update({ 'flock': 'lockf', }) - elif flavor == 'aix': + elif flavor == 'openbsd': + copy_archive_arguments = '-pPRf' header_params.update({ + 'copy_archive_args': copy_archive_arguments, + }) + elif flavor == 'aix': + copy_archive_arguments = '-pPRf' + header_params.update({ + 'copy_archive_args': copy_archive_arguments, 'link_commands': LINK_COMMANDS_AIX, 'flock': './gyp-flock-tool flock', 'flock_index': 2, @@ -2034,7 +2072,6 @@ def GenerateOutput(target_list, target_dicts, data, params): build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) make_global_settings_array = data[build_file].get('make_global_settings', []) wrappers = {} - wrappers['LINK'] = '%s $(builddir)/linker.lock' % flock_command for key, value in make_global_settings_array: if key.endswith('_wrapper'): wrappers[key[:-len('_wrapper')]] = '$(abspath %s)' % value diff --git a/third_party/gyp/generator/msvs.py b/third_party/gyp/generator/msvs.py index 9dcdab6c..e60c0256 100644 --- a/third_party/gyp/generator/msvs.py +++ b/third_party/gyp/generator/msvs.py @@ -2,7 +2,6 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import collections import copy import ntpath import os @@ -13,6 +12,7 @@ import sys import gyp.common import gyp.easy_xml as easy_xml +import gyp.generator.ninja as ninja_generator import gyp.MSVSNew as MSVSNew import gyp.MSVSProject as MSVSProject import gyp.MSVSSettings as MSVSSettings @@ -21,6 +21,7 @@ import gyp.MSVSUserFile as MSVSUserFile import gyp.MSVSUtil as MSVSUtil import gyp.MSVSVersion as MSVSVersion from gyp.common import GypError +from gyp.common import OrderedSet # TODO: Remove once bots are on 2.7, http://crbug.com/241769 def _import_OrderedDict(): @@ -41,7 +42,7 @@ OrderedDict = _import_OrderedDict() # if IncrediBuild is executed from inside Visual Studio. This regex # validates that the string looks like a GUID with all uppercase hex # letters. -VALID_MSVS_GUID_CHARS = re.compile('^[A-F0-9\-]+$') +VALID_MSVS_GUID_CHARS = re.compile(r'^[A-F0-9\-]+$') generator_default_variables = { @@ -81,8 +82,16 @@ generator_additional_non_configuration_keys = [ 'msvs_external_builder_out_dir', 'msvs_external_builder_build_cmd', 'msvs_external_builder_clean_cmd', + 'msvs_external_builder_clcompile_cmd', + 'msvs_enable_winrt', + 'msvs_requires_importlibrary', + 'msvs_enable_winphone', + 'msvs_application_type_revision', + 'msvs_target_platform_version', + 'msvs_target_platform_minversion', ] +generator_filelist_paths = None # List of precompiled header related keys. precomp_keys = [ @@ -97,46 +106,6 @@ cached_username = None cached_domain = None -# Based on http://code.activestate.com/recipes/576694/. -class OrderedSet(collections.MutableSet): - def __init__(self, iterable=None): - self.end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.map = {} # key --> [key, prev, next] - if iterable is not None: - self |= iterable - - def __len__(self): - return len(self.map) - - def discard(self, key): - if key in self.map: - key, prev, next = self.map.pop(key) - prev[2] = next - next[1] = prev - - def __contains__(self, key): - return key in self.map - - def add(self, key): - if key not in self.map: - end = self.end - curr = end[1] - curr[2] = end[1] = self.map[key] = [key, curr, end] - - def update(self, iterable): - for i in iterable: - if i not in self: - self.add(i) - - def __iter__(self): - end = self.end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - # TODO(gspencer): Switch the os.environ calls to be # win32api.GetDomainName() and win32api.GetUserName() once the # python version in depot_tools has been updated to work on Vista @@ -153,11 +122,11 @@ def _GetDomainAndUserName(): call = subprocess.Popen(['net', 'config', 'Workstation'], stdout=subprocess.PIPE) config = call.communicate()[0] - username_re = re.compile('^User name\s+(\S+)', re.MULTILINE) + username_re = re.compile(r'^User name\s+(\S+)', re.MULTILINE) username_match = username_re.search(config) if username_match: username = username_match.group(1) - domain_re = re.compile('^Logon domain\s+(\S+)', re.MULTILINE) + domain_re = re.compile(r'^Logon domain\s+(\S+)', re.MULTILINE) domain_match = domain_re.search(config) if domain_match: domain = domain_match.group(1) @@ -288,6 +257,8 @@ def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False): if not tools.get(tool_name): tools[tool_name] = dict() tool = tools[tool_name] + if 'CompileAsWinRT' == setting: + return if tool.get(setting): if only_if_unset: return if type(tool[setting]) == list and type(value) == list: @@ -317,13 +288,28 @@ def _ConfigFullName(config_name, config_data): return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name) +def _ConfigWindowsTargetPlatformVersion(config_data): + ver = config_data.get('msvs_windows_sdk_version') + + for key in [r'HKLM\Software\Microsoft\Microsoft SDKs\Windows\%s', + r'HKLM\Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows\%s']: + sdk_dir = MSVSVersion._RegistryGetValue(key % ver, 'InstallationFolder') + if not sdk_dir: + continue + version = MSVSVersion._RegistryGetValue(key % ver, 'ProductVersion') or '' + # Find a matching entry in sdk_dir\include. + names = sorted([x for x in os.listdir(r'%s\include' % sdk_dir) + if x.startswith(version)], reverse=True) + return names[0] + + def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path, quote_cmd, do_setup_env): if [x for x in cmd if '$(InputDir)' in x]: input_dir_preamble = ( 'set INPUTDIR=$(InputDir)\n' - 'set INPUTDIR=%INPUTDIR:$(ProjectDir)=%\n' + 'if NOT DEFINED INPUTDIR set INPUTDIR=.\\\n' 'set INPUTDIR=%INPUTDIR:~0,-1%\n' ) else: @@ -852,23 +838,27 @@ def _GenerateRulesForMSVS(p, output_dir, options, spec, if rules_external: _GenerateExternalRules(rules_external, output_dir, spec, sources, options, actions_to_add) - _AdjustSourcesForRules(spec, rules, sources, excluded_sources) + _AdjustSourcesForRules(rules, sources, excluded_sources, False) -def _AdjustSourcesForRules(spec, rules, sources, excluded_sources): +def _AdjustSourcesForRules(rules, sources, excluded_sources, is_msbuild): # Add outputs generated by each rule (if applicable). for rule in rules: - # Done if not processing outputs as sources. - if int(rule.get('process_outputs_as_sources', False)): - # Add in the outputs from this rule. - trigger_files = _FindRuleTriggerFiles(rule, sources) - for trigger_file in trigger_files: + # Add in the outputs from this rule. + trigger_files = _FindRuleTriggerFiles(rule, sources) + for trigger_file in trigger_files: + # Remove trigger_file from excluded_sources to let the rule be triggered + # (e.g. rule trigger ax_enums.idl is added to excluded_sources + # because it's also in an action's inputs in the same project) + excluded_sources.discard(_FixPath(trigger_file)) + # Done if not processing outputs as sources. + if int(rule.get('process_outputs_as_sources', False)): inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file) inputs = OrderedSet(_FixPaths(inputs)) outputs = OrderedSet(_FixPaths(outputs)) inputs.remove(_FixPath(trigger_file)) sources.update(inputs) - if not spec.get('msvs_external_builder'): + if not is_msbuild: excluded_sources.update(inputs) sources.update(outputs) @@ -955,6 +945,42 @@ def _GenerateProject(project, options, version, generator_flags): return _GenerateMSVSProject(project, options, version, generator_flags) +# TODO: Avoid code duplication with _ValidateSourcesForOSX in make.py. +def _ValidateSourcesForMSVSProject(spec, version): + """Makes sure if duplicate basenames are not specified in the source list. + + Arguments: + spec: The target dictionary containing the properties of the target. + version: The VisualStudioVersion object. + """ + # This validation should not be applied to MSVC2010 and later. + assert not version.UsesVcxproj() + + # TODO: Check if MSVC allows this for loadable_module targets. + if spec.get('type', None) not in ('static_library', 'shared_library'): + return + sources = spec.get('sources', []) + basenames = {} + for source in sources: + name, ext = os.path.splitext(source) + is_compiled_file = ext in [ + '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S'] + if not is_compiled_file: + continue + basename = os.path.basename(name) # Don't include extension. + basenames.setdefault(basename, []).append(source) + + error = '' + for basename, files in basenames.iteritems(): + if len(files) > 1: + error += ' %s: %s\n' % (basename, ' '.join(files)) + + if error: + print('static library %s has several files with the same basename:\n' % + spec['target_name'] + error + 'MSVC08 cannot handle that.') + raise GypError('Duplicate basenames in sources section, see list above') + + def _GenerateMSVSProject(project, options, version, generator_flags): """Generates a .vcproj file. It may create .rules and .user files too. @@ -980,6 +1006,11 @@ def _GenerateMSVSProject(project, options, version, generator_flags): for config_name, config in spec['configurations'].iteritems(): _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config) + # MSVC08 and prior version cannot handle duplicate basenames in the same + # target. + # TODO: Take excluded sources into consideration if possible. + _ValidateSourcesForMSVSProject(spec, version) + # Prepare list of sources and excluded sources. gyp_file = os.path.split(project.build_file)[1] sources, excluded_sources = _PrepareListOfSources(spec, generator_flags, @@ -1099,7 +1130,8 @@ def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config): for this configuration. """ # Get the information for this configuration - include_dirs, resource_include_dirs = _GetIncludeDirs(config) + include_dirs, midl_include_dirs, resource_include_dirs = \ + _GetIncludeDirs(config) libraries = _GetLibraries(spec) library_dirs = _GetLibraryDirs(config) out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False) @@ -1127,6 +1159,8 @@ def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config): # Add the information to the appropriate tool _ToolAppend(tools, 'VCCLCompilerTool', 'AdditionalIncludeDirectories', include_dirs) + _ToolAppend(tools, 'VCMIDLTool', + 'AdditionalIncludeDirectories', midl_include_dirs) _ToolAppend(tools, 'VCResourceCompilerTool', 'AdditionalIncludeDirectories', resource_include_dirs) # Add in libraries. @@ -1182,10 +1216,14 @@ def _GetIncludeDirs(config): include_dirs = ( config.get('include_dirs', []) + config.get('msvs_system_include_dirs', [])) + midl_include_dirs = ( + config.get('midl_include_dirs', []) + + config.get('msvs_system_include_dirs', [])) resource_include_dirs = config.get('resource_include_dirs', include_dirs) include_dirs = _FixPaths(include_dirs) + midl_include_dirs = _FixPaths(midl_include_dirs) resource_include_dirs = _FixPaths(resource_include_dirs) - return include_dirs, resource_include_dirs + return include_dirs, midl_include_dirs, resource_include_dirs def _GetLibraryDirs(config): @@ -1219,7 +1257,7 @@ def _GetLibraries(spec): found = OrderedSet() unique_libraries_list = [] for entry in reversed(libraries): - library = re.sub('^\-l', '', entry) + library = re.sub(r'^\-l', '', entry) if not os.path.splitext(library)[1]: library += '.lib' if library not in found: @@ -1479,8 +1517,14 @@ def _AdjustSourcesAndConvertToFilterHierarchy( # Prune filters with a single child to flatten ugly directory structures # such as ../../src/modules/module1 etc. - while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter): - sources = sources[0].contents + if version.UsesVcxproj(): + while all([isinstance(s, MSVSProject.Filter) for s in sources]) \ + and len(set([s.name for s in sources])) == 1: + assert all([len(s.contents) == 1 for s in sources]) + sources = [s.contents[0] for s in sources] + else: + while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter): + sources = sources[0].contents return sources, excluded_sources, excluded_idl @@ -1816,7 +1860,7 @@ def _CreateProjectObjects(target_list, target_dicts, options, msvs_version): return projects -def _InitNinjaFlavor(options, target_list, target_dicts): +def _InitNinjaFlavor(params, target_list, target_dicts): """Initialize targets for the ninja flavor. This sets up the necessary variables in the targets to generate msvs projects @@ -1824,7 +1868,7 @@ def _InitNinjaFlavor(options, target_list, target_dicts): if they have not been set. This allows individual specs to override the default values initialized here. Arguments: - options: Options provided to the generator. + params: Params provided to the generator. target_list: List of target pairs: 'base/base.gyp:base'. target_dicts: Dict of target properties keyed on target pair. """ @@ -1838,8 +1882,15 @@ def _InitNinjaFlavor(options, target_list, target_dicts): spec['msvs_external_builder'] = 'ninja' if not spec.get('msvs_external_builder_out_dir'): - spec['msvs_external_builder_out_dir'] = \ - options.depth + '/out/$(Configuration)' + gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target) + gyp_dir = os.path.dirname(gyp_file) + configuration = '$(Configuration)' + if params.get('target_arch') == 'x64': + configuration += '_x64' + spec['msvs_external_builder_out_dir'] = os.path.join( + gyp.common.RelativePath(params['options'].toplevel_dir, gyp_dir), + ninja_generator.ComputeOutputDir(params), + configuration) if not spec.get('msvs_external_builder_build_cmd'): spec['msvs_external_builder_build_cmd'] = [ path_to_ninja, @@ -1852,8 +1903,7 @@ def _InitNinjaFlavor(options, target_list, target_dicts): path_to_ninja, '-C', '$(OutDir)', - '-t', - 'clean', + '-tclean', '$(ProjectName)', ] @@ -1905,6 +1955,19 @@ def PerformBuild(data, configurations, params): rtn = subprocess.check_call(arguments) +def CalculateGeneratorInputInfo(params): + if params.get('flavor') == 'ninja': + toplevel = params['options'].toplevel_dir + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, ninja_generator.ComputeOutputDir(params), + 'gypfiles-msvs-ninja')) + + global generator_filelist_paths + generator_filelist_paths = { + 'toplevel': toplevel, + 'qualified_out_dir': qualified_out_dir, + } + def GenerateOutput(target_list, target_dicts, data, params): """Generate .sln and .vcproj files. @@ -1934,7 +1997,7 @@ def GenerateOutput(target_list, target_dicts, data, params): # Optionally configure each spec to use ninja as the external builder. if params.get('flavor') == 'ninja': - _InitNinjaFlavor(options, target_list, target_dicts) + _InitNinjaFlavor(params, target_list, target_dicts) # Prepare the set of configurations. configs = set() @@ -1987,7 +2050,7 @@ def GenerateOutput(target_list, target_dicts, data, params): def _GenerateMSBuildFiltersFile(filters_path, source_files, - extension_to_rule_name): + rule_dependencies, extension_to_rule_name): """Generate the filters file. This file is used by Visual Studio to organize the presentation of source @@ -2000,8 +2063,8 @@ def _GenerateMSBuildFiltersFile(filters_path, source_files, """ filter_group = [] source_group = [] - _AppendFiltersForMSBuild('', source_files, extension_to_rule_name, - filter_group, source_group) + _AppendFiltersForMSBuild('', source_files, rule_dependencies, + extension_to_rule_name, filter_group, source_group) if filter_group: content = ['Project', {'ToolsVersion': '4.0', @@ -2016,7 +2079,7 @@ def _GenerateMSBuildFiltersFile(filters_path, source_files, os.unlink(filters_path) -def _AppendFiltersForMSBuild(parent_filter_name, sources, +def _AppendFiltersForMSBuild(parent_filter_name, sources, rule_dependencies, extension_to_rule_name, filter_group, source_group): """Creates the list of filters and sources to be added in the filter file. @@ -2042,11 +2105,12 @@ def _AppendFiltersForMSBuild(parent_filter_name, sources, ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]]) # Recurse and add its dependents. _AppendFiltersForMSBuild(filter_name, source.contents, - extension_to_rule_name, + rule_dependencies, extension_to_rule_name, filter_group, source_group) else: # It's a source. Create a source entry. - _, element = _MapFileToMsBuildSourceType(source, extension_to_rule_name) + _, element = _MapFileToMsBuildSourceType(source, rule_dependencies, + extension_to_rule_name) source_entry = [element, {'Include': source}] # Specify the filter it is part of, if any. if parent_filter_name: @@ -2054,7 +2118,8 @@ def _AppendFiltersForMSBuild(parent_filter_name, sources, source_group.append(source_entry) -def _MapFileToMsBuildSourceType(source, extension_to_rule_name): +def _MapFileToMsBuildSourceType(source, rule_dependencies, + extension_to_rule_name): """Returns the group and element type of the source file. Arguments: @@ -2077,9 +2142,15 @@ def _MapFileToMsBuildSourceType(source, extension_to_rule_name): elif ext == '.rc': group = 'resource' element = 'ResourceCompile' + elif ext == '.asm': + group = 'masm' + element = 'MASM' elif ext == '.idl': group = 'midl' element = 'Midl' + elif source in rule_dependencies: + group = 'rule_dependency' + element = 'CustomBuild' else: group = 'none' element = 'None' @@ -2089,7 +2160,8 @@ def _MapFileToMsBuildSourceType(source, extension_to_rule_name): def _GenerateRulesForMSBuild(output_dir, options, spec, sources, excluded_sources, props_files_of_rules, targets_files_of_rules, - actions_to_add, extension_to_rule_name): + actions_to_add, rule_dependencies, + extension_to_rule_name): # MSBuild rules are implemented using three files: an XML file, a .targets # file and a .props file. # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx @@ -2105,6 +2177,7 @@ def _GenerateRulesForMSBuild(output_dir, options, spec, continue msbuild_rule = MSBuildRule(rule, spec) msbuild_rules.append(msbuild_rule) + rule_dependencies.update(msbuild_rule.additional_dependencies.split(';')) extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name if msbuild_rules: base = spec['target_name'] + options.suffix @@ -2126,7 +2199,7 @@ def _GenerateRulesForMSBuild(output_dir, options, spec, if rules_external: _GenerateExternalRules(rules_external, output_dir, spec, sources, options, actions_to_add) - _AdjustSourcesForRules(spec, rules, sources, excluded_sources) + _AdjustSourcesForRules(rules, sources, excluded_sources, True) class MSBuildRule(object): @@ -2305,6 +2378,9 @@ def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules): rule_name, {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != " "'true'" % (rule_name, rule_name), + 'EchoOff': 'true', + 'StandardOutputImportance': 'High', + 'StandardErrorImportance': 'High', 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name, 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name, 'Inputs': rule_inputs @@ -2579,14 +2655,60 @@ def _GetMSBuildProjectConfigurations(configurations): def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name): namespace = os.path.splitext(gyp_file_name)[0] - return [ + properties = [ ['PropertyGroup', {'Label': 'Globals'}, - ['ProjectGuid', guid], - ['Keyword', 'Win32Proj'], - ['RootNamespace', namespace], + ['ProjectGuid', guid], + ['Keyword', 'Win32Proj'], + ['RootNamespace', namespace], + ['IgnoreWarnCompileDuplicatedFilename', 'true'], ] - ] + ] + if os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or \ + os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64': + properties[0].append(['PreferredToolArchitecture', 'x64']) + + if spec.get('msvs_enable_winrt'): + properties[0].append(['DefaultLanguage', 'en-US']) + properties[0].append(['AppContainerApplication', 'true']) + if spec.get('msvs_application_type_revision'): + app_type_revision = spec.get('msvs_application_type_revision') + properties[0].append(['ApplicationTypeRevision', app_type_revision]) + else: + properties[0].append(['ApplicationTypeRevision', '8.1']) + + if spec.get('msvs_target_platform_version'): + target_platform_version = spec.get('msvs_target_platform_version') + properties[0].append(['WindowsTargetPlatformVersion', + target_platform_version]) + if spec.get('msvs_target_platform_minversion'): + target_platform_minversion = spec.get('msvs_target_platform_minversion') + properties[0].append(['WindowsTargetPlatformMinVersion', + target_platform_minversion]) + else: + properties[0].append(['WindowsTargetPlatformMinVersion', + target_platform_version]) + if spec.get('msvs_enable_winphone'): + properties[0].append(['ApplicationType', 'Windows Phone']) + else: + properties[0].append(['ApplicationType', 'Windows Store']) + + platform_name = None + msvs_windows_sdk_version = None + for configuration in spec['configurations'].itervalues(): + platform_name = platform_name or _ConfigPlatform(configuration) + msvs_windows_sdk_version = (msvs_windows_sdk_version or + _ConfigWindowsTargetPlatformVersion(configuration)) + if platform_name and msvs_windows_sdk_version: + break + + if platform_name == 'ARM': + properties[0].append(['WindowsSDKDesktopARMSupport', 'true']) + if msvs_windows_sdk_version: + properties[0].append(['WindowsTargetPlatformVersion', + str(msvs_windows_sdk_version)]) + + return properties def _GetMSBuildConfigurationDetails(spec, build_file): properties = {} @@ -2597,8 +2719,9 @@ def _GetMSBuildConfigurationDetails(spec, build_file): _AddConditionalProperty(properties, condition, 'ConfigurationType', msbuild_attributes['ConfigurationType']) if character_set: - _AddConditionalProperty(properties, condition, 'CharacterSet', - character_set) + if 'msvs_enable_winrt' not in spec : + _AddConditionalProperty(properties, condition, 'CharacterSet', + character_set) return _GetMSBuildPropertyGroup(spec, 'Configuration', properties) @@ -2813,7 +2936,7 @@ def _AddConditionalProperty(properties, condition, name, value): # Regex for msvs variable references ( i.e. $(FOO) ). -MSVS_VARIABLE_REFERENCE = re.compile('\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)') +MSVS_VARIABLE_REFERENCE = re.compile(r'\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)') def _GetMSBuildPropertyGroup(spec, label, properties): @@ -2897,7 +3020,8 @@ def _FinalizeMSBuildSettings(spec, configuration): converted = True msvs_settings = configuration.get('msvs_settings', {}) msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings) - include_dirs, resource_include_dirs = _GetIncludeDirs(configuration) + include_dirs, midl_include_dirs, resource_include_dirs = \ + _GetIncludeDirs(configuration) libraries = _GetLibraries(spec) library_dirs = _GetLibraryDirs(configuration) out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True) @@ -2927,6 +3051,8 @@ def _FinalizeMSBuildSettings(spec, configuration): # if you don't have any resources. _ToolAppend(msbuild_settings, 'ClCompile', 'AdditionalIncludeDirectories', include_dirs) + _ToolAppend(msbuild_settings, 'Midl', + 'AdditionalIncludeDirectories', midl_include_dirs) _ToolAppend(msbuild_settings, 'ResourceCompile', 'AdditionalIncludeDirectories', resource_include_dirs) # Add in libraries, note that even for empty libraries, we want this @@ -2957,6 +3083,13 @@ def _FinalizeMSBuildSettings(spec, configuration): 'PrecompiledHeaderFile', precompiled_header) _ToolAppend(msbuild_settings, 'ClCompile', 'ForcedIncludeFiles', [precompiled_header]) + else: + _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'NotUsing') + # Turn off WinRT compilation + _ToolAppend(msbuild_settings, 'ClCompile', 'CompileAsWinRT', 'false') + # Turn on import libraries if appropriate + if spec.get('msvs_requires_importlibrary'): + _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'false') # Loadable modules don't generate import libraries; # tell dependent projects to not expect one. if spec['type'] == 'loadable_module': @@ -3024,15 +3157,18 @@ def _VerifySourcesExist(sources, root_dir): return missing_sources -def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name, - actions_spec, sources_handled_by_action, list_excluded): - groups = ['none', 'midl', 'include', 'compile', 'resource', 'rule'] +def _GetMSBuildSources(spec, sources, exclusions, rule_dependencies, + extension_to_rule_name, actions_spec, + sources_handled_by_action, list_excluded): + groups = ['none', 'masm', 'midl', 'include', 'compile', 'resource', 'rule', + 'rule_dependency'] grouped_sources = {} for g in groups: grouped_sources[g] = [] _AddSources2(spec, sources, exclusions, grouped_sources, - extension_to_rule_name, sources_handled_by_action, list_excluded) + rule_dependencies, extension_to_rule_name, + sources_handled_by_action, list_excluded) sources = [] for g in groups: if grouped_sources[g]: @@ -3043,13 +3179,15 @@ def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name, def _AddSources2(spec, sources, exclusions, grouped_sources, - extension_to_rule_name, sources_handled_by_action, + rule_dependencies, extension_to_rule_name, + sources_handled_by_action, list_excluded): extensions_excluded_from_precompile = [] for source in sources: if isinstance(source, MSVSProject.Filter): _AddSources2(spec, source.contents, exclusions, grouped_sources, - extension_to_rule_name, sources_handled_by_action, + rule_dependencies, extension_to_rule_name, + sources_handled_by_action, list_excluded) else: if not source in sources_handled_by_action: @@ -3092,7 +3230,7 @@ def _AddSources2(spec, sources, exclusions, grouped_sources, detail.append(['PrecompiledHeader', '']) detail.append(['ForcedIncludeFiles', '']) - group, element = _MapFileToMsBuildSourceType(source, + group, element = _MapFileToMsBuildSourceType(source, rule_dependencies, extension_to_rule_name) grouped_sources[group].append([element, {'Include': source}] + detail) @@ -3136,6 +3274,7 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): actions_to_add = {} props_files_of_rules = set() targets_files_of_rules = set() + rule_dependencies = set() extension_to_rule_name = {} list_excluded = generator_flags.get('msvs_list_excluded_files', True) @@ -3144,10 +3283,11 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): _GenerateRulesForMSBuild(project_dir, options, spec, sources, excluded_sources, props_files_of_rules, targets_files_of_rules, - actions_to_add, extension_to_rule_name) + actions_to_add, rule_dependencies, + extension_to_rule_name) else: rules = spec.get('rules', []) - _AdjustSourcesForRules(spec, rules, sources, excluded_sources) + _AdjustSourcesForRules(rules, sources, excluded_sources, True) sources, excluded_sources, excluded_idl = ( _AdjustSourcesAndConvertToFilterHierarchy(spec, options, @@ -3170,6 +3310,7 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): spec, actions_to_add) _GenerateMSBuildFiltersFile(project.path + '.filters', sources, + rule_dependencies, extension_to_rule_name) missing_sources = _VerifySourcesExist(sources, project_dir) @@ -3184,6 +3325,12 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]] import_cpp_targets_section = [ ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]] + import_masm_props_section = [ + ['Import', + {'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.props'}]] + import_masm_targets_section = [ + ['Import', + {'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.targets'}]] macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]] content = [ @@ -3197,8 +3344,12 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name) content += import_default_section content += _GetMSBuildConfigurationDetails(spec, project.build_file) - content += _GetMSBuildLocalProperties(project.msbuild_toolset) + if spec.get('msvs_enable_winphone'): + content += _GetMSBuildLocalProperties('v120_wp81') + else: + content += _GetMSBuildLocalProperties(project.msbuild_toolset) content += import_cpp_props_section + content += import_masm_props_section content += _GetMSBuildExtensions(props_files_of_rules) content += _GetMSBuildPropertySheets(configurations) content += macro_section @@ -3206,10 +3357,11 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): project.build_file) content += _GetMSBuildToolSettingsSections(spec, configurations) content += _GetMSBuildSources( - spec, sources, exclusions, extension_to_rule_name, actions_spec, - sources_handled_by_action, list_excluded) + spec, sources, exclusions, rule_dependencies, extension_to_rule_name, + actions_spec, sources_handled_by_action, list_excluded) content += _GetMSBuildProjectReferences(project) content += import_cpp_targets_section + content += import_masm_targets_section content += _GetMSBuildExtensionTargets(targets_files_of_rules) if spec.get('msvs_external_builder'): @@ -3226,7 +3378,9 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): def _GetMSBuildExternalBuilderTargets(spec): """Return a list of MSBuild targets for external builders. - Right now, only "Build" and "Clean" targets are generated. + The "Build" and "Clean" targets are always generated. If the spec contains + 'msvs_external_builder_clcompile_cmd', then the "ClCompile" target will also + be generated, to support building selected C/C++ files. Arguments: spec: The gyp target spec. @@ -3245,7 +3399,17 @@ def _GetMSBuildExternalBuilderTargets(spec): clean_target = ['Target', {'Name': 'Clean'}] clean_target.append(['Exec', {'Command': clean_cmd}]) - return [build_target, clean_target] + targets = [build_target, clean_target] + + if spec.get('msvs_external_builder_clcompile_cmd'): + clcompile_cmd = _BuildCommandLineForRuleRaw( + spec, spec['msvs_external_builder_clcompile_cmd'], + False, False, False, False) + clcompile_target = ['Target', {'Name': 'ClCompile'}] + clcompile_target.append(['Exec', {'Command': clcompile_cmd}]) + targets.append(clcompile_target) + + return targets def _GetMSBuildExtensions(props_files_of_rules): @@ -3299,8 +3463,8 @@ def _GenerateActionsForMSBuild(spec, actions_to_add): # get too long. See also _AddActions: cygwin's setup_env mustn't be called # for every invocation or the command that sets the PATH will grow too # long. - command = ( - '\r\nif %errorlevel% neq 0 exit /b %errorlevel%\r\n'.join(commands)) + command = '\r\n'.join([c + '\r\nif %errorlevel% neq 0 exit /b %errorlevel%' + for c in commands]) _AddMSBuildAction(spec, primary_input, inputs, diff --git a/third_party/gyp/generator/ninja.py b/third_party/gyp/generator/ninja.py index 1ed23f64..9cfc7060 100644 --- a/third_party/gyp/generator/ninja.py +++ b/third_party/gyp/generator/ninja.py @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import collections import copy import hashlib import json @@ -13,6 +14,7 @@ import subprocess import sys import gyp import gyp.common +from gyp.common import OrderedSet import gyp.msvs_emulation import gyp.MSVSUtil as MSVSUtil import gyp.xcode_emulation @@ -60,17 +62,7 @@ generator_additional_path_sections = [] generator_extra_sources_for_rules = [] generator_filelist_paths = None -# TODO: figure out how to not build extra host objects in the non-cross-compile -# case when this is enabled, and enable unconditionally. -generator_supports_multiple_toolsets = ( - os.environ.get('GYP_CROSSCOMPILE') or - os.environ.get('AR_host') or - os.environ.get('CC_host') or - os.environ.get('CXX_host') or - os.environ.get('AR_target') or - os.environ.get('CC_target') or - os.environ.get('CXX_target')) - +generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested() def StripPrefix(arg, prefix): if arg.startswith(prefix): @@ -106,7 +98,7 @@ def AddArch(output, arch): return '%s.%s%s' % (output, arch, extension) -class Target: +class Target(object): """Target represents the paths used within a single gyp target. Conceptually, building a single target A is a series of steps: @@ -147,8 +139,11 @@ class Target: self.bundle = None # On Windows, incremental linking requires linking against all the .objs # that compose a .lib (rather than the .lib itself). That list is stored - # here. + # here. In this case, we also need to save the compile_deps for the target, + # so that the the target that directly depends on the .objs can also depend + # on those. self.component_objs = None + self.compile_deps = None # Windows only. The import .lib is the output of a build step, but # because dependents only link against the lib (not both the lib and the # dll) we keep track of the import library here. @@ -210,8 +205,8 @@ class Target: # an output file; the result can be namespaced such that it is unique # to the input file name as well as the output target name. -class NinjaWriter: - def __init__(self, qualified_target, target_outputs, base_dir, build_dir, +class NinjaWriter(object): + def __init__(self, hash_for_rules, target_outputs, base_dir, build_dir, output_file, toplevel_build, output_file_name, flavor, toplevel_dir=None): """ @@ -221,7 +216,7 @@ class NinjaWriter: toplevel_dir: path to the toplevel directory """ - self.qualified_target = qualified_target + self.hash_for_rules = hash_for_rules self.target_outputs = target_outputs self.base_dir = base_dir self.build_dir = build_dir @@ -338,12 +333,15 @@ class NinjaWriter: obj += '.' + self.toolset path_dir, path_basename = os.path.split(path) + assert not os.path.isabs(path_dir), ( + "'%s' can not be absolute path (see crbug.com/462153)." % path_dir) + if qualified: path_basename = self.name + '.' + path_basename return os.path.normpath(os.path.join(obj, self.base_dir, path_dir, path_basename)) - def WriteCollapsedDependencies(self, name, targets): + def WriteCollapsedDependencies(self, name, targets, order_only=None): """Given a list of targets, return a path for a single file representing the result of building all the targets or None. @@ -351,10 +349,11 @@ class NinjaWriter: assert targets == filter(None, targets), targets if len(targets) == 0: + assert not order_only return None - if len(targets) > 1: + if len(targets) > 1 or order_only: stamp = self.GypPathToUniqueOutput(name + '.stamp') - targets = self.ninja.build(stamp, 'stamp', targets) + targets = self.ninja.build(stamp, 'stamp', targets, order_only=order_only) self.ninja.newline() return targets[0] @@ -380,10 +379,16 @@ class NinjaWriter: # should be used for linking. self.uses_cpp = False + self.target_rpath = generator_flags.get('target_rpath', r'\$$ORIGIN/lib/') + self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec) self.xcode_settings = self.msvs_settings = None if self.flavor == 'mac': self.xcode_settings = gyp.xcode_emulation.XcodeSettings(spec) + mac_toolchain_dir = generator_flags.get('mac_toolchain_dir', None) + if mac_toolchain_dir: + self.xcode_settings.mac_toolchain_dir = mac_toolchain_dir + if self.flavor == 'win': self.msvs_settings = gyp.msvs_emulation.MsvsSettings(spec, generator_flags) @@ -391,6 +396,9 @@ class NinjaWriter: self.ninja.variable('arch', self.win_env[arch]) self.ninja.variable('cc', '$cl_' + arch) self.ninja.variable('cxx', '$cl_' + arch) + self.ninja.variable('cc_host', '$cl_' + arch) + self.ninja.variable('cxx_host', '$cl_' + arch) + self.ninja.variable('asm', '$ml_' + arch) if self.flavor == 'mac': self.archs = self.xcode_settings.GetActiveArchs(config_name) @@ -472,17 +480,20 @@ class NinjaWriter: else: print "Warning: Actions/rules writing object files don't work with " \ "multiarch targets, dropping. (target %s)" % spec['target_name'] + elif self.flavor == 'mac' and len(self.archs) > 1: + link_deps = collections.defaultdict(list) - + compile_deps = self.target.actions_stamp or actions_depends if self.flavor == 'win' and self.target.type == 'static_library': self.target.component_objs = link_deps + self.target.compile_deps = compile_deps # Write out a link step, if needed. output = None is_empty_bundle = not link_deps and not mac_bundle_depends if link_deps or self.target.actions_stamp or actions_depends: output = self.WriteTarget(spec, config_name, config, link_deps, - self.target.actions_stamp or actions_depends) + compile_deps) if self.is_mac_bundle: mac_bundle_depends.append(output) @@ -523,7 +534,7 @@ class NinjaWriter: def WriteWinIdlFiles(self, spec, prebuild): """Writes rules to match MSVS's implicit idl handling.""" assert self.flavor == 'win' - if self.msvs_settings.HasExplicitIdlRules(spec): + if self.msvs_settings.HasExplicitIdlRulesOrActions(spec): return [] outputs = [] for source in filter(lambda x: x.endswith('.idl'), spec['sources']): @@ -554,12 +565,16 @@ class NinjaWriter: if 'sources' in spec and self.flavor == 'win': outputs += self.WriteWinIdlFiles(spec, prebuild) + if self.xcode_settings and self.xcode_settings.IsIosFramework(): + self.WriteiOSFrameworkHeaders(spec, outputs, prebuild) + stamp = self.WriteCollapsedDependencies('actions_rules_copies', outputs) if self.is_mac_bundle: - self.WriteMacBundleResources( + xcassets = self.WriteMacBundleResources( extra_mac_bundle_resources + mac_bundle_resources, mac_bundle_depends) - self.WriteMacInfoPlist(mac_bundle_depends) + partial_info_plist = self.WriteMacXCassets(xcassets, mac_bundle_depends) + self.WriteMacInfoPlist(partial_info_plist, mac_bundle_depends) return stamp @@ -580,23 +595,24 @@ class NinjaWriter: def WriteActions(self, actions, extra_sources, prebuild, extra_mac_bundle_resources): # Actions cd into the base directory. - env = self.GetSortedXcodeEnv() - if self.flavor == 'win': - env = self.msvs_settings.GetVSMacroEnv( - '$!PRODUCT_DIR', config=self.config_name) + env = self.GetToolchainEnv() all_outputs = [] for action in actions: # First write out a rule for the action. - name = '%s_%s' % (action['action_name'], - hashlib.md5(self.qualified_target).hexdigest()) + name = '%s_%s' % (action['action_name'], self.hash_for_rules) description = self.GenerateDescription('ACTION', action.get('message', None), name) is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(action) if self.flavor == 'win' else False) args = action['action'] + depfile = action.get('depfile', None) + if depfile: + depfile = self.ExpandSpecial(depfile, self.base_to_build) + pool = 'console' if int(action.get('ninja_use_console', 0)) else None rule_name, _ = self.WriteNewNinjaRule(name, args, description, - is_cygwin, env=env) + is_cygwin, env, pool, + depfile=depfile) inputs = [self.GypPathToNinja(i, env) for i in action['inputs']] if int(action.get('process_outputs_as_sources', False)): @@ -616,15 +632,16 @@ class NinjaWriter: def WriteRules(self, rules, extra_sources, prebuild, mac_bundle_resources, extra_mac_bundle_resources): - env = self.GetSortedXcodeEnv() + env = self.GetToolchainEnv() all_outputs = [] for rule in rules: - # First write out a rule for the rule action. - name = '%s_%s' % (rule['rule_name'], - hashlib.md5(self.qualified_target).hexdigest()) # Skip a rule with no action and no inputs. if 'action' not in rule and not rule.get('rule_sources', []): continue + + # First write out a rule for the rule action. + name = '%s_%s' % (rule['rule_name'], self.hash_for_rules) + args = rule['action'] description = self.GenerateDescription( 'RULE', @@ -632,8 +649,9 @@ class NinjaWriter: ('%s ' + generator_default_variables['RULE_INPUT_PATH']) % name) is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(rule) if self.flavor == 'win' else False) + pool = 'console' if int(rule.get('ninja_use_console', 0)) else None rule_name, args = self.WriteNewNinjaRule( - name, args, description, is_cygwin, env=env) + name, args, description, is_cygwin, env, pool) # TODO: if the command references the outputs directly, we should # simplify it to just use $out. @@ -645,16 +663,32 @@ class NinjaWriter: needed_variables = set(['source']) for argument in args: for var in special_locals: - if ('${%s}' % var) in argument: + if '${%s}' % var in argument: needed_variables.add(var) + needed_variables = sorted(needed_variables) def cygwin_munge(path): + # pylint: disable=cell-var-from-loop if is_cygwin: return path.replace('\\', '/') return path + inputs = [self.GypPathToNinja(i, env) for i in rule.get('inputs', [])] + + # If there are n source files matching the rule, and m additional rule + # inputs, then adding 'inputs' to each build edge written below will + # write m * n inputs. Collapsing reduces this to m + n. + sources = rule.get('rule_sources', []) + num_inputs = len(inputs) + if prebuild: + num_inputs += 1 + if num_inputs > 2 and len(sources) > 2: + inputs = [self.WriteCollapsedDependencies( + rule['rule_name'], inputs, order_only=prebuild)] + prebuild = [] + # For each source file, write an edge that generates all the outputs. - for source in rule.get('rule_sources', []): + for source in sources: source = os.path.normpath(source) dirname, basename = os.path.split(source) root, ext = os.path.splitext(basename) @@ -663,9 +697,6 @@ class NinjaWriter: outputs = [self.ExpandRuleVariables(o, root, dirname, source, ext, basename) for o in rule['outputs']] - inputs = [self.ExpandRuleVariables(i, root, dirname, - source, ext, basename) - for i in rule.get('inputs', [])] if int(rule.get('process_outputs_as_sources', False)): extra_sources += outputs @@ -703,10 +734,12 @@ class NinjaWriter: else: assert var == None, repr(var) - inputs = [self.GypPathToNinja(i, env) for i in inputs] outputs = [self.GypPathToNinja(o, env) for o in outputs] - extra_bindings.append(('unique_name', - hashlib.md5(outputs[0]).hexdigest())) + if self.flavor == 'win': + # WriteNewNinjaRule uses unique_name for creating an rsp file on win. + extra_bindings.append(('unique_name', + hashlib.md5(outputs[0]).hexdigest())) + self.ninja.build(outputs, rule_name, self.GypPathToNinja(source), implicit=inputs, order_only=prebuild, @@ -718,7 +751,11 @@ class NinjaWriter: def WriteCopies(self, copies, prebuild, mac_bundle_depends): outputs = [] - env = self.GetSortedXcodeEnv() + if self.xcode_settings: + extra_env = self.xcode_settings.GetPerTargetSettings() + env = self.GetToolchainEnv(additional_settings=extra_env) + else: + env = self.GetToolchainEnv() for copy in copies: for path in copy['files']: # Normalize the path so trailing slashes don't confuse us. @@ -740,17 +777,90 @@ class NinjaWriter: return outputs + def WriteiOSFrameworkHeaders(self, spec, outputs, prebuild): + """Prebuild steps to generate hmap files and copy headers to destination.""" + framework = self.ComputeMacBundleOutput() + all_sources = spec['sources'] + copy_headers = spec['mac_framework_headers'] + output = self.GypPathToUniqueOutput('headers.hmap') + self.xcode_settings.header_map_path = output + all_headers = map(self.GypPathToNinja, + filter(lambda x:x.endswith(('.h')), all_sources)) + variables = [('framework', framework), + ('copy_headers', map(self.GypPathToNinja, copy_headers))] + outputs.extend(self.ninja.build( + output, 'compile_ios_framework_headers', all_headers, + variables=variables, order_only=prebuild)) + def WriteMacBundleResources(self, resources, bundle_depends): """Writes ninja edges for 'mac_bundle_resources'.""" + xcassets = [] + + extra_env = self.xcode_settings.GetPerTargetSettings() + env = self.GetSortedXcodeEnv(additional_settings=extra_env) + env = self.ComputeExportEnvString(env) + isBinary = self.xcode_settings.IsBinaryOutputFormat(self.config_name) + for output, res in gyp.xcode_emulation.GetMacBundleResources( generator_default_variables['PRODUCT_DIR'], self.xcode_settings, map(self.GypPathToNinja, resources)): output = self.ExpandSpecial(output) - self.ninja.build(output, 'mac_tool', res, - variables=[('mactool_cmd', 'copy-bundle-resource')]) - bundle_depends.append(output) + if os.path.splitext(output)[-1] != '.xcassets': + self.ninja.build(output, 'mac_tool', res, + variables=[('mactool_cmd', 'copy-bundle-resource'), \ + ('env', env), ('binary', isBinary)]) + bundle_depends.append(output) + else: + xcassets.append(res) + return xcassets - def WriteMacInfoPlist(self, bundle_depends): + def WriteMacXCassets(self, xcassets, bundle_depends): + """Writes ninja edges for 'mac_bundle_resources' .xcassets files. + + This add an invocation of 'actool' via the 'mac_tool.py' helper script. + It assumes that the assets catalogs define at least one imageset and + thus an Assets.car file will be generated in the application resources + directory. If this is not the case, then the build will probably be done + at each invocation of ninja.""" + if not xcassets: + return + + extra_arguments = {} + settings_to_arg = { + 'XCASSETS_APP_ICON': 'app-icon', + 'XCASSETS_LAUNCH_IMAGE': 'launch-image', + } + settings = self.xcode_settings.xcode_settings[self.config_name] + for settings_key, arg_name in settings_to_arg.iteritems(): + value = settings.get(settings_key) + if value: + extra_arguments[arg_name] = value + + partial_info_plist = None + if extra_arguments: + partial_info_plist = self.GypPathToUniqueOutput( + 'assetcatalog_generated_info.plist') + extra_arguments['output-partial-info-plist'] = partial_info_plist + + outputs = [] + outputs.append( + os.path.join( + self.xcode_settings.GetBundleResourceFolder(), + 'Assets.car')) + if partial_info_plist: + outputs.append(partial_info_plist) + + keys = QuoteShellArgument(json.dumps(extra_arguments), self.flavor) + extra_env = self.xcode_settings.GetPerTargetSettings() + env = self.GetSortedXcodeEnv(additional_settings=extra_env) + env = self.ComputeExportEnvString(env) + + bundle_depends.extend(self.ninja.build( + outputs, 'compile_xcassets', xcassets, + variables=[('env', env), ('keys', keys)])) + return partial_info_plist + + def WriteMacInfoPlist(self, partial_info_plist, bundle_depends): """Write build rules for bundle Info.plist files.""" info_plist, out, defines, extra_env = gyp.xcode_emulation.GetMacInfoPlist( generator_default_variables['PRODUCT_DIR'], @@ -770,10 +880,18 @@ class NinjaWriter: env = self.GetSortedXcodeEnv(additional_settings=extra_env) env = self.ComputeExportEnvString(env) + if partial_info_plist: + intermediate_plist = self.GypPathToUniqueOutput('merged_info.plist') + info_plist = self.ninja.build( + intermediate_plist, 'merge_infoplist', + [partial_info_plist, info_plist]) + keys = self.xcode_settings.GetExtraPlistItems(self.config_name) keys = QuoteShellArgument(json.dumps(keys), self.flavor) + isBinary = self.xcode_settings.IsBinaryOutputFormat(self.config_name) self.ninja.build(out, 'copy_infoplist', info_plist, - variables=[('env', env), ('keys', keys)]) + variables=[('env', env), ('keys', keys), + ('binary', isBinary)]) bundle_depends.append(out) def WriteSources(self, ninja_file, config_name, config, sources, predepends, @@ -785,6 +903,8 @@ class NinjaWriter: self.ninja.variable('cxx', '$cxx_host') self.ninja.variable('ld', '$ld_host') self.ninja.variable('ldxx', '$ldxx_host') + self.ninja.variable('nm', '$nm_host') + self.ninja.variable('readelf', '$readelf_host') if self.flavor != 'mac' or len(self.archs) == 1: return self.WriteSourcesForArch( @@ -810,6 +930,7 @@ class NinjaWriter: cflags_objcc = ['$cflags_cc'] + \ self.xcode_settings.GetCflagsObjCC(config_name) elif self.flavor == 'win': + asmflags = self.msvs_settings.GetAsmflags(config_name) cflags = self.msvs_settings.GetCflags(config_name) cflags_c = self.msvs_settings.GetCflagsC(config_name) cflags_cc = self.msvs_settings.GetCflagsCC(config_name) @@ -839,27 +960,41 @@ class NinjaWriter: os.environ.get('CFLAGS', '').split() + cflags_c) cflags_cc = (os.environ.get('CPPFLAGS', '').split() + os.environ.get('CXXFLAGS', '').split() + cflags_cc) + elif self.toolset == 'host': + cflags_c = (os.environ.get('CPPFLAGS_host', '').split() + + os.environ.get('CFLAGS_host', '').split() + cflags_c) + cflags_cc = (os.environ.get('CPPFLAGS_host', '').split() + + os.environ.get('CXXFLAGS_host', '').split() + cflags_cc) defines = config.get('defines', []) + extra_defines self.WriteVariableList(ninja_file, 'defines', [Define(d, self.flavor) for d in defines]) if self.flavor == 'win': + self.WriteVariableList(ninja_file, 'asmflags', + map(self.ExpandSpecial, asmflags)) self.WriteVariableList(ninja_file, 'rcflags', [QuoteShellArgument(self.ExpandSpecial(f), self.flavor) for f in self.msvs_settings.GetRcflags(config_name, self.GypPathToNinja)]) include_dirs = config.get('include_dirs', []) - env = self.GetSortedXcodeEnv() + + env = self.GetToolchainEnv() if self.flavor == 'win': - env = self.msvs_settings.GetVSMacroEnv('$!PRODUCT_DIR', - config=config_name) include_dirs = self.msvs_settings.AdjustIncludeDirs(include_dirs, config_name) self.WriteVariableList(ninja_file, 'includes', [QuoteShellArgument('-I' + self.GypPathToNinja(i, env), self.flavor) for i in include_dirs]) + if self.flavor == 'win': + midl_include_dirs = config.get('midl_include_dirs', []) + midl_include_dirs = self.msvs_settings.AdjustMidlIncludeDirs( + midl_include_dirs, config_name) + self.WriteVariableList(ninja_file, 'midl_includes', + [QuoteShellArgument('-I' + self.GypPathToNinja(i, env), self.flavor) + for i in midl_include_dirs]) + pch_commands = precompiled_header.GetPchBuildCommands(arch) if self.flavor == 'mac': # Most targets use no precompiled headers, so only write these if needed. @@ -868,6 +1003,8 @@ class NinjaWriter: include = precompiled_header.GetInclude(ext, arch) if include: ninja_file.variable(var, include) + arflags = config.get('arflags', []) + self.WriteVariableList(ninja_file, 'cflags', map(self.ExpandSpecial, cflags)) self.WriteVariableList(ninja_file, 'cflags_c', @@ -879,6 +1016,8 @@ class NinjaWriter: map(self.ExpandSpecial, cflags_objc)) self.WriteVariableList(ninja_file, 'cflags_objcc', map(self.ExpandSpecial, cflags_objcc)) + self.WriteVariableList(ninja_file, 'arflags', + map(self.ExpandSpecial, arflags)) ninja_file.newline() outputs = [] has_rc_source = False @@ -894,9 +1033,7 @@ class NinjaWriter: elif ext == 's' and self.flavor != 'win': # Doesn't generate .o.d files. command = 'cc_s' elif (self.flavor == 'win' and ext == 'asm' and - self.msvs_settings.GetArch(config_name) == 'x86' and not self.msvs_settings.HasExplicitAsmRules(spec)): - # Asm files only get auto assembled for x86 (not x64). command = 'asm' # Add the _asm suffix as msvs is capable of handling .cc and # .asm files of the same name without collision. @@ -956,25 +1093,35 @@ class NinjaWriter: cmd = map.get(lang) ninja_file.build(gch, cmd, input, variables=[(var_name, lang_flag)]) - def WriteLink(self, spec, config_name, config, link_deps): + def WriteLink(self, spec, config_name, config, link_deps, compile_deps): """Write out a link step. Fills out target.binary. """ if self.flavor != 'mac' or len(self.archs) == 1: return self.WriteLinkForArch( - self.ninja, spec, config_name, config, link_deps) + self.ninja, spec, config_name, config, link_deps, compile_deps) else: output = self.ComputeOutput(spec) inputs = [self.WriteLinkForArch(self.arch_subninjas[arch], spec, config_name, config, link_deps[arch], - arch=arch) + compile_deps, arch=arch) for arch in self.archs] extra_bindings = [] + build_output = output if not self.is_mac_bundle: self.AppendPostbuildVariable(extra_bindings, spec, output, output) - self.ninja.build(output, 'lipo', inputs, variables=extra_bindings) + + # TODO(yyanagisawa): more work needed to fix: + # https://code.google.com/p/gyp/issues/detail?id=411 + if (spec['type'] in ('shared_library', 'loadable_module') and + not self.is_mac_bundle): + extra_bindings.append(('lib', output)) + self.ninja.build([output, output + '.TOC'], 'solipo', inputs, + variables=extra_bindings) + else: + self.ninja.build(build_output, 'lipo', inputs, variables=extra_bindings) return output def WriteLinkForArch(self, ninja_file, spec, config_name, config, - link_deps, arch=None): + link_deps, compile_deps, arch=None): """Write out a link step. Fills out target.binary. """ command = { 'executable': 'link', @@ -985,6 +1132,15 @@ class NinjaWriter: implicit_deps = set() solibs = set() + order_deps = set() + + if compile_deps: + # Normally, the compiles of the target already depend on compile_deps, + # but a shared_library target might have no sources and only link together + # a few static_library deps, so the link step also needs to depend + # on compile_deps to make sure actions in the shared_library target + # get run before the link. + order_deps.add(compile_deps) if 'dependencies' in spec: # Two kinds of dependencies: @@ -1003,6 +1159,8 @@ class NinjaWriter: target.component_objs and self.msvs_settings.IsUseLibraryDependencyInputs(config_name)): new_deps = target.component_objs + if target.compile_deps: + order_deps.add(target.compile_deps) elif self.flavor == 'win' and target.import_lib: new_deps = [target.import_lib] elif target.UsesToc(self.flavor): @@ -1063,10 +1221,12 @@ class NinjaWriter: rpath = 'lib/' if self.toolset != 'target': rpath += self.toolset - ldflags.append('-Wl,-rpath=\$$ORIGIN/%s' % rpath) + ldflags.append(r'-Wl,-rpath=\$$ORIGIN/%s' % rpath) + else: + ldflags.append('-Wl,-rpath=%s' % self.target_rpath) ldflags.append('-Wl,-rpath-link=%s' % rpath) self.WriteVariableList(ninja_file, 'ldflags', - gyp.common.uniquer(map(self.ExpandSpecial, ldflags))) + map(self.ExpandSpecial, ldflags)) library_dirs = config.get('library_dirs', []) if self.flavor == 'win': @@ -1095,9 +1255,27 @@ class NinjaWriter: extra_bindings.append(('soname', os.path.split(output)[1])) extra_bindings.append(('lib', gyp.common.EncodePOSIXShellArgument(output))) + if self.flavor != 'win': + link_file_list = output + if self.is_mac_bundle: + # 'Dependency Framework.framework/Versions/A/Dependency Framework' -> + # 'Dependency Framework.framework.rsp' + link_file_list = self.xcode_settings.GetWrapperName() + if arch: + link_file_list += '.' + arch + link_file_list += '.rsp' + # If an rspfile contains spaces, ninja surrounds the filename with + # quotes around it and then passes it to open(), creating a file with + # quotes in its name (and when looking for the rsp file, the name + # makes it through bash which strips the quotes) :-/ + link_file_list = link_file_list.replace(' ', '_') + extra_bindings.append( + ('link_file_list', + gyp.common.EncodePOSIXShellArgument(link_file_list))) if self.flavor == 'win': extra_bindings.append(('binary', output)) - if '/NOENTRY' not in ldflags: + if ('/NOENTRY' not in ldflags and + not self.msvs_settings.GetNoImportLibrary(config_name)): self.target.import_lib = output + '.lib' extra_bindings.append(('implibflag', '/IMPLIB:%s' % self.target.import_lib)) @@ -1119,10 +1297,12 @@ class NinjaWriter: if len(solibs): - extra_bindings.append(('solibs', gyp.common.EncodePOSIXShellList(solibs))) + extra_bindings.append(('solibs', + gyp.common.EncodePOSIXShellList(sorted(solibs)))) ninja_file.build(output, command + command_suffix, link_deps, - implicit=list(implicit_deps), + implicit=sorted(implicit_deps), + order_only=list(order_deps), variables=extra_bindings) return linked_binary @@ -1137,7 +1317,7 @@ class NinjaWriter: self.target.type = 'none' elif spec['type'] == 'static_library': self.target.binary = self.ComputeOutput(spec) - if (self.flavor not in ('mac', 'openbsd', 'win') and not + if (self.flavor not in ('mac', 'openbsd', 'netbsd', 'win') and not self.is_standalone_static_library): self.ninja.build(self.target.binary, 'alink_thin', link_deps, order_only=compile_deps) @@ -1174,7 +1354,8 @@ class NinjaWriter: # needed. variables=variables) else: - self.target.binary = self.WriteLink(spec, config_name, config, link_deps) + self.target.binary = self.WriteLink(spec, config_name, config, link_deps, + compile_deps) return self.target.binary def WriteMacBundle(self, spec, mac_bundle_depends, is_empty): @@ -1187,15 +1368,32 @@ class NinjaWriter: self.AppendPostbuildVariable(variables, spec, output, self.target.binary, is_command_start=not package_framework) if package_framework and not is_empty: - variables.append(('version', self.xcode_settings.GetFrameworkVersion())) - self.ninja.build(output, 'package_framework', mac_bundle_depends, - variables=variables) + if spec['type'] == 'shared_library' and self.xcode_settings.isIOS: + self.ninja.build(output, 'package_ios_framework', mac_bundle_depends, + variables=variables) + else: + variables.append(('version', self.xcode_settings.GetFrameworkVersion())) + self.ninja.build(output, 'package_framework', mac_bundle_depends, + variables=variables) else: self.ninja.build(output, 'stamp', mac_bundle_depends, variables=variables) self.target.bundle = output return output + def GetToolchainEnv(self, additional_settings=None): + """Returns the variables toolchain would set for build steps.""" + env = self.GetSortedXcodeEnv(additional_settings=additional_settings) + if self.flavor == 'win': + env = self.GetMsvsToolchainEnv( + additional_settings=additional_settings) + return env + + def GetMsvsToolchainEnv(self, additional_settings=None): + """Returns the variables Visual Studio would set for build steps.""" + return self.msvs_settings.GetVSMacroEnv('$!PRODUCT_DIR', + config=self.config_name) + def GetSortedXcodeEnv(self, additional_settings=None): """Returns the variables Xcode would set for build steps.""" assert self.abs_build_dir @@ -1377,7 +1575,8 @@ class NinjaWriter: values = [] ninja_file.variable(var, ' '.join(values)) - def WriteNewNinjaRule(self, name, args, description, is_cygwin, env): + def WriteNewNinjaRule(self, name, args, description, is_cygwin, env, pool, + depfile=None): """Write out a new ninja "rule" statement for a given command. Returns the name of the new rule, and a copy of |args| with variables @@ -1435,7 +1634,8 @@ class NinjaWriter: # GYP rules/actions express being no-ops by not touching their outputs. # Avoid executing downstream dependencies in this case by specifying # restat=1 to ninja. - self.ninja.rule(rule_name, command, description, restat=True, + self.ninja.rule(rule_name, command, description, depfile=depfile, + restat=True, pool=pool, rspfile=rspfile, rspfile_content=rspfile_content) self.ninja.newline() @@ -1466,12 +1666,13 @@ def CalculateVariables(default_variables, params): generator_extra_sources_for_rules = getattr(xcode_generator, 'generator_extra_sources_for_rules', []) elif flavor == 'win': + exts = gyp.MSVSUtil.TARGET_TYPE_EXT default_variables.setdefault('OS', 'win') - default_variables['EXECUTABLE_SUFFIX'] = '.exe' + default_variables['EXECUTABLE_SUFFIX'] = '.' + exts['executable'] default_variables['STATIC_LIB_PREFIX'] = '' - default_variables['STATIC_LIB_SUFFIX'] = '.lib' + default_variables['STATIC_LIB_SUFFIX'] = '.' + exts['static_library'] default_variables['SHARED_LIB_PREFIX'] = '' - default_variables['SHARED_LIB_SUFFIX'] = '.dll' + default_variables['SHARED_LIB_SUFFIX'] = '.' + exts['shared_library'] # Copy additional generator configuration data from VS, which is shared # by the Windows Ninja generator. @@ -1535,6 +1736,10 @@ def CommandWithWrapper(cmd, wrappers, prog): def GetDefaultConcurrentLinks(): """Returns a best-guess for a number of concurrent links.""" + pool_size = int(os.environ.get('GYP_LINK_CONCURRENCY', 0)) + if pool_size: + return pool_size + if sys.platform in ('win32', 'cygwin'): import ctypes @@ -1555,18 +1760,21 @@ def GetDefaultConcurrentLinks(): stat.dwLength = ctypes.sizeof(stat) ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat)) - mem_limit = max(1, stat.ullTotalPhys / (4 * (2 ** 30))) # total / 4GB - hard_cap = max(1, int(os.getenv('GYP_LINK_CONCURRENCY_MAX', 2**32))) + # VS 2015 uses 20% more working set than VS 2013 and can consume all RAM + # on a 64 GB machine. + mem_limit = max(1, stat.ullTotalPhys / (5 * (2 ** 30))) # total / 5GB + hard_cap = max(1, int(os.environ.get('GYP_LINK_CONCURRENCY_MAX', 2**32))) return min(mem_limit, hard_cap) elif sys.platform.startswith('linux'): - with open("/proc/meminfo") as meminfo: - memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB') - for line in meminfo: - match = memtotal_re.match(line) - if not match: - continue - # Allow 8Gb per link on Linux because Gold is quite memory hungry - return max(1, int(match.group(1)) / (8 * (2 ** 20))) + if os.path.exists("/proc/meminfo"): + with open("/proc/meminfo") as meminfo: + memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB') + for line in meminfo: + match = memtotal_re.match(line) + if not match: + continue + # Allow 8Gb per link on Linux because Gold is quite memory hungry + return max(1, int(match.group(1)) / (8 * (2 ** 20))) return 1 elif sys.platform == 'darwin': try: @@ -1653,7 +1861,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, master_ninja = ninja_syntax.Writer(master_ninja_file, width=120) # Put build-time support tools in out/{config_name}. - gyp.common.CopyTool(flavor, toplevel_build) + gyp.common.CopyTool(flavor, toplevel_build, generator_flags) # Grab make settings for CC/CXX. # The rules are @@ -1663,14 +1871,15 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # 'CC_host'/'CXX_host' enviroment variable, cc_host/cxx_host should be set # to cc/cxx. if flavor == 'win': - # Overridden by local arch choice in the use_deps case. - # Chromium's ffmpeg c99conv.py currently looks for a 'cc =' line in - # build.ninja so needs something valid here. http://crbug.com/233985 - cc = 'cl.exe' - cxx = 'cl.exe' + ar = 'lib.exe' + # cc and cxx must be set to the correct architecture by overriding with one + # of cl_x86 or cl_x64 below. + cc = 'UNSET' + cxx = 'UNSET' ld = 'link.exe' ld_host = '$ld' else: + ar = 'ar' cc = 'cc' cxx = 'c++' ld = '$cc' @@ -1678,11 +1887,16 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, ld_host = '$cc_host' ldxx_host = '$cxx_host' + ar_host = ar cc_host = None cxx_host = None cc_host_global_setting = None cxx_host_global_setting = None clang_cl = None + nm = 'nm' + nm_host = 'nm' + readelf = 'readelf' + readelf_host = 'readelf' build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) make_global_settings = data[build_file].get('make_global_settings', []) @@ -1690,6 +1904,10 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, options.toplevel_dir) wrappers = {} for key, value in make_global_settings: + if key == 'AR': + ar = os.path.join(build_to_root, value) + if key == 'AR.host': + ar_host = os.path.join(build_to_root, value) if key == 'CC': cc = os.path.join(build_to_root, value) if cc.endswith('clang-cl'): @@ -1702,6 +1920,18 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, if key == 'CXX.host': cxx_host = os.path.join(build_to_root, value) cxx_host_global_setting = value + if key == 'LD': + ld = os.path.join(build_to_root, value) + if key == 'LD.host': + ld_host = os.path.join(build_to_root, value) + if key == 'NM': + nm = os.path.join(build_to_root, value) + if key == 'NM.host': + nm_host = os.path.join(build_to_root, value) + if key == 'READELF': + readelf = os.path.join(build_to_root, value) + if key == 'READELF.host': + readelf_host = os.path.join(build_to_root, value) if key.endswith('_wrapper'): wrappers[key[:-len('_wrapper')]] = os.path.join(build_to_root, value) @@ -1712,10 +1942,21 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, key_prefix = re.sub(r'\.HOST$', '.host', key_prefix) wrappers[key_prefix] = os.path.join(build_to_root, value) + mac_toolchain_dir = generator_flags.get('mac_toolchain_dir', None) + if mac_toolchain_dir: + wrappers['LINK'] = "export DEVELOPER_DIR='%s' &&" % mac_toolchain_dir + if flavor == 'win': + configs = [target_dicts[qualified_target]['configurations'][config_name] + for qualified_target in target_list] + shared_system_includes = None + if not generator_flags.get('ninja_use_custom_environment_files', 0): + shared_system_includes = \ + gyp.msvs_emulation.ExtractSharedMSVSSystemIncludes( + configs, generator_flags) cl_paths = gyp.msvs_emulation.GenerateEnvironmentFiles( - toplevel_build, generator_flags, OpenOutput) - for arch, path in cl_paths.iteritems(): + toplevel_build, generator_flags, shared_system_includes, OpenOutput) + for arch, path in sorted(cl_paths.iteritems()): if clang_cl: # If we have selected clang-cl, use that instead. path = clang_cl @@ -1734,14 +1975,22 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, if flavor == 'win': master_ninja.variable('ld', ld) master_ninja.variable('idl', 'midl.exe') - master_ninja.variable('ar', 'lib.exe') + master_ninja.variable('ar', ar) master_ninja.variable('rc', 'rc.exe') - master_ninja.variable('asm', 'ml.exe') + master_ninja.variable('ml_x86', 'ml.exe') + master_ninja.variable('ml_x64', 'ml64.exe') master_ninja.variable('mt', 'mt.exe') else: master_ninja.variable('ld', CommandWithWrapper('LINK', wrappers, ld)) master_ninja.variable('ldxx', CommandWithWrapper('LINK', wrappers, ldxx)) - master_ninja.variable('ar', GetEnvironFallback(['AR_target', 'AR'], 'ar')) + master_ninja.variable('ar', GetEnvironFallback(['AR_target', 'AR'], ar)) + if flavor != 'mac': + # Mac does not use readelf/nm for .TOC generation, so avoiding polluting + # the master ninja with extra unused variables. + master_ninja.variable( + 'nm', GetEnvironFallback(['NM_target', 'NM'], nm)) + master_ninja.variable( + 'readelf', GetEnvironFallback(['READELF_target', 'READELF'], readelf)) if generator_supports_multiple_toolsets: if not cc_host: @@ -1749,7 +1998,10 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, if not cxx_host: cxx_host = cxx - master_ninja.variable('ar_host', GetEnvironFallback(['AR_host'], 'ar')) + master_ninja.variable('ar_host', GetEnvironFallback(['AR_host'], ar_host)) + master_ninja.variable('nm_host', GetEnvironFallback(['NM_host'], nm_host)) + master_ninja.variable('readelf_host', + GetEnvironFallback(['READELF_host'], readelf_host)) cc_host = GetEnvironFallback(['CC_host'], cc_host) cxx_host = GetEnvironFallback(['CXX_host'], cxx_host) @@ -1832,7 +2084,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, description='IDL $in', command=('%s gyp-win-tool midl-wrapper $arch $outdir ' '$tlb $h $dlldata $iid $proxy $in ' - '$idlflags' % sys.executable)) + '$midl_includes $idlflags' % sys.executable)) master_ninja.rule( 'rc', description='RC $in', @@ -1842,20 +2094,20 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, sys.executable)) master_ninja.rule( 'asm', - description='ASM $in', + description='ASM $out', command=('%s gyp-win-tool asm-wrapper ' - '$arch $asm $defines $includes /c /Fo $out $in' % + '$arch $asm $defines $includes $asmflags /c /Fo $out $in' % sys.executable)) if flavor != 'mac' and flavor != 'win': master_ninja.rule( 'alink', description='AR $out', - command='rm -f $out && $ar rcs $out $in') + command='rm -f $out && $ar rcs $arflags $out $in') master_ninja.rule( 'alink_thin', description='AR $out', - command='rm -f $out && $ar rcsT $out $in') + command='rm -f $out && $ar rcsT $arflags $out $in') # This allows targets that only need to depend on $lib's API to declare an # order-only dependency on $lib.TOC and avoid relinking such downstream @@ -1863,38 +2115,39 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # The resulting string leaves an uninterpolated %{suffix} which # is used in the final substitution below. mtime_preserving_solink_base = ( - 'if [ ! -e $lib -o ! -e ${lib}.TOC ]; then ' - '%(solink)s && %(extract_toc)s > ${lib}.TOC; else ' - '%(solink)s && %(extract_toc)s > ${lib}.tmp && ' - 'if ! cmp -s ${lib}.tmp ${lib}.TOC; then mv ${lib}.tmp ${lib}.TOC ; ' + 'if [ ! -e $lib -o ! -e $lib.TOC ]; then ' + '%(solink)s && %(extract_toc)s > $lib.TOC; else ' + '%(solink)s && %(extract_toc)s > $lib.tmp && ' + 'if ! cmp -s $lib.tmp $lib.TOC; then mv $lib.tmp $lib.TOC ; ' 'fi; fi' % { 'solink': '$ld -shared $ldflags -o $lib -Wl,-soname=$soname %(suffix)s', 'extract_toc': - ('{ readelf -d ${lib} | grep SONAME ; ' - 'nm -gD -f p ${lib} | cut -f1-2 -d\' \'; }')}) + ('{ $readelf -d $lib | grep SONAME ; ' + '$nm -gD -f p $lib | cut -f1-2 -d\' \'; }')}) master_ninja.rule( 'solink', description='SOLINK $lib', restat=True, - command=(mtime_preserving_solink_base % { - 'suffix': '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive ' - '$libs'}), + command=mtime_preserving_solink_base % {'suffix': '@$link_file_list'}, + rspfile='$link_file_list', + rspfile_content= + '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive $libs', pool='link_pool') master_ninja.rule( 'solink_module', description='SOLINK(module) $lib', restat=True, - command=(mtime_preserving_solink_base % { - 'suffix': '-Wl,--start-group $in $solibs -Wl,--end-group ' - '$libs'}), + command=mtime_preserving_solink_base % {'suffix': '@$link_file_list'}, + rspfile='$link_file_list', + rspfile_content='-Wl,--start-group $in -Wl,--end-group $solibs $libs', pool='link_pool') master_ninja.rule( 'link', description='LINK $out', command=('$ld $ldflags -o $out ' - '-Wl,--start-group $in $solibs -Wl,--end-group $libs'), + '-Wl,--start-group $in -Wl,--end-group $solibs $libs'), pool='link_pool') elif flavor == 'win': master_ninja.rule( @@ -1933,21 +2186,31 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, 'lipo', description='LIPO $out, POSTBUILDS', command='rm -f $out && lipo -create $in -output $out$postbuilds') + master_ninja.rule( + 'solipo', + description='SOLIPO $out, POSTBUILDS', + command=( + 'rm -f $lib $lib.TOC && lipo -create $in -output $lib$postbuilds &&' + '%(extract_toc)s > $lib.TOC' + % { 'extract_toc': + '{ otool -l $lib | grep LC_ID_DYLIB -A 5; ' + 'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'})) + # Record the public interface of $lib in $lib.TOC. See the corresponding # comment in the posix section above for details. solink_base = '$ld %(type)s $ldflags -o $lib %(suffix)s' mtime_preserving_solink_base = ( - 'if [ ! -e $lib -o ! -e ${lib}.TOC ] || ' + 'if [ ! -e $lib -o ! -e $lib.TOC ] || ' # Always force dependent targets to relink if this library # reexports something. Handling this correctly would require # recursive TOC dumping but this is rare in practice, so punt. 'otool -l $lib | grep -q LC_REEXPORT_DYLIB ; then ' - '%(solink)s && %(extract_toc)s > ${lib}.TOC; ' + '%(solink)s && %(extract_toc)s > $lib.TOC; ' 'else ' - '%(solink)s && %(extract_toc)s > ${lib}.tmp && ' - 'if ! cmp -s ${lib}.tmp ${lib}.TOC; then ' - 'mv ${lib}.tmp ${lib}.TOC ; ' + '%(solink)s && %(extract_toc)s > $lib.tmp && ' + 'if ! cmp -s $lib.tmp $lib.TOC; then ' + 'mv $lib.tmp $lib.TOC ; ' 'fi; ' 'fi' % { 'solink': solink_base, @@ -1955,34 +2218,42 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, '{ otool -l $lib | grep LC_ID_DYLIB -A 5; ' 'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'}) - solink_suffix = '$in $solibs $libs$postbuilds' + + solink_suffix = '@$link_file_list$postbuilds' master_ninja.rule( 'solink', description='SOLINK $lib, POSTBUILDS', restat=True, command=mtime_preserving_solink_base % {'suffix': solink_suffix, 'type': '-shared'}, + rspfile='$link_file_list', + rspfile_content='$in $solibs $libs', pool='link_pool') master_ninja.rule( 'solink_notoc', description='SOLINK $lib, POSTBUILDS', restat=True, command=solink_base % {'suffix':solink_suffix, 'type': '-shared'}, + rspfile='$link_file_list', + rspfile_content='$in $solibs $libs', pool='link_pool') - solink_module_suffix = '$in $solibs $libs$postbuilds' master_ninja.rule( 'solink_module', description='SOLINK(module) $lib, POSTBUILDS', restat=True, - command=mtime_preserving_solink_base % {'suffix': solink_module_suffix, + command=mtime_preserving_solink_base % {'suffix': solink_suffix, 'type': '-bundle'}, + rspfile='$link_file_list', + rspfile_content='$in $solibs $libs', pool='link_pool') master_ninja.rule( 'solink_module_notoc', description='SOLINK(module) $lib, POSTBUILDS', restat=True, - command=solink_base % {'suffix': solink_module_suffix, 'type': '-bundle'}, + command=solink_base % {'suffix': solink_suffix, 'type': '-bundle'}, + rspfile='$link_file_list', + rspfile_content='$in $solibs $libs', pool='link_pool') master_ninja.rule( @@ -1999,16 +2270,35 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, master_ninja.rule( 'copy_infoplist', description='COPY INFOPLIST $in', - command='$env ./gyp-mac-tool copy-info-plist $in $out $keys') + command='$env ./gyp-mac-tool copy-info-plist $in $out $binary $keys') + master_ninja.rule( + 'merge_infoplist', + description='MERGE INFOPLISTS $in', + command='$env ./gyp-mac-tool merge-info-plist $out $in') + master_ninja.rule( + 'compile_xcassets', + description='COMPILE XCASSETS $in', + command='$env ./gyp-mac-tool compile-xcassets $keys $in') + master_ninja.rule( + 'compile_ios_framework_headers', + description='COMPILE HEADER MAPS AND COPY FRAMEWORK HEADERS $in', + command='$env ./gyp-mac-tool compile-ios-framework-header-map $out ' + '$framework $in && $env ./gyp-mac-tool ' + 'copy-ios-framework-headers $framework $copy_headers') master_ninja.rule( 'mac_tool', description='MACTOOL $mactool_cmd $in', - command='$env ./gyp-mac-tool $mactool_cmd $in $out') + command='$env ./gyp-mac-tool $mactool_cmd $in $out $binary') master_ninja.rule( 'package_framework', description='PACKAGE FRAMEWORK $out, POSTBUILDS', command='./gyp-mac-tool package-framework $out $version$postbuilds ' '&& touch $out') + master_ninja.rule( + 'package_ios_framework', + description='PACKAGE IOS FRAMEWORK $out, POSTBUILDS', + command='./gyp-mac-tool package-ios-framework $out $postbuilds ' + '&& touch $out') if flavor == 'win': master_ninja.rule( 'stamp', @@ -2043,6 +2333,15 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # objects. target_short_names = {} + # short name of targets that were skipped because they didn't contain anything + # interesting. + # NOTE: there may be overlap between this an non_empty_target_names. + empty_target_names = set() + + # Set of non-empty short target names. + # NOTE: there may be overlap between this an empty_target_names. + non_empty_target_names = set() + for qualified_target in target_list: # qualified_target is like: third_party/icu/icu.gyp:icui18n#target build_file, name, toolset = \ @@ -2057,7 +2356,15 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, if flavor == 'mac': gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[build_file], spec) - build_file = gyp.common.RelativePath(build_file, options.toplevel_dir) + # If build_file is a symlink, we must not follow it because there's a chance + # it could point to a path above toplevel_dir, and we cannot correctly deal + # with that case at the moment. + build_file = gyp.common.RelativePath(build_file, options.toplevel_dir, + False) + + qualified_target_for_hash = gyp.common.QualifiedTarget(build_file, name, + toolset) + hash_for_rules = hashlib.md5(qualified_target_for_hash).hexdigest() base_path = os.path.dirname(build_file) obj = 'obj' @@ -2066,7 +2373,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, output_file = os.path.join(obj, base_path, name + '.ninja') ninja_output = StringIO() - writer = NinjaWriter(qualified_target, target_outputs, base_path, build_dir, + writer = NinjaWriter(hash_for_rules, target_outputs, base_path, build_dir, ninja_output, toplevel_build, output_file, flavor, toplevel_dir=options.toplevel_dir) @@ -2086,6 +2393,9 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, target_outputs[qualified_target] = target if qualified_target in all_targets: all_outputs.add(target.FinalOutput()) + non_empty_target_names.add(name) + else: + empty_target_names.add(name) if target_short_names: # Write a short name to build this target. This benefits both the @@ -2093,13 +2403,23 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # able to run actions and build libraries by their short name. master_ninja.newline() master_ninja.comment('Short names for targets.') - for short_name in target_short_names: + for short_name in sorted(target_short_names): master_ninja.build(short_name, 'phony', [x.FinalOutput() for x in target_short_names[short_name]]) + # Write phony targets for any empty targets that weren't written yet. As + # short names are not necessarily unique only do this for short names that + # haven't already been output for another target. + empty_target_names = empty_target_names - non_empty_target_names + if empty_target_names: + master_ninja.newline() + master_ninja.comment('Empty targets (output for completeness).') + for name in sorted(empty_target_names): + master_ninja.build(name, 'phony') + if all_outputs: master_ninja.newline() - master_ninja.build('all', 'phony', list(all_outputs)) + master_ninja.build('all', 'phony', sorted(all_outputs)) master_ninja.default(generator_flags.get('default_target', 'all')) master_ninja_file.close() diff --git a/third_party/gyp/generator/ninja_test.py b/third_party/gyp/generator/ninja_test.py index 52661bcd..1767b2f4 100644 --- a/third_party/gyp/generator/ninja_test.py +++ b/third_party/gyp/generator/ninja_test.py @@ -15,15 +15,18 @@ import TestCommon class TestPrefixesAndSuffixes(unittest.TestCase): def test_BinaryNamesWindows(self): - writer = ninja.NinjaWriter('foo', 'wee', '.', '.', 'build.ninja', '.', - 'build.ninja', 'win') - spec = { 'target_name': 'wee' } - self.assertTrue(writer.ComputeOutputFileName(spec, 'executable'). - endswith('.exe')) - self.assertTrue(writer.ComputeOutputFileName(spec, 'shared_library'). - endswith('.dll')) - self.assertTrue(writer.ComputeOutputFileName(spec, 'static_library'). - endswith('.lib')) + # These cannot run on non-Windows as they require a VS installation to + # correctly handle variable expansion. + if sys.platform.startswith('win'): + writer = ninja.NinjaWriter('foo', 'wee', '.', '.', 'build.ninja', '.', + 'build.ninja', 'win') + spec = { 'target_name': 'wee' } + self.assertTrue(writer.ComputeOutputFileName(spec, 'executable'). + endswith('.exe')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'shared_library'). + endswith('.dll')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'static_library'). + endswith('.lib')) def test_BinaryNamesLinux(self): writer = ninja.NinjaWriter('foo', 'wee', '.', '.', 'build.ninja', '.', diff --git a/third_party/gyp/generator/xcode.py b/third_party/gyp/generator/xcode.py index 331e78ba..db99d6ab 100644 --- a/third_party/gyp/generator/xcode.py +++ b/third_party/gyp/generator/xcode.py @@ -5,6 +5,7 @@ import filecmp import gyp.common import gyp.xcodeproj_file +import gyp.xcode_ninja import errno import os import sys @@ -68,11 +69,15 @@ generator_additional_path_sections = [ # The Xcode-specific keys that exist on targets and aren't moved down to # configurations. generator_additional_non_configuration_keys = [ + 'ios_app_extension', + 'ios_watch_app', + 'ios_watchkit_extension', 'mac_bundle', 'mac_bundle_resources', 'mac_framework_headers', 'mac_framework_private_headers', 'mac_xctest_bundle', + 'mac_xcuitest_bundle', 'xcode_create_dependents_test_runner', ] @@ -83,6 +88,8 @@ generator_extra_sources_for_rules = [ 'mac_framework_private_headers', ] +generator_filelist_paths = None + # Xcode's standard set of library directories, which don't need to be duplicated # in LIBRARY_SEARCH_PATHS. This list is not exhaustive, but that's okay. xcode_standard_library_dirs = frozenset([ @@ -484,7 +491,7 @@ sys.exit(subprocess.call(sys.argv[1:]))" """ def AddSourceToTarget(source, type, pbxp, xct): # TODO(mark): Perhaps source_extensions and library_extensions can be made a # little bit fancier. - source_extensions = ['c', 'cc', 'cpp', 'cxx', 'm', 'mm', 's'] + source_extensions = ['c', 'cc', 'cpp', 'cxx', 'm', 'mm', 's', 'swift'] # .o is conceptually more of a "source" than a "library," but Xcode thinks # of "sources" as things to compile and "libraries" (or "frameworks") as @@ -520,7 +527,7 @@ def AddHeaderToTarget(header, pbxp, xct, is_public): xct.HeadersPhase().AddFile(header, settings) -_xcode_variable_re = re.compile('(\$\((.*?)\))') +_xcode_variable_re = re.compile(r'(\$\((.*?)\))') def ExpandXcodeVariables(string, expansions): """Expands Xcode-style $(VARIABLES) in string per the expansions dict. @@ -574,13 +581,47 @@ def PerformBuild(data, configurations, params): subprocess.check_call(arguments) +def CalculateGeneratorInputInfo(params): + toplevel = params['options'].toplevel_dir + if params.get('flavor') == 'ninja': + generator_dir = os.path.relpath(params['options'].generator_output or '.') + output_dir = params.get('generator_flags', {}).get('output_dir', 'out') + output_dir = os.path.normpath(os.path.join(generator_dir, output_dir)) + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, output_dir, 'gypfiles-xcode-ninja')) + else: + output_dir = os.path.normpath(os.path.join(toplevel, 'xcodebuild')) + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, output_dir, 'gypfiles')) + + global generator_filelist_paths + generator_filelist_paths = { + 'toplevel': toplevel, + 'qualified_out_dir': qualified_out_dir, + } + + def GenerateOutput(target_list, target_dicts, data, params): + # Optionally configure each spec to use ninja as the external builder. + ninja_wrapper = params.get('flavor') == 'ninja' + if ninja_wrapper: + (target_list, target_dicts, data) = \ + gyp.xcode_ninja.CreateWrapper(target_list, target_dicts, data, params) + options = params['options'] generator_flags = params.get('generator_flags', {}) parallel_builds = generator_flags.get('xcode_parallel_builds', True) serialize_all_tests = \ generator_flags.get('xcode_serialize_all_test_runs', True) - project_version = generator_flags.get('xcode_project_version', None) + upgrade_check_project_version = \ + generator_flags.get('xcode_upgrade_check_project_version', None) + + # Format upgrade_check_project_version with leading zeros as needed. + if upgrade_check_project_version: + upgrade_check_project_version = str(upgrade_check_project_version) + while len(upgrade_check_project_version) < 4: + upgrade_check_project_version = '0' + upgrade_check_project_version + skip_excluded_files = \ not generator_flags.get('xcode_list_excluded_files', True) xcode_projects = {} @@ -595,11 +636,17 @@ def GenerateOutput(target_list, target_dicts, data, params): xcode_projects[build_file] = xcp pbxp = xcp.project + # Set project-level attributes from multiple options + project_attributes = {}; if parallel_builds: - pbxp.SetProperty('attributes', - {'BuildIndependentTargetsInParallel': 'YES'}) - if project_version: - xcp.project_file.SetXcodeVersion(project_version) + project_attributes['BuildIndependentTargetsInParallel'] = 'YES' + if upgrade_check_project_version: + project_attributes['LastUpgradeCheck'] = upgrade_check_project_version + project_attributes['LastTestingUpgradeCheck'] = \ + upgrade_check_project_version + project_attributes['LastSwiftUpdateCheck'] = \ + upgrade_check_project_version + pbxp.SetProperty('attributes', project_attributes) # Add gyp/gypi files to project if not generator_flags.get('standalone'): @@ -637,14 +684,22 @@ def GenerateOutput(target_list, target_dicts, data, params): # com.googlecode.gyp.xcode.bundle, a pseudo-type that xcode.py interprets # to create a single-file mh_bundle. _types = { - 'executable': 'com.apple.product-type.tool', - 'loadable_module': 'com.googlecode.gyp.xcode.bundle', - 'shared_library': 'com.apple.product-type.library.dynamic', - 'static_library': 'com.apple.product-type.library.static', - 'executable+bundle': 'com.apple.product-type.application', - 'loadable_module+bundle': 'com.apple.product-type.bundle', - 'loadable_module+xctest': 'com.apple.product-type.bundle.unit-test', - 'shared_library+bundle': 'com.apple.product-type.framework', + 'executable': 'com.apple.product-type.tool', + 'loadable_module': 'com.googlecode.gyp.xcode.bundle', + 'shared_library': 'com.apple.product-type.library.dynamic', + 'static_library': 'com.apple.product-type.library.static', + 'mac_kernel_extension': 'com.apple.product-type.kernel-extension', + 'executable+bundle': 'com.apple.product-type.application', + 'loadable_module+bundle': 'com.apple.product-type.bundle', + 'loadable_module+xctest': 'com.apple.product-type.bundle.unit-test', + 'loadable_module+xcuitest': 'com.apple.product-type.bundle.ui-testing', + 'shared_library+bundle': 'com.apple.product-type.framework', + 'executable+extension+bundle': 'com.apple.product-type.app-extension', + 'executable+watch+extension+bundle': + 'com.apple.product-type.watchkit-extension', + 'executable+watch+bundle': + 'com.apple.product-type.application.watchapp', + 'mac_kernel_extension+bundle': 'com.apple.product-type.kernel-extension', } target_properties = { @@ -654,14 +709,35 @@ def GenerateOutput(target_list, target_dicts, data, params): type = spec['type'] is_xctest = int(spec.get('mac_xctest_bundle', 0)) + is_xcuitest = int(spec.get('mac_xcuitest_bundle', 0)) is_bundle = int(spec.get('mac_bundle', 0)) or is_xctest + is_app_extension = int(spec.get('ios_app_extension', 0)) + is_watchkit_extension = int(spec.get('ios_watchkit_extension', 0)) + is_watch_app = int(spec.get('ios_watch_app', 0)) if type != 'none': type_bundle_key = type - if is_xctest: + if is_xcuitest: + type_bundle_key += '+xcuitest' + assert type == 'loadable_module', ( + 'mac_xcuitest_bundle targets must have type loadable_module ' + '(target %s)' % target_name) + elif is_xctest: type_bundle_key += '+xctest' assert type == 'loadable_module', ( 'mac_xctest_bundle targets must have type loadable_module ' '(target %s)' % target_name) + elif is_app_extension: + assert is_bundle, ('ios_app_extension flag requires mac_bundle ' + '(target %s)' % target_name) + type_bundle_key += '+extension+bundle' + elif is_watchkit_extension: + assert is_bundle, ('ios_watchkit_extension flag requires mac_bundle ' + '(target %s)' % target_name) + type_bundle_key += '+watch+extension+bundle' + elif is_watch_app: + assert is_bundle, ('ios_watch_app flag requires mac_bundle ' + '(target %s)' % target_name) + type_bundle_key += '+watch+bundle' elif is_bundle: type_bundle_key += '+bundle' @@ -677,6 +753,9 @@ def GenerateOutput(target_list, target_dicts, data, params): assert not is_bundle, ( 'mac_bundle targets cannot have type none (target "%s")' % target_name) + assert not is_xcuitest, ( + 'mac_xcuitest_bundle targets cannot have type none (target "%s")' % + target_name) assert not is_xctest, ( 'mac_xctest_bundle targets cannot have type none (target "%s")' % target_name) @@ -703,11 +782,16 @@ def GenerateOutput(target_list, target_dicts, data, params): # and is made a dependency of this target. This way the work is done # before the dependency checks for what should be recompiled. support_xct = None - if type != 'none' and (spec_actions or spec_rules): + # The Xcode "issues" don't affect xcode-ninja builds, since the dependency + # logic all happens in ninja. Don't bother creating the extra targets in + # that case. + if type != 'none' and (spec_actions or spec_rules) and not ninja_wrapper: support_xccl = CreateXCConfigurationList(configuration_names); + support_target_suffix = generator_flags.get( + 'support_target_suffix', ' Support') support_target_properties = { 'buildConfigurationList': support_xccl, - 'name': target_name + ' Support', + 'name': target_name + support_target_suffix, } if target_product_name: support_target_properties['productName'] = \ @@ -1096,6 +1180,9 @@ exit 1 # Relative paths are relative to $(SRCROOT). dest = '$(SRCROOT)/' + dest + code_sign = int(copy_group.get('xcode_code_sign', 0)) + settings = (None, '{ATTRIBUTES = (CodeSignOnCopy, ); }')[code_sign]; + # Coalesce multiple "copies" sections in the same target with the same # "destination" property into the same PBXCopyFilesBuildPhase, otherwise # they'll wind up with ID collisions. @@ -1114,7 +1201,7 @@ exit 1 pbxcp_dict[dest] = pbxcp for file in copy_group['files']: - pbxcp.AddFile(file) + pbxcp.AddFile(file, settings) # Excluded files can also go into the project file. if not skip_excluded_files: diff --git a/third_party/gyp/input.py b/third_party/gyp/input.py index 6472912d..22eb333d 100644 --- a/third_party/gyp/input.py +++ b/third_party/gyp/input.py @@ -10,8 +10,8 @@ from compiler.ast import Module from compiler.ast import Node from compiler.ast import Stmt import compiler -import copy import gyp.common +import gyp.simple_copy import multiprocessing import optparse import os.path @@ -24,10 +24,16 @@ import threading import time import traceback from gyp.common import GypError +from gyp.common import OrderedSet # A list of types that are treated as linkable. -linkable_types = ['executable', 'shared_library', 'loadable_module'] +linkable_types = [ + 'executable', + 'shared_library', + 'loadable_module', + 'mac_kernel_extension', +] # A list of sections that contain links to other targets. dependency_sections = ['dependencies', 'export_dependent_settings'] @@ -45,18 +51,36 @@ base_path_sections = [ 'outputs', 'sources', ] -path_sections = [] +path_sections = set() -is_path_section_charset = set('=+?!') -is_path_section_match_re = re.compile('_(dir|file|path)s?$') +# These per-process dictionaries are used to cache build file data when loading +# in parallel mode. +per_process_data = {} +per_process_aux_data = {} def IsPathSection(section): - # If section ends in one of these characters, it's applied to a section + # If section ends in one of the '=+?!' characters, it's applied to a section # without the trailing characters. '/' is notably absent from this list, # because there's no way for a regular expression to be treated as a path. - while section[-1:] in is_path_section_charset: + while section and section[-1:] in '=+?!': section = section[:-1] - return section in path_sections or is_path_section_match_re.search(section) + + if section in path_sections: + return True + + # Sections mathing the regexp '_(dir|file|path)s?$' are also + # considered PathSections. Using manual string matching since that + # is much faster than the regexp and this can be called hundreds of + # thousands of times so micro performance matters. + if "_" in section: + tail = section[-6:] + if tail[-1] == 's': + tail = tail[:-1] + if tail[-5:] in ('_file', '_path'): + return True + return tail[-4:] == '_dir' + + return False # base_non_configuration_keys is a list of key names that belong in the target # itself and should not be propagated into its configurations. It is merged @@ -196,11 +220,11 @@ def CheckNode(node, keypath): elif isinstance(node, Const): return node.getChildren()[0] else: - raise TypeError, "Unknown AST node at key path '" + '.'.join(keypath) + \ - "': " + repr(node) + raise TypeError("Unknown AST node at key path '" + '.'.join(keypath) + + "': " + repr(node)) -def LoadOneBuildFile(build_file_path, data, aux_data, variables, includes, +def LoadOneBuildFile(build_file_path, data, aux_data, includes, is_target, check): if build_file_path in data: return data[build_file_path] @@ -224,7 +248,7 @@ def LoadOneBuildFile(build_file_path, data, aux_data, variables, includes, gyp.common.ExceptionAppend(e, 'while reading ' + build_file_path) raise - if not isinstance(build_file_data, dict): + if type(build_file_data) is not dict: raise GypError("%s does not evaluate to a dictionary." % build_file_path) data[build_file_path] = build_file_data @@ -236,10 +260,10 @@ def LoadOneBuildFile(build_file_path, data, aux_data, variables, includes, try: if is_target: LoadBuildFileIncludesIntoDict(build_file_data, build_file_path, data, - aux_data, variables, includes, check) + aux_data, includes, check) else: LoadBuildFileIncludesIntoDict(build_file_data, build_file_path, data, - aux_data, variables, None, check) + aux_data, None, check) except Exception, e: gyp.common.ExceptionAppend(e, 'while reading includes of ' + build_file_path) @@ -249,7 +273,7 @@ def LoadOneBuildFile(build_file_path, data, aux_data, variables, includes, def LoadBuildFileIncludesIntoDict(subdict, subdict_path, data, aux_data, - variables, includes, check): + includes, check): includes_list = [] if includes != None: includes_list.extend(includes) @@ -273,30 +297,27 @@ def LoadBuildFileIncludesIntoDict(subdict, subdict_path, data, aux_data, gyp.DebugOutput(gyp.DEBUG_INCLUDES, "Loading Included File: '%s'", include) MergeDicts(subdict, - LoadOneBuildFile(include, data, aux_data, variables, None, - False, check), + LoadOneBuildFile(include, data, aux_data, None, False, check), subdict_path, include) # Recurse into subdictionaries. for k, v in subdict.iteritems(): - if v.__class__ == dict: - LoadBuildFileIncludesIntoDict(v, subdict_path, data, aux_data, variables, + if type(v) is dict: + LoadBuildFileIncludesIntoDict(v, subdict_path, data, aux_data, None, check) - elif v.__class__ == list: - LoadBuildFileIncludesIntoList(v, subdict_path, data, aux_data, variables, + elif type(v) is list: + LoadBuildFileIncludesIntoList(v, subdict_path, data, aux_data, check) # This recurses into lists so that it can look for dicts. -def LoadBuildFileIncludesIntoList(sublist, sublist_path, data, aux_data, - variables, check): +def LoadBuildFileIncludesIntoList(sublist, sublist_path, data, aux_data, check): for item in sublist: - if item.__class__ == dict: + if type(item) is dict: LoadBuildFileIncludesIntoDict(item, sublist_path, data, aux_data, - variables, None, check) - elif item.__class__ == list: - LoadBuildFileIncludesIntoList(item, sublist_path, data, aux_data, - variables, check) + None, check) + elif type(item) is list: + LoadBuildFileIncludesIntoList(item, sublist_path, data, aux_data, check) # Processes toolsets in all the targets. This recurses into condition entries # since they can contain toolsets as well. @@ -320,7 +341,7 @@ def ProcessToolsetsInDict(data): if len(toolsets) > 0: # Optimization: only do copies if more than one toolset is specified. for build in toolsets[1:]: - new_target = copy.deepcopy(target) + new_target = gyp.simple_copy.deepcopy(target) new_target['toolset'] = build new_target_list.append(new_target) target['toolset'] = toolsets[0] @@ -328,9 +349,10 @@ def ProcessToolsetsInDict(data): data['targets'] = new_target_list if 'conditions' in data: for condition in data['conditions']: - if isinstance(condition, list): + if type(condition) is list: for condition_dict in condition[1:]: - ProcessToolsetsInDict(condition_dict) + if type(condition_dict) is dict: + ProcessToolsetsInDict(condition_dict) # TODO(mark): I don't love this name. It just means that it's going to load @@ -350,15 +372,22 @@ def LoadTargetBuildFile(build_file_path, data, aux_data, variables, includes, else: variables['DEPTH'] = d.replace('\\', '/') - if build_file_path in data['target_build_files']: - # Already loaded. - return False - data['target_build_files'].add(build_file_path) + # The 'target_build_files' key is only set when loading target build files in + # the non-parallel code path, where LoadTargetBuildFile is called + # recursively. In the parallel code path, we don't need to check whether the + # |build_file_path| has already been loaded, because the 'scheduled' set in + # ParallelState guarantees that we never load the same |build_file_path| + # twice. + if 'target_build_files' in data: + if build_file_path in data['target_build_files']: + # Already loaded. + return False + data['target_build_files'].add(build_file_path) gyp.DebugOutput(gyp.DEBUG_INCLUDES, "Loading Target Build File '%s'", build_file_path) - build_file_data = LoadOneBuildFile(build_file_path, data, aux_data, variables, + build_file_data = LoadOneBuildFile(build_file_path, data, aux_data, includes, True, check) # Store DEPTH for later use in generators. @@ -408,7 +437,8 @@ def LoadTargetBuildFile(build_file_path, data, aux_data, variables, includes, # copy with the target-specific data merged into it as the replacement # target dict. old_target_dict = build_file_data['targets'][index] - new_target_dict = copy.deepcopy(build_file_data['target_defaults']) + new_target_dict = gyp.simple_copy.deepcopy( + build_file_data['target_defaults']) MergeDicts(new_target_dict, old_target_dict, build_file_path, build_file_path) build_file_data['targets'][index] = new_target_dict @@ -443,10 +473,8 @@ def LoadTargetBuildFile(build_file_path, data, aux_data, variables, includes, else: return (build_file_path, dependencies) - def CallLoadTargetBuildFile(global_flags, - build_file_path, data, - aux_data, variables, + build_file_path, variables, includes, depth, check, generator_input_info): """Wrapper around LoadTargetBuildFile for parallel processing. @@ -462,35 +490,24 @@ def CallLoadTargetBuildFile(global_flags, for key, value in global_flags.iteritems(): globals()[key] = value - # Save the keys so we can return data that changed. - data_keys = set(data) - aux_data_keys = set(aux_data) - SetGeneratorGlobals(generator_input_info) - result = LoadTargetBuildFile(build_file_path, data, - aux_data, variables, + result = LoadTargetBuildFile(build_file_path, per_process_data, + per_process_aux_data, variables, includes, depth, check, False) if not result: return result (build_file_path, dependencies) = result - data_out = {} - for key in data: - if key == 'target_build_files': - continue - if key not in data_keys: - data_out[key] = data[key] - aux_data_out = {} - for key in aux_data: - if key not in aux_data_keys: - aux_data_out[key] = aux_data[key] + # We can safely pop the build_file_data from per_process_data because it + # will never be referenced by this process again, so we don't need to keep + # it in the cache. + build_file_data = per_process_data.pop(build_file_path) # This gets serialized and sent back to the main process via a pipe. # It's handled in LoadTargetBuildFileCallback. return (build_file_path, - data_out, - aux_data_out, + build_file_data, dependencies) except GypError, e: sys.stderr.write("gyp: %s\n" % e) @@ -521,8 +538,6 @@ class ParallelState(object): self.condition = None # The "data" dict that was passed to LoadTargetBuildFileParallel self.data = None - # The "aux_data" dict that was passed to LoadTargetBuildFileParallel - self.aux_data = None # The number of parallel calls outstanding; decremented when a response # was received. self.pending = 0 @@ -543,12 +558,9 @@ class ParallelState(object): self.condition.notify() self.condition.release() return - (build_file_path0, data0, aux_data0, dependencies0) = result + (build_file_path0, build_file_data0, dependencies0) = result + self.data[build_file_path0] = build_file_data0 self.data['target_build_files'].add(build_file_path0) - for key in data0: - self.data[key] = data0[key] - for key in aux_data0: - self.aux_data[key] = aux_data0[key] for new_dependency in dependencies0: if new_dependency not in self.scheduled: self.scheduled.add(new_dependency) @@ -558,9 +570,8 @@ class ParallelState(object): self.condition.release() -def LoadTargetBuildFilesParallel(build_files, data, aux_data, - variables, includes, depth, check, - generator_input_info): +def LoadTargetBuildFilesParallel(build_files, data, variables, includes, depth, + check, generator_input_info): parallel_state = ParallelState() parallel_state.condition = threading.Condition() # Make copies of the build_files argument that we can modify while working. @@ -568,7 +579,6 @@ def LoadTargetBuildFilesParallel(build_files, data, aux_data, parallel_state.scheduled = set(build_files) parallel_state.pending = 0 parallel_state.data = data - parallel_state.aux_data = aux_data try: parallel_state.condition.acquire() @@ -582,20 +592,16 @@ def LoadTargetBuildFilesParallel(build_files, data, aux_data, dependency = parallel_state.dependencies.pop() parallel_state.pending += 1 - data_in = {} - data_in['target_build_files'] = data['target_build_files'] - aux_data_in = {} global_flags = { 'path_sections': globals()['path_sections'], 'non_configuration_keys': globals()['non_configuration_keys'], 'multiple_toolsets': globals()['multiple_toolsets']} if not parallel_state.pool: - parallel_state.pool = multiprocessing.Pool(8) + parallel_state.pool = multiprocessing.Pool(multiprocessing.cpu_count()) parallel_state.pool.apply_async( CallLoadTargetBuildFile, args = (global_flags, dependency, - data_in, aux_data_in, variables, includes, depth, check, generator_input_info), callback = parallel_state.LoadTargetBuildFileCallback) except KeyboardInterrupt, e: @@ -636,39 +642,50 @@ def FindEnclosingBracketGroup(input_str): return (-1, -1) -canonical_int_re = re.compile('(0|-?[1-9][0-9]*)$') - - def IsStrCanonicalInt(string): """Returns True if |string| is in its canonical integer form. The canonical form is such that str(int(string)) == string. """ - return isinstance(string, str) and canonical_int_re.match(string) + if type(string) is str: + # This function is called a lot so for maximum performance, avoid + # involving regexps which would otherwise make the code much + # shorter. Regexps would need twice the time of this function. + if string: + if string == "0": + return True + if string[0] == "-": + string = string[1:] + if not string: + return False + if '1' <= string[0] <= '9': + return string.isdigit() + + return False # This matches things like "<(asdf)", "(?P<(?:(?:!?@?)|\|)?)' - '(?P[-a-zA-Z0-9_.]+)?' - '\((?P\s*\[?)' - '(?P.*?)(\]?)\))') + r'(?P(?P<(?:(?:!?@?)|\|)?)' + r'(?P[-a-zA-Z0-9_.]+)?' + r'\((?P\s*\[?)' + r'(?P.*?)(\]?)\))') # This matches the same as early_variable_re, but with '>' instead of '<'. late_variable_re = re.compile( - '(?P(?P>(?:(?:!?@?)|\|)?)' - '(?P[-a-zA-Z0-9_.]+)?' - '\((?P\s*\[?)' - '(?P.*?)(\]?)\))') + r'(?P(?P>(?:(?:!?@?)|\|)?)' + r'(?P[-a-zA-Z0-9_.]+)?' + r'\((?P\s*\[?)' + r'(?P.*?)(\]?)\))') # This matches the same as early_variable_re, but with '^' instead of '<'. latelate_variable_re = re.compile( - '(?P(?P[\^](?:(?:!?@?)|\|)?)' - '(?P[-a-zA-Z0-9_.]+)?' - '\((?P\s*\[?)' - '(?P.*?)(\]?)\))') + r'(?P(?P[\^](?:(?:!?@?)|\|)?)' + r'(?P[-a-zA-Z0-9_.]+)?' + r'\((?P\s*\[?)' + r'(?P.*?)(\]?)\))') # Global cache of results from running commands so they don't have to be run # more then once. @@ -677,7 +694,7 @@ cached_command_results = {} def FixupPlatformCommand(cmd): if sys.platform == 'win32': - if type(cmd) == list: + if type(cmd) is list: cmd = [re.sub('^cat ', 'type ', cmd[0])] + cmd[1:] else: cmd = re.sub('^cat ', 'type ', cmd) @@ -767,7 +784,7 @@ def ExpandVariables(input, phase, variables, build_file): # contexts. However, since filtration has no chance to run on <|(), # this seems like the only obvious way to give them access to filters. if file_list: - processed_variables = copy.deepcopy(variables) + processed_variables = gyp.simple_copy.deepcopy(variables) ProcessListFiltersInDict(contents, processed_variables) # Recurse to expand variables in the contents contents = ExpandVariables(contents, phase, @@ -804,7 +821,7 @@ def ExpandVariables(input, phase, variables, build_file): # This works around actions/rules which have more inputs than will # fit on the command line. if file_list: - if type(contents) == list: + if type(contents) is list: contents_list = contents else: contents_list = contents.split(' ') @@ -837,17 +854,15 @@ def ExpandVariables(input, phase, variables, build_file): use_shell = False # Check for a cached value to avoid executing commands, or generating - # file lists more than once. - # TODO(http://code.google.com/p/gyp/issues/detail?id=112): It is - # possible that the command being invoked depends on the current - # directory. For that case the syntax needs to be extended so that the - # directory is also used in cache_key (it becomes a tuple). + # file lists more than once. The cache key contains the command to be + # run as well as the directory to run it from, to account for commands + # that depend on their current directory. # TODO(http://code.google.com/p/gyp/issues/detail?id=111): In theory, # someone could author a set of GYP files where each time the command # is invoked it produces different output by design. When the need # arises, the syntax should be extended to support no caching off a # command's output so it is run every time. - cache_key = str(contents) + cache_key = (str(contents), build_file_dir) cached_value = cached_command_results.get(cache_key, None) if cached_value is None: gyp.DebugOutput(gyp.DEBUG_VARIABLES, @@ -883,11 +898,15 @@ def ExpandVariables(input, phase, variables, build_file): else: # Fix up command with platform specific workarounds. contents = FixupPlatformCommand(contents) - p = subprocess.Popen(contents, shell=use_shell, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=subprocess.PIPE, - cwd=build_file_dir) + try: + p = subprocess.Popen(contents, shell=use_shell, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + cwd=build_file_dir) + except Exception, e: + raise GypError("%s while executing command '%s' in %s" % + (e, contents, build_file)) p_stdout, p_stderr = p.communicate('') @@ -895,8 +914,8 @@ def ExpandVariables(input, phase, variables, build_file): sys.stderr.write(p_stderr) # Simulate check_call behavior, since check_call only exists # in python 2.5 and later. - raise GypError("Call to '%s' returned exit status %d." % - (contents, p.returncode)) + raise GypError("Call to '%s' returned exit status %d while in %s." % + (contents, p.returncode, build_file)) replacement = p_stdout.rstrip() cached_command_results[cache_key] = replacement @@ -925,10 +944,9 @@ def ExpandVariables(input, phase, variables, build_file): else: replacement = variables[contents] - if isinstance(replacement, list): + if type(replacement) is list: for item in replacement: - if (not contents[-1] == '/' and - not isinstance(item, str) and not isinstance(item, int)): + if not contents[-1] == '/' and type(item) not in (str, int): raise GypError('Variable ' + contents + ' must expand to a string or list of strings; ' + 'list contains a ' + @@ -938,8 +956,7 @@ def ExpandVariables(input, phase, variables, build_file): # with conditions sections. ProcessVariablesAndConditionsInList(replacement, phase, variables, build_file) - elif not isinstance(replacement, str) and \ - not isinstance(replacement, int): + elif type(replacement) not in (str, int): raise GypError('Variable ' + contents + ' must expand to a string or list of strings; ' + 'found a ' + replacement.__class__.__name__) @@ -948,7 +965,7 @@ def ExpandVariables(input, phase, variables, build_file): # Expanding in list context. It's guaranteed that there's only one # replacement to do in |input_str| and that it's this replacement. See # above. - if isinstance(replacement, list): + if type(replacement) is list: # If it's already a list, make a copy. output = replacement[:] else: @@ -957,7 +974,7 @@ def ExpandVariables(input, phase, variables, build_file): else: # Expanding in string context. encoded_replacement = '' - if isinstance(replacement, list): + if type(replacement) is list: # When expanding a list into string context, turn the list items # into a string in a way that will work with a subprocess call. # @@ -975,26 +992,32 @@ def ExpandVariables(input, phase, variables, build_file): # Prepare for the next match iteration. input_str = output - # Look for more matches now that we've replaced some, to deal with - # expanding local variables (variables defined in the same - # variables block as this one). - gyp.DebugOutput(gyp.DEBUG_VARIABLES, "Found output %r, recursing.", output) - if isinstance(output, list): - if output and isinstance(output[0], list): - # Leave output alone if it's a list of lists. - # We don't want such lists to be stringified. - pass - else: - new_output = [] - for item in output: - new_output.append( - ExpandVariables(item, phase, variables, build_file)) - output = new_output + if output == input: + gyp.DebugOutput(gyp.DEBUG_VARIABLES, + "Found only identity matches on %r, avoiding infinite " + "recursion.", + output) else: - output = ExpandVariables(output, phase, variables, build_file) + # Look for more matches now that we've replaced some, to deal with + # expanding local variables (variables defined in the same + # variables block as this one). + gyp.DebugOutput(gyp.DEBUG_VARIABLES, "Found output %r, recursing.", output) + if type(output) is list: + if output and type(output[0]) is list: + # Leave output alone if it's a list of lists. + # We don't want such lists to be stringified. + pass + else: + new_output = [] + for item in output: + new_output.append( + ExpandVariables(item, phase, variables, build_file)) + output = new_output + else: + output = ExpandVariables(output, phase, variables, build_file) # Convert all strings that are canonically-represented integers into integers. - if isinstance(output, list): + if type(output) is list: for index in xrange(0, len(output)): if IsStrCanonicalInt(output[index]): output[index] = int(output[index]) @@ -1003,6 +1026,80 @@ def ExpandVariables(input, phase, variables, build_file): return output +# The same condition is often evaluated over and over again so it +# makes sense to cache as much as possible between evaluations. +cached_conditions_asts = {} + +def EvalCondition(condition, conditions_key, phase, variables, build_file): + """Returns the dict that should be used or None if the result was + that nothing should be used.""" + if type(condition) is not list: + raise GypError(conditions_key + ' must be a list') + if len(condition) < 2: + # It's possible that condition[0] won't work in which case this + # attempt will raise its own IndexError. That's probably fine. + raise GypError(conditions_key + ' ' + condition[0] + + ' must be at least length 2, not ' + str(len(condition))) + + i = 0 + result = None + while i < len(condition): + cond_expr = condition[i] + true_dict = condition[i + 1] + if type(true_dict) is not dict: + raise GypError('{} {} must be followed by a dictionary, not {}'.format( + conditions_key, cond_expr, type(true_dict))) + if len(condition) > i + 2 and type(condition[i + 2]) is dict: + false_dict = condition[i + 2] + i = i + 3 + if i != len(condition): + raise GypError('{} {} has {} unexpected trailing items'.format( + conditions_key, cond_expr, len(condition) - i)) + else: + false_dict = None + i = i + 2 + if result == None: + result = EvalSingleCondition( + cond_expr, true_dict, false_dict, phase, variables, build_file) + + return result + + +def EvalSingleCondition( + cond_expr, true_dict, false_dict, phase, variables, build_file): + """Returns true_dict if cond_expr evaluates to true, and false_dict + otherwise.""" + # Do expansions on the condition itself. Since the conditon can naturally + # contain variable references without needing to resort to GYP expansion + # syntax, this is of dubious value for variables, but someone might want to + # use a command expansion directly inside a condition. + cond_expr_expanded = ExpandVariables(cond_expr, phase, variables, + build_file) + if type(cond_expr_expanded) not in (str, int): + raise ValueError( + 'Variable expansion in this context permits str and int ' + \ + 'only, found ' + cond_expr_expanded.__class__.__name__) + + try: + if cond_expr_expanded in cached_conditions_asts: + ast_code = cached_conditions_asts[cond_expr_expanded] + else: + ast_code = compile(cond_expr_expanded, '', 'eval') + cached_conditions_asts[cond_expr_expanded] = ast_code + if eval(ast_code, {'__builtins__': None}, variables): + return true_dict + return false_dict + except SyntaxError, e: + syntax_error = SyntaxError('%s while evaluating condition \'%s\' in %s ' + 'at character %d.' % + (str(e.args[0]), e.text, build_file, e.offset), + e.filename, e.lineno, e.offset, e.text) + raise syntax_error + except NameError, e: + gyp.common.ExceptionAppend(e, 'while evaluating condition \'%s\' in %s' % + (cond_expr_expanded, build_file)) + raise GypError(e) + def ProcessConditionsInDict(the_dict, phase, variables, build_file): # Process a 'conditions' or 'target_conditions' section in the_dict, @@ -1038,48 +1135,8 @@ def ProcessConditionsInDict(the_dict, phase, variables, build_file): del the_dict[conditions_key] for condition in conditions_list: - if not isinstance(condition, list): - raise GypError(conditions_key + ' must be a list') - if len(condition) != 2 and len(condition) != 3: - # It's possible that condition[0] won't work in which case this - # attempt will raise its own IndexError. That's probably fine. - raise GypError(conditions_key + ' ' + condition[0] + - ' must be length 2 or 3, not ' + str(len(condition))) - - [cond_expr, true_dict] = condition[0:2] - false_dict = None - if len(condition) == 3: - false_dict = condition[2] - - # Do expansions on the condition itself. Since the conditon can naturally - # contain variable references without needing to resort to GYP expansion - # syntax, this is of dubious value for variables, but someone might want to - # use a command expansion directly inside a condition. - cond_expr_expanded = ExpandVariables(cond_expr, phase, variables, - build_file) - if not isinstance(cond_expr_expanded, str) and \ - not isinstance(cond_expr_expanded, int): - raise ValueError, \ - 'Variable expansion in this context permits str and int ' + \ - 'only, found ' + expanded.__class__.__name__ - - try: - ast_code = compile(cond_expr_expanded, '', 'eval') - - if eval(ast_code, {'__builtins__': None}, variables): - merge_dict = true_dict - else: - merge_dict = false_dict - except SyntaxError, e: - syntax_error = SyntaxError('%s while evaluating condition \'%s\' in %s ' - 'at character %d.' % - (str(e.args[0]), e.text, build_file, e.offset), - e.filename, e.lineno, e.offset, e.text) - raise syntax_error - except NameError, e: - gyp.common.ExceptionAppend(e, 'while evaluating condition \'%s\' in %s' % - (cond_expr_expanded, build_file)) - raise GypError(e) + merge_dict = EvalCondition(condition, conditions_key, phase, variables, + build_file) if merge_dict != None: # Expand variables and nested conditinals in the merge_dict before @@ -1094,8 +1151,7 @@ def LoadAutomaticVariablesFromDict(variables, the_dict): # Any keys with plain string values in the_dict become automatic variables. # The variable name is the key name with a "_" character prepended. for key, value in the_dict.iteritems(): - if isinstance(value, str) or isinstance(value, int) or \ - isinstance(value, list): + if type(value) in (str, int, list): variables['_' + key] = value @@ -1108,8 +1164,7 @@ def LoadVariablesFromVariablesDict(variables, the_dict, the_dict_key): # (it could be a list or it could be parentless because it is a root dict), # the_dict_key will be None. for key, value in the_dict.get('variables', {}).iteritems(): - if not isinstance(value, str) and not isinstance(value, int) and \ - not isinstance(value, list): + if type(value) not in (str, int, list): continue if key.endswith('%'): @@ -1162,12 +1217,12 @@ def ProcessVariablesAndConditionsInDict(the_dict, phase, variables_in, for key, value in the_dict.iteritems(): # Skip "variables", which was already processed if present. - if key != 'variables' and isinstance(value, str): + if key != 'variables' and type(value) is str: expanded = ExpandVariables(value, phase, variables, build_file) - if not isinstance(expanded, str) and not isinstance(expanded, int): - raise ValueError, \ + if type(expanded) not in (str, int): + raise ValueError( 'Variable expansion in this context permits str and int ' + \ - 'only, found ' + expanded.__class__.__name__ + ' for ' + key + 'only, found ' + expanded.__class__.__name__ + ' for ' + key) the_dict[key] = expanded # Variable expansion may have resulted in changes to automatics. Reload. @@ -1221,23 +1276,23 @@ def ProcessVariablesAndConditionsInDict(the_dict, phase, variables_in, for key, value in the_dict.iteritems(): # Skip "variables" and string values, which were already processed if # present. - if key == 'variables' or isinstance(value, str): + if key == 'variables' or type(value) is str: continue - if isinstance(value, dict): + if type(value) is dict: # Pass a copy of the variables dict so that subdicts can't influence # parents. ProcessVariablesAndConditionsInDict(value, phase, variables, build_file, key) - elif isinstance(value, list): + elif type(value) is list: # The list itself can't influence the variables dict, and # ProcessVariablesAndConditionsInList will make copies of the variables # dict if it needs to pass it to something that can influence it. No # copy is necessary here. ProcessVariablesAndConditionsInList(value, phase, variables, build_file) - elif not isinstance(value, int): - raise TypeError, 'Unknown type ' + value.__class__.__name__ + \ - ' for ' + key + elif type(value) is not int: + raise TypeError('Unknown type ' + value.__class__.__name__ + \ + ' for ' + key) def ProcessVariablesAndConditionsInList(the_list, phase, variables, @@ -1246,17 +1301,17 @@ def ProcessVariablesAndConditionsInList(the_list, phase, variables, index = 0 while index < len(the_list): item = the_list[index] - if isinstance(item, dict): + if type(item) is dict: # Make a copy of the variables dict so that it won't influence anything # outside of its own scope. ProcessVariablesAndConditionsInDict(item, phase, variables, build_file) - elif isinstance(item, list): + elif type(item) is list: ProcessVariablesAndConditionsInList(item, phase, variables, build_file) - elif isinstance(item, str): + elif type(item) is str: expanded = ExpandVariables(item, phase, variables, build_file) - if isinstance(expanded, str) or isinstance(expanded, int): + if type(expanded) in (str, int): the_list[index] = expanded - elif isinstance(expanded, list): + elif type(expanded) is list: the_list[index:index+1] = expanded index += len(expanded) @@ -1264,13 +1319,13 @@ def ProcessVariablesAndConditionsInList(the_list, phase, variables, # without falling into the index increment below. continue else: - raise ValueError, \ + raise ValueError( 'Variable expansion in this context permits strings and ' + \ 'lists only, found ' + expanded.__class__.__name__ + ' at ' + \ - index - elif not isinstance(item, int): - raise TypeError, 'Unknown type ' + item.__class__.__name__ + \ - ' at index ' + index + index) + elif type(item) is not int: + raise TypeError('Unknown type ' + item.__class__.__name__ + \ + ' at index ' + index) index = index + 1 @@ -1443,6 +1498,20 @@ def RemoveSelfDependencies(targets): target_dict[dependency_key] = Filter(dependencies, target_name) +def RemoveLinkDependenciesFromNoneTargets(targets): + """Remove dependencies having the 'link_dependency' attribute from the 'none' + targets.""" + for target_name, target_dict in targets.iteritems(): + for dependency_key in dependency_sections: + dependencies = target_dict.get(dependency_key, []) + if dependencies: + for t in dependencies: + if target_dict.get('type', None) == 'none': + if targets[t].get('variables', {}).get('link_dependency', 0): + target_dict[dependency_key] = \ + Filter(target_dict[dependency_key], t) + + class DependencyGraphNode(object): """ @@ -1468,13 +1537,17 @@ class DependencyGraphNode(object): # are the "ref" attributes of DependencyGraphNodes. Every target will # appear in flat_list after all of its dependencies, and before all of its # dependents. - flat_list = [] + flat_list = OrderedSet() + + def ExtractNodeRef(node): + """Extracts the object that the node represents from the given node.""" + return node.ref # in_degree_zeros is the list of DependencyGraphNodes that have no # dependencies not in flat_list. Initially, it is a copy of the children # of this node, because when the graph was built, nodes with no # dependencies were made implicit dependents of the root node. - in_degree_zeros = set(self.dependents[:]) + in_degree_zeros = sorted(self.dependents[:], key=ExtractNodeRef) while in_degree_zeros: # Nodes in in_degree_zeros have no dependencies not in flat_list, so they @@ -1482,13 +1555,17 @@ class DependencyGraphNode(object): # as work progresses, so that the next node to process from the list can # always be accessed at a consistent position. node = in_degree_zeros.pop() - flat_list.append(node.ref) + flat_list.add(node.ref) # Look at dependents of the node just added to flat_list. Some of them # may now belong in in_degree_zeros. - for node_dependent in node.dependents: + for node_dependent in sorted(node.dependents, key=ExtractNodeRef): is_in_degree_zero = True - for node_dependent_dependency in node_dependent.dependencies: + # TODO: We want to check through the + # node_dependent.dependencies list but if it's long and we + # always start at the beginning, then we get O(n^2) behaviour. + for node_dependent_dependency in (sorted(node_dependent.dependencies, + key=ExtractNodeRef)): if not node_dependent_dependency.ref in flat_list: # The dependent one or more dependencies not in flat_list. There # will be more chances to add it to flat_list when examining @@ -1501,30 +1578,29 @@ class DependencyGraphNode(object): # All of the dependent's dependencies are already in flat_list. Add # it to in_degree_zeros where it will be processed in a future # iteration of the outer loop. - in_degree_zeros.add(node_dependent) + in_degree_zeros += [node_dependent] - return flat_list + return list(flat_list) - def FindCycles(self, path=None): + def FindCycles(self): """ Returns a list of cycles in the graph, where each cycle is its own list. """ - if path is None: - path = [self] - results = [] - for node in self.dependents: - if node in path: - cycle = [node] - for part in path: - cycle.append(part) - if part == node: - break - results.append(tuple(cycle)) - else: - results.extend(node.FindCycles([node] + path)) + visited = set() - return list(set(results)) + def Visit(node, path): + for child in node.dependents: + if child in path: + results.append([child] + path[:path.index(child) + 1]) + elif not child in visited: + visited.add(child) + Visit(child, [child] + path) + + visited.add(self) + Visit(self, [self]) + + return results def DirectDependencies(self, dependencies=None): """Returns a list of just direct dependencies.""" @@ -1589,21 +1665,26 @@ class DependencyGraphNode(object): return self._AddImportedDependencies(targets, dependencies) def DeepDependencies(self, dependencies=None): - """Returns a list of all of a target's dependencies, recursively.""" - if dependencies == None: - dependencies = [] + """Returns an OrderedSet of all of a target's dependencies, recursively.""" + if dependencies is None: + # Using a list to get ordered output and a set to do fast "is it + # already added" checks. + dependencies = OrderedSet() for dependency in self.dependencies: # Check for None, corresponding to the root node. - if dependency.ref != None and dependency.ref not in dependencies: - dependencies.append(dependency.ref) + if dependency.ref is None: + continue + if dependency.ref not in dependencies: dependency.DeepDependencies(dependencies) + dependencies.add(dependency.ref) return dependencies def _LinkDependenciesInternal(self, targets, include_shared_libraries, dependencies=None, initial=True): - """Returns a list of dependency targets that are linked into this target. + """Returns an OrderedSet of dependency targets that are linked + into this target. This function has a split personality, depending on the setting of |initial|. Outside callers should always leave |initial| at its default @@ -1616,11 +1697,13 @@ class DependencyGraphNode(object): If |include_shared_libraries| is False, the resulting dependencies will not include shared_library targets that are linked into this target. """ - if dependencies == None: - dependencies = [] + if dependencies is None: + # Using a list to get ordered output and a set to do fast "is it + # already added" checks. + dependencies = OrderedSet() # Check for None, corresponding to the root node. - if self.ref == None: + if self.ref is None: return dependencies # It's kind of sucky that |targets| has to be passed into this function, @@ -1648,15 +1731,15 @@ class DependencyGraphNode(object): # Don't traverse 'none' targets if explicitly excluded. if (target_type == 'none' and not targets[self.ref].get('dependencies_traverse', True)): - if self.ref not in dependencies: - dependencies.append(self.ref) + dependencies.add(self.ref) return dependencies - # Executables and loadable modules are already fully and finally linked. - # Nothing else can be a link dependency of them, there can only be - # dependencies in the sense that a dependent target might run an - # executable or load the loadable_module. - if not initial and target_type in ('executable', 'loadable_module'): + # Executables, mac kernel extensions and loadable modules are already fully + # and finally linked. Nothing else can be a link dependency of them, there + # can only be dependencies in the sense that a dependent target might run + # an executable or load the loadable_module. + if not initial and target_type in ('executable', 'loadable_module', + 'mac_kernel_extension'): return dependencies # Shared libraries are already fully linked. They should only be included @@ -1671,7 +1754,7 @@ class DependencyGraphNode(object): # The target is linkable, add it to the list of link dependencies. if self.ref not in dependencies: - dependencies.append(self.ref) + dependencies.add(self.ref) if initial or not is_linkable: # If this is a subsequent target and it's linkable, don't look any # further for linkable dependencies, as they'll already be linked into @@ -1735,12 +1818,22 @@ def BuildDependencyList(targets): flat_list = root_node.FlattenToList() # If there's anything left unvisited, there must be a circular dependency - # (cycle). If you need to figure out what's wrong, look for elements of - # targets that are not in flat_list. + # (cycle). if len(flat_list) != len(targets): + if not root_node.dependents: + # If all targets have dependencies, add the first target as a dependent + # of root_node so that the cycle can be discovered from root_node. + target = targets.keys()[0] + target_node = dependency_nodes[target] + target_node.dependencies.append(root_node) + root_node.dependents.append(target_node) + + cycles = [] + for cycle in root_node.FindCycles(): + paths = [node.ref for node in cycle] + cycles.append('Cycle: %s' % ' -> '.join(paths)) raise DependencyGraphNode.CircularException( - 'Some targets not reachable, cycle in dependency graph detected: ' + - ' '.join(set(flat_list) ^ set(targets))) + 'Cycles in dependency graph detected:\n' + '\n'.join(cycles)) return [dependency_nodes, flat_list] @@ -1790,20 +1883,18 @@ def VerifyNoGYPFileCircularDependencies(targets): # If there's anything left unvisited, there must be a circular dependency # (cycle). if len(flat_list) != len(dependency_nodes): - bad_files = [] - for file in dependency_nodes.iterkeys(): - if not file in flat_list: - bad_files.append(file) - common_path_prefix = os.path.commonprefix(dependency_nodes) + if not root_node.dependents: + # If all files have dependencies, add the first file as a dependent + # of root_node so that the cycle can be discovered from root_node. + file_node = dependency_nodes.values()[0] + file_node.dependencies.append(root_node) + root_node.dependents.append(file_node) cycles = [] for cycle in root_node.FindCycles(): - simplified_paths = [] - for node in cycle: - assert(node.ref.startswith(common_path_prefix)) - simplified_paths.append(node.ref[len(common_path_prefix):]) - cycles.append('Cycle: %s' % ' -> '.join(simplified_paths)) - raise DependencyGraphNode.CircularException, \ - 'Cycles in .gyp file dependency graph detected:\n' + '\n'.join(cycles) + paths = [node.ref for node in cycle] + cycles.append('Cycle: %s' % ' -> '.join(paths)) + raise DependencyGraphNode.CircularException( + 'Cycles in .gyp file dependency graph detected:\n' + '\n'.join(cycles)) def DoDependentSettings(key, flat_list, targets, dependency_nodes): @@ -1966,25 +2057,25 @@ def MergeLists(to, fro, to_file, fro_file, is_paths=False, append=True): hashable_to_set = set(x for x in to if is_hashable(x)) for item in fro: singleton = False - if isinstance(item, str) or isinstance(item, int): + if type(item) in (str, int): # The cheap and easy case. if is_paths: to_item = MakePathRelative(to_file, fro_file, item) else: to_item = item - if not isinstance(item, str) or not item.startswith('-'): + if not (type(item) is str and item.startswith('-')): # Any string that doesn't begin with a "-" is a singleton - it can # only appear once in a list, to be enforced by the list merge append # or prepend. singleton = True - elif isinstance(item, dict): + elif type(item) is dict: # Make a copy of the dictionary, continuing to look for paths to fix. # The other intelligent aspects of merge processing won't apply because # item is being merged into an empty dict. to_item = {} MergeDicts(to_item, item, to_file, fro_file) - elif isinstance(item, list): + elif type(item) is list: # Recurse, making a copy of the list. If the list contains any # descendant dicts, path fixing will occur. Note that here, custom # values for is_paths and append are dropped; those are only to be @@ -1993,9 +2084,9 @@ def MergeLists(to, fro, to_file, fro_file, is_paths=False, append=True): to_item = [] MergeLists(to_item, item, to_file, fro_file) else: - raise TypeError, \ + raise TypeError( 'Attempt to merge list item of unsupported type ' + \ - item.__class__.__name__ + item.__class__.__name__) if append: # If appending a singleton that's already in the list, don't append. @@ -2030,30 +2121,30 @@ def MergeDicts(to, fro, to_file, fro_file): # modified. if k in to: bad_merge = False - if isinstance(v, str) or isinstance(v, int): - if not (isinstance(to[k], str) or isinstance(to[k], int)): + if type(v) in (str, int): + if type(to[k]) not in (str, int): bad_merge = True - elif v.__class__ != to[k].__class__: + elif type(v) is not type(to[k]): bad_merge = True if bad_merge: - raise TypeError, \ + raise TypeError( 'Attempt to merge dict value of type ' + v.__class__.__name__ + \ ' into incompatible type ' + to[k].__class__.__name__ + \ - ' for key ' + k - if isinstance(v, str) or isinstance(v, int): + ' for key ' + k) + if type(v) in (str, int): # Overwrite the existing value, if any. Cheap and easy. is_path = IsPathSection(k) if is_path: to[k] = MakePathRelative(to_file, fro_file, v) else: to[k] = v - elif isinstance(v, dict): + elif type(v) is dict: # Recurse, guaranteeing copies will be made of objects that require it. if not k in to: to[k] = {} MergeDicts(to[k], v, to_file, fro_file) - elif isinstance(v, list): + elif type(v) is list: # Lists in dicts can be merged with different policies, depending on # how the key in the "from" dict (k, the from-key) is written. # @@ -2096,13 +2187,13 @@ def MergeDicts(to, fro, to_file, fro_file): # If the key ends in "?", the list will only be merged if it doesn't # already exist. continue - if not isinstance(to[list_base], list): + elif type(to[list_base]) is not list: # This may not have been checked above if merging in a list with an # extension character. - raise TypeError, \ + raise TypeError( 'Attempt to merge dict value of type ' + v.__class__.__name__ + \ ' into incompatible type ' + to[list_base].__class__.__name__ + \ - ' for key ' + list_base + '(' + k + ')' + ' for key ' + list_base + '(' + k + ')') else: to[list_base] = [] @@ -2114,9 +2205,9 @@ def MergeDicts(to, fro, to_file, fro_file): is_paths = IsPathSection(list_base) MergeLists(to[list_base], v, to_file, fro_file, is_paths, append) else: - raise TypeError, \ + raise TypeError( 'Attempt to merge dict value of unsupported type ' + \ - v.__class__.__name__ + ' for key ' + k + v.__class__.__name__ + ' for key ' + k) def MergeConfigWithInheritance(new_configuration_dict, build_file, @@ -2157,43 +2248,39 @@ def SetUpConfigurations(target, target_dict): if not 'configurations' in target_dict: target_dict['configurations'] = {'Default': {}} if not 'default_configuration' in target_dict: - concrete = [i for i in target_dict['configurations'].iterkeys() - if not target_dict['configurations'][i].get('abstract')] + concrete = [i for (i, config) in target_dict['configurations'].iteritems() + if not config.get('abstract')] target_dict['default_configuration'] = sorted(concrete)[0] - for configuration in target_dict['configurations'].keys(): - old_configuration_dict = target_dict['configurations'][configuration] + merged_configurations = {} + configs = target_dict['configurations'] + for (configuration, old_configuration_dict) in configs.iteritems(): # Skip abstract configurations (saves work only). if old_configuration_dict.get('abstract'): continue # Configurations inherit (most) settings from the enclosing target scope. # Get the inheritance relationship right by making a copy of the target # dict. - new_configuration_dict = copy.deepcopy(target_dict) - - # Take out the bits that don't belong in a "configurations" section. - # Since configuration setup is done before conditional, exclude, and rules - # processing, be careful with handling of the suffix characters used in - # those phases. - delete_keys = [] - for key in new_configuration_dict: + new_configuration_dict = {} + for (key, target_val) in target_dict.iteritems(): key_ext = key[-1:] if key_ext in key_suffixes: key_base = key[:-1] else: key_base = key - if key_base in non_configuration_keys: - delete_keys.append(key) - - for key in delete_keys: - del new_configuration_dict[key] + if not key_base in non_configuration_keys: + new_configuration_dict[key] = gyp.simple_copy.deepcopy(target_val) # Merge in configuration (with all its parents first). MergeConfigWithInheritance(new_configuration_dict, build_file, target_dict, configuration, []) - # Put the new result back into the target dict as a configuration. - target_dict['configurations'][configuration] = new_configuration_dict + merged_configurations[configuration] = new_configuration_dict + + # Put the new configurations back into the target dict as a configuration. + for configuration in merged_configurations.keys(): + target_dict['configurations'][configuration] = ( + merged_configurations[configuration]) # Now drop all the abstract ones. for configuration in target_dict['configurations'].keys(): @@ -2264,9 +2351,9 @@ def ProcessListFiltersInDict(name, the_dict): if operation != '!' and operation != '/': continue - if not isinstance(value, list): - raise ValueError, name + ' key ' + key + ' must be list, not ' + \ - value.__class__.__name__ + if type(value) is not list: + raise ValueError(name + ' key ' + key + ' must be list, not ' + \ + value.__class__.__name__) list_key = key[:-1] if list_key not in the_dict: @@ -2276,12 +2363,12 @@ def ProcessListFiltersInDict(name, the_dict): del_lists.append(key) continue - if not isinstance(the_dict[list_key], list): + if type(the_dict[list_key]) is not list: value = the_dict[list_key] - raise ValueError, name + ' key ' + list_key + \ - ' must be list, not ' + \ - value.__class__.__name__ + ' when applying ' + \ - {'!': 'exclusion', '/': 'regex'}[operation] + raise ValueError(name + ' key ' + list_key + \ + ' must be list, not ' + \ + value.__class__.__name__ + ' when applying ' + \ + {'!': 'exclusion', '/': 'regex'}[operation]) if not list_key in lists: lists.append(list_key) @@ -2330,8 +2417,8 @@ def ProcessListFiltersInDict(name, the_dict): action_value = 1 else: # This is an action that doesn't make any sense. - raise ValueError, 'Unrecognized action ' + action + ' in ' + name + \ - ' key ' + regex_key + raise ValueError('Unrecognized action ' + action + ' in ' + name + \ + ' key ' + regex_key) for index in xrange(0, len(the_list)): list_item = the_list[index] @@ -2378,17 +2465,17 @@ def ProcessListFiltersInDict(name, the_dict): # Now recurse into subdicts and lists that may contain dicts. for key, value in the_dict.iteritems(): - if isinstance(value, dict): + if type(value) is dict: ProcessListFiltersInDict(key, value) - elif isinstance(value, list): + elif type(value) is list: ProcessListFiltersInList(key, value) def ProcessListFiltersInList(name, the_list): for item in the_list: - if isinstance(item, dict): + if type(item) is dict: ProcessListFiltersInDict(name, item) - elif isinstance(item, list): + elif type(item) is list: ProcessListFiltersInList(name, item) @@ -2403,7 +2490,7 @@ def ValidateTargetType(target, target_dict): """ VALID_TARGET_TYPES = ('executable', 'loadable_module', 'static_library', 'shared_library', - 'none') + 'mac_kernel_extension', 'none') target_type = target_dict.get('type', None) if target_type not in VALID_TARGET_TYPES: raise GypError("Target %s has an invalid target type '%s'. " @@ -2416,9 +2503,11 @@ def ValidateTargetType(target, target_dict): target_type)) -def ValidateSourcesInTarget(target, target_dict, build_file): - # TODO: Check if MSVC allows this for loadable_module targets. - if target_dict.get('type', None) not in ('static_library', 'shared_library'): +def ValidateSourcesInTarget(target, target_dict, build_file, + duplicate_basename_check): + if not duplicate_basename_check: + return + if target_dict.get('type', None) != 'static_library': return sources = target_dict.get('sources', []) basenames = {} @@ -2438,8 +2527,8 @@ def ValidateSourcesInTarget(target, target_dict, build_file): if error: print('static library %s has several files with the same basename:\n' % - target + error + 'Some build systems, e.g. MSVC08, ' - 'cannot handle that.') + target + error + 'libtool on Mac cannot handle that. Use ' + '--no-duplicate-basename-check to disable this validation.') raise GypError('Duplicate basenames in sources section, see list above') @@ -2506,7 +2595,7 @@ def ValidateRunAsInTarget(target, target_dict, build_file): run_as = target_dict.get('run_as') if not run_as: return - if not isinstance(run_as, dict): + if type(run_as) is not dict: raise GypError("The 'run_as' in target %s from file %s should be a " "dictionary." % (target_name, build_file)) @@ -2515,17 +2604,17 @@ def ValidateRunAsInTarget(target, target_dict, build_file): raise GypError("The 'run_as' in target %s from file %s must have an " "'action' section." % (target_name, build_file)) - if not isinstance(action, list): + if type(action) is not list: raise GypError("The 'action' for 'run_as' in target %s from file %s " "must be a list." % (target_name, build_file)) working_directory = run_as.get('working_directory') - if working_directory and not isinstance(working_directory, str): + if working_directory and type(working_directory) is not str: raise GypError("The 'working_directory' for 'run_as' in target %s " "in file %s should be a string." % (target_name, build_file)) environment = run_as.get('environment') - if environment and not isinstance(environment, dict): + if environment and type(environment) is not dict: raise GypError("The 'environment' for 'run_as' in target %s " "in file %s should be a dictionary." % (target_name, build_file)) @@ -2555,17 +2644,17 @@ def TurnIntIntoStrInDict(the_dict): # Use items instead of iteritems because there's no need to try to look at # reinserted keys and their associated values. for k, v in the_dict.items(): - if isinstance(v, int): + if type(v) is int: v = str(v) the_dict[k] = v - elif isinstance(v, dict): + elif type(v) is dict: TurnIntIntoStrInDict(v) - elif isinstance(v, list): + elif type(v) is list: TurnIntIntoStrInList(v) - if isinstance(k, int): - the_dict[str(k)] = v + if type(k) is int: del the_dict[k] + the_dict[str(k)] = v def TurnIntIntoStrInList(the_list): @@ -2573,11 +2662,11 @@ def TurnIntIntoStrInList(the_list): """ for index in xrange(0, len(the_list)): item = the_list[index] - if isinstance(item, int): + if type(item) is int: the_list[index] = str(item) - elif isinstance(item, dict): + elif type(item) is dict: TurnIntIntoStrInDict(item) - elif isinstance(item, list): + elif type(item) is list: TurnIntIntoStrInList(item) @@ -2647,8 +2736,8 @@ def SetGeneratorGlobals(generator_input_info): # Set up path_sections and non_configuration_keys with the default data plus # the generator-specific data. global path_sections - path_sections = base_path_sections[:] - path_sections.extend(generator_input_info['path_sections']) + path_sections = set(base_path_sections) + path_sections.update(generator_input_info['path_sections']) global non_configuration_keys non_configuration_keys = base_non_configuration_keys[:] @@ -2663,7 +2752,7 @@ def SetGeneratorGlobals(generator_input_info): def Load(build_files, variables, includes, depth, generator_input_info, check, - circular_check, parallel, root_targets): + circular_check, duplicate_basename_check, parallel, root_targets): SetGeneratorGlobals(generator_input_info) # A generator can have other lists (in addition to sources) be processed # for rules. @@ -2677,15 +2766,14 @@ def Load(build_files, variables, includes, depth, generator_input_info, check, # well as meta-data (e.g. 'included_files' key). 'target_build_files' keeps # track of the keys corresponding to "target" files. data = {'target_build_files': set()} - aux_data = {} # Normalize paths everywhere. This is important because paths will be # used as keys to the data dict and for references between input files. build_files = set(map(os.path.normpath, build_files)) if parallel: - LoadTargetBuildFilesParallel(build_files, data, aux_data, - variables, includes, depth, check, - generator_input_info) + LoadTargetBuildFilesParallel(build_files, data, variables, includes, depth, + check, generator_input_info) else: + aux_data = {} for build_file in build_files: try: LoadTargetBuildFile(build_file, data, aux_data, @@ -2707,6 +2795,10 @@ def Load(build_files, variables, includes, depth, generator_input_info, check, # Expand dependencies specified as build_file:*. ExpandWildcardDependencies(targets, data) + # Remove all dependencies marked as 'link_dependency' from the targets of + # type 'none'. + RemoveLinkDependenciesFromNoneTargets(targets) + # Apply exclude (!) and regex (/) list filters only for dependency_sections. for target_name, target_dict in targets.iteritems(): tmp_dict = {} @@ -2792,10 +2884,8 @@ def Load(build_files, variables, includes, depth, generator_input_info, check, target_dict = targets[target] build_file = gyp.common.BuildFile(target) ValidateTargetType(target, target_dict) - # TODO(thakis): Get vpx_scale/arm/scalesystemdependent.c to be renamed to - # scalesystemdependent_arm_additions.c or similar. - if 'arm' not in variables.get('target_arch', ''): - ValidateSourcesInTarget(target, target_dict, build_file) + ValidateSourcesInTarget(target, target_dict, build_file, + duplicate_basename_check) ValidateRulesInTarget(target, target_dict, extra_sources_for_rules) ValidateRunAsInTarget(target, target_dict, build_file) ValidateActionsInTarget(target, target_dict, build_file) diff --git a/third_party/gyp/input_test.py b/third_party/gyp/input_test.py index cdbf6b2f..4234fbb8 100755 --- a/third_party/gyp/input_test.py +++ b/third_party/gyp/input_test.py @@ -44,16 +44,16 @@ class TestFindCycles(unittest.TestCase): def test_cycle_self_reference(self): self._create_dependency(self.nodes['a'], self.nodes['a']) - self.assertEquals([(self.nodes['a'], self.nodes['a'])], + self.assertEquals([[self.nodes['a'], self.nodes['a']]], self.nodes['a'].FindCycles()) def test_cycle_two_nodes(self): self._create_dependency(self.nodes['a'], self.nodes['b']) self._create_dependency(self.nodes['b'], self.nodes['a']) - self.assertEquals([(self.nodes['a'], self.nodes['b'], self.nodes['a'])], + self.assertEquals([[self.nodes['a'], self.nodes['b'], self.nodes['a']]], self.nodes['a'].FindCycles()) - self.assertEquals([(self.nodes['b'], self.nodes['a'], self.nodes['b'])], + self.assertEquals([[self.nodes['b'], self.nodes['a'], self.nodes['b']]], self.nodes['b'].FindCycles()) def test_two_cycles(self): @@ -65,9 +65,9 @@ class TestFindCycles(unittest.TestCase): cycles = self.nodes['a'].FindCycles() self.assertTrue( - (self.nodes['a'], self.nodes['b'], self.nodes['a']) in cycles) + [self.nodes['a'], self.nodes['b'], self.nodes['a']] in cycles) self.assertTrue( - (self.nodes['b'], self.nodes['c'], self.nodes['b']) in cycles) + [self.nodes['b'], self.nodes['c'], self.nodes['b']] in cycles) self.assertEquals(2, len(cycles)) def test_big_cycle(self): @@ -77,12 +77,12 @@ class TestFindCycles(unittest.TestCase): self._create_dependency(self.nodes['d'], self.nodes['e']) self._create_dependency(self.nodes['e'], self.nodes['a']) - self.assertEquals([(self.nodes['a'], + self.assertEquals([[self.nodes['a'], self.nodes['b'], self.nodes['c'], self.nodes['d'], self.nodes['e'], - self.nodes['a'])], + self.nodes['a']]], self.nodes['a'].FindCycles()) diff --git a/third_party/gyp/mac_tool.py b/third_party/gyp/mac_tool.py index c61a3ef6..055d79cf 100755 --- a/third_party/gyp/mac_tool.py +++ b/third_party/gyp/mac_tool.py @@ -17,6 +17,7 @@ import plistlib import re import shutil import string +import struct import subprocess import sys import tempfile @@ -45,9 +46,10 @@ class MacTool(object): """Transforms a tool name like copy-info-plist to CopyInfoPlist""" return name_string.title().replace('-', '') - def ExecCopyBundleResource(self, source, dest): + def ExecCopyBundleResource(self, source, dest, convert_to_binary): """Copies a resource file to the bundle/Resources directory, performing any necessary compilation on each resource.""" + convert_to_binary = convert_to_binary == 'True' extension = os.path.splitext(source)[1].lower() if os.path.isdir(source): # Copy tree. @@ -61,11 +63,16 @@ class MacTool(object): return self._CopyXIBFile(source, dest) elif extension == '.storyboard': return self._CopyXIBFile(source, dest) - elif extension == '.strings': + elif extension == '.strings' and not convert_to_binary: self._CopyStringsFile(source, dest) else: + if os.path.exists(dest): + os.unlink(dest) shutil.copy(source, dest) + if convert_to_binary and extension in ('.plist', '.strings'): + self._ConvertToBinary(dest) + def _CopyXIBFile(self, source, dest): """Compiles a XIB file with ibtool into a binary plist in the bundle.""" @@ -76,8 +83,26 @@ class MacTool(object): if os.path.relpath(dest): dest = os.path.join(base, dest) - args = ['xcrun', 'ibtool', '--errors', '--warnings', '--notices', - '--output-format', 'human-readable-text', '--compile', dest, source] + args = ['xcrun', 'ibtool', '--errors', '--warnings', '--notices'] + + if os.environ['XCODE_VERSION_ACTUAL'] > '0700': + args.extend(['--auto-activate-custom-fonts']) + if 'IPHONEOS_DEPLOYMENT_TARGET' in os.environ: + args.extend([ + '--target-device', 'iphone', '--target-device', 'ipad', + '--minimum-deployment-target', + os.environ['IPHONEOS_DEPLOYMENT_TARGET'], + ]) + else: + args.extend([ + '--target-device', 'mac', + '--minimum-deployment-target', + os.environ['MACOSX_DEPLOYMENT_TARGET'], + ]) + + args.extend(['--output-format', 'human-readable-text', '--compile', dest, + source]) + ibtool_section_re = re.compile(r'/\*.*\*/') ibtool_re = re.compile(r'.*note:.*is clipping its content') ibtoolout = subprocess.Popen(args, stdout=subprocess.PIPE) @@ -92,6 +117,10 @@ class MacTool(object): sys.stdout.write(line) return ibtoolout.returncode + def _ConvertToBinary(self, dest): + subprocess.check_call([ + 'xcrun', 'plutil', '-convert', 'binary1', '-o', dest, dest]) + def _CopyStringsFile(self, source, dest): """Copies a .strings file using iconv to reconvert the input into UTF-16.""" input_code = self._DetectInputEncoding(source) or "UTF-8" @@ -131,7 +160,7 @@ class MacTool(object): else: return None - def ExecCopyInfoPlist(self, source, dest, *keys): + def ExecCopyInfoPlist(self, source, dest, convert_to_binary, *keys): """Copies the |source| Info.plist to the destination directory |dest|.""" # Read the source Info.plist into memory. fd = open(source, 'r') @@ -146,7 +175,7 @@ class MacTool(object): # Go through all the environment variables and replace them as variables in # the file. - IDENT_RE = re.compile('[/\s]') + IDENT_RE = re.compile(r'[_/\s]') for key in os.environ: if key.startswith('_'): continue @@ -185,6 +214,9 @@ class MacTool(object): # "compiled". self._WritePkgInfo(dest) + if convert_to_binary == 'True': + self._ConvertToBinary(dest) + def _WritePkgInfo(self, info_plist): """This writes the PkgInfo file from the data stored in Info.plist.""" plist = plistlib.readPlist(info_plist) @@ -218,14 +250,49 @@ class MacTool(object): def ExecFilterLibtool(self, *cmd_list): """Calls libtool and filters out '/path/to/libtool: file: foo.o has no symbols'.""" - libtool_re = re.compile(r'^.*libtool: file: .* has no symbols$') - libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE) + libtool_re = re.compile(r'^.*libtool: (?:for architecture: \S* )?' + r'file: .* has no symbols$') + libtool_re5 = re.compile( + r'^.*libtool: warning for library: ' + + r'.* the table of contents is empty ' + + r'\(no object file members in the library define global symbols\)$') + env = os.environ.copy() + # Ref: + # http://www.opensource.apple.com/source/cctools/cctools-809/misc/libtool.c + # The problem with this flag is that it resets the file mtime on the file to + # epoch=0, e.g. 1970-1-1 or 1969-12-31 depending on timezone. + env['ZERO_AR_DATE'] = '1' + libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE, env=env) _, err = libtoolout.communicate() for line in err.splitlines(): - if not libtool_re.match(line): + if not libtool_re.match(line) and not libtool_re5.match(line): print >>sys.stderr, line + # Unconditionally touch the output .a file on the command line if present + # and the command succeeded. A bit hacky. + if not libtoolout.returncode: + for i in range(len(cmd_list) - 1): + if cmd_list[i] == "-o" and cmd_list[i+1].endswith('.a'): + os.utime(cmd_list[i+1], None) + break return libtoolout.returncode + def ExecPackageIosFramework(self, framework): + # Find the name of the binary based on the part before the ".framework". + binary = os.path.basename(framework).split('.')[0] + module_path = os.path.join(framework, 'Modules'); + if not os.path.exists(module_path): + os.mkdir(module_path) + module_template = 'framework module %s {\n' \ + ' umbrella header "%s.h"\n' \ + '\n' \ + ' export *\n' \ + ' module * { export * }\n' \ + '}\n' % (binary, binary) + + module_file = open(os.path.join(module_path, 'module.modulemap'), "w") + module_file.write(module_template) + module_file.close() + def ExecPackageFramework(self, framework, version): """Takes a path to Something.framework and the Current version of that and sets up all the symlinks.""" @@ -262,49 +329,105 @@ class MacTool(object): os.remove(link) os.symlink(dest, link) - def ExecCodeSignBundle(self, key, resource_rules, entitlements, provisioning): + def ExecCompileIosFrameworkHeaderMap(self, out, framework, *all_headers): + framework_name = os.path.basename(framework).split('.')[0] + all_headers = map(os.path.abspath, all_headers) + filelist = {} + for header in all_headers: + filename = os.path.basename(header) + filelist[filename] = header + filelist[os.path.join(framework_name, filename)] = header + WriteHmap(out, filelist) + + def ExecCopyIosFrameworkHeaders(self, framework, *copy_headers): + header_path = os.path.join(framework, 'Headers'); + if not os.path.exists(header_path): + os.makedirs(header_path) + for header in copy_headers: + shutil.copy(header, os.path.join(header_path, os.path.basename(header))) + + def ExecCompileXcassets(self, keys, *inputs): + """Compiles multiple .xcassets files into a single .car file. + + This invokes 'actool' to compile all the inputs .xcassets files. The + |keys| arguments is a json-encoded dictionary of extra arguments to + pass to 'actool' when the asset catalogs contains an application icon + or a launch image. + + Note that 'actool' does not create the Assets.car file if the asset + catalogs does not contains imageset. + """ + command_line = [ + 'xcrun', 'actool', '--output-format', 'human-readable-text', + '--compress-pngs', '--notices', '--warnings', '--errors', + ] + is_iphone_target = 'IPHONEOS_DEPLOYMENT_TARGET' in os.environ + if is_iphone_target: + platform = os.environ['CONFIGURATION'].split('-')[-1] + if platform not in ('iphoneos', 'iphonesimulator'): + platform = 'iphonesimulator' + command_line.extend([ + '--platform', platform, '--target-device', 'iphone', + '--target-device', 'ipad', '--minimum-deployment-target', + os.environ['IPHONEOS_DEPLOYMENT_TARGET'], '--compile', + os.path.abspath(os.environ['CONTENTS_FOLDER_PATH']), + ]) + else: + command_line.extend([ + '--platform', 'macosx', '--target-device', 'mac', + '--minimum-deployment-target', os.environ['MACOSX_DEPLOYMENT_TARGET'], + '--compile', + os.path.abspath(os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH']), + ]) + if keys: + keys = json.loads(keys) + for key, value in keys.iteritems(): + arg_name = '--' + key + if isinstance(value, bool): + if value: + command_line.append(arg_name) + elif isinstance(value, list): + for v in value: + command_line.append(arg_name) + command_line.append(str(v)) + else: + command_line.append(arg_name) + command_line.append(str(value)) + # Note: actool crashes if inputs path are relative, so use os.path.abspath + # to get absolute path name for inputs. + command_line.extend(map(os.path.abspath, inputs)) + subprocess.check_call(command_line) + + def ExecMergeInfoPlist(self, output, *inputs): + """Merge multiple .plist files into a single .plist file.""" + merged_plist = {} + for path in inputs: + plist = self._LoadPlistMaybeBinary(path) + self._MergePlist(merged_plist, plist) + plistlib.writePlist(merged_plist, output) + + def ExecCodeSignBundle(self, key, entitlements, provisioning, path, preserve): """Code sign a bundle. This function tries to code sign an iOS bundle, following the same algorithm as Xcode: - 1. copy ResourceRules.plist from the user or the SDK into the bundle, - 2. pick the provisioning profile that best match the bundle identifier, + 1. pick the provisioning profile that best match the bundle identifier, and copy it into the bundle as embedded.mobileprovision, - 3. copy Entitlements.plist from user or SDK next to the bundle, - 4. code sign the bundle. + 2. copy Entitlements.plist from user or SDK next to the bundle, + 3. code sign the bundle. """ - resource_rules_path = self._InstallResourceRules(resource_rules) substitutions, overrides = self._InstallProvisioningProfile( provisioning, self._GetCFBundleIdentifier()) entitlements_path = self._InstallEntitlements( entitlements, substitutions, overrides) - subprocess.check_call([ - 'codesign', '--force', '--sign', key, '--resource-rules', - resource_rules_path, '--entitlements', entitlements_path, - os.path.join( - os.environ['TARGET_BUILD_DIR'], - os.environ['FULL_PRODUCT_NAME'])]) - def _InstallResourceRules(self, resource_rules): - """Installs ResourceRules.plist from user or SDK into the bundle. - - Args: - resource_rules: string, optional, path to the ResourceRules.plist file - to use, default to "${SDKROOT}/ResourceRules.plist" - - Returns: - Path to the copy of ResourceRules.plist into the bundle. - """ - source_path = resource_rules - target_path = os.path.join( - os.environ['BUILT_PRODUCTS_DIR'], - os.environ['CONTENTS_FOLDER_PATH'], - 'ResourceRules.plist') - if not source_path: - source_path = os.path.join( - os.environ['SDKROOT'], 'ResourceRules.plist') - shutil.copy2(source_path, target_path) - return target_path + args = ['codesign', '--force', '--sign', key] + if preserve == 'True': + args.extend(['--deep', '--preserve-metadata=identifier,entitlements']) + else: + args.extend(['--entitlements', entitlements_path]) + args.extend(['--timestamp=none', path]) + subprocess.check_call(args) def _InstallProvisioningProfile(self, profile, bundle_identifier): """Installs embedded.mobileprovision into the bundle. @@ -398,6 +521,19 @@ class MacTool(object): 'security', 'cms', '-D', '-i', profile_path, '-o', temp.name]) return self._LoadPlistMaybeBinary(temp.name) + def _MergePlist(self, merged_plist, plist): + """Merge |plist| into |merged_plist|.""" + for key, value in plist.iteritems(): + if isinstance(value, dict): + merged_value = merged_plist.get(key, {}) + if isinstance(merged_value, dict): + self._MergePlist(merged_value, value) + merged_plist[key] = merged_value + else: + merged_plist[key] = value + else: + merged_plist[key] = value + def _LoadPlistMaybeBinary(self, plist_path): """Loads into a memory a plist possibly encoded in binary format. @@ -506,5 +642,71 @@ class MacTool(object): return {k: self._ExpandVariables(data[k], substitutions) for k in data} return data +def NextGreaterPowerOf2(x): + return 2**(x).bit_length() + +def WriteHmap(output_name, filelist): + """Generates a header map based on |filelist|. + + Per Mark Mentovai: + A header map is structured essentially as a hash table, keyed by names used + in #includes, and providing pathnames to the actual files. + + The implementation below and the comment above comes from inspecting: + http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt + while also looking at the implementation in clang in: + https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp + """ + magic = 1751998832 + version = 1 + _reserved = 0 + count = len(filelist) + capacity = NextGreaterPowerOf2(count) + strings_offset = 24 + (12 * capacity) + max_value_length = len(max(filelist.items(), key=lambda (k,v):len(v))[1]) + + out = open(output_name, "wb") + out.write(struct.pack('.+)') - for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]: - try: - output = self._GetStdout(['/usr/sbin/pkgutil', '--pkg-info', key]) - return re.search(regex, output).groupdict()['version'] - except: - continue - - def _XcodeVersion(self): - # `xcodebuild -version` output looks like - # Xcode 4.6.3 - # Build version 4H1503 - # or like - # Xcode 3.2.6 - # Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0 - # BuildVersion: 10M2518 - # Convert that to '0463', '4H1503'. - if len(XcodeSettings._xcode_version_cache) == 0: - try: - version_list = self._GetStdout(['xcodebuild', '-version']).splitlines() - # In some circumstances xcodebuild exits 0 but doesn't return - # the right results; for example, a user on 10.7 or 10.8 with - # a bogus path set via xcode-select - # In that case this may be a CLT-only install so fall back to - # checking that version. - if len(version_list) < 2: - raise GypError, "xcodebuild returned unexpected results" - except: - version = self._CLTVersion() - if version: - version = re.match('(\d\.\d\.?\d*)', version).groups()[0] - else: - raise GypError, "No Xcode or CLT version detected!" - # The CLT has no build information, so we return an empty string. - version_list = [version, ''] - version = version_list[0] - build = version_list[-1] - # Be careful to convert "4.2" to "0420": - version = version.split()[-1].replace('.', '') - version = (version + '0' * (3 - len(version))).zfill(4) - if build: - build = build.split()[-1] - XcodeSettings._xcode_version_cache = (version, build) - return XcodeSettings._xcode_version_cache + return GetStdout(['sw_vers', '-buildVersion']) def _XcodeIOSDeviceFamily(self, configname): family = self.xcode_settings[configname].get('TARGETED_DEVICE_FAMILY', '1') @@ -944,28 +1153,40 @@ class XcodeSettings(object): cache = {} cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild() - xcode, xcode_build = self._XcodeVersion() + xcode, xcode_build = XcodeVersion() cache['DTXcode'] = xcode cache['DTXcodeBuild'] = xcode_build + compiler = self.xcode_settings[configname].get('GCC_VERSION') + if compiler is not None: + cache['DTCompiler'] = compiler sdk_root = self._SdkRoot(configname) if not sdk_root: sdk_root = self._DefaultSdkRoot() - cache['DTSDKName'] = sdk_root - if xcode >= '0430': + sdk_version = self._GetSdkVersionInfoItem(sdk_root, '--show-sdk-version') + cache['DTSDKName'] = sdk_root + (sdk_version or '') + if xcode >= '0720': cache['DTSDKBuild'] = self._GetSdkVersionInfoItem( - sdk_root, 'ProductBuildVersion') + sdk_root, '--show-sdk-build-version') + elif xcode >= '0430': + cache['DTSDKBuild'] = sdk_version else: cache['DTSDKBuild'] = cache['BuildMachineOSBuild'] if self.isIOS: - cache['DTPlatformName'] = cache['DTSDKName'] + cache['MinimumOSVersion'] = self.xcode_settings[configname].get( + 'IPHONEOS_DEPLOYMENT_TARGET') + cache['DTPlatformName'] = sdk_root + cache['DTPlatformVersion'] = sdk_version + if configname.endswith("iphoneos"): - cache['DTPlatformVersion'] = self._GetSdkVersionInfoItem( - sdk_root, 'ProductVersion') cache['CFBundleSupportedPlatforms'] = ['iPhoneOS'] + cache['DTPlatformBuild'] = cache['DTSDKBuild'] else: cache['CFBundleSupportedPlatforms'] = ['iPhoneSimulator'] + # This is weird, but Xcode sets DTPlatformBuild to an empty field + # for simulator builds. + cache['DTPlatformBuild'] = "" XcodeSettings._plist_cache[configname] = cache # Include extra plist items that are per-target, not per global @@ -982,14 +1203,15 @@ class XcodeSettings(object): project, then the environment variable was empty. Starting with this version, Xcode uses the name of the newest SDK installed. """ - if self._XcodeVersion() < '0500': + xcode_version, xcode_build = XcodeVersion() + if xcode_version < '0500': return '' default_sdk_path = self._XcodeSdkPath('') default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path) if default_sdk_root: return default_sdk_root try: - all_sdks = self._GetStdout(['xcodebuild', '-showsdks']) + all_sdks = GetStdout(['xcodebuild', '-showsdks']) except: # If xcodebuild fails, there will be no valid SDKs return '' @@ -1002,28 +1224,6 @@ class XcodeSettings(object): return sdk_root return '' - def _DefaultArch(self): - # For Mac projects, Xcode changed the default value used when ARCHS is not - # set from "i386" to "x86_64". - # - # For iOS projects, if ARCHS is unset, it defaults to "armv7 armv7s" when - # building for a device, and the simulator binaries are always build for - # "i386". - # - # For new projects, ARCHS is set to $(ARCHS_STANDARD_INCLUDING_64_BIT), - # which correspond to "armv7 armv7s arm64", and when building the simulator - # the architecture is either "i386" or "x86_64" depending on the simulated - # device (respectively 32-bit or 64-bit device). - # - # Since the value returned by this function is only used when ARCHS is not - # set, then on iOS we return "i386", as the default xcode project generator - # does not set ARCHS if it is not set in the .gyp file. - if self.isIOS: - return 'i386' - version, build = self._XcodeVersion() - if version >= '0500': - return 'x86_64' - return 'i386' class MacPrefixHeader(object): """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature. @@ -1131,6 +1331,81 @@ class MacPrefixHeader(object): ] +def XcodeVersion(): + """Returns a tuple of version and build version of installed Xcode.""" + # `xcodebuild -version` output looks like + # Xcode 4.6.3 + # Build version 4H1503 + # or like + # Xcode 3.2.6 + # Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0 + # BuildVersion: 10M2518 + # Convert that to '0463', '4H1503'. + global XCODE_VERSION_CACHE + if XCODE_VERSION_CACHE: + return XCODE_VERSION_CACHE + try: + version_list = GetStdout(['xcodebuild', '-version']).splitlines() + # In some circumstances xcodebuild exits 0 but doesn't return + # the right results; for example, a user on 10.7 or 10.8 with + # a bogus path set via xcode-select + # In that case this may be a CLT-only install so fall back to + # checking that version. + if len(version_list) < 2: + raise GypError("xcodebuild returned unexpected results") + except: + version = CLTVersion() + if version: + version = re.match(r'(\d\.\d\.?\d*)', version).groups()[0] + else: + raise GypError("No Xcode or CLT version detected!") + # The CLT has no build information, so we return an empty string. + version_list = [version, ''] + version = version_list[0] + build = version_list[-1] + # Be careful to convert "4.2" to "0420": + version = version.split()[-1].replace('.', '') + version = (version + '0' * (3 - len(version))).zfill(4) + if build: + build = build.split()[-1] + XCODE_VERSION_CACHE = (version, build) + return XCODE_VERSION_CACHE + + +# This function ported from the logic in Homebrew's CLT version check +def CLTVersion(): + """Returns the version of command-line tools from pkgutil.""" + # pkgutil output looks like + # package-id: com.apple.pkg.CLTools_Executables + # version: 5.0.1.0.1.1382131676 + # volume: / + # location: / + # install-time: 1382544035 + # groups: com.apple.FindSystemFiles.pkg-group com.apple.DevToolsBoth.pkg-group com.apple.DevToolsNonRelocatableShared.pkg-group + STANDALONE_PKG_ID = "com.apple.pkg.DeveloperToolsCLILeo" + FROM_XCODE_PKG_ID = "com.apple.pkg.DeveloperToolsCLI" + MAVERICKS_PKG_ID = "com.apple.pkg.CLTools_Executables" + + regex = re.compile('version: (?P.+)') + for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]: + try: + output = GetStdout(['/usr/sbin/pkgutil', '--pkg-info', key]) + return re.search(regex, output).groupdict()['version'] + except: + continue + + +def GetStdout(cmdlist): + """Returns the content of standard output returned by invoking |cmdlist|. + Raises |GypError| if the command return with a non-zero return code.""" + job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE) + out = job.communicate()[0] + if job.returncode != 0: + sys.stderr.write(out + '\n') + raise GypError('Error %d running %s' % (job.returncode, cmdlist[0])) + return out.rstrip('\n') + + def MergeGlobalXcodeSettingsToSpec(global_dict, spec): """Merges the global xcode_settings dictionary into each configuration of the target represented by spec. For keys that are both in the global and the local @@ -1153,7 +1428,10 @@ def IsMacBundle(flavor, spec): Bundles are directories with a certain subdirectory structure, instead of just a single file. Bundle rules do not produce a binary but also package resources into that directory.""" - is_mac_bundle = (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac') + is_mac_bundle = int(spec.get('mac_xctest_bundle', 0)) != 0 or \ + int(spec.get('mac_xcuitest_bundle', 0)) != 0 or \ + (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac') + if is_mac_bundle: assert spec['type'] != 'none', ( 'mac_bundle targets cannot have type none (target "%s")' % @@ -1271,6 +1549,7 @@ def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, # These are filled in on a as-needed basis. env = { + 'BUILT_FRAMEWORKS_DIR' : built_products_dir, 'BUILT_PRODUCTS_DIR' : built_products_dir, 'CONFIGURATION' : configuration, 'PRODUCT_NAME' : xcode_settings.GetProductName(), @@ -1281,12 +1560,16 @@ def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, # written for bundles: 'TARGET_BUILD_DIR' : built_products_dir, 'TEMP_DIR' : '${TMPDIR}', + 'XCODE_VERSION_ACTUAL' : XcodeVersion()[0], } if xcode_settings.GetPerConfigSetting('SDKROOT', configuration): env['SDKROOT'] = xcode_settings._SdkPath(configuration) else: env['SDKROOT'] = '' + if xcode_settings.mac_toolchain_dir: + env['DEVELOPER_DIR'] = xcode_settings.mac_toolchain_dir + if spec['type'] in ( 'executable', 'static_library', 'shared_library', 'loadable_module'): env['EXECUTABLE_NAME'] = xcode_settings.GetExecutableName() @@ -1310,6 +1593,11 @@ def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, install_name_base = xcode_settings.GetInstallNameBase() if install_name_base: env['DYLIB_INSTALL_NAME_BASE'] = install_name_base + if XcodeVersion() >= '0500' and not env.get('SDKROOT'): + sdk_root = xcode_settings._SdkRoot(configuration) + if not sdk_root: + sdk_root = xcode_settings._XcodeSdkPath('') + env['SDKROOT'] = sdk_root if not additional_settings: additional_settings = {} @@ -1420,16 +1708,17 @@ def _HasIOSTarget(targets): def _AddIOSDeviceConfigurations(targets): """Clone all targets and append -iphoneos to the name. Configure these targets - to build for iOS devices.""" - for target_dict in targets.values(): - for config_name in target_dict['configurations'].keys(): - config = target_dict['configurations'][config_name] - new_config_name = config_name + '-iphoneos' - new_config_dict = copy.deepcopy(config) - if target_dict['toolset'] == 'target': - new_config_dict['xcode_settings']['ARCHS'] = ['armv7'] - new_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos' - target_dict['configurations'][new_config_name] = new_config_dict + to build for iOS devices and use correct architectures for those builds.""" + for target_dict in targets.itervalues(): + toolset = target_dict['toolset'] + configs = target_dict['configurations'] + for config_name, simulator_config_dict in dict(configs).iteritems(): + iphoneos_config_dict = copy.deepcopy(simulator_config_dict) + configs[config_name + '-iphoneos'] = iphoneos_config_dict + configs[config_name + '-iphonesimulator'] = simulator_config_dict + if toolset == 'target': + simulator_config_dict['xcode_settings']['SDKROOT'] = 'iphonesimulator' + iphoneos_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos' return targets def CloneConfigurationForDeviceAndEmulator(target_dicts): diff --git a/third_party/gyp/xcode_ninja.py b/third_party/gyp/xcode_ninja.py new file mode 100644 index 00000000..bc76ffff --- /dev/null +++ b/third_party/gyp/xcode_ninja.py @@ -0,0 +1,289 @@ +# Copyright (c) 2014 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Xcode-ninja wrapper project file generator. + +This updates the data structures passed to the Xcode gyp generator to build +with ninja instead. The Xcode project itself is transformed into a list of +executable targets, each with a build step to build with ninja, and a target +with every source and resource file. This appears to sidestep some of the +major performance headaches experienced using complex projects and large number +of targets within Xcode. +""" + +import errno +import gyp.generator.ninja +import os +import re +import xml.sax.saxutils + + +def _WriteWorkspace(main_gyp, sources_gyp, params): + """ Create a workspace to wrap main and sources gyp paths. """ + (build_file_root, build_file_ext) = os.path.splitext(main_gyp) + workspace_path = build_file_root + '.xcworkspace' + options = params['options'] + if options.generator_output: + workspace_path = os.path.join(options.generator_output, workspace_path) + try: + os.makedirs(workspace_path) + except OSError, e: + if e.errno != errno.EEXIST: + raise + output_string = '\n' + \ + '\n' + for gyp_name in [main_gyp, sources_gyp]: + name = os.path.splitext(os.path.basename(gyp_name))[0] + '.xcodeproj' + name = xml.sax.saxutils.quoteattr("group:" + name) + output_string += ' \n' % name + output_string += '\n' + + workspace_file = os.path.join(workspace_path, "contents.xcworkspacedata") + + try: + with open(workspace_file, 'r') as input_file: + input_string = input_file.read() + if input_string == output_string: + return + except IOError: + # Ignore errors if the file doesn't exist. + pass + + with open(workspace_file, 'w') as output_file: + output_file.write(output_string) + +def _TargetFromSpec(old_spec, params): + """ Create fake target for xcode-ninja wrapper. """ + # Determine ninja top level build dir (e.g. /path/to/out). + ninja_toplevel = None + jobs = 0 + if params: + options = params['options'] + ninja_toplevel = \ + os.path.join(options.toplevel_dir, + gyp.generator.ninja.ComputeOutputDir(params)) + jobs = params.get('generator_flags', {}).get('xcode_ninja_jobs', 0) + + target_name = old_spec.get('target_name') + product_name = old_spec.get('product_name', target_name) + product_extension = old_spec.get('product_extension') + + ninja_target = {} + ninja_target['target_name'] = target_name + ninja_target['product_name'] = product_name + if product_extension: + ninja_target['product_extension'] = product_extension + ninja_target['toolset'] = old_spec.get('toolset') + ninja_target['default_configuration'] = old_spec.get('default_configuration') + ninja_target['configurations'] = {} + + # Tell Xcode to look in |ninja_toplevel| for build products. + new_xcode_settings = {} + if ninja_toplevel: + new_xcode_settings['CONFIGURATION_BUILD_DIR'] = \ + "%s/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)" % ninja_toplevel + + if 'configurations' in old_spec: + for config in old_spec['configurations'].iterkeys(): + old_xcode_settings = \ + old_spec['configurations'][config].get('xcode_settings', {}) + if 'IPHONEOS_DEPLOYMENT_TARGET' in old_xcode_settings: + new_xcode_settings['CODE_SIGNING_REQUIRED'] = "NO" + new_xcode_settings['IPHONEOS_DEPLOYMENT_TARGET'] = \ + old_xcode_settings['IPHONEOS_DEPLOYMENT_TARGET'] + for key in ['BUNDLE_LOADER', 'TEST_HOST']: + if key in old_xcode_settings: + new_xcode_settings[key] = old_xcode_settings[key] + + ninja_target['configurations'][config] = {} + ninja_target['configurations'][config]['xcode_settings'] = \ + new_xcode_settings + + ninja_target['mac_bundle'] = old_spec.get('mac_bundle', 0) + ninja_target['mac_xctest_bundle'] = old_spec.get('mac_xctest_bundle', 0) + ninja_target['ios_app_extension'] = old_spec.get('ios_app_extension', 0) + ninja_target['ios_watchkit_extension'] = \ + old_spec.get('ios_watchkit_extension', 0) + ninja_target['ios_watchkit_app'] = old_spec.get('ios_watchkit_app', 0) + ninja_target['type'] = old_spec['type'] + if ninja_toplevel: + ninja_target['actions'] = [ + { + 'action_name': 'Compile and copy %s via ninja' % target_name, + 'inputs': [], + 'outputs': [], + 'action': [ + 'env', + 'PATH=%s' % os.environ['PATH'], + 'ninja', + '-C', + new_xcode_settings['CONFIGURATION_BUILD_DIR'], + target_name, + ], + 'message': 'Compile and copy %s via ninja' % target_name, + }, + ] + if jobs > 0: + ninja_target['actions'][0]['action'].extend(('-j', jobs)) + return ninja_target + +def IsValidTargetForWrapper(target_extras, executable_target_pattern, spec): + """Limit targets for Xcode wrapper. + + Xcode sometimes performs poorly with too many targets, so only include + proper executable targets, with filters to customize. + Arguments: + target_extras: Regular expression to always add, matching any target. + executable_target_pattern: Regular expression limiting executable targets. + spec: Specifications for target. + """ + target_name = spec.get('target_name') + # Always include targets matching target_extras. + if target_extras is not None and re.search(target_extras, target_name): + return True + + # Otherwise just show executable targets and xc_tests. + if (int(spec.get('mac_xctest_bundle', 0)) != 0 or + (spec.get('type', '') == 'executable' and + spec.get('product_extension', '') != 'bundle')): + + # If there is a filter and the target does not match, exclude the target. + if executable_target_pattern is not None: + if not re.search(executable_target_pattern, target_name): + return False + return True + return False + +def CreateWrapper(target_list, target_dicts, data, params): + """Initialize targets for the ninja wrapper. + + This sets up the necessary variables in the targets to generate Xcode projects + that use ninja as an external builder. + Arguments: + target_list: List of target pairs: 'base/base.gyp:base'. + target_dicts: Dict of target properties keyed on target pair. + data: Dict of flattened build files keyed on gyp path. + params: Dict of global options for gyp. + """ + orig_gyp = params['build_files'][0] + for gyp_name, gyp_dict in data.iteritems(): + if gyp_name == orig_gyp: + depth = gyp_dict['_DEPTH'] + + # Check for custom main gyp name, otherwise use the default CHROMIUM_GYP_FILE + # and prepend .ninja before the .gyp extension. + generator_flags = params.get('generator_flags', {}) + main_gyp = generator_flags.get('xcode_ninja_main_gyp', None) + if main_gyp is None: + (build_file_root, build_file_ext) = os.path.splitext(orig_gyp) + main_gyp = build_file_root + ".ninja" + build_file_ext + + # Create new |target_list|, |target_dicts| and |data| data structures. + new_target_list = [] + new_target_dicts = {} + new_data = {} + + # Set base keys needed for |data|. + new_data[main_gyp] = {} + new_data[main_gyp]['included_files'] = [] + new_data[main_gyp]['targets'] = [] + new_data[main_gyp]['xcode_settings'] = \ + data[orig_gyp].get('xcode_settings', {}) + + # Normally the xcode-ninja generator includes only valid executable targets. + # If |xcode_ninja_executable_target_pattern| is set, that list is reduced to + # executable targets that match the pattern. (Default all) + executable_target_pattern = \ + generator_flags.get('xcode_ninja_executable_target_pattern', None) + + # For including other non-executable targets, add the matching target name + # to the |xcode_ninja_target_pattern| regular expression. (Default none) + target_extras = generator_flags.get('xcode_ninja_target_pattern', None) + + for old_qualified_target in target_list: + spec = target_dicts[old_qualified_target] + if IsValidTargetForWrapper(target_extras, executable_target_pattern, spec): + # Add to new_target_list. + target_name = spec.get('target_name') + new_target_name = '%s:%s#target' % (main_gyp, target_name) + new_target_list.append(new_target_name) + + # Add to new_target_dicts. + new_target_dicts[new_target_name] = _TargetFromSpec(spec, params) + + # Add to new_data. + for old_target in data[old_qualified_target.split(':')[0]]['targets']: + if old_target['target_name'] == target_name: + new_data_target = {} + new_data_target['target_name'] = old_target['target_name'] + new_data_target['toolset'] = old_target['toolset'] + new_data[main_gyp]['targets'].append(new_data_target) + + # Create sources target. + sources_target_name = 'sources_for_indexing' + sources_target = _TargetFromSpec( + { 'target_name' : sources_target_name, + 'toolset': 'target', + 'default_configuration': 'Default', + 'mac_bundle': '0', + 'type': 'executable' + }, None) + + # Tell Xcode to look everywhere for headers. + sources_target['configurations'] = {'Default': { 'include_dirs': [ depth ] } } + + # Put excluded files into the sources target so they can be opened in Xcode. + skip_excluded_files = \ + not generator_flags.get('xcode_ninja_list_excluded_files', True) + + sources = [] + for target, target_dict in target_dicts.iteritems(): + base = os.path.dirname(target) + files = target_dict.get('sources', []) + \ + target_dict.get('mac_bundle_resources', []) + + if not skip_excluded_files: + files.extend(target_dict.get('sources_excluded', []) + + target_dict.get('mac_bundle_resources_excluded', [])) + + for action in target_dict.get('actions', []): + files.extend(action.get('inputs', [])) + + if not skip_excluded_files: + files.extend(action.get('inputs_excluded', [])) + + # Remove files starting with $. These are mostly intermediate files for the + # build system. + files = [ file for file in files if not file.startswith('$')] + + # Make sources relative to root build file. + relative_path = os.path.dirname(main_gyp) + sources += [ os.path.relpath(os.path.join(base, file), relative_path) + for file in files ] + + sources_target['sources'] = sorted(set(sources)) + + # Put sources_to_index in it's own gyp. + sources_gyp = \ + os.path.join(os.path.dirname(main_gyp), sources_target_name + ".gyp") + fully_qualified_target_name = \ + '%s:%s#target' % (sources_gyp, sources_target_name) + + # Add to new_target_list, new_target_dicts and new_data. + new_target_list.append(fully_qualified_target_name) + new_target_dicts[fully_qualified_target_name] = sources_target + new_data_target = {} + new_data_target['target_name'] = sources_target['target_name'] + new_data_target['_DEPTH'] = depth + new_data_target['toolset'] = "target" + new_data[sources_gyp] = {} + new_data[sources_gyp]['targets'] = [] + new_data[sources_gyp]['included_files'] = [] + new_data[sources_gyp]['xcode_settings'] = \ + data[orig_gyp].get('xcode_settings', {}) + new_data[sources_gyp]['targets'].append(new_data_target) + + # Write workspace to file. + _WriteWorkspace(main_gyp, sources_gyp, params) + return (new_target_list, new_target_dicts, new_data) diff --git a/third_party/gyp/xcodeproj_file.py b/third_party/gyp/xcodeproj_file.py index 79c3abcf..1bc90c7d 100644 --- a/third_party/gyp/xcodeproj_file.py +++ b/third_party/gyp/xcodeproj_file.py @@ -173,7 +173,7 @@ _escaped = re.compile('[\\\\"]|[\x00-\x1f]') # Used by SourceTreeAndPathFromPath -_path_leading_variable = re.compile('^\$\((.*?)\)(/(.*))?$') +_path_leading_variable = re.compile(r'^\$\((.*?)\)(/(.*))?$') def SourceTreeAndPathFromPath(input_path): """Given input_path, returns a tuple with sourceTree and path values. @@ -196,7 +196,7 @@ def SourceTreeAndPathFromPath(input_path): return (source_tree, output_path) def ConvertVariablesToShellSyntax(input_string): - return re.sub('\$\((.*?)\)', '${\\1}', input_string) + return re.sub(r'\$\((.*?)\)', '${\\1}', input_string) class XCObject(object): """The abstract base of all class types used in Xcode project files. @@ -341,13 +341,13 @@ class XCObject(object): elif isinstance(value, dict): # dicts are never strong. if is_strong: - raise TypeError, 'Strong dict for key ' + key + ' in ' + \ - self.__class__.__name__ + raise TypeError('Strong dict for key ' + key + ' in ' + \ + self.__class__.__name__) else: that._properties[key] = value.copy() else: - raise TypeError, 'Unexpected type ' + value.__class__.__name__ + \ - ' for key ' + key + ' in ' + self.__class__.__name__ + raise TypeError('Unexpected type ' + value.__class__.__name__ + \ + ' for key ' + key + ' in ' + self.__class__.__name__) return that @@ -366,8 +366,7 @@ class XCObject(object): ('name' in self._schema and self._schema['name'][3]): return self._properties['name'] - raise NotImplementedError, \ - self.__class__.__name__ + ' must implement Name' + raise NotImplementedError(self.__class__.__name__ + ' must implement Name') def Comment(self): """Return a comment string for the object. @@ -466,10 +465,10 @@ class XCObject(object): for descendant in descendants: if descendant.id in ids: other = ids[descendant.id] - raise KeyError, \ + raise KeyError( 'Duplicate ID %s, objects "%s" and "%s" in "%s"' % \ (descendant.id, str(descendant._properties), - str(other._properties), self._properties['rootObject'].Name()) + str(other._properties), self._properties['rootObject'].Name())) ids[descendant.id] = descendant def Children(self): @@ -630,7 +629,7 @@ class XCObject(object): sep printable += end_tabs + '}' else: - raise TypeError, "Can't make " + value.__class__.__name__ + ' printable' + raise TypeError("Can't make " + value.__class__.__name__ + ' printable') if comment != None: printable += ' ' + self._EncodeComment(comment) @@ -756,31 +755,31 @@ class XCObject(object): for property, value in properties.iteritems(): # Make sure the property is in the schema. if not property in self._schema: - raise KeyError, property + ' not in ' + self.__class__.__name__ + raise KeyError(property + ' not in ' + self.__class__.__name__) # Make sure the property conforms to the schema. (is_list, property_type, is_strong) = self._schema[property][0:3] if is_list: if value.__class__ != list: - raise TypeError, \ + raise TypeError( property + ' of ' + self.__class__.__name__ + \ - ' must be list, not ' + value.__class__.__name__ + ' must be list, not ' + value.__class__.__name__) for item in value: if not isinstance(item, property_type) and \ not (item.__class__ == unicode and property_type == str): # Accept unicode where str is specified. str is treated as # UTF-8-encoded. - raise TypeError, \ + raise TypeError( 'item of ' + property + ' of ' + self.__class__.__name__ + \ ' must be ' + property_type.__name__ + ', not ' + \ - item.__class__.__name__ + item.__class__.__name__) elif not isinstance(value, property_type) and \ not (value.__class__ == unicode and property_type == str): # Accept unicode where str is specified. str is treated as # UTF-8-encoded. - raise TypeError, \ + raise TypeError( property + ' of ' + self.__class__.__name__ + ' must be ' + \ - property_type.__name__ + ', not ' + value.__class__.__name__ + property_type.__name__ + ', not ' + value.__class__.__name__) # Checks passed, perform the assignment. if do_copy: @@ -804,9 +803,9 @@ class XCObject(object): elif isinstance(value, dict): self._properties[property] = value.copy() else: - raise TypeError, "Don't know how to copy a " + \ - value.__class__.__name__ + ' object for ' + \ - property + ' in ' + self.__class__.__name__ + raise TypeError("Don't know how to copy a " + \ + value.__class__.__name__ + ' object for ' + \ + property + ' in ' + self.__class__.__name__) else: self._properties[property] = value @@ -837,15 +836,15 @@ class XCObject(object): # Schema validation. if not key in self._schema: - raise KeyError, key + ' not in ' + self.__class__.__name__ + raise KeyError(key + ' not in ' + self.__class__.__name__) (is_list, property_type, is_strong) = self._schema[key][0:3] if not is_list: - raise TypeError, key + ' of ' + self.__class__.__name__ + ' must be list' + raise TypeError(key + ' of ' + self.__class__.__name__ + ' must be list') if not isinstance(value, property_type): - raise TypeError, 'item of ' + key + ' of ' + self.__class__.__name__ + \ - ' must be ' + property_type.__name__ + ', not ' + \ - value.__class__.__name__ + raise TypeError('item of ' + key + ' of ' + self.__class__.__name__ + \ + ' must be ' + property_type.__name__ + ', not ' + \ + value.__class__.__name__) # If the property doesn't exist yet, create a new empty list to receive the # item. @@ -869,7 +868,7 @@ class XCObject(object): for property, attributes in self._schema.iteritems(): (is_list, property_type, is_strong, is_required) = attributes[0:4] if is_required and not property in self._properties: - raise KeyError, self.__class__.__name__ + ' requires ' + property + raise KeyError(self.__class__.__name__ + ' requires ' + property) def _SetDefaultsFromSchema(self): """Assign object default values according to the schema. This will not @@ -1143,16 +1142,16 @@ class PBXGroup(XCHierarchicalElement): child_path = child.PathFromSourceTreeAndPath() if child_path: if child_path in self._children_by_path: - raise ValueError, 'Found multiple children with path ' + child_path + raise ValueError('Found multiple children with path ' + child_path) self._children_by_path[child_path] = child if isinstance(child, PBXVariantGroup): child_name = child._properties.get('name', None) key = (child_name, child_path) if key in self._variant_children_by_name_and_path: - raise ValueError, 'Found multiple PBXVariantGroup children with ' + \ - 'name ' + str(child_name) + ' and path ' + \ - str(child_path) + raise ValueError('Found multiple PBXVariantGroup children with ' + \ + 'name ' + str(child_name) + ' and path ' + \ + str(child_path)) self._variant_children_by_name_and_path[key] = child def AppendChild(self, child): @@ -1493,6 +1492,7 @@ class PBXFileReference(XCFileLikeElement, XCContainerPortal, XCRemoteObject): 'icns': 'image.icns', 'java': 'sourcecode.java', 'js': 'sourcecode.javascript', + 'kext': 'wrapper.kext', 'm': 'sourcecode.c.objc', 'mm': 'sourcecode.cpp.objcpp', 'nib': 'wrapper.nib', @@ -1508,10 +1508,12 @@ class PBXFileReference(XCFileLikeElement, XCContainerPortal, XCRemoteObject): 's': 'sourcecode.asm', 'storyboard': 'file.storyboard', 'strings': 'text.plist.strings', + 'swift': 'sourcecode.swift', 'ttf': 'file', 'xcassets': 'folder.assetcatalog', 'xcconfig': 'text.xcconfig', 'xcdatamodel': 'wrapper.xcdatamodel', + 'xcdatamodeld':'wrapper.xcdatamodeld', 'xib': 'file.xib', 'y': 'sourcecode.yacc', } @@ -1606,7 +1608,7 @@ class XCConfigurationList(XCObject): if configuration._properties['name'] == name: return configuration - raise KeyError, name + raise KeyError(name) def DefaultConfiguration(self): """Convenience accessor to obtain the default XCBuildConfiguration.""" @@ -1663,7 +1665,7 @@ class XCConfigurationList(XCObject): value = configuration_value else: if value != configuration_value: - raise ValueError, 'Variant values for ' + key + raise ValueError('Variant values for ' + key) return value @@ -1770,8 +1772,8 @@ class XCBuildPhase(XCObject): # added, either as a child or deeper descendant. The second item should # be a boolean indicating whether files should be added into hierarchical # groups or one single flat group. - raise NotImplementedError, \ - self.__class__.__name__ + ' must implement FileGroup' + raise NotImplementedError( + self.__class__.__name__ + ' must implement FileGroup') def _AddPathToDict(self, pbxbuildfile, path): """Adds path to the dict tracking paths belonging to this build phase. @@ -1780,7 +1782,7 @@ class XCBuildPhase(XCObject): """ if path in self._files_by_path: - raise ValueError, 'Found multiple build files with path ' + path + raise ValueError('Found multiple build files with path ' + path) self._files_by_path[path] = pbxbuildfile def _AddBuildFileToDicts(self, pbxbuildfile, path=None): @@ -1835,8 +1837,8 @@ class XCBuildPhase(XCObject): # problem. if xcfilelikeelement in self._files_by_xcfilelikeelement and \ self._files_by_xcfilelikeelement[xcfilelikeelement] != pbxbuildfile: - raise ValueError, 'Found multiple build files for ' + \ - xcfilelikeelement.Name() + raise ValueError('Found multiple build files for ' + \ + xcfilelikeelement.Name()) self._files_by_xcfilelikeelement[xcfilelikeelement] = pbxbuildfile def AppendBuildFile(self, pbxbuildfile, path=None): @@ -1950,6 +1952,7 @@ class PBXCopyFilesBuildPhase(XCBuildPhase): # path_tree_to_subfolder maps names of Xcode variables to the associated # dstSubfolderSpec property value used in a PBXCopyFilesBuildPhase object. path_tree_to_subfolder = { + 'BUILT_FRAMEWORKS_DIR': 10, # Frameworks Directory 'BUILT_PRODUCTS_DIR': 16, # Products Directory # Other types that can be chosen via the Xcode UI. # TODO(mark): Map Xcode variable names to these. @@ -1957,7 +1960,6 @@ class PBXCopyFilesBuildPhase(XCBuildPhase): # : 6, # Executables: 6 # : 7, # Resources # : 15, # Java Resources - # : 10, # Frameworks # : 11, # Shared Frameworks # : 12, # Shared Support # : 13, # PlugIns @@ -2000,8 +2002,8 @@ class PBXCopyFilesBuildPhase(XCBuildPhase): subfolder = 0 relative_path = path[1:] else: - raise ValueError, 'Can\'t use path %s in a %s' % \ - (path, self.__class__.__name__) + raise ValueError('Can\'t use path %s in a %s' % \ + (path, self.__class__.__name__)) self._properties['dstPath'] = relative_path self._properties['dstSubfolderSpec'] = subfolder @@ -2237,10 +2239,16 @@ class PBXNativeTarget(XCTarget): # Mapping from Xcode product-types to settings. The settings are: # filetype : used for explicitFileType in the project file # prefix : the prefix for the file name - # suffix : the suffix for the filen ame + # suffix : the suffix for the file name _product_filetypes = { - 'com.apple.product-type.application': ['wrapper.application', - '', '.app'], + 'com.apple.product-type.application': ['wrapper.application', + '', '.app'], + 'com.apple.product-type.application.watchapp': ['wrapper.application', + '', '.app'], + 'com.apple.product-type.watchkit-extension': ['wrapper.app-extension', + '', '.appex'], + 'com.apple.product-type.app-extension': ['wrapper.app-extension', + '', '.appex'], 'com.apple.product-type.bundle': ['wrapper.cfbundle', '', '.bundle'], 'com.apple.product-type.framework': ['wrapper.framework', @@ -2253,8 +2261,12 @@ class PBXNativeTarget(XCTarget): '', ''], 'com.apple.product-type.bundle.unit-test': ['wrapper.cfbundle', '', '.xctest'], + 'com.apple.product-type.bundle.ui-testing': ['wrapper.cfbundle', + '', '.xctest'], 'com.googlecode.gyp.xcode.bundle': ['compiled.mach-o.dylib', '', '.so'], + 'com.apple.product-type.kernel-extension': ['wrapper.kext', + '', '.kext'], } def __init__(self, properties=None, id=None, parent=None, @@ -2307,17 +2319,19 @@ class PBXNativeTarget(XCTarget): force_extension = suffix[1:] if self._properties['productType'] == \ - 'com.apple.product-type-bundle.unit.test': + 'com.apple.product-type-bundle.unit.test' or \ + self._properties['productType'] == \ + 'com.apple.product-type-bundle.ui-testing': if force_extension is None: force_extension = suffix[1:] if force_extension is not None: # If it's a wrapper (bundle), set WRAPPER_EXTENSION. + # Extension override. + suffix = '.' + force_extension if filetype.startswith('wrapper.'): self.SetBuildSetting('WRAPPER_EXTENSION', force_extension) else: - # Extension override. - suffix = '.' + force_extension self.SetBuildSetting('EXECUTABLE_EXTENSION', force_extension) if filetype.startswith('compiled.mach-o.executable'): @@ -2733,8 +2747,53 @@ class PBXProject(XCContainerPortal): self._SetUpProductReferences(other_pbxproject, product_group, project_ref) + inherit_unique_symroot = self._AllSymrootsUnique(other_pbxproject, False) + targets = other_pbxproject.GetProperty('targets') + if all(self._AllSymrootsUnique(t, inherit_unique_symroot) for t in targets): + dir_path = project_ref._properties['path'] + product_group._hashables.extend(dir_path) + return [product_group, project_ref] + def _AllSymrootsUnique(self, target, inherit_unique_symroot): + # Returns True if all configurations have a unique 'SYMROOT' attribute. + # The value of inherit_unique_symroot decides, if a configuration is assumed + # to inherit a unique 'SYMROOT' attribute from its parent, if it doesn't + # define an explicit value for 'SYMROOT'. + symroots = self._DefinedSymroots(target) + for s in self._DefinedSymroots(target): + if (s is not None and not self._IsUniqueSymrootForTarget(s) or + s is None and not inherit_unique_symroot): + return False + return True if symroots else inherit_unique_symroot + + def _DefinedSymroots(self, target): + # Returns all values for the 'SYMROOT' attribute defined in all + # configurations for this target. If any configuration doesn't define the + # 'SYMROOT' attribute, None is added to the returned set. If all + # configurations don't define the 'SYMROOT' attribute, an empty set is + # returned. + config_list = target.GetProperty('buildConfigurationList') + symroots = set() + for config in config_list.GetProperty('buildConfigurations'): + setting = config.GetProperty('buildSettings') + if 'SYMROOT' in setting: + symroots.add(setting['SYMROOT']) + else: + symroots.add(None) + if len(symroots) == 1 and None in symroots: + return set() + return symroots + + def _IsUniqueSymrootForTarget(self, symroot): + # This method returns True if all configurations in target contain a + # 'SYMROOT' attribute that is unique for the given target. A value is + # unique, if the Xcode macro '$SRCROOT' appears in it in any form. + uniquifier = ['$SRCROOT', '$(SRCROOT)'] + if any(x in symroot for x in uniquifier): + return True + return False + def _SetUpProductReferences(self, other_pbxproject, product_group, project_ref): # TODO(mark): This only adds references to products in other_pbxproject @@ -2803,7 +2862,7 @@ class PBXProject(XCContainerPortal): product_group = ref_dict['ProductGroup'] product_group._properties['children'] = sorted( product_group._properties['children'], - cmp=lambda x, y: CompareProducts(x, y, remote_products)) + cmp=lambda x, y, rp=remote_products: CompareProducts(x, y, rp)) class XCProjectFile(XCObject): @@ -2811,27 +2870,10 @@ class XCProjectFile(XCObject): _schema.update({ 'archiveVersion': [0, int, 0, 1, 1], 'classes': [0, dict, 0, 1, {}], - 'objectVersion': [0, int, 0, 1, 45], + 'objectVersion': [0, int, 0, 1, 46], 'rootObject': [0, PBXProject, 1, 1], }) - def SetXcodeVersion(self, version): - version_to_object_version = { - '2.4': 45, - '3.0': 45, - '3.1': 45, - '3.2': 46, - } - if not version in version_to_object_version: - supported_str = ', '.join(sorted(version_to_object_version.keys())) - raise Exception( - 'Unsupported Xcode version %s (supported: %s)' % - ( version, supported_str ) ) - compatibility_version = 'Xcode %s' % version - self._properties['rootObject'].SetProperty('compatibilityVersion', - compatibility_version) - self.SetProperty('objectVersion', version_to_object_version[version]); - def ComputeIDs(self, recursive=True, overwrite=True, hash=None): # Although XCProjectFile is implemented here as an XCObject, it's not a # proper object in the Xcode sense, and it certainly doesn't have its own diff --git a/third_party/jsmn/LICENSE b/third_party/jsmn/LICENSE new file mode 100644 index 00000000..c84fb2e9 --- /dev/null +++ b/third_party/jsmn/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010 Serge A. Zaitsev + +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. + diff --git a/third_party/jsmn/README.md b/third_party/jsmn/README.md new file mode 100644 index 00000000..105897b8 --- /dev/null +++ b/third_party/jsmn/README.md @@ -0,0 +1,167 @@ + +JSMN +==== + +jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be +easily integrated into resource-limited or embedded projects. + +You can find more information about JSON format at [json.org][1] + +Library sources are available at https://github.com/zserge/jsmn + +The web page with some information about jsmn can be found at +[http://zserge.com/jsmn.html][2] + +Philosophy +---------- + +Most JSON parsers offer you a bunch of functions to load JSON data, parse it +and extract any value by its name. jsmn proves that checking the correctness of +every JSON packet or allocating temporary objects to store parsed JSON fields +often is an overkill. + +JSON format itself is extremely simple, so why should we complicate it? + +jsmn is designed to be **robust** (it should work fine even with erroneous +data), **fast** (it should parse data on the fly), **portable** (no superfluous +dependencies or non-standard C extensions). And of course, **simplicity** is a +key feature - simple code style, simple algorithm, simple integration into +other projects. + +Features +-------- + +* compatible with C89 +* no dependencies (even libc!) +* highly portable (tested on x86/amd64, ARM, AVR) +* about 200 lines of code +* extremely small code footprint +* API contains only 2 functions +* no dynamic memory allocation +* incremental single-pass parsing +* library code is covered with unit-tests + +Design +------ + +The rudimentary jsmn object is a **token**. Let's consider a JSON string: + + '{ "name" : "Jack", "age" : 27 }' + +It holds the following tokens: + +* Object: `{ "name" : "Jack", "age" : 27}` (the whole object) +* Strings: `"name"`, `"Jack"`, `"age"` (keys and some values) +* Number: `27` + +In jsmn, tokens do not hold any data, but point to token boundaries in JSON +string instead. In the example above jsmn will create tokens like: Object +[0..31], String [3..7], String [12..16], String [20..23], Number [27..29]. + +Every jsmn token has a type, which indicates the type of corresponding JSON +token. jsmn supports the following token types: + +* Object - a container of key-value pairs, e.g.: + `{ "foo":"bar", "x":0.3 }` +* Array - a sequence of values, e.g.: + `[ 1, 2, 3 ]` +* String - a quoted sequence of chars, e.g.: `"foo"` +* Primitive - a number, a boolean (`true`, `false`) or `null` + +Besides start/end positions, jsmn tokens for complex types (like arrays +or objects) also contain a number of child items, so you can easily follow +object hierarchy. + +This approach provides enough information for parsing any JSON data and makes +it possible to use zero-copy techniques. + +Install +------- + +To clone the repository you should have Git installed. Just run: + + $ git clone https://github.com/zserge/jsmn + +Repository layout is simple: jsmn.c and jsmn.h are library files, tests are in +the jsmn\_test.c, you will also find README, LICENSE and Makefile files inside. + +To build the library, run `make`. It is also recommended to run `make test`. +Let me know, if some tests fail. + +If build was successful, you should get a `libjsmn.a` library. +The header file you should include is called `"jsmn.h"`. + +API +--- + +Token types are described by `jsmntype_t`: + + typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 + } jsmntype_t; + +**Note:** Unlike JSON data types, primitive tokens are not divided into +numbers, booleans and null, because one can easily tell the type using the +first character: + +* 't', 'f' - boolean +* 'n' - null +* '-', '0'..'9' - number + +Token is an object of `jsmntok_t` type: + + typedef struct { + jsmntype_t type; // Token type + int start; // Token start position + int end; // Token end position + int size; // Number of child (nested) tokens + } jsmntok_t; + +**Note:** string tokens point to the first character after +the opening quote and the previous symbol before final quote. This was made +to simplify string extraction from JSON data. + +All job is done by `jsmn_parser` object. You can initialize a new parser using: + + jsmn_parser parser; + jsmntok_t tokens[10]; + + jsmn_init(&parser); + + // js - pointer to JSON string + // tokens - an array of tokens available + // 10 - number of tokens available + jsmn_parse(&parser, js, strlen(js), tokens, 10); + +This will create a parser, and then it tries to parse up to 10 JSON tokens from +the `js` string. + +A non-negative return value of `jsmn_parse` is the number of tokens actually +used by the parser. +Passing NULL instead of the tokens array would not store parsing results, but +instead the function will return the value of tokens needed to parse the given +string. This can be useful if you don't know yet how many tokens to allocate. + +If something goes wrong, you will get an error. Error will be one of these: + +* `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted +* `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large +* `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data + +If you get `JSON_ERROR_NOMEM`, you can re-allocate more tokens and call +`jsmn_parse` once more. If you read json data from the stream, you can +periodically call `jsmn_parse` and check if return value is `JSON_ERROR_PART`. +You will get this error until you reach the end of JSON data. + +Other info +---------- + +This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), + so feel free to integrate it in your commercial products. + +[1]: http://www.json.org/ +[2]: http://zserge.com/jsmn.html diff --git a/third_party/jsmn/example/jsondump.c b/third_party/jsmn/example/jsondump.c new file mode 100644 index 00000000..cf08c5ca --- /dev/null +++ b/third_party/jsmn/example/jsondump.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include "../jsmn.h" + +/* Function realloc_it() is a wrapper function for standart realloc() + * with one difference - it frees old memory pointer in case of realloc + * failure. Thus, DO NOT use old data pointer in anyway after call to + * realloc_it(). If your code has some kind of fallback algorithm if + * memory can't be re-allocated - use standart realloc() instead. + */ +static inline void *realloc_it(void *ptrmem, size_t size) { + void *p = realloc(ptrmem, size); + if (!p) { + free (ptrmem); + fprintf(stderr, "realloc(): errno=%d\n", errno); + } + return p; +} + +/* + * An example of reading JSON from stdin and printing its content to stdout. + * The output looks like YAML, but I'm not sure if it's really compatible. + */ + +static int dump(const char *js, jsmntok_t *t, size_t count, int indent) { + int i, j, k; + if (count == 0) { + return 0; + } + if (t->type == JSMN_PRIMITIVE) { + printf("%.*s", t->end - t->start, js+t->start); + return 1; + } else if (t->type == JSMN_STRING) { + printf("'%.*s'", t->end - t->start, js+t->start); + return 1; + } else if (t->type == JSMN_OBJECT) { + printf("\n"); + j = 0; + for (i = 0; i < t->size; i++) { + for (k = 0; k < indent; k++) printf(" "); + j += dump(js, t+1+j, count-j, indent+1); + printf(": "); + j += dump(js, t+1+j, count-j, indent+1); + printf("\n"); + } + return j+1; + } else if (t->type == JSMN_ARRAY) { + j = 0; + printf("\n"); + for (i = 0; i < t->size; i++) { + for (k = 0; k < indent-1; k++) printf(" "); + printf(" - "); + j += dump(js, t+1+j, count-j, indent+1); + printf("\n"); + } + return j+1; + } + return 0; +} + +int main() { + int r; + int eof_expected = 0; + char *js = NULL; + size_t jslen = 0; + char buf[BUFSIZ]; + + jsmn_parser p; + jsmntok_t *tok; + size_t tokcount = 2; + + /* Prepare parser */ + jsmn_init(&p); + + /* Allocate some tokens as a start */ + tok = malloc(sizeof(*tok) * tokcount); + if (tok == NULL) { + fprintf(stderr, "malloc(): errno=%d\n", errno); + return 3; + } + + for (;;) { + /* Read another chunk */ + r = fread(buf, 1, sizeof(buf), stdin); + if (r < 0) { + fprintf(stderr, "fread(): %d, errno=%d\n", r, errno); + return 1; + } + if (r == 0) { + if (eof_expected != 0) { + return 0; + } else { + fprintf(stderr, "fread(): unexpected EOF\n"); + return 2; + } + } + + js = realloc_it(js, jslen + r + 1); + if (js == NULL) { + return 3; + } + strncpy(js + jslen, buf, r); + jslen = jslen + r; + +again: + r = jsmn_parse(&p, js, jslen, tok, tokcount); + if (r < 0) { + if (r == JSMN_ERROR_NOMEM) { + tokcount = tokcount * 2; + tok = realloc_it(tok, sizeof(*tok) * tokcount); + if (tok == NULL) { + return 3; + } + goto again; + } + } else { + dump(js, tok, p.toknext, 0); + eof_expected = 1; + } + } + + return 0; +} diff --git a/third_party/jsmn/example/simple.c b/third_party/jsmn/example/simple.c new file mode 100644 index 00000000..de448838 --- /dev/null +++ b/third_party/jsmn/example/simple.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include "../jsmn.h" + +/* + * A small example of jsmn parsing when JSON structure is known and number of + * tokens is predictable. + */ + +const char *JSON_STRING = + "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n " + "\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}"; + +static int jsoneq(const char *json, jsmntok_t *tok, const char *s) { + if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start && + strncmp(json + tok->start, s, tok->end - tok->start) == 0) { + return 0; + } + return -1; +} + +int main() { + int i; + int r; + jsmn_parser p; + jsmntok_t t[128]; /* We expect no more than 128 tokens */ + + jsmn_init(&p); + r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t, sizeof(t)/sizeof(t[0])); + if (r < 0) { + printf("Failed to parse JSON: %d\n", r); + return 1; + } + + /* Assume the top-level element is an object */ + if (r < 1 || t[0].type != JSMN_OBJECT) { + printf("Object expected\n"); + return 1; + } + + /* Loop over all keys of the root object */ + for (i = 1; i < r; i++) { + if (jsoneq(JSON_STRING, &t[i], "user") == 0) { + /* We may use strndup() to fetch string value */ + printf("- User: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "admin") == 0) { + /* We may additionally check if the value is either "true" or "false" */ + printf("- Admin: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "uid") == 0) { + /* We may want to do strtol() here to get numeric value */ + printf("- UID: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "groups") == 0) { + int j; + printf("- Groups:\n"); + if (t[i+1].type != JSMN_ARRAY) { + continue; /* We expect groups to be an array of strings */ + } + for (j = 0; j < t[i+1].size; j++) { + jsmntok_t *g = &t[i+j+2]; + printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start); + } + i += t[i+1].size + 1; + } else { + printf("Unexpected key: %.*s\n", t[i].end-t[i].start, + JSON_STRING + t[i].start); + } + } + return 0; +} diff --git a/third_party/jsmn/jsmn.c b/third_party/jsmn/jsmn.c new file mode 100644 index 00000000..e7765eb1 --- /dev/null +++ b/third_party/jsmn/jsmn.c @@ -0,0 +1,311 @@ +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + diff --git a/third_party/jsmn/jsmn.h b/third_party/jsmn/jsmn.h new file mode 100644 index 00000000..01ca99c8 --- /dev/null +++ b/third_party/jsmn/jsmn.h @@ -0,0 +1,76 @@ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; + +/** + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSMN_H_ */ diff --git a/third_party/jsmn/library.json b/third_party/jsmn/library.json new file mode 100644 index 00000000..8e2f5c25 --- /dev/null +++ b/third_party/jsmn/library.json @@ -0,0 +1,16 @@ +{ + "name": "jsmn", + "keywords": "json", + "description": "Minimalistic JSON parser/tokenizer in C. It can be easily integrated into resource-limited or embedded projects", + "repository": + { + "type": "git", + "url": "https://github.com/zserge/jsmn.git" + }, + "frameworks": "*", + "platforms": "*", + "examples": [ + "example/*.c" + ], + "exclude": "test" +} diff --git a/third_party/jsmn/test/test.h b/third_party/jsmn/test/test.h new file mode 100644 index 00000000..35f704f9 --- /dev/null +++ b/third_party/jsmn/test/test.h @@ -0,0 +1,27 @@ +#ifndef __TEST_H__ +#define __TEST_H__ + +static int test_passed = 0; +static int test_failed = 0; + +/* Terminate current test with error */ +#define fail() return __LINE__ + +/* Successful end of the test case */ +#define done() return 0 + +/* Check single condition */ +#define check(cond) do { if (!(cond)) fail(); } while (0) + +/* Test runner */ +static void test(int (*func)(void), const char *name) { + int r = func(); + if (r == 0) { + test_passed++; + } else { + test_failed++; + printf("FAILED: %s (at line %d)\n", name, r); + } +} + +#endif /* __TEST_H__ */ diff --git a/third_party/jsmn/test/tests.c b/third_party/jsmn/test/tests.c new file mode 100644 index 00000000..a72689ec --- /dev/null +++ b/third_party/jsmn/test/tests.c @@ -0,0 +1,378 @@ +#include +#include +#include +#include + +#include "test.h" +#include "testutil.h" + +int test_empty(void) { + check(parse("{}", 1, 1, + JSMN_OBJECT, 0, 2, 0)); + check(parse("[]", 1, 1, + JSMN_ARRAY, 0, 2, 0)); + check(parse("[{},{}]", 3, 3, + JSMN_ARRAY, 0, 7, 2, + JSMN_OBJECT, 1, 3, 0, + JSMN_OBJECT, 4, 6, 0)); + return 0; +} + +int test_object(void) { + check(parse("{\"a\":0}", 3, 3, + JSMN_OBJECT, 0, 7, 1, + JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, "0")); + check(parse("{\"a\":[]}", 3, 3, + JSMN_OBJECT, 0, 8, 1, + JSMN_STRING, "a", 1, + JSMN_ARRAY, 5, 7, 0)); + check(parse("{\"a\":{},\"b\":{}}", 5, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "a", 1, + JSMN_OBJECT, -1, -1, 0, + JSMN_STRING, "b", 1, + JSMN_OBJECT, -1, -1, 0)); + check(parse("{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }", 7, 7, + JSMN_OBJECT, -1, -1, 3, + JSMN_STRING, "Day", 1, + JSMN_PRIMITIVE, "26", + JSMN_STRING, "Month", 1, + JSMN_PRIMITIVE, "9", + JSMN_STRING, "Year", 1, + JSMN_PRIMITIVE, "12")); + check(parse("{\"a\": 0, \"b\": \"c\"}", 5, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, "0", + JSMN_STRING, "b", 1, + JSMN_STRING, "c", 0)); + +#ifdef JSMN_STRICT + check(parse("{\"a\"\n0}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\", 0}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); + /* FIXME */ + /*check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2));*/ + /*check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{,}", JSMN_ERROR_INVAL, 4));*/ +#endif + return 0; +} + +int test_array(void) { + /* FIXME */ + /*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/ + /*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/ + check(parse("[10]", 2, 2, + JSMN_ARRAY, -1, -1, 1, + JSMN_PRIMITIVE, "10")); + check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3)); + /* FIXME */ + /*check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3));*/ + return 0; +} + +int test_primitive(void) { + check(parse("{\"boolVar\" : true }", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, + JSMN_PRIMITIVE, "true")); + check(parse("{\"boolVar\" : false }", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, + JSMN_PRIMITIVE, "false")); + check(parse("{\"nullVar\" : null }", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "nullVar", 1, + JSMN_PRIMITIVE, "null")); + check(parse("{\"intVar\" : 12}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "intVar", 1, + JSMN_PRIMITIVE, "12")); + check(parse("{\"floatVar\" : 12.345}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, + JSMN_PRIMITIVE, "12.345")); + return 0; +} + +int test_string(void) { + check(parse("{\"strVar\" : \"hello world\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, + JSMN_STRING, "hello world", 0)); + check(parse("{\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, + JSMN_STRING, "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\", 0)); + check(parse("{\"strVar\": \"\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, + JSMN_STRING, "", 0)); + check(parse("{\"a\":\"\\uAbcD\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "\\uAbcD", 0)); + check(parse("{\"a\":\"str\\u0000\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "str\\u0000", 0)); + check(parse("{\"a\":\"\\uFFFFstr\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "\\uFFFFstr", 0)); + check(parse("{\"a\":[\"\\u0280\"]}", 4, 4, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_ARRAY, -1, -1, 1, + JSMN_STRING, "\\u0280", 0)); + + check(parse("{\"a\":\"str\\uFFGFstr\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\":\"str\\u@FfF\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL, 4)); + return 0; +} + +int test_partial_string(void) { + int i; + int r; + jsmn_parser p; + jsmntok_t tok[5]; + const char *js = "{\"x\": \"va\\\\ue\", \"y\": \"value y\"}"; + + jsmn_init(&p); + for (i = 1; i <= strlen(js); i++) { + r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0])); + if (i == strlen(js)) { + check(r == 5); + check(tokeq(js, tok, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "x", 1, + JSMN_STRING, "va\\\\ue", 0, + JSMN_STRING, "y", 1, + JSMN_STRING, "value y", 0)); + } else { + check(r == JSMN_ERROR_PART); + } + } + return 0; +} + +int test_partial_array(void) { +#ifdef JSMN_STRICT + int r; + int i; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js = "[ 1, true, [123, \"hello\"]]"; + + jsmn_init(&p); + for (i = 1; i <= strlen(js); i++) { + r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0])); + if (i == strlen(js)) { + check(r == 6); + check(tokeq(js, tok, 6, + JSMN_ARRAY, -1, -1, 3, + JSMN_PRIMITIVE, "1", + JSMN_PRIMITIVE, "true", + JSMN_ARRAY, -1, -1, 2, + JSMN_PRIMITIVE, "123", + JSMN_STRING, "hello", 0)); + } else { + check(r == JSMN_ERROR_PART); + } + } +#endif + return 0; +} + +int test_array_nomem(void) { + int i; + int r; + jsmn_parser p; + jsmntok_t toksmall[10], toklarge[10]; + const char *js; + + js = " [ 1, true, [123, \"hello\"]]"; + + for (i = 0; i < 6; i++) { + jsmn_init(&p); + memset(toksmall, 0, sizeof(toksmall)); + memset(toklarge, 0, sizeof(toklarge)); + r = jsmn_parse(&p, js, strlen(js), toksmall, i); + check(r == JSMN_ERROR_NOMEM); + + memcpy(toklarge, toksmall, sizeof(toksmall)); + + r = jsmn_parse(&p, js, strlen(js), toklarge, 10); + check(r >= 0); + check(tokeq(js, toklarge, 4, + JSMN_ARRAY, -1, -1, 3, + JSMN_PRIMITIVE, "1", + JSMN_PRIMITIVE, "true", + JSMN_ARRAY, -1, -1, 2, + JSMN_PRIMITIVE, "123", + JSMN_STRING, "hello", 0)); + } + return 0; +} + +int test_unquoted_keys(void) { +#ifndef JSMN_STRICT + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = "key1: \"value\"\nkey2 : 123"; + + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0); + check(tokeq(js, tok, 4, + JSMN_PRIMITIVE, "key1", + JSMN_STRING, "value", 0, + JSMN_PRIMITIVE, "key2", + JSMN_PRIMITIVE, "123")); +#endif + return 0; +} + +int test_issue_22(void) { + int r; + jsmn_parser p; + jsmntok_t tokens[128]; + const char *js; + + js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, " + "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", " + "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], " + "\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, " + "\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", " + "\"imageheight\":64, \"imagewidth\":160, \"margin\":0, \"name\":\"Tiles\", " + "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], " + "\"tilewidth\":32, \"version\":1, \"width\":10 }"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 128); + check(r >= 0); + return 0; +} + +int test_issue_27(void) { + const char *js = + "{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", "; + check(parse(js, JSMN_ERROR_PART, 8)); + return 0; +} + +int test_input_length(void) { + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "{\"a\": 0}garbage"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, 8, tokens, 10); + check(r == 3); + check(tokeq(js, tokens, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, "0")); + return 0; +} + +int test_count(void) { + jsmn_parser p; + const char *js; + + js = "{}"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + + js = "[]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + + js = "[[]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + + js = "[[], [[]], [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + + js = "[\"a\", [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + + js = "[[], \"[], [[]]\", [[]]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + + js = "[1, 2, 3]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4); + + js = "[1, 2, [3, \"a\"], null]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + + return 0; +} + + +int test_nonstrict(void) { +#ifndef JSMN_STRICT + const char *js; + js = "a: 0garbage"; + check(parse(js, 2, 2, + JSMN_PRIMITIVE, "a", + JSMN_PRIMITIVE, "0garbage")); + + js = "Day : 26\nMonth : Sep\n\nYear: 12"; + check(parse(js, 6, 6, + JSMN_PRIMITIVE, "Day", + JSMN_PRIMITIVE, "26", + JSMN_PRIMITIVE, "Month", + JSMN_PRIMITIVE, "Sep", + JSMN_PRIMITIVE, "Year", + JSMN_PRIMITIVE, "12")); +#endif + return 0; +} + +int main(void) { + test(test_empty, "test for a empty JSON objects/arrays"); + test(test_object, "test for a JSON objects"); + test(test_array, "test for a JSON arrays"); + test(test_primitive, "test primitive JSON data types"); + test(test_string, "test string JSON data types"); + + test(test_partial_string, "test partial JSON string parsing"); + test(test_partial_array, "test partial array reading"); + test(test_array_nomem, "test array reading with a smaller number of tokens"); + test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); + test(test_input_length, "test strings that are not null-terminated"); + test(test_issue_22, "test issue #22"); + test(test_issue_27, "test issue #27"); + test(test_count, "test tokens count estimation"); + test(test_nonstrict, "test for non-strict mode"); + printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); + return (test_failed > 0); +} diff --git a/third_party/jsmn/test/testutil.h b/third_party/jsmn/test/testutil.h new file mode 100644 index 00000000..9a1eb2d6 --- /dev/null +++ b/third_party/jsmn/test/testutil.h @@ -0,0 +1,94 @@ +#ifndef __TEST_UTIL_H__ +#define __TEST_UTIL_H__ + +#include "../jsmn.c" + +static int vtokeq(const char *s, jsmntok_t *t, int numtok, va_list ap) { + if (numtok > 0) { + int i, start, end, size; + int type; + char *value; + + size = -1; + value = NULL; + for (i = 0; i < numtok; i++) { + type = va_arg(ap, int); + if (type == JSMN_STRING) { + value = va_arg(ap, char *); + size = va_arg(ap, int); + start = end = -1; + } else if (type == JSMN_PRIMITIVE) { + value = va_arg(ap, char *); + start = end = size = -1; + } else { + start = va_arg(ap, int); + end = va_arg(ap, int); + size = va_arg(ap, int); + value = NULL; + } + if (t[i].type != type) { + printf("token %d type is %d, not %d\n", i, t[i].type, type); + return 0; + } + if (start != -1 && end != -1) { + if (t[i].start != start) { + printf("token %d start is %d, not %d\n", i, t[i].start, start); + return 0; + } + if (t[i].end != end ) { + printf("token %d end is %d, not %d\n", i, t[i].end, end); + return 0; + } + } + if (size != -1 && t[i].size != size) { + printf("token %d size is %d, not %d\n", i, t[i].size, size); + return 0; + } + + if (s != NULL && value != NULL) { + const char *p = s + t[i].start; + if (strlen(value) != t[i].end - t[i].start || + strncmp(p, value, t[i].end - t[i].start) != 0) { + printf("token %d value is %.*s, not %s\n", i, t[i].end-t[i].start, + s+t[i].start, value); + return 0; + } + } + } + } + return 1; +} + +static int tokeq(const char *s, jsmntok_t *tokens, int numtok, ...) { + int ok; + va_list args; + va_start(args, numtok); + ok = vtokeq(s, tokens, numtok, args); + va_end(args); + return ok; +} + +static int parse(const char *s, int status, int numtok, ...) { + int r; + int ok = 1; + va_list args; + jsmn_parser p; + jsmntok_t *t = malloc(numtok * sizeof(jsmntok_t)); + + jsmn_init(&p); + r = jsmn_parse(&p, s, strlen(s), t, numtok); + if (r != status) { + printf("status is %d, not %d\n", r, status); + return 0; + } + + if (status >= 0) { + va_start(args, numtok); + ok = vtokeq(s, t, numtok, args); + va_end(args); + } + free(t); + return ok; +} + +#endif /* __TEST_UTIL_H__ */ diff --git a/third_party/protobuf/gtest/libtool b/third_party/protobuf/gtest/libtool index 1471747b..889db512 100755 --- a/third_party/protobuf/gtest/libtool +++ b/third_party/protobuf/gtest/libtool @@ -2,7 +2,7 @@ # libtool - Provide generalized library-building support services. # Generated automatically by config.status (gtest) 1.6.0 -# Libtool was configured on host horcrux.kir.corp.google.com: +# Libtool was configured on host gmorgan-linux1.kir.corp.google.com: # NOTE: Changes made to this file will be lost: look at ltmain.sh. # # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, diff --git a/third_party/protobuf/libtool b/third_party/protobuf/libtool index df4b2405..9e456720 100755 --- a/third_party/protobuf/libtool +++ b/third_party/protobuf/libtool @@ -2,7 +2,7 @@ # libtool - Provide generalized library-building support services. # Generated automatically by config.status (protobuf) 2.5.0 -# Libtool was configured on host horcrux.kir.corp.google.com: +# Libtool was configured on host gmorgan-linux1.kir.corp.google.com: # NOTE: Changes made to this file will be lost: look at ltmain.sh. # # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, diff --git a/third_party/stringencoders/src/modp_b64.cpp b/third_party/stringencoders/src/modp_b64.cpp new file mode 100644 index 00000000..436143ea --- /dev/null +++ b/third_party/stringencoders/src/modp_b64.cpp @@ -0,0 +1,269 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +/* vi: set expandtab shiftwidth=4 tabstop=4: */ +/** + * \file modp_b64.c + *

+ * MODP_B64 - High performance base64 encoder/decoder
+ * http://code.google.com/p/stringencoders/
+ *
+ * Copyright © 2005, 2006, 2007  Nick Galbreath -- nickg [at] modp [dot] com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ *   Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ *   Neither the name of the modp.com nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * 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.
+ *
+ * This is the standard "new" BSD license:
+ * http://www.opensource.org/licenses/bsd-license.php
+ * 
+ */ + +/* public header */ +#include "modp_b64.h" + +/* + * If you are ripping this out of the library, comment out the next + * line and uncomment the next lines as approrpiate + */ +#include "config.h" + +/* if on motoral, sun, ibm; uncomment this */ +/* #define WORDS_BIGENDIAN 1 */ +/* else for Intel, Amd; uncomment this */ +/* #undef WORDS_BIGENDIAN */ + +#include "modp_b64_data.h" + +#define BADCHAR 0x01FFFFFF + +/** + * you can control if we use padding by commenting out this + * next line. However, I highly recommend you use padding and not + * using it should only be for compatability with a 3rd party. + * Also, 'no padding' is not tested! + */ +#define DOPAD 1 + +/* + * if we aren't doing padding + * set the pad character to NULL + */ +#ifndef DOPAD +#undef CHARPAD +#define CHARPAD '\0' +#endif + +int modp_b64_encode(char* dest, const char* str, int len) +{ + int i; + const uint8_t* s = (const uint8_t*) str; + uint8_t* p = (uint8_t*) dest; + + /* unsigned here is important! */ + /* uint8_t is fastest on G4, amd */ + /* uint32_t is fastest on Intel */ + uint32_t t1, t2, t3; + + for (i = 0; i < len - 2; i += 3) { + t1 = s[i]; t2 = s[i+1]; t3 = s[i+2]; + *p++ = e0[t1]; + *p++ = e1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *p++ = e1[((t2 & 0x0F) << 2) | ((t3 >> 6) & 0x03)]; + *p++ = e2[t3]; + } + + switch (len - i) { + case 0: + break; + case 1: + t1 = s[i]; + *p++ = e0[t1]; + *p++ = e1[(t1 & 0x03) << 4]; + *p++ = CHARPAD; + *p++ = CHARPAD; + break; + default: /* case 2 */ + t1 = s[i]; t2 = s[i+1]; + *p++ = e0[t1]; + *p++ = e1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *p++ = e2[(t2 & 0x0F) << 2]; + *p++ = CHARPAD; + } + + *p = '\0'; + return (int)(p - (uint8_t*)dest); +} + +#ifdef WORDS_BIGENDIAN /* BIG ENDIAN -- SUN / IBM / MOTOROLA */ +int modp_b64_decode(char* dest, const char* src, int len) +{ + int i; + if (len == 0) return 0; + +#ifdef DOPAD + /* if padding is used, then the message must be at least + 4 chars and be a multiple of 4. + there can be at most 2 pad chars at the end */ + if (len < 4 || (len % 4 != 0)) return -1; + if (src[len-1] == CHARPAD) { + len--; + if (src[len -1] == CHARPAD) { + len--; + } + } +#endif /* DOPAD */ + + int leftover = len % 4; + int chunks = (leftover == 0) ? len / 4 - 1 : len /4; + + uint8_t* p = (uint8_t*) dest; + uint32_t x = 0; + uint32_t* destInt = (uint32_t*) p; + uint32_t* srcInt = (uint32_t*) src; + uint32_t y = *srcInt++; + for (i = 0; i < chunks; ++i) { + x = d0[y >> 24 & 0xff] | d1[y >> 16 & 0xff] | + d2[y >> 8 & 0xff] | d3[y & 0xff]; + + if (x >= BADCHAR) return -1; + *destInt = x << 8; + p += 3; + destInt = (uint32_t*)p; + y = *srcInt++; + } + + switch (leftover) { + case 0: + x = d0[y >> 24 & 0xff] | d1[y >> 16 & 0xff] | + d2[y >> 8 & 0xff] | d3[y & 0xff]; + if (x >= BADCHAR) return -1; + *p++ = ((uint8_t*)&x)[1]; + *p++ = ((uint8_t*)&x)[2]; + *p = ((uint8_t*)&x)[3]; + return (chunks+1)*3; +#ifndef DOPAD + case 1: /* with padding this is an impossible case */ + x = d3[y >> 24]; + *p = (uint8_t)x; + break; +#endif + case 2: + x = d3[y >> 24] *64 + d3[(y >> 16) & 0xff]; + *p = (uint8_t)(x >> 4); + break; + default: /* case 3 */ + x = (d3[y >> 24] *64 + d3[(y >> 16) & 0xff])*64 + + d3[(y >> 8) & 0xff]; + *p++ = (uint8_t) (x >> 10); + *p = (uint8_t) (x >> 2); + break; + } + + if (x >= BADCHAR) return -1; + return 3*chunks + (6*leftover)/8; +} + +#else /* LITTLE ENDIAN -- INTEL AND FRIENDS */ + +int modp_b64_decode(char* dest, const char* src, int len) +{ + int i; + if (len == 0) return 0; + +#ifdef DOPAD + /* + * if padding is used, then the message must be at least + * 4 chars and be a multiple of 4 + */ + if (len < 4 || (len % 4 != 0)) return -1; /* error */ + /* there can be at most 2 pad chars at the end */ + if (src[len-1] == CHARPAD) { + len--; + if (src[len -1] == CHARPAD) { + len--; + } + } +#endif + + int leftover = len % 4; + int chunks = (leftover == 0) ? len / 4 - 1 : len /4; + + uint8_t* p = (uint8_t*) dest; + uint32_t x = 0; + uint32_t* destInt = (uint32_t*) p; + uint32_t* srcInt = (uint32_t*) src; + uint32_t y = *srcInt++; + for (i = 0; i < chunks; ++i) { + x = d0[y & 0xff] | + d1[(y >> 8) & 0xff] | + d2[(y >> 16) & 0xff] | + d3[(y >> 24) & 0xff]; + + if (x >= BADCHAR) return -1; + *destInt = x ; + p += 3; + destInt = (uint32_t*)p; + y = *srcInt++;} + + + switch (leftover) { + case 0: + x = d0[y & 0xff] | + d1[(y >> 8) & 0xff] | + d2[(y >> 16) & 0xff] | + d3[(y >> 24) & 0xff]; + + if (x >= BADCHAR) return -1; + *p++ = ((uint8_t*)(&x))[0]; + *p++ = ((uint8_t*)(&x))[1]; + *p = ((uint8_t*)(&x))[2]; + return (chunks+1)*3; + break; +#ifndef DOPAD + case 1: /* with padding this is an impossible case */ + x = d0[y & 0xff]; + *p = *((uint8_t*)(&x)); // i.e. first char/byte in int + break; +#endif + case 2: // * case 2, 1 output byte */ + x = d0[y & 0xff] | d1[y >> 8 & 0xff]; + *p = *((uint8_t*)(&x)); // i.e. first char + break; + default: /* case 3, 2 output bytes */ + x = d0[y & 0xff] | + d1[y >> 8 & 0xff ] | + d2[y >> 16 & 0xff]; /* 0x3c */ + *p++ = ((uint8_t*)(&x))[0]; + *p = ((uint8_t*)(&x))[1]; + break; + } + + if (x >= BADCHAR) return -1; + + return 3*chunks + (6*leftover)/8; +} + +#endif /* if bigendian / else / endif */ diff --git a/third_party/stringencoders/src/modp_b64.h b/third_party/stringencoders/src/modp_b64.h new file mode 100644 index 00000000..3256af7a --- /dev/null +++ b/third_party/stringencoders/src/modp_b64.h @@ -0,0 +1,234 @@ +/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +/* vi: set expandtab shiftwidth=4 tabstop=4: */ + +/** + * \file + *
+ * High performance base64 encoder / decoder
+ *
+ * Copyright © 2005, 2006, 2007 Nick Galbreath -- nickg [at] modp [dot] com
+ * All rights reserved.
+ *
+ * http://code.google.com/p/stringencoders/
+ *
+ * Released under bsd license.  See modp_b64.c for details.
+ * 
+ * + * This uses the standard base 64 alphabet. If you are planning + * to embed a base 64 encoding inside a URL use modp_b64w instead. + * + */ + +#ifndef COM_MODP_STRINGENCODERS_B64 +#define COM_MODP_STRINGENCODERS_B64 + +#ifdef __cplusplus +#define BEGIN_C extern "C" { +#define END_C } +#else +#define BEGIN_C +#define END_C +#endif + +BEGIN_C + +/** + * Encode a raw binary string into base 64. + * \param[out] dest should be allocated by the caller to contain + * at least modp_b64_encode_len(len) bytes (see below) + * This will contain the null-terminated b64 encoded result + * \param[in] src contains the bytes + * \param[in] len contains the number of bytes in the src + * \return length of the destination string plus the ending null byte + * i.e. the result will be equal to strlen(dest) + 1 + * + * Example + * + * \code + * char* src = ...; + * int srclen = ...; //the length of number of bytes in src + * char* dest = (char*) malloc(modp_b64_encode_len); + * int len = modp_b64_encode(dest, src, sourcelen); + * if (len == -1) { + * printf("Error\n"); + * } else { + * printf("b64 = %s\n", dest); + * } + * \endcode + * + */ +int modp_b64_encode(char* dest, const char* str, int len); + +/** + * Decode a base64 encoded string + * + * \param[out] dest should be allocated by the caller to contain at least + * len * 3 / 4 bytes. The destination cannot be the same as the source + * They must be different buffers. + * \param[in] src should contain exactly len bytes of b64 characters. + * if src contains -any- non-base characters (such as white + * space, -1 is returned. + * \param[in] len is the length of src + * + * \return the length (strlen) of the output, or -1 if unable to + * decode + * + * \code + * char* src = ...; + * int srclen = ...; // or if you don't know use strlen(src) + * char* dest = (char*) malloc(modp_b64_decode_len(srclen)); + * int len = modp_b64_decode(dest, src, sourcelen); + * if (len == -1) { error } + * \endcode + */ +int modp_b64_decode(char* dest, const char* src, int len); + +/** + * Given a source string of length len, this returns the amount of + * memory the destination string should have. + * + * remember, this is integer math + * 3 bytes turn into 4 chars + * ceiling[len / 3] * 4 + 1 + * + * +1 is for any extra null. + */ +#define modp_b64_encode_len(A) ((A+2)/3 * 4 + 1) + +/** + * Given a base64 string of length len, + * this returns the amount of memory required for output string + * It maybe be more than the actual number of bytes written. + * NOTE: remember this is integer math + * this allocates a bit more memory than traditional versions of b64 + * decode 4 chars turn into 3 bytes + * floor[len * 3/4] + 2 + */ +#define modp_b64_decode_len(A) (A / 4 * 3 + 2) + +/** + * Will return the strlen of the output from encoding. + * This may be less than the required number of bytes allocated. + * + * This allows you to 'deserialized' a struct + * \code + * char* b64encoded = "..."; + * int len = strlen(b64encoded); + * + * struct datastuff foo; + * if (modp_b64_encode_strlen(sizeof(struct datastuff)) != len) { + * // wrong size + * return false; + * } else { + * // safe to do; + * if (modp_b64_decode((char*) &foo, b64encoded, len) == -1) { + * // bad characters + * return false; + * } + * } + * // foo is filled out now + * \endcode + */ +#define modp_b64_encode_strlen(A) ((A + 2)/ 3 * 4) + +END_C + +#ifdef __cplusplus +#include +#include + +namespace modp { + /** \brief b64 encode a cstr with len + * + * \param[in] s the input string to encode + * \param[in] len the length of the input string + * \return a newly allocated b64 string. Empty if failed. + */ + inline std::string b64_encode(const char* s, size_t len) + { + std::string x(modp_b64_encode_len(len), '\0'); + int d = modp_b64_encode(const_cast(x.data()), s, + static_cast(len)); + x.erase(d, std::string::npos); + return x; + } + + /** \brief b64 encode a cstr + * + * \param[in] s the input string to encode + * \return a newly allocated b64 string. Empty if failed. + */ + inline std::string b64_encode(const char* s) + { + return b64_encode(s, static_cast(strlen(s))); + } + + /** \brief b64 encode a const std::string + * + * \param[in] s the input string to encode + * \return a newly allocated b64 string. Empty if failed. + */ + inline std::string b64_encode(const std::string& s) + { + return b64_encode(s.data(), s.size()); + } + + /** + * base 64 encode a string (self-modifing) + * + * This function is for C++ only (duh) + * + * \param[in,out] s the string to be decoded + * \return a reference to the input string + */ + inline std::string& b64_encode(std::string& s) + { + std::string x(b64_encode(s.data(), s.size())); + s.swap(x); + return s; + } + + inline std::string b64_decode(const char* src, size_t len) + { + std::string x(modp_b64_decode_len(len)+1, '\0'); + int d = modp_b64_decode(const_cast(x.data()), src, + static_cast(len)); + if (d < 0) { + x.clear(); + } else { + x.erase(d, std::string::npos); + } + return x; + } + + inline std::string b64_decode(const char* src) + { + return b64_decode(src, strlen(src)); + } + + /** + * base 64 decode a string (self-modifing) + * On failure, the string is empty. + * + * This function is for C++ only (duh) + * + * \param[in,out] s the string to be decoded + * \return a reference to the input string + */ + inline std::string& b64_decode(std::string& s) + { + std::string x(b64_decode(s.data(), s.size())); + s.swap(x); + return s; + } + + inline std::string b64_decode(const std::string& s) + { + return b64_decode(s.data(), s.size()); + } + +} + +#endif /* __cplusplus */ + +#endif /* MODP_B64 */ diff --git a/third_party/stringencoders/src/modp_b64_data.h b/third_party/stringencoders/src/modp_b64_data.h new file mode 100644 index 00000000..4fb321c5 --- /dev/null +++ b/third_party/stringencoders/src/modp_b64_data.h @@ -0,0 +1,480 @@ +#include +#define CHAR62 '+' +#define CHAR63 '/' +#define CHARPAD '=' +static const unsigned char e0[256] = { + 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', + 'C', 'C', 'D', 'D', 'D', 'D', 'E', 'E', 'E', 'E', + 'F', 'F', 'F', 'F', 'G', 'G', 'G', 'G', 'H', 'H', + 'H', 'H', 'I', 'I', 'I', 'I', 'J', 'J', 'J', 'J', + 'K', 'K', 'K', 'K', 'L', 'L', 'L', 'L', 'M', 'M', + 'M', 'M', 'N', 'N', 'N', 'N', 'O', 'O', 'O', 'O', + 'P', 'P', 'P', 'P', 'Q', 'Q', 'Q', 'Q', 'R', 'R', + 'R', 'R', 'S', 'S', 'S', 'S', 'T', 'T', 'T', 'T', + 'U', 'U', 'U', 'U', 'V', 'V', 'V', 'V', 'W', 'W', + 'W', 'W', 'X', 'X', 'X', 'X', 'Y', 'Y', 'Y', 'Y', + 'Z', 'Z', 'Z', 'Z', 'a', 'a', 'a', 'a', 'b', 'b', + 'b', 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'd', + 'e', 'e', 'e', 'e', 'f', 'f', 'f', 'f', 'g', 'g', + 'g', 'g', 'h', 'h', 'h', 'h', 'i', 'i', 'i', 'i', + 'j', 'j', 'j', 'j', 'k', 'k', 'k', 'k', 'l', 'l', + 'l', 'l', 'm', 'm', 'm', 'm', 'n', 'n', 'n', 'n', + 'o', 'o', 'o', 'o', 'p', 'p', 'p', 'p', 'q', 'q', + 'q', 'q', 'r', 'r', 'r', 'r', 's', 's', 's', 's', + 't', 't', 't', 't', 'u', 'u', 'u', 'u', 'v', 'v', + 'v', 'v', 'w', 'w', 'w', 'w', 'x', 'x', 'x', 'x', + 'y', 'y', 'y', 'y', 'z', 'z', 'z', 'z', '0', '0', + '0', '0', '1', '1', '1', '1', '2', '2', '2', '2', + '3', '3', '3', '3', '4', '4', '4', '4', '5', '5', + '5', '5', '6', '6', '6', '6', '7', '7', '7', '7', + '8', '8', '8', '8', '9', '9', '9', '9', '+', '+', + '+', '+', '/', '/', '/', '/' +}; + +static const unsigned char e1[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' +}; + +static const unsigned char e2[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' +}; + + + +#ifdef WORDS_BIGENDIAN + + +/* SPECIAL DECODE TABLES FOR BIG ENDIAN (IBM/MOTOROLA/SUN) CPUS */ + +static const uint32_t d0[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00f80000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00fc0000, +0x00d00000, 0x00d40000, 0x00d80000, 0x00dc0000, 0x00e00000, 0x00e40000, +0x00e80000, 0x00ec0000, 0x00f00000, 0x00f40000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00040000, 0x00080000, 0x000c0000, 0x00100000, 0x00140000, 0x00180000, +0x001c0000, 0x00200000, 0x00240000, 0x00280000, 0x002c0000, 0x00300000, +0x00340000, 0x00380000, 0x003c0000, 0x00400000, 0x00440000, 0x00480000, +0x004c0000, 0x00500000, 0x00540000, 0x00580000, 0x005c0000, 0x00600000, +0x00640000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00680000, 0x006c0000, 0x00700000, 0x00740000, 0x00780000, +0x007c0000, 0x00800000, 0x00840000, 0x00880000, 0x008c0000, 0x00900000, +0x00940000, 0x00980000, 0x009c0000, 0x00a00000, 0x00a40000, 0x00a80000, +0x00ac0000, 0x00b00000, 0x00b40000, 0x00b80000, 0x00bc0000, 0x00c00000, +0x00c40000, 0x00c80000, 0x00cc0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d1[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0003e000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0003f000, +0x00034000, 0x00035000, 0x00036000, 0x00037000, 0x00038000, 0x00039000, +0x0003a000, 0x0003b000, 0x0003c000, 0x0003d000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, +0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, +0x0000d000, 0x0000e000, 0x0000f000, 0x00010000, 0x00011000, 0x00012000, +0x00013000, 0x00014000, 0x00015000, 0x00016000, 0x00017000, 0x00018000, +0x00019000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0001a000, 0x0001b000, 0x0001c000, 0x0001d000, 0x0001e000, +0x0001f000, 0x00020000, 0x00021000, 0x00022000, 0x00023000, 0x00024000, +0x00025000, 0x00026000, 0x00027000, 0x00028000, 0x00029000, 0x0002a000, +0x0002b000, 0x0002c000, 0x0002d000, 0x0002e000, 0x0002f000, 0x00030000, +0x00031000, 0x00032000, 0x00033000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d2[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000f80, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000fc0, +0x00000d00, 0x00000d40, 0x00000d80, 0x00000dc0, 0x00000e00, 0x00000e40, +0x00000e80, 0x00000ec0, 0x00000f00, 0x00000f40, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000040, 0x00000080, 0x000000c0, 0x00000100, 0x00000140, 0x00000180, +0x000001c0, 0x00000200, 0x00000240, 0x00000280, 0x000002c0, 0x00000300, +0x00000340, 0x00000380, 0x000003c0, 0x00000400, 0x00000440, 0x00000480, +0x000004c0, 0x00000500, 0x00000540, 0x00000580, 0x000005c0, 0x00000600, +0x00000640, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000680, 0x000006c0, 0x00000700, 0x00000740, 0x00000780, +0x000007c0, 0x00000800, 0x00000840, 0x00000880, 0x000008c0, 0x00000900, +0x00000940, 0x00000980, 0x000009c0, 0x00000a00, 0x00000a40, 0x00000a80, +0x00000ac0, 0x00000b00, 0x00000b40, 0x00000b80, 0x00000bc0, 0x00000c00, +0x00000c40, 0x00000c80, 0x00000cc0, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d3[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000003e, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000003f, +0x00000034, 0x00000035, 0x00000036, 0x00000037, 0x00000038, 0x00000039, +0x0000003a, 0x0000003b, 0x0000003c, 0x0000003d, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, +0x00000007, 0x00000008, 0x00000009, 0x0000000a, 0x0000000b, 0x0000000c, +0x0000000d, 0x0000000e, 0x0000000f, 0x00000010, 0x00000011, 0x00000012, +0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018, +0x00000019, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000001a, 0x0000001b, 0x0000001c, 0x0000001d, 0x0000001e, +0x0000001f, 0x00000020, 0x00000021, 0x00000022, 0x00000023, 0x00000024, +0x00000025, 0x00000026, 0x00000027, 0x00000028, 0x00000029, 0x0000002a, +0x0000002b, 0x0000002c, 0x0000002d, 0x0000002e, 0x0000002f, 0x00000030, +0x00000031, 0x00000032, 0x00000033, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +#else + + +/* SPECIAL DECODE TABLES FOR LITTLE ENDIAN (INTEL) CPUS */ + +static const uint32_t d0[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x000000f8, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x000000fc, +0x000000d0, 0x000000d4, 0x000000d8, 0x000000dc, 0x000000e0, 0x000000e4, +0x000000e8, 0x000000ec, 0x000000f0, 0x000000f4, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000004, 0x00000008, 0x0000000c, 0x00000010, 0x00000014, 0x00000018, +0x0000001c, 0x00000020, 0x00000024, 0x00000028, 0x0000002c, 0x00000030, +0x00000034, 0x00000038, 0x0000003c, 0x00000040, 0x00000044, 0x00000048, +0x0000004c, 0x00000050, 0x00000054, 0x00000058, 0x0000005c, 0x00000060, +0x00000064, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000068, 0x0000006c, 0x00000070, 0x00000074, 0x00000078, +0x0000007c, 0x00000080, 0x00000084, 0x00000088, 0x0000008c, 0x00000090, +0x00000094, 0x00000098, 0x0000009c, 0x000000a0, 0x000000a4, 0x000000a8, +0x000000ac, 0x000000b0, 0x000000b4, 0x000000b8, 0x000000bc, 0x000000c0, +0x000000c4, 0x000000c8, 0x000000cc, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d1[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000e003, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000f003, +0x00004003, 0x00005003, 0x00006003, 0x00007003, 0x00008003, 0x00009003, +0x0000a003, 0x0000b003, 0x0000c003, 0x0000d003, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, +0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, +0x0000d000, 0x0000e000, 0x0000f000, 0x00000001, 0x00001001, 0x00002001, +0x00003001, 0x00004001, 0x00005001, 0x00006001, 0x00007001, 0x00008001, +0x00009001, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000a001, 0x0000b001, 0x0000c001, 0x0000d001, 0x0000e001, +0x0000f001, 0x00000002, 0x00001002, 0x00002002, 0x00003002, 0x00004002, +0x00005002, 0x00006002, 0x00007002, 0x00008002, 0x00009002, 0x0000a002, +0x0000b002, 0x0000c002, 0x0000d002, 0x0000e002, 0x0000f002, 0x00000003, +0x00001003, 0x00002003, 0x00003003, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d2[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00800f00, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00c00f00, +0x00000d00, 0x00400d00, 0x00800d00, 0x00c00d00, 0x00000e00, 0x00400e00, +0x00800e00, 0x00c00e00, 0x00000f00, 0x00400f00, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00400000, 0x00800000, 0x00c00000, 0x00000100, 0x00400100, 0x00800100, +0x00c00100, 0x00000200, 0x00400200, 0x00800200, 0x00c00200, 0x00000300, +0x00400300, 0x00800300, 0x00c00300, 0x00000400, 0x00400400, 0x00800400, +0x00c00400, 0x00000500, 0x00400500, 0x00800500, 0x00c00500, 0x00000600, +0x00400600, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00800600, 0x00c00600, 0x00000700, 0x00400700, 0x00800700, +0x00c00700, 0x00000800, 0x00400800, 0x00800800, 0x00c00800, 0x00000900, +0x00400900, 0x00800900, 0x00c00900, 0x00000a00, 0x00400a00, 0x00800a00, +0x00c00a00, 0x00000b00, 0x00400b00, 0x00800b00, 0x00c00b00, 0x00000c00, +0x00400c00, 0x00800c00, 0x00c00c00, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d3[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x003e0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x003f0000, +0x00340000, 0x00350000, 0x00360000, 0x00370000, 0x00380000, 0x00390000, +0x003a0000, 0x003b0000, 0x003c0000, 0x003d0000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050000, 0x00060000, +0x00070000, 0x00080000, 0x00090000, 0x000a0000, 0x000b0000, 0x000c0000, +0x000d0000, 0x000e0000, 0x000f0000, 0x00100000, 0x00110000, 0x00120000, +0x00130000, 0x00140000, 0x00150000, 0x00160000, 0x00170000, 0x00180000, +0x00190000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x001a0000, 0x001b0000, 0x001c0000, 0x001d0000, 0x001e0000, +0x001f0000, 0x00200000, 0x00210000, 0x00220000, 0x00230000, 0x00240000, +0x00250000, 0x00260000, 0x00270000, 0x00280000, 0x00290000, 0x002a0000, +0x002b0000, 0x002c0000, 0x002d0000, 0x002e0000, 0x002f0000, 0x00300000, +0x00310000, 0x00320000, 0x00330000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +#endif

BjtU( zx{2SW@U1mSk5Ow&0}^>DpQG{|ETc?>wwzfD(zjwkoN1+0&9}*=JO=yvt;wZ4#Ju>+ z+u^#Wc_n8Y{Sb+mj?k5g{&jE5@l}2P64o6vj`!f@G5J^O_5yMBK1|~^5}3kqn1AYN z8h1z*`M44S&LwoDieX~n+=m(T2Uy3%#J&x)=x@FzGhx%dAYXc0{3#}w>%Copxs z?M@Zr?1JQ5%v#mAsdN@{z(hFheSexoe~jxuza@V`wnW(TOr@i6o@xq~Dh7TabV^oR zZ$KiPC=%=LTd&hu1pSd$H@H&}^ncecHi+9x_0#UvJBWq);&IIF=N z**Jk9z*U2!ih&AQ3`S=)C^1gRTNv(4+4dv8YrM|umJ4hB`>gv+HKGSbAREMt#oeq7 zc`GSi*=e8JU(*6JF@t|GFQ0XYq}4(;^TRGM#tA(>eB}VefO9&o4A;-M=`by+Vp>|% z9k*d7`&D|`P_XSKf`({@w!IrGKid^Wo*@0$>$v7ZH#f=Q+X5`b2B-G7KoBYae)dktO- zytW#pDK%)S02|yqAYETYvrz{YrAJ>ylM)qbymh6bO(HB!+ZakhY_T9s+nf)}TOn^a zRzWSVE$@qLE*l<9n^XY;VQJH5kZF2#hSZ}gmX)pDaP+k%*KipA8yl4MeR<8L#9*{; zPVGe-f>kK|8K<@~I)AhZu>`^j-PcIifESGOs`x+K`bK(&2H@%YArih!DQm~fL^)~N zaI}2u+*rs2%-PkcSMIT-)h2dic4sOrFog?iA7*H5l5oK*)26%a;)DtpywY#F+b+9< zz`Ix#|IW8HULyb$M}HTHOalb7xbi05LD-pax`NaOW*V;EHh*X3fSva2>;}OVxjd8MYftPex+a@*Nvn&Ex-N1qr4?a8mZTrI?gmn(g<7Fxq_4=X@pc2`!Lh?gGnQE@t-+U z$TUN-s|ERsfy3dI@aERs<&|9Co);tI?b~z$F|0gPeSe+PpDC{md%mjIj(wdI8xPl# zw1k){Q^sOyQu1aNgmAdy)bCqwz%{#rq>Ri!Xo25FE6`;0Ir&?H^r#%j^K@}UHArvvk;{F z-$pn*s(;pRCi|9w+7d^p{_-*cJFypP>kVpV=kTY%W|@t#23O&;S!Vya-8wEW6NpDkIrtih7<@`% zpKFY8-mxGl&){x-Nu)O$wHE)|vhYYGNOvSRnAeTxdbnc=-*j0()m)esnZ>&|CzzsRO^0sA|oqy$R%P@1k?Ve$FmN%it2&}2thiL;0)z2MO zQ_v}QIT6*AF_Zt&jO8se4u5&2FL#3HvJcZKcS6&L!_amKF&FgYy(ndczHYIF4NT}uuk8~_owb-`=XB*wZr*2H834`{o4tm+x-UFS+_f1 zdOl_!ZpAsy^4^Pc@-h2xbn`QVNrJ2R3>f0=xVP%qH zT~_|QAZ+-RTO0c@OF;ox7e;${X(F>Iz47V3RQiI!N!2ekOck9CX@6&Ycqus)-?wJY z6j(SE#!y0TnJR7^if6IhI24_4yK^YszRmOnVbjI=L@?$3qU}4cz+r@Y=$_iD{lfhr8|^+>V;~NxYidWp;`o%)Vf#aT@~rml$%;! zeIE_o3pkNe@n2dQZ|m3X)-jJfSH{~$3I1^wM#i7phI7Fc#IpX}HXM)4l@peOZvlm8 zo}9VepDCml0C=WUp&%VPVmkY`lG4DUr^+$bptF@3>+6y|KRKYUwb!su8O zyw19@s0kjPI?mrX6w^$h`c_7Ynn;(v53|UW!GpRuInFPIXwW-c{AcGWR9>X#k&>?* z;6}k1g{X9h$$v(QOrDN8r~_z_X~S`b0cq~Zl4_-bbLo47l^ob@wBGzF@V=dGHoLzd zq{@z0Ebx35$W_5-sdcl=@Rgiuqhx;yEJG?+kk5JApaq1$`KO*`NH~610OC{1sCR*7 zNU_JhA7_(|#8T}x()5oEktxo?3Q?b@0O7z{h*>$?I)4~1Wk_!_h4W^yP4*9&!da8I zG|7t@i!YXv?8Q7ZUyxYT)Mrgf;hVfGX5klB61dj6RkGcb(tTSGkp99?OZIgyupq0= zYxNB!`(KY#+we8g>VVc;DgL@Ky}pdDcj1=_Rc(G1?p_G>fml9WwnYvxUl z5P$YzMF+x(DEd_zetViqh|YitIlXP<=V||B5o~Sq3O0Wh&Lm!C_>1j|*&Is^$dJLv zYE{Eb)3QL0u9Eg)(+p#&K`(PZGyf2Nw_Bvd7%=1N$c0}f$ee!t3!?^14w6Q_GXUx@NI(;N`EFMxoUzLSdAs4!TmO7U_pwtM47l9t5Whs zkaIq<%)o*|Ov%@q%)rX;#K80N7FMOa69dodR&sl_cVggq-N?@YlwFlhH;fvEdHq*n zp!02n78$-*@z)JdHor6eoMAn(gGNwC%r-fzY#$i3vNnhpI1sYgt9z~)WS#F+K7VJk z*bW)el$6!iU6TXr{`;I?weSsml8kpwWq>XCBuRd}vTDvHLr!$4d>xn*ltM2*m!MNzXVxFO-1?BrZqTkY2S_Q z&%3}_EPVZ}PrU}8%pYRXpz<^f@kV_K+m5e8NV_QQi67Q&z-Jw4QE;1G_aFPKHcVqJ`et=pSM#4?CKh`mO3cSRYhl!Y^S(TgbD0hcH*r={@t;QG zja0K#SKNka#LM=Lv_t*dOCMc0A>=6U!2IXe9~|z(uW5%nGHK;qGIQHhL(f>)25FJv z-kxR%I@oD^u1v~f)Y9A~*z`yEgIH^5&eU(Y<|pbLVf9O1!r> zcUBJMuL*B&?kwbNzJWz4RV|1qu`8d*;O(X6LWo@3t!r&*vVRLIb1z4p{({uWcAAUJ z>Go|(lM68oq<2hz7rJm$rl@qUUbn$p<(zZ(zm0kC^{OL13%Rs~OjjN0S#hz8J@bZ# zuX`muzOpl`t3!a^UUFiJ|56gukc!NoDSkq_>Z&!;cmA3hhO0v^{+gDfE4^0|LRUuI z+_gZzRcK3Xvww8?AA59!Jf4;p@V)iAnkAkENqTfZ$+hYp8Rja?PKp(O*kE5NmY%pL zOL7(9_V?v|>}enF6dK!|CH7->zpX(+ zsF9L?>V=pFrmgzNnSC{+`Au@ASCZ}1RakdcTrHED?V@f!n>OjI!9cTb>)BVM%&V}@ zc^bP%7Jrr>>{HO+Mq(PMn&cZJCfmamoWpnB$zMZSL1jMd1y3M>et$3@Z7?g~;?cW3 zO}~^Sgv@COu8=o2;?8MK-?!|6H!jd}z8}&_QeRu>Uv931$iCqLK*_zctKFznKS1G?@hoNtp} zl#tu~X$B5gIs@&30O!1SvW7te&i$nZsyDWc$tzc(920vYPC;)ME26VmECe8D>hNbu ztvi6#i@&GRi*nX=3XbVjtLF!nO)ZG&MaiBYarJSzJ?#t5%`7r3?t%!;g^66c=$s9n z#(xWHEvN1-NFtwgy%HClDUi=P;LqT{l#DYO*2$MQ=^hD`tM=3aSeozL;`E=!Ye(KD zKYSb2DHNdkHd|^P*hPGCEZ&}Ga4t@Lt#dp~fmAY~$532BS~>Z>*Wf;F<>ZgcF+Qiq zM<&VwbowVNCnm~5hMT**IWbWdZ&gD6rGLIVG>E!d5RFW)CX>DS-$tAZTQsM?Agkxc z%HMXD7k~n;)@oqj%oKiL?OCg6GgDH@1ZL&?FJ*iN{zuxMW^pcA>gJ-FJ`$csD>Gw3mq~_%)wod5*Sx$EA95YAk0vpP(G+Z41mq5IZszYN-9j0t$$j2 z&9Gat{cQ?**ge_anQ3N6hq$<$&U-5TNII;&uS1yc&{NjBSC>f^CeHLy9!~w$Xk@X= zIp5Y;3fGd-mgZ8T)3o&*lfS&|M*{2oa83&oXJDPxIkl{>ja*JW6?fQ$b4T$N{)Kag zR`>?MHgbASFGQSNOw)$0l{t{vRBoAoD^C*plQd_v=m?#^iu~1$Nt>u_V z!c6r!%5^p8WU419!1rM}W^%zc0_^m%W`54!zE@;B-$v@sJg-adW5Sm}r_xs@S2<=R z5jx={gc#7UEB1Fa?F3Ao25}qVTSK4Ir=Xp9Ze2Z zeVYLkEUc^Wd8fZ_jMj~-n7~;OCO*}%vJ7wEW|F%*DV};B6x$`T-BNIOVRf6h=&H}k;R@iunr=jC^a=jEKWedh`*=Nxd!Pk&$I9A65jf$H~i}Kq`2eP za~BDgP-iC3`@1e34Ngo{>JJ3nU=BzLusb-C!Yu3+x!yzab@ zJpzP`xqkpj?#pXCITq$mrB?izO6U(hYVtRW4IksoWw#0G@EZI>D3sK?1rQb-M`KBtR%Z(Xsd&g^HQ=K!U-(hpD9_p zgjK^|UTI|;S5@Bar8=)xTG^&mmFIk0pJZ=c9Dm`?x3QINhtjS7(!Pb+c4x#wkl5|{9Ua|Sd zS?IU`!BcnFP57j9O4CNT-1@V712y~t0gK4(fGBJ z-xn^gTDNZrvm~7GwPk4}<_U}I`h1)vLVsAUUU+BaP#|wZ%4hwVc4#CftgDymbsJ1} zt6iI2{cRK8qIi}5F3^8#qiaoW5@x$pL??d=k_kKG7|GqNN~+fsjDzuo+=~^_Ik6oQ{;R$D0X4}mtd8^WM!^oQ@_aX&O&m*|Uxe5i&xMJ_;nF<9Coh1LIjil+Zj(_G) zp^?j<7wP-c^euug!uFBXOAQ1s?58=^y6HQ9;Q#}2)_$*n;OzwvmejWyfW^Gp))%+> zd8UnF3SQ;-O0_Xe0Vr9Ey9?w1>wuElE@&Dvp&FTs0QIaxn)DP4;{owBhAC$1${RFO zV3-0^)p$Wqfnf?jM71EM;JgjYw0}jlAZ8fJy=G5pLCj3qEbQmz`)Fm_B+dEz=KJ`x z9$1XUCfna6l8-OF608BKE2H z-^P$>V;YVB(!!LSJjy-=6PBdPYIWV9S6!XDA=8ojs$sglRD(nSiVg@^9)G3)l{MO2 zUD|I?(*TxD6$_h3?N8I8W&nyX)9TWG8>S&ANvum=rJT+b*i9&1IU~-3uwr1g#j|yu zLgM8j%(7hZo~IxI*f>|s{CV9Z0Ne4&%@s=tz+!AG?i3`&b&@%Bf11X*_uN(I+gPJn z^J=tm8>Xt)rJIm2Jnh4DEPs^U5-D%VpXogSdwZHj&CqNC3M>12D!HrD6xp9C1F$Ve zbpXG16M||RO5RM3CX@qvcn(*31#-r+Y~Sr^CIHKdsJd+5c?ztmjIk}-cRmYO05++a zny0Z1BAhL)22C5pTL9LXDcd0SxavpG%bP1%RItEmvrM+>EwXwWrhlWCt$b*+oNw#t zL!0F+2n6YeHp^MwL6X-{)dBo1X@%sp!r@RN@kCm2{aWe!TQz9~VAHF<38imS)!|EN zsMlKDhiTFZ)^bY0nR=Qctza!@>Y-O9<)()b{2uIsmdh_in*H5c>%8~uVH6^hT(@DG zw4zit@0cO)=lQp!6@P_EN1?6w_;I(64$JA+O_;c|8hz1bo=*ia>crEAZ*SNd=_lFM zS-{ag$*$h8NeaNGB+5(yjw8k_{OpLz6mPXRWeYV>3F55|WzKDwn4BONxx`t8F`CTU z&+c1Rp$a1lr_yvLb(Vbc$*2hp$$&sQiw=jYO$cRyXYf%7UXOQp?|(Dw*Xgk3tLOCoy#ha%RT!D24i>bPdi|R1?K+6m7rV-5m`#VBQeCig z-_I};bWBw`Z0G%HCf>#CfmyR$&EAH6>0J+HH6+*SGt9!Qz*c`{S<|0krY;dq4NUIk z41Q*+r@(+rpNm26HcUT_+>9|z^-_bz#OfSxp!E$MI5 zGiA|!>sfu94K?49`p+;^v&zfG(rSD7Hun96{~Y&WmWI2(O3foR|J$gMVJ&myKb>DH)MepHcyeAU4F>Y+WO*BAXp&fs-&dQin}iq$ z&VI@5f|BQNtL&FNRsr5^7ff(?l7(tTlWxPlBsa@ZNbY6<&b^=-$^w7qr!gZ3h_eKL z3e3o1|M-2FtqU#eaNLqFArkeCv8t`t*8^EHa(~jwNxeI+vHp&2L6r=V^&7$5&_Q+b|P=g;>5jL!Y<)U8334 zaW+}YY?~ApyUk6=b*Pyonl&#*4`*C`bx<5n^k;ClKyZQ+2<{f#J-7vDaaf$7gIjQS zg1fsd8iEttHMqM5xc&a>?yBzo>fWB+shO#rey{s|WG&-jU;;_Zi`Qg2u<%&CsN?-L z=f7RDN*`mV3Me1TmOp-U0$SC|oJ{{1)yjrnSK{k*-x&H2kf*k7uw~>IOg|}iN8zHQ z?6$S4`i#P^A$h{O=w0)wFszeQU0=E~gKh%?df6J;IUE~%Mh5tO>IO)S)Bx=lhPXNQ zRH-|^`7V7AT1$enwdAby^$y1D52@zNe^(`&ZV@f(ukvD`K%!7%Mb8RdeIf<9@fiO& z+>MI(Gx;vS*5g-?>Q*>u#pRu6afSV~l$ING4x`<-VR6=)V&L} z2G8|P9;4$h3$@gb^_uW7^ z4Uh#kHX;y|&)RWbFl0NIpTGxT4qdO@GU=NiU+AlRggAk{x z#xS_Fw15PC&B)PuSmvvr2f3A`L$&XWM45qbX&r5ejZM}en*(nHfsGss(QRXSH`d-Y zFB62o%L$t`bWd!aiV6OXj?RQ@G@+`-B8;9vf{s;8+MUoZR@+0HtBi+ptoD_3GGNmzaNcyRXor3n!KXp?><+vu`C{Y^i5S0gc`~^M>S8*4y?<5oLwQE5i0pX)6xQ* zpe4-C46Zg&q|a`?qJ-pd^Hc8kXwfSpGn7m zMzxmdB5-8m3edEVqLDJm{ZS{|XD#cMTmI1QJ@Va##e|;Qs=_6GOy6uV*8;9+Y&rzx zrc12U^4(%mQzXw^+qaeq_%%-k^{EZog9*j-$QZL4nt?Hmq4FnG?c5LAA;e<LtZ9BI9xFH)MKXew4#dmlUgi zL>6ZAEs}r#n*bo(&QNAMyDh%?fLZ~u#aGULDz{&XBK^>D)Hb7L)@-0~8C5 z@vme@uks}K+Cq+COITZe@Nq?pV5^mQeJ!WB!0l^E+hn1IjVP6ZaZ$=lcApK_7@`*x zRFz^rnU=xYvQNLmNbWgb9GWh#8&Te3xR#hqLCHT@oddnht&4$*21eQGkF7hz<&0g^ z=*7QNg1U9>5Q9p_fdf=e;;$2R)x~sBb|r1?*NHzWU9IZIC`|JO6@Trv9KFQ?y){kC zri_@jC8H0ZaB4+M)Vx-G)^F|T&gH2#H%KDbK!S`*O1jN9`F?WqDuLLfJ*q%B z`Zb1;Eq9B1F-3B{Z*ZaqS&COd6^qsMC_tRzHeL!$bNiA&) zyZO0M2+#AYl54tUoIrz@GKjAcLO5H+v73wT2sr{-~9HO|JC!D>@Cg`RHu){!)fkQ(d?O1Sac;QH6G>h*r3ZOpctjGn%m{04Dvr8m9Yh)N8kW*-ao*3N;aR zwi5mj4NVi-m;SJcwl;oF+=~G6{z3fMa-+gJ05op?&S~za>rWF2abPXDhX7)HIbQ9k z$L*ZeKFye=RY$^YrH3x#h3QELpzbtMF_fMEkuS(di*&zEicIviL|SEos&Snb)3PMztK=0Hio zUwO5sBg{ag#?x-?%))m+PVn(AgITx{H8l_h`aMxQBg)%~!%|bEQPYbdr?#@~s&G%x zs;EH89KyUVF9j26${aAO%UkZH6AR_sC2(*CUCMuOf!N0(xhOVKJefjGb^jDAp{Vh2 z_eRa%&cU5CrJ&0-Y^bjCj*3pm3dxD{d#q73*Rq;aV{bXBsPBaM7_HGxdZP}+3XuU$ zXPn%d0@tRVEY8K(?d;Cu`WaJe1{5BgU~4`1kgUV;5K^@MHLqe`?ZXoY7U$u3v>Pfv z7vRYN#y0ZJQu6P3M`K8uJu#2PL1F-HS!(C$e>0wMcRKhhvym`E)!OVddu(@>FwfdS z^TaUeSZ?pQpUhytWDcTx6ieO^0m~H?2veo${KgFgxAxstG$W5tKE|};O|Fe*jouFD z*kY=8(~xBvH)-S0h3Y8J*f)fY3MmIxZaMk)5}of$g;h91;S7cTl{5pH%tjfkce_7q z(in_P3%u1l9yvi_oIb{1)!AC=?dJ7sI#d11nS+sDVJ#-ifHKY7Lroc0VDwQ0$h_DX z%g2`_aIj;-f%2$OSE^2A;uKZt^;L%-SDNz^Rupd$tZCrBwbqNpn97mmT>r|4>LL&# zZNc=p`04O6s1ixlWKu3NMkonu=^k3@%=Qm4D^QIYi09`pM4(X1#omKwl$z50`1Mj1 zWi3%F%>%**eABv;nZHf|_IPjR!@Kswl%*wyRhB;-bXFk>HN(pplJSQl?t(e$a%x@# zxf?l9-8v%oeMw)>L>k(6GY1ODjl3v#jXKMt`k{7xr8dPG)mV{e4l5^+=AEjOU_GeZ zmjN1nXAd>lGdB>jYRsZ;UTTCfGhhC`Fievfnpt%wGk-t6(BuNZpa6@|1nkUF_*EpD ze?RlnMY}7%u`D!DFB!vb_}%f0_tF5I#s4 zJ(=Rv`dpjhZ>dCD;lT5@;Wx5Aecfhox{cV)u07qo4n4GbU-DtT=GR=tC6D4hnx@uX zaWU3U2w<`Q5DJ7rWf>!PChC4Aba&FQqPXCAc=o565~#0-S?`AxzWw2pV6LHDAgp|5 zRA)YKQC<(RlqO4O&F&PO0h1T^(BV3-HHat~-SnQMXxQF$S4l@G++nZ54bjk^^jVFQ zBW{xCH9I?&h)yF7{mz;a^6%_qzQ`${2AiN#tEDa*Xu1kzj~M3X49a)JHDtz8A+=!rf1=~M1!2!Pn$3*F?SW? z5TE;uxB()LuRQCIvl)1@?7V~Ktx<{!Zn28ERM$|TRE|f$+^!SXZkUz*lC1dzehd~` zA5+Yrq3SD}h0~7OsIa1~0-tbaT{eWE6k0+mXktPrxZ8h>qieDGf5I zr-3l0=_4i5sS&f}d7qW40|-g{MzaTA)i4H}3!Kv= z$cQkq7l%n23|yf`9PRTFHrV1zVQ*Xs<0iOIkP_l>oSr`WV=N>#6~nYy!ynJ~K!}OQ z136{uKHk|3W2#?r!N*v1CkN*1cC#?Pj zpTGxkzc^flb$1TIl?FF%tlpcwgLqK#OG)nIUD4cT4pZ~UXp@cRb(N+eWyJ)9f=)B) zlv;o-9$HZ zLl^~}Vu@OKMbSnhy#~Dae`iM~s;U*fa*iQ=%RLh$^f&&>-4Fs5v|I3np-x}~+a)9I z&Lw)4DSN_3mIZK0yk6CF|3$7)ZEv86&P(BIU`uVrgJ!EGKUL^C zp|xu_1Y^^|bQ#Y%er7MZ_LYrP`zwpFn1+U{2*dCaMY$+kFVJ00^k^DqlLJTc z;-I{^mia@Xjt#oXx47oYf$73Cw(*|?zWC@i#is==yADD>oDc<>n!-$ynaT>Z093Vx znJ{6Fj29If_cTDfeR4L~S1y|%&?|#z9@=VqBsDl=^R3RH*7I%f(D4i5tMQGJsC%Rl z>-}q>fDb*N{R7R=$CZ^UsQyBec78L8ipBCFzHSX0cN+r_5ES4ppvZ&L(|z($^olgW zUQI4M;HVHSh#+k?$In_HiGt|2@YsM$x+>zEZZ^{oz(r0xQ%ffBnm@Ugb>1Emnz5ds zb>|futTRo3XzEy=l=iL7KiK-)LUlz?M)f9gWUdI{pX@gWl6)In^?qzZ9VeB~bd3x6 zdECk&dFBOen3i=|9>+^o4hivtyh%*C@vh!ROv7~Xo3;#SRy}T%*$PKchO3Bxosfmz z$Iv=}2%$5l_81#ikCww%W)6z4&C_f(kLKN8+CZS1@w!EFfkY2+8NBpP2n5r-<~6{q zKy;tz;E5S}m7xj_Tcdv#%PYzgK|j@LR1~a^%+u*V3F6%dujSs`tDUGy2C{}<%u zU~@cf;l`kNEcC!irk2LD*Uy5!=Y5}dQU>$dQ1=u3d1LzhE?f=HfTD*A3crb$_RF$v z+>i_5%3SrPPkn}Y+P70w2Z5bjzX($ZiO#?Ew}-!PaNU<7UqBA#F0RfX6MIx>FEtc` zDDnS`nN$5=1#@-^K0XRI3N;E=X{eYv*Z=NvQv7cPbDwU+&j2qs7ythwVE)Tjf5KJJ zVeOgdmk3NMt*SN!@}K*CLZP4@9^)RY!!hz9z3J+j=Ari$?PN5+MW27_8`(!}Ytj(> zt?_?*E(w3LqJDYGeS2JadpeyO`S+iIc?qF6)<2_d*4K^hf4d7$sogKn;J1IN7snfK zz&&_Nn@K3lNNC=OJ?ZHf{Pwic{W6%l2_%{-Zao@x84-@g3B0YS_8-^4i8Q|=kUzf| z0nhs)X7+xwkCP9#3&1n8@ckrs(^CH5?ZVr?)9w(yE8q>mIgH!FqM_l2LDT8dL{ zFG^{#te@+vQ*ULkK6tO+^UT((lB+t~k>BwQN=V1Go>vX{>Y|>iI*$!+=tu6V%6zUr zXa3a0G+ba=rh5m{WKMhtXAXqC${@(dmbGA!BZTeH6#x%b!xZ4iypGgl8^9}yis3UR zij6K~tj^zq@eTddFv$p~?#ldoo*qhmI;nMN=c-_9A(TpXJylLNHbyXf?S&iV7N$;R zJe4V83rA*z{7i$2kYUUGS*Bo{7yC}NM^?`qGK)_oNEzf)21;?adHd;ZvteCwm;HYl z0l;-e`Bd<976SZF>$<*DoW1O^eAwDzAlP)?sQ#=hm@U$7U2-kMd+PdnP@El;+GP9R z2?WmV|K}GsPfZ)gpcfc&=}3XVDdn$%y3VoBGC$@&T~6Ubj*Nu(4%qeNrYxQ1UT|*CI=jJv?A ztnUq>d%Lx-T0eEHq4e#&mPt1Uq5fK#uyw^Bx4iuwL#@8-zUf%K)e03hMFv-2-FXjr zK5JxZ9~~`naS^)KR9ecJ=E35JaXzW9rteuhYDAuNRR^-yH$I~ps$-OW1L*YDdt1oL ztDEi9Emdfxpl+{$ySuzo`dc5pi18svX{m z2xMPV4#* z+G)>abldm6|2Sawxty0;mieMS`4)=zf)RcWUt0)lW9NubwI%LqQ6A+Net{&8`O0H%OS)_V9`F4 ze+bA9j<$Z4Ih3PKyW314s6@O)%+~uCt4t6Pb+~R_b{$a!zmfR)>o-R&3)!7j>wxXR zj{(1T0aq`zrlcKtEfe|(HpLkw051m*vh-oCQAD3eK1N`Ag7OL2%0m=hyjk~k3cdRY zbMn|6;2gTfS z=Q{YZVJgV^NE7=6DN^+ciKF@iB~>chTqF7fjY(zS82roFj49bUV-ARiC_jNvO& z-m0RHue)Td{IY$qo#pt~O&wjutBrCROUsPD$^$kC?2M*@e-EYR7KVT#6>12sEyk|tAGT}w= zLv2ydW~guaK^n3h13$VO8eL^jN16<(p3|YIVp>-~|Kdv*ipwbN=l1kii@lVJ6t8R5 zv~%p0T?_ILss8bscX+>K>15%l<$^O`whF5L?Et7*6*4_oA276tg~bG_zMP3M_*~lt zUFs1YAq!}bH*)A6^ZKeu7qjC{wQCD??#c%45PFow3M|SQj~f0(l1NYY?3vG)1`E(7 z!2i}6%RUl~N)N+RSU$86cY6q!);k?>`RXw;)F@hfi^H|nBikgl`%DU3lXHHlec)P5 z!VSC^&wtn|^!bh}S=_s!{~P3{gLHZB8YLr^L=TbvET%(^t>(Vl$aeh>bIPLS02IDJ z5kQWo4MR<-c97Q|rSblQ_D3aMQ8IBkDebG6(`wvJ^Y{TC&;}hFXM%c0C|p;q$v=d$nK)??QjH;4@|&4AWU-la@~i zmSK>P!LYzNa^-Amg4+bnW})y^a;3|VzHS-J@RM=WCR?C{{2%F6_*%Zhx52ZF%8OCJ9xLV@}Fs~6Ca?qnTJmV0T-hd+>%Lmx9jta|Bq z`*gV%F_^)a9+tCFSNs^T1Dmgc_#`Y=wh_X5DPqYue76$&+pfUm-_igt+me zt(MH6Ps}e<@KX^~H8HOiOep-y!fSv96H%Bz*^<5)5$uq@Z{Q7lU{d@X=y2?)M@|Cm zsL%pjAo7_$U)d&*Y)^a(E!>nweHVv9U5L5LyR(u=A(T=<1$tRy(0kp|#FL8ikee9U z9`gmaOHwtYlz;1;uwBZ%t%qe)Q97K)5P@&`1hu3dmQS82;gZ6ZSVt}C0+NbZ6h2k! za=zdE@k6gzWSOzk>3jLJjM8>SthM;s-kH4xtL5@=5lKU8ow(FPi~r=7@(Reva8z+? zWz6tMpG_6Lp!-X8?b)rZK%eOilECfe8?mDKnF-A=@Dp;>t7uF;n(@DBDp@~9o5}7U z5UC!JK}#8VrRbaG%d&6!Jdmg_G-{i!#bKQyg>^e+j;i^28wBrXqL2Spn(XqOt98Nt zjl1BTZmC*MTf{ZaW;Wd-9F&0;Jwk+D&$V*Akc6jLm1!pX%%RTNH)4eByC`B~w-G0U z2K6bavBuA6atkldeBv%})795D-59oAkD+o^wXQqaX;N{yjF(X120B{aMXI7st8s{o zMhPf!#ovC;_w=2`EB_%N;N5x=l#Y1 zsh+$&ZSxgz2u<1snlJZ&+ib{8ZD<17R7#SAiuGeq;7#yt0V9h@>7S&;fdknaic`pV z1P*KrPCS!FR7}YqK=k85A6BJSCAG>2=Tq?JWz9jL6AAKFyKpV}>YtL z2~6k#FT>(@p??CIB!L@#e))-*TVpQcR9)+TbuI6H!@SkjJMIlh$WE=}(wQdw)%BF| zyFlNMGuAphBae}mVXdOIrG6&=5@$_>L6Et}{t0qCk+AR_JLR_KxX&)|mOZv@_o_ojV1OHnuzh7`rue?~#QznF(_L>)$58Kg{WgLFRG zk18^R49J8HjM48kE6V8f8JD!wo{xV4Ktmc`S$7K@Bnskl*S^wI<6M~>i}A_KUA`J& zSw25gR&V8$X5ci1_zKU-=Tuxa8x5D~e~G|u0Ch9T6E4Cd9w*pg5U=Zx$fv5+uIu*c zhfjfb$c|$P9lQ^>SYPdFeT+uOfu9o(=}D`QT$Y=I#bD*mw-FG?j-ZL`BmSO?1 zg_ThZ*TyMrb68Ipq$q2G7S|Qj98A|eVLu)Q>3Qi2@*sEAX{BvwZcrk_hCX-}=wfEH zFVjQhz|$sITJh?q4C1%FxNy85w837iWKVMAnWKP_{+3TwgQC&{b_A(wJD2~EUMzVh zRSxT)JAHc0}pRkEg5MdgeIguO)`Z^ss#fSx@GnVt{aq zyqorWYFP$}+t2yO5aIQ{Y`BzOQ@(uuBG>@R4zZ->aYHNiqh@=AaBx@x%~_Wj-$E=y znY*JF#+rPHYKG*j>YT>!OHgRxnI!zfVb9^Ynr>RL;Ug5ws=_NwGtsixMzq5EC z(!W3cTEmX5U?xP8!}=otAWX70L}&Q>bBU@6$!BE!U!t1#O*3i`^p!gpi2R|`-tI*b zemP#n{R_M-C|{Xu&%ouTW?(oW$|UCNdas;VYshG%JbPK_P2nyq&fr!h1<#p%g9_F=XzfO-rPr z4Mh_~F`V|�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 bhO%N2kw|Q03>NJtrUx+;Q&Y1$Z!h+5p~^yu literal 537874 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