From ab0ae45f79caaae86b36f8230f9399138c9e73bc Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Mon, 19 Aug 2013 20:56:33 +0800 Subject: [PATCH] Put FastCGI code into server framework Implement basic SensorHandler function. Can now get data from (simulated still) sensor displayed in web browser. SensorHandler deals with argument checking, identifies the sensor, sends the JSON, etc, calls Sensor_Query appropriately. Sensor_Query actually fills a buffer with requested number of data points. Example of use: http://server.address/api/sensor?id=0 Use run.sh to start fastcgi server under valgrind. --- MCTX3420.pod | Bin 272380 -> 272252 bytes server/Makefile | 6 +- server/common.h | 1 + server/fastcgi.c | 269 ++++++++++++++++++++++++++++++++++++++++++++++ server/fastcgi.h | 26 +++++ server/index.html | 82 ++++++++++++++ server/main.c | 4 +- server/query.c | 70 ------------ server/query.h | 14 --- server/run.sh | 3 + server/sensor.c | 27 ++++- server/sensor.h | 6 +- 12 files changed, 414 insertions(+), 94 deletions(-) create mode 100644 server/fastcgi.c create mode 100644 server/fastcgi.h create mode 100644 server/index.html delete mode 100644 server/query.c delete mode 100644 server/query.h create mode 100755 server/run.sh diff --git a/MCTX3420.pod b/MCTX3420.pod index e796d027efd3579afc125c7cb865aebf9604e7dc..c319c2f0ac9410c39d22b6ffcc501c130137d747 100644 GIT binary patch delta 16819 zcmeI3c|4R~`1jpo9kQ<>vSlaxz9(Bkw(KGM&X9e|5)rDwSiZ6pLWq&EMcQo1(t@ZY zdr9`4=bjmfzTe;T{P+Cz^M_Z*Ip=*}=iKLf?sHw&X}gDN`wi8jaC`tC6@gC+A;!Bx zf`^9(ppb-d5c0kKpWpz0JRStKEgedQQA^8nJA?a5k;3JZme&FLHV?LrHSZxRi(xhK3pgwcVLI6rs2X`RAMhU~iH@5c* z^uvq9qrk%>2r<;dC!oV4AtAx5i={>MXt2N^n|1rJXwt!LhXAW=fHnnkWbvY?c5}>)g0NfHd)^C1V6;?nf>#}fd&oV)yV^UKJcu4 zQiU^H+Xc}f@6su&Yf*#Hp}N@ zb>ld?_ZFoNC;W4Gp*PdO~v#UGL(mCS%tTgi!={hN!TO|r8vwUq) z7L-c&=HmsHMUQ^+COzNd;$Dj!sh84of2<9`k7=|eqM zA^4qZ?{=Uw?N^KN+C$H)7164AS}C6E&i5+O*_j?E?$EXY6#Z)aR!YkHD7cW z-GJM#W%L*y7cb?IH(uVd_HP?)pHEynbEi})SjBlT@YY7$PKM=zvdkCf)%ZtDJTI)3 zRok2Rd8CrVicPO>I=~mohMC7L^Po3Fb33kg-ELJ1O%gLZD@Da;Z)(mxb)q7@!NfnC zvnS|Ew{|qS*t&K5hGyy~(`DAqOJn`_InsCF<2JnWp<_D(qUUY4w%=%d5>g!xu}}V` zMGzK=hTV3;D>p7xykd@DPAKt}??#fU__MxPby?E%I-!){Lh5#cQ6(?hr`exZHWmwW zKfmK+dM6NkFF$Y4=;oR3M;T)YW8rD9PP@sVps}@EZ9m=2haAL)?N?)}e5I*#bh=nH zD&VkhO*617E8{WGhb!2ja7bCOhzT6t_2(qWY>c^e(<(q!62e+-C#NPoVpg;`kO54tinAh z@L`8_SYKB_^)%R+0r<>4O@kXce zsOIY;q}~!+%66pP}~cnNuY^@oKKYiTdQN zU*Rq}B=T^smkFig&!ulGUbLwZ5=hfAV%ZJn92< z@+P6)MfHLSk(aM?s|rtrsQjeQtCGFAy_-xFEA9GE^=GfDK7Y@GN@e5Z_X}(!JZKL= zNL8YLNpbDSsqvN1$@Fg3)I4_aRH8INTGMjR48K+vP$QoRG~^esxGlp~T9iWOvKw>P z7V)a)wFqCTnANP!nTSjrSN)z|=A+oF>hb)ptMv(^sNSwR?TYbm(-CdMF2cDl?d7JIacl4Xxou2LDHk4#y&E9v|iNZ29b3}zxb%fYs z{g_I!*0U$r+6&jah|zjiCX@AF>#WWmyA(e0Vf%{A;8`|*VLpNyN7`VK{EMgV4b*<0 zA-uCF!Mv=fFC6p8Jn<8I@~!AosqY66d~XPt$YdqGI!zI*;={6CKd4mTvz7PwQ$h;T zM2u_hvDodb#u9BvkC!-j=~`T%%`f7%_;MDSXeE7lvQm2ZOG34deB$cY%J~ST8QvGJ zd+^h`=b&MQTT9Qb6V;nO_%&MRUZ^iJ9v}Z?iSES(pN7b<)$5wZIfbLn!q?<`eBZ{u znIbdR3NB68q0BW$%kbuig~FlITHc#V$vY*~^!6iOsT;e(g%KXvRCHOx4|}|%Dh>1> zDCBOgr>X4rnyzSrF>x zoZFMP(RweJSEe%{KGdA?@zk?{*h}{)D90~sPeczFDg?Bhn&b`(Bs$&f@3Gs+;>kr> z&p-$HSOrx?8VVu$Jcd`6Avzn!$wTiJF{lrgz5Ge3)viX{Is09Q&INLlkTpwsqEv?> z)9W9npm=up%0ORgn_{0XlK*30Qf1_pdUQC;qDhr>p-{;UL*qLSeeT7)6lM`$GS8m# z(H!E=(cJnqu-1Hcnbx6ZPlbu6DCSB3PKdom^oW2-u&?#w{!@(pXI^{`A$2H`v1#$O z*e)n}bHNvtuA1Xg=XPdlDYk@5(X{J+Tu<3>HsTG_6F5OOm&^`Z+wQyggwsEzJHz+V z`>EK9=)SXS%j8MPCoWUjDX0N@0=t&-R8zKM#ZPbGcxdhSNqFw?Gv5A=WA(qF->&Kwi6H>sAu0D~)Q1sCN5f1ky_;S2w`b%B@=VdV+?-<$OHb z@XC-KCnd^QhG~4Zd+)3H=9I}!KZo-bGgqP3FI|P$(CU0avvqMxc#NGi8Vu0cn2I`M zxyx_KF76_9@fi(`aIVqo?c-MNch=xfAd(EkiC;eV<@FLJ8GmRLmbOgRUw-e>Stb7& zKYu*POY>xqk;uxs^x>GMkHb}*`qBmCWYlAX&wi39=`jgdn@NsUn`_FLz_^m&yHf+a zKH-7$?xK`bTahM)+m4!BPtQ+WxC_*H*KdC0BCnDDapRSy=k+LlHl^Nse$~2Nofple z)Nj*-KlIW3l@4E0ofz|(_YgQ<-M_PN%Wzh=D9_BKGx(*>lTO+!_gWW3Le|fm7<1Gt zL&{5OQuiaZRb_nLhpo-WSEQ2{j%fV ze?GpibZ30-}gnueDnqB6as` z5%r0lsp9j$OoR~(%9Y-g2F#AjaENE}$Z{C#mNA#Wv$`R^rD!YNtyj_hofBLfU|a#F@w~orvO1*81vSJN3lalLs$&^4L>n-6O+@hDY{N z7)CVi{Kv`TaNS=gk6ZWv6QW&I44}mxI%qMTOJD%Z&HF$<4FAJ(DPu9EQ_1Ac$tYTV z-&-I|nfSrHN}k_f`WbP$WQgz$HIJpuRx%N9=kxtr@^()GcqNSZ=orS@%kjKi0)^GXH+?S7! zgP?qH{Usj;fzpHV?>#3eJV5zSu%1!%<7?_|p^!N_U?f&I{+gnHLpjb!pr+Rz9#Ql5 zWx;oy*Hi}U=1Pn_GAKfu<>>1!_VS;OI4%`ZILD>%u;U`qGiru$T=vo^?)<#cewIQK zn%Bh0J^rzyD>>VsP_Fr&qy?+SOAfJWL%g1*4^0tg{&rkC;fEcUiMB(IO9$wy|U7Vk4nGQ3Z~ z{@seYY3hrTW&Q5+?^(LnAHv|`+^G$B_#e=xiQD>Js9a1f%9{I9P*6l%^!5G2hVE~7 zgP62`zFALXxB!!iVBkOOxiqec?raW^g-uoCJeQn&tmk4{t(1y8bbJF39lG0J@8#-J zGQ2t84SFuDm%^8J$6VG1osW7hE>~t}_|IzGQ|lpnr%n>qIJh$=Dd(zg5uuba=5V#@ zDlMwWTB?H@m8AEYVxb(qOO&n2_x8=A9#zNQ*d0CgnK59`~c-c0+f&Y z!+cAQ!@65z6o2F+)9^~29e-0er2?`GVqCH3W` zewwk%G`8-8k&gmh>e9~t!ErfU2g(N?;=UCh5d;_g`1J@UA9UE$2OW6&xZrce{*QY6 zm&>vrU$q&ilYCh-ra?`gN@sM@yyPAJ?FZHh<(@*8W4Hio?pvL;G8F2wly;f`g*J(w zt6G$XH;G>!9FP&AuQ?iEwV0Pb?~8x4hFxMSjJ@SccF0z@256+Mlv3NTSaj<|`>zuk zmqufsopFQ@rSXNg;#=z=@kZZKB7Jr;|0C{ktvMMJ_fR_$_ptWek9#ElSKPzIB^n#| z2*t)dkYL<{5sZ68fB)mM|B)Vjx6%kt1<|k*wY2$9twZr9)kPw(QsHkn){kIn(oG_ zlPv=&eeM>iLAit(_V85Wq(O`D)2i1hZrq>uNO8K8Ddl&I???UpZG;-9Fr2a+ohiW} zWDmWO9!4htf0dp!zGF~{m-MoA3`r@;SEKeVbwxQbDczAaVkOi6Ve*uq+LNNILFL+rB)$#xnBcTWjq0M**GZA|Np-A-edP3?wC}<9wl_Aca+S6pA&KK7 zR0Q~Ts|q55(f<5Bc$R4Eh?(0&mMp7U-#Dc;TIo{Qru$?q3Z0xDmNDmRLzO?EIfL6; zBI^9-K4;_PL-DA5#Qc_zQ&{EYdYU?gloTfBPQiv#{xU(SG`_{>Uhf*q?uQz0aVKIKbu9(%8bYJpiV0*iV0-ED@d|_w*PysxYyDm;tDU| z0XyW96UPl|`kfMjLYEMY>~6PwH13jqC`#p7p_1cGM}gz4)^4nkgYF>9Cm%*>srGWUSYj7bUN3(RtusH?_fDpVzFiRsDuBij?PbMoTPD35VN$vb`($Y8rlgkhy+lO?I{TtVxU?%4kxnHf=TSgu0=t zH%b}(zDZdse{%axW6#cdVv&2(95KCMrIt(l_9lEKXEtCgI<``DPfWv$e)J;eRk01x z7Yko7^07@G>+(OHJ`UIYaas5^^dWfkn0P5Q9z6{2n6Z<0fRBH`NfRe~FNBdG9X>80 z1BOXchR@K!{b*R#bZ}5OF&czEtP0sS{e)+khkYg^b|QTy zH?qSFDF~ZfH!7$};E7#T)OFXN_NK(dzRH608WBXr1)s8lT>YkEUAOB(%^+`RpU%VS z*(qWo#$lQwcOndbS%2Bk*8^W*#4&xG%TuG<33`QV?O~hK;5AFO%Mq-3&`lD}_J~Si zDcJEVS+)A4`7v#wR-P}IDVC&Jis(j$t>@H2jMt5Pk5{PX7d!ZNWYRh>0!YkZ!x4@T ztA1HMrT1EjjY4FTP1k#Ztbr*;_{*zv;oPJ~lEPgnikZUi9qg>##fX)%q0la+f`_+_ zK9G!DPxF#`(=%Ou3Rp1R7Gyc|Y5rCU1naBl@MX-j-PKYplxrxK48a^W#1{?m44OYT zuh!VcTp(WNFzEd`n7Vp3Hsd=VyqGy3wc#5NQ6jtX1<4lAE#vX+wA0{P{D#)^RLfnq zk3M2o)GsE`To7Tu&>khH<({>s@&r<2c3sd8^i|#wg1!o|r?jn}(6M*Y6Bl#;xu)%3 zTqQ!KpIR0XI38qjcW&P5wls_JVj3lVy`%$>lS*VEb>il?jB!av~g!Lw`rl(dEKWjwqH=XK)u8%S1Qk=Je z@H`*FU(UXcB|#g-GLmV>EM{6SYd^@i!*8lkFwE^-_hq7tZUP#&k7IULw4rGB8&q#Ta>lL>O(yi60YX&B|GB5O8|+WT!BFW!!uomRox<^nq-&c! zu5Iz&GhZ_k6^*-U5Td1ROYpb5Oe%oFz0+Gc+^w(UyH3x@b#Av;^fy0>1EU-#q(_tQ zWJ^F+rmuXCn4N0ODqX19Gbw0d%h6cu+IxTHRZkha5Io{*ikxQ|UfTu8&6#-)on0b~ zWIX1-^`Zwnab*67!*Zw&k!vN4C=~^jLR$`k$ACFz2r$6#C=I=x0=?{EZjLy~IB-`k z{>xoKbm>FkVj?;oMhp4zB)SQ%7WYp2ouoCfcNA z>}m{E90RxP_!05Qx4lIbr^`AFipPh>WZIFMo05$R67%2J!j?WqULj7ncGdh`k68Fu zFF!f)@HQ%@;QZ5l7Xx`n?SA3)+_z%D^BpTD8Z>!@|;F zO(gu_^7#*1%0X)E1tfJ-L3vRW--13>YoA0^>M+7ZpVPMorc#Q?p`u|CPwnYQycmEw`giICjY*vxcpye*di>bbNs9Ya@i^##tat7qC<=c1 z;5?9rpa=5dPKpv`?u7uDfxEcM^+k=#mn-n@hM?dfb8?3w7(c)W2Eyp9l)B48D;{CP zDVmlx_IPe%Fo0n{7;s~QH#)|07qFZ^?Q-s76b+3CyK(#WadUT?HF!BhGM%`~?#l;k z0OK98khEnI7{CyFdGW-TYli-fwIWoFeCDCEopbR`y~8qGU;twj3}EoU?|d>}R_rhw ztG-X29^KiP-^5a~_NB$Tarb3TN?6F%!L@r)&)5YA6SoTWEQop=CT0`YqvVo}t`O@OKYcL3u|`GeH235f7C-qY{h zy@?Yn+5V|BFPjxfga>$tTsLtw(MbH>{ty52zVOl@*$5=xw!3IYe*WyB_V@KyB z$G^VL2z}~b zyL8J#jU%rtJAR7hq0@!XNYN_Wll;o})aE7L>1nT8R&8~|q2j$U)$%qPe$r1bKCN;y zt!S9IHS|i(Id0t>PNpByNm~xfZpsLbwo-$jN9)tKm&Pkj!2+{MA+k@)emXs z`yFb~@bGU-I*uG?wAiBfjCh91KFuR*rP{qzV~LAR4KODOf*+6f0HMc4-x(|8*(KjQ zkixw`usk3nzmodEFgL4oqQv@Ccet98ZB|YDwAV(pnTOQbx zM1%6ZO`8Oczz>f=U$nH&V0Xclkikdo=?(w6uYGbP00t4p-Sr~P5BKa8N7JdfE&qM|WeP|>qx$qKza!?3s z`?iyFP%Mj%zA7y61rHGW8t~PyHG8Gv+kJ0RuI``BnQJRsPcqS8KF5O5iuf71>!MBi zB3jqynIQP0bf=ihD$aZ*V;jwcLGhA5HTc4J<`rL~^In`Yj=6~R;&VJ#iV1egPqX}q zRvZL7Eq+HU{uS)xC!nDSk{-GRMk~DEHC*t$;#i3PlKgAzu2UVPJ~^S4$0g`iX8^@L z6W9Fbo36!)ngfUow;xNQp(1cyZ)dWGX=3u8XC~p2#|+=;za_KD6fxyAuy#MqVv`7k z>YXTfn0~=*DJLt_`~4e(nerFP3u$}2C(eAz{S)lW(Nzvo3K!`9YE#%T#w0*-`*tiO zcr$78vCE1gon;f|@O%r{}cf-jn3KB*v$k&eGm)-iE7kXIjt_L!aN(d#%F2 z{cdrsFEm@u@fl6(E#HlJ^~oOD3y7xqHPYUTY(nrrt!Q-?$@3OH7M?ZC+0$iSV6ame z8|>5vgPj%2?`{v9g*2mGG_yR3*J7bmT3VXWhl+S)bwZtaT4>hG6onPl-3p@O(C97zB~< zP-EkcTWLR2;$kkCix+}<)??-fc0&eXVMQV384t&x`1xg-zEoaZ!egP_0KR<@oA9W^Bs`FYKG=lE z#EB_f!b5+EoBV9p_qYL->>BVWFwGH9W8DAbu%9)BrEcV(D2#Ap zq{d}Uc?OCJ-23O7*IxIufC&$UNE1Y;5v#~voa`<$#Fk;c`B}d=_o3Sr2X)xJ zjrrn}TmZlLOqhMm%>=`PYS;%jVfXaGx8C^gZ#^4gM4JTec5d#2VM*s$L7?D=^PLQe zuzVil5hDw{(PnS68!#A+`z%JSl#+z5UIza}H9Iaji5a?6%r z!SL6ExPmV-nH|mpu=m7E-=1vjZiY9{M@$ZD86G2O^2T1Lh(2_kA^~@sLfHlaziO;v z+q~k!Hfd?-P{gnPuuD2s?36KbNZ5mXIS^*#8$hl*;#4%Q;i>-89y~lxMRhs#M(L(s z4jgx6T;@o)8s<$bFn!v4KsfOj1lkiMJ&4)o6$6-k9#N0~G7#M7Qgsyd$(`7J{$@@7 zrfz;L#rJzTA`CVL4$knZwwAGazWPkz=>m31MY6XuZ|$!ZcxTy8cfv|&LdM*qho&vh zXt*A){?t$YKWObXTKd)A?NAK0UpobuEJwvybA=4~EtN>cU8y*5tOhG4t5KEE)tQ0G zYCysrJPCg#!^86Z=YHzw(`(o^sIbxK+H|CrfMH2%zy8Wa0x+&H992-X#XAFrC56ZC z5_vt0HNE`Cq}^2{P&Pxk%OvbOlG4ezm5vbY_!)1n#;>wuJyY;#L@iri>mQm_`(@Mn&pzQ}y1_kG@MPtAcCJ2mEY*ok#xpkrhdxh7 zqU%c3yvYR=Wy{{xbi}e!KXTl|zsRgVP-)Hh#Y5Mc)Vqk3Tlu*#uY3V<(fI`vQQG6+ zl?rp-F{TqS<+rmVQ{awp_cNMIWXb+2HRX>h=T1gl2*_TkFP~f~fkKxX=b!lQG3u`b z4;u)W8Bz9}+?a+ZqZt@rIF1Q05aJz&;ZY%c{Fo>S{^cHEBm)Z9pZjpB3RH}_+8X)W!oj|+ zcTNr8VXBcHX7`-_e=0^W&3#6-P`==I8dZNQhVp#G)NmZA7_1Wi3Pird#vv7vBEVC4 z2fOH}O+m^4VLq|9bQ~X?SUQ;yC0#$#9}{iLH}KWf6Y=~MH(unB&L4Y}p^zW&c|zP( zjYqFqezA&dlS=cc5*nW##)ST!u1Rz)#zlbzmj=b;F&djku8lQ-X;5bCIBTs5TpBd& z#M#1L^@A)ZDj5FpFU7cfTGdEKUliulU~)sz?{TXqO!ySHP*8lerLINy6?f;&k|4iM zZ@JYQmD^utPR{OL^&D44;0c7PseAKa6hkMAOW}UUB9j2+?c0SA*>&fo$;41LQsG`= z#j|BjMne${mz?iKxyQU2iv~zx`f%<@y9mxZugh;l$JqDqOy)vq+&8vdpFICZ%pR(% zT^Fv)vE<@eqVs9*CU`Pn7K#Y8!=4OG=N}Kc@<{iY=a#&h4W^8_GyK@}+Ms6rh_RJ- z*Z9QeiUi4__yo~2@#Y;t=5G@(Ydmu*pibWrXw0u>aa*-(kq!I46vw`@2*=*Qid8Xt z#2AFo)5Llaf+>={A0LS_Cmwha4u8Ce7lWjuC-zp=Cq8~cOPU@Fnb{52<_qL8E&|h_ zzAtasPC(B|4WkJ)_16sg%Paz|UewdG=<_QgDf*rXh`R{*OCmQ-w@w9vIdlTO1aLW75HwXeb1|K}fuV;lXP9ytw>fh2{ zp~;hf4hAHBcDoJ&l8rY)L;$N(#mFG>?}T?|F*mWax??bfdf!2i>Jjcbv_-}2PlZH!rgfzyD$oUW1I9!L5 z4a83^1=Lq7e}I4)hR2D7_W+!z5F0KCz>LVT^Frq015m{ADjF2eB}ROJ1=(r|aD!F$ z4^$Baj$CA{Fg!*LA8%(je=l@?I3Nxo#be#qA_$WDBEXIibXJBk@BaxOL0u({df-e3 z0Z!mZtQZmzSFkS(%&l;^aS*V<@K_MP0$5P?Zd?#3+d;?Mg(8SX4^ilEIzD2_LxKRj z)XomJW<{O#oCP4@zq@?70XC3Hhw$(|4TvC)c`_4#Ayz(YqYvIoV55^q8VMjo{D^Q3 zf#WDcKL!W{kKUiLE{Kpw1UC%NAPztil^moD0W=Za z!A?hpFN5+A0f&DUH+)U(@bwOc4{)K#!;K*T7s5W$395N8dd##D!Z{!SH?AL8|3H<@ z4BHQ5n;yb1h?PJKGf=8<4%EkM{NRhw+V4(;c@ZYaM=<~iDc(_LNel}(7Hu3<3k$XX z3(5n-lS5c0$l+cH&pwkFal6n2amg79{rwc&yR}FV0)eM+Kprg6^8jd%u@cZhF(jP@ z8|ffyQ!f9p64xJciw>;P#r4<0_P=tpKY<+#PZarv7$S-qx&tm#JH$@vzcBe}V9g~Q z#STk}I!XcTQ6?FR5WpUBJL~8Rl152olR^LoTxWYsXQCu*XUzHpcu_q$3J`!7L6+wP zwLkKLD)XQafDhM?7yHnRgNG(zUyvRQj~O9S#Ef#f2im8HxKa2wL%4_n0-VD!^stP3 zM;U+*s_TIs1nRT@XmZSp!0yr`%S;;8Sauu&_~J-D*ilsc8ATZCa)lDOM8ftd6xbz# z*n0&1LqSnjse$hz9ES+QBI*wi1pF{O9^`@;ga~Al9I*1A~5HXIvPy z^iBl=72Y?39y?o@oh-Qj7kpIyfCdB*!9j!(w`Wz!hz=lISVN3pcnrv^4iE+u<$Lh$ z8zHJjoc;i?({1_*+(s905F;#v`X~etMa_Sa2B%qc|G|t0rWpM;1xxB0BLHXlG!7+- zMX~-yL8!5VGDcmR(1QTRh=)`EnvrQ12q=Hp50^2vGw<)t0B%OaP#d#y5UAMxLysSr zVf@<+RQ{Y21Q5qT#IO+Y-w+5d)&f(M_b+J()O5cK^I_3F;2=1fWKgTB#pgyh0f)gXL4`Vs7=r2vz$;Dwk*I#PKffI zzh}m1mlv#Y#&vhXb~pareSc~K&`70(M-l-B5I_p~h6Hdrz+u-c+8+v>fB@3C{!-ZfZom6OY_Y>IN3$QpJj5JnL-DWv z03`r^QZM5An`8UKj`k;zf#LBZZ_@$%=yqy=6CAY+@&_#lGXMRCF$)cSiwtt~x z87v8YK!RZ{aX~9F0xDoD7i8q|f0@F`43L9$t~i1VmXL5jz>P%?{gf5Z1zX7>kFf)w zXu2FR;t@N5QB8RqLJo^aJ3u60j35Yv;RzxQc>qCl947$ozd&Re_rGj0-~renKoE`- zh~?xRaB!wXpm%uzb#SFc?3=)iU1^x_!0$JXdqtB102OFdBn}pVg_RwJ5k$lA6p#QP zc%9#+1Hz4dDU6-yXylCOzf4O4&t@Pl28WBr;%blL05|kG2|yD9b=x=X6lUq2LOzuO zR1V}4EiDbuLI8Ih#|_jwx^zQ8h^Q6ILZW4XWWWu1Ru1q!W`H>-{Q`eMT7wY_VJrj+ zF!0OtXaFLBhRS2+Pys2d2panPakz2!DFSQ|KoQ4R_{}HlJ+QSEyC^Ksa23D_0=3w; zl@FZ8Q%DtkP(*RZuA|f&>VO0Uu*6X<4yeONsRSP2*9a-54ID?iYGN$(K;G0kVqyM( zELvO}v-mx6ERO@$LO+Xh47~NNW8f49vQJtPfB_fM6Ws$^m2HF9sZKW(Y_@ z066mIg~JSFUkCsiVhjKf;3^IRKY(l>g%E^*M|NZd=oz5F4>aNuW%pvx~} z-e@Qe69QsXNbrfV4D2TmP(q)x0@NUY5;EBaG$Q24-VwJ2C?S9{uCo%R^D%sy-v;0l zW8c*^bl)XF5(2%p-%|vu)Zm>iB=$n+@hTB?sUrY^08u#1HJlA64%wiBmURZKAyAcl z1o%zbKMnte;#QBG8z2AyRB;>?oH=|4=A6K8Wh=~9W@o?`+&^iNAPWFgz}#*a>_tbh9700ZH+^_JQDM3p5gQEIMlBbr^tgdc1K|FC10* zC>4lB3ts^zjTA3-pC*p`zO{q^xWfy2Cz*YA`$|q^KZ16ZXIe zRoe%FpRN75`;EqoRTdov2QbGqbsSF(XBHT?2OULl6XWY(CXd(#WP-7ABk9Y)HaH(~%%5NV4;+Tf6ON0HDt@EWk8zmKyU>e>q~uyq`=^~T}#>68F$fL>%g z4ibkOr~99A5^I3}(ON}C1?Mkm?3*KfFv__7zu?|zeG0G&($3&$8aR^z4@`1LbKL>F zNr>f4#H8gVrO+8!fTt7zSdv5n6M#0_uotKxIwqjt{{ONc~k`@r1DR_4!V>9QhT^uegbmnc{=Du zZpglAn4##P8w4ROhugmrhdex7Zjn3MzF8IW_;CGGElAy;vKIRJc}U9Pa*iqF#^JJ` zH6;FU`HUkZ`f!=X9g=;xyzYCn{?!#o)ZuzWBqa85*)bOK;BeXO=F$G#2uSAPdXlW8 z_KoBneg3mzNb=$K*%c7v;c~=dNbcdXOViQut38L5AFh|{gxo${rtgFB{aGJc==j0I z?^6pM_2sC2W|NSl!~I3(An?QG^`)cF@85*fAFlrZ;FlgQcah-t9xhK(;Dk~!+JJ~5@OOBCnQWR0j!7;K&M&`lEh^WXYBP%kp zqwJCWd!5np{(K+5^G6Toe%<%;T=#X~_w)6*nnV34%~VAcR5s+607*5K>Z72qJ_Y(V)r-uehWg#i~Xt66NL^sVaS$lf%4B zX&{ZTsf6=M8U5319^9-HDh{ii=4^u!KXg5&V^cV8QyON45zwo!(LWh0ITIrD#761m zjc~tb+!rG+8(CepFvawWZX^^~non|hSpRM+~3V#Q#s(aysMMsO3EA0&a(qQORZ|Ul=h+e%SGzdqUU~sp7rn% zuPS;6fBh%JX$xiwpU=?}TlVREqOgi(izP{^)xR$7E6m5#j(%F^tYX&doA0oa@9(4H zF1GTt_`70@(%gOs%udM&5|U!{L~Q(l6GhIaLSYe;zK78FqQU2EYr=jDpGRV_K znwyO9^R^v7VnkGkJ0lIJ669GNg$Lx1@|MzRse8iGP9K%kwv5xL?IyLSAG0bIc+I+B zQMK|e)w=)vu^z#nndxbc{OTEfY4M!S2G5GSWpoCA43mad?let1nSKt5mx`Al-jQZ$ z=ZWfvf1=kwo=TSSkQ=r+TUFohp0c$U^?CDR*173rXwMCfH)hSBUsr`xNAXmP%}WV< zE)j-S91VocAjb<6Ca!r;){3j0t`6|pSY>rROOe1gazd#8vI0Z1sY>>qMavpAbpM<8 z?%B|@g!aXgk3^%ml?b%fHh7TF2{OyJJ2PPrc{3ga?Id+c(*mz&YtL43d)JRrxb=a69_d^qd zjk4tryVb-AO#BgJgzMe+pE!pFzxt^N)$Sw@T}4j%vQ)%AOWGiCmEWV{t$jwL_hmHP zvPl;n5Epm7xzKZo-!-y*(n~UbfPld%Yj0OtaCVJWSg$){k$f%4+W)}I6r$S%k+`q}`L5%QZO zRR26foR$>k%F0y_gAi-k-?VqT^_y)>4zmqABH54> z4!f%Jqc5$z^=el&QvringDSImx19yOYT<_s5>|!U_5f7i&{ppm4{?T2m1WK_KS!pu zk~4HP8=^~TYlgd?f@hhsn3WY66)GQOjGjqoap;Hjl87d3POt7QZv~9*d_-@iA0_qc zU1NL0JR*lY@y3@jIGCB7;gP**#u5?D5?rU62+|x>ePhp-W7=H*YL0-)i#F*PF}~ck z?{O96+bq@mFRRFT7sQQLxTMp^OXB6zz51ra9hG@0%EVVnDYj|VUY|u1Fu{(Z=i}7w zC^ddc_LjDjN~&_HC}R9#WihAZsq2>xKUSnvlzH_=O9Fe={4jNc`9FAp$)93x?l!k2O0-Zz{fmN**DJuo?c8)=B)xsINv?+WfKZTCYoMsoXh|^hqB# zHBKJ`k@cR^p^(~vp;AFz>5NGVx^dpd1=6#6M+L1f$&8nos+}>0op`iA&-}a?9&`ZD zwC3R4*vn1(+5B)LIJq@jfxvP2=6bdUi=*2B!=s1+N+Kf#|1^yWyV$tc(jw;&Zr-1l zM<0#}A2bEGPT4$m7Cctgy|?^Ce@-hW?UJ$9S!Ykf;+2pb^B~KUPvO>ISz-Dk)(;K6gHygT32K?c?D6+Aa-4oaSV;0Bz8EkeN0mVmsTUOk8bJ~{&D97Jh^nottM?zd zVtAN9IN%W$CX9-=E^LDw(v$YH;YybrQ-P@XT?!H*Cvo9$WfzIPD|aK3HZSJ*ZrAyb z2DU_~q`#y+Z&^-W9iJRVvBUhbAY!~Z|D|AzJE5n+jl77AnYu5?mdJ0p%p=WJivfE< zGbyWeRV42;Uq|?inWd9TSFu^7Unh&adwIaIaGM^k#_t#MeEkT7ZRhK5h2m;S=UsY~ z8{IXl%JfLGyzO>3+4(E(X;F8F=Jm(Vp?Hs9Wf0`he#w0MdVomg5#usWmkHXME7S(( z6_(`nm7_e-3o==>7yQamE%#qci^DVfV(;#-o?_>B596d3ZLojuZA>X5>vckSar0=l z3M+e`2He!+$ez;GoMnAKt|fz;@8b6yQ5b%3-!KfL&)knJeF(k)TV_fp-+TW172o?1<{Ts0Fs zLKhuM;J30q-TV9lNFZ0J{qA0DO9u(0QAcA-k`$i)hJ=7|c1V-0eoaX-!Hj`Kpe0tea%cx+_^`FF$b209ks6zG7cqb&m$>Hu|C>PS8{*QUDyY-dkvAncfs zv1G;0?^Fl^(maTk9Q^k5r@Hk#j)Dv#{-z}w!yv}V1{ZEiMweSp3Fz>$58Tgqd+*bd zR{p}$%c!~8rjede-xf#Z`jO5K1$(O>+1YTEF@;5SXz{4r&b&v&VrqN0kReUaH@PDJ z;37V&0=u1;mAc`@6b()694)bv6FX>aU*?NCAHp~0*xpUH7QPd8FDq1-H1E-ZT{T*_ zBrnxb@Ay6~5ymrhU267LViV~%Yhj;yId$qp{Yy4_=`swq++(vZ1WfPUd~Z1~Bt|d9E}*q7h$s8f8#58k&!nrKT!U`39zb$lB}|95jo(k_({AWc zhw7$$t)yF@xh5g3VAKYCESf*G`stgDKO9b`OILSurIV58k2|LPlgt(`^@ZhlE$ zv(y@`q7G7+*66Lxyi~ZD@gv{eZ2GD7_iWKN`^9xqip4=CYwKHz(aW_@Fsvcu7Tu&If+1Kzz z={OO(KmBc>gZ8<%e8viKH$*o+{OXqDNl@3uXe&pm2+b6VYeWJXLY^oZPxs3L2j?cldM@QH>c zm9fl8pUNu_gL+8Wdd5{fLo(cll)r1gGCoE$e)V3}th|oqW3YYhH9|NJ%N1v6A%#bhJ_OQ*^H8p zeOTIVXw5!uea2Ya&i+1vE4?RHCr>Ujdc%>&=~b&yyw+enm0CDS&evGu;jg6wIdd9D zzN37ZM0;D0f|&WgIg1uP%XTlx_E=hn4_$}vxh)Sfn8m48L%kE59i<(morg&g28cnmfXIzcC5#W6Bh3O1GpQ*cOkPpIb3X~j4=SieZIYUgzB{U^#s<5q&?m8sb!^d_?ozy zeC2xA=JBIXJXBN2`*LD<*Xic8qt~rsN@^z*9QK`8_X-7)%Bzl^tiQcJ;iS!V{=q(x zfwNY%Y4w@7O3L?zs-YSujnBB<AdV&VlG#y-9*#~nLtE;WIwQW)ku2ByOzJ3}5aoP6JjIIYTj6v|`fkivQ z^AZCUn8r}WegpX<4q+fEie(2zWHApYE&^o5fcIE&@g5VMczeN|-2ZQ$;DJZWg~YhC z2LJ4QNZxv+YdlSD{I!;`Q&}NBH~)tXRaVdV0nu-+d%eptK1%t`7TZ_-2G-_}4(M-&>MTPZ(}8Img?)_+6PQqA0~5Mm zfk_iSFp=H>0~6A}0~7M#5Q1bX>8oI1^2Xehe1Y0jPM?ijbnUmSK;1JQnLmvq37_iA zu(RRtgr;lzL>+1^A6%80_tL(a#XpRC06eUA@=rhYDO+nYJc7z0(?yKSHcvIC*Ra*O z&Pd`@;b7IVSFr;p>sAi3?&a<FkyZJuh%G8l$<}5nm66N)-Q%Kb3{6{O6TBP`DLC_(Ud+8X`f;^GV6~s<-pK^j76PPrX+28_`pqVzK{<@c&o^Vkb z+MOVfB(8xZ;f^7Ra*!m(1F6cTzej+w;yy-JbY@;Cd)U?*Vk)Kb6dI5V%8L8&y(?4Q zq8?7zz@!Bmm>3u4#`yP)XJKW&5`s4n7^Z0{9feG)>bHkw!OknZ_3{1imU%_i&zF*(=Jaa7mXb@Ta#e8>;c&J^q zNX<0-qH9KqHfLPR>{fDsKY3TDW9N=!1zqANdr($9y7|_1 zLI|ZAfUXbDQTPfEzf`T$_1Gh5WA!A<1BsUL0fVQHOT90J`> zRh>Jb{~%~_52R3CBdL)Mjr_(cWd{%y3sQSiEk>&Z}4!vj^Ae+|7n9E ziR4Gr6aSa2`1=_?E&;`bmE;LLecUkw>BIb-7|@1Q8Ev4-F!b=X{r|H^xKe>?v!Q|A(eCHpllBb7g$XmF+brxBbcCiw8iLUAn9?x0I z922(;w$ykx7A1d-kY5zYpdDVm9xF zd@ zoc5nL!}c^9W32Z%({+V!`padFba8LMm?XnO zZo4BjwQB$KD>%*VTX%R!$L}_#@(*ZioIO8*&-!&}Pq=(a0B%!`(HK>GErn5(@6lX(L@q|H z(c7f!ye-=zT9-$>IV9i}Tl;;nx`URHXX@cUK4CP5#sN-a)QpO_u|65PLr$Yt;66C1 z@|bMk)B!>KhC?EAao8ed?`3l9KDylHNT0}A|8vq__pUo9R4%Kn`vncMO`N=P9n=^` z2%h+3o^*U4)Dw;mn+!5<&?=`_wQYYfxf!UQM!fg2MsM$DM7neB{K;7U;^yfmyDRYZ z!mV5D_p*yccf}sKF^&0Y-I3Zm!_zW~VGp13F5W*91N-e?iGdJRl?7`y%DP9u{3i8ri#hdE7)i(PSaO)mPZz9Z^H)8G}6O*;KU9=aqoM%gUd*$*IkGs9&6q1ck049$lcwPLT|T62ZXrgRtbG|pG;YQNhz=2 zcJDBbkeHD^|bpU6Bj>Kws6KQX?jFFy8Om=#NkcM-P=h8XZbzI70E^3F!8Tp zQx=V`fv@dj$lcc91CV4_)b}lVuLzQR5p@ruEw=895Zkt)#VpP-hVm!IO=+aGS2~x9 zid4kkOZgBIGR1MB?>&zTq5GRwYpIVN1BHCRH3PmsWci;<13B<;16qFR{wbR;U1>c;VA7Ms zRoYrxh_VMvdY+Rdc{+LVniXdvzv&K04)F?~M$v+9AUSA*m4yh_w zou0vJwgyjCln>ndRC9bS0&;=&E$=%;gK{KD4q#oS-FffxZ5Br>nWK6S2Obj|DQu@T zBfz9*MR<|(dF~T_Hm+(*4mYml`D{Jtmx`sl^{v59GdDh3z(~HxF3dc-uRtz4?-Ge` zZthgNlV3&tqRNYe+0Z&tWZ%;&S?8}e=tb?KqOQa1@S%5L3{iu^E*A#Y{%l`{eSx7bT(q&8%48 zQW>E?+ZmeSm6XVAm|=3$-@iCNh#$;+s)3o$geN~L%C@FFp^mC1ug*RicMS`_a}8Bw zST1~!mZ@G|0ir*()(|g{qHF}V&;$#akwbtd&-a=lr zO#!vuQ;3bDKOQYw@7WESg>^lJ+s0H!|1eoinsTEeXVsthD$r{@WcglcAKPk)bW{O%P^C~-pMP1X$m z=7}1uom~ztFmEw%N8Wk(nVEiS&`$D6_^ui#FxCsT80WfVx$D<#N+klVZ^5?;DB$e| z7DO^}B0)x=#yW4xa z*}|Nzd2t%s|5b~%IGo(XR7c3*VmS+y%3X2q)aEa_Cch`=uq?@HspKKHO8P+~?dO-U z<*-w^THrBa$44GIu1ve!DL8BE!OijAX6%Eu!EM15!f$G{G(=JU{M&(u1XrIV_w@$t z>7D7F`PHquMc=wtB=3}7x8=+nty`*m$#$&n8YyM1e!Sz0YWh%7zdP4IozQ{EY)!ml zh0oa5fq9BV?zfmc#Wh3kgyAx2rP1@uL}$*Vy|r@C!sIE~j(DAfeXO!1#^xzB&iN=G z&mes{h8b7Nq@K@DaleEcy1M?%xYJ})NT_^yPH zH}pb&#T%JmywUdGc*C@U!pQ$rsb0X;!3Ac6&69SgND>M4M=0oTy0pA`aa^#Y^emXi zJciF>ZakWXub=SeAQ$c=QBW_iH<$}NawP|>goI!!A(%qQV?6~%U5CE6%8g|+^gDXQ zWUqDu%SRkO#+MTc@C2O8>D-bi>g8D^0Sh4*!7$v5D};~;Nt-{y7eeZ=g%B&S5K^?( zQ!!+4s}Ah}>b~3$PyIE?WAl9^#bpWMTa(d~+UwQ4Z@q zRPU<5f=Dx15J`;36hsm*1rcG%DJQTVa_ymy^UkN#bJ%)FY!O%wN#vJI$JRq`^?$(C zLrkSx;=4w+zpi)iy$Pjd4pj;zbUmdptldrEow(4T)Ar)Xfr1`P9Vs$~e=npP@ zJ!En_Mk#Z9c$4~IHz@}c4qi;Biklst?6$(!lUG&;A83zS?z#|z$%*9<$hDQ&un9Oe zIk9pE;nNFhg*0qEg!$ic>hFixTm==Tnu;LN1sQ`K!x&TmI}Adl>*4JVW-6{Do@yS$ z>j)0SOI=DhbU9s#kXKFkg~1aU+VIwjPpwU%hR=^bd}mv`^Xy`^O>**jk@BC+#LZvw zf&X9m;P7f*CNHT`^V(ukpiTW~(&olc4Val=6Fb|y6pC({dcVZ3dSxuM@%**kSpI+p z0tEyI@VeB38D4ad{9x-yOuW-W+W9gn`^XEu_C9UqB!L80j9)At(EBC`Y|$p^dtPo& z?v$Osd8`~0TCi>n5Cw*DkEIL+lgmX8dAD(gDm6BIJ6s!LSQ#_A; zV5VrC@}{O0@lM=Wh?|FkWT}VsgGi!+Eaba2x`@kP&OIg$m3Dkq zmVz2^l#=c;Ee7+KKRH68hkjK&`oW6F7B+uL$7>TtEBbUHC4c6^gNI0_$0cm~T~tiN znwbaN@4n5aj_&0mNx}jZ;b6t1^zN&drd2W5A%zx2bvN_~3aNH7J?WCtuaIldtT{e= zF2jYn<(apdet2PtqD5$k zyFw*-!xKvs2qS#~xPeC3JKY{HvTpHXXVFQQMD<@r67Qv^JE>InKO8QOdU}s9rg!KQ z|3~7Z6f$ydt-ZBu;zKgc-)Tp`d1MIa`DMg%7?n?0r0)MLCEk*r%)8;HW!9~bzQtoU zs!rA>zhFOIEIl-uSa#ulr zusg4|74Kw^V5PyU9Ace@4}1hvX<$jC;@gB#vVuvQ-80eLROXYa;{NntMPpm`@m?Mi zldS?m*?{pfOV486=2}O+Q+|{Dxl5%h{(i(Q#k;1X0*b|fFV{YV);nNJ7^J89M#E(G znK4qJePAG}8$9zV|Dy)_`xzqG;yhy606grRn8Qv6aKa#DsuB~~cW?U)@nQ&M{4#Qf zeltNBbKaLPH@+H>pZ7XeflVbm0A+L-_jvGVFu8wZjdwFwsAl7vWMS}%1ad10b46m* z@`sYA5uvBw?<*3lPFrcs{7i$hCFiQ){-{|J@W2y%efL_U<%jDV#=TrRncR0I6QpXx zmG=ga{srRXZ?lQLx_slGlzic8bxtfZpY3(s%kv3R_mCnBFW$BBr}M5?t-N_f6EgLR zldE6TW_mbRL1QxpydjZy`8;?-Vr;oTu|Gv}Ro^jKc8p9;_<`Xfid2Gq>Jw)OoccJW zewxP#&)xCBE;|QZwk#o%bEOCRG2LyWYBRdix|-TgRa$2V$gN~%F6*xDKL<0BtyiWZ zo-y%L;JvE2gq&z)% z!%-4UB^<$|5@Zv33SPd@@iRGfgk>x+@YJ0%Z&dKd+a`jE*Mw&FKpww_vL2L{p5F(%f$=&<7LzfQ=~?NPt_Hmd$;ep5}5arlZqjJid1&vY~~ z)pW#Wf3ESk*T5y)QPY0^eyHps4K&JEXVQxtz{tt%u`x(MmQV4TB0?~U3B#ILt zQDE*@v0(mGHf5v!Z`1%Ej$j4vL?rNB)G?}Y)8D<~SvpiWAor9h;6_6<-%#|?a_J!& z>7O?wyvkk;ZOtXiCxok{|Ktq&35E`D>cW3sIe?A^2so!c)VsT;_T(I6K+l7WnTrl( zpWUi^DONofo`Dt4FJ99%JAaB7h_^tvj9kFwpyvDr7_3+MZcM`$b)9(Gk5sXl>2XaZS@13Q+ z&NzeF$C^?w`*?pz<$&BW6qkLJ*PPnJ-Kv7)u2mu8ofN*=tXRf;X_tm|(Xgb1=Dif9 z)bG&+Z*C^In{ew{x}RpXiXbD?X3uyLXLxBfIWZw9-zvV_B0Lbh6roog2HvW&_;stw zLf6^>!x_G~TUA)js5+C>Z_|n4jB{gR=E4ymXFx#CfE%3%IfCH~7jDmh-KT#^1VKLiEY+OA)Ms)lbzA;6K_V&eXj;@cdJ~xNVEQ6k(3GJiDs| zN(7RO|1A-|ff8YA)H2tyYA-G)n(Nf?aZ$4DB0H_#hgx|W!WZXEQeGQ(2fOiWDaeo& zXU$PZc@;<}8v2PybvLqzpIm)r5t&)F24BD7TpzVkyb;aKlArLXJi%i`_MrLI7LDF@ zGUhziIaqR5fwHNQH_7L7h(SKyT#5#Dc8>S4KR=Pw$1QwJPK-AK@+BB2TzseNtKU_w zJaI8F>iUdP5(DDLUcF2*6MYhs+LbnA#=@U<`_;hSG@-1cido>RDuxBFit)+01gv75fNtQc7}};_6(doM=nN~hQRZBrv?eOL!D9dYc{;;$bV8Xd?Z8 z@CNo7A?6`2nn0CW+y@AgVGwTQ!AF1_Wo2~&3M3JKte}esC|Mn*V=rBfOe*wYnL;}>KI=m>MP{8M(ey6B@V7vXAqK*$2kb!ddIR^zY z5QTpBhkRpjixLWCVttc=_05G}12MkIg+YXo2XlZhiZuXSlw5>jkUd$(KhB*9;(`Ks zSb$s%0F@CQ0As}kgn}QVG=vp=VgdqzKV*zWvO0_e z1Uugj)YQXal78_8F3XC>^l2zl8$Ur1xglH`3UuY=hSTiV4gY{1} z)}-72f$2c;$16a|I`A;$_^m7lLXjO>F0v$YDA0*T>%gFqhT_pMTN&s^r6pEMkweRHo{&Qgaxsm&4QB31ec%}VU~RuWG|Z<3iM$? zda)o0he3cfR6?#clxz(@7b=WPu-6(6O(eYF7!+8?Lal*NszkpIBqtOQM4c`=3%Xwr z;ZbsUzcQ3kfKN_=13?=Fu`Ve5;{r}1z#0bOMS6-sdC{@~05{mz8nIOYZWTeiML2gP zR~`i)+2CNTv2#KHHJ3u%R11ojlmj z4zck1KM>IkWZ)xv9E2SfqU|sQ5g-GD97E7FA493X#mq+r;oS5eB)ihN}ba1-8# zLAa0?jiFp9oeuD<-$%H2*^`C*<4*c+CMfU#JM=zgD8-!L4HyK1S<_L>-U7a$DtZ*5 zWMA;3m=Hjv71{lNFcSKypujK|;|q2I>wisP4!c7~VUQznSD{Bxx`W{Ak0Lz3*dGF6 z8V1kD7#3s{3vzG>1SEiysKQZckN{5Nf1Vj@%;-PHgpU(~{^SRNz#lq^3_b!VQIY)x zb%32!7>aEQ-0Wfap+|omT; zu81EbP+$T(cpPU8#~)*$3>ag`r(h6T{MfMfa?DNoJF4|2K1o7EbvJ>@8Ndz&zF`q&aR~Z< z5TNSV9k7S)pa9N7fjuM@^?wjbsR2-U>|+u3a0upy5Wtrs6AU7NRAdAM&^~ln!eT-u zGJsfne>)p}juBu3U33KJA|@P~-Cta9_=KIT@6a)Bhml(PwX@x0-{mj z&?s?eafi^@fQRS>aX<}9_7HC!AAZMDB6s8g5#$pofDS8hrO^XY00R_=z)t%il1>It zB2B}?Ax&j~$G}76v<%=*8G@x$@E>TJAHp`8m2eXV5k#KuhYF(eWC8FTZX!R(gXaGF zMch{QQ~)@jz)vjtCKjEl=r3c_u*(yLW>*C4p=42bWBEa2`EZ6}{?Ef7Nc6HY-~aEsVv1<*i&6)eayZpVRdS*&cr?1wQ75OoO*LVe!;s`qtHFAoP8 zsta`zN>+l05y9_1Ipo^a-y2Wi8USwOQY>l-&a}0EOhcjH+XI$RG87&~6l>ZqOq^*g zj({K(D93`JaHj45W!f5cj+N+O7tHam!~>lM&9VAz90^+49dq*0SddEa-K|JXKzaxS z@ckfTU=2ypZlU5g_J@BJ4pCFLiHI;?aeF~N3fKUF2ys9JI8)F6WvU!H^C{p?N+M${CM_o+jXs(PxJqHZ zNy&*xA`LpB=g}Pv@~w?;5_h_7}q2v zhHLt^0`&fB62~=>Z3Fd(niv4+2Yi#5EUwonQfMWe|gi61C)3fh2glETmZyg0PzP}5o2-vfX3Qc!}Te_`PVp3{I<aQlew`OX7drRUBZeT+rwD4Cid{b2m z^eMhcOdJhvS6BS@lfdo|H8k14?*+t7MGZ|b`rE^}Ev|;{pMpl>VZ^aks-cU&LxX-H zW8JBSzO@R@1?M|)BppDYjyBr #include #include +#include #endif //_COMMON_H diff --git a/server/fastcgi.c b/server/fastcgi.c new file mode 100644 index 0000000..1b758a4 --- /dev/null +++ b/server/fastcgi.c @@ -0,0 +1,269 @@ +/** + * @file fastcgi.c + * @purpose Runs the FCGI request loop to handle web interface requests. + * + * fcgi_stdio.h must be included before all else so the stdio function + * redirection works ok. + */ + +#include +#include +#include "fastcgi.h" +#include "common.h" +#include "sensor.h" +#include "log.h" +#include + +static void LoginHandler(void *data, char *params) { + static char loginkey[41] = {0}, ip[256]; + static time_t timestamp = 0; + const char *key, *value; + int force = 0, end = 0; + + while ((params = FCGI_KeyPair(params, &key, &value))) { + if (!strcmp(key, "force")) + force = !force; + else if (!strcmp(key, "end")) + end = !end; + } + + if (end) { + *loginkey = 0; + FCGI_BeginJSON(200, "login"); + FCGI_EndJSON(); + return; + } + + time_t now = time(NULL); + if (force || !*loginkey || (now - timestamp > 180)) { + SHA_CTX sha1ctx; + unsigned char sha1[20]; + int i = rand(); + + SHA1_Init(&sha1ctx); + SHA1_Update(&sha1ctx, &now, sizeof(now)); + SHA1_Update(&sha1ctx, &i, sizeof(i)); + SHA1_Final(sha1, &sha1ctx); + + timestamp = now; + for (i = 0; i < 20; i++) + sprintf(loginkey+i*2, "%02x", sha1[i]); + sprintf(ip, "%s", getenv("REMOTE_ADDR")); + FCGI_BeginJSON(200, "login"); + FCGI_BuildJSON("key", loginkey); + FCGI_EndJSON(); + } else { + char buf[128]; + strftime(buf, 128, "%H:%M:%S %d-%m-%Y",localtime(×tamp)); + FCGI_BeginJSON(401, "login"); + FCGI_BuildJSON("description", "Already logged in"); + FCGI_BuildJSON("user", ip); + FCGI_BuildJSON("time", buf); + FCGI_EndJSON(); + } +} + +/** + * Handle a request to the sensor module + * @param data - Data to pass to module (?) + * @param params - Parameters passed + */ +static void SensorHandler(void * data, char * params) +{ + static DataPoint buffer[SENSOR_QUERYBUFSIZ]; + StatusCodes status = STATUS_OK; + const char * key; const char * value; + + int sensor_id = SENSOR_NONE; + + do + { + params = FCGI_KeyPair(params, &key, &value); + Log(LOGDEBUG, "Got key=%s and value=%s", key, value); + if (strcmp(key, "id") == 0) + { + if (sensor_id != SENSOR_NONE) + { + Log(LOGERR, "Only one sensor id should be specified"); + status = STATUS_BADREQUEST; + break; + } + //TODO: Use human readable sensor identifier string for API? + sensor_id = atoi(value); + if (sensor_id == 0 && strcmp(value, "0") != 0) + { + Log(LOGERR, "Sensor id not an integer; %s", value); + status = STATUS_BADREQUEST; + break; + } + } + else + { + Log(LOGERR, "Unknown key \"%s\" (value = %s)", key, value); + status = STATUS_BADREQUEST; + break; + } + } + while (params != NULL && *params != '\0'); + + if (sensor_id == SENSOR_NONE) + { + Log(LOGERR, "No sensor id specified"); + status = STATUS_BADREQUEST; + } + if (sensor_id >= NUMSENSORS || sensor_id < 0) + { + Log(LOGERR, "Invalid sensor id %d", sensor_id); + status = STATUS_BADREQUEST; + } + + FCGI_BeginJSON(status, "sensor"); + + if (status != STATUS_BADREQUEST) + { + FCGI_BuildJSON(key, value); // should spit back sensor ID + //Log(LOGDEBUG, "Call Sensor_Query..."); + int amount_read = Sensor_Query(&(g_sensors[sensor_id]), buffer, SENSOR_QUERYBUFSIZ); + //Log(LOGDEBUG, "Read %d DataPoints", amount_read); + //Log(LOGDEBUG, "Produce JSON response"); + printf(",\r\n\t\"data\" : ["); + for (int i = 0; i < amount_read; ++i) + { + printf("[%f,%f]", buffer[i].time, buffer[i].value); + if (i+1 < amount_read) + printf(","); + } + printf("]"); + //Log(LOGDEBUG, "Done producing JSON response"); + } + FCGI_EndJSON(); + +} + +/** + * Extracts a key/value pair from a request string. + * Note that the input is modified by this function. + * @param in The string from which to extract the pair + * @param key A pointer to a variable to hold the key string + * @param value A pointer to a variable to hold the value string + * @return A pointer to the start of the next search location, or NULL if + * the EOL is reached. + */ +char *FCGI_KeyPair(char *in, const char **key, const char **value) +{ + char *ptr; + if (!in || !*in) { //Invalid input or string is EOL + return NULL; + } + + *key = in; + //Find either = or &, whichever comes first + if ((ptr = strpbrk(in, "=&"))) { + if (*ptr == '&') { //No value specified + *value = ptr; + *ptr++ = 0; + } else { + //Stopped at an '=' sign + *ptr++ = 0; + *value = ptr; + if ((ptr = strchr(ptr,'&'))) { + *ptr++ = 0; + } else { + ptr = ""; + } + } + } else { //No value specified and no other pair + ptr = ""; + *value = ptr; + } + return ptr; +} + +/** + * Begins a response to the client in JSON format. + * @param status_code The HTTP status code to be returned. + * @param module The name of the module that initiated the response. + */ +void FCGI_BeginJSON(StatusCodes status_code, const char *module) +{ + switch (status_code) { + case STATUS_OK: + break; + case STATUS_UNAUTHORIZED: + printf("Status: 401 Unauthorized\r\n"); + break; + default: + printf("Status: 400 Bad Request\r\n"); + } + printf("Content-type: application/json; charset=utf-8\r\n\r\n"); + printf("{\r\n"); + printf("\t\"module\" : \"%s\"", module); +} + +/** + * Adds a key/value pair to a JSON response. The response must have already + * been initiated by FCGI_BeginJSON. Note that characters are not escaped. + * @param key The key of the JSON entry + * ¶m value The value associated with the key. + */ +void FCGI_BuildJSON(const char *key, const char *value) +{ + printf(",\r\n\t\"%s\" : \"%s\"", key, value); +} + +/** + * Ends a JSON response that was initiated by FCGI_BeginJSON. + */ +void FCGI_EndJSON() +{ + printf("\r\n}\r\n"); +} + +/** + * Main FCGI request loop that receives/responds to client requests. + * @param data A data field to be passed to the selected module handler. + */ +void FCGI_RequestLoop (void * data) +{ + int count = 0; + while (FCGI_Accept() >= 0) { + ModuleHandler module_handler = NULL; + char module[BUFSIZ], params[BUFSIZ]; + + //strncpy doesn't zero-truncate properly + snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL")); + snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING")); + + //Remove trailing slashes (if present) from module query + size_t lastchar = strlen(module) - 1; + if (lastchar > 0 && module[lastchar] == '/') + module[lastchar] = 0; + + + if (!strcmp("sensors", module)) { + module_handler = SensorHandler; + } else if (!strcmp("login", module)) { + module_handler = LoginHandler; + } else if (!strcmp("actuators", module)) { + + } + + if (module_handler) { + module_handler(data, params); + } else { + char buf[BUFSIZ]; + + FCGI_BeginJSON(400, module); + FCGI_BuildJSON("description", "400 Invalid response"); + snprintf(buf, BUFSIZ, "%d", count); + FCGI_BuildJSON("request-number", buf); + FCGI_BuildJSON("params", params); + FCGI_BuildJSON("host", getenv("SERVER_HOSTNAME")); + FCGI_BuildJSON("user", getenv("REMOTE_USER")); + FCGI_BuildJSON("userip", getenv("REMOTE_ADDR")); + FCGI_EndJSON(); + } + + count++; + } +} diff --git a/server/fastcgi.h b/server/fastcgi.h new file mode 100644 index 0000000..c43927d --- /dev/null +++ b/server/fastcgi.h @@ -0,0 +1,26 @@ +/** + * @file fastcgi.h + * @purpose Headers for the fastcgi web interface + */ + +#ifndef _FASTCGI_H +#define _FASTCGI_H + +/**HTTP status codes that fcgi module handlers can return**/ +typedef enum StatusCodes { + STATUS_OK = 200, + STATUS_BADREQUEST = 400, + STATUS_UNAUTHORIZED = 401 +} StatusCodes; + +typedef void (*ModuleHandler) (void *data, char *params); + +extern char *FCGI_KeyPair(char *in, const char **key, const char **value); +extern void FCGI_BeginJSON(StatusCodes status_code, const char *module); +extern void FCGI_BuildJSON(const char *key, const char *value); +extern void FCGI_EndJSON(); +extern void FCGI_RequestLoop (void *data); + +#define SENSOR_QUERYBUFSIZ 10 + +#endif //_FASTCGI_H diff --git a/server/index.html b/server/index.html new file mode 100644 index 0000000..a3ff3ec --- /dev/null +++ b/server/index.html @@ -0,0 +1,82 @@ + + + + + + FastCGI API Test + + + + + + +

FastCGI API Test

+ The API is located at: http://mctx.us.to:8080/api/
+

Input

+ Place a query string here. Examples include:
+
    +
  • sensors?key=value&key2
  • +
  • doesntexist?f
  • +
+ Response times are inaccurate via JavaScript. Use the web console of + your browser to determine how long the query takes.
+ Hopefully this doesn't break! +
+
+ Query string:
+ +
+
+ +

Output

+
+
+ + diff --git a/server/main.c b/server/main.c index 5e2c6c8..596fb39 100644 --- a/server/main.c +++ b/server/main.c @@ -9,7 +9,7 @@ #include // for signal handling // --- Custom headers --- // -#include "query.h" +#include "fastcgi.h" #include "log.h" #include "options.h" #include "sensor.h" @@ -74,7 +74,7 @@ int main(int argc, char ** argv) } // run request thread in the main thread - Query_Main(NULL); //TODO: Replace with FastCGI code + FCGI_RequestLoop(NULL); return 0; } diff --git a/server/query.c b/server/query.c deleted file mode 100644 index 86696bb..0000000 --- a/server/query.c +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @file query.c - * @purpose Temporary file to run a test thread that will query a sensors thread - * Code will probably be combined with Jeremy's FastCGI API - */ - - - -#include "query.h" - -#include "sensor.h" -#include "log.h" - -static DataPoint buffer[QUERY_BUFSIZ]; - -/** - * Query sensor with id - * @param id - The index of the sensor in g_sensors - */ -void QuerySensor(int id) //TODO: This code will form the SensorHandler FastCGI function (I think?) -{ - Sensor * s = g_sensors+id; - - int amount_read = 0; - //CRITICAL SECTION (Don't access file while sensor thread is writing to it!) - pthread_mutex_lock(&(s->mutex)); - - fseek(s->file, -QUERY_BUFSIZ*sizeof(DataPoint), SEEK_END); - amount_read = fread(&buffer, sizeof(DataPoint), QUERY_BUFSIZ, s->file); - Log(LOGDEBUG, "Read %d data points", amount_read); - - pthread_mutex_unlock(&(s->mutex)); - //End critical section - - // So... we have a buffer - // I guess we'll want to JSON it or something? - // Just print it out for now - for (int i = 0; i < amount_read; ++i) - { - printf("%f\t%f\n", buffer[i].time, buffer[i].value); - } - - // Will want to handle case where there actually wasn't anything new to respond with - // (In case we have a sensor that is slower than the rate of jQuery requests) - if (amount_read == 0) - { - Log(LOGWARN, "No data points read from sensor%s file"); - printf("# No data\n"); - } -} - -/** - * Test function to simulate responding to HTTP requests - * @param args - IGNORED (void* required to pass function to pthread_create) - * @returns NULL (void* required to pass function to pthread_create) - */ -void * Query_Main(void * args) -{ - while (true) //TODO: Exit condition - { - - for (int i = 0; i < NUMSENSORS; ++i) - { - printf("# Sensor %d\n", i); - QuerySensor(i); - printf("\n"); - } - usleep(REQUEST_RATE); - } -} diff --git a/server/query.h b/server/query.h deleted file mode 100644 index 55a4991..0000000 --- a/server/query.h +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @file query.h - * @purpose Header for query stuff - */ - -#ifndef _QUERY_H -#define _QUERY_H - -#define REQUEST_RATE 2000000 // rate to make requests -#define QUERY_BUFSIZ 10 // size of query buffer - -extern void * Query_Main(void * args); - -#endif //QUERY_H diff --git a/server/run.sh b/server/run.sh new file mode 100755 index 0000000..756f9cf --- /dev/null +++ b/server/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Use this to quickly test run the server in valgrind +spawn-fcgi -p9005 -n /usr/bin/valgrind ./server diff --git a/server/sensor.c b/server/sensor.c index 83ffaaa..ab687af 100644 --- a/server/sensor.c +++ b/server/sensor.c @@ -64,9 +64,10 @@ void Sensor_Init(Sensor * s, int id) s->read_offset = 0; s->id = id; - #define FILENAMESIZE 4 + #define FILENAMESIZE BUFSIZ char filename[FILENAMESIZE]; - if (s->id >= pow(10, FILENAMESIZE)) + //if (s->id >= pow(10, FILENAMESIZE)) + if (false) { Fatal("Too many sensors! FILENAMESIZE is %d; increase it and recompile.", FILENAMESIZE); } @@ -116,7 +117,7 @@ void * Sensor_Main(void * arg) { Fatal("Wrote %d data points and expected to write %d to \"%s\" - %s", amount_written, SENSOR_DATABUFSIZ, strerror(errno)); } - Log(LOGDEBUG, "Wrote %d data points for sensor %d", amount_written, s->id); + //Log(LOGDEBUG, "Wrote %d data points for sensor %d", amount_written, s->id); pthread_mutex_unlock(&(s->mutex)); // End of critical section @@ -126,4 +127,24 @@ void * Sensor_Main(void * arg) return NULL; } +/** + * Fill buffer with most recent sensor data + * @param s - Sensor to use + * @param buffer - Buffer to fill + * @param bufsiz - Size of buffer to fill + * @returns The number of DataPoints actually read + */ +int Sensor_Query(Sensor * s, DataPoint * buffer, int bufsiz) +{ + int amount_read = 0; + //CRITICAL SECTION (Don't access file while sensor thread is writing to it!) + pthread_mutex_lock(&(s->mutex)); + + fseek(s->file, -bufsiz*sizeof(DataPoint), SEEK_END); + amount_read = fread(buffer, sizeof(DataPoint), bufsiz, s->file); + //Log(LOGDEBUG, "Read %d data points", amount_read); + pthread_mutex_unlock(&(s->mutex)); + return amount_read; +} + diff --git a/server/sensor.h b/server/sensor.h index ae17ce3..da23044 100644 --- a/server/sensor.h +++ b/server/sensor.h @@ -31,14 +31,14 @@ typedef struct typedef struct { /** ID number of the sensor **/ - enum {SENSOR_TEST0=0, SENSOR_TEST1=1} id; + enum {SENSOR_TEST0=0, SENSOR_TEST1=1, SENSOR_NONE} id; /** Buffer to store data from the sensor **/ DataPoint buffer[SENSOR_DATABUFSIZ]; /** Index of last point written in the data buffer **/ int write_index; /** Offset position in binary file for query thread to read from**/ int read_offset; - /** File to write data into when buffer is full **/ + /** Binary file to write data into when buffer is full **/ FILE * file; /** Thread running the sensor **/ pthread_t thread; @@ -54,6 +54,8 @@ extern Sensor g_sensors[]; extern void Sensor_Init(Sensor * s, int id); // Initialise sensor extern void * Sensor_Main(void * args); // main loop for sensor thread; pass a Sensor* cast to void* +extern int Sensor_Query(Sensor * s, DataPoint * buffer, int bufsiz); // fill buffer with sensor data + #endif //_SENSOR_H -- 2.20.1