diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6f80eed --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: ci +on: [push, pull_request] + +jobs: + check: + name: Check build + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo check --release + + test: + name: Run Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install nextest + uses: taiki-e/install-action@nextest + - run: cargo nextest run --all-features + - run: cargo test --doc --all-features + + cross-check: + name: Check Cross-Compilation + runs-on: ubuntu-latest + strategy: + matrix: + target: + - armv7-unknown-linux-gnueabihf + - thumbv7em-none-eabihf + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf" + - run: cargo check -p satrs --release --target=${{matrix.target}} --no-default-features + + fmt: + name: Check formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo fmt --all -- --check + + docs: + name: Check Documentation Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - run: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]' + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo clippy -- -D warnings diff --git a/.gitignore b/.gitignore index cf44893..6a5b49d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ target/ output.log /Cargo.lock +output.log output.log diff --git a/README.md b/README.md index 350c2c3..d8b7a85 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,17 @@ Each project has its own `CHANGELOG.md`. [`satrs`](https://egit.irs.uni-stuttgart.de/rust/satrs/src/branch/main/satrs) crate. +# Flight Heritage + +There is an active and continuous effort to get early flight heritage for the sat-rs library. +Currently this library has the following flight heritage: + +- Submission as an [OPS-SAT experiment](https://www.esa.int/Enabling_Support/Operations/OPS-SAT) + which has also + [flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/). + The application is strongly based on the sat-rs example application. You can find the repository + of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs). + # Coverage Coverage was generated using [`grcov`](https://github.com/mozilla/grcov). If you have not done so @@ -64,5 +75,5 @@ rustup component add llvm-tools-preview cargo install grcov --locked ``` -After that, you can simply run `coverage.py` to test the `satrs-core` crate with coverage. You can +After that, you can simply run `coverage.py` to test the `satrs` crate with coverage. You can optionally supply the `--open` flag to open the coverage report in your webbrowser. diff --git a/automation/Jenkinsfile b/automation/Jenkinsfile index 7aafe26..18cb443 100644 --- a/automation/Jenkinsfile +++ b/automation/Jenkinsfile @@ -33,6 +33,7 @@ pipeline { stage('Test') { steps { sh 'cargo nextest r --all-features' + sh 'cargo test --doc --all-features' } } stage('Check with all features') { diff --git a/images/satrs-example-goal/satrs-example-goal.graphml b/images/satrs-example-goal/satrs-example-goal.graphml index bfc62e3..10d3500 100644 --- a/images/satrs-example-goal/satrs-example-goal.graphml +++ b/images/satrs-example-goal/satrs-example-goal.graphml @@ -166,7 +166,7 @@ Subsystem - TM Funnel + TM Sink @@ -260,7 +260,7 @@ Mode Tree - satrs-satellite + satrs-minisim Simulator based on asynchronix @@ -272,7 +272,7 @@ Simulator based on asynchronix - satrs-tmtc + pytmtc Command-line interface based TMTC handling diff --git a/images/satrs-example-goal/satrs-example-goal.pdf b/images/satrs-example-goal/satrs-example-goal.pdf index b755b40..c874b8c 100644 --- a/images/satrs-example-goal/satrs-example-goal.pdf +++ b/images/satrs-example-goal/satrs-example-goal.pdf @@ -8,8 +8,8 @@ /Keywords () /Creator (yExport 1.5) /Producer (org.freehep.graphicsio.pdf.YPDFGraphics2D 1.5) - /CreationDate (D:20240216132134+01'00') - /ModDate (D:20240216132134+01'00') + /CreationDate (D:20240506155936+02'00') + /ModDate (D:20240506155936+02'00') /Trapped /False >> endobj @@ -438,555 +438,533 @@ q[*"`C31_GaVPT9a__6P`Whabrqne>,T.PJ)<)&ZTjKgl70jdoHd.^p3B`.1lXa9=,#=89W(BL:hV5s_ $dX!U/=S.0BoOZkCqgBSo-Z^cO;sj+@qS09gPqC*8J^.JXAqo'Mo+e2eo"!@7Cc$SHVZP^ekqZ#E_SFB 1t9DUE9'1oii>5af<.'Hg*`uq,p/+[?^b`3g6[2@MY-d`de2HW3*b -^>@n0,LDInI`YCbF7KA9]W#Qc+S&m#$.g_"%/(qhpkr*0kO5,1Y?nl:5b`E2]*89q<#;ITf6h0N#\]\qt=f0N'@hWjgX -1NF$dU?=coRu:#lNn2'#9i"Kk>8$BPnZ/t91Jbt4IF6r6c'd`,.a=45FbgO9HcW.T.'&]V"^,$K_=r=c -"q,6F)o;!YdsM7lN'2GuI@Qq^UE*TK-'^g-Tg!?;NZ_]*g'Q%FoBP`6Ul8Y,:U!>W:2sa$9].R8dtn^C -[d[99rVWlie7n8fBXmElP(g5@Y$Y5p>d`=bOmuFJO?L2>$]r=-q$qahDmr>b'@S"CqA1-9`kAX7+1O%LP`D\]H-[^=gF6b?cp -iN#_M=t\S'6/MpdAXR,$:2omXgZCK/kjds\/.HJPq$2n`b_T^2S_U-S`GHm-Hgt1Nm47hMf6>K#?-Sdu ->;kikVTWNSTA$Bi2R]gCaeSTdkE*M'o,^Vg^.$k#;pdU$<(aXt]]ff]\'*4q/UV9PppY1YS!$+F"R:GNri/-INA!Fr?[AoehAa>] -HPJ]=hIE`2HETL(?@1nLpK4r6eTX`0u?:(Od#[F\.*.)ZE -/b]u[S/A.eU'seDEg-JSlf/tfe"0)0c0q7E9.Gsg<#dQj3*oL&4J^t]8B(+^MW5ipa$V>:QW+q8WAkqs -EP/)VIn7QXOG`u_(&LWRp6SbF0<*T2;R+!4@XgqfnW8Xg,o+Hf.ZT9`*&"9N*`?u;&"g6aI")[+3$(t; -4J_!36RiT4Lb1mcG@2NO/:YVZpI;jq'AFEI2pRKqA(*e\4(^_ATZ;\_!OU@Rb>^i3SmFqflNcs(@%Y!/ -*D#^LprjZ9cGo](WAPe%L8)%Ko(*hF@t-h0A=*.,K#U#Hr?ldb%7T_JEUaccbm2h\dYYm^2FC.8SiJ$a -JJ6FT&ESdH9mbtfWp00(f$U"P1FC5Ar"YIfF54C(3Af%!F!nO?49hCHSPggmPgU`r2d/dq_1S.uZ!V0\ -(D:q:?-&5O5+o(G!R<8Y82&3@PjN-;LY,^[D;OF;0#uPZ`3+eSIZ1.[r%L^M&p=/0]F2*Hl='uQ3_HC- -pP3fuVZp)lVS)9ZnIEf(7p+9:pLQ_Uq8j8md&Q&@9g7),E9?3m8.&&Is_KF&CYe#Cg[Y2s4`$XQdS^39RZ8aA!FK -q].f3m>MBoZ(2.LbR!5XqEpI8<1D<^=B1VJi\a.(FfgWM01,qQr4ilWX/(BO3D061T/qOu;sCm=,.6j. -G3)Yb$#P5d,DPJ,?iI+Ylb+lOS?2fRR;^W'Q,):oFYCHZNr!\\p&B8,r-ICn&$5B5p$?Ues7QEln,EJO -l%`3'SNm)FXiqCP>I\9Re:1u3s)5WdrpTUK5J=hWF\m8DBl[PR2;mr="Z^7FYke!!\/lO.qUV,P^?Y<: -Dp7[<(:.,rkRl*EUIX(O1hVMN2/`"Z?KL;sGi>\na8Yr:4b.!c"tCZK0N<`sX*0b/oqDG[DW7%@X\=(LfOo0XhXE87 -?^Pi^kIWL0*\,4_le4c0"! -=]a2'M-L^_>*jI5Ta,SLA)$re)ptOr#CdEV,rOCL:Yrkdrf;?%rX\``od"4@Z'QSn]u1IorARdUs+YsI -^Y$$q][%Hc^Xd"01r%uQ/Ag:qc*KZR>Q5@\HgZsO](.5_lIXYVYO?P)F$/"95FX_fi,\bAhs_[]_fdM> -I](o\#I-E^o2?si[3$r\$;.Z%=,?(b=BF`iTa167YKlrUC%>T'&%g#oEADn.'r^`,JagI#$%Ftg?aRET -[HP<^Ms09_4kfNgTYH5qrpeBPpIJg^FTrkp`^VF0oTp7sAe0g6b<:-=/Ra^7g)E4Co@BK./OAiCcV+SW -H_MTP]OB\Ph]7i+O.s#QQ^L#YjMEB<*[&f9?9ltkD:"1I'_DU435t#5bIJqfmFl7mkm^tbm[ZEf7=Q7` -+4+]6FRthZ#JH<`H),Z$Bcnjl^]?BDiridJnmt7C'liDBNT4ef%m5gb3b%<=m@fu^MehPKg<\3-pBe-3hHVn/..#* -oh4eRg(g7FX5FK\#^iZOMGG>Sj+roMFiK4f=#+Hff"n9L(\mWIPNp,b`Ssso-^1Usn>T6CE0<89/T'ZL -[4=Gk=2Cg"&bo?('q]5oi_sF.SFt%f'Ug)"_`Gpfa"TMg^0\#XYnJl>+K`('3IFTJ -6mZ%'kG^:*K72ID-*t`%F:$6bKr033UtO_]gU&&]^[n<<T' -BYi8eJ]qG1?/UlB$G!qHbA*u&]?,#++8ItD3j%h6D4PZmB/E`NPFg8ngrYE+&AF$%Xa:PsmOjmn/4&gK -d/`D^1N7*s$Z5US-iMA(D6J>jUVrGH0Djr)Isf*o'"q9prLHJHZjUYKoJY%(E@H?>\M@F(I*%ufM,rE( -$;?:T.]FCIA&c)BRq?)AGs./AB(umr0+.W4=?W5Sdj7E^@D,8<3N=\+&&FZVb's]ogI>;8h\Hd5N>eg1 -<@7Q,QRqM$;+k*!A[8BHoa*.RcOW1/^1L#N)-:ooQSf8fWdVk?/K1\eFL1gW*aJ&79H+YuIcfr9QaF!]bK4"NGk&,\6\F3%j]s+9Rih\&;WgZkm" -6K8=E\qldp;k\E>Z.-:dC7e9cX,['S.J=[."dP"IOP8C980=IdK%3ra:DiN!pA+:tob#r;cI[k\1Mkau -/[I)]DjB4LmX_MOMQWkRGCL,dkaD:)En<&-Cm3ZmG_OpiK6lD85T>l3TOSqH;p8`#Bk`a<@Gg0epNS9m -d`k[Z=aLnGP.ML_l#O6\QC?U:R,Ud,!f,R=2e?7^G1"Lo0"r!>pO"^V/p\qfMZ/CdD3ER#Q&IW3gUbBc -/g%dfDM)du\_A`$''luf]DNu:0&(NZgP0seJ_hic\7qR2*f"*'V2%5XGC/?GB3irMEgIb4\U3oYjDIbX -C6Z>j7I_(4[r%/'g_?+ID!RCg*.c;h2F@s9YN_B@R\GWo.HE+,Qu\N5mW/'i5M*uNA>S)K1Nj1]B#rf2 -?;^.aaZ2,egU.,?akEX<2IpMD+btQ/p;85$H[kHGHKU)XmWS!/:%!2^*J,qp6OIp<"V2SNUpm6Ama?'= -]3UlFrM^`32iYI"de(8UX2Z`I-9c,P<4"VeMI,`D`80PeVl! -JPp>1g(ilrIVIS!AE@eQ0r2Wa'4&-"WLsB9KD@PtPD'LH&';5rdU9?J@mRcd2&`dPD<94Q< -nM]g[TQ^dSCG5Zl;Al8$;Y`K&Y'f$[93P$#\(eA;p]a5s"X/Vd(_ZcD+4YNTZsuVlZa$nV8cMMbAaZ(* -cTO&V4Z*E-YL:;97^Ve&dY7RZ%][@350&X24WGi8_V8I<7%E<%1b@Du[#2&]Y3B9>qe2FHdJM!oXs83L -m,XUW)-l'gqrgpHIrM.XF1SIMO4[6^9s`gWZ]+htVlioj?FA2E@MpNdE@;PDaV -$up!4rjh.6?XcJ(Db`1W<;293Cuu1a#YEs1Ld_2#rpP^#r7Ci>+&=bp]m1QZKU!,uMBim:jBsPTM>b;2 -(0sR_7(<6Sp%.MDfCr"C<88PML"D/Wg\!`bpm[qY6&'N$-;gSVTus547=Rh_`" -bGLi7*bHO,_R]0`\GE%uehk<)R]:NCqbmar^j)CSKI@dQ$]To0$ee\p]6$0BeTOe*)gFi4S"tCegH&qq -@_Qe7Ft`hm#TdSd95A%9Gr2ds,9!2[s#(g$!S4"c4H7$UE-tkkKU:@!*rQ8R<8<]]%Au_Ueu#k(N(Ssf -_p5>=IYuh9]f,9mW?leKc0A,A)sIk^1>$*ZQl5iuP,<(Pp2dV1 -FPU"hBGYG8Xa;("Xpmg;R7tFKn;oVHht!]nQ1e>S\reSEA^Vm2e?m%D?RTn.S\?m*Niu&es_&S1)+O-rT]YV,O!^g!qC0:td#+<.q@,ebomQC2\Xm$5lGU8R;XLosDOdY<\8e6L]=h]5O*0L$ZF1"AN578idce0#*4gV$ -au.bH[)Ds*Ok?*ggc0T'05#Z#7.Td_`nk/7=mD)qdjHukZ=QH$kWXQ/ -S;Yofg*lsaP"FcQOh)jJ[+ccpY+iO@B/LV3\X+Ri:%H-g=Q7E5KAIpm`lD<+YV'#9>+ZVE9bR)ZpQ0E( -ji9Nl\fR11<(&k4E`iK6(J3"bFXQ9q:4\#RPO$KScdZg/dF@T\J*,nYZ?aB*Hu\i%O#p>#*gLJ1[s%7H -Q*aYBc`;Ja@iK^cibqAu?)U&/UIJ?EB^-Mc@o&%&2$^r9_Mh1BqjFcNFnPS#Yj0SWOmhCo(A&Rh5F1fgo+(CLmle:FJJ!V*.'0MF"ce=bX-;,`g)b3#$nQORHSh`"'m7),(Rm\JTD$WM\!;VaY[@l>u#"?1': -r'QLe["<:m7X1$SM/0uheP=V*gct5F*IBE5GfWS7H'QTYf6nV-RVpW*'B;QApp@Bqi%Dq@C?0'aW-S%8 -?bZQ'P0Lk#/&@]4\2aimmC3P1rYqp;^Zm9IfDG7`4iPKYB+ZCNX#>[UQVejK7V)C7i:Dkqq#s/)-G36$Q.npGYcY#%@Po)JnGPMr"4_7I -bWS8)q(b79pr*WMKQl+3d#%NXQn'MjH7Vi]^`4YIX9-f:n-K1hC#YicO:c#1e-U,Z?JgOFHSR6 -LD@JWLU*M.C>2g,h88c.kcb.@g.K:$ilprP>^MY9fY.d]@I(Qbetk3YB<:'G\q;Es;]smO+MPj& -`Q&K/oP6b@&,?$M9#5*Ps;[>"*L1M]g3(AS#`2N?qHe8T;[Za=]H?%LL]M?LB+nZ%@0%uRa0F:%0N`Bb84"7Db8&%0V;1Y%\Jsqdk -Z0\hr>dWiEn*,66\gY8!$6?$lA&K(DC3>%ci16m*J5&[_Y',2,L@0eW.5X-Mk,!u^?_!-`15F,=Scc_; -p0eXX['QcG$E5g(L$+]NZT_-Pc5oS2.JF[$D>0I3o[;MjgPTKPl,rip#(e2TbW&Q]\fNko%RnS(aiDV> -P;R^6905?uH`&6(",A0h9M5NcH!)=aF##nn;%$$-Bo]DEAREgt=dg(i:4&<(J4"8EpM]Daa:_"1,m)tZ/.q7K_\-,+.iI6HopF3Dr^j.&V-EQKR!WW*QH7@AETHf7 -UdhoAAVhO":hYDC!aEsI&R&RdY'c7;2eiL8heoee9FEhS(*KIi^b;Mfm!eQ&"a64AZ;A,!cD,l_"nnaG -J^R-#Dd$OYpZPdg03Ssb"nlC9NMh:n$TP"!\EO,JYW&[FC'!7.D60Yc:@).l9=pH,IAI_;3*ZjX8\(I\ -4)9M>7p`+?!gk8eD6g9*:qj=;hXQdFfMsi]%-1Rl=B]*6mb:GDm)F0*n(^gh!kZq\2_D810rP7HC"EEH -;nauo^m8L2D_+KiC&tt_-nPG!8^?c:qmin[6oQcAMn1o=L)/E9eiiVpe^+B0YoRQ[@b1Zc[\mhjm1Q7F -PS5XU!%HHHB!;0RQ?Jrqf^mZ+Ha2)-^t.A4>$u9/XtskG%P;Yk1%T3UqIgMuTHM8P+hNiu".>Nm54B>) -Op"`ud.lZUWn.ZLN(Ton^4%p=FLQ -*0$9par:1U4!h9B4',c(742dd1Ctefk+FM^L3MeoCuj`l_O!=TAmt_jnZ#*2/EkTC)6[(e -C#^i_o40.7F,>bV\2]If0ASBhTcLj36ekGD^phY*\VEM?%muXK/QMLo-M=>>DtF*Rd3h"ep5&-hf2[\G -B^F2U]Aftb&))s[BRJ>j8\Ptd0#kG4B!NlOpmTrh=,-P$65q-/jj7&t[W1RUotCAo);nC#qoL6`?07'" -RJB,Zk.\rL4jt`2e^;A+ni'r_Mu:pV=%BQA(:3;s8?34uK`-$2_pN_.jA?6K]0.B1d#k"Fa[WTQElddg -ld!QMSG`B+=:]Ym,Yq#]plU9l2a/,JndAqD=tQXOTa\/,K'\cKn090ET)?YI0<`f&S9(IuOdI(a4q*[u -2R-=nnd+]?eWh3lf`7oq9a-,+:tb9drf!8o[!OUsc0d=0nK%Ff:G96H.j&=?V#FHqAK@;Gp8bRiX-Qg9 -kJn)0i2f+GB#+)%+.:4KaMN=!50F-F+pUH#>BW@/O\_$AnmW:KJ!_6Op7:?H -9/>DrpYU&746H,['f\rd[kR-_Z0M@5SFZZ+IJYZmH-Is$rYJcb`66Ilm1#4+T#*_7eFq`,d6@rJ(g#8p -=YV#K9(4_]j5Aq=)pl]lJ\@C[4#=`,O5Hj[]2OiQr9AS#n(@*m&OrGm!e%c%LRkHY-Ze.%UbA4P(k/WI -ed"ssfIpih`f&)_ncjQ[\ku7\8b1o0c%0>WL)8i^prc%8jm=ZbT-^TN;_BOEVkn"$0_RjiP??AMmgu -Zs1dNXKP[rqPZnP760:W@m#!9q2L`s`kF.non58l=*K<]j7TO-qf6K1]Vq26Db]S#3PHqL"Ra]`WTFB! -1p%Y_Q:AI==#!)-&Q.Y\PfN\VcG8e-Bm37##?D%^e?jgHRYqof/Ap7/oj0q/0mN7W)/$VrYB2,HK:7)g -lJQ]J%^$SkdEjgtIbphqL9SZp(J(`2"CA/kD'e05hlMs*jK$.AN^paVFCE]S2?7NShEd+_Gh+$"EQYs+ -ouo5]Kfs9fM5u05fmJe58\8]go6OgOe23Z5*BkRg=_O%s2"F3;%hDqJ+3sEmi5?Pk4@#pdL7UF -h/C]&S8+tmAdSJ[PN01OGl4t.R:ojE$(btaVN99aA3">1peI0G6O^pE:#bN; -*RQ]jMeD(qo>H>f@e5?gl@o#PEupl,\*.sQe1g#jm5FqW21)#JD+L2T?c4V\k#oI,48$l.^NrZ:m&Pi' -c+";_%4QO_O"tm4(9sfl6s\#4bk*CgL9s2M'g[:i]eY8.R;Mhb'hDW-RXh]`d^XfdZi]-3#, -qfPU'a,0[VE=C2>jsS1IOd0He$u$L;ZiR34Y;[e=Y6;/B9ljpCNF+,h.Wl;?>c1G%pY==m\C\#;-^gar -I1Wj?*gQS9YqE/uXLaKol3$Cib2+L:1A2O<^tshA``Q*mU/Aq'r%u$/^hTpu:r@\JP-/:ep=:1F>DYFb -#fD$&CVWS--M`AH1nmU\T8;HfDSpn(d!Qh(8hk@XXcc:T>/jn3Lis0@!ZDi?%FV(L:/si0eqU*s+P1c8 -*PMBFGoibbUTJ=4R/J,>1OV`:QZi<_Jbo2nW'_JGYSe]POetQNe.>I6EP=d#ncbJ#C580"EMdobP_t9>HK18)SMdf.Ic@6g2<)p6PY;D@Lr^Hm0$<6UZNXqf -\!kU6d^Y6B@m@P8.BuN]edn8Qruc"dP8D\b=LKeUJshX:4/fWHNi -i:fHnLH0D3,.'8)EFi)Z33IR4eRbX%G%N$ia"4YlWtV`o?7O3pGScTaSK^tqgtK&IXs$A#DmVZjAis\" -5Bln<(P`IeSo21RhfrSbc:Woe7RYseXC=EagZFVX8Sc5jLfFT3`#8aFY1!#/>^oarfb19E21g(Fc#jLl -i+)a'F*=[jLcLA*Y8?X[mFiZW[RBm4[m\lC>9Z_k+]li"QTakMMnQh]G4I'$V>/D7.dp`l'`L=G";0TsbG\n#H! -*%ZgfF4Hb^8)t@3VRG?Ejg@Lgj,BOniGSXh,-@QHT!m(Yr(nt9di"3@jLgsHX -Vu7-RXeEQoj9&o+\7R3>Z>l?737dcjcQ*8[WLn(`(lP-E:hJJlpR%b"Q[dEF9k-bR246d3GEUZ.Pte,c72n,t-5=57)nM+ogW?8FLKJdtR+1MK_\:j,?'r)Ju^,f+"g[-N21sirLP'nVrj3N4m(]DK`Z0\O3 -I!dGNVdA*PB+h30e\[>ph7)!cj%C?W5LRPd;3F:k3c,\H1J]"E3+r&@E9B+A(0X\8daX_E$?3+mE-AA* -;eg&A+j"]^L7.!R)pdI(jl.^F^IiBQO+t\QA0IPLYH)cd]2h'9ibE[$^,i5\d3J&?eR+jgQ0M3aB;@QA -%6=pO`h63W++]"[ID7ClqoWN@#.5em1mt*hbD,!/DdN!k=?dSI("Wd,,?Wak,)/A2\6eRFp<`R=,V!o0b_' -pKr@mPr[e$DE2dZqqZ;PiHNODs,I&fpYPqjBAd:=rqq8'Gkh==KoD,n@]s.D'*%"Wk:8V>U8c="QMb[A -2+sto.fBSk0/7W1fa3+>FEQRn(7@)fn?QA4FEKnQ/#0-Y?X>eHbkb=S>^9L6X(<*UbPZ:jW)%(!Cd^_O -D&/h>Lc*3g&&_gU`!YH,M<_Y2p`7"YIenT+(AYQSs&8aiPt"[e[=nDb[6^fJMRt$ujOOb`jqZ[/M_o:X -FmB4GS@heDW'A=LUD,XK-E5>TI.i%D3c`LVo>q_Y08,#t\IEaoj>'=8j8@+j,@-O&lHg+"_,iGQY.IO[ -nHMEn+*:/$2pb"@?^@7D/M0MEXZOtKl&Rn.M`n5!)b]%;+OYaEV-Q"n>4CM1c:)hXOSHKc1-)O)e9Jq* -$EO1>0-4hfjVQ6I2jai\T/X,ln97+_k2Ph^CNJegW7fh!9B0)>Zc:VG10uT4a;Z6/1WGi]i*@ADp7A"P -NHJ2bbU@]IobUXrTdIRD/)0rnKnA/fg/U2)hEM:DZ)$)f[soYX:=RJ`4QQrk6mUTsar;5mFRsH+,@/d3 -`!<^f^'cl(9ur(0F_Rda%5SqE@O3O2/EcDQMO3lO0s1'=OsCK5MAHNe>4]k12`)a[f4V1[1`i6][rp4>H$QZff:R&ptf -5T1Ep!3+2J3(/`6FhDa))$Lb?\WLMOkJ0;1l#tke@QZ/2T_jE[T3aF[/13iLd2mitlgd2m%)i#sbYXTR -5uts+.:"M%SSkMYUKs0:Z;TM^7.rL[Yehr*L2Tc7#14ISX>>?0h/Q*(Ls4Nk1[n* -Hg?5W_L>!n2RGe6/>`BTTK+cNFZFZd9Q0->7OUZ<: -S`Q7]fk28]&kA3PSMC)[Z1@$UM\-c>AuGKDlr#u^7Zl)?F3ZhQI.gdEF%hh`-/EW1g@u(3H9OqOcjD78 -XcejD/^h[PKR?[nBCLZ9A,[UOMJb61h-+7#hGN8bJn*Ts0HJ;lqM@>(7Fkf(-orZ[8_'Q)VjeS#\rbHr -orV-JE\qW9FC/R)<^E(=L/Oo] -+jF=pI.33["bfZVPOa<+X"T?C6]!XrKR?V/OtqYF,./8=9Bi0p@M22#Y$X)Q;bB$Mj:lsEd$I&0&Q>?3-k^gbSrLiQ.E[kb2+,-h0`Yd=.W2-m@jPuI#ehOUHZPX -Simj4)Y'AW(VWAC?6`^Yhm_5+gHEUWdr!Kr*>9raG]AZG2^t5>?CC2ijAp0IbH'DMSCd/*>N.#/g$mf+ -;!^cdFE/^#_-V=(U@#d$isRB"pY$4Y6$2+k\[)Gla?PPhp58jDh;2\Kj/X30"PBK0$kDVW;!cG+MNN(b -0$p:NN=AiJDm]1u.+jerh!"Xa,NUR3CoQ!DhWKm+iUlQ(3Rbb'[Kh/qgjr&0;7V%T66,=&8dE+G\^bY? -]f16@l2Bk34+;-+DR'NO]tr_gYM_'0D#!^IZ!KDNi"+:?kRl]G@"3a;Dqe,]cK`UiTDA>8p/BmA]U:F5 -*R'FjnMfhWZo.Yg#P!XpB-)0d(Hh1&B"i!&GtSN`)QI]6A](LZQ.\&OENm&T;\a<";5 -aBlt+;'`bcN%Z53Q5&D)htWL`\0#t*[VAmOKOmq$$'Tc:&t(4$B=_[HAWD)7e_i*KJ!TI[ -cUkl#NAgN&b>tRpb>n+fe3[1l0?o_sRp!Aph/4h==-7u-4X0Ir&gNI*IYh`R[HhqM=%+G-8//`$qfPhb -.4[;3YlW$3Ba[!>b;=+Om]qGKaqAR&":4K8X*@7K%d4"\E[LTT -lq=,0,/)Eomn;1UU>V,YO+tF18C;>%!FHS,cm,IH7:/sJ+t8/bq'*1XBM+10T_=c-agm`)]7\.\K&XT* -.#iE%BK(/J>"G0p8:PUKVM5hHS%h[oGiMrA`,3\2(O%5!.kA;I?"Sl4 -3o@"H0$Bh:*]dGPJ7_/DnjFA2]\D8T$JfU+)I3\c,I_u]N3HRHQ!1FKH[$i$DjJ8o"I1J4MRqZLfittM -H+8rY_:u=kE(:SJCH]>kGrH7Wn2a([;N?-th'Q:NI1>'0A@E&d.gn\ -R-$FoTuTSPcMR(r%p9OY1p]$&&!O_Jd04fd$?BK?G$]urr@VkoZEtKajEAUEdBHI4k3Dm#nTpr+>NFK# -BInbr)e2>(?@je`P&aE+Co8^-G$dQ*$u[+_P,/TB2KfFPQ2Re8%YWZ[EdicIDPod*F-JOFhm,`LI[*5P -R6R#'?8%C2.P-K!OMk7_Pdc&9Q_UqW[`cekS9)=P6i?=r#daLLX8[Ud]/BUthu<5Z27C*^fdAZ5^-9'k -B"gjdK9=X"EWHp9dRhN$H?N\PfLH_,_Ah%HOBS=An:!Q`e[<&G?@'6pp.KbSTsK]eD-C#5-;(TfeNKjX -Foc%4H*I3u"n0DR"2I4!X6qf\2p7ZOM*CC0N"9:q[k-@FD8Xa0cR*%!/r\j]L=]ms!.GlMV=Wj74-6P[(Pd/P"e5qKLPqChkT@ -=3pdUDk/;ODd88Tf*R>>5?6JS,koi[hRKr\mXAd1R&Z(CmYu`m"LXe2jO=l@! -9r=[\&')>F1:S/bYseK,f*Mp:b[ZcOhNYif)8@`8f3p\+%oVPIAuI9.)fs:@;36.79YUC3F?09)JJrd4 -Ml,6Rc!G5gL!/CfJ3'eUiPo2+*lq7T9?S\dWp]n,*>P^dXLWq,Ul\*WGhrSMgImO8%[nK;6TMS[d(:"? -A[?KA^KnQ-9>r$M&<1bf>&\:2/PMXb`-3R_+1t%r/(]fp3m]DCmQbE/QW0eBsjLjYKA4BH&E:L.3uC"0&gRkPq4Z:E!.1"goVB&[rE\S-Qib/4G(;-#?Q;ni4Sp>7jq -c/:drcL]T=`tMRn&\[NC-r/WFn4' -SMR9cL;18ocHmLU>$lFthf=nl?'I8FIH*2Vf^oX8Yjbp8jB!u5_@Q57Q@\e]5dc3Q!RGu%d+:L0;^G&@ -3Y?R*N\#KP246=OE$\3t1t;SXDW:QC`6-nmpK1*Y'^,DBfgEiLY*[F*WU;QH9U[B9aq3Q7f2Xl6ZpW:9 -7D6n=Wr%rs9crP';=rjXaFo,T/S@P%#OX]Y_(-ISih=u;=oX1k@P5U7`c_!tJoMf6nlg_c<^@IVMR -Y&A[GTQ0?TgVAne;#V6^UqBWeqLNEilN_/8J'Xf4]#i[O=t0/W6fq"G47P_])Gba?5EWFH39Z^N+\u@M -,4>9bh>^)q2B]Da\*QND3I^"WE%`4lOXjcL)iHQ'82k1sA17"#=5N>8Z@Fjj#0]]Os6p4?1dm#]*D[5(;%`A20?nEKbK^VSr2)EE3@M\]Co\OI\p -m:A?uO_$'KrcT:0!;55Op%dT:^KWhp]C$GcY845=)rE6M)NrTRCn*jb!oR_mp"rmEY83moNV6_N=,WnI -7^E"4hgJ`QNID-+4sH?\+5!FG>#/i<*8835BnI^GoVSjiE8e,XUV^^;3_(WV&b)_%bLPa66Y\ -tDTc;=?qWh\FAd0)Q\__Mc-DJYkFMSag0ejES6h];S2k.,m;g( -Q:'c^#$[760HWgnD/f(Wh_3>oRbD.B&hA%J%W/GHch.'dD"9n6)'SH/!nRXgLN[&0(=rcK`6\^a%(3pS -g_J`^#jF-[K=h0Q#kEDoME><\9GM>hl'2^)Z*Y]RL1+2Y;/Z8b2u_K;e'j)cruX47S_M>fogG&FgSrtf -NG_Ucf\8dnhcTi^*GMetKdWe?4N#)TGsDn'Drn?44[6"7&:Ujr.Aq&NDBGV@hA"m6^C5Rj2`*H`g"\+/ -%<)(b:6-/t\O:q6f"(UVUX\Wks/"uSBGWa3IFZV[]LPBH?fHfAh>#/#ahr:m%f@fU`#b%m2grGYMOUt` -s\CS,qF/LDa:G3SH'2Djk$E -Q>?1bPX^..mrL<:j[]u1+?F&)l\\akXhC\TJ*YCEOM('D>9?Ku;ldCGnPr.0Y/W_=CYO:gn1b)dqh=r;[&5m7@C(4&i -7uqQaqHK!<&c-a/]CMBEMj](S@HW/c8(.#DpTi.Lo[OFkElu*dP6;un.Gop,&GLW%c$X:=l]*mP9Utq' -4[/#a]\)^05SA):fs+06/]cEsAthf>+:jY$7Y&)9J]A&ffSCbIVMEnF]3E&h_f@^r5i6^D!-5(-\4 -PL;3>hp$ip`a2G](qWD^iOu0Yp%G'Fa6EKn(35coD1@tWh/\mrTH(BV<*[j\f,/NF4p`mE\(,6^[Tmk* -?J9?Y]F-T$T7L5W'2oD4Q^#D]5/nn(3?+4#UqV)Pa"AM4,$m<5GAs0\DE1B/;@[X4daj+)3D>mQ*%%g` -nJQ@l:\(8C(dgA*o#S5fi$I_qJZ;HI37kV-M$S3n(W>N]W4IS#;+sKiQ2KI[HLYnnC5W65;RNq -hW[)ke.uhS>L(5+[3T%.jMJDj"j82RIRO9du/ZP -G[@,q&^OL9?DpEG'>9Z^kgbQlB'Mr^m-!nX$i('bmDWE3V7l]am4R+^qW]grTH75UXS5dnYo;S1g^(/t -QK3;J=Q;OYWB!Fq."Ze)5nRSF";/P+2hYE*WrjLu/>PY8b!CI$!"NK+gNJ\L$0*@;Xi`hddeW[7Z5BWJ -gbdAZf:thdqJ16*CHVJSj2@E.B]sAOsfo$Rqio -/QUW@MS!*XO^=p.Ut[bVQr-7Zj,KVeJK%t`0J./:kk3Z,??8)jDS#5]("DkNKWU$\odX/IC-ks&pYf!' -FHc^J;60O?HJN?.aUm'!$e2.Q"#&6Eqaa2sL0$I$ED>=.VL<2/+aVHY:Pl$(bi_pl"c^=(tZK#!uI%]7o.Y -LgGdGFBehm9m/&([\*e;_00$5l-3'>RlQBM%tZ2Vq+ -8lo<46dp\&m4Br_j3@OKD9=^uq,PYEa^t%PhGb2=0:jSMpQ5!5FC@pSdd6Iddr^3UVc+S=TK,oqH""%- -Xk\a+O=D6:#NC*@r3WepZM:DG^iTP`clf4Wj76XN_FS5AlqZN2CR+&2^WP@e2P3\I70\9[?*@Rfs`DM*&#n1+`T%7nWGk[o!LZF -3*hr`:#^9-IcD&rUN),H'!]Of;Mc$!>]7]2JVH63%e;;FiXFq%Vo0aU`#4snelL?u/o()%ZQY/-l:\"I -rulACUm4QM8i,ZdDi)pJ^LLX:@H^.-?1SWq3!^B8!Gu]d[F)6a";<-jst9sQ/sko_fSNk8d.g,Xp%Ff>J.b)Ha5SUB:TTo7<<%YVU*!Dq_knF!Nt[Kf%Os,@4@5^fTPNC -dGask-W7*G$G^jQ*'D(2h`*EhmpM*MbigS:`\\1]ZT:'3C(J[aLO;V;\WHfX,qi!H=a$Hn6m#XF3En^P -]Jp!>hUs-V)tITW9$cOMX>B+k_gQUGJo%!G(7FDIMZkH=.)!QoN`3[^KLIT&,/>._p=bn.'BDUL+&VE- -":O+h6DW)E9!]d;F,iBM%#p$kOb(nHAj\n&fo/ZR]WHt$1j(.JagU\C][Vo^l@%VHe0fKC0R&6A+)=aB -NTHn>NiTOlZt1BP@<8N5UYWYa*L1nqDE`g)?@!V'E@etm)LofZ/dpf1mm^U.Q4PpRn*"mUo`Fm.%n[uQF'XeYH;F*uq%T`K\nP?T>BP:L?e+FDM9'pfjkt*,-2"a/*UDZCoGJ6Z_93Y5, -=-!dDC3M+ikoegWlIH3ZhuIEN..l6+g!5n.Vj?5&PYs0%?^!W(LkoI=on!Zkj[A?9Ta)b#X-.);*F&?i -aW!k$&GQDW.BC)@cBa3Y6IZF?#Cg[Q2)Mr5TJ=*o/+UPheud.o$m+Qs!2.NGP-ZJ@GPc_6!cqYJeQag:X.$9T!l_: -6;D(`e10N3Mcm:YA8UEe7j017N=Oi!3q<2KP71X+Ok$g6^V,=cE)oBt.U5n!Ze/?l8??f1=p5-Ia99r, -V2i>7rHr?^2 -jNC8K8%S5@eA<1X`_J+L6]q3:jBdI.bTB[k3E5CX9o*oF;+aUPe-\Z4[);E1#;WK)``FrEJMjXDg;(rh -PqT:]aM5^B5%@#=QW'BM(>6#>+`M'HW@VhdQc(QscITPRV/iY@-&qaB1^)ts.f/;f0AR%!1bO0<<7iH2 -Dqt:#pFE!,06g^p]aJK!\@dFZeg:o&cRhk._G[J'`$i>Do]?#ONdeqSAGoVmd\VYcWj,G7o+".>oCeC( -%();:H\3&rAHb[IH&8p@iud@"-[,?krYlZdR/3>6Dr'FTKdjF=ps\fg>:*W_"ab7WLYj_ZFdEh9k'V)E -Jj`uGn6TCb%^op:&m9Z3>(0iY0K.=gMV3qsq:Ua&U?-gePYlFAZ2]Y6%dVVh:Y`KN^jWPA.shL"eD3Ad -)^d*R;e,I]A81]UE7Z#_/+g&EZ8'DmiiFR]FA^Lf7TB(`a]g8d[(>C\nT"M9q$YC:cUfA:N)9r*5?g=Q -QsjM/QK=b+%<'qnLgLM4*t`$\RP2kaT\K%`.:8B*@`2gQ#?jQ=_t$#?/H;72_t),&?^1jjk>X+XTD`oU?ecpM?T\b?T6-dgO8S++'k98Yh"'A"[eu!;nopU+)OCD;pU:"- -)B*)kD`s[jI9?Ie?1VaVYl(osAbetK2"F\^/H:KQq,0U57TYZb1`+;JADe-XPe!go`iRK+b.$1L/?,33 -qC@k<<;dVV]0R9W"#;p\DE-=3Dn?&'P%?Ql'Qujho3jS^+)X"C9,jjZi,REZV]T>+S?0c?h/I>c%0qUC -+=+RMp_nX1fF>\`^\4lNKOm))k2TZ+QXT]TUn#WXmt[O\]2"+/!h-q[5Buec-EA]3chXjnkrs(RH0=-9 -eS`stKkmA__(ILp8mQ$RQ[T]-M7b+p@kQ6U]RMHGm5U;[pBM.8YZ4?3p3`^e8CPsHF,j"$[qjnMHGVRQ)\*[@s1 -d.K--HB5dRL+p=c]8tu&*3Vu.pPm(KA;\B.*d1o!Qh(0l_9o)Alonk2I0\6h@h*>/I3$#HQeePo_2lH09tMk7rhfW-H1'Hi$#XFjS#SaASZo]2n57n!cR&m$UW3CT -&@ke5&\IS],ULbSQ]UB0-:ZPorc0C[[u&\;.uOjHPFYpDpD:p9C).3AOMcfhg+>+YI+)sOrE[p)X+!Io -D0J=\SDM;lXV\Z0nj'3$pp9U`b1G?gS!5S'UY<6UA@[](]K+PcQ-*i+kE4HWEG[OoLWqu=emiBa"R4:/ -F$ss2*0V.4da9VS-:s*iH@D-n+neH)ViB>sP2>4KSW[>Cpo`#n.B^U)pu9q\D!m0D<35R/1Q+8iGS3r#U+ql%nWkXPaAo8%&rqPefo7?f2XQ_1ZbIup)LkF4rA -KCeo!c1T'iLcsG&G!nQf@schIIqcM]Dg-iko[T/5p%/8Jaqb/YrW8Xq@5KOs8@.Ea(EIP5c9kRRQC$>T -\Bc(9qD-6EE)+JZCUMK4/Gd@iC5.1\3#`D(*jiNBIJRjpm.@N+6U*APqZ!lj+p,H!MUHJa7&QlqNJ:qI -+)HAl@dJ.3fFEVi0d@/sG7Wd,->o_3r_&/r;nImkAGYTE'?uSaoRg'7rW7Z-h=8L;Y6SW8hrZn,I2J6\ -i?,@DUo6@gNmEp(&'s]ZUd%#N(YUcF7MG6*O4,)uJGT(Za1]9J7*c+mI#KinLDKSh,2N]LCse=;b5n&D -ZjY/PRI-0l`o3h,@m<(q&Gce,;tgF3AhS!-1PoHs9;$7<@TN*?okEHpD5B]\)mCR[=4gh*nR(!3hqi86 -XRiL7Fp.)m.!X586#i+S^[9X]g's[_NW,5ZHoFtRrp,Q*gOo7rOp,S=p[(40!og&fM)=)Gm\*hCB[9a9 -,S#1>eKKfcm2TVM7Hq`__CqrM_4k522q;!14X7'M#s!bP&(M]D8'6"G%f@fU`#b%m&,I3`Hbo!PHuct2 -DpH_UCj'UTZur1rGmG5-/:lWH8l)Y9;pEk-F@)+!ihDui.&J610o=8fW'q\'GoR@AOA?)TSH&GEb[OM -JohZpgHamC'9;TnE81iApkUpoaF0&o"Mlt0KU'aNR?LPoB]d0gnX[ib.8##CcM5\mLin^G4CF15YV^uV -F1Mt'8*o8M>SSo>4OW%>J,dLtq9Lu@:,6.Ep,/&8'lqNiLpN@D32VPDC"S6DPd6$EM">PJ[:B,MM*.=" -1\dianih,\YQAID)7sr./f!n6\W@M'6XaDF;E7**',n<%idWI$R3a;FE-B@S'Wh: -D*ud)6dJ+Ujl4Wq_3eDW_ig[+"U5$`')Qp.*JscHI=/mKhg)/4qtPoFbS]4SQm7RY4SR<6"Pn3sNUMOh -Y:0&]ZLLeaq=k;-0im3d,>0O@&B>"qj.XHg_S5%%q`!(5*42`M!XE9Y[StYU%G93,%(_b-Jrg.U?1XhbO=ZF]]Zga,Uf8(AlEo -*E7sipK53tP<52Jc#t';3dht/.O=5nb=TRr4)EjdCj,cnq3[ePr,Cf@f\tJ@^BZ'L=WGT[3\@OWHCeNN -_9tB\5l7k)%Q/Hp:?e"N%;!V]$5kXI^NM#B.9LFWQ5o]A9\A@m%l,351Wn0aKiOb'3$V'k)Ab]CTAA3n -cntp'GYVtjh`%ugiRg&(0h3I)G-TjQH[J?V$O%]\"[fheCP@!apS^e,"$/?AB`L$^c:[(qP: -r$boUF+*-I3RYY/YiAWbY3!q@ATfXA#[2a<2UB^6F*4d>RP_`D6Zm?+*Q9C(L+$G -Nlrts$9LPCC;tJ3n=eonS1d*S[B&F3>duuM(WA1TcUMbYfjU"P^\>$AoIHf,jVm%;!On+1i0Ui%gS@DfllWjq$VYqTn>rnBm0CV6J?Jf -&!IJ#jcMRJgP%-Fe3f#R[-!1\Pi8L[*H-R5Wm"?Zo,PW;3N4jj;O.-H( -qH-=G8h)H,fH[N->uRLf[2t[][taWWMRcB9T!]A^]NrEL?hHT9`(CX;)f!:n0;8!(*H)[f?-o4.TmbZZ -l;;;Pq`oX0'8LD5S(gFX`@jXb[HgetY;uih7o/u%c&?#^bq[3h@r@&G@YpH.ZY04q*YC,r&LVIsbZ[EI -Lp&kWf.+-'Ds[/!`VI^E!HBcBG-ebDH/Qg5]nHVPNS['?dQWa!2]f/A@hgDFsJF!4!s!FOm/:Y?$oF2ZtN -0t!#"->d7+8_[@NlRc%-j8>Cb3H+&81pU'893.T7K3.Wa8Ve\C\*i#>KW#qphc,Q&;TtKkYmaqj3.2"rG6S"C$"'VCkS*)rK>nmP>9,]QMoXH -hhl6sLRcH1q --"=l8o1R:q<[Uu>\g]8":gGF?(dZWaWnMpHX0cjmg2!bI`7/l>N0NS:GsNM>d$s+cTc!gAs,`+Tr,U]P -),XVI,SglhJbj;495aXQ-+U.3960V3/udO@'0dad?]r>rR/e=lg>634",GqaVW@1*QgYRroilH\btI/6 -n2"U]A[K&pGAk7]<@V/-4DV_Y\\%"#0qX&iS6STF(LL4GjTPSc`uOXDDsWYAnh[f:B$Vm9]AtptGF'RF -ejejMXsD1"H*&lL]-^.H_[G?qT=8o$43X1[YhO#Y0Bu(IRf4H\ed'`gc_m"&J'R -K?U`nJX.J*]L5hU4J(eKAB:OY[[4tV/\TOBi947dgiut&j3l\Seg!e1]WmY7J-\[&@00&Fd" -b`:LoFPSu8GEp6F$E\3@S>0tmR^p6r^,'lsPXa.s9<;Lcg -$Je>8\ogOJRV]hoV-E=5QqOoKouhQF]IW?LHKMHf343b?ur8tC88pI$\6q,5L,f2\D` -d<-tS7,;T:0/)-B-1NJR_5>SN;en[CQ;d!NU\G2P$;M#c3b2ZfDRE]FY$6[W(3Ss!J)3jMg-=TikTu_< -Z@pq+6i%?'U!I_NOqQ",L1QCKn33D8G$/+1W;&MDmb)lXc=YpO-"7X3c&AqB[r@'j6Rgj"jT;L,`P3YO%jq.p(V> -gFVhZ@Z9*Q0pWo+r#3GbDg!ZP>D;eM_*P`;\2s#MAp4>=pU>Bs^XJq=?1dgi;)pS0_"H5;:EO1:#*nSB:Y#hlGiM\9tKGM8=VD]>T/^=TU2b -8SY7^;$JUae(AZcA@T_tbBp'JB+iWQPN0VD^6&8Vq>6>^;n4nUg&3ZP^#_?1o"3Cs;julC(G>C#SN.<$ -2%8n'2+i&JO_iMSn^bHjXt?3B$kGe=PE%h5)?VhG3Ks4Qk=iOLbB'P>]UaMsjLAqVDqU/9o0JejolZf] -M:ZamA3IU'5a_5+iZ,!k)^UFu4ttuLo:]GnBAUXW;=rl.YIN%q-eHt@:7b41lYV"gJ'sl_H$s\Ab:(Cm -P-T("ef@-**-^s_7WL!1I,T7@BTB];iG6FB3:dIJSfl;^E$$h5\kW\S0D8J1W)[cfIH16?[NdEtLt4?Y -laAW?j/))$g_b)_qOVim\K6"X4>(qmh2,t=;sj?Tp,dS[X5E>3V>ABqBR,@k,fLYe -\k8/s:'&570('$4c6hB\L?&U.aUYoRXt[:O^J'X]@ZtV&^FQmCFl[XK-HOOF*&$_WBp5C!K(#_s[Ht8] -noT1*$RpDmpQoat@Kb>S"ibHHB+U-[g;3:-i?fp'1[ND-(?J*KSA\9[06.ODZFbu)NoJ038unb/"kqSr -m+e%F^,s724mgNis*a)%ooEVMc;^:BK\98sn#U;KG&[-1g$)r/SqG21;)pd!CB*iQeK;0XKZ[n?Km2*c -AMX,T6J@t:&)PoDEUI@nQ9X\Qq(MV8e/-HlMn_6[I$HVCe/8Ot9"-Auog#?Ks3qsZ-i]:')GcJBn!#_V -9'qX?DNnaGGBS9u&#E2eHtnGVY.&c]/i.R_ZrkW-g!o,RkdrY@C$-LMq/2>&ACJ"]H$/kRG;/bENtD=3 -GjomE=rY,:\Sc+uGp?QafZcaR$t_^3+qKq^XjD//0Rh])Pt/HGhn`CKF]KsP>(@IQSGFPW5hU$gJKhjn -h*FMkBOe?__8dCPQS,GR;4J+t]DG[C^:NRh6'!YpJqqugSc0^)^Ed_aH5)jtWVVUn61:P]Gu/Ah5Ap4$ -X+7OOdOfh-d'S[c\@S^4Y-#C+jYc:RSDqK2,uc/q;t5#G"e`%C7_l!*S3u!^AM>#U*L&i=4!!*s-C&7G -PCea;+FOh2&KKdGH[_Lknn+bGk#)/<3FC[T297qD!;*Tse)*9__X?\3C6tYY0Y4^r(L_NQH'YAaPpicjO+AL:,o\inAZFP.RdeiI/i;HK*e[;;#i:%Ts/+_gA/F?>ZuT:o[f* -+en.[pI;Q+_p-@OHRmuEG;b.&lS?JS"2TjC9m*G/m67OnqBjZpf3,03dfj+Mc?u+GQF/oio/; -:!J;p]s?3`iXp%L_krua=[gT;<8KloeC"6V)&e)cM-Un(*d]Ke4("gYB,o[*PdB5%B_BcUoT%&=<[b(2 -DmU-pt4cGGK%qKg9=VR/!7lr=P^sH_qcn[F*HBHB(,3`mM>=$ -g>0&i>dHE)NtpL*UU8C6[,#RP:Wo_QT5X&/,8QHS>9_t\&p&8T?cp7Rj_93gSa;hgc=d@O9^bc,8KXm"Zni5&K`5Lj\Y\bafc'.@rr>_3=YoG]I%q47tOWE]dkT -7]K`DkR`)C:eBG\=b -Y=b:X8!EdA'VDae)Fo^,%[3rhK_mq%(\)qeBg:cOY77$V1U+[oY@:,M]qG>sC --FpT.G!G[mO2s3<=>P"_pNXJNTAP!*IdcoBJPOUf`Aek2LaQ&c\]Z[SHr7'kf\U$+ -?fGWl[ECL2o[Rbjn`it@-8P9Ro*lIp`_.U,Ao]qC^;2!CAUB'K.;7>7SA+P6&8/_nU>";W@.BQL%2Su( -?7\=STPqT=a0D6skWW'#q:7AKqctA?99T%gCugbf]W6h)8TDSe3Y6=X':UoM?ePZi[Au5goMrQ?U)r9Y -(%kXHY\5k"Wg/fAn/^CoENV,?RFBpI@!V=PaZ[g'r/(2Pi`JRU4Q:.54har6n[54U/&(_M42/-Wjr;lL -WB,euQJt2SHu1=,Um4F9Ig*FDCC>R[Lp&C!k:=SV%0RW<36"n:6Ri.mD,g9Ki"JUce%lW'X.CK;W>*<> -EL"-pqH+".KtU]*HMD;Kc'KOL:c[NN$R<7u?X'P3a(BFq=+'CFIPKk74oDgUpI7+Y`lQ&Y>8T.K5An;3 -!r?mEUYOhK'p9XtnF9Hl^[$F'eufp?Mc-r16=&Dp3*$@_k;Gu4qm'm-9:$aP1?i(n>g#_2OWW3G'n,/` -'p>180`8Ek3^GiHV_(+cEUXHAp]J^#CRN<"O/p"3DUpdX2d<^<\!ntQ>KEkV*e)mBkN0`9dge58Wc@-^&>Hp?@;Nf9@^`eX$XE^XZs;^+X:).IU#_SIbas'fs`Kd)=uGPHA_:s -1%1GaQ0[=eCQAh#36EN:qp/B_=pkJB1h3GHefct:&d/4H,RCM#q/JhPpi.mXGZaY9PmK#G5H\RPr/r/R -o'h'55AkIJ%VSss+8=`(aFmmgm/:sP3OsWQJc;>HqP+4?f'YBt4^+LC>+e)`T_nXQ0!n+V2!sU'$T(9e -LcU`)@p5O>=);'U&[alNj8DUIciWhulV=4(s+t"hZptM27glg;MctWp>AA_tj.,fR/^PRk!5FK"J4G'M -$bKmA9TF>QR5QW.k8,pkeTIO=WLXK#3<>O.c.D -gr_%$-1TD$:0Pj6_N?%*FiR!K(s`:+JWP&V_XJ0,Qh1lo?2"qql[?(M/,0D&h_9[EY%WbnEt:<:cL=@5 ->G^sc^dmn<(Y7o2Yb2\FPPp5$d= -4sWK67sf/=Y[-/0Mc/3l(QW5ALI<3b8)pEeHdnPq[pAarfiNOm>!2H=RMic/$r::LGR*oEO`q%8`k)>% -ILh?GM?=E@=/I22.;*7Bo\ea/'FPlA"t$W"mMFo*<"@JXF#Wano\] -@7g;o2_<4Tkm!2\0^1fEpPugnl5gF-V8#_`0KR$m_itDKpi>Y)flVikl3?03j8Lb@A)s^L+RF\E]DbL$uRPXk>Vnm1n`I#<(e_;=CacfG-Hb.RR\LfUng>2D.o;cLOP/#Kk -Gqt]H,&L&57\ps$Id&QfCmS,\=1;Y4X[eMfUq"IJAPcVj'bhM8e9aTiH#o.Rc[(WK9oqj$&#?1HIfO26Z8r5GDA/GI!HC=i;2@?1hba"RkIM,H>WAm -8i]q!Wm+@1+*+/MJWC-pkLl5!eio3U0geOcdW0-[Q8@\Zd8m%a>FY2&/o^`Jl;[eip[n.C*:WPAq3beSWemS.8VDD+/;50&6>PiqTDL -5-`;T?!\Bef>E(s5:lN<2a8k_\)S.1 -cKrG,KA0%EjK*-F0P;D:lut4G]aq*aaN,["0",1'1`V+%Weo*i=bA;VbsW+.UA\W3L4qP]u2,Ae=Br -T;#$''`*/_eldR\`STt]^-"N?)%^mMr'CaCHU\cF)gF"t`CoBo&kXV$s1[fLFTd.8>Xs`]ibBlt7d$3j -I)j(/8-3e43L"EHNk=`Hd5o\e/K&J(@MD:\#Pl4Q&M3,$)*;/"Zh%.*6_J2&[Q:D/W4:5HAK/JLR\(;A -m&HOkVQnFbV\\qh5c="MY*6Iof>c!& -pejDD%"bPbY0m_pi=BbjJf&:'DsqI9N4NA\#c2=$$PL=&5-HdtMY&<$L1S?q;Ti-AEQWD8<[qVK7cd]' -?0^W%W!!L`)fN=XSL$/1ht`t\4kI+P^f!?jfHaM=&bcZa11-9cCnT;*1S(J)WdU8JV$2sLF!8i7](!-4f9:&[kMT>S1/W_CR$8Z*o]ah8j#p-gl'Wsaf"^9_[eub:]`NtVL -C0K*/S.T?4euBOg_Yc,\^:]arZ!72C^$_3dcI)3l.-rY*4%7=7=cm:MYPhZ(aN*YD.pDpf]pA\edIOc[ -[9NWjB_`=jnm=1VrbK35s'!h@8%rY>rbK97s%]cZ^N6d[GE/!h0!FATOmR&^mh.4%8$kR.2`cT-0lTDVF,J#?uUUVGFOa(VX;:EHCp&%]jQ -?fSQlm8fWZrbFpFh[qGi@Trcn#B@q:c.DOLC7s\'*^.72C\dE5/V!?p\r>UZGKZUVJX/MH"tfj'?_HNJ -9aFrp6TN/AnFsn#17.Yu1$dS@#_V#i$knp84#jh\A+jNWY_,Q0@4+fu\I;u&?qMk[-$C0GSC_(NJpoe[ -"<#3q]Tch09p]9)D3+Rd0#R/cJ7KMg%_E.m#s(RR_M,V#bkDZDqs#7[#.#GiQ\R.Hb%N*3V=V3hqMA_7mV$DiH!OkDb$XEcF/\&%isJRmD!297I\QXQt$ -PQs7u\k@6*>_\C?P[Wi#Um1^/)9+5N?7JUOn=>@UA/OPt)0Si#)0Sk\WnUg6l'IK?RUaGnnb;ZW)2cTk -p=Ejm8)l?u4?T#p)j5:37=[eo)-Jf`0#L*9c;c0T;P\g&lN)k.W:Rub&nIA1"([eM>[dX+HM9!2CC-ik -&*ZfL&nIB*66U7V9"7HaFnro+"01GV$9>9R]f7bHTW2Rhb_]:u2WdCW&&&=bq+21CY.Du_[a)2R'X'hK -mT@!aE[H"(DPi.F?:4`F-g$1[m2ZR[0CY"@oj6SL$$e06_X>)/Md/kbmDg+)($WYAhEs%(Z9"&e!bVmj -@!OEpmX`"elmoac)MbsAB9_/a(O4-@V+9*3J?gK!?m"?=#'hlk0#R?gi2$j-0+NTQK@L46r(RFL!I/_k -<\M*%G^gs>)9snJ(O1d)h@$tOEP30_[(I1k_g?BCip.hnl8rVf2EbVC[[YX^%"=lG3>n*M<2D^LA0[MqTK]dX_tk;TJHY-DCa.CR:lio"oI98$XmCn -XFIKsWG0qX*ONrV]X_2`.@"J`j/nYB?S'drVKL^YlD?Q?]`*eZI]WR+jh5p$KNBAWH`ic -lmPqnHUn!^Se=Y<:5WCRI\kUdR0K.`%.u#u3CaUnA/>'g2.C`+hEE-!%J\]JPm4X'?buKWn(H^7/_GCW -AuFE"gO"CcqO]aUf\0X,ema+T/b!TXofo_Hh\JR$XbG+lHm,+^(g?^M:ET?(V>e'Yqq0)!YtI10s2;&L -LoCi0U1?,ud/E$hn^ipXMKO+2[7)m\-u[InWssB#V16gpG3CCCZ^T129H1?5h_hf-PD9CR`L!CP^H]rl#l?jVSb)Nj""R"DGe]^P/1WuJEuTlmcZ -8FPp?fmdJ\DnF_282j?a%/1Mm,$Hd8g0dJNlT6t=K.cuW)..j=rGiF?7#j.WeUD96I+ds@`9Oe3Ymf`p -%>\7.&5sP;Qdc4FIV`bOY5Of'"5Y#k]^LcK6>b(nZiL'Sg9e\X4\KWGjL(tG8hO0a7/gVJZgN<>M@`]* -N't3mg46*aXpt<[N6-15]2RR1SLtD5NZXs:;HKrF3RP58q39T`G7:DZaO.."@fLF8an!9=)-gf%r6'(Q -GWC$kQf;0<)`c[8@FTql6/ud$&$gX+S;Z?i>Aa&>m4hQKAOiZ$3UaVf*[,OMaia8qZ(8"ePNP$*:Yg'd -rVjo)ZX/laOmq@a]"]-Qgnd'Bi0J6L%mm/iA\$b0[aETGUi_]X[,IL>q6DuKAiJ(uqee%(I2H6lqdeSJ -DXZ%lA(J"u_-?<9Nj1-^qm\'cZ5sX<_k5"sB6YV=Vd9%3D4u_#dah0@#r4nPf#iX4X2BD>8Yp#D#6MS; -\Y\u'lp6pld#P=?8t@dip^VA#G'rE49jJ&^glUQVq0%VF0%T'7fs)SNAFD(B_nrE;]r9-D)V1eqZu>tq -5lVTlmAUSnMd>($LqGU+#Wt(@UKU=A6dCpi!WQ]GQ5Ob8DVNs>h),Z762n-Qi!J7p=d^!6rqq5 -,(P/-eTpUjH$(5P>>T6bh=Oriau@=@,=7BVK!N\A?_+`Vikbu+37>oqPW:p -JRn6I&s3qSPW%+#'69ULJBNFo``r\u#I@&BhZG7k3WT?nj098t:E7@ZXRV^TjQ"FR>E-t9UlWhQ-]Hto -9cWJZmpo&GrK^X6.EBK1M*AY+HdXCQCmc8M\VT8Z^-$=b."C0eB))7!Cu0'fJMF$5rCl/%Ymn)AM5&e! -)]I4Hc#/eu6dt./dOOgkm>asq86;&39U;iL%?Y\TJa)J0Vhjj<2+o\#lf,")%e+H;JVWXp&Pp&-&"i%4 -Wb8`TCYb:_^k^5Ig;L9gAnW*W.:dRSX*XrUrXC8'.if#L[%5T08%C<%r_>ADWU4kd1UT*'uZI]tMXR7G6XV;hS[_QhN7lA';?nYZQ51h<5KgZp%4@.J> -bJs&PjW^6d3L@g[>lBc*og9q07C2rK_(0\1O*t$r9(Qs@rgXng])2kQV)T$XfZTn[VO%eldVe3a=!l\P -14.HH[!C$@2Q]5$@:qSA\9Go!G0]e([7ZFKd9$`.@X]PKAXY#DRVrrH>[,,]pK-:&4M-a[3^F_T"r]VK -XWU7@W-3!C!L/7dK%#&p?..a*EhqSX,JP#TX+:6?m$cWEF2`1OA9A.=f\i3_Kk!&7MPOKg(0\kjUcqZI -UbCk*FkoshX*H[*Q<%])Z^gB1(XmQOY?BN<_2OQs'O>'!++):,fXc,QBBGt7>'_JPERE#cf)N[3>5[;i -Gsc$3OmD;.`2"tf,mu&@JO7@0U*]I'p1#9F83!]dV6uE8&(`5RnnMs##l9-3ngcQ:_@=biFb@*VWG.cI -LA6eDr=qEmCB#4WrP(SZK,HcJj^$'^fcm?K];*ZRc0c"Ib"/gjZ%>[$gY8j=91p(Dh;fn(i#DZ9SkYle -]/8e"X'q;In'M>VfjE'KfHc5K$HdFMgS>8(X?0!ZZUW\IO4Hb/^It>19CPd19j+o/V%*3";/),)6,>hD -T98-9S!!;AX,gUs-Esr?%,e;h/eH283)G2OEO'e3R -6`k7gI,M&nH2p*K^"?;nLXBX)(=\W!&KN@_#cQ7/dd=66S)oa&P[Upm&6T.nQ"#+dl/<:^\.]t?1knF;O3\3(rN"Y$9cSB5Mig. -DCpktHaHc)_2pRX8,A$fKTs^)-9LimO-l\3g*kcY>B.B_djiQ`Um>Qu-1CcC\1p`k;bg%VUL;>W`dTJ` -5,2%!i=KES3G466&T==AZelWn24etQ^G?`jPsUQ..p[sd8$]q![,!dt?(MYYpI4buHUKBLrUoj'dAU#J -%DS,1\=$B1paR\j*X2Bq##2bd)>2_*;i/n1Qq]sp%GDUM7e):i&E=uf)=pJ80KUc?Z?RpiWbaHYM%CDS -)))2s@0t"(:->3tprd5]:28DTV>R?f+]jn_kjOdk(lajlmZMV$FE$!(<)Vl5:+N)]n%HmS'sjj8t@Z -H,O#AE=SFu-D'8g)Q=NrDhs.rrbOLh-#U@/I4^D4U=U`!jSL`Bma([Om^#X&IFW/;0AiCaMj/^7hVDA. -[-u<\F+6(oWD/7h%_4fnZ@lnbcUCuQ>'nQ)".LpN)&K/ujlNbH8Qn]F7[]Lsa,#2s\>Tb\VKTgP5Pi -%F%)`b?`J%fOKkZ7+;OgVf%11=Tp`.le%cPTp_X&Z0\2I'iXJ?O[\T1.="Is4KaGPO"i&eXh.)6V7Ynq -NukV)_Eh)$@)Hd@?+EfY2n2Lam:j%-$89E`6!=`k\&c92>pNtm;:hbq`kJda'X,-Enb0KbVfZ;:Wf!i! -X(TO=Mc_h'o7oj(TMLC6>LYY.7`ugeWCKqh=3`$QMi$grb`q]X]N2JU?%X'#V;'ndd9?*[-,rP8XuqVf -,@*@jb::[`;-0SnZlR]FXr=@@dc+-">3.5I(MK!X[ejlS9a,=pXE/H0oCmciB."XiXP>AOAC)=dt-=^]5tHZo\"pC'(W2sSOFpg"/ZUr`qZqo -/g5+k(U^[9OASRs,a!&pO56HE,7TLI`X3V -(rDUR60b%l"H!H1%7f>W`-sgY]?1')T`7iL"(`rl,Ba!S3'0e3rV=ij`otO5l&=#ZO0,UQM@](I-gQ7k -ln+$u9.Z%ZJTX"k/=YChN-(gXb'5@U*M\:X-Q,;7V+!Qo`q]@$T[h;mfKF;\G%X.q]*UDcd-8L%mcLe1 -ij!\gbI%$KSr86efO*f5NlF`J,$UBDI`jCd-6(1n$EFbYDIL8HFh1gbgolX@]2g"$UUKHo05"f2GO:?h -Uuk#ki4H9Nmm*gnOJ[oR>#2d$Q/epnB'@WiHu[#_d>mdAc>hU(h]q#?%&Dmjm*sqP*$T_JV,asI^N@!Q -(AV2qcQi'1d+9L7RTAUT+,[5ZC1Y$[Xni\O_>@o)#(mWj5Bb=UO-X4Da=0Ib5IQ"I5IT:2+'W*++'Un^ -bjCh$rqVDlOW6t>Z&q>ZLdP:RaD+BsAWPD2hVTO11YkRjDKWjDm:puM4'*hhGknf]FU/akFEYBPG`?ZS>R.Pf>e3jVLp+#,?K?@D6@+S2=TG0'%uT:M'k -Fr+AQ5C]ZfKU#_03=hsY(:,c&O0e]Gl_a>oVrEYip3H[I`l[PQ6QS_mlkm&oT:M);e/-#8pX&//R7Xr! -ccSp":Me_$-[)+&Z8K4Qe4/r_C)h+9[8+H[bWQj)8pg%)+KOKR/`BID6"j>>*RY!)Z.jBd_fFW0=^%;p -HgaoRFRN2!G%ibaa;Ku6/_m;*.l0`]mNZ1EjG-t#8D2US)*?YIa*a%91:D?PV=g2g -VLFAVm^(*=7d]&S)\/pcjk:?F6cKVU]""=MQh*"I/F0Qc##(7*CMSk)Y(aKe@B:s/]n]3tk&")CLZHlk -F:TWDo$mq.p3_RU7I\t:(XI?jVe(g,i'#6\V:Z=QfeKKmP.SmR>+#? -doa6141Rbt\.QcNFlB1?CmWuhf$ucppPb7*feMd2I%A2HBd"qDL -#O(P=4(REb5!)7*d:Xi^/C\KSNS0o.dL=a?0!Zt&8Q,nXs8;ZVFrtpU3WSh1[gd-9^1s7Y\8,. -FoTY!lWucem&0C87RbF.M=QA*>2mK>==r<4c/>d6CUDRI)mDVdhHiJ.=/SNlS/)?0k2&cu57uMTKtf:L ->T_ifR)_!lgS&`N$ahuSR:uHq;YGRtc33b&%\rW`M>G1bO_9'a?Ko(XWIBADZmu`Z(9f?c$sUTp^K!Jt -W:0A3[5%u2Bsd6iGWe)I4o3+KZ6uM\cdQ*%p/?4tm7"D6=_q6>l[Vb;06M-6R%R*qfhdo3tHo%t.R -Rq9nZG$=T-DV5-Ln_jAm:OFTLAT>K;/c2OGM/iFBD>D:[dnABRQtY/Kq(ReDr'X=jC)7g8a'C&kZV8PO -9%05ga;Lgc+6WBrZ?n@T"[9YsfFLHE/pSO5[k2<+U\p9ek(o=>-'O/.p%7M1Wn.bjFi-qRh^IflNQ=7u -<]>",B!:guYTY(WOFj5d`K)RjnMUF*8SeTqLfpYX/B$ImJU&EDICT0@m3b]eS`(f1q3BQVoNl%-e^4oH4t(B -D0iRJs8*eX7$'eS=$&NV(NK@4nYlM\V9M%?B>W)aG;(-Q67]I\QAj/)tf2 -16/7AP]9q)"3NUta.&%B29NFUM?0aG*+dpN;9!IDT9D<\Vc,?gl.29I_:"l'auJ<@*h3jmip4SHX,S4r -b.+":N8#f(\Eu*-1o>W[lAR^Hpb&b/gGR-`Xo!Spj0ik*;9!J/Ai1IH)5W:Mo8B[smb``mU8/_$+pZpC -p4_@1m7\#P>tQWtI_rDg--7jr].>BqV6[9UlP-+oFRQLh"qC;Z$Y-E.F,$W?+4:f)K7(Lb@+S#i,J%3_ -)*GG)$l6k&@0Y7tc!^'t0?\6dQh*1,U5`chU`/AsPf18YP2q,0^!#F*QKWsE\<`8?o9GkI@K)>r.]pMEUiV#3Tfh3dd0gJMHql2;o0C0@aSe?dL.RipKA;moV<=Jc -rpf0ThpqbqO+7"uhuDkhrEK9tp](#Nr`8FdrVPLPr\+YQ)PBG/jB^5Q:EG*rk9@ -IfdU>Ld(~> +^>@n0,LDInI`YCbF7/ip*o=pr)%4Z1c-8\C\6YK@p44;EM@7=RZ_B:_Vl1fJGH+Ze9D+erqLXBm4iA8)ndht)O> +/?h]_"MY"6o*pL2p>hY7EIMAXG&W;';VHVm4"1iNf(K(=#[cIT`XkeS_-O``GGlp?8A,-sgt>QO^uM]'P<,MVB,]?sb,$/=.r8V4]ZW +AL8,(#e[]O[VV`?1b[#\?^52gmrKfK2_;<%-V5`ZF/6]jIZ=QSm,6uk=ftI^.Y57Im=o2, +Y<%q`\SU"*I30/iAX7U&_:;n2S)a.8a00us1q:Zg+#Lhi,Dm(TEhDV4[uo&*`F&;HYL"M$h_dWA2.o*j +fs!-!/sd<#C9oPIfY2)JO5;+Q6O&=iHSe1f(3S5'7HZd1.GaRdVCs@S/kbAon>.#W963"*QO1!i1`eLnX*c(]Z+Z?&QTfDEO`b>s4kr)9-[19c)E@30.SWiS; +^O1,CgLYYRh]#9dKbcr`)X=\"*.lY2#QKm\FT)BWGP9K/ZN^?j?W+/DUgegF`Vu$Xr?2or6Rn+_<#cFJ3$(t;]VOP38B'OMI+H=,rB6t9GY#gD +OG`'WWAq+,[K+ufJUN3Xr%7?H2sU(.f[fi-EJAiXRiYbf1&FNI4WZ]pTVXPPVAdVJoKiU]5D[&Vqohe% +5`5dNom.-0hFRjYFqDXj5)B4TlD-F443`Z1%pH=>C%Ce&h4`5W.#o/8OO3OTDEFtLr.@l-X8LInM2W9[7??(b1t(H[H#;+g5i +5F`*C241*sj^grCGA,8^_P/Bj0,o4\Prb1(?ZR8maC.G9qs,=L)e,!`g']ge`D9^\a'6^=K2(#1Ba4NY +U8T,Ra#VuU4[oC"Q0Mm,&"of'>]d4uq@2i]64bGf!&!'[dcJ62Mp`q8pj6Mc3@CS69c)*k3N'PYDR,Y` +n)3/AB2kO^m6jKR7MnqK+.C]Tg6C1+WiFhdY9EbGCMbnR));c`#DVGJ,oqBX;XF0i.CHDR/GZUIW:Nk&5WnA@K@[Sa?@6m5S7ZE5jo&f*B#[4atO5)ms)`f]T#Q73Ph5PbIGI$0gnC:t2aMZKAAdER`7`1$-)uII5O,$?L:u+CmOCc;NV3ltH +/`DDW@Q\/8F9LK`Y[)piQHoE)F7mLKZ.W+VNukRZD')Zjie[^%IhgsFf2^O)g-D!=aBf**N:5]*Q0l!\ +PRSeM"k`YZ%V9KZ+_o,j`:NFO[uL%^V1X2"L?=Ho-e]hYFSRDWCh[GMTm\3j>u%$c7d2[]Gm.A6.h(3F +n2E17_7^(9@`#^Da'R,JN`jWjmkU,_`jQ=\5&qV>2P-:?F?'V62-]85;ADpBCEoN0JVWuZ8ljbYWr]o4 +PQuj=NcgT;]oFl"L7P&THa_/p5D5Q`;*%_2A)5=`,H>>'A[?LNcVqR1hQ7L51qTu8,`^]]+oW7kA>7V( +A$:BiBXcEm(,L/;_NOMjs2M-i>-sb!2LFR`!5A;;2aZBRAj>0t@)^l+!L"OZ""RolFt\gT,2h@hX$Fo`c/>R?f]]oDo<;CiW?k' +Ip&usfWNoV?[qlQ1FU9bAXjmJ^P9I/j5V&]UbnnXJ)O:\Xn.L4rNb%PIaGNAkpGl8j6DKg-Q5eNLgl%, +e1#2?FLF^@[d.QFr9AecRQVQK6XkNS>pD3(m5Uma`r!cbe2u6t9p%$Q,Xma\Bs4efXIXfgi9U-OZYtm? +Vb*(cd;\^ap##OZdJSY6T*gK0O/I%_E3iDn:CgXSV^6KV-#FNX8e[g<1n@DE@2=R5Vtq_7<:,Q=oi5sd +H3?E3J&S=HnSuK=lT/OoPq*_/<9rI@oa?.>:PeUVYt2dralM_JHn#gTS>8ng9AB8Am>=^4@du^:V;ldV?sg_fL9s9&(#H2s$^K5&JSC +cLRSIAA9CY^'Bg5n(YN)`sQMJ9kiAZ07=7Id,\GAXHtgTDi0m)QHJBD*/q",hNXW,e]N&Pe_BM_eq4*#.6PT^\h'l"!Gp"X!:U9'9*qb[;_!s4 +d[P8RpNDbZdc=--F^!m$3HIs!=RKGSZ\CEX<]N/p)_+G9']UL;.fB8>f46;j0/Zekq\kjfIj7 +mral)Er'AI^mbtfOm&oFl0h"YU>7qMHJ:MGoVNU-Vn=IC0D)+m9!j3KOCLnQ;G#uc))Gn%lY>b?hS(^t +\R"DjC#g2->\RG+k!pr]0?Vrl%R]J'8$rM="$!X4n4X\^G'O=Ohj1nWed(qo?r\P"(7$LfqCtI@feQ^] +LAHRgBV+5TUfXYNdTOR)?.rkL+%cO$oI<:>A\3YC!qYU-RkfbnVN$FK5@\gl[6?!D-mOG=\Z+[N)hm.> +7b\orQQGToA*[4\ADKt[^ogVl7I-YsO)mjIL\BjPEn$o@[hC_n\@C_C//*Ik8]nA@"$2!4Hf,H-Q& +-q3t7V2[MW^B>oBH'q6l#+MJt7$f;nr/o'KClHc+lG]=qi_Ub7LCehKo$>F"iZNH6oa8hl(, +5Ks&c[Hlm5\Y.$sU/` +bNoV[>NPU4dpiV\l?ZW[p\3$eSuJnAG9'W-2VFXP=302UW96kbZlthB3jp1nj\V^D>+/u=fQXm]4^O&I +FL0J/f&#l*jK?J[\_QS/V'^CS4^SW.M]PKrUAlB\Rq+EL-)43&[]Zm@ +h+#_LabU/UP]F6=IO*K]+a<`Tb$DIAEjlMJHiq&\E,j>jT01tR@&$'r\_@eL5O4K0Pe5ufb2&P:+Er1, +<285k/;g?e]5;UOjefP`B3ViUjTo,Z[Ma,6YnGU9o^2m?@+1EgKb'h!L;Gd8h@PbN8.TJ!eX2fW/kuRY70k +m&1imkr$0+'so%1LHNs)Qh=je#$6g90-tko?>PJH`32iYI"de(8UX2Z`I-F^]Hd(mEMmN:\4+-#YEZeT +-\504,ha!u+b".\4(RGm+#0S-;\NHHf;YDGFG'BM7[&N9WXm4Z_U;iu<,12!dUPSUiADOZf59Y1(L%pr +MoIXBc(K7g\SH,i>Z)H7o2>BId&qcOge1iCYd`Ar,kqR6@0JApTkjDDpn=iR3-:UgHV9E9r:(h?IV1@W@JO$G?e1/(12hAf9%Iki +HK/&IK>5[bYIIs&EL4P"]<".OB'JY5C/5#]Z3]G#=./ +XLRlbEL2u2q4kICMAqM5]*nfQZS:Ak%r*/0C$1:Rga-Q"EL1:n"A`Or'cc*FOc.YedO)OngngQNGhckI +>!(!-e@BV(1BWZI9@ZMeV-8K&fW:)`mS^_`gnf=Qa@c,4EL.I*&>e%>1%#[%(ibo%;T)jA]FW/p9-M8[ +#&Z6l)f_45A&V`BjnVd/V>2B#MRsRP/+-=5Xgh$1^HROe"C2^c$(=<aOq0U^?'/P-khIENkq/]D"1h(.nk<6M(1AuTKs>JkqeQmea'u7r_gB*rp!&(^Xf/' +raC;5&MKndi`$9PhK7D/Ylp=+aTr5[oR0QKneG'G[4^fQ16/YH+I3U$aD*dG=0cT)M(rC +3A8?)9bM]_eaJ(JX%/1Xtl%Ckld7MpkI6Ti?]q?&Pf5/JN5,SN&K=2XiA[h=A"?r^d +)iHWTVR=$LWL7!=@[8Ob0qQY"gDaLcq,gEcS_+RE&Ru!5ANdsB[7%4ShQ]/:PrSms^NT+h(b?mZNIL1a +GW;M^i;^SugFpcRQaZH&gou1oq;mQrL_p'`_Ur):+\NhrDn;`+?_=pmEBHK$G6N2W*1;\22B=K`U9>rk +'XG;UEV`_i'G6medV[N.7JtoAigj!3.&+iX$%]9?6d4d&?ZXNM^Th7SX53-Kf^+Wi0fGO"]OS9"GL]3g +fp3R.d8(rFp6[P5=?:D>P##Np!T@R)k +$sRGsS7\E0')`%_=Th3p9hR?>+LK01l#/jFc9n`0#0I]hL8@O11?^?*;&P4th5O,j6?=+m$S`@4RPu +QdhD6r<0]:epu.:;D_P48NEA@TV2&?H8G@QVl[V0Ft`f(?Zl="Hdh!>Q%R,JhGL9nh:f\h?lf,m"m]LSXkge_Q.@NnUgIMB +nsdl?+uIVPiR!<<2&"kJ'/locq_,.DZ(p-hhl?2ebBdpHgG(+3:t7?lb41['R!L@*O4d8QLumf+BC +s,rPi_MKeZlQXF!"8qPr$a]gOX8X46'`[^;rM-8-3QNaNQC%\J>:16521MhNMhsfP1..[cDL)9=' +WW!8ao.@7sq\re%"'b_Piu2nVbRmk"2R!eHrYtY1!dDf4NL:,if$[Q9/,d>nZJpePbnp0SSnIES2F_9b ++PsauSgo3Z_Ca^MDE5dh?cknHpJ/GfhC.Fu<`:Yn#iHFNQ;@<&H7/&cEsbf=N;h +raUrGh2%8aW?EK\-EhGip"(YqY)='pYd1;Tda'-sQN@m&!eLc5gC_r&6!i!q/<$r]Fn=@:>J>HWc?.FCQ^7D?.A(jHG7\TA)& +IY"X^9&Z\M*L,mLn')h1=k)+HT@(dI7G%Gm#Ea9@:\5fC*8!d6f2M)M1IJP$J)Q]<02abSkCUYYRG=;t +OIF>+@p8-<-Hh]"SV,]tK68U\d1>1!f>X0<`OY^1"gECmg;DKeDlcTG%aS7p>@Ih7eu(dEAO(X&^P\'o +39\u8'2NH:s*i2toAuX"gB-O-\SQftl*h1i*BZ(V?4NUJ)[BqdH0TEX,\pugQnr185%0r0X+#Q,O8coa +D&ZkQ?T2i&KK#EO%2J(5b7K;+['beRU_Z6[(4.X/@5f*\r'd-DQY'.)RO595^s)d+W_7eFkX1VA1UM$O +#9SUkYube.QbuXT#"3qW["!\(@(.cU:3po%`l8E:_R(J)3e;=m!J7]$^DmK\g&;r&*OfR;PSnm$34e+NL1;P4=;*%aOea90J=s*_6E(1"77LNrtElPE6 +:3"[eS%PZn:$A>DdOK@/nr45[7&nci-JK@V2$+:[ld +/Who^qJ3L:rW3SC_;E#4>9Z][@?q1H+0n;=rsqb5Bs2&p\(5%aYPBQJ7[.f8Iq+e?H!;ISF2j>n-'K8/ +6C0!MCbDCbp'>KfF[g%qh7<):K&*@?Cj`kB"u.^fLg#=r=oc=%(9m\\QPS4Z2TS5L$9=1HKbi=)('[AY +jh>G[NSU@FJ\>_cQkn>44Z&`3$8(2'>2Z7k[(tP*lDOl:YI1X,<]dR%JM$7iB(V?lJE(T#YUAO]@[+W4C>N*DU]kq>rGPkI94Ld+OOC98*2e0o +C4RtY!8^hB)Unh8;Cb^YGk)lGIFn47?[moM?Kr;KF,gY\Ya__@Au)H#"a4B;e,mRk"h&5CdfS8M9)g;d +r6CG(@XX!OKWG-pc]fEL9hAfg6I]"2ii9"N]7%3bq,eQE/N;5g^(sN;+k1G(>e^,QJ +W7%@/aYm)$)*#0@k#L+\\*THH;I/ZjBeG^;X%/Fq.5fWZ[MM]%piq&WSJ!Thi2^KL1dC7;GEnc$P-tF_3VMriP3\Ns&V4W>fo1gf8(K +0,HD_,sua1M?C@JNH$W-EY>&d^->!?::.gGcbR)H6CJEPQKp##p4"F^W<+l>''c2eG09.`h$:-a'Jn+g +VnmPq?'?>:))KY7rbdmH/Y33)F3,D";qCIr[fu6:nuII&`5/h5#C>`7htpcFX"1T^T8XE5A\MiFRE=u, +m[J%D1Mu\\*F0XS#S*%[-Ac&fO't"*0$:W*gND4gG=uq*H10ifkC(G%QSu_h>,]q +PMb77lN&Z+j@Ld(5I.Nk`[<]JIUVS0X(D;[PQCJM:bLJh@$Gn+OFH8LG6I.Zc?Me,rQ%"BLOi@V-MD1J +*oS-E](3`_DqgMPWBdZl.bgNJT/"BOG&PC6ff`q-^*>l5klpg0jeI3BeGngKXFi=[I[([teM?(Dr7GF" +SCL-QiO1r@,87U0=d\Z,BA&qEj?Ue,b9JB!o''!Z*lAqo_1DEuJU")WIcNj@C6rtgOFC_[#lMaSraFB_ +5s#0tADubeVqoSc;TIZ/V`0tWQQF&VPG.(hdpJ>?l9P9QY\\2S#%,V+V.[DiSAY;Rlb!2Sb/2+eY479X%ZO#L,r-EZFWK:0e:cae)X)_+*=i,QF2fO$GE"mNMtl_r[!I_kGLaC*)`Nl +a)3I*q&=Ke#G^1b3cPZtnd2kS6@'eRQXlTAZ?NH=VQSk!U/Nh4U0!bnc6tt%R>]8?ra?j$flrmg0$/Le +hnS?=GKf5A.WCoRDIM+GA(CU'SFZZi^AWsg?F($"s2A"Fna\J4]]f4-;trkNg>70"qBmG%hT@l&/*&'\58/ICCPf0hXU2qE6@ +>eJ%3m9JGRbXIZ>hX(PcfI%n.j+8#7m%ouQo]BPe=$[>)e)R1:R7@O$_ZEtHgS];pM[VqS-6)_\g_I::do-Ro,p&43#ZqGtI]\M\>4KGSIV`/^,CS+9#+/tJ>D?LUV:RaH"e +UmlcI1Sc'(g#\1!,;s$@mm$Sr+A/Asm4Y&>s.,pbfmI*e<16*!Y59dhP#LaI +hCRWLX1eXdqre>2[Z%WOIsGo-=RAhKK1:dfFNHP];#F_?;!_<5m(]b!](fQq/q.@`kJIs/2fH9LA1t-r +-/0P:*L3M5gLe@/QWK(J=qt?jHQVCCjR#:Fc5lPs$OYt`V&ONuhO6I7+MOZ^i\Z9?R:"LXh!-_5i5?KI +:E0gEY[4OK?L3C5!p8Ps4#IX,[E0,Ro2i_5t`)s>$e`EXq^97?' +4^*eW6@XR#HotgbguDmT%esTcIL`ibJA3*p$`O*FWdhEiq,+=2%7c>3:??BPNEP-_'dg&cNX36?M,4"k +9_-5\q`Sp-cm7a$`r2$bA7H$^(?;b&Ws>ge-KH$IZFWbOAu1Jo.>B +!@t&JJR<(b,H=00M&6`e2Vc=[c,\52'I2eo.]G8$AaFQ"BXdI]TOD9^+C,?94W-Z4fJ,OULbacu:SLn)@;&`X6Jt" +NQ^NCp7k9H'"bM_rP1G_&!HW?Gqn-0A_YW9=:(Jq!!Qr70m\i3a$8/4iChA\^Vs-`[sL;$[VNu3q":!5 +@?-ke#d$g8>T&e/NiO4&FkLJbSQUfs0oPGIl9sutQV-ith?@IqVhc$J\$diVCIjW7)f:Y3)3:3LcQ,4= +`F2-GV]2hX2dR?1ZP)92&Wq5\X4U5G[Ws,ndb6B3@MOGPZ,T&@X0b#$f3_N`[R@-*N?I(jF.L2^pm=:c +S6]n]@a8H$lc$J/]ClKD/J8!$/J5GTQH553aORT7AP[\V,!/Xg;D&Ls7T^r$p!.e(b_Y8C[UuUj'Fc>B +!ms8AGp%.I9.0NM^XV[Qm1.h\4edMW,9lk3/85KU48^DM-.T/FA,'&oSZ530D="HA^*PF.k/bU99:$>T +eH_B7eLdCh)P8C*+c2P/rXBY=Or^+!pK;mX`:`3_-,)<%?gH8h96TVGr^`lq+Z22'N9'T+0%jAK#E*$3 +>kM^p&NRgeWNEX?GqIG33+F=I\=:7388Dn@Vld\35k1si`K4ZC&\9B +CM7Ytq7r@NY*hn>CtrV&Nci&4F2nI,LYGJ,dO^&n+05/Y#8/B>\V2Wn2mt3>kh>pjgaXQR?LZ-cr-^0)jjg +A+V#iVJ6S9,7sAi^Fa**_m6u=QP"F/QC(1t3Q='#+u.le&e5gC[2lO,&$5dMQ@1%fqhbsc['(=+iV(g< +c0[%$O3kUMI#Qf]lA+^V@EkA.#B#)E/n)>>`@(o!Y>,eLG5I.O%CEph7)!cj%C?W0@IjT;3F:k3c,\H1J]"E3+r&@E9B+A(0X\8daX_E$?3,8Olqm2PGcp[ +RR31r_@8<=?T+LlbKIk'Aq$$#`#]AKV8ttc@p`=%OE[gQ4N,e_s%6Ms#t5"cWa7[%EZ?0qh:CA^CYm/mRW+ +&C-*[/"J'$(*eQX*EQ^_qSAP;)e3TN^[/3?5`?9<=)$&cX$Ac^ouI73!tq=BG0l,FcmKtVj%5:P/9/`S +e:1uc(GQ9VfSf-l(l4$8'=-`J#^_\2+LDq&oq7^=jFZ-ELK='t*R\:6#^$k*187Q.K,GQUn+lq"0;L@- +\)/cM[Vrob:-AHW3U4BLIsLM]5<]/k/,%m#'=>dbl>,0qK,ca7T:YiAe?MM +=EA1^X:S^"W1GN"hn`OZV[o_AOhVk*5F&EZBf&ge9=V`5)l+sY\/TH[DVYk5?GWbR8CL%4[pJRI +aZHnhqK';.8o_1IP5:-_BE&43,NntYT3P7e6l4]k12`)a[f4V1[1`i6\PVo%4V%BkL(mfc&6JZcM_B +87e+++_k8\j-:S,.&a)gNBB!W#Q@"\\*Gp`j&nADh(Q_AoWj$Z3?aiMp'A<6QgZA6+Pql^L9a2RZ77YN_ +p,dT,R>"I8^KomH)r;n,p4;=VadKF,aS(o@g>q0+V.ltHQM499cC("^/`&;;"f7l=^!JmQKln!nCKWBT +DY.+EKr"UObj7X@?u?j&&\M,ApJ^:M[,LHmYOTPb)%@`H^`.unJ9(W(ar"[SPFOnH]e#T"7&P0m-GE7u +:8`bbmY-T3^S27[1qM>bE&urPT[#*\Rr93JQ9S9EY_S&c3G8!k)?_jcDjc,Y&"15+)bj)/>VXBTCsNYJGh3kbC9S:J?ci>2:(C4Bpn*mr!$mh+Y1I?2HOE?4$sK.#uuHaMTd>3\@XDpU.;Y7XmF +m]+l&9G\:`8K?a0]TnZVX$jSFPPcnU&j6lZTteF-(lal*l7J3dUk;<=;Mh]F'J.>HGL%(MZ52,0fY%h@ +SlC-.UeE(Gne)[O3AS)+rjYs2gN-q-m;VeWGP\0)DjNr?YHReW?_NO9rkaN]e#j`toS%3;kG]EFGkF9Sh"9sWoHJ*Pp;)RBGiM!_,%JW>6uRJ'Dm/&* +ho@1iHM;+.21@5cXOLOAZP+F)q6#4&UIXgp]Q#`5C$hCkm*2fgbnnBJjnZjB*r>oVpBYasl6@kEfjeHg +&`:]TiG/S^28]%_I=(q,h/iLdDMt+QQrnXPMdn2^!S?$ +mn^.BnUml^P=6n`%3EQ8"b1f,_md+5otktWJ!eeRO8*;gq[:>jgQ#0P?Z:#:j!.!)c#4kbdNpr)^qd]iS*'bpKZ[B.mQ,EnLb(>I"WU`d_T`jaI8rP6=Y43?S+sT#(ObmZY[*r:AR\! +[(/t9[R/5FY*(`>(:'BSBr5.ibhAWVG&+Y'MYqP\&WugS(jX?t913!b?)Ie=.Oq,^U>Sc'YFFNE=g=*G +%9WM-boVK)KUQPG0RQ>"_n8>$_H7KCZVQ2G3g'j<%jMc0g8*nF_r/i9EP6K6K`qLa\b26;&Xr,!q&)pe@,DHfTB\21+fioOYBLrfHd9HN@Mal@3hl+iX^7AW_=#6cdW4t,h] +NZ4^]+ackfLY%i%DO\"nbrf<\(0;'d#_:#or$AnUa^QY/b)fQL,NnSkHU<(fH!:j@!Nci7%)mEglWRG^ +1'Xe^;a)#T'DH:]ig3in6^"0NSEH%8.mQ4Gd2*L8NV7E<`B12V#2_XaBEH\5A(A'4jAh?Kr\iafCS45m=t1=8o.m]dbrGA/7>=bbi]^>o#ngZh`60^l[Q@c++i=m8ZH1-7VLq.i2T^:5C[foag2/J>2\>i$>r]*kMf2uo^puYc;WDMF`p`K;1tVO0 +4YBoXhLAm^aHGb`RAH=!R.QZ%0$*?XOS]Zro0W*9G?G<;V9Mg][mRM\u=PF?Ar+5Croh^%L' +aD#(nJ-p>],igsK4"s(h,7A8FNG;up/^STa5G-$^>$aTM4l-G@V7ks:=iT2SXfG52V:gsq*n3?bJ2$OV +=FS)ULTg_^Q>YKdiFmsY'/1mP7e.eACU$D1^@5LQdX>TcGWds!6L":l +mq;aP1HVTECptHZf:?0jG!jof/C[k9s#(fr=[A@IMH`.*IuUNPQaWr"8uAd`&EBWuL!7l5R[6,UAa':":#\(Yt-ckb2BRa?P_MaYailA.<@p6<0la-3Tl;Ka?erL"aJ3!&DZ].iV8c^Ec&^>I**Y&Z>mfi%=ZX[^ +/mk!B@ZXKD`]7rh34>.hVThBc]+^PBhBM?PCWBa%k`p:7TX27'W_:Y(+8rJh*ZFK-*5iF/.g4X-50P0i +&:Q(2h>V:[^.N76.l*Eg0!;$-lI4H]R#aGa,NR1!SgWu'A:hgT27Dk-K4E1^Cu +qI-,>*p7+YK86Z^cis):^^a +hcqOAkRA0,0>!?b\OhT08fRcDnJY2>q?D"Cpb!!G.i3]o#Piek^jBji>/AsDkIY$`lS#uO&uA^$LcZhO +(;6W7baLc+P,8\WC&gOYnU5.]jmL'6/1)U!F+3B?+_4=!9BW*MpR*T[9pAdl#]Zs3.\0Vo+mJluhW9+7 +I7^oFO)qHPHLW_#_SFrm1F'X9GFkqpQ&X\kd-KPis2$WJ(Lge43dYGS\;mag@*MSeN(VbWpuVk6\%Qb- +\Fu>-?q<1(IuA)?hPj$D^br@h"12cHA8Pt]'RR/0F/@N[GTp^Y-bdTmaBMF[oZQ!+hWh(Xb!68-Dd_]"%>'(P6,-"jY6'mXRaL$B]K7E[Q8"g&U +FH,d3MJ#;&XTQushWM]N0?kpu\N1CN6)cqh87D$l)5QM+)79bFoR,3Hi,L9,*5;>>MlZk_,ES8cZCQur +16r#l3t>tTqJWJ3HuYq=/Y;iFGDj7<$Wd:emhSt68STh8c64J7E3t/lDlie:Mp]R7q_-9r`DS!,=oXKM +5-Bu:^B/a(Oj!GWm*]dRV_eMdD;a'R_3-XT7-iD.9T9cd +]#EQ,kb/HIIotETaL)'<2?*-qQGbN@W;jl?%IX&]/1']7#$\\f9)nk<%`$Ks]5isJj5.L[oW/l[["7%Y +EHuK3AM.i(,p?,HWO\-a7+SPRB1];u:B(T:YW$2iY>&iY*45f_/!%h^l/[j(EGG[MK%e$.9Y%gn,G`*) +]e!mSpigSZV4j-)VatT,`Y-I&05U\'g45RE!5FaVfTO\4#$^dE?H]RdO6!uc1V'm/Sq6\MJnkJ3TlOk" +/P/$k&#=n;\G:pK(MGa%D6P=CQ+:L^2V?GjHMp,fGp/6-OS9`GCP1G14"pbpCBJNbBEM.eg0e"M$LeCU +oGP'Qh1'YPq7^U_SZsuJMD`*>b(dT3>*18O0J(U"\,F/_s6QrRl,2BX2k78#m:FgoU.Tt'<:7aq\#<*h +m@jPuXT$k41g,gTPLF'6@H*5.TEiTJEa#ZI[*lAO%@*V^0`h<9j0GY1sCc^fLkO'8pF->0X5 +_fk^/-obboUZ!jd;RYn5i)`Jrp.,n1kf^Z=An:D=:0@CO-`;3cml)5eL;/5uWVkQsF4>Fr1Q`lY>=Kfd +dW7.Yc1FeTE4RH7ejN?G0@m8tp(XlEo4]tc/b_.3XW_;">t/O=SHTAG!,hGH%'Ae7m2!Aoc*&nG21$T@ +UVEu+3?"3(Ukh/F*AYr`73uQ4,m=Z`+hZrqCBeh(7dqo^DV6(1Z*)hO?B%=/GuJ_2+2:4Q%f2O43\^TH +9%dkVP!/M^7j^1`(4BeULn*JCP`>SejX/`)Bu7&GRV^+^8/(VsGBYn_PqL3sr"=)W.5`Q^#-!i^7lPkp +/P#:9ID!L(q986FbhqrpF$RA_BmPY`=_KD`C54c/A`'7SJrfShn&&doHU!a,^9rc>8"43>cMZfG+7jAV +D"nkjM7ETP=JbX*A83!gcEZOsV[#B!ZfU!bP(;K^c-dN%O]]M2;Kh8`dMC8OFH*]O(R7m4?4ucYnK@:,ZVX<)D,mislMB(FtZ0qU4D\$c:>DESJ*Ok`mS"1<3L7RhR +Y#gq!mYSV@^A#XV27GC/Ki+)4'lp_R*BhaY)$_=/MQESD-qfXN:f=7_/\`dR4`uh6V>1sU,7nmNkB#8j +2kC\d7B2Xn85UPI6%MD.8Y&N'OZ'J^]ke+TAEHW>8LlFh +'\F$SXl?B.%ldp!G0N1"^/*(a*%#j>MnS$N[ueM[nOO/E$OrFB[MKie%blmY/nt'A-!.KM+'7^&[*6^; +F!6=Df972oY#9:b05=@t4Xg2IqEDTH*+hju?fl.5*+giPL[>6[QaGC&L)bqD_Fs33[G&5EQQ3H#2I$a\ +6I"(o+IC!aPf*DFjflFZEQMY7#3@P<0-9D`$OFM'QVQ*Pgo$KC%[2X3_csa[FeMGNab%c@6bLYLkdFKQ +Yu5??3@_EDEbZ+h-)Z(nA\/PK23QF5fIAljm;\Qi1Mi>/SH$>&Ei_-mg3h!hNQYAE8UR/Bq@pA1:AqIu +EbZ+h"W:14Zs:%3[T>e)NT?NNNC3IHelBoNBW;E/$lMV?T!d;1KU,,q3fqt@6oc=kp33j_g/=Be+[s(``CmBYDoulp1>I+AV#GRTHiZo@[.ATC%!Z8btTYG,J`_Ss"s@a?UIn\29J +HRMd5N;-3PpMPR,`/4jFL(/&,\)LHo6=*bbLK]sqqO@R+`8%ih+QX9V9t4AlI2JgPnOmFJu7dXB\[XT$9 +\'uY99UA>[C]6#_Lqq"L421hU^=m"!g@3MU[8"?sE%'OagRg!pl(&)a&$>.t[KP*5XKM!/UU984rL'E] +ohG(ie``2?Y+6?,;kjP'XGch8N/e@NFsuC>^,&I[g+Md1m'bJPX?l&1NCMSZbnF^a`%2q?n/VW.NjstQ +fbWHp(^$UZ#6hkIHq/dp8Y#2/Yp*Mg+c8r!laJtVV`A%I@2a-1U'iLuo3l,,QQ)3m\pT:1 +#CkK4>X$2tqt8[-CO#ENQt;K)jDV`2./mO8;&8UDk:c):SAqAXi?bDd"t*0D3*nmlUn%%nB)>\'HoYX: +nV+&aYG8t"e+AY*qd5#.hHi6F3_HLN_Ah=*)BpRU0jo>ljGR01-h;cB&r5*ccHtm--?4ds.5Qf:P2l`k +:=gFm-?6WR!&L$p*+:"1qBBcQWqUC)iq03gK%p-X4rJ)YEOrGo;rTcCXP6,%=9RV0EVea3I3rsF13K)) +l$30H,n]s'=W+gauUl<_M3C0I..m +6A[K2Fg+FW8!H!KOuVKJ+4a9%,nBM(8`KA:).IBe03WF]3*gHF;<%46S>Ob(&uTs-PgsJO6K3+O4r[5j +G?`ZfIn?0eWUijD=QK$!EO_#WEVcIgV>cN[iq1Jsg/'9BCQO;8Q8k*9PW2nUPW6."Tu?3&b]AETdCZc1 +&%mqA=-fF[0<_AS$b&MgXDing3Lh0=&2bSC^Jj<`Zl%5$kkSWBbu$*6fe-@@_EMW2^cenFXJooS<476c +gTGBT8h[;@PisVF^;aO[('jflhr6,dq48+!U%Q4JU^LneU?Fg.e7seh8QMFf@0!]9UqRcV&kHB +T3A.\Fg7gg_"Kfit0Udk<:;HhmHI\D6cDaYYZY>X"O)8!+%.u3t1MRp7IAn)?EEHK3d<4@bqh_.$Q +Eh-).^)Q(i$+BlRR5'*p@jGVT?P,5oETj"E_$O"dJ-J0t4*&W#oZ&M.C!t;4"$Iq+f\4U'qUslI)(>%# +*c^"t2e,d4*Znp`8Zs3'Rkkgjlcc`W9[?im0[W>R,p%3!nr5"JJuKIchPdg5`Y=sd05CNCC<%N#phgfj +Sb?'P-#&fhjCr7?4S4O?N#"/f&ou0-F?8At4kum51N?-tL#:mdDY-co$^[PN_8Cl8/!U'9MgB:m(QEQB +V2_<'"/-6pA!J&$p/ULYbfb)!0r4Nkl[Xe$&[onChnX'qN+c56Cn%%sBQrF2W@kkFB67F5FcGNVp,-Jn +V0b[tW8+K#S*eX2J="^0(cG"&IjG?j]f5GC9^AH\*\!2`h=+S*Wt"l2fqX;rpt/prN\+V7jk`EXmD!=5 +JkD2"TO:.J.d>]nj=dW)7t5YM6#m\3GF=$ +Ecf]UP/#5=32P(ue"YZugIqIEj>JL/Dd;E+2epr^!btP?F4Wbq"\=@#M"n?&tZ\P_Hg_UXZHpa`]'#ih9Z6 +baeC]`gbN-^=%8l>)6gG`80nWAOF-[,4$F`s.7e/k'se.DQPV]U.)=-'N&\kE+Lk6,/tXdbhm8H4"$*^*m_0a8/1d@I8T +4s>e;)+SW7Mkf>q4,s5l/Ib(,FfQ[uJs'q/g\gd0jt,7\Ttb18*S89AN#oAIotb3">n4XEmF$W3Q>MbO +`d>A1#qd7a7XA,9(g\??Lcm8d1E$tnLbUhn)hmh)=ED+]:4DqaiZ&t3\[$LYR(TWSCa9`4ksL:.YJ`fB +0^a*ebJWe%@TK4b)DE>"M(s-K!AL;W& +q2"m#VG58thegmXb!/^WY=!7tadXPJnHF_'=c-3DU?g+1V8544DRdck2S,NYWOa; +a#a,5fH^+PqHI4N\%e^;)_^$;-f2r*4<\'sdG$MoAopW\f_tat%;**9/JgCoQhLSAq@tNBo`IjR,."j] +$K@?j=0$4!2q,.Up7>uMqtZfidF)l$iY[.k+$O\kY>IbREFK]?a*dCEAg`Z(m\]d.U9Rb^pMCYH:0Wd& +iG,iEBD4ldmoP\^aZGOU;-I[2g(4Q.%5b^.KeeNA@=+?uE);5&Iq(QI+i]CTdc)Uas1iY&;J[.-#^\hreCaTD?XFoAVHD@D>Ea/O/ +s3<4rSY)5JcF;oMdr"pLoX+PJ^+K@2q)OjEr*fE)F^+h\a6T&+,?k)b[/B_m>5f<-HWg>`h3S)p\)kBu +e+2D\C&.4F>Q0rcbUrj#SsKDZ\)kBjD>?9I*X@!ork$sO^YSe"/:W$@h(=,74*LS7p=&TQke)#nQN(,a +bC@dknmD!sr%6OrAbcF`jhKqpq6,'JrbFYKs0ZafT08u=[J&)?[jZ;N[A[iLr-*Tnro?^Ur]B#'^SBjP +HWg?@W%@t>T9fN&\)kBRD>BsA@JQ+Vh""h<[erE-=oN2#/Ur[hGKYJqg[]PVbFb/fru/B.^LVUX[/FS3 +YB=(KjEHqU45H2e=#OUuH/C3'8d/DtPSge*Du%6ej2TKsTr@W0r#L#Sk-Ft*ZTdtg6Tup)[X/`?icL;0 +fqKWKq[Pc]PK%J8-aWM'XWZqK^1`[N\YSd^(n"rlbgNnaRA_XtBWB3HCf9ObKBFDrY(_tppc!TcNuN;] +i``4e`Y\5e/u1pE2X2j:FCIWq.$G>;eR1"BpY"7ckup`fnDPLI*!@ +,NebP>Chhg%??b797qQ0`g;"1M".n)pen^oT4DG>I$K$Gc5gi`.uLgDna/Rr^@JN&Elno>O4^Q?rVhdf +0l*jS@'d@9F4M +*,YB+=S8H.Cu3]U1!A=O0B@0uHaW@[Wc*0t=6pBG3K?:f)-2%@i6!5s@P,YVN>6:E.?_PmUE`tQ.+q25 +)")cQ\orkQjkXfg<@[;Qhj$fOk6r-h9'-DOZ0KJi/'Ei$bDRmoM^uu//'Ei$Mf\!=T#&uHB((X.3@8cX +\ZB!Ss+;iF\26o4o?n]5nnsjpANoWQ\o&JW,1S:9rgmJ9AX/i2=4SQYKb4@k78X#DDm%#cJh,58E$`9W +>f[[epu]_b3I9?8_Qppto,[JN1/0[7nPr69ha&%//Nu7mF\CF/\lMtdYQo5T@?:G]a``[/Fg><0>,['Y +B/ufhjRKLQ!q@T=1$n@]ejoFXY$+,q^CQ(Mf^&_4]&!=`RSDYdW3X]WJ*3,R9d7'bhqGYTR6DhO?"pP5 +@"7@*jaS4.K)I:Fr5(&/$EYDpL"kLq,tTuB]=EWDqHkoQS"oT'a\l?RKqcc4:Krt?trm(6"I$AMp--?F4%qEt?p31D2*a%G=8$Ka^@=RU0YeL]@TQ=:kchZqb1g6u)X\K5OH +Ej*Wp7B+AUWH>1_`4D<6/ZAaI4#^7OFjG2`j\pc?U?6:Koh2iJkJD=4b1fQ52VZpLqs89[14'j;.]=Bp/JfaV0GH +dJ%MJi`o+l0@+%]ipE4?AJT2k\&I*-W__$:4m7(B(8R\Z(-FIKD:/5t>7e>^d9f"FDoDX0YOOKQ)N:k^ +Or.QfBJmLRTAi7Q^.(q.4B=C*7\d&QDg77k02'C:Na+WG>.E8Mf_5ob +i7N3`s8!_"\"S)/4l1=CSTu3FMrD*o8hGk::be1TS[rG0G\K;SFAUkLfZlrl';Z/c_?^&;U`oTZ&b"%Q +GbOO9O\QTZBnh(i7oPB&q9G%YCkr9YBVO!Gg@3@Ea;Ku7=gn0F)s^a`q_5Ucp-*/`h1j[r#L=JU3k&bI +kk6b%%^I,gnVqk8p;Zn'jZ("[kb3^tZu\$ZJkr*]EeY[;QOWS,?S,HMDN,tdK+!XIfj\n"1-PD07u +Ys.6dU'Jq1_ZUlqpmN4--FZ@/Hle&Y\,h"]Z6%`Q`Bb04Z1k4n0Bt=*L@o6_mH?bGkhiO@$Uf'I=HMK- +jK3\1d_S0Acp96d%lmRC#1gtOCUMT_DVNs>h),Yp$1G\g[LR3?`u)6f:#PJPF1rRWI9+M$nUbmocN>5) +r(aiUc,W2X;8>gAX+fOk]R49)DT8%965bp"NmjECo?/q2B16:l2V(a*f/fo>FfS>V7do/dK&.ubVtLO9 +6ag/7p;*Kt_>E7*__Qa)ji`cHHeVI>9s:2VbuL/\EJP,aB#CSI`i:+4-*)Zh!mk`T%AI%ej&"a +phH71e?F;Q-:a(D2jldGHtbd44SBW.KA"$"?+0Y(#MJthQEQLnlInGE921I+Su0CEe!((k_;Er0f1i-3 ++Fb@gSTt3nB%C#Y*Oj%u;.6%]gZnnfl],VHI?]IXnBJ?B:V4:u@,/lO2BgLX>c!F&m;1"P^QQdT5\$qu +l\"Fe]b3]^jnf:s`ae`2)dhV-f(dI,oC6D;Xi>""gW*t#"@fW)*@(IVp,6pOV&!%Tna5p=7?-gXglke7 +6&">LJN,jPi-Dq*oc&oK_r&3`?]d?n@#R>IZ.%#%Nr>*X#IOShdPN:ad%_QR<[g!b"ZMWG2uhTlr:GS_ +72b!.8S7!J1E.P"b3-=lE..d/p3BF8k6K\p`q_nQ>Y.^R'Ep`anX`8UrucAqJf"IJ+2MYr"t)$nnRSt1 +nQqi].:dSGL@Wd/\s?nR&!erQ-=\(TIaLI#OI_OH%:tqMNq"Yumt$,/o4pedpPY0UV_?EG-AC).AG/(j +qOK>r(fP'P;`9+d/-QuB$Tl4=eZMe3V/OT7JQ%DMEQ9ib2Iq=.)4Yqqq4:s=Gc)NUGh)Z1ONI`U)Tqjt +Cf7fic3N:#$0o:q/a)M$J[=e>/u3&-,rUtsg$BL/aoD"#o!nH'/sO55Yk,)nR4!bM?E=O;0l9f8\Ya;q +V,anLLMAZR\;+Pm]sdSHDV:i#PubsU^,oI23LAot23\(,r2r@1ic^effD3#<_,k,"3g"G8O[6PSP0!;B +OkHJ^I`1.ZMpc$@2s=Smo5XPHC6U7DZg@]hJckDBMi0ec9eRObV8_N3?SC1,#Bs)"c=`&9[V5mhJ*W5a +H)CA[cIj87(1536Y$Gg^;<';g2%rdECC^8jj4\CpXBH@>VW&T*8lIJ1[9A-$=BZG9pAlCs5E +6qc_oqQ]ZJ4n,i\Qe"K(5?su]+R+eQ"F\*gg^9%Wr(^8X#&C\He+/c$iY[1B>O;!_rU2H(s8AAsaI^t8 +GL$]*G&o_BN8dlbc_C6[RUkk=*'BVq%6J^]MaUi*SA^%_l71u!QY:U=9A$NgPdQEiR[Mm@OO]U99$E^3 +@uf<>$tjIM2G)`M=0pST'e&pj1`F]rB-Z6[DO!-<=%/rOSh9!oV!lo0ES&HP`'22UPTb8)\pquK@V,jL +T/UsDb0Dmd,64iM_)FlMmfl[':FqJ&APVH[SLh@d[VZ16ONOkYoe_,"K7ggo]CBb'm.Gg;/k/Y"/>uoa!"7 +RGD`DQ[W"j)MpC5G%`DO=2*NppA'?j6DQVdk?$U(&c.SfPB#eg1RV+t9=&PVl&-/aG7,b\9[Wdt%?tW$ +4)[uT8%Y>LRCkJ@Xrd86FHR +nM]:8`,14G6bF4efc3*;j?,7UpVoRVPo#dG):IuB*L1MF4Ntc4O2r=lNu;=8,a0;dEf(N+AtdAs_7<%I +nZ)L6NE8qRndbU-Bo(/]l/42Y\qLg+2lT3gS.qdL:AQT^Et@XXP@mMR^Z3tMU&H!@^3C"i(8C9D&>?<+ +flSaWI2$*3q_Q1CCO\5]GAkK&7(tm,]NCNo%3tV`bM8^am1W=*dr%M/Wq"S9F5FO<3Mkslb("C8j*h#V +'Zdqp\\#6I)3OC6*-6UQdtogWM3R$!$omBYm\b1e[F0feDHJXBlmoVsj)?'-PC#"DT_ZsQ\-3JW2uC:8 +_n?C+`S.[5+BAe;KGZ0&GuHC2X3N]T5i+pIl9C3\3D4A +;rc,B>;1+\AKo0pdS9+2aj'$&=36VdeS%M!jo4W/@n?9hSR +[7bQn=]NFZ@'$D.]#=$FiFYUORo[&`d[37A2>We`\In9>3o4jHk3OGImKC!.omn$MI@uq=TYI_OGde"L +jZ&lAbW^")h#OO+#rCP2*@F3RRZXHt6JA<3KbB5*3H0-HhL"e\gkmQGia.Ph.;I//Xn +GqF'_39KVa?+@23GVI$=ebj[sZKmHW2tDZ*i4I+"i-YB^XR;23b^=0d7D=^V:A%k(jC>g@ZGbeIPaA`Y +^A`.h2jqG\o%L[GZrhX&VoLBZ_0qm1R7%RnB'hbVCIR^c."1I!ELSFY%rU`I(VY$8OQ4>Ulf&Q:k4aPm +nJNF_a+)e"\9TCdSCAq4lt`N?Z!AEl/T2qQBh>Mler]g1T[K-"?\,B83dYo9a--qPBuZJh1H&'o^F*ab/2]G7:7f>Q?FlL3?*s8E'a1nNPsT+>.u2bW#[m8);_A*be'YtHX\ +eH"oWnErjGKKY4efO^*b'YY[%fH35DE=2*076W#I@6O2Q'#(k_JHp+GkWIC)?;l:=bB_9c]6<%3 +U#tD6*+.Be0b>fTV!4s8YL0M#jnPj_)1"+]^,Y)kgH4tMc4THdmu\iLJQ*'$:Alci+gS"C>%MLDTL6(j +jE'EFO/kbeI6mBIhZ3T,3U^(`L:A22>?m*#\'/&]YQEb'c<9J\1*7&fAV^T[Nlcb6-/+V+>@3(+>F2l'I1Qc6%oj=g,RYBK>'Ka'^( +l4Hm4aLFaX?ZMrfhGj-NP"PO2Q[@/I[rWI)_9';GQ_i!>1uifHOZUDp=]0!3jA#kfj:7sLo;*<\cNLM^ +b<02cIWoRKa(D/ZCR8_0=Brh0\N")2A5_P=A`ISZqA,7O?+Tta)<[W1m!eBhB:_mM^N]Bmg9gB-g;iQ& +]B)1OI&RPr-_uM9l#Z)$Srp%[>S_:]o#4PrE<+05hnj7ncA6=VuO0#GKYAmot4Y<#hDm +o%E+O.=jPjMW&!8(rs-4guP#G$+W@VNkXaurO:ftn]?cgK_IWDHr9,B4]@,N6(cCa@1rluJpc@t&I!f1u7j4^olN6G)a?nJ2mg +G.a/.T:M'kG/Eb%*>C4CVd8$#p#_4QdWWT/jOPcnGdE9LOj>Y!D)\W1NMP2s>;]-(36(4[af%eC-9iJf +Q-&>Zak0cf9'\2X9`9d%m^(*=hV,Xl)@igbjk:?F6cQuUQ^dpB9ui7oj,1=;m3I+ +<,iG6grsG2cA1HnBIJ^OqY]JcaFp_PGiqdCknNpZEh&Hc"q0H>1D"a^]]%4)OrK:bY]K]X/\/("FB?=P +2$YJi\mfu2Q`Luldoa6141X.&3Pc\ReqKGCZX%lA=BF[]SUI1"R+#,CaEF[^m9pD@5\%8!g#$t1:I1oCe`\A.['I)IA5nA"T9e*H[FIUh>K">4i?lHo$Qh]pXtZ]o\&?/I>mP&][l&h\`F2Ec4D?UfUW+aWu:gmClNVlHNQ0> +Tm%aA?-Z3)J6*2,`Viqd=.dQ[AThUfdOiCkDeD7R*R.SF +Hl"Nf%oA=1-13d"=;DP?Wb+KqRs/,SH/:3tme$:?Ab8rX_@kY4hL4`r6(AAKSAO!Re0lEV@QCmM$]p.n* +'.s)1+/X%;TBKu_/[/)m`&akngQ`#esop0T%nUDnG[\EdMj0k[b9u_0B +.HBjl[2CXchhCg!UjDDs$=7H+DQqLbX.;gRg='%PeUHN%G"Ij*Lq>2e5>#Ko*D^\6XX_:q(>_>Egh#Fb +/Atg$Fmh\Dgk!TEGnZ:l[8QN6>u.O>4Lk1q(>b%g%'%pJ_;[Dfc3tK3If9>l146r%2`^=s-5E.+8S[rQ\pQ/]1ESX +p=5ru6L.K`%sp[2UZ!jddaW1bqH$67^DWQ9^R(;#PX6GooG"R6qGPa/XqSF=&r)u=BY'cbIYn1iF`;Le +Pj;mCj?DnEZ0Fi;I_1ap\8E+K +or7e+*4IFko==`!A7j%#DkUOaWo>#gQ(L2jpNRo#=\ukHq_fEl'r4ZZ[IfH'PqiU;(Dn3,jgrZ=;rI"S$b,u^U +Or-+Rqhfj=W-FN'/D612>HronlmQuR5=,fi-:mj3,AT5neQ!k#iZ\YW4MQ>-+7s(ZQr@Jj9l&W*lPIcH +qLMl#a=ZK'$_%sC:<_NFp^gI2S9\&s4mQXq='N2df$JH?6M=>(O$.-q)"?s[A]2EGJ$dXX$RtI?"Sd.u +OALo/^e1'N[E&kih96fI>Q9*ERL/&IHt&9Iou)KN/T)/fKOIUj +BX)WYd#Z$5JN"NF&L&g\d4.>O#m_ae/UWmpG3keGBZksC5GDk3d,2TkO7@aY5O^Bk@#Y[P=lV#smM]H0 +I`;(E`Z-3oeFsm=F,aK^jPtme[)_o3:TG4V$llu0KfM?T@jUqGjLtaU8bbK#Cs,c`$k))_9DrE?qlc\AhW2[RtH4ZULpp!&YpL2JWbJCQ_B504hJDV3XsT +@l!ouT$9Yt#pEGd_@*]/?^f6(ik8btnZ+0[Cg$?bUVA[#UDf!ZA.#^.SY^C( +6XA-F[!;'3>1o]14eV^N7\cbS.9f,UH:M`sTB!VMnr!63jN>U``JkM2c_*5lRrpu5C>[mHAZ_G$$Cji1 +rGh`la&_H+\5Qm5YPL`14kV36k7-V5pt_9JbZ>dt28I@`N.gEnXL[hG[glmKIoCWfDF33ol*N49loRD_ +.sh`4p@u3/e01pqHf;=+V;HkFmp4j1IFY]Yo`732?*X87A7NMjfZK?1%3&*i_;=3Pgdn8#cL3rQJf[Dj +qC_9>E:OtE88nBcgZ&40lDSoS-TRVioX=b2"LB/Xk/]#2U-psQkae=$l[HNLZ.DfrYeJ-2dc_VM9flBbfn4l4pQlhp&.*eAV_$D-n' +..2-SgOJUkh6FZJl0k#s$QHqPlXaMaa0F1+XaX9'F5P\"7R0*/lpD\pGcssrF0tYk"Gt#;EHR)s>!$ot +rS4LW.g`sAI_julDfLBnX2VtilB+n^#]\J&IE"hRhOjtc8h.)9Uu4[KVg-Be4k6@Y\)YFFHI!q$QL%m( +]dtlJa=VtnW`lN+bl\IPOsOj+qb*u4X:M`LXA[ZsL7Ca@q6p*"M9G;\#b%[;)+W3ZFl])"^M1@)o!3I& +kpLp@/%H#!c"+MVGdkejejTtUg'q\&>8,82&sR+qA"'2^m3g^:AOCH:Tnc/HEb]Z4dTh?!oF\mQaUEfX?`hA +D+UfDH7uu"p#[d/HNMT$J_`t-MpgE[9=4i5(IR(1YV3Cf5DH4Mol54Cb8u52\K.fD^9/21MA^8X"^8dq +'.\$$fHjXN6<\rY0k9Z2pZEn6%M]2'%bNAod#ga)E9Ki[5B,h*(IXK*cbYb;m"9bLD@;mS*;W-#4ufqH +Yn*tP2_$Uai'uX$iY[_^gKT0e(nWM%B8)=GN3Bk9guG`*aq`t&IcMeEcpCh1q!>lX\Q`QuI!jFV0tn'! +N"t"$aO1FNLuSToj.XHgJRc[`pRbPg.6TYa@^oM7%JK.?-/l:bTmg/nn,CoNXd0_m3tM#@)lsP%@l)"D=>jB<9(r7Q"Y;Loa_Y5< +j1+=6Fk(,U"RXj8&ep!FjJ6i/oX]<`o7=(/\u1+Dk,hYhp7$"& +qnAuc%0-]LX(H\K\'#K8g@t5XQRTnge7l+T!^$ibQaXb8arkP32KK[CaW)*jI7h9G:pZX6]>!i\!,cfCpX+uZ=/MQo-:;7a$F)>rHcJ&i8OEq3b +2^e5([+n-`4Tfr4e8C<4%ieJS^]27#!cMT/tDY_,tBH=6am7dnr#%Yh6HaYG2nCX95h8n_BkHg:u:>tWHJ\O%0;rbQIjkr#ul1OaJ +O#0AjY@^V+!S0drpS*:"6WA[1+:::9]D(K9@Ef9a,!LVINSW7ID$qCdah!>F.8`XT#n:[ug<-[R^@`Q1 +X`#5`UD7kET^L4f3BVVkj/T:PT-ro:&rkrN"%]-.dDCq1/`$m3P;9L`lUsArWDKA)5W6%cG&r(GV"geM +T_AYUr2W+Ln'Oj:Y*o7mDskkW?BH=NgJqsIGX>$@o:MDrH];35379 +of$'W:Im+sisk&/$ch^s2kGFcp<`4P^5u;GM/TooeORqQ$Z.-$qr%.uIpcu[^^[\7rU*X")r<+Wg4o:O +WGPasSbg:*eq`\`B5QgI[oP]Yoo8=`YcVa^LUQ/9$e.'>@O32U@m'"AS\TT4q"mi\D<&,ugGkYM2W!b? +aqVIgG=rq5h[F40GEAE%b&2qr=pKQA28Qp86YIE>USkPW)/ZIZE]-gW,) +;_W[VL/=g_Qt9=`(Hhc%Hs\2%9[e7RoS=*+h=c0%G!>5>jH\o=GN$jX[ebtM(4:;d2:;>.:e!6jrsJ*S +4Pp*0%ec?LHrIVjPJeJj5Gi52JNah52t-e!gD3m^]X(3/M#$K/sZ +>$#sPZsV$H5rJ%].Th+6*TZh/HMt'LYb+Z/g<)"Ni/gD<3;=k=,lj!'.M4UMAF`C'[JD>) +.s[^#QV8eN'A=K.I0LeN3*Ih6@[Y&h&+;Icau!YoOb[r&Y;k>7qtm/gXQ&-P,511Wjl\(LKt#RIo:PfB +go(7g-+CYJOPKb#88>lt`g>c[Rm[e/h<$;gr=nf5]HoKk-$SZ^i?tk\p`[2;k/CuT:P":;p2eZ;jDOXk +*3GCS0"G3^3o\P*XEd+&G$,ue@4oL+fflKB^MEEGhK-WmJ)ZRFINCWt2g\$9h'C#i\XOj)C9Ku`E@&$d +Fnc9Eg:gdf_d.:Aa`0r@2Po#Q`07d^-20F7X;ML_4Z7rM)oi52]BtNZH",TU56$I@eA)hhIIg.#ldZ[I +<4YSV=AQQU1A*3I/j4`"kETnrp3,djmsaYllQ;Em5BO:,KDWjK%s]gr[Dc\Z]56cs$P=,W`J2e6i_4FfV-_&+)C*p+u[#kA9P6k_&Qg0;#2dGkTXhI=?l_krj@dWZGm/DB.E9k(db><2u7UHH&6?uRcXTe"@nSDLs'VAU=JePV2mBSEO\+ZdBj]?DCkgm*?XT"gQ59eR)b,.Jdh^hK# +JC%rjh^:uL5L#FBf97TEpc1HC%O +6="'RQnRk`3C4_-FVN5\R_HXKTJcG2r4U-7PN1;.i]36ZCon6JBpC:A*A-Y7nRP5X1U4):pR1q!#kl5, +hh_/'#HilISoc1<3oq5T632"r^`.`1mPB-,LA%%0YDg0kH'ooNl;a`,p:Bhg2]Y&ZL*SY,qG7Aj?a5MI +;Mcmu2;c('Gq&SF!:>?D!?'Q>j@;T]Gbs.ZU +BBt@R^1&IHkCsJ[[^*Q0L-?Xs"#SLkPP>mi9TGd&3m[FqCWH687XB<.8[`-fHr/sHI3&2YicXTDGn5rq +&8$>`EK4&$PnI\1e;er(k=tB"3t48ogkfL4M3WdCkBc,M<7;+M+6.(2X4R2oWq9rS)'u1#:JT!Nn]DkJ +3W*uh8(ob^Sp\bKSI4XJF&]$nZ"?'r"P][J=.RMd-JUOX$sRt^^QLuBl='/635:n'f'!TXo0e)GQKDkT +<=uCeJds-FWou^i`9Pcq0D_aE=$C@n].E.N]i.8**PMh1>2P1J+m_Y;=fA6G734$VSo%&]T(Fn$ejhJC +"2V%-;(3Q,(00HD-3#G6`Lao/Pm#4;aEsMJO7thJk_-H"T+],O[00;3"UNO#X/ur(bp-POcsqgc82HO`W3@lgW2=@Ro8m +:121*_hknjSI*J$e'kDG`]'ahc9/`qSBd:AL(d%m1C;oK5/qnheG>%ReV)ZP>B:aDkcCDL@eT,=jIHU$ +MFQfhD6-.97%P=!04I\*Y'>lmFT>EZG`#e#AE4!9aX8b.dPh/uILjs?uj1IqLdBOJ[+g&En +Fm')qK;G-T'RN.,U"lIZW_Rd*?ad.c]smPophDtc.YU1s&7_>2^Vh0Ahn-9]qKf2juH\esHnNt2$+RCdP7+/e,*f:9?D<8riVo?ahBa=PR7@f>`Qo/s1cUh3-!9D/]gr]KB/ +.akrCp.A64Uhpe[-_?E37<6c@LR-:?j.Hg$"G2Lk32E-D#,@%+:)S_2rpnQ.f1^(oRM9;H"6k_[Ls7)O +Z)NM2j"%G/GU>)LfDL"0D.qKG?a&j&)>Gdm9SCH +;g;@M4"91Gdb1MOoR4o!+&*.3.[?+QQpL)9dCW">(4r14/[>q"f!@lthG#@u'n7Wmi*?r`\L;['79JE3 +i>URr^;Q6RKj/s_G1%'>0]1U^iU3Dl[FM"/j/rHUn"JGHdCT]2Yj9##6=,+en<:%8g904Tf(e$:QR +5;+GR$E"3>G9ng/$C1P,IOqc+'A$=Hj0DHt>E#DL[V"d59]T=+3b_ONoVNn0@!;W!SOBG2>>HHRkXX2;s5o +T4O#dC\#B#aApPa6[Ns$H2=K4hmD"XqXL,(4l`+685%HcT8LtU([JXSntdsR*o&1eCY^9Xd>GO4nn^'s +_GfGo+@mNE?`mmS#Zfal=l;4m*0LAuMq4mkNYPMc8bF6A\_\jMj0I.7m(;VA)^Lm=k!-U_PM[tlnVT7g +34W>VPE'b3?Tr4A>M;UI6h]-TlJXobC=_>:!D6SEKm=ILX^c-m+O^_7ZbVuIU>;GJXPWD%p#oPsS1==R +)A+dUpe.dXWefKjr<\b#G-%?pfd=@M+J0:SAkGMdIB3(.Z)TQ2*hL-LZH#%2Z&ZWgL%;-+kM"i'>$Q-5 +.J;!PdHK(@']"UM3G?["/rV0E=%J:e[=$%3hIEd'QdNDSXK($C/*k4IDHl7Nq'p6=JWS'[+"m#j.I+0? +"o^LYadVb3[>Pi(q*1G,D&_JEn&&[O+c;d-Is,l*d(ADgoPU/Lm.m4ADNW6fn!6,tR`5:)?I>l]XdQVn +HabTaQ;a!>O/h]jKqroc'](Dr]j(D4(3[m1rS=ie?fGCHi0i0aGY.3kr6Ro.]:QJZODi0*6[?0%&:1f5 +s2=>X.$`J1o0?G\!+eT>R:\&VLnH']2V)/a\VpiXH3LG8hDERJ//78%]/'^(0W*lp'j] +/o-3U6JRO!*n=mQN +s5@J\,`CJldLjs*G=E:\)_jK;r),Y6pMQS@=2gC%Lt\iCRj\(Z"#3]!WM%0&@6l2b\6YM$meraK`OcfS +f9'bfR,2sMF.Q=V[pRuTb?.1pP1 +HrX%+IT^j-DC^fY>=Y`'m^36>Nq].@h4gQgFO1g<+0_q3DXE_^[51D3JiMcBo=6X.M"d>Ulh9k!p[e=> +-g(/*lb?ahR`rZXO15kbFl+2a`ja^1++<,(UXed4%u_D=NjBqG`"2%'G`JtM5!Pa%81 +(njs[D+BY@73eki>CPT,K%7IS5OkU*^\1IQ?`]5KGTgdMq*Rcd^PJh#WecZ:A=CF84,$k)ZXMLX7V?^,GPAHr'qjU]?7AG]?8IAJ` +AI]_2"j?a.*T9SN`G7Jt^5kY8/Tdd&DuYird#_4n--#)de\.P'efC9VA.dAiIro&`$b?jX".(g55?CTuUlN@MrE +>kteGHOZ6fjjt,8I]Yg5#AOcK2V-CNC7;]liN;(SES!2DYPur)uHL^NQC& +o^!G3[@58&#Mum$T)Z_jYK?:ZY>ro1f9EU^U:A>j)XTEUo/c6o>b3HXA$i7+CHh<)1GcY.?^ltTa6Iau +7t0h_^R4Qf>]JP(Zlb*2c`]oAKQrV$& +L!>e(3\20gc&UiH)HJ"0;pu?!buG\2f0WJ$&7o$Q`,RFk5s*HBjM>+`tbEc +a5*,/T,:Eqbou)VL<_c]d>?PKjX$qSa3#]=8/%?7UK(2;PC(V8DIsS)EEq!:(A>3C4WtA3GfjATNt_$E>`JA3;GimCc\ocEkEi6g9n^V^R4Po=:AXA^k\NIjbaWM*u*u^*78m] +(GUNs,mu:oOEk;dZE?DnN50DgZEC;S7UYH(b/cP8'!:3HC!A^H7uBjg=c=Kp4:bm(6Ln64&L63e)'e'd +,=[c=oZ<_r7uVus4_AZ(.iKY_gk-*+1ZB[7Y>Rh:&0n^IMei%U\\C(X8$5%m>L..k0`k2aT"^o3lYepM +@g!\47F^%62VW`7dhO)]]JC/LBD.-0.,H7;.S%+W+8F^^j[CVCqDh=lP'KYDFMl>. +Ao,Ca]0NMB="_Ud+rl&GJg`7==?ZB1G/pq&R@:JhBHKVk\C=eLf7,I8Ja@@<ssLli-ta^B%g~> endstream endobj 7 0 obj - 76312 + 74596 endobj 3 0 obj << @@ -1031,15 +1009,15 @@ xref 0000000000 65535 f 0000000015 00000 n 0000000315 00000 n -0000077055 00000 n +0000075339 00000 n 0000000445 00000 n 0000000521 00000 n 0000000609 00000 n -0000077031 00000 n -0000077509 00000 n -0000077225 00000 n -0000077264 00000 n -0000077366 00000 n +0000075315 00000 n +0000075793 00000 n +0000075509 00000 n +0000075548 00000 n +0000075650 00000 n trailer << /Size 12 @@ -1047,5 +1025,5 @@ trailer /Info 1 0 R >> startxref -77582 +75866 %%EOF diff --git a/satrs-book/src/events.md b/satrs-book/src/events.md index e21aac9..3f910a7 100644 --- a/satrs-book/src/events.md +++ b/satrs-book/src/events.md @@ -17,3 +17,8 @@ The following images shows how the flow of events could look like in a system wh can generate events, and where other system components might be interested in those events: ![Event flow](images/events/event_man_arch.png) + +For the concrete implementation of your own event management and/or event routing system, you +can have a look at the event management documentation inside the +[API documentation](https://docs.rs/satrs/latest/satrs/event_man/index.html) where you can also +find references to all examples. diff --git a/satrs-book/src/introduction.md b/satrs-book/src/introduction.md index babd8ca..1105000 100644 --- a/satrs-book/src/introduction.md +++ b/satrs-book/src/introduction.md @@ -32,3 +32,14 @@ The [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/m provides various practical usage examples of the `sat-rs` framework. If you are more interested in the practical application of `sat-rs` inside an application, it is recommended to have a look at the example application. + +# Flight Heritage + +There is an active and continuous effort to get early flight heritage for the sat-rs library. +Currently this library has the following flight heritage: + +- Submission as an [OPS-SAT experiment](https://www.esa.int/Enabling_Support/Operations/OPS-SAT) + which has also + [flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/). + The application is strongly based on the sat-rs example application. You can find the repository + of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs). diff --git a/satrs-example/src/config.rs b/satrs-example/src/config.rs index a8c495a..d84ad2e 100644 --- a/satrs-example/src/config.rs +++ b/satrs-example/src/config.rs @@ -182,7 +182,7 @@ pub mod pool { use super::*; pub fn create_static_pools() -> (StaticMemoryPool, StaticMemoryPool) { ( - StaticMemoryPool::new(StaticPoolConfig::new( + StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( vec![ (30, 32), (15, 64), @@ -193,7 +193,7 @@ pub mod pool { ], true, )), - StaticMemoryPool::new(StaticPoolConfig::new( + StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( vec![ (30, 32), (15, 64), @@ -208,7 +208,7 @@ pub mod pool { } pub fn create_sched_tc_pool() -> StaticMemoryPool { - StaticMemoryPool::new(StaticPoolConfig::new( + StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( vec![ (30, 32), (15, 64), diff --git a/satrs-example/src/events.rs b/satrs-example/src/events.rs index cb0caf8..8f4bd08 100644 --- a/satrs-example/src/events.rs +++ b/satrs-example/src/events.rs @@ -2,7 +2,6 @@ use std::sync::mpsc::{self}; use crate::pus::create_verification_reporter; use satrs::event_man::{EventMessageU32, EventRoutingError}; -use satrs::params::WritableToBeBytes; use satrs::pus::event::EventTmHookProvider; use satrs::pus::verification::VerificationReporter; use satrs::pus::EcssTmSender; @@ -42,6 +41,7 @@ pub struct PusEventHandler { tm_sender: TmSender, time_provider: CdsTime, timestamp: [u8; 7], + small_data_buf: [u8; 64], verif_handler: VerificationReporter, } @@ -82,6 +82,7 @@ impl PusEventHandler { pus_event_man_rx, time_provider: CdsTime::new_with_u16_days(0, 0), timestamp: [0; 7], + small_data_buf: [0; 64], verif_handler, tm_sender, } @@ -132,21 +133,26 @@ impl PusEventHandler { // Perform the generation of PUS event packets match self.pus_event_man_rx.try_recv() { Ok(event_msg) => { - update_time(&mut self.time_provider, &mut self.timestamp); - let param_vec = event_msg.params().map_or(Vec::new(), |param| { - param.to_vec().expect("failed to convert params to vec") - }); // We use the TM modification hook to set the sender APID for each event. self.pus_event_tm_creator.reporter.tm_hook.next_apid = UniqueApidTargetId::from(event_msg.sender_id()).apid; - self.pus_event_tm_creator - .generate_pus_event_tm_generic( + update_time(&mut self.time_provider, &mut self.timestamp); + let generation_result = self + .pus_event_tm_creator + .generate_pus_event_tm_generic_with_generic_params( &self.tm_sender, &self.timestamp, event_msg.event(), - Some(¶m_vec), + &mut self.small_data_buf, + event_msg.params(), ) .expect("Sending TM as event failed"); + if !generation_result.params_were_propagated { + log::warn!( + "Event TM parameters were not propagated: {:?}", + event_msg.params() + ); + } } Err(e) => match e { mpsc::TryRecvError::Empty => break, diff --git a/satrs-example/src/interface/udp.rs b/satrs-example/src/interface/udp.rs index d7816e2..e7720bb 100644 --- a/satrs-example/src/interface/udp.rs +++ b/satrs-example/src/interface/udp.rs @@ -3,14 +3,13 @@ use std::net::{SocketAddr, UdpSocket}; use std::sync::mpsc; use log::{info, warn}; +use satrs::pus::HandlingStatus; use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderRaw}; use satrs::{ hal::std::udp_server::{ReceiveResult, UdpTcServer}, pool::{PoolProviderWithGuards, SharedStaticMemoryPool}, }; -use crate::pus::HandlingStatus; - pub trait UdpTmHandler { fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr); } diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index d6cf6ff..02138c5 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -458,7 +458,7 @@ fn dyn_tmtc_pool_main() { info!("Starting TM funnel task"); let jh_tm_funnel = thread::Builder::new() - .name("sat-rs tm-funnel".to_string()) + .name("sat-rs tm-sink".to_string()) .spawn(move || loop { tm_funnel.operation(); }) diff --git a/satrs-example/src/pus/action.rs b/satrs-example/src/pus/action.rs index dcdb345..03d362c 100644 --- a/satrs-example/src/pus/action.rs +++ b/satrs-example/src/pus/action.rs @@ -1,23 +1,23 @@ -use log::{error, warn}; +use log::warn; use satrs::action::{ActionRequest, ActionRequestVariant}; -use satrs::params::WritableToBeBytes; use satrs::pool::SharedStaticMemoryPool; use satrs::pus::action::{ ActionReplyPus, ActionReplyVariant, ActivePusActionRequestStd, DefaultActiveActionRequestMap, }; use satrs::pus::verification::{ - FailParams, FailParamsWithStep, TcStateAccepted, TcStateStarted, VerificationReporter, + handle_completion_failure_with_generic_params, handle_step_failure_with_generic_params, + FailParamHelper, FailParams, TcStateAccepted, TcStateStarted, VerificationReporter, VerificationReportingProvider, VerificationToken, }; use satrs::pus::{ ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver, - MpscTmAsVecSender, PusPacketHandlerResult, PusReplyHandler, PusServiceHelper, + MpscTmAsVecSender, PusPacketHandlingError, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter, }; use satrs::request::{GenericMessage, UniqueApidTargetId}; use satrs::spacepackets::ecss::tc::PusTcReader; -use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket}; +use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket, PusServiceId}; use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use satrs_example::config::components::PUS_ACTION_SERVICE; use satrs_example::config::tmtc_err; @@ -61,7 +61,7 @@ impl PusReplyHandler for ActionReplyH active_request: &ActivePusActionRequestStd, tm_sender: &(impl EcssTmSender + ?Sized), verification_handler: &impl VerificationReportingProvider, - time_stamp: &[u8], + timestamp: &[u8], ) -> Result { let verif_token: VerificationToken = active_request .token() @@ -69,15 +69,23 @@ impl PusReplyHandler for ActionReplyH .expect("invalid token state"); let remove_entry = match &reply.message.variant { ActionReplyVariant::CompletionFailed { error_code, params } => { - let mut fail_data_len = 0; - if let Some(params) = params { - fail_data_len = params.write_to_be_bytes(&mut self.fail_data_buf)?; - } - verification_handler.completion_failure( + let error_propagated = handle_completion_failure_with_generic_params( tm_sender, verif_token, - FailParams::new(time_stamp, error_code, &self.fail_data_buf[..fail_data_len]), + verification_handler, + FailParamHelper { + error_code, + params: params.as_ref(), + timestamp, + small_data_buf: &mut self.fail_data_buf, + }, )?; + if !error_propagated { + log::warn!( + "error params for completion failure were not propated: {:?}", + params.as_ref() + ); + } true } ActionReplyVariant::StepFailed { @@ -85,31 +93,35 @@ impl PusReplyHandler for ActionReplyH step, params, } => { - let mut fail_data_len = 0; - if let Some(params) = params { - fail_data_len = params.write_to_be_bytes(&mut self.fail_data_buf)?; - } - verification_handler.step_failure( + let error_propagated = handle_step_failure_with_generic_params( tm_sender, verif_token, - FailParamsWithStep::new( - time_stamp, - &EcssEnumU16::new(*step), + verification_handler, + FailParamHelper { error_code, - &self.fail_data_buf[..fail_data_len], - ), + params: params.as_ref(), + timestamp, + small_data_buf: &mut self.fail_data_buf, + }, + &EcssEnumU16::new(*step), )?; + if !error_propagated { + log::warn!( + "error params for completion failure were not propated: {:?}", + params.as_ref() + ); + } true } ActionReplyVariant::Completed => { - verification_handler.completion_success(tm_sender, verif_token, time_stamp)?; + verification_handler.completion_success(tm_sender, verif_token, timestamp)?; true } ActionReplyVariant::StepSuccess { step } => { verification_handler.step_success( tm_sender, &verif_token, - time_stamp, + timestamp, EcssEnumU16::new(*step), )?; false @@ -266,43 +278,23 @@ pub struct ActionServiceWrapper TargetedPusService for ActionServiceWrapper { - /// Returns [true] if the packet handling is finished. - fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus { - match self.service.poll_and_handle_next_tc(time_stamp) { - Ok(result) => match result { - PusPacketHandlerResult::RequestHandled => {} - PusPacketHandlerResult::RequestHandledPartialSuccess(e) => { - warn!("PUS 8 partial packet handling success: {e:?}") - } - PusPacketHandlerResult::CustomSubservice(invalid, _) => { - warn!("PUS 8 invalid subservice {invalid}"); - } - PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => { - warn!("PUS 8 subservice {subservice} not implemented"); - } - PusPacketHandlerResult::Empty => return HandlingStatus::Empty, - }, - Err(error) => { - error!("PUS packet handling error: {error:?}"); - // To avoid permanent loops on error cases. - return HandlingStatus::Empty; - } + const SERVICE_ID: u8 = PusServiceId::Action as u8; + const SERVICE_STR: &'static str = "action"; + + delegate::delegate! { + to self.service { + fn poll_and_handle_next_tc( + &mut self, + time_stamp: &[u8], + ) -> Result; + + fn poll_and_handle_next_reply( + &mut self, + time_stamp: &[u8], + ) -> Result; + + fn check_for_request_timeouts(&mut self); } - HandlingStatus::HandledOne - } - - fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus { - // This only fails if all senders disconnected. Treat it like an empty queue. - self.service - .poll_and_check_next_reply(time_stamp) - .unwrap_or_else(|e| { - warn!("PUS 8: Handling reply failed with error {e:?}"); - HandlingStatus::Empty - }) - } - - fn check_for_request_timeouts(&mut self) { - self.service.check_for_request_timeouts(); } } @@ -417,7 +409,7 @@ mod tests { } let result = result.unwrap(); match result { - PusPacketHandlerResult::RequestHandled => (), + HandlingStatus::HandledOne => (), _ => panic!("unexpected result {result:?}"), } } @@ -429,19 +421,19 @@ mod tests { } let result = result.unwrap(); match result { - PusPacketHandlerResult::Empty => (), + HandlingStatus::Empty => (), _ => panic!("unexpected result {result:?}"), } } pub fn verify_next_reply_is_handled_properly(&mut self, time_stamp: &[u8]) { - let result = self.service.poll_and_check_next_reply(time_stamp); + let result = self.service.poll_and_handle_next_reply(time_stamp); assert!(result.is_ok()); assert_eq!(result.unwrap(), HandlingStatus::HandledOne); } pub fn verify_all_replies_handled(&mut self, time_stamp: &[u8]) { - let result = self.service.poll_and_check_next_reply(time_stamp); + let result = self.service.poll_and_handle_next_reply(time_stamp); assert!(result.is_ok()); assert_eq!(result.unwrap(), HandlingStatus::Empty); } diff --git a/satrs-example/src/pus/event.rs b/satrs-example/src/pus/event.rs index 4726ba0..caecdd9 100644 --- a/satrs-example/src/pus/event.rs +++ b/satrs-example/src/pus/event.rs @@ -1,19 +1,20 @@ use std::sync::mpsc; use crate::pus::create_verification_reporter; -use log::{error, warn}; use satrs::pool::SharedStaticMemoryPool; use satrs::pus::event_man::EventRequestWithToken; use satrs::pus::event_srv::PusEventServiceHandler; use satrs::pus::verification::VerificationReporter; use satrs::pus::{ - EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, - EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlerResult, PusServiceHelper, + DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, + EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver, + MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper, }; +use satrs::spacepackets::ecss::PusServiceId; use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use satrs_example::config::components::PUS_EVENT_MANAGEMENT; -use super::HandlingStatus; +use super::{DirectPusService, HandlingStatus}; pub fn create_event_service_static( tm_sender: PacketSenderWithSharedPool, @@ -61,26 +62,52 @@ pub struct EventServiceWrapper, } -impl - EventServiceWrapper +impl DirectPusService + for EventServiceWrapper { - pub fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus { - match self.handler.poll_and_handle_next_tc(time_stamp) { - Ok(result) => match result { - PusPacketHandlerResult::RequestHandled => {} - PusPacketHandlerResult::RequestHandledPartialSuccess(e) => { - warn!("PUS 5 partial packet handling success: {e:?}") - } - PusPacketHandlerResult::CustomSubservice(invalid, _) => { - warn!("PUS 5 invalid subservice {invalid}"); - } - PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => { - warn!("PUS 5 subservice {subservice} not implemented"); - } - PusPacketHandlerResult::Empty => return HandlingStatus::Empty, - }, - Err(error) => { - error!("PUS packet handling error: {error:?}") + const SERVICE_ID: u8 = PusServiceId::Event as u8; + + const SERVICE_STR: &'static str = "events"; + + fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus { + let error_handler = |partial_error: &PartialPusHandlingError| { + log::warn!( + "PUS {}({}) partial error: {:?}", + Self::SERVICE_ID, + Self::SERVICE_STR, + partial_error + ); + }; + let result = self + .handler + .poll_and_handle_next_tc(error_handler, time_stamp); + if let Err(e) = result { + log::warn!( + "PUS {}({}) error: {:?}", + Self::SERVICE_ID, + Self::SERVICE_STR, + e + ); + // To avoid permanent loops on continuous errors. + return HandlingStatus::Empty; + } + match result.unwrap() { + DirectPusPacketHandlerResult::Handled(handling_status) => return handling_status, + DirectPusPacketHandlerResult::CustomSubservice(subservice, _) => { + log::warn!( + "PUS {}({}) subservice {} not implemented", + Self::SERVICE_ID, + Self::SERVICE_STR, + subservice + ); + } + DirectPusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => { + log::warn!( + "PUS {}({}) subservice {} not implemented", + Self::SERVICE_ID, + Self::SERVICE_STR, + subservice + ); } } HandlingStatus::HandledOne diff --git a/satrs-example/src/pus/hk.rs b/satrs-example/src/pus/hk.rs index bbecf19..0092241 100644 --- a/satrs-example/src/pus/hk.rs +++ b/satrs-example/src/pus/hk.rs @@ -1,5 +1,4 @@ use derive_new::new; -use log::{error, warn}; use satrs::hk::{CollectionIntervalFactor, HkRequest, HkRequestVariant, UniqueId}; use satrs::pool::SharedStaticMemoryPool; use satrs::pus::verification::{ @@ -10,11 +9,11 @@ use satrs::pus::{ ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver, MpscTmAsVecSender, - PusPacketHandlerResult, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter, + PusPacketHandlingError, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter, }; use satrs::request::{GenericMessage, UniqueApidTargetId}; use satrs::spacepackets::ecss::tc::PusTcReader; -use satrs::spacepackets::ecss::{hk, PusPacket}; +use satrs::spacepackets::ecss::{hk, PusPacket, PusServiceId}; use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use satrs_example::config::components::PUS_HK_SERVICE; use satrs_example::config::{hk_err, tmtc_err}; @@ -24,7 +23,7 @@ use std::time::Duration; use crate::pus::{create_verification_reporter, generic_pus_request_timeout_handler}; use crate::requests::GenericRequestRouter; -use super::{HandlingStatus, PusTargetedRequestService}; +use super::{HandlingStatus, PusTargetedRequestService, TargetedPusService}; #[derive(Clone, PartialEq, Debug, new)] pub struct HkReply { @@ -297,45 +296,26 @@ pub struct HkServiceWrapper, } -impl - HkServiceWrapper +impl TargetedPusService + for HkServiceWrapper { - pub fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus { - match self.service.poll_and_handle_next_tc(time_stamp) { - Ok(result) => match result { - PusPacketHandlerResult::RequestHandled => {} - PusPacketHandlerResult::RequestHandledPartialSuccess(e) => { - warn!("PUS 3 partial packet handling success: {e:?}") - } - PusPacketHandlerResult::CustomSubservice(invalid, _) => { - warn!("PUS 3 invalid subservice {invalid}"); - } - PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => { - warn!("PUS 3 subservice {subservice} not implemented"); - } - PusPacketHandlerResult::Empty => return HandlingStatus::Empty, - }, - Err(error) => { - error!("PUS packet handling error: {error:?}"); - // To avoid permanent loops on error cases. - return HandlingStatus::Empty; - } + const SERVICE_ID: u8 = PusServiceId::Housekeeping as u8; + const SERVICE_STR: &'static str = "housekeeping"; + + delegate::delegate! { + to self.service { + fn poll_and_handle_next_tc( + &mut self, + time_stamp: &[u8], + ) -> Result; + + fn poll_and_handle_next_reply( + &mut self, + time_stamp: &[u8], + ) -> Result; + + fn check_for_request_timeouts(&mut self); } - HandlingStatus::HandledOne - } - - pub fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus { - // This only fails if all senders disconnected. Treat it like an empty queue. - self.service - .poll_and_check_next_reply(time_stamp) - .unwrap_or_else(|e| { - warn!("PUS 3: Handling reply failed with error {e:?}"); - HandlingStatus::Empty - }) - } - - pub fn check_for_request_timeouts(&mut self) { - self.service.check_for_request_timeouts(); } } diff --git a/satrs-example/src/pus/mod.rs b/satrs-example/src/pus/mod.rs index 6e5ec37..f305308 100644 --- a/satrs-example/src/pus/mod.rs +++ b/satrs-example/src/pus/mod.rs @@ -8,8 +8,8 @@ use satrs::pus::verification::{ use satrs::pus::{ ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError, - PusPacketHandlerResult, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, - PusServiceHelper, PusTcToRequestConverter, TcInMemory, + HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, PusServiceHelper, + PusTcToRequestConverter, TcInMemory, }; use satrs::queue::{GenericReceiveError, GenericSendError}; use satrs::request::{Apid, GenericMessage, MessageMetadata}; @@ -31,12 +31,6 @@ pub mod scheduler; pub mod stack; pub mod test; -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub enum HandlingStatus { - Empty, - HandledOne, -} - pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> VerificationReporter { let verif_cfg = VerificationReporterCfg::new(apid, 1, 2, 8).unwrap(); // Every software component which needs to generate verification telemetry, gets a cloned @@ -79,7 +73,7 @@ impl PusTcDistributor { pub fn handle_tc_packet_vec( &mut self, packet_as_vec: PacketAsVec, - ) -> Result { + ) -> Result { self.handle_tc_generic(packet_as_vec.sender_id, None, &packet_as_vec.packet) } @@ -87,7 +81,7 @@ impl PusTcDistributor { &mut self, packet_in_pool: PacketInPool, pus_tc_copy: &[u8], - ) -> Result { + ) -> Result { self.handle_tc_generic( packet_in_pool.sender_id, Some(packet_in_pool.store_addr), @@ -100,7 +94,7 @@ impl PusTcDistributor { sender_id: ComponentId, addr_opt: Option, raw_tc: &[u8], - ) -> Result { + ) -> Result { let pus_tc_result = PusTcReader::new(raw_tc); if pus_tc_result.is_err() { log::warn!( @@ -109,7 +103,8 @@ impl PusTcDistributor { pus_tc_result.unwrap_err() ); log::warn!("raw data: {:x?}", raw_tc); - return Ok(PusPacketHandlerResult::RequestHandled); + // TODO: Shouldn't this be an error? + return Ok(HandlingStatus::HandledOne); } let pus_tc = pus_tc_result.unwrap().0; let init_token = self.verif_reporter.add_tc(&pus_tc); @@ -189,17 +184,65 @@ impl PusTcDistributor { } } } - Ok(PusPacketHandlerResult::RequestHandled) + Ok(HandlingStatus::HandledOne) } } pub trait TargetedPusService { - /// Returns [true] if the packet handling is finished. - fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus; - fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus; + const SERVICE_ID: u8; + const SERVICE_STR: &'static str; + + fn poll_and_handle_next_tc_default_handler(&mut self, time_stamp: &[u8]) -> HandlingStatus { + let result = self.poll_and_handle_next_tc(time_stamp); + if let Err(e) = result { + log::error!( + "PUS service {}({})packet handling error: {:?}", + Self::SERVICE_ID, + Self::SERVICE_STR, + e + ); + // To avoid permanent loops on error cases. + return HandlingStatus::Empty; + } + result.unwrap() + } + + fn poll_and_handle_next_reply_default_handler(&mut self, time_stamp: &[u8]) -> HandlingStatus { + // This only fails if all senders disconnected. Treat it like an empty queue. + self.poll_and_handle_next_reply(time_stamp) + .unwrap_or_else(|e| { + warn!( + "PUS servce {}({}): Handling reply failed with error {:?}", + Self::SERVICE_ID, + Self::SERVICE_STR, + e + ); + HandlingStatus::Empty + }) + } + + fn poll_and_handle_next_tc( + &mut self, + time_stamp: &[u8], + ) -> Result; + + fn poll_and_handle_next_reply( + &mut self, + time_stamp: &[u8], + ) -> Result; + fn check_for_request_timeouts(&mut self); } +/// Generic trait for services which handle packets directly. Kept minimal right now because +/// of the difficulty to allow flexible user code for these services.. +pub trait DirectPusService { + const SERVICE_ID: u8; + const SERVICE_STR: &'static str; + + fn poll_and_handle_next_tc(&mut self, timestamp: &[u8]) -> HandlingStatus; +} + /// This is a generic handler class for all PUS services where a PUS telecommand is converted /// to a targeted request. /// @@ -297,10 +340,10 @@ where pub fn poll_and_handle_next_tc( &mut self, time_stamp: &[u8], - ) -> Result { + ) -> Result { let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?; if possible_packet.is_none() { - return Ok(PusPacketHandlerResult::Empty); + return Ok(HandlingStatus::Empty); } let ecss_tc_and_token = possible_packet.unwrap(); self.service_helper @@ -356,7 +399,7 @@ where return Err(e.into()); } } - Ok(PusPacketHandlerResult::RequestHandled) + Ok(HandlingStatus::HandledOne) } fn handle_conversion_to_request_error( @@ -409,7 +452,7 @@ where } } - pub fn poll_and_check_next_reply( + pub fn poll_and_handle_next_reply( &mut self, time_stamp: &[u8], ) -> Result { @@ -439,20 +482,17 @@ where return Ok(()); } let active_request = active_req_opt.unwrap(); - let request_finished = self - .reply_handler - .handle_reply( - reply, - active_request, - &self.service_helper.common.tm_sender, - &self.service_helper.common.verif_reporter, - time_stamp, - ) - .unwrap_or(false); - if request_finished { + let result = self.reply_handler.handle_reply( + reply, + active_request, + &self.service_helper.common.tm_sender, + &self.service_helper.common.verif_reporter, + time_stamp, + ); + if result.is_err() || (result.is_ok() && *result.as_ref().unwrap()) { self.active_request_map.remove(reply.request_id()); } - Ok(()) + result.map(|_| ()) } pub fn check_for_request_timeouts(&mut self) { diff --git a/satrs-example/src/pus/mode.rs b/satrs-example/src/pus/mode.rs index 5f3c0ff..56ae9d4 100644 --- a/satrs-example/src/pus/mode.rs +++ b/satrs-example/src/pus/mode.rs @@ -1,5 +1,4 @@ use derive_new::new; -use log::{error, warn}; use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use std::sync::mpsc; use std::time::Duration; @@ -9,7 +8,7 @@ use satrs::pool::SharedStaticMemoryPool; use satrs::pus::verification::VerificationReporter; use satrs::pus::{ DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, - EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlerResult, + EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlingError, PusServiceHelper, }; use satrs::request::GenericMessage; @@ -36,7 +35,7 @@ use satrs::{ ComponentId, }; use satrs_example::config::components::PUS_MODE_SERVICE; -use satrs_example::config::{mode_err, tmtc_err}; +use satrs_example::config::{mode_err, tmtc_err, CustomPusServiceId}; use super::{ create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus, @@ -272,44 +271,26 @@ pub struct ModeServiceWrapper TargetedPusService for ModeServiceWrapper { - /// Returns [true] if the packet handling is finished. - fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus { - match self.service.poll_and_handle_next_tc(time_stamp) { - Ok(result) => match result { - PusPacketHandlerResult::RequestHandled => {} - PusPacketHandlerResult::RequestHandledPartialSuccess(e) => { - warn!("PUS mode service: partial packet handling success: {e:?}") - } - PusPacketHandlerResult::CustomSubservice(invalid, _) => { - warn!("PUS mode service: invalid subservice {invalid}"); - } - PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => { - warn!("PUS mode service: {subservice} not implemented"); - } - PusPacketHandlerResult::Empty => return HandlingStatus::Empty, - }, - Err(error) => { - error!("PUS mode service: packet handling error: {error:?}"); - // To avoid permanent loops on error cases. - return HandlingStatus::Empty; - } + const SERVICE_ID: u8 = CustomPusServiceId::Mode as u8; + const SERVICE_STR: &'static str = "mode"; + + delegate::delegate! { + to self.service { + fn poll_and_handle_next_tc( + &mut self, + time_stamp: &[u8], + ) -> Result; + + fn poll_and_handle_next_reply( + &mut self, + time_stamp: &[u8], + ) -> Result; + + fn check_for_request_timeouts(&mut self); } - HandlingStatus::HandledOne - } - - fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus { - self.service - .poll_and_check_next_reply(time_stamp) - .unwrap_or_else(|e| { - warn!("PUS action service: Handling reply failed with error {e:?}"); - HandlingStatus::HandledOne - }) - } - - fn check_for_request_timeouts(&mut self) { - self.service.check_for_request_timeouts(); } } + #[cfg(test)] mod tests { use satrs::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0, TEST_UNIQUE_ID_0}; diff --git a/satrs-example/src/pus/scheduler.rs b/satrs-example/src/pus/scheduler.rs index 5346e19..eaa03c4 100644 --- a/satrs-example/src/pus/scheduler.rs +++ b/satrs-example/src/pus/scheduler.rs @@ -2,20 +2,22 @@ use std::sync::mpsc; use std::time::Duration; use crate::pus::create_verification_reporter; -use log::{error, info, warn}; +use log::info; use satrs::pool::{PoolProvider, StaticMemoryPool}; use satrs::pus::scheduler::{PusScheduler, TcInfo}; use satrs::pus::scheduler_srv::PusSchedServiceHandler; use satrs::pus::verification::VerificationReporter; use satrs::pus::{ - EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, - EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlerResult, PusServiceHelper, + DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, + EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver, + MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper, }; +use satrs::spacepackets::ecss::PusServiceId; use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool}; use satrs::ComponentId; use satrs_example::config::components::PUS_SCHED_SERVICE; -use super::HandlingStatus; +use super::{DirectPusService, HandlingStatus}; pub trait TcReleaser { fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool; @@ -77,6 +79,61 @@ pub struct SchedulingServiceWrapper, } +impl DirectPusService + for SchedulingServiceWrapper +{ + const SERVICE_ID: u8 = PusServiceId::Verification as u8; + + const SERVICE_STR: &'static str = "verification"; + + fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus { + let error_handler = |partial_error: &PartialPusHandlingError| { + log::warn!( + "PUS {}({}) partial error: {:?}", + Self::SERVICE_ID, + Self::SERVICE_STR, + partial_error + ); + }; + + let result = self.pus_11_handler.poll_and_handle_next_tc( + error_handler, + time_stamp, + &mut self.sched_tc_pool, + ); + if let Err(e) = result { + log::warn!( + "PUS {}({}) error: {:?}", + Self::SERVICE_ID, + Self::SERVICE_STR, + e + ); + // To avoid permanent loops on continuous errors. + return HandlingStatus::Empty; + } + match result.unwrap() { + DirectPusPacketHandlerResult::Handled(handling_status) => return handling_status, + DirectPusPacketHandlerResult::CustomSubservice(subservice, _) => { + log::warn!( + "PUS {}({}) subservice {} not implemented", + Self::SERVICE_ID, + Self::SERVICE_STR, + subservice + ); + } + DirectPusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => { + log::warn!( + "PUS {}({}) subservice {} not implemented", + Self::SERVICE_ID, + Self::SERVICE_STR, + subservice + ); + } + } + HandlingStatus::HandledOne + } +} + impl SchedulingServiceWrapper { @@ -103,31 +160,6 @@ impl info!("{released_tcs} TC(s) released from scheduler"); } } - - pub fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus { - match self - .pus_11_handler - .poll_and_handle_next_tc(time_stamp, &mut self.sched_tc_pool) - { - Ok(result) => match result { - PusPacketHandlerResult::RequestHandled => {} - PusPacketHandlerResult::RequestHandledPartialSuccess(e) => { - warn!("PUS11 partial packet handling success: {e:?}") - } - PusPacketHandlerResult::CustomSubservice(invalid, _) => { - warn!("PUS11 invalid subservice {invalid}"); - } - PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => { - warn!("PUS11: Subservice {subservice} not implemented"); - } - PusPacketHandlerResult::Empty => return HandlingStatus::Empty, - }, - Err(error) => { - error!("PUS packet handling error: {error:?}") - } - } - HandlingStatus::HandledOne - } } pub fn create_scheduler_service_static( diff --git a/satrs-example/src/pus/stack.rs b/satrs-example/src/pus/stack.rs index fac9bce..3594bd6 100644 --- a/satrs-example/src/pus/stack.rs +++ b/satrs-example/src/pus/stack.rs @@ -7,10 +7,12 @@ use satrs::{ use super::{ action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper, - scheduler::SchedulingServiceWrapper, test::TestCustomServiceWrapper, HandlingStatus, - TargetedPusService, + scheduler::SchedulingServiceWrapper, test::TestCustomServiceWrapper, DirectPusService, + HandlingStatus, TargetedPusService, }; +// TODO: For better extensibility, we could create 2 vectors: One for direct PUS services and one +// for targeted services.. #[derive(new)] pub struct PusStack { test_srv: TestCustomServiceWrapper, @@ -28,52 +30,28 @@ impl // Release all telecommands which reached their release time before calling the service // handlers. self.schedule_srv.release_tcs(); - let time_stamp = cds::CdsTime::now_with_u16_days() + let timestamp = cds::CdsTime::now_with_u16_days() .expect("time stamp generation error") .to_vec() .unwrap(); + let mut loop_count = 0_u32; + // Hot loop which will run continuously until all request and reply handling is done. loop { let mut nothing_to_do = true; - let mut is_srv_finished = - |_srv_id: u8, - tc_handling_status: HandlingStatus, - reply_handling_status: Option| { - if tc_handling_status == HandlingStatus::HandledOne - || (reply_handling_status.is_some() - && reply_handling_status.unwrap() == HandlingStatus::HandledOne) - { - nothing_to_do = false; - } - }; - is_srv_finished( - 17, - self.test_srv.poll_and_handle_next_packet(&time_stamp), - None, + Self::direct_service_checker(&mut self.test_srv, ×tamp, &mut nothing_to_do); + Self::direct_service_checker(&mut self.schedule_srv, ×tamp, &mut nothing_to_do); + Self::direct_service_checker(&mut self.event_srv, ×tamp, &mut nothing_to_do); + Self::targeted_service_checker( + &mut self.action_srv_wrapper, + ×tamp, + &mut nothing_to_do, ); - is_srv_finished( - 11, - self.schedule_srv.poll_and_handle_next_tc(&time_stamp), - None, - ); - is_srv_finished(5, self.event_srv.poll_and_handle_next_tc(&time_stamp), None); - is_srv_finished( - 8, - self.action_srv_wrapper.poll_and_handle_next_tc(&time_stamp), - Some( - self.action_srv_wrapper - .poll_and_handle_next_reply(&time_stamp), - ), - ); - is_srv_finished( - 3, - self.hk_srv_wrapper.poll_and_handle_next_tc(&time_stamp), - Some(self.hk_srv_wrapper.poll_and_handle_next_reply(&time_stamp)), - ); - is_srv_finished( - 200, - self.mode_srv.poll_and_handle_next_tc(&time_stamp), - Some(self.mode_srv.poll_and_handle_next_reply(&time_stamp)), + Self::targeted_service_checker( + &mut self.hk_srv_wrapper, + ×tamp, + &mut nothing_to_do, ); + Self::targeted_service_checker(&mut self.mode_srv, ×tamp, &mut nothing_to_do); if nothing_to_do { // Timeout checking is only done once. self.action_srv_wrapper.check_for_request_timeouts(); @@ -81,6 +59,37 @@ impl self.mode_srv.check_for_request_timeouts(); break; } + // Safety mechanism to avoid infinite loops. + loop_count += 1; + if loop_count >= 500 { + log::warn!("reached PUS stack loop count 500, breaking"); + break; + } + } + } + + pub fn direct_service_checker( + service: &mut S, + timestamp: &[u8], + nothing_to_do: &mut bool, + ) { + let handling_status = service.poll_and_handle_next_tc(timestamp); + if handling_status == HandlingStatus::HandledOne { + *nothing_to_do = false; + } + } + + pub fn targeted_service_checker( + service: &mut S, + timestamp: &[u8], + nothing_to_do: &mut bool, + ) { + let request_handling = service.poll_and_handle_next_tc_default_handler(timestamp); + let reply_handling = service.poll_and_handle_next_reply_default_handler(timestamp); + if request_handling == HandlingStatus::HandledOne + || reply_handling == HandlingStatus::HandledOne + { + *nothing_to_do = false; } } } diff --git a/satrs-example/src/pus/test.rs b/satrs-example/src/pus/test.rs index 585e93b..473dc3e 100644 --- a/satrs-example/src/pus/test.rs +++ b/satrs-example/src/pus/test.rs @@ -1,24 +1,22 @@ use crate::pus::create_verification_reporter; -use log::{info, warn}; +use log::info; use satrs::event_man::{EventMessage, EventMessageU32}; use satrs::pool::SharedStaticMemoryPool; use satrs::pus::test::PusService17TestHandler; use satrs::pus::verification::{FailParams, VerificationReporter, VerificationReportingProvider}; -use satrs::pus::EcssTcInSharedStoreConverter; use satrs::pus::{ - EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver, - MpscTmAsVecSender, PusPacketHandlerResult, PusServiceHelper, + DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, + EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusServiceHelper, }; +use satrs::pus::{EcssTcInSharedStoreConverter, PartialPusHandlingError}; use satrs::spacepackets::ecss::tc::PusTcReader; -use satrs::spacepackets::ecss::PusPacket; -use satrs::spacepackets::time::cds::CdsTime; -use satrs::spacepackets::time::TimeWriter; +use satrs::spacepackets::ecss::{PusPacket, PusServiceId}; use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use satrs_example::config::components::PUS_TEST_SERVICE; use satrs_example::config::{tmtc_err, TEST_EVENT}; use std::sync::mpsc; -use super::HandlingStatus; +use super::{DirectPusService, HandlingStatus}; pub fn create_test_service_static( tm_sender: PacketSenderWithSharedPool, @@ -35,7 +33,7 @@ pub fn create_test_service_static( )); TestCustomServiceWrapper { handler: pus17_handler, - test_srv_event_sender: event_sender, + event_tx: event_sender, } } @@ -53,7 +51,7 @@ pub fn create_test_service_dynamic( )); TestCustomServiceWrapper { handler: pus17_handler, - test_srv_event_sender: event_sender, + event_tx: event_sender, } } @@ -61,33 +59,55 @@ pub struct TestCustomServiceWrapper, - pub test_srv_event_sender: mpsc::SyncSender, + pub event_tx: mpsc::SyncSender, } -impl - TestCustomServiceWrapper +impl DirectPusService + for TestCustomServiceWrapper { - pub fn poll_and_handle_next_packet(&mut self, time_stamp: &[u8]) -> HandlingStatus { - let res = self.handler.poll_and_handle_next_tc(time_stamp); - if res.is_err() { - warn!("PUS17 handler failed with error {:?}", res.unwrap_err()); - return HandlingStatus::HandledOne; + const SERVICE_ID: u8 = PusServiceId::Test as u8; + + const SERVICE_STR: &'static str = "test"; + + fn poll_and_handle_next_tc(&mut self, timestamp: &[u8]) -> HandlingStatus { + let error_handler = |partial_error: &PartialPusHandlingError| { + log::warn!( + "PUS {}({}) partial error: {:?}", + Self::SERVICE_ID, + Self::SERVICE_STR, + partial_error + ); + }; + let res = self + .handler + .poll_and_handle_next_tc(error_handler, timestamp); + if let Err(e) = res { + log::warn!( + "PUS {}({}) error: {:?}", + Self::SERVICE_ID, + Self::SERVICE_STR, + e + ); + // To avoid permanent loops on continuous errors. + return HandlingStatus::Empty; } match res.unwrap() { - PusPacketHandlerResult::RequestHandled => { - info!("Received PUS ping command TC[17,1]"); - info!("Sent ping reply PUS TM[17,2]"); + DirectPusPacketHandlerResult::Handled(handling_status) => { + if handling_status == HandlingStatus::HandledOne { + info!("Received PUS ping command TC[17,1]"); + info!("Sent ping reply PUS TM[17,2]"); + } + return handling_status; } - PusPacketHandlerResult::RequestHandledPartialSuccess(partial_err) => { - warn!( - "Handled PUS ping command with partial success: {:?}", - partial_err + DirectPusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => { + log::warn!( + "PUS {}({}) subservice {} not implemented", + Self::SERVICE_ID, + Self::SERVICE_STR, + subservice ); } - PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => { - warn!("PUS17: Subservice {subservice} not implemented") - } - PusPacketHandlerResult::CustomSubservice(subservice, token) => { + DirectPusPacketHandlerResult::CustomSubservice(subservice, token) => { let (tc, _) = PusTcReader::new( self.handler .service_helper @@ -95,29 +115,34 @@ impl .tc_slice_raw(), ) .unwrap(); - let time_stamper = CdsTime::now_with_u16_days().unwrap(); - let mut stamp_buf: [u8; 7] = [0; 7]; - time_stamper.write_to_bytes(&mut stamp_buf).unwrap(); if subservice == 128 { - info!("Generating test event"); - self.test_srv_event_sender + info!("generating test event"); + self.event_tx .send(EventMessage::new(PUS_TEST_SERVICE.id(), TEST_EVENT.into())) .expect("Sending test event failed"); - let start_token = self - .handler - .service_helper - .verif_reporter() - .start_success(self.handler.service_helper.tm_sender(), token, &stamp_buf) - .expect("Error sending start success"); - self.handler - .service_helper - .verif_reporter() - .completion_success( - self.handler.service_helper.tm_sender(), - start_token, - &stamp_buf, - ) - .expect("Error sending completion success"); + match self.handler.service_helper.verif_reporter().start_success( + self.handler.service_helper.tm_sender(), + token, + timestamp, + ) { + Ok(started_token) => { + if let Err(e) = self + .handler + .service_helper + .verif_reporter() + .completion_success( + self.handler.service_helper.tm_sender(), + started_token, + timestamp, + ) + { + error_handler(&PartialPusHandlingError::Verification(e)); + } + } + Err(e) => { + error_handler(&PartialPusHandlingError::Verification(e)); + } + } } else { let fail_data = [tc.subservice()]; self.handler @@ -127,7 +152,7 @@ impl self.handler.service_helper.tm_sender(), token, FailParams::new( - &stamp_buf, + timestamp, &tmtc_err::INVALID_PUS_SUBSERVICE, &fail_data, ), @@ -135,7 +160,6 @@ impl .expect("Sending start failure verification failed"); } } - PusPacketHandlerResult::Empty => return HandlingStatus::Empty, } HandlingStatus::HandledOne } diff --git a/satrs-example/src/tmtc/tc_source.rs b/satrs-example/src/tmtc/tc_source.rs index bd99fb2..94b642c 100644 --- a/satrs-example/src/tmtc/tc_source.rs +++ b/satrs-example/src/tmtc/tc_source.rs @@ -1,12 +1,13 @@ use satrs::{ pool::PoolProvider, + pus::HandlingStatus, tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool}, }; use std::sync::mpsc::{self, TryRecvError}; use satrs::pus::MpscTmAsVecSender; -use crate::pus::{HandlingStatus, PusTcDistributor}; +use crate::pus::PusTcDistributor; // TC source components where static pools are the backing memory of the received telecommands. pub struct TcSourceTaskStatic { diff --git a/satrs-minisim/Cargo.toml b/satrs-minisim/Cargo.toml index cb8e29c..e1449a9 100644 --- a/satrs-minisim/Cargo.toml +++ b/satrs-minisim/Cargo.toml @@ -10,9 +10,14 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" log = "0.4" thiserror = "1" +fern = "0.5" +humantime = "2" [dependencies.asynchronix] version = "0.2.1" +git = "https://github.com/asynchronics/asynchronix.git" +branch = "main" +features = ["serde"] [dependencies.satrs] path = "../satrs" diff --git a/satrs-minisim/src/acs.rs b/satrs-minisim/src/acs.rs index 141840d..77b8cb0 100644 --- a/satrs-minisim/src/acs.rs +++ b/satrs-minisim/src/acs.rs @@ -189,11 +189,11 @@ pub mod tests { #[test] fn test_basic_mgm_request() { let mut sim_testbench = SimTestbench::new(); - let request = SimRequest::new(MgmRequest::RequestSensorData); + let request = SimRequest::new_with_epoch_time(MgmRequest::RequestSensorData); sim_testbench .send_request(request) .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step(); let sim_reply = sim_testbench.try_receive_next_reply(); assert!(sim_reply.is_some()); @@ -212,11 +212,11 @@ pub mod tests { let mut sim_testbench = SimTestbench::new(); switch_device_on(&mut sim_testbench, PcduSwitch::Mgm); - let mut request = SimRequest::new(MgmRequest::RequestSensorData); + let mut request = SimRequest::new_with_epoch_time(MgmRequest::RequestSensorData); sim_testbench .send_request(request) .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step(); let mut sim_reply_res = sim_testbench.try_receive_next_reply(); assert!(sim_reply_res.is_some()); @@ -226,11 +226,11 @@ pub mod tests { .expect("failed to deserialize MGM sensor values"); sim_testbench.step_by(Duration::from_millis(50)); - request = SimRequest::new(MgmRequest::RequestSensorData); + request = SimRequest::new_with_epoch_time(MgmRequest::RequestSensorData); sim_testbench .send_request(request) .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step(); sim_reply_res = sim_testbench.try_receive_next_reply(); assert!(sim_reply_res.is_some()); @@ -245,11 +245,11 @@ pub mod tests { #[test] fn test_basic_mgt_request_is_off() { let mut sim_testbench = SimTestbench::new(); - let request = SimRequest::new(MgtRequest::RequestHk); + let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk); sim_testbench .send_request(request) .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step(); let sim_reply_res = sim_testbench.try_receive_next_reply(); assert!(sim_reply_res.is_none()); @@ -259,12 +259,12 @@ pub mod tests { fn test_basic_mgt_request_is_on() { let mut sim_testbench = SimTestbench::new(); switch_device_on(&mut sim_testbench, PcduSwitch::Mgt); - let request = SimRequest::new(MgtRequest::RequestHk); + let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk); sim_testbench .send_request(request) .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step(); let sim_reply_res = sim_testbench.try_receive_next_reply(); assert!(sim_reply_res.is_some()); @@ -281,11 +281,11 @@ pub mod tests { } fn check_mgt_hk(sim_testbench: &mut SimTestbench, expected_hk_set: MgtHkSet) { - let request = SimRequest::new(MgtRequest::RequestHk); + let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk); sim_testbench .send_request(request) .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step(); let sim_reply_res = sim_testbench.try_receive_next_reply(); assert!(sim_reply_res.is_some()); @@ -309,14 +309,14 @@ pub mod tests { y: 200, z: 1000, }; - let request = SimRequest::new(MgtRequest::ApplyTorque { + let request = SimRequest::new_with_epoch_time(MgtRequest::ApplyTorque { duration: Duration::from_millis(100), dipole: commanded_dipole, }); sim_testbench .send_request(request) .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step_by(Duration::from_millis(5)); check_mgt_hk( diff --git a/satrs-minisim/src/controller.rs b/satrs-minisim/src/controller.rs index d725e45..9255932 100644 --- a/satrs-minisim/src/controller.rs +++ b/satrs-minisim/src/controller.rs @@ -49,25 +49,27 @@ impl SimController { } pub fn run(&mut self, start_time: MonotonicTime, udp_polling_interval_ms: u64) { - let mut t = start_time + Duration::from_millis(udp_polling_interval_ms); - self.sys_clock.synchronize(t); + let mut t = start_time; loop { + let t_old = t; // Check for UDP requests every millisecond. Shift the simulator ahead here to prevent // replies lying in the past. t += Duration::from_millis(udp_polling_interval_ms); + self.sys_clock.synchronize(t); + self.handle_sim_requests(t_old); self.simulation .step_until(t) .expect("simulation step failed"); - self.handle_sim_requests(); - - self.sys_clock.synchronize(t); } } - pub fn handle_sim_requests(&mut self) { + pub fn handle_sim_requests(&mut self, old_timestamp: MonotonicTime) { loop { match self.request_receiver.try_recv() { Ok(request) => { + if request.timestamp < old_timestamp { + log::warn!("stale data with timestamp {:?} received", request.timestamp); + } if let Err(e) = match request.target() { SimTarget::SimCtrl => self.handle_ctrl_request(&request), SimTarget::Mgm => self.handle_mgm_request(&request), @@ -172,11 +174,11 @@ mod tests { #[test] fn test_basic_ping() { let mut sim_testbench = SimTestbench::new(); - let request = SimRequest::new(SimCtrlRequest::Ping); + let request = SimRequest::new_with_epoch_time(SimCtrlRequest::Ping); sim_testbench .send_request(request) .expect("sending sim ctrl request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step(); let sim_reply = sim_testbench.try_receive_next_reply(); assert!(sim_reply.is_some()); diff --git a/satrs-minisim/src/eps.rs b/satrs-minisim/src/eps.rs index 2873348..ebbeb4e 100644 --- a/satrs-minisim/src/eps.rs +++ b/satrs-minisim/src/eps.rs @@ -86,14 +86,14 @@ pub(crate) mod tests { switch: PcduSwitch, target: SwitchStateBinary, ) { - let request = SimRequest::new(PcduRequest::SwitchDevice { + let request = SimRequest::new_with_epoch_time(PcduRequest::SwitchDevice { switch, state: target, }); sim_testbench .send_request(request) .expect("sending MGM switch request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step(); } @@ -113,11 +113,11 @@ pub(crate) mod tests { } fn check_switch_state(sim_testbench: &mut SimTestbench, expected_switch_map: &SwitchMap) { - let request = SimRequest::new(PcduRequest::RequestSwitchInfo); + let request = SimRequest::new_with_epoch_time(PcduRequest::RequestSwitchInfo); sim_testbench .send_request(request) .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step(); let sim_reply = sim_testbench.try_receive_next_reply(); assert!(sim_reply.is_some()); @@ -143,11 +143,11 @@ pub(crate) mod tests { #[test] fn test_pcdu_switcher_request() { let mut sim_testbench = SimTestbench::new(); - let request = SimRequest::new(PcduRequest::RequestSwitchInfo); + let request = SimRequest::new_with_epoch_time(PcduRequest::RequestSwitchInfo); sim_testbench .send_request(request) .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests(); + sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step_by(Duration::from_millis(1)); let sim_reply = sim_testbench.try_receive_next_reply(); diff --git a/satrs-minisim/src/lib.rs b/satrs-minisim/src/lib.rs index 215e5d4..2762326 100644 --- a/satrs-minisim/src/lib.rs +++ b/satrs-minisim/src/lib.rs @@ -1,5 +1,8 @@ +use asynchronix::time::MonotonicTime; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +pub const SIM_CTRL_UDP_PORT: u16 = 7303; + #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum SimTarget { SimCtrl, @@ -19,6 +22,7 @@ pub struct SimMessage { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SimRequest { inner: SimMessage, + pub timestamp: MonotonicTime, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -53,12 +57,22 @@ pub trait SimMessageProvider: Serialize + DeserializeOwned + Clone + Sized { } impl SimRequest { - pub fn new>(serializable_request: T) -> Self { + pub fn new_with_epoch_time>( + serializable_request: T, + ) -> Self { + Self::new(serializable_request, MonotonicTime::EPOCH) + } + + pub fn new>( + serializable_request: T, + timestamp: MonotonicTime, + ) -> Self { Self { inner: SimMessage { target: T::TARGET, payload: serde_json::to_string(&serializable_request).unwrap(), }, + timestamp, } } } @@ -363,7 +377,7 @@ pub mod tests { #[test] fn test_basic_request() { - let sim_request = SimRequest::new(DummyRequest::Ping); + let sim_request = SimRequest::new_with_epoch_time(DummyRequest::Ping); assert_eq!(sim_request.target(), SimTarget::SimCtrl); assert_eq!(sim_request.msg_type(), SimMessageType::Request); let dummy_request = diff --git a/satrs-minisim/src/main.rs b/satrs-minisim/src/main.rs index bfa4a26..59701f7 100644 --- a/satrs-minisim/src/main.rs +++ b/satrs-minisim/src/main.rs @@ -3,7 +3,7 @@ use asynchronix::simulation::{Mailbox, SimInit}; use asynchronix::time::{MonotonicTime, SystemClock}; use controller::SimController; use eps::PcduModel; -use satrs_minisim::{SimReply, SimRequest}; +use satrs_minisim::{SimReply, SimRequest, SIM_CTRL_UDP_PORT}; use std::sync::mpsc; use std::thread; use std::time::{Duration, SystemTime}; @@ -83,14 +83,38 @@ fn main() { let t0 = MonotonicTime::EPOCH; let mut sim_ctrl = create_sim_controller(ThreadingModel::Default, t0, reply_sender, request_receiver); + // Configure logger at runtime + fern::Dispatch::new() + // Perform allocation-free log formatting + .format(|out, message, record| { + out.finish(format_args!( + "[{} {} {}] {}", + humantime::format_rfc3339(std::time::SystemTime::now()), + record.level(), + record.target(), + message + )) + }) + // Add blanket level filter - + .level(log::LevelFilter::Debug) + // - and per-module overrides + // Output to stdout, files, and other Dispatch configurations + .chain(std::io::stdout()) + .chain(fern::log_file("output.log").expect("could not open log output file")) + // Apply globally + .apply() + .expect("could not apply logger configuration"); + log::info!("starting simulation thread"); // This thread schedules the simulator. let sim_thread = thread::spawn(move || { sim_ctrl.run(t0, 1); }); - let mut udp_server = SimUdpServer::new(0, request_sender, reply_receiver, 200, None) - .expect("could not create UDP request server"); + let mut udp_server = + SimUdpServer::new(SIM_CTRL_UDP_PORT, request_sender, reply_receiver, 200, None) + .expect("could not create UDP request server"); + log::info!("starting UDP server on port {}", SIM_CTRL_UDP_PORT); // This thread manages the simulator UDP server. let udp_tc_thread = thread::spawn(move || { udp_server.run(); diff --git a/satrs-minisim/src/test_helpers.rs b/satrs-minisim/src/test_helpers.rs index a8bf0a1..a4459df 100644 --- a/satrs-minisim/src/test_helpers.rs +++ b/satrs-minisim/src/test_helpers.rs @@ -26,10 +26,13 @@ impl SimTestbench { request_sender, } } + pub fn handle_sim_requests_time_agnostic(&mut self) { + self.handle_sim_requests(MonotonicTime::EPOCH); + } delegate! { to self.sim_controller { - pub fn handle_sim_requests(&mut self); + pub fn handle_sim_requests(&mut self, old_timestamp: MonotonicTime); } to self.sim_controller.simulation { pub fn step(&mut self); diff --git a/satrs-minisim/src/udp.rs b/satrs-minisim/src/udp.rs index 36800b7..ad50672 100644 --- a/satrs-minisim/src/udp.rs +++ b/satrs-minisim/src/udp.rs @@ -270,7 +270,7 @@ mod tests { UdpTestbench::new(true, Some(SERVER_WAIT_TIME_MS), 10) .expect("could not create testbench"); let server_thread = std::thread::spawn(move || udp_server.run()); - let sim_request = SimRequest::new(PcduRequest::RequestSwitchInfo); + let sim_request = SimRequest::new_with_epoch_time(PcduRequest::RequestSwitchInfo); udp_testbench .send_request(&sim_request) .expect("sending request failed"); @@ -292,7 +292,7 @@ mod tests { .expect("could not create testbench"); let server_thread = std::thread::spawn(move || udp_server.run()); udp_testbench - .send_request(&SimRequest::new(SimCtrlRequest::Ping)) + .send_request(&SimRequest::new_with_epoch_time(SimCtrlRequest::Ping)) .expect("sending request failed"); let sim_reply = SimReply::new(PcduReply::SwitchInfo(get_all_off_switch_map())); @@ -316,7 +316,7 @@ mod tests { // Send a ping so that the server knows the address of the client. // Do not check that the request arrives on the receiver side, is done by other test. udp_testbench - .send_request(&SimRequest::new(SimCtrlRequest::Ping)) + .send_request(&SimRequest::new_with_epoch_time(SimCtrlRequest::Ping)) .expect("sending request failed"); // Send a reply to the server, ensure it gets forwarded to the client. @@ -347,7 +347,7 @@ mod tests { // Connect by sending a ping. udp_testbench - .send_request(&SimRequest::new(SimCtrlRequest::Ping)) + .send_request(&SimRequest::new_with_epoch_time(SimCtrlRequest::Ping)) .expect("sending request failed"); std::thread::sleep(Duration::from_millis(SERVER_WAIT_TIME_MS)); @@ -376,7 +376,7 @@ mod tests { // Connect by sending a ping. udp_testbench - .send_request(&SimRequest::new(SimCtrlRequest::Ping)) + .send_request(&SimRequest::new_with_epoch_time(SimCtrlRequest::Ping)) .expect("sending request failed"); std::thread::sleep(Duration::from_millis(SERVER_WAIT_TIME_MS)); diff --git a/satrs/CHANGELOG.md b/satrs/CHANGELOG.md index 6124919..6e9d5a6 100644 --- a/satrs/CHANGELOG.md +++ b/satrs/CHANGELOG.md @@ -8,6 +8,36 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +## Changed + +- Renamed `StaticPoolConfig::new` to `StaticPoolConfig::new_from_subpool_cfg_tuples`. The new + `new` implementation expects a type struct instead of tuples. + +## Added + +- `StaticHeaplessMemoryPool` which can be grown with user-provided static buffers. + +# [v0.2.1] 2024-05-19 + +## Changed + +- The HAL TCP server `ServerConfig::new` method now sets the `reuse_port` and `reuse_addr` + fields to `true`. + +## Fixed + +- Possibly subtly broken v0.2.0 build artifact. + +# [v0.2.0] 2024-05-02 + +## Changed + +- Various improvements for the PUS stack components. + +## Added + +- Added `HandlingStatus` enumeration. + # [v0.2.0-rc.5] 2024-04-24 ## Added diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index 94f8e79..ec2b2d0 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "satrs" -version = "0.2.0-rc.5" +version = "0.2.1" edition = "2021" rust-version = "1.71.1" authors = ["Robin Mueller "] @@ -15,6 +15,7 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup [dependencies] delegate = ">0.7, <=0.10" paste = "1" +derive-new = "0.6" smallvec = "1" crc = "3" diff --git a/satrs/src/events.rs b/satrs/src/events.rs index 5544eb2..352bf5e 100644 --- a/satrs/src/events.rs +++ b/satrs/src/events.rs @@ -21,11 +21,11 @@ //! use satrs::events::{EventU16, EventU32, EventU32TypedSev, Severity, SeverityHigh, SeverityInfo}; //! //! const MSG_RECVD: EventU32TypedSev = EventU32TypedSev::new(1, 0); -//! const MSG_FAILED: EventU32 = EventU32::new(Severity::LOW, 1, 1); +//! const MSG_FAILED: EventU32 = EventU32::new(Severity::Low, 1, 1); //! //! const TEMPERATURE_HIGH: EventU32TypedSev = EventU32TypedSev::new(2, 0); //! -//! let small_event = EventU16::new(Severity::INFO, 3, 0); +//! let small_event = EventU16::new(Severity::Info, 3, 0); //! ``` use core::fmt::Debug; use core::hash::Hash; diff --git a/satrs/src/hal/std/tcp_server.rs b/satrs/src/hal/std/tcp_server.rs index 983702a..f1cacae 100644 --- a/satrs/src/hal/std/tcp_server.rs +++ b/satrs/src/hal/std/tcp_server.rs @@ -66,8 +66,8 @@ impl ServerConfig { inner_loop_delay, tm_buffer_size, tc_buffer_size, - reuse_addr: false, - reuse_port: false, + reuse_addr: true, + reuse_port: true, } } } diff --git a/satrs/src/params.rs b/satrs/src/params.rs index da770eb..63135c5 100644 --- a/satrs/src/params.rs +++ b/satrs/src/params.rs @@ -643,52 +643,18 @@ impl From<&str> for Params { } } -/// Please note while [WritableToBeBytes] is implemented for [Params], the default implementation -/// will not be able to process the [Params::Store] parameter variant. -impl WritableToBeBytes for Params { +impl WritableToBeBytes for ParamsHeapless { fn written_len(&self) -> usize { match self { - Params::Heapless(p) => match p { - ParamsHeapless::Raw(raw) => raw.written_len(), - ParamsHeapless::EcssEnum(enumeration) => enumeration.written_len(), - }, - Params::Store(_) => 0, - #[cfg(feature = "alloc")] - Params::Vec(vec) => vec.len(), - #[cfg(feature = "alloc")] - Params::String(string) => string.len(), + ParamsHeapless::Raw(raw) => raw.written_len(), + ParamsHeapless::EcssEnum(ecss_enum) => ecss_enum.written_len(), } } fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { match self { - Params::Heapless(p) => match p { - ParamsHeapless::Raw(raw) => raw.write_to_be_bytes(buf), - ParamsHeapless::EcssEnum(enumeration) => enumeration.write_to_be_bytes(buf), - }, - Params::Store(_) => Ok(0), - #[cfg(feature = "alloc")] - Params::Vec(vec) => { - if buf.len() < vec.len() { - return Err(ByteConversionError::ToSliceTooSmall { - found: buf.len(), - expected: vec.len(), - }); - } - buf[0..vec.len()].copy_from_slice(vec); - Ok(vec.len()) - } - #[cfg(feature = "alloc")] - Params::String(string) => { - if buf.len() < string.len() { - return Err(ByteConversionError::ToSliceTooSmall { - found: buf.len(), - expected: string.len(), - }); - } - buf[0..string.len()].copy_from_slice(string.as_bytes()); - Ok(string.len()) - } + ParamsHeapless::Raw(raw) => raw.write_to_be_bytes(buf), + ParamsHeapless::EcssEnum(ecss_enum) => ecss_enum.write_to_be_bytes(buf), } } } @@ -837,10 +803,9 @@ mod tests { #[test] fn test_params_written_len_raw() { let param_raw = ParamsRaw::from((500_u32, 1000_u32)); - let param: Params = Params::Heapless(param_raw.into()); - assert_eq!(param.written_len(), 8); + assert_eq!(param_raw.written_len(), 8); let mut buf: [u8; 8] = [0; 8]; - param + param_raw .write_to_be_bytes(&mut buf) .expect("writing to buffer failed"); assert_eq!(u32::from_be_bytes(buf[0..4].try_into().unwrap()), 500); @@ -848,21 +813,28 @@ mod tests { } #[test] - fn test_params_written_string() { - let string = "Test String".to_string(); - let param = Params::String(string.clone()); - assert_eq!(param.written_len(), string.len()); - let vec = param.to_vec().unwrap(); - let string_conv_back = String::from_utf8(vec).expect("conversion to string failed"); - assert_eq!(string_conv_back, string); + fn test_heapless_param_writable_trait_raw() { + let param_heapless = ParamsHeapless::Raw(ParamsRaw::from((500_u32, 1000_u32))); + assert_eq!(param_heapless.written_len(), 8); + let mut buf: [u8; 8] = [0; 8]; + let size = param_heapless + .write_to_be_bytes(&mut buf) + .expect("writing failed"); + assert_eq!(size, 8); + assert_eq!(u32::from_be_bytes(buf[0..4].try_into().unwrap()), 500); + assert_eq!(u32::from_be_bytes(buf[4..8].try_into().unwrap()), 1000); } #[test] - fn test_params_written_vec() { - let vec: Vec = alloc::vec![1, 2, 3, 4, 5]; - let param = Params::Vec(vec.clone()); - assert_eq!(param.written_len(), vec.len()); - assert_eq!(param.to_vec().expect("writing vec params failed"), vec); + fn test_heapless_param_writable_trait_ecss_enum() { + let param_heapless = ParamsHeapless::EcssEnum(ParamsEcssEnum::U16(5.into())); + assert_eq!(param_heapless.written_len(), 2); + let mut buf: [u8; 2] = [0; 2]; + let size = param_heapless + .write_to_be_bytes(&mut buf) + .expect("writing failed"); + assert_eq!(size, 2); + assert_eq!(u16::from_be_bytes(buf[0..2].try_into().unwrap()), 5); } #[test] diff --git a/satrs/src/pool.rs b/satrs/src/pool.rs index 77ca66a..e4c2776 100644 --- a/satrs/src/pool.rs +++ b/satrs/src/pool.rs @@ -4,8 +4,11 @@ //! machanism for variable sized data like Telemetry and Telecommand (TMTC) packets. The core //! abstraction for this is the [PoolProvider] trait. //! -//! It also contains the [StaticMemoryPool] as a concrete implementation which can be used to avoid -//! dynamic run-time allocations for the storage of TMTC packets. +//! Currently, two concrete [PoolProvider] implementations are provided: +//! +//! - The [StaticMemoryPool] required [alloc] support but pre-allocated all required memory +//! and does not perform dynamic run-time allocations for the storage of TMTC packets. +//! - The [StaticHeaplessMemoryPool] which can be grown by user provided static storage. //! //! # Example for the [StaticMemoryPool] //! @@ -13,28 +16,28 @@ //! use satrs::pool::{PoolProvider, StaticMemoryPool, StaticPoolConfig}; //! //! // 4 buckets of 4 bytes, 2 of 8 bytes and 1 of 16 bytes -//! let pool_cfg = StaticPoolConfig::new(vec![(4, 4), (2, 8), (1, 16)], false); -//! let mut local_pool = StaticMemoryPool::new(pool_cfg); +//! let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(4, 4), (2, 8), (1, 16)], false); +//! let mut mem_pool = StaticMemoryPool::new(pool_cfg); //! let mut read_buf: [u8; 16] = [0; 16]; //! let mut addr; //! { //! // Add new data to the pool //! let mut example_data = [0; 4]; //! example_data[0] = 42; -//! let res = local_pool.add(&example_data); +//! let res = mem_pool.add(&example_data); //! assert!(res.is_ok()); //! addr = res.unwrap(); //! } //! //! { //! // Read the store data back -//! let res = local_pool.read(&addr, &mut read_buf); +//! let res = mem_pool.read(&addr, &mut read_buf); //! assert!(res.is_ok()); //! let read_bytes = res.unwrap(); //! assert_eq!(read_bytes, 4); //! assert_eq!(read_buf[0], 42); //! // Modify the stored data -//! let res = local_pool.modify(&addr, |buf| { +//! let res = mem_pool.modify(&addr, |buf| { //! buf[0] = 12; //! }); //! assert!(res.is_ok()); @@ -42,7 +45,7 @@ //! //! { //! // Read the modified data back -//! let res = local_pool.read(&addr, &mut read_buf); +//! let res = mem_pool.read(&addr, &mut read_buf); //! assert!(res.is_ok()); //! let read_bytes = res.unwrap(); //! assert_eq!(read_bytes, 4); @@ -50,11 +53,11 @@ //! } //! //! // Delete the stored data -//! local_pool.delete(addr); +//! mem_pool.delete(addr); //! //! // Get a free element in the pool with an appropriate size //! { -//! let res = local_pool.free_element(12, |buf| { +//! let res = mem_pool.free_element(12, |buf| { //! buf[0] = 7; //! }); //! assert!(res.is_ok()); @@ -64,7 +67,7 @@ //! // Read back the data //! { //! // Read the store data back -//! let res = local_pool.read(&addr, &mut read_buf); +//! let res = mem_pool.read(&addr, &mut read_buf); //! assert!(res.is_ok()); //! let read_bytes = res.unwrap(); //! assert_eq!(read_bytes, 12); @@ -75,6 +78,9 @@ pub use alloc_mod::*; use core::fmt::{Display, Formatter}; use delegate::delegate; +use derive_new::new; +#[cfg(feature = "heapless")] +pub use heapless_mod::*; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use spacepackets::ByteConversionError; @@ -127,6 +133,7 @@ impl Display for StaticPoolAddr { #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum StoreIdError { InvalidSubpool(u16), InvalidPacketIdx(u16), @@ -150,11 +157,14 @@ impl Error for StoreIdError {} #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum PoolError { /// Requested data block is too large DataTooLarge(usize), /// The store is full. Contains the index of the full subpool StoreFull(u16), + /// The store can not hold any data. + NoCapacity, /// Store ID is invalid. This also includes partial errors where only the subpool is invalid InvalidStoreId(StoreIdError, Option), /// Valid subpool and packet index, but no data is stored at the given address @@ -171,6 +181,9 @@ impl Display for PoolError { PoolError::DataTooLarge(size) => { write!(f, "data to store with size {size} is too large") } + PoolError::NoCapacity => { + write!(f, "store does not have any capacity") + } PoolError::StoreFull(u16) => { write!(f, "store is too full. index for full subpool: {u16}") } @@ -351,10 +364,396 @@ impl<'a, MemProvider: PoolProvider> PoolRwGuard<'a, MemProvider> { ); } +type UsedBlockSize = usize; +pub const STORE_FREE: UsedBlockSize = UsedBlockSize::MAX; +pub const MAX_BLOCK_SIZE: UsedBlockSize = STORE_FREE - 1; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, new)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SubpoolConfig { + num_blocks: NumBlocks, + block_size: usize, +} + +#[cfg(feature = "heapless")] +pub mod heapless_mod { + use super::*; + + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct PoolIsFull; + + /// Helper macro to generate static buffers for the [crate::pool::StaticHeaplessMemoryPool]. + #[macro_export] + macro_rules! static_subpool { + ($pool_name: ident, $sizes_list_name: ident, $num_blocks: expr, $block_size: expr) => { + static mut $pool_name: core::mem::MaybeUninit<[u8; $num_blocks * $block_size]> = + core::mem::MaybeUninit::new([0; $num_blocks * $block_size]); + static mut $sizes_list_name: core::mem::MaybeUninit<[usize; $num_blocks]> = + core::mem::MaybeUninit::new([$crate::pool::STORE_FREE; $num_blocks]); + }; + ($pool_name: ident, $sizes_list_name: ident, $num_blocks: expr, $block_size: expr, $meta_data: meta) => { + #[$meta_data] + static mut $pool_name: core::mem::MaybeUninit<[u8; $num_blocks * $block_size]> = + core::mem::MaybeUninit::new([0; $num_blocks * $block_size]); + #[$meta_data] + static mut $sizes_list_name: core::mem::MaybeUninit<[usize; $num_blocks]> = + core::mem::MaybeUninit::new([$crate::pool::STORE_FREE; $num_blocks]); + }; + } + + /// A static memory pool similar to [super::StaticMemoryPool] which does not reply on + /// heap allocations. + /// + /// This implementation is empty after constructions and must be grown with user-provided + /// static mutable memory using the [Self::grow] method. The (maximum) number of subpools + /// has to specified via a generic parameter. + /// + /// The [crate::static_subpool] macro can be used to avoid some boilerplate code for + /// specifying the static memory blocks for the pool. + /// + /// ``` + /// use satrs::pool::{PoolProvider, StaticHeaplessMemoryPool}; + /// use satrs::static_subpool; + /// + /// const SUBPOOL_SMALL_NUM_BLOCKS: u16 = 16; + /// const SUBPOOL_SMALL_BLOCK_SIZE: usize = 32; + /// static_subpool!( + /// SUBPOOL_SMALL, + /// SUBPOOL_SMALL_SIZES, + /// SUBPOOL_SMALL_NUM_BLOCKS as usize, + /// SUBPOOL_SMALL_BLOCK_SIZE + /// ); + /// const SUBPOOL_LARGE_NUM_BLOCKS: u16 = 8; + /// const SUBPOOL_LARGE_BLOCK_SIZE: usize = 64; + /// static_subpool!( + /// SUBPOOL_LARGE, + /// SUBPOOL_LARGE_SIZES, + /// SUBPOOL_LARGE_NUM_BLOCKS as usize, + /// SUBPOOL_LARGE_BLOCK_SIZE + /// ); + /// + /// let mut mem_pool: StaticHeaplessMemoryPool<2> = StaticHeaplessMemoryPool::new(true); + /// mem_pool.grow( + /// unsafe { SUBPOOL_SMALL.assume_init_mut() }, + /// unsafe { SUBPOOL_SMALL_SIZES.assume_init_mut() }, + /// SUBPOOL_SMALL_NUM_BLOCKS, + /// false + /// ); + /// mem_pool.grow( + /// unsafe { SUBPOOL_LARGE.assume_init_mut() }, + /// unsafe { SUBPOOL_LARGE_SIZES.assume_init_mut() }, + /// SUBPOOL_LARGE_NUM_BLOCKS, + /// false + /// ); + /// + /// let mut read_buf: [u8; 16] = [0; 16]; + /// let mut addr; + /// { + /// // Add new data to the pool + /// let mut example_data = [0; 4]; + /// example_data[0] = 42; + /// let res = mem_pool.add(&example_data); + /// assert!(res.is_ok()); + /// addr = res.unwrap(); + /// } + /// + /// { + /// // Read the store data back + /// let res = mem_pool.read(&addr, &mut read_buf); + /// assert!(res.is_ok()); + /// let read_bytes = res.unwrap(); + /// assert_eq!(read_bytes, 4); + /// assert_eq!(read_buf[0], 42); + /// // Modify the stored data + /// let res = mem_pool.modify(&addr, |buf| { + /// buf[0] = 12; + /// }); + /// assert!(res.is_ok()); + /// } + /// + /// { + /// // Read the modified data back + /// let res = mem_pool.read(&addr, &mut read_buf); + /// assert!(res.is_ok()); + /// let read_bytes = res.unwrap(); + /// assert_eq!(read_bytes, 4); + /// assert_eq!(read_buf[0], 12); + /// } + /// + /// // Delete the stored data + /// mem_pool.delete(addr); + /// ``` + pub struct StaticHeaplessMemoryPool { + pool: heapless::Vec<(SubpoolConfig, &'static mut [u8]), MAX_NUM_SUBPOOLS>, + sizes_lists: heapless::Vec<&'static mut [UsedBlockSize], MAX_NUM_SUBPOOLS>, + spill_to_higher_subpools: bool, + } + + impl StaticHeaplessMemoryPool { + pub fn new(spill_to_higher_subpools: bool) -> Self { + Self { + pool: heapless::Vec::new(), + sizes_lists: heapless::Vec::new(), + spill_to_higher_subpools, + } + } + + /// Grow the memory pool using statically allocated memory. + /// + /// Please note that this API assumes the static memory was initialized properly by the + /// user. The sizes list in particular must be set to the [super::STORE_FREE] value + /// by the user for the data structure to work properly. This method will take care of + /// setting all the values inside the sizes list depending on the passed parameters. + /// + /// # Parameters + /// + /// * `subpool_memory` - Static memory for a particular subpool to store the actual data. + /// * `sizes_list` - Static sizes list structure to store the size of the data which is + /// actually stored. + /// * `num_blocks ` - The number of memory blocks inside the subpool. + /// * `set_sizes_list_to_all_free` - If this is set to true, the method will take care + /// of setting all values in the sizes list to [super::STORE_FREE]. This does not have + /// to be done if the user initializes the sizes list to that value themselves. + pub fn grow( + &mut self, + subpool_memory: &'static mut [u8], + sizes_list: &'static mut [UsedBlockSize], + num_blocks: NumBlocks, + set_sizes_list_to_all_free: bool, + ) -> Result<(), PoolIsFull> { + assert!( + (subpool_memory.len() % num_blocks as usize) == 0, + "pool slice length must be multiple of number of blocks" + ); + assert!( + num_blocks as usize == sizes_list.len(), + "used block size list slice must be of same length as number of blocks" + ); + let subpool_config = SubpoolConfig { + num_blocks, + block_size: subpool_memory.len() / num_blocks as usize, + }; + self.pool + .push((subpool_config, subpool_memory)) + .map_err(|_| PoolIsFull)?; + if set_sizes_list_to_all_free { + sizes_list.fill(STORE_FREE); + } + self.sizes_lists.push(sizes_list).map_err(|_| PoolIsFull)?; + Ok(()) + } + + fn reserve(&mut self, data_len: usize) -> Result { + if self.pool.is_empty() { + return Err(PoolError::NoCapacity); + } + let mut subpool_idx = self.find_subpool(data_len, 0)?; + + if self.spill_to_higher_subpools { + while let Err(PoolError::StoreFull(_)) = self.find_empty(subpool_idx) { + if (subpool_idx + 1) as usize == self.sizes_lists.len() { + return Err(PoolError::StoreFull(subpool_idx)); + } + subpool_idx += 1; + } + } + + let (slot, size_slot_ref) = self.find_empty(subpool_idx)?; + *size_slot_ref = data_len; + Ok(StaticPoolAddr { + pool_idx: subpool_idx, + packet_idx: slot, + }) + } + + fn addr_check(&self, addr: &StaticPoolAddr) -> Result { + self.validate_addr(addr)?; + let pool_idx = addr.pool_idx as usize; + let size_list = self.sizes_lists.get(pool_idx).unwrap(); + let curr_size = size_list[addr.packet_idx as usize]; + if curr_size == STORE_FREE { + return Err(PoolError::DataDoesNotExist(PoolAddr::from(*addr))); + } + Ok(curr_size) + } + + fn validate_addr(&self, addr: &StaticPoolAddr) -> Result<(), PoolError> { + let pool_idx = addr.pool_idx as usize; + if pool_idx >= self.pool.len() { + return Err(PoolError::InvalidStoreId( + StoreIdError::InvalidSubpool(addr.pool_idx), + Some(PoolAddr::from(*addr)), + )); + } + if addr.packet_idx >= self.pool[addr.pool_idx as usize].0.num_blocks { + return Err(PoolError::InvalidStoreId( + StoreIdError::InvalidPacketIdx(addr.packet_idx), + Some(PoolAddr::from(*addr)), + )); + } + Ok(()) + } + + fn find_subpool(&self, req_size: usize, start_at_subpool: u16) -> Result { + for (i, &(pool_cfg, _)) in self.pool.iter().enumerate() { + if i < start_at_subpool as usize { + continue; + } + if pool_cfg.block_size as usize >= req_size { + return Ok(i as u16); + } + } + Err(PoolError::DataTooLarge(req_size)) + } + + fn write(&mut self, addr: &StaticPoolAddr, data: &[u8]) -> Result<(), PoolError> { + let packet_pos = self.raw_pos(addr).ok_or(PoolError::InternalError(0))?; + let (_elem_size, subpool) = self + .pool + .get_mut(addr.pool_idx as usize) + .ok_or(PoolError::InternalError(1))?; + let pool_slice = &mut subpool[packet_pos..packet_pos + data.len()]; + pool_slice.copy_from_slice(data); + Ok(()) + } + + fn find_empty(&mut self, subpool: u16) -> Result<(u16, &mut usize), PoolError> { + if let Some(size_list) = self.sizes_lists.get_mut(subpool as usize) { + for (i, elem_size) in size_list.iter_mut().enumerate() { + if *elem_size == STORE_FREE { + return Ok((i as u16, elem_size)); + } + } + } else { + return Err(PoolError::InvalidStoreId( + StoreIdError::InvalidSubpool(subpool), + None, + )); + } + Err(PoolError::StoreFull(subpool)) + } + + fn raw_pos(&self, addr: &StaticPoolAddr) -> Option { + let (pool_cfg, _) = self.pool.get(addr.pool_idx as usize)?; + Some(addr.packet_idx as usize * pool_cfg.block_size as usize) + } + } + + impl PoolProvider for StaticHeaplessMemoryPool { + fn add(&mut self, data: &[u8]) -> Result { + let data_len = data.len(); + if data_len > MAX_BLOCK_SIZE { + return Err(PoolError::DataTooLarge(data_len)); + } + let addr = self.reserve(data_len)?; + self.write(&addr, data)?; + Ok(addr.into()) + } + + fn free_element( + &mut self, + len: usize, + mut writer: W, + ) -> Result { + if len > MAX_BLOCK_SIZE { + return Err(PoolError::DataTooLarge(len)); + } + let addr = self.reserve(len)?; + let raw_pos = self.raw_pos(&addr).unwrap(); + let block = + &mut self.pool.get_mut(addr.pool_idx as usize).unwrap().1[raw_pos..raw_pos + len]; + writer(block); + Ok(addr.into()) + } + + fn modify( + &mut self, + addr: &PoolAddr, + mut updater: U, + ) -> Result<(), PoolError> { + let addr = StaticPoolAddr::from(*addr); + let curr_size = self.addr_check(&addr)?; + let raw_pos = self.raw_pos(&addr).unwrap(); + let block = &mut self.pool.get_mut(addr.pool_idx as usize).unwrap().1 + [raw_pos..raw_pos + curr_size]; + updater(block); + Ok(()) + } + + fn read(&self, addr: &PoolAddr, buf: &mut [u8]) -> Result { + let addr = StaticPoolAddr::from(*addr); + let curr_size = self.addr_check(&addr)?; + if buf.len() < curr_size { + return Err(ByteConversionError::ToSliceTooSmall { + found: buf.len(), + expected: curr_size, + } + .into()); + } + let raw_pos = self.raw_pos(&addr).unwrap(); + let block = + &self.pool.get(addr.pool_idx as usize).unwrap().1[raw_pos..raw_pos + curr_size]; + //block.copy_from_slice(&src); + buf[..curr_size].copy_from_slice(block); + Ok(curr_size) + } + + fn delete(&mut self, addr: PoolAddr) -> Result<(), PoolError> { + let addr = StaticPoolAddr::from(addr); + self.addr_check(&addr)?; + let subpool_cfg = self.pool.get(addr.pool_idx as usize).unwrap().0; + let raw_pos = self.raw_pos(&addr).unwrap(); + let block = &mut self.pool.get_mut(addr.pool_idx as usize).unwrap().1 + [raw_pos..raw_pos + subpool_cfg.block_size as usize]; + let size_list = self.sizes_lists.get_mut(addr.pool_idx as usize).unwrap(); + size_list[addr.packet_idx as usize] = STORE_FREE; + block.fill(0); + Ok(()) + } + + fn has_element_at(&self, addr: &PoolAddr) -> Result { + let addr = StaticPoolAddr::from(*addr); + self.validate_addr(&addr)?; + let pool_idx = addr.pool_idx as usize; + let size_list = self.sizes_lists.get(pool_idx).unwrap(); + let curr_size = size_list[addr.packet_idx as usize]; + if curr_size == STORE_FREE { + return Ok(false); + } + Ok(true) + } + + fn len_of_data(&self, addr: &PoolAddr) -> Result { + let addr = StaticPoolAddr::from(*addr); + self.validate_addr(&addr)?; + let pool_idx = addr.pool_idx as usize; + let size_list = self.sizes_lists.get(pool_idx).unwrap(); + let size = size_list[addr.packet_idx as usize]; + Ok(match size { + STORE_FREE => 0, + _ => size, + }) + } + } + + impl PoolProviderWithGuards + for StaticHeaplessMemoryPool + { + fn modify_with_guard(&mut self, addr: PoolAddr) -> PoolRwGuard { + PoolRwGuard::new(self, addr) + } + + fn read_with_guard(&mut self, addr: PoolAddr) -> PoolGuard { + PoolGuard::new(self, addr) + } + } +} + #[cfg(feature = "alloc")] mod alloc_mod { - use super::{PoolGuard, PoolProvider, PoolProviderWithGuards, PoolRwGuard, StaticPoolAddr}; - use crate::pool::{NumBlocks, PoolAddr, PoolError, StoreIdError}; + use super::*; + use crate::pool::{PoolAddr, PoolError, StoreIdError}; use alloc::vec; use alloc::vec::Vec; use spacepackets::ByteConversionError; @@ -364,10 +763,6 @@ mod alloc_mod { #[cfg(feature = "std")] pub type SharedStaticMemoryPool = Arc>; - type PoolSize = usize; - const STORE_FREE: PoolSize = PoolSize::MAX; - pub const POOL_MAX_SIZE: PoolSize = STORE_FREE - 1; - /// Configuration structure of the [static memory pool][StaticMemoryPool] /// /// # Parameters @@ -380,27 +775,42 @@ mod alloc_mod { /// the chocking of larger subpools by underdimensioned smaller subpools. #[derive(Clone)] pub struct StaticPoolConfig { - cfg: Vec<(NumBlocks, usize)>, + cfg: Vec, spill_to_higher_subpools: bool, } impl StaticPoolConfig { - pub fn new(cfg: Vec<(NumBlocks, usize)>, spill_to_higher_subpools: bool) -> Self { + pub fn new(cfg: Vec, spill_to_higher_subpools: bool) -> Self { StaticPoolConfig { cfg, spill_to_higher_subpools, } } - pub fn cfg(&self) -> &Vec<(NumBlocks, usize)> { + pub fn new_from_subpool_cfg_tuples( + cfg: Vec<(NumBlocks, usize)>, + spill_to_higher_subpools: bool, + ) -> Self { + StaticPoolConfig { + cfg: cfg + .iter() + .map(|(num_blocks, block_size)| SubpoolConfig::new(*num_blocks, *block_size)) + .collect(), + spill_to_higher_subpools, + } + } + + pub fn subpool_cfg(&self) -> &Vec { &self.cfg } pub fn sanitize(&mut self) -> usize { - self.cfg - .retain(|&(bucket_num, size)| bucket_num > 0 && size < POOL_MAX_SIZE); - self.cfg - .sort_unstable_by(|(_, sz0), (_, sz1)| sz0.partial_cmp(sz1).unwrap()); + self.cfg.retain(|&subpool_cfg| { + subpool_cfg.num_blocks > 0 && subpool_cfg.block_size < MAX_BLOCK_SIZE + }); + self.cfg.sort_unstable_by(|&cfg0, &cfg1| { + cfg0.block_size.partial_cmp(&cfg1.block_size).unwrap() + }); self.cfg.len() } } @@ -425,7 +835,7 @@ mod alloc_mod { pub struct StaticMemoryPool { pool_cfg: StaticPoolConfig, pool: Vec>, - sizes_lists: Vec>, + sizes_lists: Vec>, } impl StaticMemoryPool { @@ -438,10 +848,10 @@ mod alloc_mod { pool: Vec::with_capacity(subpools_num), sizes_lists: Vec::with_capacity(subpools_num), }; - for &(num_elems, elem_size) in local_pool.pool_cfg.cfg.iter() { - let next_pool_len = elem_size * num_elems as usize; + for &subpool_cfg in local_pool.pool_cfg.cfg.iter() { + let next_pool_len = subpool_cfg.num_blocks as usize * subpool_cfg.block_size; local_pool.pool.push(vec![0; next_pool_len]); - let next_sizes_list_len = num_elems as usize; + let next_sizes_list_len = subpool_cfg.num_blocks as usize; local_pool .sizes_lists .push(vec![STORE_FREE; next_sizes_list_len]); @@ -468,7 +878,7 @@ mod alloc_mod { Some(PoolAddr::from(*addr)), )); } - if addr.packet_idx >= self.pool_cfg.cfg[addr.pool_idx as usize].0 { + if addr.packet_idx >= self.pool_cfg.cfg[addr.pool_idx as usize].num_blocks { return Err(PoolError::InvalidStoreId( StoreIdError::InvalidPacketIdx(addr.packet_idx), Some(PoolAddr::from(*addr)), @@ -498,11 +908,11 @@ mod alloc_mod { } fn find_subpool(&self, req_size: usize, start_at_subpool: u16) -> Result { - for (i, &(_, elem_size)) in self.pool_cfg.cfg.iter().enumerate() { + for (i, &config) in self.pool_cfg.cfg.iter().enumerate() { if i < start_at_subpool as usize { continue; } - if elem_size >= req_size { + if config.block_size >= req_size { return Ok(i as u16); } } @@ -537,15 +947,15 @@ mod alloc_mod { } fn raw_pos(&self, addr: &StaticPoolAddr) -> Option { - let (_, size) = self.pool_cfg.cfg.get(addr.pool_idx as usize)?; - Some(addr.packet_idx as usize * size) + let cfg = self.pool_cfg.cfg.get(addr.pool_idx as usize)?; + Some(addr.packet_idx as usize * cfg.block_size) } } impl PoolProvider for StaticMemoryPool { fn add(&mut self, data: &[u8]) -> Result { let data_len = data.len(); - if data_len > POOL_MAX_SIZE { + if data_len > MAX_BLOCK_SIZE { return Err(PoolError::DataTooLarge(data_len)); } let addr = self.reserve(data_len)?; @@ -558,7 +968,7 @@ mod alloc_mod { len: usize, mut writer: W, ) -> Result { - if len > POOL_MAX_SIZE { + if len > MAX_BLOCK_SIZE { return Err(PoolError::DataTooLarge(len)); } let addr = self.reserve(len)?; @@ -604,7 +1014,12 @@ mod alloc_mod { fn delete(&mut self, addr: PoolAddr) -> Result<(), PoolError> { let addr = StaticPoolAddr::from(addr); self.addr_check(&addr)?; - let block_size = self.pool_cfg.cfg.get(addr.pool_idx as usize).unwrap().1; + let block_size = self + .pool_cfg + .cfg + .get(addr.pool_idx as usize) + .unwrap() + .block_size; let raw_pos = self.raw_pos(&addr).unwrap(); let block = &mut self.pool.get_mut(addr.pool_idx as usize).unwrap() [raw_pos..raw_pos + block_size]; @@ -652,80 +1067,99 @@ mod alloc_mod { #[cfg(test)] mod tests { - use crate::pool::{ - PoolError, PoolGuard, PoolProvider, PoolProviderWithGuards, PoolRwGuard, StaticMemoryPool, - StaticPoolAddr, StaticPoolConfig, StoreIdError, POOL_MAX_SIZE, - }; + use super::*; use std::vec; fn basic_small_pool() -> StaticMemoryPool { // 4 buckets of 4 bytes, 2 of 8 bytes and 1 of 16 bytes - let pool_cfg = StaticPoolConfig::new(vec![(4, 4), (2, 8), (1, 16)], false); + let pool_cfg = + StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(4, 4), (2, 8), (1, 16)], false); StaticMemoryPool::new(pool_cfg) } #[test] fn test_cfg() { // Values where number of buckets is 0 or size is too large should be removed - let mut pool_cfg = StaticPoolConfig::new(vec![(0, 0), (1, 0), (2, POOL_MAX_SIZE)], false); + let mut pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(0, 0), (1, 0), (2, MAX_BLOCK_SIZE)], + false, + ); pool_cfg.sanitize(); - assert_eq!(*pool_cfg.cfg(), vec![(1, 0)]); + assert_eq!(*pool_cfg.subpool_cfg(), vec![SubpoolConfig::new(1, 0)]); + // Entries should be ordered according to bucket size - pool_cfg = StaticPoolConfig::new(vec![(16, 6), (32, 3), (8, 12)], false); + pool_cfg = + StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(16, 6), (32, 3), (8, 12)], false); pool_cfg.sanitize(); - assert_eq!(*pool_cfg.cfg(), vec![(32, 3), (16, 6), (8, 12)]); + assert_eq!( + *pool_cfg.subpool_cfg(), + vec![ + SubpoolConfig::new(32, 3), + SubpoolConfig::new(16, 6), + SubpoolConfig::new(8, 12) + ] + ); + // Unstable sort is used, so order of entries with same block length should not matter - pool_cfg = StaticPoolConfig::new(vec![(12, 12), (14, 16), (10, 12)], false); + pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(12, 12), (14, 16), (10, 12)], + false, + ); pool_cfg.sanitize(); assert!( - *pool_cfg.cfg() == vec![(12, 12), (10, 12), (14, 16)] - || *pool_cfg.cfg() == vec![(10, 12), (12, 12), (14, 16)] + *pool_cfg.subpool_cfg() + == vec![ + SubpoolConfig::new(12, 12), + SubpoolConfig::new(10, 12), + SubpoolConfig::new(14, 16) + ] + || *pool_cfg.subpool_cfg() + == vec![ + SubpoolConfig::new(10, 12), + SubpoolConfig::new(12, 12), + SubpoolConfig::new(14, 16) + ] ); } - #[test] - fn test_add_and_read() { - let mut local_pool = basic_small_pool(); - let mut test_buf: [u8; 16] = [0; 16]; + fn generic_test_add_and_read(pool_provider: &mut impl PoolProvider) { + let mut test_buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; for (i, val) in test_buf.iter_mut().enumerate() { *val = i as u8; } - let mut other_buf: [u8; 16] = [0; 16]; - let addr = local_pool.add(&test_buf).expect("Adding data failed"); + let mut other_buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; + let addr = pool_provider.add(&test_buf).expect("adding data failed"); // Read back data and verify correctness - let res = local_pool.read(&addr, &mut other_buf); + let res = pool_provider.read(&addr, &mut other_buf); assert!(res.is_ok()); let read_len = res.unwrap(); - assert_eq!(read_len, 16); + assert_eq!(read_len, BUF_SIZE); for (i, &val) in other_buf.iter().enumerate() { assert_eq!(val, i as u8); } } - #[test] - fn test_add_smaller_than_full_slot() { - let mut local_pool = basic_small_pool(); + fn generic_test_add_smaller_than_full_slot(pool_provider: &mut impl PoolProvider) { let test_buf: [u8; 12] = [0; 12]; - let addr = local_pool.add(&test_buf).expect("Adding data failed"); - let res = local_pool + let addr = pool_provider.add(&test_buf).expect("adding data failed"); + let res = pool_provider .read(&addr, &mut [0; 12]) .expect("Read back failed"); assert_eq!(res, 12); } - #[test] - fn test_delete() { - let mut local_pool = basic_small_pool(); + fn generic_test_delete(pool_provider: &mut impl PoolProvider) { + // let mut local_pool = basic_small_pool(); let test_buf: [u8; 16] = [0; 16]; - let addr = local_pool.add(&test_buf).expect("Adding data failed"); + let addr = pool_provider.add(&test_buf).expect("Adding data failed"); // Delete the data - let res = local_pool.delete(addr); + let res = pool_provider.delete(addr); assert!(res.is_ok()); let mut writer = |buf: &mut [u8]| { assert_eq!(buf.len(), 12); }; // Verify that the slot is free by trying to get a reference to it - let res = local_pool.free_element(12, &mut writer); + let res = pool_provider.free_element(12, &mut writer); assert!(res.is_ok()); let addr = res.unwrap(); assert_eq!( @@ -737,18 +1171,16 @@ mod tests { ); } - #[test] - fn test_modify() { - let mut local_pool = basic_small_pool(); + fn generic_test_modify(pool_provider: &mut impl PoolProvider) { let mut test_buf: [u8; 16] = [0; 16]; for (i, val) in test_buf.iter_mut().enumerate() { *val = i as u8; } - let addr = local_pool.add(&test_buf).expect("Adding data failed"); + let addr = pool_provider.add(&test_buf).expect("Adding data failed"); { // Verify that the slot is free by trying to get a reference to it - local_pool + pool_provider .modify(&addr, &mut |buf: &mut [u8]| { buf[0] = 0; buf[1] = 0x42; @@ -756,7 +1188,7 @@ mod tests { .expect("Modifying data failed"); } - local_pool + pool_provider .read(&addr, &mut test_buf) .expect("Reading back data failed"); assert_eq!(test_buf[0], 0); @@ -765,31 +1197,27 @@ mod tests { assert_eq!(test_buf[3], 3); } - #[test] - fn test_consecutive_reservation() { - let mut local_pool = basic_small_pool(); + fn generic_test_consecutive_reservation(pool_provider: &mut impl PoolProvider) { // Reserve two smaller blocks consecutively and verify that the third reservation fails - let res = local_pool.free_element(8, |_| {}); + let res = pool_provider.free_element(8, |_| {}); assert!(res.is_ok()); let addr0 = res.unwrap(); - let res = local_pool.free_element(8, |_| {}); + let res = pool_provider.free_element(8, |_| {}); assert!(res.is_ok()); let addr1 = res.unwrap(); - let res = local_pool.free_element(8, |_| {}); + let res = pool_provider.free_element(8, |_| {}); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!(err, PoolError::StoreFull(1)); // Verify that the two deletions are successful - assert!(local_pool.delete(addr0).is_ok()); - assert!(local_pool.delete(addr1).is_ok()); + assert!(pool_provider.delete(addr0).is_ok()); + assert!(pool_provider.delete(addr1).is_ok()); } - #[test] - fn test_read_does_not_exist() { - let local_pool = basic_small_pool(); + fn generic_test_read_does_not_exist(pool_provider: &mut impl PoolProvider) { // Try to access data which does not exist - let res = local_pool.read( + let res = pool_provider.read( &StaticPoolAddr { packet_idx: 0, pool_idx: 0, @@ -804,13 +1232,11 @@ mod tests { )); } - #[test] - fn test_store_full() { - let mut local_pool = basic_small_pool(); + fn generic_test_store_full(pool_provider: &mut impl PoolProvider) { let test_buf: [u8; 16] = [0; 16]; - assert!(local_pool.add(&test_buf).is_ok()); + assert!(pool_provider.add(&test_buf).is_ok()); // The subpool is now full and the call should fail accordingly - let res = local_pool.add(&test_buf); + let res = pool_provider.add(&test_buf); assert!(res.is_err()); let err = res.unwrap_err(); assert!(matches!(err, PoolError::StoreFull { .. })); @@ -819,15 +1245,13 @@ mod tests { } } - #[test] - fn test_invalid_pool_idx() { - let local_pool = basic_small_pool(); + fn generic_test_invalid_pool_idx(pool_provider: &mut impl PoolProvider) { let addr = StaticPoolAddr { pool_idx: 3, packet_idx: 0, } .into(); - let res = local_pool.read(&addr, &mut []); + let res = pool_provider.read(&addr, &mut []); assert!(res.is_err()); let err = res.unwrap_err(); assert!(matches!( @@ -836,15 +1260,13 @@ mod tests { )); } - #[test] - fn test_invalid_packet_idx() { - let local_pool = basic_small_pool(); + fn generic_test_invalid_packet_idx(pool_provider: &mut impl PoolProvider) { let addr = StaticPoolAddr { pool_idx: 2, packet_idx: 1, }; assert_eq!(addr.raw(), 0x00020001); - let res = local_pool.read(&addr.into(), &mut []); + let res = pool_provider.read(&addr.into(), &mut []); assert!(res.is_err()); let err = res.unwrap_err(); assert!(matches!( @@ -853,148 +1275,139 @@ mod tests { )); } - #[test] - fn test_add_too_large() { - let mut local_pool = basic_small_pool(); + fn generic_test_add_too_large(pool_provider: &mut impl PoolProvider) { let data_too_large = [0; 20]; - let res = local_pool.add(&data_too_large); + let res = pool_provider.add(&data_too_large); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!(err, PoolError::DataTooLarge(20)); } - #[test] - fn test_data_too_large_1() { - let mut local_pool = basic_small_pool(); - let res = local_pool.free_element(POOL_MAX_SIZE + 1, |_| {}); + fn generic_test_data_too_large_1(pool_provider: &mut impl PoolProvider) { + let res = pool_provider.free_element(MAX_BLOCK_SIZE + 1, |_| {}); assert!(res.is_err()); - assert_eq!(res.unwrap_err(), PoolError::DataTooLarge(POOL_MAX_SIZE + 1)); + assert_eq!( + res.unwrap_err(), + PoolError::DataTooLarge(MAX_BLOCK_SIZE + 1) + ); } - #[test] - fn test_free_element_too_large() { - let mut local_pool = basic_small_pool(); + fn generic_test_free_element_too_large(pool_provider: &mut impl PoolProvider) { // Try to request a slot which is too large - let res = local_pool.free_element(20, |_| {}); + let res = pool_provider.free_element(20, |_| {}); assert!(res.is_err()); assert_eq!(res.unwrap_err(), PoolError::DataTooLarge(20)); } - #[test] - fn test_pool_guard_deletion_man_creation() { - let mut local_pool = basic_small_pool(); + fn generic_test_pool_guard_deletion_man_creation(pool_provider: &mut impl PoolProvider) { let test_buf: [u8; 16] = [0; 16]; - let addr = local_pool.add(&test_buf).expect("Adding data failed"); - let read_guard = PoolGuard::new(&mut local_pool, addr); + let addr = pool_provider.add(&test_buf).expect("Adding data failed"); + let read_guard = PoolGuard::new(pool_provider, addr); drop(read_guard); - assert!(!local_pool.has_element_at(&addr).expect("Invalid address")); + assert!(!pool_provider + .has_element_at(&addr) + .expect("Invalid address")); } - #[test] - fn test_pool_guard_deletion() { - let mut local_pool = basic_small_pool(); + fn generic_test_pool_guard_deletion(pool_provider: &mut impl PoolProviderWithGuards) { let test_buf: [u8; 16] = [0; 16]; - let addr = local_pool.add(&test_buf).expect("Adding data failed"); - let read_guard = local_pool.read_with_guard(addr); + let addr = pool_provider.add(&test_buf).expect("Adding data failed"); + let read_guard = pool_provider.read_with_guard(addr); drop(read_guard); - assert!(!local_pool.has_element_at(&addr).expect("Invalid address")); + assert!(!pool_provider + .has_element_at(&addr) + .expect("Invalid address")); } - #[test] - fn test_pool_guard_with_release() { - let mut local_pool = basic_small_pool(); + fn generic_test_pool_guard_with_release(pool_provider: &mut impl PoolProviderWithGuards) { let test_buf: [u8; 16] = [0; 16]; - let addr = local_pool.add(&test_buf).expect("Adding data failed"); - let mut read_guard = PoolGuard::new(&mut local_pool, addr); + let addr = pool_provider.add(&test_buf).expect("Adding data failed"); + let mut read_guard = PoolGuard::new(pool_provider, addr); read_guard.release(); drop(read_guard); - assert!(local_pool.has_element_at(&addr).expect("Invalid address")); + assert!(pool_provider + .has_element_at(&addr) + .expect("Invalid address")); } - #[test] - fn test_pool_modify_guard_man_creation() { - let mut local_pool = basic_small_pool(); + fn generic_test_pool_modify_guard_man_creation( + pool_provider: &mut impl PoolProviderWithGuards, + ) { let test_buf: [u8; 16] = [0; 16]; - let addr = local_pool.add(&test_buf).expect("Adding data failed"); - let mut rw_guard = PoolRwGuard::new(&mut local_pool, addr); + let addr = pool_provider.add(&test_buf).expect("Adding data failed"); + let mut rw_guard = PoolRwGuard::new(pool_provider, addr); rw_guard.update(&mut |_| {}).expect("modify failed"); drop(rw_guard); - assert!(!local_pool.has_element_at(&addr).expect("Invalid address")); + assert!(!pool_provider + .has_element_at(&addr) + .expect("Invalid address")); } - #[test] - fn test_pool_modify_guard() { - let mut local_pool = basic_small_pool(); + fn generic_test_pool_modify_guard(pool_provider: &mut impl PoolProviderWithGuards) { let test_buf: [u8; 16] = [0; 16]; - let addr = local_pool.add(&test_buf).expect("Adding data failed"); - let mut rw_guard = local_pool.modify_with_guard(addr); + let addr = pool_provider.add(&test_buf).expect("Adding data failed"); + let mut rw_guard = pool_provider.modify_with_guard(addr); rw_guard.update(&mut |_| {}).expect("modify failed"); drop(rw_guard); - assert!(!local_pool.has_element_at(&addr).expect("Invalid address")); + assert!(!pool_provider + .has_element_at(&addr) + .expect("Invalid address")); } - #[test] - fn modify_pool_index_above_0() { - let mut local_pool = basic_small_pool(); + fn generic_modify_pool_index_above_0(pool_provider: &mut impl PoolProvider) { let test_buf_0: [u8; 4] = [1; 4]; let test_buf_1: [u8; 4] = [2; 4]; let test_buf_2: [u8; 4] = [3; 4]; let test_buf_3: [u8; 4] = [4; 4]; - let addr0 = local_pool.add(&test_buf_0).expect("Adding data failed"); - let addr1 = local_pool.add(&test_buf_1).expect("Adding data failed"); - let addr2 = local_pool.add(&test_buf_2).expect("Adding data failed"); - let addr3 = local_pool.add(&test_buf_3).expect("Adding data failed"); - local_pool + let addr0 = pool_provider.add(&test_buf_0).expect("Adding data failed"); + let addr1 = pool_provider.add(&test_buf_1).expect("Adding data failed"); + let addr2 = pool_provider.add(&test_buf_2).expect("Adding data failed"); + let addr3 = pool_provider.add(&test_buf_3).expect("Adding data failed"); + pool_provider .modify(&addr0, |buf| { assert_eq!(buf, test_buf_0); }) .expect("Modifying data failed"); - local_pool + pool_provider .modify(&addr1, |buf| { assert_eq!(buf, test_buf_1); }) .expect("Modifying data failed"); - local_pool + pool_provider .modify(&addr2, |buf| { assert_eq!(buf, test_buf_2); }) .expect("Modifying data failed"); - local_pool + pool_provider .modify(&addr3, |buf| { assert_eq!(buf, test_buf_3); }) .expect("Modifying data failed"); } - #[test] - fn test_spills_to_higher_subpools() { - let pool_cfg = StaticPoolConfig::new(vec![(2, 8), (2, 16)], true); - let mut local_pool = StaticMemoryPool::new(pool_cfg); - local_pool.free_element(8, |_| {}).unwrap(); - local_pool.free_element(8, |_| {}).unwrap(); - let mut in_larger_subpool_now = local_pool.free_element(8, |_| {}); + fn generic_test_spills_to_higher_subpools(pool_provider: &mut impl PoolProvider) { + pool_provider.free_element(8, |_| {}).unwrap(); + pool_provider.free_element(8, |_| {}).unwrap(); + let mut in_larger_subpool_now = pool_provider.free_element(8, |_| {}); assert!(in_larger_subpool_now.is_ok()); let generic_addr = in_larger_subpool_now.unwrap(); let pool_addr = StaticPoolAddr::from(generic_addr); assert_eq!(pool_addr.pool_idx, 1); assert_eq!(pool_addr.packet_idx, 0); - assert!(local_pool.has_element_at(&generic_addr).unwrap()); - in_larger_subpool_now = local_pool.free_element(8, |_| {}); + assert!(pool_provider.has_element_at(&generic_addr).unwrap()); + in_larger_subpool_now = pool_provider.free_element(8, |_| {}); assert!(in_larger_subpool_now.is_ok()); let generic_addr = in_larger_subpool_now.unwrap(); let pool_addr = StaticPoolAddr::from(generic_addr); assert_eq!(pool_addr.pool_idx, 1); assert_eq!(pool_addr.packet_idx, 1); - assert!(local_pool.has_element_at(&generic_addr).unwrap()); + assert!(pool_provider.has_element_at(&generic_addr).unwrap()); } - #[test] - fn test_spillage_fails_as_well() { - let pool_cfg = StaticPoolConfig::new(vec![(1, 8), (1, 16)], true); - let mut local_pool = StaticMemoryPool::new(pool_cfg); - local_pool.free_element(8, |_| {}).unwrap(); - local_pool.free_element(8, |_| {}).unwrap(); - let should_fail = local_pool.free_element(8, |_| {}); + fn generic_test_spillage_fails_as_well(pool_provider: &mut impl PoolProvider) { + pool_provider.free_element(8, |_| {}).unwrap(); + pool_provider.free_element(8, |_| {}).unwrap(); + let should_fail = pool_provider.free_element(8, |_| {}); assert!(should_fail.is_err()); if let Err(err) = should_fail { assert_eq!(err, PoolError::StoreFull(1)); @@ -1003,29 +1416,23 @@ mod tests { } } - #[test] - fn test_spillage_works_across_multiple_subpools() { - let pool_cfg = StaticPoolConfig::new(vec![(1, 8), (1, 12), (1, 16)], true); - let mut local_pool = StaticMemoryPool::new(pool_cfg); - local_pool.free_element(8, |_| {}).unwrap(); - local_pool.free_element(12, |_| {}).unwrap(); - let in_larger_subpool_now = local_pool.free_element(8, |_| {}); + fn generic_test_spillage_works_across_multiple_subpools(pool_provider: &mut impl PoolProvider) { + pool_provider.free_element(8, |_| {}).unwrap(); + pool_provider.free_element(12, |_| {}).unwrap(); + let in_larger_subpool_now = pool_provider.free_element(8, |_| {}); assert!(in_larger_subpool_now.is_ok()); let generic_addr = in_larger_subpool_now.unwrap(); let pool_addr = StaticPoolAddr::from(generic_addr); assert_eq!(pool_addr.pool_idx, 2); assert_eq!(pool_addr.packet_idx, 0); - assert!(local_pool.has_element_at(&generic_addr).unwrap()); + assert!(pool_provider.has_element_at(&generic_addr).unwrap()); } - #[test] - fn test_spillage_fails_across_multiple_subpools() { - let pool_cfg = StaticPoolConfig::new(vec![(1, 8), (1, 12), (1, 16)], true); - let mut local_pool = StaticMemoryPool::new(pool_cfg); - local_pool.free_element(8, |_| {}).unwrap(); - local_pool.free_element(12, |_| {}).unwrap(); - local_pool.free_element(16, |_| {}).unwrap(); - let should_fail = local_pool.free_element(8, |_| {}); + fn generic_test_spillage_fails_across_multiple_subpools(pool_provider: &mut impl PoolProvider) { + pool_provider.free_element(8, |_| {}).unwrap(); + pool_provider.free_element(12, |_| {}).unwrap(); + pool_provider.free_element(16, |_| {}).unwrap(); + let should_fail = pool_provider.free_element(8, |_| {}); assert!(should_fail.is_err()); if let Err(err) = should_fail { assert_eq!(err, PoolError::StoreFull(2)); @@ -1033,4 +1440,448 @@ mod tests { panic!("unexpected store address"); } } + + #[test] + fn test_add_and_read() { + let mut local_pool = basic_small_pool(); + generic_test_add_and_read::<16>(&mut local_pool); + } + + #[test] + fn test_add_smaller_than_full_slot() { + let mut local_pool = basic_small_pool(); + generic_test_add_smaller_than_full_slot(&mut local_pool); + } + + #[test] + fn test_delete() { + let mut local_pool = basic_small_pool(); + generic_test_delete(&mut local_pool); + } + + #[test] + fn test_modify() { + let mut local_pool = basic_small_pool(); + generic_test_modify(&mut local_pool); + } + + #[test] + fn test_consecutive_reservation() { + let mut local_pool = basic_small_pool(); + generic_test_consecutive_reservation(&mut local_pool); + } + + #[test] + fn test_read_does_not_exist() { + let mut local_pool = basic_small_pool(); + generic_test_read_does_not_exist(&mut local_pool); + } + + #[test] + fn test_store_full() { + let mut local_pool = basic_small_pool(); + generic_test_store_full(&mut local_pool); + } + + #[test] + fn test_invalid_pool_idx() { + let mut local_pool = basic_small_pool(); + generic_test_invalid_pool_idx(&mut local_pool); + } + + #[test] + fn test_invalid_packet_idx() { + let mut local_pool = basic_small_pool(); + generic_test_invalid_packet_idx(&mut local_pool); + } + + #[test] + fn test_add_too_large() { + let mut local_pool = basic_small_pool(); + generic_test_add_too_large(&mut local_pool); + } + + #[test] + fn test_data_too_large_1() { + let mut local_pool = basic_small_pool(); + generic_test_data_too_large_1(&mut local_pool); + } + + #[test] + fn test_free_element_too_large() { + let mut local_pool = basic_small_pool(); + generic_test_free_element_too_large(&mut local_pool); + } + + #[test] + fn test_pool_guard_deletion_man_creation() { + let mut local_pool = basic_small_pool(); + generic_test_pool_guard_deletion_man_creation(&mut local_pool); + } + + #[test] + fn test_pool_guard_deletion() { + let mut local_pool = basic_small_pool(); + generic_test_pool_guard_deletion(&mut local_pool); + } + + #[test] + fn test_pool_guard_with_release() { + let mut local_pool = basic_small_pool(); + generic_test_pool_guard_with_release(&mut local_pool); + } + + #[test] + fn test_pool_modify_guard_man_creation() { + let mut local_pool = basic_small_pool(); + generic_test_pool_modify_guard_man_creation(&mut local_pool); + } + + #[test] + fn test_pool_modify_guard() { + let mut local_pool = basic_small_pool(); + generic_test_pool_modify_guard(&mut local_pool); + } + + #[test] + fn modify_pool_index_above_0() { + let mut local_pool = basic_small_pool(); + generic_modify_pool_index_above_0(&mut local_pool); + } + + #[test] + fn test_spills_to_higher_subpools() { + let subpool_config_vec = vec![SubpoolConfig::new(2, 8), SubpoolConfig::new(2, 16)]; + let pool_cfg = StaticPoolConfig::new(subpool_config_vec, true); + let mut local_pool = StaticMemoryPool::new(pool_cfg); + generic_test_spills_to_higher_subpools(&mut local_pool); + } + + #[test] + fn test_spillage_fails_as_well() { + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(1, 8), (1, 16)], true); + let mut local_pool = StaticMemoryPool::new(pool_cfg); + generic_test_spillage_fails_as_well(&mut local_pool); + } + + #[test] + fn test_spillage_works_across_multiple_subpools() { + let pool_cfg = + StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(1, 8), (1, 12), (1, 16)], true); + let mut local_pool = StaticMemoryPool::new(pool_cfg); + generic_test_spillage_works_across_multiple_subpools(&mut local_pool); + } + + #[test] + fn test_spillage_fails_across_multiple_subpools() { + let pool_cfg = + StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(1, 8), (1, 12), (1, 16)], true); + let mut local_pool = StaticMemoryPool::new(pool_cfg); + generic_test_spillage_fails_across_multiple_subpools(&mut local_pool); + } + + #[cfg(feature = "heapless")] + mod heapless_tests { + use super::*; + use crate::static_subpool; + use core::mem::MaybeUninit; + + const SUBPOOL_1_BLOCK_SIZE: usize = 4; + const SUBPOOL_1_NUM_ELEMENTS: u16 = 4; + static mut SUBPOOL_1: MaybeUninit< + [u8; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE], + > = MaybeUninit::new([0; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE]); + static mut SUBPOOL_1_SIZES: MaybeUninit<[usize; SUBPOOL_1_NUM_ELEMENTS as usize]> = + MaybeUninit::new([STORE_FREE; SUBPOOL_1_NUM_ELEMENTS as usize]); + + const SUBPOOL_2_NUM_ELEMENTS: u16 = 2; + const SUBPOOL_2_BLOCK_SIZE: usize = 8; + static mut SUBPOOL_2: MaybeUninit< + [u8; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE], + > = MaybeUninit::new([0; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE]); + static mut SUBPOOL_2_SIZES: MaybeUninit<[usize; SUBPOOL_2_NUM_ELEMENTS as usize]> = + MaybeUninit::new([STORE_FREE; SUBPOOL_2_NUM_ELEMENTS as usize]); + + const SUBPOOL_3_NUM_ELEMENTS: u16 = 1; + const SUBPOOL_3_BLOCK_SIZE: usize = 16; + static_subpool!( + SUBPOOL_3, + SUBPOOL_3_SIZES, + SUBPOOL_3_NUM_ELEMENTS as usize, + SUBPOOL_3_BLOCK_SIZE + ); + + const SUBPOOL_4_NUM_ELEMENTS: u16 = 2; + const SUBPOOL_4_BLOCK_SIZE: usize = 16; + static_subpool!( + SUBPOOL_4, + SUBPOOL_4_SIZES, + SUBPOOL_4_NUM_ELEMENTS as usize, + SUBPOOL_4_BLOCK_SIZE + ); + + const SUBPOOL_5_NUM_ELEMENTS: u16 = 1; + const SUBPOOL_5_BLOCK_SIZE: usize = 8; + static_subpool!( + SUBPOOL_5, + SUBPOOL_5_SIZES, + SUBPOOL_5_NUM_ELEMENTS as usize, + SUBPOOL_5_BLOCK_SIZE + ); + + const SUBPOOL_6_NUM_ELEMENTS: u16 = 1; + const SUBPOOL_6_BLOCK_SIZE: usize = 12; + static_subpool!( + SUBPOOL_6, + SUBPOOL_6_SIZES, + SUBPOOL_6_NUM_ELEMENTS as usize, + SUBPOOL_6_BLOCK_SIZE + ); + + fn small_heapless_pool() -> StaticHeaplessMemoryPool<3> { + let mut heapless_pool: StaticHeaplessMemoryPool<3> = + StaticHeaplessMemoryPool::new(false); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_1.assume_init_mut() }, + unsafe { SUBPOOL_1_SIZES.assume_init_mut() }, + SUBPOOL_1_NUM_ELEMENTS, + false + ) + .is_ok()); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_2.assume_init_mut() }, + unsafe { SUBPOOL_2_SIZES.assume_init_mut() }, + SUBPOOL_2_NUM_ELEMENTS, + false + ) + .is_ok()); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_3.assume_init_mut() }, + unsafe { SUBPOOL_3_SIZES.assume_init_mut() }, + SUBPOOL_3_NUM_ELEMENTS, + false + ) + .is_ok()); + heapless_pool + } + + #[test] + fn test_heapless_add_and_read() { + let mut pool_provider = small_heapless_pool(); + generic_test_add_and_read::<16>(&mut pool_provider); + } + + #[test] + fn test_add_smaller_than_full_slot() { + let mut pool_provider = small_heapless_pool(); + generic_test_add_smaller_than_full_slot(&mut pool_provider); + } + + #[test] + fn test_delete() { + let mut pool_provider = small_heapless_pool(); + generic_test_delete(&mut pool_provider); + } + + #[test] + fn test_modify() { + let mut pool_provider = small_heapless_pool(); + generic_test_modify(&mut pool_provider); + } + + #[test] + fn test_consecutive_reservation() { + let mut pool_provider = small_heapless_pool(); + generic_test_consecutive_reservation(&mut pool_provider); + } + + #[test] + fn test_read_does_not_exist() { + let mut pool_provider = small_heapless_pool(); + generic_test_read_does_not_exist(&mut pool_provider); + } + + #[test] + fn test_store_full() { + let mut pool_provider = small_heapless_pool(); + generic_test_store_full(&mut pool_provider); + } + + #[test] + fn test_invalid_pool_idx() { + let mut pool_provider = small_heapless_pool(); + generic_test_invalid_pool_idx(&mut pool_provider); + } + + #[test] + fn test_invalid_packet_idx() { + let mut pool_provider = small_heapless_pool(); + generic_test_invalid_packet_idx(&mut pool_provider); + } + + #[test] + fn test_add_too_large() { + let mut pool_provider = small_heapless_pool(); + generic_test_add_too_large(&mut pool_provider); + } + + #[test] + fn test_data_too_large_1() { + let mut pool_provider = small_heapless_pool(); + generic_test_data_too_large_1(&mut pool_provider); + } + + #[test] + fn test_free_element_too_large() { + let mut pool_provider = small_heapless_pool(); + generic_test_free_element_too_large(&mut pool_provider); + } + + #[test] + fn test_pool_guard_deletion_man_creation() { + let mut pool_provider = small_heapless_pool(); + generic_test_pool_guard_deletion_man_creation(&mut pool_provider); + } + + #[test] + fn test_pool_guard_deletion() { + let mut pool_provider = small_heapless_pool(); + generic_test_pool_guard_deletion(&mut pool_provider); + } + + #[test] + fn test_pool_guard_with_release() { + let mut pool_provider = small_heapless_pool(); + generic_test_pool_guard_with_release(&mut pool_provider); + } + + #[test] + fn test_pool_modify_guard_man_creation() { + let mut pool_provider = small_heapless_pool(); + generic_test_pool_modify_guard_man_creation(&mut pool_provider); + } + + #[test] + fn test_pool_modify_guard() { + let mut pool_provider = small_heapless_pool(); + generic_test_pool_modify_guard(&mut pool_provider); + } + + #[test] + fn modify_pool_index_above_0() { + let mut pool_provider = small_heapless_pool(); + generic_modify_pool_index_above_0(&mut pool_provider); + } + + #[test] + fn test_spills_to_higher_subpools() { + let mut heapless_pool: StaticHeaplessMemoryPool<2> = + StaticHeaplessMemoryPool::new(true); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_2.assume_init_mut() }, + unsafe { SUBPOOL_2_SIZES.assume_init_mut() }, + SUBPOOL_2_NUM_ELEMENTS, + false + ) + .is_ok()); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_4.assume_init_mut() }, + unsafe { SUBPOOL_4_SIZES.assume_init_mut() }, + SUBPOOL_4_NUM_ELEMENTS, + false + ) + .is_ok()); + generic_test_spills_to_higher_subpools(&mut heapless_pool); + } + + #[test] + fn test_spillage_fails_as_well() { + let mut heapless_pool: StaticHeaplessMemoryPool<2> = + StaticHeaplessMemoryPool::new(true); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_5.assume_init_mut() }, + unsafe { SUBPOOL_5_SIZES.assume_init_mut() }, + SUBPOOL_5_NUM_ELEMENTS, + false + ) + .is_ok()); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_3.assume_init_mut() }, + unsafe { SUBPOOL_3_SIZES.assume_init_mut() }, + SUBPOOL_3_NUM_ELEMENTS, + false + ) + .is_ok()); + generic_test_spillage_fails_as_well(&mut heapless_pool); + } + + #[test] + fn test_spillage_works_across_multiple_subpools() { + let mut heapless_pool: StaticHeaplessMemoryPool<3> = + StaticHeaplessMemoryPool::new(true); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_5.assume_init_mut() }, + unsafe { SUBPOOL_5_SIZES.assume_init_mut() }, + SUBPOOL_5_NUM_ELEMENTS, + false + ) + .is_ok()); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_6.assume_init_mut() }, + unsafe { SUBPOOL_6_SIZES.assume_init_mut() }, + SUBPOOL_6_NUM_ELEMENTS, + false + ) + .is_ok()); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_3.assume_init_mut() }, + unsafe { SUBPOOL_3_SIZES.assume_init_mut() }, + SUBPOOL_3_NUM_ELEMENTS, + false + ) + .is_ok()); + generic_test_spillage_works_across_multiple_subpools(&mut heapless_pool); + } + + #[test] + fn test_spillage_fails_across_multiple_subpools() { + let mut heapless_pool: StaticHeaplessMemoryPool<3> = + StaticHeaplessMemoryPool::new(true); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_5.assume_init_mut() }, + unsafe { SUBPOOL_5_SIZES.assume_init_mut() }, + SUBPOOL_5_NUM_ELEMENTS, + false + ) + .is_ok()); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_6.assume_init_mut() }, + unsafe { SUBPOOL_6_SIZES.assume_init_mut() }, + SUBPOOL_6_NUM_ELEMENTS, + false + ) + .is_ok()); + assert!(heapless_pool + .grow( + unsafe { SUBPOOL_3.assume_init_mut() }, + unsafe { SUBPOOL_3_SIZES.assume_init_mut() }, + SUBPOOL_3_NUM_ELEMENTS, + false + ) + .is_ok()); + generic_test_spillage_fails_across_multiple_subpools(&mut heapless_pool); + } + } } diff --git a/satrs/src/pus/action.rs b/satrs/src/pus/action.rs index 75d5962..6bcd270 100644 --- a/satrs/src/pus/action.rs +++ b/satrs/src/pus/action.rs @@ -54,11 +54,11 @@ pub type GenericActionReplyPus = GenericMessage; impl GenericActionReplyPus { pub fn new_action_reply( - requestor_info: MessageMetadata, + replier_info: MessageMetadata, action_id: ActionId, reply: ActionReplyVariant, ) -> Self { - Self::new(requestor_info, ActionReplyPus::new(action_id, reply)) + Self::new(replier_info, ActionReplyPus::new(action_id, reply)) } } diff --git a/satrs/src/pus/event.rs b/satrs/src/pus/event.rs index 578167f..a6301e2 100644 --- a/satrs/src/pus/event.rs +++ b/satrs/src/pus/event.rs @@ -407,7 +407,7 @@ mod tests { severity_to_subservice(severity) as u8 ); assert_eq!(tm_info.common.dest_id, 0); - assert_eq!(tm_info.common.time_stamp, time_stamp_empty); + assert_eq!(tm_info.common.timestamp, time_stamp_empty); assert_eq!(tm_info.common.msg_counter, 0); assert_eq!(tm_info.common.apid, EXAMPLE_APID); assert_eq!(tm_info.event, event); diff --git a/satrs/src/pus/event_man.rs b/satrs/src/pus/event_man.rs index c8aec2d..8de8f6f 100644 --- a/satrs/src/pus/event_man.rs +++ b/satrs/src/pus/event_man.rs @@ -50,12 +50,6 @@ pub mod heapless_mod { phantom: PhantomData, } - /// Safety: All contained field are [Send] as well - unsafe impl Send - for HeaplessPusMgmtBackendProvider - { - } - impl PusEventReportingMapProvider for HeaplessPusMgmtBackendProvider { @@ -107,6 +101,7 @@ pub mod alloc_mod { use crate::{ events::EventU16, + params::{Params, WritableToBeBytes}, pus::event::{DummyEventHook, EventTmHookProvider}, }; @@ -147,6 +142,12 @@ pub mod alloc_mod { } } + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct EventGenerationResult { + pub event_was_enabled: bool, + pub params_were_propagated: bool, + } + pub struct PusEventTmCreatorWithMap< ReportingMap: PusEventReportingMapProvider, Event: GenericEvent, @@ -212,6 +213,53 @@ pub mod alloc_mod { .map_err(|e| e.into()), } } + + pub fn generate_pus_event_tm_generic_with_generic_params( + &self, + sender: &(impl EcssTmSender + ?Sized), + time_stamp: &[u8], + event: Event, + small_data_buf: &mut [u8], + params: Option<&Params>, + ) -> Result { + let mut result = EventGenerationResult { + event_was_enabled: false, + params_were_propagated: true, + }; + if params.is_none() { + result.event_was_enabled = + self.generate_pus_event_tm_generic(sender, time_stamp, event, None)?; + return Ok(result); + } + let params = params.unwrap(); + result.event_was_enabled = match params { + Params::Heapless(heapless_param) => { + heapless_param + .write_to_be_bytes(&mut small_data_buf[..heapless_param.written_len()]) + .map_err(EcssTmtcError::ByteConversion)?; + self.generate_pus_event_tm_generic( + sender, + time_stamp, + event, + Some(small_data_buf), + )? + } + Params::Vec(vec) => { + self.generate_pus_event_tm_generic(sender, time_stamp, event, Some(vec))? + } + Params::String(string) => self.generate_pus_event_tm_generic( + sender, + time_stamp, + event, + Some(string.as_bytes()), + )?, + _ => { + result.params_were_propagated = false; + self.generate_pus_event_tm_generic(sender, time_stamp, event, None)? + } + }; + Ok(result) + } } impl @@ -261,6 +309,12 @@ pub mod alloc_mod { } #[cfg(test)] mod tests { + use alloc::string::{String, ToString}; + use alloc::vec; + use spacepackets::ecss::event::Subservice; + use spacepackets::ecss::tm::PusTmReader; + use spacepackets::ecss::PusPacket; + use super::*; use crate::request::UniqueApidTargetId; use crate::{events::SeverityInfo, tmtc::PacketAsVec}; @@ -336,4 +390,70 @@ mod tests { assert!(event_sent); event_rx.try_recv().expect("No info event received"); } + + #[test] + fn test_event_with_generic_string_param() { + let event_man = create_basic_man_1(); + let mut small_data_buf = [0; 128]; + let param_data = "hello world"; + let (event_tx, event_rx) = mpsc::channel::(); + let res = event_man.generate_pus_event_tm_generic_with_generic_params( + &event_tx, + &EMPTY_STAMP, + INFO_EVENT.into(), + &mut small_data_buf, + Some(¶m_data.to_string().into()), + ); + assert!(res.is_ok()); + let res = res.unwrap(); + assert!(res.event_was_enabled); + assert!(res.params_were_propagated); + let event_tm = event_rx.try_recv().expect("no event received"); + let (tm, _) = PusTmReader::new(&event_tm.packet, 7).expect("reading TM failed"); + assert_eq!(tm.service(), 5); + assert_eq!(tm.subservice(), Subservice::TmInfoReport as u8); + assert_eq!(tm.user_data().len(), 4 + param_data.len()); + let u32_event = u32::from_be_bytes(tm.user_data()[0..4].try_into().unwrap()); + assert_eq!(u32_event, INFO_EVENT.raw()); + let string_data = String::from_utf8_lossy(&tm.user_data()[4..]); + assert_eq!(string_data, param_data); + } + + #[test] + fn test_event_with_generic_vec_param() { + let event_man = create_basic_man_1(); + let mut small_data_buf = [0; 128]; + let param_data = vec![1, 2, 3, 4]; + let (event_tx, event_rx) = mpsc::channel::(); + let res = event_man.generate_pus_event_tm_generic_with_generic_params( + &event_tx, + &EMPTY_STAMP, + INFO_EVENT.into(), + &mut small_data_buf, + Some(¶m_data.clone().into()), + ); + assert!(res.is_ok()); + let res = res.unwrap(); + assert!(res.event_was_enabled); + assert!(res.params_were_propagated); + let event_tm = event_rx.try_recv().expect("no event received"); + let (tm, _) = PusTmReader::new(&event_tm.packet, 7).expect("reading TM failed"); + assert_eq!(tm.service(), 5); + assert_eq!(tm.subservice(), Subservice::TmInfoReport as u8); + assert_eq!(tm.user_data().len(), 4 + param_data.len()); + let u32_event = u32::from_be_bytes(tm.user_data()[0..4].try_into().unwrap()); + assert_eq!(u32_event, INFO_EVENT.raw()); + let vec_data = tm.user_data()[4..].to_vec(); + assert_eq!(vec_data, param_data); + } + + #[test] + fn test_event_with_generic_store_param_not_propagated() { + // TODO: Test this. + } + + #[test] + fn test_event_with_generic_heapless_param() { + // TODO: Test this. + } } diff --git a/satrs/src/pus/event_srv.rs b/satrs/src/pus/event_srv.rs index 8ea54ec..cb1bcb5 100644 --- a/satrs/src/pus/event_srv.rs +++ b/satrs/src/pus/event_srv.rs @@ -1,7 +1,7 @@ use crate::events::EventU32; use crate::pus::event_man::{EventRequest, EventRequestWithToken}; use crate::pus::verification::TcStateToken; -use crate::pus::{PartialPusHandlingError, PusPacketHandlerResult, PusPacketHandlingError}; +use crate::pus::{DirectPusPacketHandlerResult, PartialPusHandlingError, PusPacketHandlingError}; use crate::queue::GenericSendError; use spacepackets::ecss::event::Subservice; use spacepackets::ecss::PusPacket; @@ -10,7 +10,7 @@ use std::sync::mpsc::Sender; use super::verification::VerificationReportingProvider; use super::{ EcssTcInMemConverter, EcssTcReceiver, EcssTmSender, GenericConversionError, - GenericRoutingError, PusServiceHelper, + GenericRoutingError, HandlingStatus, PusServiceHelper, }; pub struct PusEventServiceHandler< @@ -46,13 +46,14 @@ impl< } } - pub fn poll_and_handle_next_tc( + pub fn poll_and_handle_next_tc( &mut self, + mut error_callback: ErrorCb, time_stamp: &[u8], - ) -> Result { + ) -> Result { let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?; if possible_packet.is_none() { - return Ok(PusPacketHandlerResult::Empty); + return Ok(HandlingStatus::Empty.into()); } let ecss_tc_and_token = possible_packet.unwrap(); self.service_helper @@ -62,13 +63,13 @@ impl< let subservice = tc.subservice(); let srv = Subservice::try_from(subservice); if srv.is_err() { - return Ok(PusPacketHandlerResult::CustomSubservice( + return Ok(DirectPusPacketHandlerResult::CustomSubservice( tc.subservice(), ecss_tc_and_token.token, )); } - let handle_enable_disable_request = - |enable: bool| -> Result { + let mut handle_enable_disable_request = + |enable: bool| -> Result { if tc.user_data().len() < 4 { return Err(GenericConversionError::NotEnoughAppData { expected: 4, @@ -79,21 +80,20 @@ impl< let user_data = tc.user_data(); let event_u32 = EventU32::from(u32::from_be_bytes(user_data[0..4].try_into().unwrap())); - let start_token = self - .service_helper - .common - .verif_reporter - .start_success( - &self.service_helper.common.tm_sender, - ecss_tc_and_token.token, - time_stamp, - ) - .map_err(|_| PartialPusHandlingError::Verification); - let partial_error = start_token.clone().err(); let mut token: TcStateToken = ecss_tc_and_token.token.into(); - if let Ok(start_token) = start_token { - token = start_token.into(); + match self.service_helper.common.verif_reporter.start_success( + &self.service_helper.common.tm_sender, + ecss_tc_and_token.token, + time_stamp, + ) { + Ok(start_token) => { + token = start_token.into(); + } + Err(e) => { + error_callback(&PartialPusHandlingError::Verification(e)); + } } + let event_req_with_token = if enable { EventRequestWithToken { request: EventRequest::Enable(event_u32), @@ -112,12 +112,7 @@ impl< GenericSendError::RxDisconnected, )) })?; - if let Some(partial_error) = partial_error { - return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess( - partial_error, - )); - } - Ok(PusPacketHandlerResult::RequestHandled) + Ok(HandlingStatus::HandledOne.into()) }; match srv.unwrap() { @@ -136,14 +131,14 @@ impl< handle_enable_disable_request(false)?; } Subservice::TcReportDisabledList | Subservice::TmDisabledEventsReport => { - return Ok(PusPacketHandlerResult::SubserviceNotImplemented( + return Ok(DirectPusPacketHandlerResult::SubserviceNotImplemented( subservice, ecss_tc_and_token.token, )); } } - Ok(PusPacketHandlerResult::RequestHandled) + Ok(HandlingStatus::HandledOne.into()) } } @@ -167,7 +162,7 @@ mod tests { use crate::pus::verification::{ RequestId, VerificationReporter, VerificationReportingProvider, }; - use crate::pus::{GenericConversionError, MpscTcReceiver}; + use crate::pus::{GenericConversionError, HandlingStatus, MpscTcReceiver}; use crate::tmtc::PacketSenderWithSharedPool; use crate::{ events::EventU32, @@ -175,7 +170,7 @@ mod tests { event_man::EventRequestWithToken, tests::PusServiceHandlerWithSharedStoreCommon, verification::{TcStateAccepted, VerificationToken}, - EcssTcInSharedStoreConverter, PusPacketHandlerResult, PusPacketHandlingError, + DirectPusPacketHandlerResult, EcssTcInSharedStoreConverter, PusPacketHandlingError, }, }; @@ -229,9 +224,11 @@ mod tests { } impl SimplePusPacketHandler for Pus5HandlerWithStoreTester { - fn handle_one_tc(&mut self) -> Result { + fn handle_one_tc( + &mut self, + ) -> Result { let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap(); - self.handler.poll_and_handle_next_tc(&time_stamp) + self.handler.poll_and_handle_next_tc(|_| {}, &time_stamp) } } @@ -293,10 +290,13 @@ mod tests { let result = test_harness.handle_one_tc(); assert!(result.is_ok()); let result = result.unwrap(); - if let PusPacketHandlerResult::Empty = result { - } else { - panic!("unexpected result type {result:?}") - } + assert!( + matches!( + result, + DirectPusPacketHandlerResult::Handled(HandlingStatus::Empty) + ), + "unexpected result type {result:?}" + ) } #[test] @@ -311,7 +311,7 @@ mod tests { let result = test_harness.handle_one_tc(); assert!(result.is_ok()); let result = result.unwrap(); - if let PusPacketHandlerResult::CustomSubservice(subservice, _) = result { + if let DirectPusPacketHandlerResult::CustomSubservice(subservice, _) = result { assert_eq!(subservice, 200); } else { panic!("unexpected result type {result:?}") diff --git a/satrs/src/pus/mod.rs b/satrs/src/pus/mod.rs index 78caa4d..d4c5ff6 100644 --- a/satrs/src/pus/mod.rs +++ b/satrs/src/pus/mod.rs @@ -45,6 +45,15 @@ pub use std_mod::*; use self::verification::VerificationReportingProvider; +/// Generic handling status for an object which is able to continuosly handle a queue to handle +/// request or replies until the queue is empty. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum HandlingStatus { + HandledOne, + Empty, +} + #[derive(Debug, PartialEq, Eq, Clone)] pub enum PusTmVariant<'time, 'src_data> { InStore(PoolAddr), @@ -649,14 +658,11 @@ pub mod alloc_mod { #[cfg(feature = "std")] pub mod std_mod { + use super::*; use crate::pool::{ PoolAddr, PoolError, PoolProvider, PoolProviderWithGuards, SharedStaticMemoryPool, }; use crate::pus::verification::{TcStateAccepted, VerificationToken}; - use crate::pus::{ - EcssTcAndToken, EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericReceiveError, - GenericSendError, PusTmVariant, TryRecvTmtcError, - }; use crate::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use crate::ComponentId; use alloc::vec::Vec; @@ -920,26 +926,24 @@ pub mod std_mod { #[error("generic timestamp generation error")] Time(#[from] StdTimestampError), #[error("error sending telemetry: {0}")] - TmSend(#[from] EcssTmtcError), + TmSend(EcssTmtcError), #[error("error sending verification message")] - Verification, + Verification(EcssTmtcError), #[error("invalid verification token")] NoVerificationToken, } /// Generic result type for handlers which can process PUS packets. #[derive(Debug, Clone)] - pub enum PusPacketHandlerResult { - RequestHandled, - RequestHandledPartialSuccess(PartialPusHandlingError), + pub enum DirectPusPacketHandlerResult { + Handled(HandlingStatus), SubserviceNotImplemented(u8, VerificationToken), CustomSubservice(u8, VerificationToken), - Empty, } - impl From for PusPacketHandlerResult { - fn from(value: PartialPusHandlingError) -> Self { - Self::RequestHandledPartialSuccess(value) + impl From for DirectPusPacketHandlerResult { + fn from(value: HandlingStatus) -> Self { + Self::Handled(value) } } @@ -1222,7 +1226,7 @@ pub mod test_util { use super::{ verification::{self, TcStateAccepted, VerificationToken}, - PusPacketHandlerResult, PusPacketHandlingError, + DirectPusPacketHandlerResult, PusPacketHandlingError, }; pub const TEST_APID: u16 = 0x101; @@ -1246,7 +1250,8 @@ pub mod test_util { } pub trait SimplePusPacketHandler { - fn handle_one_tc(&mut self) -> Result; + fn handle_one_tc(&mut self) + -> Result; } } @@ -1284,36 +1289,46 @@ pub mod tests { pub seq_count: u16, pub msg_counter: u16, pub dest_id: u16, - pub time_stamp: [u8; 7], + pub timestamp: Vec, } impl CommonTmInfo { - pub fn new_zero_seq_count( + pub fn new( subservice: u8, apid: u16, + seq_count: u16, + msg_counter: u16, dest_id: u16, - time_stamp: [u8; 7], + timestamp: &[u8], ) -> Self { Self { subservice, apid, - seq_count: 0, - msg_counter: 0, + seq_count, + msg_counter, dest_id, - time_stamp, + timestamp: timestamp.to_vec(), } } + pub fn new_zero_seq_count( + subservice: u8, + apid: u16, + dest_id: u16, + timestamp: &[u8], + ) -> Self { + Self::new(subservice, apid, 0, 0, dest_id, timestamp) + } pub fn new_from_tm(tm: &PusTmCreator) -> Self { - let mut time_stamp = [0; 7]; - time_stamp.clone_from_slice(&tm.timestamp()[0..7]); + let mut timestamp = [0; 7]; + timestamp.clone_from_slice(&tm.timestamp()[0..7]); Self { subservice: PusPacket::subservice(tm), apid: tm.apid(), seq_count: tm.seq_count(), msg_counter: tm.msg_counter(), dest_id: tm.dest_id(), - time_stamp, + timestamp: timestamp.to_vec(), } } } @@ -1341,7 +1356,10 @@ pub mod tests { /// /// The PUS service handler is instantiated with a [EcssTcInStoreConverter]. pub fn new(id: ComponentId) -> (Self, PusServiceHelperStatic) { - let pool_cfg = StaticPoolConfig::new(alloc::vec![(16, 16), (8, 32), (4, 64)], false); + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples( + alloc::vec![(16, 16), (8, 32), (4, 64)], + false, + ); let tc_pool = StaticMemoryPool::new(pool_cfg.clone()); let tm_pool = StaticMemoryPool::new(pool_cfg); let shared_tc_pool = SharedStaticMemoryPool::new(RwLock::new(tc_pool)); diff --git a/satrs/src/pus/scheduler.rs b/satrs/src/pus/scheduler.rs index 1088142..b13e103 100644 --- a/satrs/src/pus/scheduler.rs +++ b/satrs/src/pus/scheduler.rs @@ -939,7 +939,10 @@ mod tests { #[test] fn test_reset() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let mut buf: [u8; 32] = [0; 32]; @@ -1088,7 +1091,10 @@ mod tests { } #[test] fn test_release_telecommands() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let mut buf: [u8; 32] = [0; 32]; @@ -1153,7 +1159,10 @@ mod tests { #[test] fn release_multi_with_same_time() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let mut buf: [u8; 32] = [0; 32]; @@ -1210,7 +1219,10 @@ mod tests { #[test] fn release_with_scheduler_disabled() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); scheduler.disable(); @@ -1278,7 +1290,10 @@ mod tests { fn insert_unwrapped_tc() { let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut buf: [u8; 32] = [0; 32]; let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, &[]); @@ -1325,7 +1340,10 @@ mod tests { fn insert_wrapped_tc() { let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut buf: [u8; 32] = [0; 32]; let tc = scheduled_tc(UnixTime::new_only_secs(100), &mut buf); @@ -1374,7 +1392,10 @@ mod tests { fn insert_wrong_service() { let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut buf: [u8; 32] = [0; 32]; let tc = wrong_tc_service(UnixTime::new_only_secs(100), &mut buf); @@ -1396,7 +1417,10 @@ mod tests { fn insert_wrong_subservice() { let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut buf: [u8; 32] = [0; 32]; let tc = wrong_tc_subservice(UnixTime::new_only_secs(100), &mut buf); @@ -1417,7 +1441,10 @@ mod tests { #[test] fn insert_wrapped_tc_faulty_app_data() { let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let tc = invalid_time_tagged_cmd(); let insert_res = scheduler.insert_wrapped_tc::(&tc, &mut pool); assert!(insert_res.is_err()); @@ -1431,7 +1458,10 @@ mod tests { #[test] fn insert_doubly_wrapped_time_tagged_cmd() { let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut buf: [u8; 64] = [0; 64]; let tc = double_wrapped_time_tagged_tc(UnixTime::new_only_secs(50), &mut buf); let insert_res = scheduler.insert_wrapped_tc::(&tc, &mut pool); @@ -1465,7 +1495,10 @@ mod tests { fn release_time_within_time_margin() { let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut buf: [u8; 32] = [0; 32]; @@ -1489,7 +1522,10 @@ mod tests { #[test] fn test_store_error_propagation_release() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let mut buf: [u8; 32] = [0; 32]; let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, &[]); @@ -1523,7 +1559,10 @@ mod tests { #[test] fn test_store_error_propagation_reset() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let mut buf: [u8; 32] = [0; 32]; let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, &[]); @@ -1546,7 +1585,10 @@ mod tests { #[test] fn test_delete_by_req_id_simple_retrieve_addr() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let mut buf: [u8; 32] = [0; 32]; let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, &[]); @@ -1564,7 +1606,10 @@ mod tests { #[test] fn test_delete_by_req_id_simple_delete_all() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let mut buf: [u8; 32] = [0; 32]; let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, &[]); @@ -1582,7 +1627,10 @@ mod tests { #[test] fn test_delete_by_req_id_complex() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let mut buf: [u8; 32] = [0; 32]; let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, &[]); @@ -1627,7 +1675,10 @@ mod tests { fn insert_full_store_test() { let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(1, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(1, 64)], + false, + )); let mut buf: [u8; 32] = [0; 32]; // Store is full after this. @@ -1663,7 +1714,10 @@ mod tests { #[test] fn test_time_window_retrieval_select_all() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let tc_info_0 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50); let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100); @@ -1692,7 +1746,10 @@ mod tests { #[test] fn test_time_window_retrieval_select_from_stamp() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let _ = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50); let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100); @@ -1724,7 +1781,10 @@ mod tests { #[test] fn test_time_window_retrieval_select_to_time() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let tc_info_0 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50); let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100); @@ -1756,7 +1816,10 @@ mod tests { #[test] fn test_time_window_retrieval_select_from_time_to_time() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let _ = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50); let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100); @@ -1794,7 +1857,10 @@ mod tests { #[test] fn test_deletion_all() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50); insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100); @@ -1820,7 +1886,10 @@ mod tests { #[test] fn test_deletion_from_start_time() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50); let cmd_0_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100); @@ -1842,7 +1911,10 @@ mod tests { #[test] fn test_deletion_to_end_time() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let cmd_0_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50); let cmd_1_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100); @@ -1865,7 +1937,10 @@ mod tests { #[test] fn test_deletion_from_start_time_to_end_time() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let cmd_out_of_range_0 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50); let cmd_0_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100); @@ -1897,7 +1972,10 @@ mod tests { #[test] fn test_release_without_deletion() { - let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false)); + let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (5, 64)], + false, + )); let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5)); let mut buf: [u8; 32] = [0; 32]; diff --git a/satrs/src/pus/scheduler_srv.rs b/satrs/src/pus/scheduler_srv.rs index 4d538b8..f84a0c6 100644 --- a/satrs/src/pus/scheduler_srv.rs +++ b/satrs/src/pus/scheduler_srv.rs @@ -1,11 +1,12 @@ use super::scheduler::PusSchedulerProvider; use super::verification::{VerificationReporter, VerificationReportingProvider}; use super::{ - EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcReceiver, - EcssTmSender, MpscTcReceiver, PusServiceHelper, + DirectPusPacketHandlerResult, EcssTcInMemConverter, EcssTcInSharedStoreConverter, + EcssTcInVecConverter, EcssTcReceiver, EcssTmSender, HandlingStatus, MpscTcReceiver, + PartialPusHandlingError, PusServiceHelper, }; use crate::pool::PoolProvider; -use crate::pus::{PusPacketHandlerResult, PusPacketHandlingError}; +use crate::pus::PusPacketHandlingError; use crate::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use alloc::string::ToString; use spacepackets::ecss::{scheduling, PusPacket}; @@ -64,14 +65,15 @@ impl< &self.scheduler } - pub fn poll_and_handle_next_tc( + pub fn poll_and_handle_next_tc( &mut self, + mut error_callback: ErrorCb, time_stamp: &[u8], sched_tc_pool: &mut (impl PoolProvider + ?Sized), - ) -> Result { + ) -> Result { let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?; if possible_packet.is_none() { - return Ok(PusPacketHandlerResult::Empty); + return Ok(HandlingStatus::Empty.into()); } let ecss_tc_and_token = possible_packet.unwrap(); self.service_helper @@ -81,34 +83,34 @@ impl< let subservice = PusPacket::subservice(&tc); let standard_subservice = scheduling::Subservice::try_from(subservice); if standard_subservice.is_err() { - return Ok(PusPacketHandlerResult::CustomSubservice( + return Ok(DirectPusPacketHandlerResult::CustomSubservice( subservice, ecss_tc_and_token.token, )); } - let partial_error = None; match standard_subservice.unwrap() { scheduling::Subservice::TcEnableScheduling => { - let start_token = self - .service_helper - .verif_reporter() - .start_success( - &self.service_helper.common.tm_sender, - ecss_tc_and_token.token, - time_stamp, - ) - .expect("Error sending start success"); - + let opt_started_token = match self.service_helper.verif_reporter().start_success( + &self.service_helper.common.tm_sender, + ecss_tc_and_token.token, + time_stamp, + ) { + Ok(started_token) => Some(started_token), + Err(e) => { + error_callback(&PartialPusHandlingError::Verification(e)); + None + } + }; self.scheduler.enable(); - if self.scheduler.is_enabled() { - self.service_helper - .verif_reporter() - .completion_success( - &self.service_helper.common.tm_sender, - start_token, - time_stamp, - ) - .expect("Error sending completion success"); + + if self.scheduler.is_enabled() && opt_started_token.is_some() { + if let Err(e) = self.service_helper.verif_reporter().completion_success( + &self.service_helper.common.tm_sender, + opt_started_token.unwrap(), + time_stamp, + ) { + error_callback(&PartialPusHandlingError::Verification(e)); + } } else { return Err(PusPacketHandlingError::Other( "failed to enabled scheduler".to_string(), @@ -116,26 +118,27 @@ impl< } } scheduling::Subservice::TcDisableScheduling => { - let start_token = self - .service_helper - .verif_reporter() - .start_success( - &self.service_helper.common.tm_sender, - ecss_tc_and_token.token, - time_stamp, - ) - .expect("Error sending start success"); + let opt_started_token = match self.service_helper.verif_reporter().start_success( + &self.service_helper.common.tm_sender, + ecss_tc_and_token.token, + time_stamp, + ) { + Ok(started_token) => Some(started_token), + Err(e) => { + error_callback(&PartialPusHandlingError::Verification(e)); + None + } + }; self.scheduler.disable(); - if !self.scheduler.is_enabled() { - self.service_helper - .verif_reporter() - .completion_success( - &self.service_helper.common.tm_sender, - start_token, - time_stamp, - ) - .expect("Error sending completion success"); + if !self.scheduler.is_enabled() && opt_started_token.is_some() { + if let Err(e) = self.service_helper.verif_reporter().completion_success( + &self.service_helper.common.tm_sender, + opt_started_token.unwrap(), + time_stamp, + ) { + error_callback(&PartialPusHandlingError::Verification(e)); + } } else { return Err(PusPacketHandlingError::Other( "failed to disable scheduler".to_string(), @@ -194,18 +197,13 @@ impl< } _ => { // Treat unhandled standard subservices as custom subservices for now. - return Ok(PusPacketHandlerResult::CustomSubservice( + return Ok(DirectPusPacketHandlerResult::CustomSubservice( subservice, ecss_tc_and_token.token, )); } } - if let Some(partial_error) = partial_error { - return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess( - partial_error, - )); - } - Ok(PusPacketHandlerResult::RequestHandled) + Ok(HandlingStatus::HandledOne.into()) } } /// Helper type definition for a PUS 11 handler with a dynamic TMTC memory backend and regular @@ -257,7 +255,7 @@ mod tests { verification::{RequestId, TcStateAccepted, VerificationToken}, EcssTcInSharedStoreConverter, }; - use crate::pus::{MpscTcReceiver, PusPacketHandlerResult, PusPacketHandlingError}; + use crate::pus::{DirectPusPacketHandlerResult, MpscTcReceiver, PusPacketHandlingError}; use crate::tmtc::PacketSenderWithSharedPool; use alloc::collections::VecDeque; use delegate::delegate; @@ -288,7 +286,10 @@ mod tests { impl Pus11HandlerWithStoreTester { pub fn new() -> Self { let test_scheduler = TestScheduler::default(); - let pool_cfg = StaticPoolConfig::new(alloc::vec![(16, 16), (8, 32), (4, 64)], false); + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples( + alloc::vec![(16, 16), (8, 32), (4, 64)], + false, + ); let sched_tc_pool = StaticMemoryPool::new(pool_cfg.clone()); let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new(0); Self { @@ -298,10 +299,12 @@ mod tests { } } - pub fn handle_one_tc(&mut self) -> Result { + pub fn handle_one_tc( + &mut self, + ) -> Result { let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap(); self.handler - .poll_and_handle_next_tc(&time_stamp, &mut self.sched_tc_pool) + .poll_and_handle_next_tc(|_| {}, &time_stamp, &mut self.sched_tc_pool) } } @@ -387,7 +390,7 @@ mod tests { let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap(); test_harness .handler - .poll_and_handle_next_tc(&time_stamp, &mut test_harness.sched_tc_pool) + .poll_and_handle_next_tc(|_| {}, &time_stamp, &mut test_harness.sched_tc_pool) .unwrap(); test_harness.check_next_verification_tm(1, request_id); test_harness.check_next_verification_tm(3, request_id); diff --git a/satrs/src/pus/test.rs b/satrs/src/pus/test.rs index a1ca93e..5094be9 100644 --- a/satrs/src/pus/test.rs +++ b/satrs/src/pus/test.rs @@ -1,5 +1,5 @@ use crate::pus::{ - PartialPusHandlingError, PusPacketHandlerResult, PusPacketHandlingError, PusTmVariant, + DirectPusPacketHandlerResult, PartialPusHandlingError, PusPacketHandlingError, PusTmVariant, }; use crate::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader}; @@ -10,7 +10,7 @@ use std::sync::mpsc; use super::verification::{VerificationReporter, VerificationReportingProvider}; use super::{ EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcReceiver, - EcssTmSender, GenericConversionError, MpscTcReceiver, PusServiceHelper, + EcssTmSender, GenericConversionError, HandlingStatus, MpscTcReceiver, PusServiceHelper, }; /// This is a helper class for [std] environments to handle generic PUS 17 (test service) packets. @@ -43,13 +43,14 @@ impl< Self { service_helper } } - pub fn poll_and_handle_next_tc( + pub fn poll_and_handle_next_tc( &mut self, + mut error_callback: ErrorCb, time_stamp: &[u8], - ) -> Result { + ) -> Result { let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?; if possible_packet.is_none() { - return Ok(PusPacketHandlerResult::Empty); + return Ok(HandlingStatus::Empty.into()); } let ecss_tc_and_token = possible_packet.unwrap(); self.service_helper @@ -60,21 +61,16 @@ impl< return Err(GenericConversionError::WrongService(tc.service()).into()); } if tc.subservice() == 1 { - let mut partial_error = None; - let result = self - .service_helper - .verif_reporter() - .start_success( - &self.service_helper.common.tm_sender, - ecss_tc_and_token.token, - time_stamp, - ) - .map_err(|_| PartialPusHandlingError::Verification); - let start_token = if let Ok(result) = result { - Some(result) - } else { - partial_error = Some(result.unwrap_err()); - None + let opt_started_token = match self.service_helper.verif_reporter().start_success( + &self.service_helper.common.tm_sender, + ecss_tc_and_token.token, + time_stamp, + ) { + Ok(token) => Some(token), + Err(e) => { + error_callback(&PartialPusHandlingError::Verification(e)); + None + } }; // Sequence count will be handled centrally in TM funnel. // It is assumed that the verification reporter was built with a valid APID, so we use @@ -83,42 +79,30 @@ impl< SpHeader::new_for_unseg_tm(self.service_helper.verif_reporter().apid(), 0, 0); let tc_header = PusTmSecondaryHeader::new_simple(17, 2, time_stamp); let ping_reply = PusTmCreator::new(reply_header, tc_header, &[], true); - let result = self + if let Err(e) = self .service_helper .common .tm_sender .send_tm(self.service_helper.id(), PusTmVariant::Direct(ping_reply)) - .map_err(PartialPusHandlingError::TmSend); - if let Err(err) = result { - partial_error = Some(err); + { + error_callback(&PartialPusHandlingError::TmSend(e)); } - - if let Some(start_token) = start_token { - if self - .service_helper - .verif_reporter() - .completion_success( - &self.service_helper.common.tm_sender, - start_token, - time_stamp, - ) - .is_err() - { - partial_error = Some(PartialPusHandlingError::Verification) + if let Some(start_token) = opt_started_token { + if let Err(e) = self.service_helper.verif_reporter().completion_success( + &self.service_helper.common.tm_sender, + start_token, + time_stamp, + ) { + error_callback(&PartialPusHandlingError::Verification(e)); } } - if let Some(partial_error) = partial_error { - return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess( - partial_error, - )); - }; } else { - return Ok(PusPacketHandlerResult::CustomSubservice( + return Ok(DirectPusPacketHandlerResult::CustomSubservice( tc.subservice(), ecss_tc_and_token.token, )); } - Ok(PusPacketHandlerResult::RequestHandled) + Ok(HandlingStatus::HandledOne.into()) } } @@ -158,8 +142,9 @@ mod tests { }; use crate::pus::verification::{TcStateAccepted, VerificationToken}; use crate::pus::{ - EcssTcInSharedStoreConverter, EcssTcInVecConverter, GenericConversionError, MpscTcReceiver, - MpscTmAsVecSender, PusPacketHandlerResult, PusPacketHandlingError, + DirectPusPacketHandlerResult, EcssTcInSharedStoreConverter, EcssTcInVecConverter, + GenericConversionError, HandlingStatus, MpscTcReceiver, MpscTmAsVecSender, + PartialPusHandlingError, PusPacketHandlingError, }; use crate::tmtc::PacketSenderWithSharedPool; use crate::ComponentId; @@ -221,9 +206,12 @@ mod tests { } } impl SimplePusPacketHandler for Pus17HandlerWithStoreTester { - fn handle_one_tc(&mut self) -> Result { + fn handle_one_tc( + &mut self, + ) -> Result { let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap(); - self.handler.poll_and_handle_next_tc(&time_stamp) + self.handler + .poll_and_handle_next_tc(|_partial_error: &PartialPusHandlingError| {}, &time_stamp) } } @@ -276,9 +264,12 @@ mod tests { } } impl SimplePusPacketHandler for Pus17HandlerWithVecTester { - fn handle_one_tc(&mut self) -> Result { + fn handle_one_tc( + &mut self, + ) -> Result { let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap(); - self.handler.poll_and_handle_next_tc(&time_stamp) + self.handler + .poll_and_handle_next_tc(|_partial_error: &PartialPusHandlingError| {}, &time_stamp) } } @@ -328,10 +319,11 @@ mod tests { let mut test_harness = Pus17HandlerWithStoreTester::new(0); let result = test_harness.handle_one_tc(); assert!(result.is_ok()); - let result = result.unwrap(); - if let PusPacketHandlerResult::Empty = result { - } else { - panic!("unexpected result type {result:?}") + match result.unwrap() { + DirectPusPacketHandlerResult::Handled(handled) => { + assert_eq!(handled, HandlingStatus::Empty); + } + _ => panic!("unexpected result"), } } @@ -367,7 +359,7 @@ mod tests { let result = test_harness.handle_one_tc(); assert!(result.is_ok()); let result = result.unwrap(); - if let PusPacketHandlerResult::CustomSubservice(subservice, _) = result { + if let DirectPusPacketHandlerResult::CustomSubservice(subservice, _) = result { assert_eq!(subservice, 200); } else { panic!("unexpected result type {result:?}") diff --git a/satrs/src/pus/verification.rs b/satrs/src/pus/verification.rs index 2f81e41..8986e88 100644 --- a/satrs/src/pus/verification.rs +++ b/satrs/src/pus/verification.rs @@ -31,7 +31,9 @@ //! const TEST_APID: u16 = 0x02; //! const TEST_COMPONENT_ID: UniqueApidTargetId = UniqueApidTargetId::new(TEST_APID, 0x05); //! -//! let pool_cfg = StaticPoolConfig::new(vec![(10, 32), (10, 64), (10, 128), (10, 1024)], false); +//! let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples( +//! vec![(10, 32), (10, 64), (10, 128), (10, 1024)], false +//! ); //! let tm_pool = StaticMemoryPool::new(pool_cfg.clone()); //! let shared_tm_pool = SharedStaticMemoryPool::new(RwLock::new(tm_pool)); //! let (verif_tx, verif_rx) = mpsc::sync_channel(10); @@ -79,6 +81,7 @@ //! The [integration test](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/fsrc-core/tests/verification_test.rs) //! for the verification module contains examples how this module could be used in a more complex //! context involving multiple threads +use crate::params::{Params, WritableToBeBytes}; use crate::pus::{source_buffer_large_enough, EcssTmSender, EcssTmtcError}; use core::fmt::{Debug, Display, Formatter}; use core::hash::{Hash, Hasher}; @@ -353,7 +356,7 @@ pub struct FailParams<'stamp, 'fargs> { impl<'stamp, 'fargs> FailParams<'stamp, 'fargs> { pub fn new( time_stamp: &'stamp [u8], - failure_code: &'fargs impl EcssEnumeration, + failure_code: &'fargs dyn EcssEnumeration, failure_data: &'fargs [u8], ) -> Self { Self { @@ -381,7 +384,7 @@ impl<'stamp, 'fargs> FailParamsWithStep<'stamp, 'fargs> { pub fn new( time_stamp: &'stamp [u8], step: &'fargs impl EcssEnumeration, - failure_code: &'fargs impl EcssEnumeration, + failure_code: &'fargs dyn EcssEnumeration, failure_data: &'fargs [u8], ) -> Self { Self { @@ -1171,26 +1174,143 @@ pub mod alloc_mod { } } -/* -#[cfg(feature = "std")] -pub mod std_mod { - use std::sync::mpsc; - - use crate::pool::StoreAddr; - use crate::pus::verification::VerificationReporterWithSender; - - use super::alloc_mod::VerificationReporterWithSharedPoolSender; - - pub type VerificationReporterWithSharedPoolMpscSender = - VerificationReporterWithSharedPoolSender>; - pub type VerificationReporterWithSharedPoolMpscBoundedSender = - VerificationReporterWithSharedPoolSender>; - pub type VerificationReporterWithVecMpscSender = - VerificationReporterWithSender>>; - pub type VerificationReporterWithVecMpscBoundedSender = - VerificationReporterWithSender>>; +pub struct FailParamHelper<'stamp, 'fargs, 'buf, 'params> { + pub timestamp: &'stamp [u8], + pub error_code: &'fargs dyn EcssEnumeration, + pub small_data_buf: &'buf mut [u8], + pub params: Option<&'params Params>, +} + +/// This helper function simplifies generating completion failures where the error data has +/// the generic [Params] type. +/// +/// A small data buffer needs to be supplied for the [Params::Heapless] type because all data +/// suplied as error data must be held in a slice. Passing a static buffer avoids dynamic memory +/// allocation for this case. +/// +/// Please note that this specific function can not propagate the [Params::Store] variant. +/// This function also might not be able to propagate other error variants which are added in +/// the future. The returned boolean on success denotes whether the error parameters were +/// propagated properly. +pub fn handle_completion_failure_with_generic_params( + tm_sender: &(impl EcssTmSender + ?Sized), + verif_token: VerificationToken, + verif_reporter: &impl VerificationReportingProvider, + helper: FailParamHelper, +) -> Result { + let mut error_params_propagated = true; + if helper.params.is_none() { + verif_reporter.completion_failure( + tm_sender, + verif_token, + FailParams::new(helper.timestamp, helper.error_code, &[]), + )?; + return Ok(true); + } + let error_params = helper.params.unwrap(); + match error_params { + Params::Heapless(heapless_param) => { + heapless_param + .write_to_be_bytes(&mut helper.small_data_buf[..heapless_param.written_len()])?; + verif_reporter.completion_failure( + tm_sender, + verif_token, + FailParams::new( + helper.timestamp, + helper.error_code, + &helper.small_data_buf[..heapless_param.written_len()], + ), + )?; + } + #[cfg(feature = "alloc")] + Params::Vec(vec) => { + verif_reporter.completion_failure( + tm_sender, + verif_token, + FailParams::new(helper.timestamp, helper.error_code, vec), + )?; + } + #[cfg(feature = "alloc")] + Params::String(str) => { + verif_reporter.completion_failure( + tm_sender, + verif_token, + FailParams::new(helper.timestamp, helper.error_code, str.as_bytes()), + )?; + } + _ => { + verif_reporter.completion_failure( + tm_sender, + verif_token, + FailParams::new(helper.timestamp, helper.error_code, &[]), + )?; + error_params_propagated = false; + } + } + Ok(error_params_propagated) +} + +/// This function is similar to [handle_completion_failure_with_generic_params] but handles the +/// step failure case. +pub fn handle_step_failure_with_generic_params( + tm_sender: &(impl EcssTmSender + ?Sized), + verif_token: VerificationToken, + verif_reporter: &impl VerificationReportingProvider, + helper: FailParamHelper, + step: &impl EcssEnumeration, +) -> Result { + if helper.params.is_none() { + verif_reporter.step_failure( + tm_sender, + verif_token, + FailParamsWithStep::new(helper.timestamp, step, helper.error_code, &[]), + )?; + return Ok(true); + } + let error_params = helper.params.unwrap(); + let mut error_params_propagated = true; + match error_params { + Params::Heapless(heapless_param) => { + heapless_param + .write_to_be_bytes(&mut helper.small_data_buf[..heapless_param.written_len()])?; + verif_reporter.step_failure( + tm_sender, + verif_token, + FailParamsWithStep::new( + helper.timestamp, + step, + helper.error_code, + &helper.small_data_buf[..heapless_param.written_len()], + ), + )?; + } + #[cfg(feature = "alloc")] + Params::Vec(vec) => { + verif_reporter.step_failure( + tm_sender, + verif_token, + FailParamsWithStep::new(helper.timestamp, step, helper.error_code, vec), + )?; + } + #[cfg(feature = "alloc")] + Params::String(str) => { + verif_reporter.step_failure( + tm_sender, + verif_token, + FailParamsWithStep::new(helper.timestamp, step, helper.error_code, str.as_bytes()), + )?; + } + _ => { + verif_reporter.step_failure( + tm_sender, + verif_token, + FailParamsWithStep::new(helper.timestamp, step, helper.error_code, &[]), + )?; + error_params_propagated = false; + } + } + Ok(error_params_propagated) } - */ #[cfg(any(feature = "test_util", test))] pub mod test_util { @@ -1566,73 +1686,19 @@ pub mod test_util { .pop_front() .expect("report queue is empty") } - /* - pub fn verification_info(&self, req_id: &RequestId) -> Option { - let verif_map = self.verification_map.lock().unwrap(); - let value = verif_map.borrow().get(req_id).cloned(); - value - } - - - pub fn check_started(&self, req_id: &RequestId) -> bool { - let verif_map = self.verification_map.lock().unwrap(); - if let Some(entry) = verif_map.borrow().get(req_id) { - return entry.started.unwrap_or(false); - } - false - } - - fn generic_completion_checks( - entry: &VerificationStatus, - step: Option, - completion_success: bool, - ) { - assert!(entry.accepted.unwrap()); - assert!(entry.started.unwrap()); - if let Some(step) = step { - assert!(entry.step_status.unwrap()); - assert_eq!(entry.step, step); - } else { - assert!(entry.step_status.is_none()); - } - assert_eq!(entry.completed.unwrap(), completion_success); - } - - - pub fn assert_completion_failure( - &self, - req_id: &RequestId, - step: Option, - error_code: u64, - ) { - let verif_map = self.verification_map.lock().unwrap(); - if let Some(entry) = verif_map.borrow().get(req_id) { - Self::generic_completion_checks(entry, step, false); - assert_eq!(entry.fail_enum.unwrap(), error_code); - return; - } - panic!("request not in verification map"); - } - - pub fn completion_status(&self, req_id: &RequestId) -> Option { - let verif_map = self.verification_map.lock().unwrap(); - if let Some(entry) = verif_map.borrow().get(req_id) { - return entry.completed; - } - panic!("request not in verification map"); - } - */ } } #[cfg(test)] pub mod tests { + use crate::params::Params; use crate::pool::{SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig}; use crate::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0}; use crate::pus::tests::CommonTmInfo; use crate::pus::verification::{ - EcssTmSender, EcssTmtcError, FailParams, FailParamsWithStep, RequestId, TcStateNone, - VerificationReporter, VerificationReporterCfg, VerificationToken, + handle_step_failure_with_generic_params, EcssTmSender, EcssTmtcError, FailParams, + FailParamsWithStep, RequestId, TcStateNone, VerificationReporter, VerificationReporterCfg, + VerificationToken, }; use crate::pus::{ChannelWithId, PusTmVariant}; use crate::request::MessageMetadata; @@ -1640,6 +1706,7 @@ pub mod tests { use crate::tmtc::{PacketSenderWithSharedPool, SharedPacketPool}; use crate::ComponentId; use alloc::format; + use alloc::string::ToString; use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader}; use spacepackets::ecss::{ EcssEnumU16, EcssEnumU32, EcssEnumU8, EcssEnumeration, PusError, PusPacket, @@ -1654,8 +1721,9 @@ pub mod tests { use std::vec::Vec; use super::{ - DummyVerificationHook, SeqCountProviderSimple, TcStateAccepted, TcStateStarted, - VerificationHookProvider, VerificationReportingProvider, WasAtLeastAccepted, + handle_completion_failure_with_generic_params, DummyVerificationHook, FailParamHelper, + SeqCountProviderSimple, TcStateAccepted, TcStateStarted, VerificationHookProvider, + VerificationReportingProvider, WasAtLeastAccepted, }; fn is_send(_: &T) {} @@ -1663,6 +1731,7 @@ pub mod tests { fn is_sync(_: &T) {} const EMPTY_STAMP: [u8; 7] = [0; 7]; + const DUMMY_STAMP: &[u8] = &[0, 1, 0, 1, 0, 1, 0]; #[derive(Debug, Eq, PartialEq, Clone)] struct TmInfo { @@ -1740,8 +1809,8 @@ pub mod tests { tc: Vec, } - fn base_reporter(id: ComponentId) -> VerificationReporter { - let cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, 8).unwrap(); + fn base_reporter(id: ComponentId, max_fail_data_len: usize) -> VerificationReporter { + let cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, max_fail_data_len).unwrap(); VerificationReporter::new(id, &cfg) } @@ -1844,66 +1913,57 @@ pub mod tests { .completion_failure(&self.sender, token, params) } - fn completion_success_check(&mut self, incrementing_couters: bool) { - assert_eq!(self.sender.service_queue.borrow().len(), 3); - let mut current_seq_count = 0; + fn check_acceptance_success(&self, timestamp: &[u8; 7]) { let cmp_info = TmInfo { requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo { - subservice: 1, - apid: TEST_APID, - seq_count: current_seq_count, - msg_counter: current_seq_count, - dest_id: self.reporter.dest_id(), - time_stamp: EMPTY_STAMP, - }, + common: CommonTmInfo::new(1, TEST_APID, 0, 0, self.reporter.dest_id(), timestamp), additional_data: None, }; - let mut info = self.sender.service_queue.borrow_mut().pop_front().unwrap(); + let mut service_queue = self.sender.service_queue.borrow_mut(); + assert!(service_queue.len() >= 1); + let info = service_queue.pop_front().unwrap(); assert_eq!(info, cmp_info); + } - if incrementing_couters { - current_seq_count += 1; - } - + fn check_start_success(&mut self, seq_count: u16, msg_counter: u16, timestamp: &[u8]) { + let mut srv_queue = self.sender.service_queue.borrow_mut(); let cmp_info = TmInfo { requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo { - subservice: 3, - apid: TEST_APID, - msg_counter: current_seq_count, - seq_count: current_seq_count, - dest_id: self.reporter.dest_id(), - time_stamp: [0, 1, 0, 1, 0, 1, 0], - }, + common: CommonTmInfo::new( + 3, + TEST_APID, + seq_count, + msg_counter, + self.reporter.dest_id(), + timestamp, + ), additional_data: None, }; - info = self.sender.service_queue.borrow_mut().pop_front().unwrap(); + let info = srv_queue.pop_front().unwrap(); assert_eq!(info, cmp_info); + } - if incrementing_couters { - current_seq_count += 1; - } + fn check_completion_success(&mut self, seq_count: u16, msg_counter: u16) { let cmp_info = TmInfo { requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo { - subservice: 7, - apid: TEST_APID, - msg_counter: current_seq_count, - seq_count: current_seq_count, - dest_id: self.reporter.dest_id(), - time_stamp: EMPTY_STAMP, - }, + common: CommonTmInfo::new( + 7, + TEST_APID, + seq_count, + msg_counter, + self.reporter.dest_id(), + &EMPTY_STAMP, + ), additional_data: None, }; - info = self.sender.service_queue.borrow_mut().pop_front().unwrap(); + let info = self.sender.service_queue.borrow_mut().pop_front().unwrap(); assert_eq!(info, cmp_info); } } impl VerificationReporterTestbench { - fn new(id: ComponentId, tc: PusTcCreator) -> Self { - let reporter = base_reporter(id); + fn new(id: ComponentId, tc: PusTcCreator, max_fail_data_len: usize) -> Self { + let reporter = base_reporter(id, max_fail_data_len); Self { id, sender: TestSender::default(), @@ -1913,36 +1973,10 @@ pub mod tests { } } - fn acceptance_check(&self, time_stamp: &[u8; 7]) { + fn check_acceptance_failure(&mut self, timestamp: &[u8; 7]) { let cmp_info = TmInfo { requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo { - subservice: 1, - apid: TEST_APID, - seq_count: 0, - msg_counter: 0, - dest_id: self.reporter.dest_id(), - time_stamp: *time_stamp, - }, - additional_data: None, - }; - let mut service_queue = self.sender.service_queue.borrow_mut(); - assert_eq!(service_queue.len(), 1); - let info = service_queue.pop_front().unwrap(); - assert_eq!(info, cmp_info); - } - - fn acceptance_fail_check(&mut self, stamp_buf: [u8; 7]) { - let cmp_info = TmInfo { - requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo { - subservice: 2, - seq_count: 0, - apid: TEST_APID, - msg_counter: 0, - dest_id: self.reporter.dest_id(), - time_stamp: stamp_buf, - }, + common: CommonTmInfo::new(2, TEST_APID, 0, 0, self.reporter.dest_id(), timestamp), additional_data: Some([0, 2].to_vec()), }; let service_queue = self.sender.service_queue.get_mut(); @@ -1951,12 +1985,12 @@ pub mod tests { assert_eq!(info, cmp_info); } - fn start_fail_check(&mut self, fail_data_raw: [u8; 4]) { + fn check_start_failure(&mut self, fail_data_raw: [u8; 4]) { let mut srv_queue = self.sender.service_queue.borrow_mut(); assert_eq!(srv_queue.len(), 2); let mut cmp_info = TmInfo { requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count(1, TEST_APID, 0, EMPTY_STAMP), + common: CommonTmInfo::new_zero_seq_count(1, TEST_APID, 0, &EMPTY_STAMP), additional_data: None, }; let mut info = srv_queue.pop_front().unwrap(); @@ -1964,148 +1998,67 @@ pub mod tests { cmp_info = TmInfo { requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count(4, TEST_APID, 0, EMPTY_STAMP), + common: CommonTmInfo::new_zero_seq_count(4, TEST_APID, 0, &EMPTY_STAMP), additional_data: Some([&[22], fail_data_raw.as_slice()].concat().to_vec()), }; info = srv_queue.pop_front().unwrap(); assert_eq!(info, cmp_info); } - fn step_success_check(&mut self, time_stamp: &[u8; 7]) { - let mut cmp_info = TmInfo { + fn check_step_success(&mut self, step: u8, timestamp: &[u8; 7]) { + let cmp_info = TmInfo { requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count(1, TEST_APID, 0, *time_stamp), - additional_data: None, + common: CommonTmInfo::new_zero_seq_count(5, TEST_APID, 0, timestamp), + additional_data: Some([step].to_vec()), }; let mut srv_queue = self.sender.service_queue.borrow_mut(); - let mut info = srv_queue.pop_front().unwrap(); - assert_eq!(info, cmp_info); - cmp_info = TmInfo { - requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count(3, TEST_APID, 0, *time_stamp), - additional_data: None, - }; - info = srv_queue.pop_front().unwrap(); - assert_eq!(info, cmp_info); - cmp_info = TmInfo { - requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count(5, TEST_APID, 0, *time_stamp), - additional_data: Some([0].to_vec()), - }; - info = srv_queue.pop_front().unwrap(); - assert_eq!(info, cmp_info); - cmp_info = TmInfo { - requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count(5, TEST_APID, 0, *time_stamp), - additional_data: Some([1].to_vec()), - }; - info = srv_queue.pop_front().unwrap(); + let info = srv_queue.pop_front().unwrap(); assert_eq!(info, cmp_info); } - fn check_step_failure(&mut self, fail_data_raw: [u8; 4]) { - assert_eq!(self.sender.service_queue.borrow().len(), 4); - let mut cmp_info = TmInfo { - requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count( - 1, - TEST_APID, - self.reporter.dest_id(), - EMPTY_STAMP, - ), - additional_data: None, - }; - let mut info = self.sender.service_queue.borrow_mut().pop_front().unwrap(); - assert_eq!(info, cmp_info); - - cmp_info = TmInfo { - requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count( - 3, - TEST_APID, - self.reporter.dest_id(), - [0, 1, 0, 1, 0, 1, 0], - ), - additional_data: None, - }; - info = self.sender.service_queue.borrow_mut().pop_front().unwrap(); - assert_eq!(info, cmp_info); - - cmp_info = TmInfo { - requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count( - 5, - TEST_APID, - self.reporter.dest_id(), - EMPTY_STAMP, - ), - additional_data: Some([0].to_vec()), - }; - info = self.sender.service_queue.get_mut().pop_front().unwrap(); - assert_eq!(info, cmp_info); - - cmp_info = TmInfo { + fn check_step_failure( + &mut self, + step: &impl EcssEnumeration, + error_code: &impl EcssEnumeration, + fail_data: &[u8], + ) { + let mut additional_data = Vec::new(); + additional_data.extend(step.to_vec()); + additional_data.extend(error_code.to_vec()); + additional_data.extend(fail_data); + let cmp_info = TmInfo { requestor: MessageMetadata::new(self.request_id.into(), self.id), common: CommonTmInfo::new_zero_seq_count( 6, TEST_APID, self.reporter.dest_id(), - EMPTY_STAMP, - ), - additional_data: Some( - [ - [1].as_slice(), - &[0, 0, 0x10, 0x20], - fail_data_raw.as_slice(), - ] - .concat() - .to_vec(), + &EMPTY_STAMP, ), + additional_data: Some(additional_data), }; - info = self.sender.service_queue.get_mut().pop_front().unwrap(); + let info = self.sender.service_queue.get_mut().pop_front().unwrap(); assert_eq!(info, cmp_info); } - fn completion_fail_check(&mut self) { - assert_eq!(self.sender.service_queue.borrow().len(), 3); - - let mut cmp_info = TmInfo { - requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count( - 1, - TEST_APID, - self.reporter.dest_id(), - EMPTY_STAMP, - ), - additional_data: None, - }; - let mut info = self.sender.service_queue.get_mut().pop_front().unwrap(); - assert_eq!(info, cmp_info); - - cmp_info = TmInfo { - requestor: MessageMetadata::new(self.request_id.into(), self.id), - common: CommonTmInfo::new_zero_seq_count( - 3, - TEST_APID, - self.reporter.dest_id(), - [0, 1, 0, 1, 0, 1, 0], - ), - additional_data: None, - }; - info = self.sender.service_queue.get_mut().pop_front().unwrap(); - assert_eq!(info, cmp_info); - - cmp_info = TmInfo { + fn check_completion_failure( + &mut self, + error_code: &impl EcssEnumeration, + fail_data: &[u8], + ) { + let mut additional_data = Vec::new(); + additional_data.extend(error_code.to_vec()); + additional_data.extend(fail_data); + let cmp_info = TmInfo { requestor: MessageMetadata::new(self.request_id.into(), self.id), common: CommonTmInfo::new_zero_seq_count( 8, TEST_APID, self.reporter.dest_id(), - EMPTY_STAMP, + &EMPTY_STAMP, ), - additional_data: Some([0, 0, 0x10, 0x20].to_vec()), + additional_data: Some(additional_data), }; - info = self.sender.service_queue.get_mut().pop_front().unwrap(); + let info = self.sender.service_queue.get_mut().pop_front().unwrap(); assert_eq!(info, cmp_info); } } @@ -2118,7 +2071,10 @@ pub mod tests { #[test] fn test_mpsc_verif_send() { - let pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(8, 8)], false)); + let pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(8, 8)], + false, + )); let shared_tm_store = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new(pool))); let (tx, _) = mpsc::sync_channel(10); @@ -2128,7 +2084,7 @@ pub mod tests { #[test] fn test_state() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); assert_eq!(testbench.reporter.apid(), TEST_APID); testbench.reporter.set_apid(TEST_APID + 1); assert_eq!(testbench.reporter.apid(), TEST_APID + 1); @@ -2136,43 +2092,43 @@ pub mod tests { #[test] fn test_basic_acceptance_success() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); let token = testbench.init(); testbench .acceptance_success(token, &EMPTY_STAMP) .expect("sending acceptance success failed"); - testbench.acceptance_check(&EMPTY_STAMP); + testbench.check_acceptance_success(&EMPTY_STAMP); } #[test] fn test_basic_acceptance_failure() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); let init_token = testbench.init(); - let stamp_buf = [1, 2, 3, 4, 5, 6, 7]; + let timestamp = [1, 2, 3, 4, 5, 6, 7]; let fail_code = EcssEnumU16::new(2); - let fail_params = FailParams::new_no_fail_data(stamp_buf.as_slice(), &fail_code); + let fail_params = FailParams::new_no_fail_data(timestamp.as_slice(), &fail_code); testbench .acceptance_failure(init_token, fail_params) .expect("sending acceptance failure failed"); - testbench.acceptance_fail_check(stamp_buf); + testbench.check_acceptance_failure(×tamp); } #[test] fn test_basic_acceptance_failure_with_helper() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); let init_token = testbench.init(); - let stamp_buf = [1, 2, 3, 4, 5, 6, 7]; + let timestamp = [1, 2, 3, 4, 5, 6, 7]; let fail_code = EcssEnumU16::new(2); - let fail_params = FailParams::new_no_fail_data(stamp_buf.as_slice(), &fail_code); + let fail_params = FailParams::new_no_fail_data(timestamp.as_slice(), &fail_code); testbench .acceptance_failure(init_token, fail_params) .expect("sending acceptance failure failed"); - testbench.acceptance_fail_check(stamp_buf); + testbench.check_acceptance_failure(×tamp); } #[test] fn test_acceptance_fail_data_too_large() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 8); let init_token = testbench.init(); let stamp_buf = [1, 2, 3, 4, 5, 6, 7]; let fail_code = EcssEnumU16::new(2); @@ -2204,7 +2160,7 @@ pub mod tests { #[test] fn test_basic_acceptance_failure_with_fail_data() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); let fail_code = EcssEnumU8::new(10); let fail_data = EcssEnumU32::new(12); let mut fail_data_raw = [0; 4]; @@ -2216,7 +2172,7 @@ pub mod tests { .expect("sending acceptance failure failed"); let cmp_info = TmInfo { requestor: MessageMetadata::new(testbench.request_id.into(), testbench.id), - common: CommonTmInfo::new_zero_seq_count(2, TEST_APID, 0, EMPTY_STAMP), + common: CommonTmInfo::new_zero_seq_count(2, TEST_APID, 0, &EMPTY_STAMP), additional_data: Some([10, 0, 0, 0, 12].to_vec()), }; let mut service_queue = testbench.sender.service_queue.borrow_mut(); @@ -2227,7 +2183,7 @@ pub mod tests { #[test] fn test_start_failure() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); let init_token = testbench.init(); let fail_code = EcssEnumU8::new(22); let fail_data: i32 = -12; @@ -2241,12 +2197,12 @@ pub mod tests { testbench .start_failure(accepted_token, fail_params) .expect("Start failure failure"); - testbench.start_fail_check(fail_data_raw); + testbench.check_start_failure(fail_data_raw); } #[test] fn test_start_failure_with_helper() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); let token = testbench.init(); let fail_code = EcssEnumU8::new(22); let fail_data: i32 = -12; @@ -2260,12 +2216,12 @@ pub mod tests { testbench .start_failure(accepted_token, fail_params) .expect("start failure failed"); - testbench.start_fail_check(fail_data_raw); + testbench.check_start_failure(fail_data_raw); } #[test] fn test_steps_success() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); let token = testbench.init(); let accepted_token = testbench .acceptance_success(token, &EMPTY_STAMP) @@ -2280,12 +2236,15 @@ pub mod tests { .step_success(&started_token, &EMPTY_STAMP, EcssEnumU8::new(1)) .expect("step 1 failed"); assert_eq!(testbench.sender.service_queue.borrow().len(), 4); - testbench.step_success_check(&EMPTY_STAMP); + testbench.check_acceptance_success(&EMPTY_STAMP); + testbench.check_start_success(0, 0, &EMPTY_STAMP); + testbench.check_step_success(0, &EMPTY_STAMP); + testbench.check_step_success(1, &EMPTY_STAMP); } #[test] fn test_step_failure() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); let token = testbench.init(); let fail_code = EcssEnumU32::new(0x1020); let fail_data: f32 = -22.3232; @@ -2303,7 +2262,7 @@ pub mod tests { .acceptance_success(token, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = testbench - .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0]) + .start_success(accepted_token, DUMMY_STAMP) .expect("Sending start success failed"); testbench .step_success(&started_token, &EMPTY_STAMP, EcssEnumU8::new(0)) @@ -2311,12 +2270,15 @@ pub mod tests { testbench .step_failure(started_token, fail_params) .expect("Step failure failed"); - testbench.check_step_failure(fail_data_raw); + testbench.check_acceptance_success(&EMPTY_STAMP); + testbench.check_start_success(0, 0, DUMMY_STAMP); + testbench.check_step_success(0, &EMPTY_STAMP); + testbench.check_step_failure(&fail_step, &fail_code, &fail_data_raw); } #[test] fn test_completion_failure() { - let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping()); + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 16); let token = testbench.init(); let fail_code = EcssEnumU32::new(0x1020); let fail_params = FailParams::new_no_fail_data(&EMPTY_STAMP, &fail_code); @@ -2325,29 +2287,34 @@ pub mod tests { .acceptance_success(token, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = testbench - .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0]) + .start_success(accepted_token, DUMMY_STAMP) .expect("Sending start success failed"); testbench .completion_failure(started_token, fail_params) .expect("Completion failure"); - testbench.completion_fail_check(); + testbench.check_acceptance_success(&EMPTY_STAMP); + testbench.check_start_success(0, 0, DUMMY_STAMP); + + testbench.check_completion_failure(&fail_code, &[]); } #[test] fn test_complete_success_sequence() { let mut testbench = - VerificationReporterTestbench::new(TEST_COMPONENT_ID_0.id(), create_generic_ping()); + VerificationReporterTestbench::new(TEST_COMPONENT_ID_0.id(), create_generic_ping(), 16); let token = testbench.init(); let accepted_token = testbench .acceptance_success(token, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = testbench - .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0]) + .start_success(accepted_token, DUMMY_STAMP) .expect("Sending start success failed"); testbench .completion_success(started_token, &EMPTY_STAMP) .expect("Sending completion success failed"); - testbench.completion_success_check(false); + testbench.check_acceptance_success(&EMPTY_STAMP); + testbench.check_start_success(0, 0, DUMMY_STAMP); + testbench.check_completion_success(0, 0); } #[test] @@ -2367,6 +2334,83 @@ pub mod tests { testbench .completion_success(started_token, &EMPTY_STAMP) .expect("Sending completion success failed"); - testbench.completion_success_check(true); + testbench.check_acceptance_success(&EMPTY_STAMP); + testbench.check_start_success(1, 1, DUMMY_STAMP); + testbench.check_completion_success(2, 2); + } + + #[test] + fn test_completion_failure_helper_string_param() { + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 32); + let token = testbench.init(); + let accepted_token = testbench + .acceptance_success(token, &EMPTY_STAMP) + .expect("Sending acceptance success failed"); + let mut small_data_buf: [u8; 16] = [0; 16]; + let fail_code = EcssEnumU8::new(1); + let fail_data = "error 404 oh no".to_string(); + let fail_params = Params::String(fail_data.clone()); + let result = handle_completion_failure_with_generic_params( + &testbench.sender, + accepted_token, + &testbench.reporter, + FailParamHelper { + timestamp: &EMPTY_STAMP, + error_code: &fail_code, + small_data_buf: &mut small_data_buf, + params: Some(&fail_params), + }, + ); + assert!(result.unwrap()); + testbench.check_acceptance_success(&EMPTY_STAMP); + testbench.check_completion_failure(&fail_code, fail_data.as_bytes()); + } + + #[test] + fn test_step_failure_helper_string_param() { + let mut testbench = VerificationReporterTestbench::new(0, create_generic_ping(), 32); + let token = testbench.init(); + let accepted_token = testbench + .acceptance_success(token, &EMPTY_STAMP) + .expect("Sending acceptance success failed"); + let started_token = testbench + .start_success(accepted_token, &EMPTY_STAMP) + .expect("Sending start success failed"); + let mut small_data_buf: [u8; 16] = [0; 16]; + let step = EcssEnumU8::new(2); + let fail_code = EcssEnumU8::new(1); + let fail_data = "AAAAAAAAAAAHHHHHH".to_string(); + let fail_params = Params::String(fail_data.clone()); + let result = handle_step_failure_with_generic_params( + &testbench.sender, + started_token, + &testbench.reporter, + FailParamHelper { + timestamp: &EMPTY_STAMP, + error_code: &fail_code, + small_data_buf: &mut small_data_buf, + params: Some(&fail_params), + }, + &step, + ); + assert!(result.unwrap()); + testbench.check_acceptance_success(&EMPTY_STAMP); + testbench.check_start_success(0, 0, &EMPTY_STAMP); + testbench.check_step_failure(&step, &fail_code, fail_data.as_bytes()); + } + + #[test] + fn test_completion_failure_helper_vec_param() { + // TODO: Test this. + } + + #[test] + fn test_completion_failure_helper_raw_param() { + // TODO: Test this. + } + + #[test] + fn test_completion_failure_helper_store_param_ignored() { + // TODO: Test this. } } diff --git a/satrs/src/request.rs b/satrs/src/request.rs index 3999278..f188798 100644 --- a/satrs/src/request.rs +++ b/satrs/src/request.rs @@ -10,7 +10,7 @@ pub use std_mod::*; use spacepackets::{ ecss::{tc::IsPusTelecommand, PusPacket}, - ByteConversionError, CcsdsPacket, + ByteConversionError, }; use crate::{queue::GenericTargetedMessagingError, ComponentId}; @@ -47,7 +47,7 @@ impl UniqueApidTargetId { /// This function attempts to build the ID from a PUS telecommand by extracting the APID /// and the first four bytes of the application data field as the target field. pub fn from_pus_tc( - tc: &(impl CcsdsPacket + PusPacket + IsPusTelecommand), + tc: &(impl PusPacket + IsPusTelecommand), ) -> Result { if tc.user_data().len() < 4 { return Err(ByteConversionError::FromSliceTooSmall { diff --git a/satrs/src/tmtc/mod.rs b/satrs/src/tmtc/mod.rs index f075c42..498c1bd 100644 --- a/satrs/src/tmtc/mod.rs +++ b/satrs/src/tmtc/mod.rs @@ -486,7 +486,9 @@ pub(crate) mod tests { use std::sync::RwLock; - use crate::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; + use crate::pool::{ + PoolProviderWithGuards, SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig, + }; use super::*; use std::sync::mpsc; @@ -554,7 +556,7 @@ pub(crate) mod tests { #[test] fn test_basic_shared_store_sender_unbounded_sender() { let (tc_tx, tc_rx) = mpsc::channel(); - let pool_cfg = StaticPoolConfig::new(vec![(2, 8)], true); + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(2, 8)], true); let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new( StaticMemoryPool::new(pool_cfg), ))); @@ -571,7 +573,7 @@ pub(crate) mod tests { #[test] fn test_basic_shared_store_sender() { let (tc_tx, tc_rx) = mpsc::sync_channel(10); - let pool_cfg = StaticPoolConfig::new(vec![(2, 8)], true); + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(2, 8)], true); let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new( StaticMemoryPool::new(pool_cfg), ))); @@ -588,7 +590,7 @@ pub(crate) mod tests { #[test] fn test_basic_shared_store_sender_rx_dropped() { let (tc_tx, tc_rx) = mpsc::sync_channel(10); - let pool_cfg = StaticPoolConfig::new(vec![(2, 8)], true); + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(2, 8)], true); let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new( StaticMemoryPool::new(pool_cfg), ))); @@ -606,7 +608,7 @@ pub(crate) mod tests { #[test] fn test_basic_shared_store_sender_queue_full() { let (tc_tx, tc_rx) = mpsc::sync_channel(1); - let pool_cfg = StaticPoolConfig::new(vec![(2, 8)], true); + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(2, 8)], true); let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new( StaticMemoryPool::new(pool_cfg), ))); @@ -629,7 +631,7 @@ pub(crate) mod tests { #[test] fn test_basic_shared_store_store_error() { let (tc_tx, tc_rx) = mpsc::sync_channel(1); - let pool_cfg = StaticPoolConfig::new(vec![(1, 8)], true); + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(1, 8)], true); let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new( StaticMemoryPool::new(pool_cfg), ))); diff --git a/satrs/tests/pools.rs b/satrs/tests/pools.rs index 9e61097..436300d 100644 --- a/satrs/tests/pools.rs +++ b/satrs/tests/pools.rs @@ -9,7 +9,8 @@ const DUMMY_DATA: [u8; 4] = [0, 1, 2, 3]; #[test] fn threaded_usage() { - let pool_cfg = StaticPoolConfig::new(vec![(16, 6), (32, 3), (8, 12)], false); + let pool_cfg = + StaticPoolConfig::new_from_subpool_cfg_tuples(vec![(16, 6), (32, 3), (8, 12)], false); let shared_pool = Arc::new(RwLock::new(StaticMemoryPool::new(pool_cfg))); let shared_clone = shared_pool.clone(); let (tx, rx): (Sender, Receiver) = mpsc::channel(); diff --git a/satrs/tests/pus_verification.rs b/satrs/tests/pus_verification.rs index 4451909..ac446a1 100644 --- a/satrs/tests/pus_verification.rs +++ b/satrs/tests/pus_verification.rs @@ -33,8 +33,10 @@ pub mod crossbeam_test { // each reporter have an own sequence count provider. let cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, 8).unwrap(); // Shared pool object to store the verification PUS telemetry - let pool_cfg = - StaticPoolConfig::new(vec![(10, 32), (10, 64), (10, 128), (10, 1024)], false); + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples( + vec![(10, 32), (10, 64), (10, 128), (10, 1024)], + false, + ); let shared_tm_pool = SharedStaticMemoryPool::new(RwLock::new(StaticMemoryPool::new(pool_cfg.clone()))); let shared_tc_pool =