From 1cf86768c01d71b011577e663f18b9e8745384e7 Mon Sep 17 00:00:00 2001 From: Jakob Gegeniger Date: Sun, 15 Dec 2024 12:09:32 +0100 Subject: [PATCH] including tftp based shell and sockets from different project --- .configs/TelnetTeraTerm.INI | Bin 0 -> 51330 bytes .configs/UartTeraTerm.INI | Bin 0 -> 51144 bytes .vscode/settings.json | 6 +- .vscode/tasks.json | 12 + components/project_bases/CMakeLists.txt | 4 +- .../project_bases/include/countDown.hpp | 36 + .../project_bases/{ => include}/portBase.hpp | 0 .../project_bases/include/rotatingIndex.hpp | 58 + components/project_bases/include/utils.hpp | 95 ++ components/project_bases/src/utils.cpp | 9 + components/sockets/CMakeLists.txt | 5 + components/sockets/include/etherCommon.hpp | 196 +++ components/sockets/include/tcpSockets.hpp | 307 +++++ components/sockets/include/udpSockets.hpp | 303 +++++ components/sockets/src/etherCommon.cpp | 68 + components/sockets/src/tcpSockets.cpp | 9 + components/sockets/src/udpSocekts.cpp | 35 + components/tftpShell/CMakeLists.txt | 4 + components/tftpShell/extern/embedded_cli.c | 1156 +++++++++++++++++ components/tftpShell/extern/embedded_cli.h | 312 +++++ components/tftpShell/extern/license | 21 + components/tftpShell/extern/readme.md | 7 + components/tftpShell/include/cliWrapper.hpp | 161 +++ main/watchMain.cpp | 101 ++ sdkconfig | 8 +- sdkconfig.defaults | 14 +- 26 files changed, 2917 insertions(+), 10 deletions(-) create mode 100644 .configs/TelnetTeraTerm.INI create mode 100644 .configs/UartTeraTerm.INI create mode 100644 components/project_bases/include/countDown.hpp rename components/project_bases/{ => include}/portBase.hpp (100%) create mode 100644 components/project_bases/include/rotatingIndex.hpp create mode 100644 components/project_bases/include/utils.hpp create mode 100644 components/project_bases/src/utils.cpp create mode 100644 components/sockets/CMakeLists.txt create mode 100644 components/sockets/include/etherCommon.hpp create mode 100644 components/sockets/include/tcpSockets.hpp create mode 100644 components/sockets/include/udpSockets.hpp create mode 100644 components/sockets/src/etherCommon.cpp create mode 100644 components/sockets/src/tcpSockets.cpp create mode 100644 components/sockets/src/udpSocekts.cpp create mode 100644 components/tftpShell/CMakeLists.txt create mode 100644 components/tftpShell/extern/embedded_cli.c create mode 100644 components/tftpShell/extern/embedded_cli.h create mode 100644 components/tftpShell/extern/license create mode 100644 components/tftpShell/extern/readme.md create mode 100644 components/tftpShell/include/cliWrapper.hpp diff --git a/.configs/TelnetTeraTerm.INI b/.configs/TelnetTeraTerm.INI new file mode 100644 index 0000000000000000000000000000000000000000..e70423cf65dca52f7cf93cf4de63b638e3f10010 GIT binary patch literal 51330 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 fXW1Vpzwv%NWkV@>EMlEjLnS!Y+sFR_iEk)f literal 0 HcmV?d00001 diff --git a/.configs/UartTeraTerm.INI b/.configs/UartTeraTerm.INI new file mode 100644 index 0000000000000000000000000000000000000000..985dfded5965602ab1cf3402da08bf7dce9215eb GIT binary patch literal 51144 zcmeI5Yj<47m7e=6vsV5CMiY(cj6g`dh!+{MK@gNEUJZbjJd#Hy35wE)pg062%Fg7k zCwZTG`|YYa=QPl~OjbT*^#Z#4oT|Nd?R)L2I{m-@*VnVFv(ILKnf=$hD0kjs*Fka6rX|{=U<-U-b?6 zFBdR;?}gfe`KLl>y~}7A*N*N6+UD%Tf_FI5E#Z1DxDV6=tRBhM?B48#FrF@>*wFv) z^fKi@HlL>Yy60x;2d?1T6PMr$<9aSkKZ|!i6b?NXZfFnHdjof8 zg7MAl3*Gxs7#`~LmY_YKT`R30==-j&KM+5F{Y2j_M=)5P7j&(~-_m$pJ&)9KZT714 zhqJHr4Z8n6hqxiwKh1ube+$j&xmQ{~p4~2O9!sidL6769+Mqko1ILf_8e*KrG1YT9 zrW=yj+U#>-I}nzAy}vB@Ow*diYFQ)CJEHTt=z(*g`UCYwW=O2(2j7qwnh&?WTl8e4 z*wNq0y)}&!ENi;YIN{=QXls(?(*p0G)niZae-LEg^Zk~3^6gr|^<<$fQaCkAxvI-4_M6*y{xbe0le}SgrP(*?*s%5vFJQ%LsV?TCzh| z9*AmxI$U_HQxo^rq@A>f-}XVzB&S1J3s-^or|$md?1J!~UEus=c1qlaJ5SW_f&L@2 z)5V)S5tr~$-^({)h1l*h-G_f0vOV+#yj+gi)QK9o`!PZia2Sv8_pLhcIe59*C7u&%8(0gdlc<}=-3Wg`T zdZus(>|7&`lg*V^GuCT8Mx(@Yc^dGugzHb+8LVj3@{VP=ePSB=lahG(2hr?_klkDR?ydq$H4~~mh}{SuY9PGkQZaddk}A{G^S}fQX6|% zWDRCUMFhnL;+4=B_{{YO#kZK+$f3nQ9OX%rk{6)&*3M`iQXY@Neun7ZN&qVkCx_!d zUE0E3@QRwZA|<$bE{p`=#gO z!qJ!VhkZ_gHvmg?qqQM;|2JXA>Ky;liddI8gT+0OMIbk9>7`yCTb~;p6bi4Yb;aiQs`8Noy0#&^f3 z+3xXG4~qXt#2^EIsVl(SDr5dhysn(ME1I9`{tdlf73KCMhl-78Tyor=k9ffsY8jmj z58Lc8ITcCLYwHEVubN?owyWxY>jYcBBbdxVY(X{p+AG?pI&WTAOeKeY zq?d7#GvYsyCG(ANYpR9uSR;EZW7%C3zmW)j+%e8E-nritW!FxmY&iR!^6=AR8&k8V zu~u53f9UnM#fvBNS{DDkUN>)-G5jnlPmbMbd%PA7r8t)SSPujFb*LD_Jz1l|F{I?&4~e}A_CPw0_orm% zcjWJ=(cl?~HCVZGNclN0z?$jCanxb*7hFsKV;2f3Ni)Sayr}6cS>2aA|6AzgKwDfqKM1)Wh z7__AF#W;}-UfR;D^gBv0_lD!+`{hjl=^O1U8YTnagt-#JVXK=>1rrC3kC#}|I7bkGlyWsg)lI#Dk z%`WT8NBST7!NwRf?)Dzn8GWwhJ$-=D;>dma!HImQeLiy!s@iMqJ-D3j{kt`1ZC(Lq zUXm;Sx8-Q=`G4g&l;<0x%jdU~Av%Mkhwqjod48{#Rg&$!NHW**-b#|sru$sc(nzsw zo6oHjb1m=OGcV2-ZDTfq*7-d$yC)mn*Xz%7DZai8FBL5QZ+rCT@I7~?Y%=%IM1Su4 z_7*w3hfVg^f^!wCZ1MSXtnyNkbmRaZ^Z6s;LksxMbz7yc4ezy3SS9|?X)62Eqt8(q5 zL}cE})orR=E1ZhG^|U?ADpz~4X-~UXavyRHud9$U1noaj*PsF*UtuCS5;F0*{}{lYwgVR6um7x}Pg^2LRZ;SRjHq!)hDR>M0G z9Fw_bn>jnTgLCCOxea~%4SpHMg}J4nFZ2!ML^adHfBM)ze_S9^?*>!#1ol7YOC-k! znkpb7+4F@Sk25}V()9Swr)e-NS4UjHM&41P5$ilPjtt%;#S9w=2}Kh zN3Dz6(4qbxs3)JO58!8jNL{4r8<-CDa`sOp0bHzvqX`%F390h`to~DNI4BvdD>Lgw ztH~i*-DCd1I_;6Z_apFk&sA7=ut!{`(O4$3tN)|Z*1wD8&ZVL$)@k&G9+&4D$hTyf z=jXCKuluz5Sd!r`GP_XjfeA_W{tq9{RsN7B(sPb-x#$fN1V-MY$<~9jbA8DB+MF_= z0XeC_WvvXwV^cc`QTA!QSTKbx69+k%n zB|w0`V5-{u;e71vJ5se|Pjx#UqR+19G3Q`56J7J&=kuD_j zJMeFz)$mSdpv`pQnZU;Y!I&E7^ZB65Et8wt1%KrmldMXN_h$u$uwk1_|CfO4!W3P?hJsHww7q=<~Y!lzp@3#s?H$;i` zuu2Vzv-&aIrQ<4A-MK`)=(%K9tAb-(fn_|ql6poD&c6P7s_4OA+sb3Oh+ck7{ z-mrJUn#U-~*{AiQw%nPwo08F18RIKa{X!$RUxB8n>#hG>u2)Azd+`D6&|;Se^kKcf z6W>3bTOwn~Z5P?{OL1U=E;)_?KhFmr~oQ80Q6kICW5`C(aF2lE6hgao6)#le3{c3h} z+{i0lj~Q;)@vaxWa-I9%OAbahvbqKTK|H@Yk4+Wo4asg(*T?)_mo&XtH+UvGNa=eS@{LXptJMncj-()YQA#TctkiP)0r69XZ+wrW;{z;Z{DC)_h@w<3> z=9yR#zLq=5x}6m~SIg&Juc?R_Y%RBHbV8TXVvPUov6G{i&aCcD`7Y)P$@JLaWN$Gw znw^FVQ*%=!1F!Fi>+ED;CtHOMZr|UMu6X}B845Gbsa9V}hn&NZ{ZF?68(eHDL$B)Y z3Z(gS$p?Tn9iQ6X?amB#WRXc!%E8sP7(!1D0$pvpK&n}o<7ppLSu1p2=R7;wmuR?b z#nD+Qw4It;op&uJldjndQPo_nnQY7Z%-h_!I_*MBivdbSk*;74A-H7*Kd2>x)9nb6OJGq^!?Bo=y@xJeK z694!(x|*Lq{yM#>wWR)mgZC6&+CRn*H{8GX5x(F*rPIb@;9KgHf$KJM#N|O z&VAVx`3UEau(5VG_N3JnsD#O;6x+XHkrPbuT8vJQ_FPFxUQ_C%R z%+*-%^^&8pU%tv=2sdq~q=w~;p|>qt8CBUN-g$IXK-+w~?!>P-xOq6#C-(F*XZUfE zt=})YQ^ykwx732ukVG2SQ`p7k)fx0H?a0a4&HO^Gn9=;I@8~t>&wMVSZAt58H1zN{ z1+9hpW>B_7*Aq#LePXUAG5demQGd70u&5!uE6SNwz@rBpp4G=bq?!shXSZ4&+>j2u zD|)G}|5~Jl59;x}KGiLi+tMDihPmUrq85z|%of+1QM#%&f@!t3ksdwr*Uvp%;qI%v zwDk3%H;5Nl9{=eJhQNI!j%UWoS}!|8A1K}&S1%so-Wm17M-1!6@rO`=Z_D>heZ~T6 zecc%z`x99uthq~QXAOv&1*^|^6#T1Ca9-2ZCyOhai(HYO>7AULxh<<5UiHQw`K-iA zv-R|^eh_|!itK4+U5>ShWs*z#<)ilsKI^HWJbl(mwF(YUMf#ia2I6`>$G4%8Fq(C7 z$!nT!>;1n6ssJs!{-|(+%qr_Zt_ub8AEh4^@8$q+&t+Gg*$vAlY}%(0s2f}@mD+3K z(5-ppi7PkNiuF7`XUxNDCo^MuQ7vGl@|yY}K{BKk1LN;kRN9uoM5~tS{wym|KQ$ck z`MO?59g7@_%#4^9%n#Kb?s5M>?_Lja1PpBBeyDM_H9^-9GHPl}M4!MN)><~onAu&i zQLuO>Z&{^p9plgv)1pH6=%~`&7Vod>9V?(8@E@pR&-$Np&>qVrwMGl4EZ(Q|okg@h z~K?PYifRFxFnr`x&OkW(co?xnHG!6U=SZbqe#I#+J2ES2w+`3axOG z=msaLiALH)qpYT~a#nY#(*hrVxVXQms!Q9C6=%*fvjzu`9g%Z&cYcj}j&}*AN`}ue zUg)wPLF(D*j_q*1F}{R-I;0XS%;tDfc@eD$pKi^J+0h@`nPSg9UD@j*2(`3jr&M_ z4oXjBWMwO__ZAS|(>0$`MnjP;m1gYdJ@u~;-z)TmuI&Q%scdG7Q~Ti@kSF?!<)I|s@QH7nwV?$E1vx+<&&(# zt*br#W8tT-#&XzWa<&_j?dZx$x(;=l>TiMAez8B z3b_#(K01fY`JS^U-Q)a|OYfaG(x$!}758uKU^m*a>9cpVdn=sw!sV?=ZDo@kD%0aF^X3cewn zA(vhW)7xpr$yA)A5AfJ|1T$#QI?zQTRKLOo$FOuiv7~05u6NibmvjF{*V}7o-Zz#t zH2hS{q1Y-hqs}L)Zh<$ugdOkBh~_^RJHsatx0qLt$){dAiA7?mOVc~@9Gv`sb^X@<58C5 zGtA%#cdfhVG+K@AMkZhC3SJ2Piwve?-4KQ$)c)yRy(!p3!B>KQx6GGs3)3(|aJ82D z?vX~zc@S3mAL;!gUGr$a(sk>|p&*fAY^nv8TKffC8=I!QcTdf3fss92b$`acbTrqT zV9Lw2)YV*|ozbt&daOvI?jiL2zMe>6BrVKcwf$v2|H4-hQQO^eu0FJWlB~iwYW9@; zCY}XtPX^;`#Cg>9x#uB^z$+jB)Uj)l%PUdzRPW5A+xkH=C^U?fq)%ejtMAiiroD}Tf82kl5!9ixdXmxBJcwCDjUaHLW|Vk)ca@z`A}LFY^Wu565uOqE zzJ=K@k%LUbGbgk?pLn(={)6)b_A$|hZTTH~UJ{;%qWPunTOREU9`8f+8}4RkxtW?& zoo6E{@I=R_TJ%-2`b-t%;~mcULhK?Zj<93u3S>n1_g>~?JKp2RD4-f^i58&|uCL`> z-YY?KRG>Z!kFWQGyBBO2wD zL!T8BubAWD30(Kz65bxdaoKJkA(B@)8)e1Q94GBLa}QcY076CW_&A883OIdGtaW;Vd;Hu=h(nWLL281(oWt zPx-RwnRE7*{_r-`w;6q&X;Jgp`iWiqWS@Pmk&%)$!;uO-PnRO&#kR~1`zvzLXz9NC z4ZR3hJ!g6)j$k=8(p3yS2P`+{EI|Hp-p_oS)=aE`8ABqbJ%RJ{*mB~ly?u?zz7M8I zD6tmVB*!01C9R1qnSC=3PN9uq!W(APjAHtn8r5L(H9Qntd|pPMtJo^nrpA9&u!&Gv zZQ=CVEq&|b8GDS8nb(Hw3(|8uy4*oa?3vcp6Wj39pK|~1{H{+k9jJ2POq@C&JYCif zJ~(^l8K-Sao<@w{z@B+pOoixnv~^yPQDZ%cK||YTpEEHZPt{tPb*#mES5Tm;x5S|r zyeh9todjOs9in;mKFO2~_ufLIrlhu0XhNn~TPh2tGBGPLsgI=gQ)EV!mS8kgVk(vE zu@`UqWcHiBp0`Z(q7GS~JU8uwdhcI-ZnbhWAB(EiQQ^Y*LygG$kCDaQdF!!m)?UL1 zRI&C^-rn6Bl@s}lx}gnf>Jjn=9%yMV4~ z52bCh(&i{>xZw~o;Hl8SgzjWLQ*>8ILw-#?TXo7#5@8s#>(TZC6>=NNZAod_+m!8Y z8I_z{Y*%OTh-BaR0lfP%9%8jwDcDe}WMz0#XKR(GJ&wo}46E|9UQ!n&VMXMz!fq?GInN;!I#zW^dUO_hkDW*ob)kQl362# zsd2Zs>5px|KSKr%bUIgYhJ|*KY0>gznB=hRT&yo(=BnpSoD}lRBwT=!%&96R2gTYm zB6vkV_jcZqosrPT)94somE3LhfCrqOwvKQ=9H1R*cZc)Q+0%Iz@k%}Oyj@zg^P2QW zv(Ra1fI~kl<}@|qckBiW{2M-n9*krPnf7Fay%r**})|^RxGLLX9gIHM-yGxqJVtYWEdgIVY&U6UU11*_qdm;+$vS)!#X;#YeBt_eCXA z*8G)>*|yeWMJtSRUovs-Z!A1}HhyUA3wtO;9JCtknD%#2h)3qh6V?x`HU0SXwj=p+ zLBYPp)b71GekACC{RVX5KN*4N57pbD=R0Z_`%AA_)tE!~M8)v5;t)!0!#*o^^NiuV zC++=Suz1c6>z?gdNn*XNF161ghV)$yza14L_q>~W5d1(lys8h6#?Wi4aAZ6JYOCxz zBN~u5t=^NeRv4`w7x6q_6lFAMbUE1t6my6RDazO&JF#c zrFazUD(~_ACs*4Hr(*N>8x6#I59)oNjK1bEtwqs#I45l>C*E?x{;uYRcxHTR|BEd= zSC;kaaF+N1FKhm6y{5YBN}nZ=7{QZmKPVVJP?cd%@cqu2FTU@pLYz;zw_8qz@f01d z@Sa(YX$_^=4C~P^rCB|dJ(q|_Ul#gY&B0Q5I_$rx7je*bgBGCq!#d;_^H$gmZN{*_ z5Jwn2oI^uhx2h*Fa=+g7y;eHQ+T4&uI^%gP4ZACzxh~9V(ltqhJT#9moT-BM;iH4b>>tIm$A4+y5@?_|^MI-Dz5wsbrR&kf<* z6G6hqkbh9YVkeg~#Y!oySd-8Fx2GFeS(#xc-&UD_^)#T{)!X4!87&zS85|jz^BdC{ zZ?-jW*U}i7+|n2rOa1j7eC0SZ8@9tk_N{>4XC=``uLiNN6YjcRNckc@#|M7P3%i~|s|J&vNo^q}C6-ko!pV9X- z`i6($>2>RR|G4}=U#@1@{ECJitzlE&&0QVeJ2_qC2JdV zgyr(6t?{1O33j^lU=GUDD+1q8vZA)5dsIB3fmO4qNZ+@KCQtLD?jErog1a5W ztjMLj%ilFm*V(fo@Kju7P99HDC19vO%1;mBjIGz}wZxm{&tgb*5e#mhdS3R!Pz`4X zs$cC`Dtw|saZ5BAJC}h@mvr$usb_)f$O|9UoXn z;ky|N5bAiatIH#tnfn|+-!g`@-C4}c_JzT*#FQ+P20ER3FEbumXSJX4BK4QLj?ZD` zh3D_#FV4ia9>kpCUr>GL|qi zIBG3V%2=7D)GA)}VmB3)Zs~Phnbx+v(q7Tpo1&P?7XFleN4i&gk8xE_SIA@t?z75cpbhirss?x&WtW85mTc+=``<+-V4 z_EKx-=0>DKi&R#yskVNM)Q2;xduk8WjE9(rMe{9CpA`+_O3&HaFZx2hf^YQ<+^0{H z-_$!5t|Q|wf`47Grzp#>As!ZQ;0TLc;&m!t7(v~2ds^J~Zy|l4){M{R()h-VfZ0=K z0IbE3GkfLA^5%q(Pf2)YOs-ZlX-596#1zhH)58&s=dWJNLbcC!BX7%oH_pHI^Gs~Q=Q*(OWV-1=d&9=w zrZenB+sGb?#m<0--PKuan@d1_yArrBzT+8=^o}QF*CW|$%h$Gj&8J<_AuGkFz$?$+ z4h08a2n}sjkw|GR@O-6KE^Fnl#Z)c5_htKnye!@k6Y(a!i6=V(-%D!NbFWZ~t%uuk^7)|W~AIV5q z-Rh;|TngGf+hsEn2m#**0 zp8d}AmHdhG&^-HTd-0Cd`$}p)rY)4UbPcIuWc?gD=e1j1#p54H7Vy^hKU37dBTh!2 z@usvOy7m@=PS(?Bbx#k`3$Jgz3KedhR&ARpN?XXOrukeI5^43>aIY4bPfN3fRd=00 zAJ*@Y(`ILW4E{U`7Ax@t=gJ~4azJd&DkWHt>R>=6BBuhP0~79Z&I z9pp8s3^`W0(O<+BCHM=!Qq(T62xb)crW|-%@S~uxHz|O}x^S6n&1ef6gs^!&#_y zwJ@HoR^j18i*rM^yC;ivHahHC`!T{xr843*zU8>4t3yM`sS%OYw6A^~#3Q_N_=fka z>$J{qe9d(z&WZ55GtAz2`T0=nOa7hu1scCBzFw|jF-N+yA>FX%a*yb}G4~%C=QJ*! zbY@(f*9M<)O~W){qepo4f_ktxL<8bv(svli?hU&$(9U8C#g4rFoU7pcJ7N3tWe^1&@4 zCoB@5mkRcVsHy7${5lNl7}V)wqtWAC-3yd)ysZ6to9fqqv8^_Ow%MLn1^U@EHGOgu zwRfMlly+)sF8U{L(Lxd)$aHiI29i?>>W)2Paud(3n9uKMq(lee5~nZm_hjTlisOUu zZRGmMjI}Iy;EFlkDY}_hS$`9PUw~<0aBc%n$&+!|8RPw0&bRq}7&P*}_E}IN#EY&k zzFm~BwiXh>d9kKwSpJPlpWRP?uR8WU+>i4?>|A0r{n{cL%^1jpTR0a7m;;A7hUcwt z1>T`&^o9QSi$@L|e$xdX-Et#vSe+*eFkgE}@L>!owZZTrkn%*mAwB%w(ilo|;UN^B zdFfmTIQS&*z&a|m3i2xqw%iK+ZT~1c{R(?w(@IBTDm$^Jj2Pf zUX={;+bJ3K`MDn57G?E!+Nsc1JwO+)YUO5{p1aZ+IFde$#JWJeCL=;4&o6;MMHe1h zhsn&zqws0u1{DS?H<#xydfUc(F&B8Goa*^t{x+=V;q&n1(9GU+&Ywiy_zEHoSe>a_ zZp>HluIR7VZ^)~1n!r7#{F3Vz+v+>s8|FC}5NXx6@trAnj1F3%ojr!GoO_LO?7!?$ zh!999l~1q{Z?W_WkNS0eVr-vlx}uV*{f#+iLV3RXne6qHC-*%UJgx<;&_${jSX#U& zaIg+!!8n`;QmIKjFfCIrV7|`p%segaGv}ZcoU!gQ_IrBWP!#caE0yExq3y4Vx;{B` zzR>t|_8U=zXK+Qqx>C;)(l2Lb(t`-))|NEAnPE*ynJNXfA^a(M44Ozy&lONOK=qJs zNZ#5I{xHJi(0wh(RKX8R5;_9)nm(fs74sBb_JnVmhxK(O#uSWRF>JMLyU_AfTG3~D zP}Nr>-X%@&GkM*RTf5D@ld1Z;+Z*$jqAkek=i))ykrMr;_rCQfvhgvl#kMD}ZP_7MFdPtAXR#W;JG+qwHu6UXVjsY=h5>I%+i z(ONQ-~=Hii+nC@UEV+gR1iry^xXf zVdDI_&xMITjxWUTWxZQ@_jFR&~=B>I`mJOz^PAL~Cn3?Kijm{^~1P<83gqSEt& z!(;0EeQ&G;pOeZFmfZWk7V62*2I!&pBmDoG8VSOQ|JOCOm3uG8)it$$u0^f0_F4y5 z{-x2~)cmGAVGj{6LvD$6k^}bH1zxV!J%?EG48B{Xm2EG-Y1UF>|6W(OsCZwLO*}P{NeO#QHm68n+#n;4vYSlwp4V^eTeG0MdYv1GO(vD+!bm@EdvYE<; zTBy?=;l=Fl9d~*Ry*91l6;_R0%OTe&g;tPtA|tOqrq=FRlw;H?kX%9L_bu-&fiz{~ zHCk7i&;VvJl`8T}szkL;yDUyk!6OoEQCjK&)BC|8T zTY`1125n^SOa+jN@uu*iLw=$Il3u>Tv(vQ#8b`mkV!jBj!&Ki5>3npv?RZ?O8q1Jg|2Dt)V-g!ig8Qo z3@_$3nbpLem)oM))ktt66LynfKS#|3$uveNc&D%T>tw*v3ezl$_;5^h{sJc98!!Otm z+g$0|c(>@V4I}(y-VSSPX|HW7ZNKYaR@#{Hhqlc@o6y#_`Lt_OX=~d++wT|ccv2d* z3HJ7V&`9tPlPyeHDB7BAW2|X&b>0U1W*%9`*7BVbcD90ZMVI_*=%%`I>Ds)_6h||{ zou&BFf~SfZ7c`M)lG)*JhO|}8pDpbXDP%OCFKJn2k(M`>w5(JN$N$CB9-$&*yt$-h zr6MhF&0F?X@kCm2sMDja^%C!~x-+`UT(a`zcGo-5kME6U+*#1ax`ZpE%#7FNYrJQb zHO3DW|4*jD`;pH`gQ};(Yf^u;?xJ_mB zH%0xYindpDZedfW11@W3enac%*X2*PHHWw+%iodrf&MMg|9j0W`W-blb^j}2KHi(g z$MvfzAI&+3w`MOU=attrI%~13mh1A-t`t6(POlf)q1{z(@z1M6IX?*}K73U;>!Oo$ z0!W=W2<=``YNL_};6U|nK#kn8L<3jO9HizP^=kn%hw5PU66j&F{`DG9?)#y=eX;{N{ z(VW_jISREq+VgwDAFhx~M3X8M(={dS=*3jqp}gZghi!?@%nMh^wKAWmFv5vdA!L?O zA@p2dotDf+Dx}bY^j3$H`AUT|mfGsjGOMZ3hSFOdR^~rbuq@rxVZ9q`zg_IDwQ_RH zE3$BQ1b!;}`b4(=VR`n;IoVAc?Y2wa?v-i$UG}2ANo0rz@J`+QIu5oPyZ?KcPqn`| z7<CgYYv@AYmtY);%Od@u*X%PsMxuPU7+ zg|-5o^^CE0vtx<%+o$3`JA~`+6j^^FbLN57D%z8xNdBgzP5tJXayMsIJO_{6mGs&y zJ-=60_v517x0+d`B@p`R+-`ICmfBH;csk#;G^}VY?-^R|DKpFIpU0vV%?fp{v!KPpC#5XI(Ei*7cdbW962p~SolJ=$owwV%baiLdzFfF>%TTN?K7H9-&KbE z8|9bq4(R*B=$U$7b#B*5JiBLh($3|PdY&%q%!5<=!2ee7JKEXBb6~#}$F_uJOP}#$ zd9@kKbiWAK)ekp9bsw8j<=d(rcWNOFtyd!-3>bEW0Ln$jI_EX38 z1Q(}#YcD=sTA?R=+Eq=1Q|uKh<2)Iw=R(({;kV1Gnz0k9+!q^oBF;xEVt+i(*ZTn3 zf0%Rkb=Cuf%gS;6dvLvv_G$MYns+v`tVpW$it zth}J1K66e^&)&$>#m}>23H$bLjNg`>H`)8fngEn})y*DisKNGBpD&tZWX`5GnKO6DOwmG)UJm);tZenmHq3`i?{Nsv^CBZ zNVJ00QlD*3W{_+Ezer8Y<8^d_2Yh$+fjtVBbj7ua*B^H~hTY;P_XHa)$f?P_qBo{_ z$bo3O{kr#|dsm~^bkSVshc0v;?+@Mn{e#!lv3_N0t3U(RkSbz$eRgbJ30;NC*IN-? zMo~|}Q1{z4-RuqszoPwc%KS`y32S~sG47=y(7>6`V_U$ zld(8Qxu#gOH*sj*LCxl|T#N)cgYX4fFsb_!4uH5l%T*uP0 z$MiOLShHxKrik>cf7mK#Nh&SpPK3s;0#>AW&MNZ^){LJQ2;ol4hh$;F5G&1|Zm#=U zu1xI%Pw#8R57x>iQG{sdSdvk$7yoXQvA>k7jw;BSQx=B2uXi-oyTmt@ z6On=YH+k7FM22;wJRO}T-zARI%K6n#^Oo$h%9-%UEPF{zg?GHr^V4!(HLdEN6D-Ae zTWI0WGHAp5S0-Q#bIBn^!~0V(dfpLbhr0isYLuM(9k+~4k4Lw&u;nqdf0*?z_pZ|Y zN;7j}7kkj?&4~ib**v+cE03k=WWPw8JZfs+*H`8K-$V&|VF<_@U1arZPxn0wsq-YF zNUv*O6&>+8ROVP_G9JXV;QxGu9TK z`DDHI?l_+D(Q*kZWuE%0GGMQze=^r|w@+&_I=B2l@7eqNNudxJ?5zL_Ye8V0hQb~` zw**RU>mGjIjrSX32gD)v^wc$NdNE_m`Ab?=DD5~y_qjqhJI_|Z2q&g+j44?@M$Si6 zPJx-WZeRC=Vk51EGkeMEB6C{S8QtfUB*w3?!4FP~W(MjCPkNEnx>Do$e`-9ZM!&s^ z)iU?<{KtKQF{`YZX?;Dn9HJa&dHP+vz#*gZ6AYb?1@G4j?|d}3CVo45?C(6rTt~|e z<*75OGm`bM!hzpp-q!DvIaPKOb0USBF_wV!l6CY@t#$0=IU{NCggu_M8Sb(p`4?&b z@i5S%uZxcJJcKL4^-y-h(=@N>Dr*HiYmjf8_oSwBMSqX=?OeIW-3tvTmSJt9BKB;{ z_nAQEzO4@QqeSn%E;!NRD|)R))BB>(UTIoFEk$HTiJ3iiPWY@MIi70GQ^oqWjP!?v zM4}BfDz-HIEcufCVW&B>hU~J#dKrZu3v2rRVSs;H%aAU$G(X3BqiELm3o7dqnLkr2AI^Gh@1L~i)LWRGxze9x8izVGGIcUS+h z6|Ck^?`I@c&h*ljzp?d3Ca+_m>9gGa(qeV%8rxQDS%G()Ei8jj?I2Lzl}-oVyVStI ze0z-xq*klgV{0p`rz~8}LOap}UHV3^Gy3BU0<(&5|wxG4aIv9eYWru bWi8$#%G@NqTj= Max) { + _cnt = 0; + _pFlag = true; + } + } + + bool GetVolatileFlag() { + if (!_pFlag) + return false; + + // Reset _pFlag in any case, so it only be consumed once + // hence volatile + _pFlag = false; + return true; + } + + private: + const unsigned Max; + unsigned _cnt; + bool _pFlag; +}; diff --git a/components/project_bases/portBase.hpp b/components/project_bases/include/portBase.hpp similarity index 100% rename from components/project_bases/portBase.hpp rename to components/project_bases/include/portBase.hpp diff --git a/components/project_bases/include/rotatingIndex.hpp b/components/project_bases/include/rotatingIndex.hpp new file mode 100644 index 0000000..7fc0916 --- /dev/null +++ b/components/project_bases/include/rotatingIndex.hpp @@ -0,0 +1,58 @@ +/** + * @file rotatingIndex.hpp + * @author Gustice + * @brief Classes for typical index manipulation + * + * @copyright Copyright (c) 2020 + */ +#pragma once + +#include + +/** + * @brief Index with automatic revolution back to start position + * @details Useful for Buffer-Pointer, or similar tasks + */ +class RotatingIndex { + public: + /// @brief Value at witch the index rotates back to beginning + const uint16_t MaxIndex; + + /// @brief Constructor for rotating index + /// @brief output index will be set to 0 if incremented value reaches Maximum + /// @param max Maximum index for overflow-condition + RotatingIndex(uint16_t max) : MaxIndex(max) { _index = 0; } + + /// @brief Get the current index and inkrement index afterwards + /// @return uint16_t current index + uint16_t getIndexAndIncrement(void) { + auto retVal = _index; + _index++; + if (_index >= MaxIndex) { + _index = 0; + } + return retVal; + }; + + /// @brief Get the current index without incrementing the index + /// @return uint16_t current index + uint16_t getIndex(void) { return _index; } + + /// @brief Check if next get will lead to overflow condition + bool peekRevolution(void) { + if (MaxIndex == 0) + return true; + + if (_index >= MaxIndex - 1) { + return true; + } + return false; + } + + /// @brief Reset Index to 0 + void resetIndex(void) { _index = 0; } + + private: + /// Current Index + uint16_t _index; +}; diff --git a/components/project_bases/include/utils.hpp b/components/project_bases/include/utils.hpp new file mode 100644 index 0000000..8ed23d4 --- /dev/null +++ b/components/project_bases/include/utils.hpp @@ -0,0 +1,95 @@ +/** + * @file utils.hpp + * @author Gustice + * @brief Template + * + * @copyright Copyright (c) 2024 + */ +#pragma once + +#include +#include +#include +#include +#include + +/** + * @brief Simple exception + */ +class CoreException : public std::exception { + char const *Text; + + public: + /// @brief Constructor with simple pointer to text + CoreException(char const *text) : Text(text) { + } + + /// @brief error message getter + char const *what() const throw() override { return Text; } +}; + +/** + * @brief Common interface for character streams + */ +class CharStream { + public: + /// @brief Read method + /// @return string + virtual std::string read() = 0; + + /// @brief Write character + /// @param c character to write + virtual void write(char c) = 0; + + /// @brief Write string + /// @param str string to write + virtual void write(std::string str) = 0; +}; + +/** + * @brief Common interface + */ +class ByteStream { + /// @brief Read method + /// @return series of bytes + virtual std::vector read() = 0; + + /// @brief write single byte + /// @param b byte to write + virtual void write(uint8_t b) = 0; + + /// @brief write series of bytes + /// @param data series to write + virtual void write(std::vector data) = 0; +}; + +/** + * @brief Base for constructs that allow only one concurrently active instance + * + * @tparam T + * @note needs a `T * T::SingleActive::active{};` definition in source + */ +template +class SingleActive { + static bool active; + + public: + /// @brief Constructor, memorizes if already on instance was successfully build + SingleActive() { + if (active) { + throw "Instance already active"; + } + assert(active == false && "Constructor my only be run if no instance active"); + active = true; + } + + /// @brief Destructor, clears memory of existing instance + ~SingleActive() { + assert(active == true && "Destructor my only be run on active instance"); + active = false; + } + + // Disable move and assignment + SingleActive(SingleActive &other) = delete; + void operator=(const SingleActive &) = delete; +}; diff --git a/components/project_bases/src/utils.cpp b/components/project_bases/src/utils.cpp new file mode 100644 index 0000000..c31d1f5 --- /dev/null +++ b/components/project_bases/src/utils.cpp @@ -0,0 +1,9 @@ +/** + * @file utils.cpp + * @author Gustice + * @brief Implementation of utilities + * + * @copyright Copyright (c) 2024 + */ + +#include "utils.hpp" diff --git a/components/sockets/CMakeLists.txt b/components/sockets/CMakeLists.txt new file mode 100644 index 0000000..473ab9d --- /dev/null +++ b/components/sockets/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "src" + INCLUDE_DIRS "include" + REQUIRES esp_netif lwip project_bases freeRtosApi +) diff --git a/components/sockets/include/etherCommon.hpp b/components/sockets/include/etherCommon.hpp new file mode 100644 index 0000000..db13aa7 --- /dev/null +++ b/components/sockets/include/etherCommon.hpp @@ -0,0 +1,196 @@ +/** + * @file etherCommon.hpp + * @author Gustice + * @brief Helper Functions for Ethernet usage + * + * @copyright Copyright (c) 2024 + */ +#pragma once + +#include "esp_log.h" +#include "esp_netif.h" +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include +#include +#include +#include + +#if defined(CONFIG_LWIP_NETBUF_RECVINFO) +constexpr bool NETBUF_RECVINFO = true; +#else +constexpr bool NETBUF_RECVINFO = false; +#endif +#if defined(CONFIG_EXAMPLE_IPV4) +constexpr bool USE_IPV4 = true; +#else +constexpr bool USE_IPV4 = false; +#endif +#if defined(CONFIG_EXAMPLE_IPV6) +constexpr bool USE_IPV6 = true; +#else +constexpr bool USE_IPV6 = false; +#endif + +class Ip { + public: + Ip(uint32_t ipV4) { + _address.word = ipV4; + } + Ip(esp_ip4_addr_t ipV4) { + _address.word = ipV4.addr; + } + + Ip(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) { + _address.bytes[0] = b0; + _address.bytes[1] = b1; + _address.bytes[2] = b2; + _address.bytes[3] = b3; + } + + Ip(std::string ipStr) { + _address.word = ipaddr_addr(ipStr.c_str()); + } + + uint8_t operator[](int index) const { return _address.bytes[index]; }; + uint32_t asWord() const { return _address.word; } + std::string asString() const { return inet_ntoa(_address.word); } + + private: + union { + uint8_t bytes[4]; // IPv4 address + uint32_t word; + } _address; +}; + +class Mac { + public: + Mac(uint8_t val[NETIF_MAX_HWADDR_LEN]) { + std::memcpy(bytes.begin(), val, NETIF_MAX_HWADDR_LEN); + } + + uint8_t operator[](int index) const { + return bytes[index]; + }; + + std::string asString() const { + char buffer[64]; + sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x", bytes[0], bytes[1], + bytes[2], bytes[3], bytes[4], bytes[5]); + return {buffer}; + } + + const std::array &asArray() const { + return bytes; + } + + private: + std::array bytes; // IPv4 address +}; + +/** + * @brief IP-set class + */ +struct IpInfo { + /// @brief IP address + Ip address{0u}; + /// @brief IP subnet + Ip subnet{0u}; + /// @brief Gateway IP + Ip gateway{0u}; + /// @brief MAC-Address + Mac macAddress; + + /// @brief Get broadcast address from IP and subnet + /// @return + Ip getBradcastAddress() const { + auto sn = address.asWord() | ~subnet.asWord(); + return Ip(sn); + } +}; + +struct IpAdr { + public: + IpAdr(std::string ipStr, uint16_t port); + IpAdr(uint32_t ip, uint16_t port); + IpAdr(uint16_t port); + IpAdr(sockaddr_in ipV4data); + + static constexpr int IpProtocol = IPPROTO_IP; // IPPROTO_IPV6 for version 6 + static constexpr int ProtocolFamily = AF_INET; // AF_INET6 for version 6 + /// @brief StackFamily e.g. AF_INET or AF_INET6 + sa_family_t getProtocolFamily() const { return addrStore.ss_family; } + + std::string getIp() { + struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore; + return inet_ntoa(ipV4->sin_addr.s_addr); + } + uint16_t getPort() const { + struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore; + return ntohs(ipV4->sin_port); + }; + struct sockaddr *asSockAddrPtr() const { return (struct sockaddr *)&addrStore; } + + static constexpr int Size = sizeof(sockaddr_storage); + private: + struct sockaddr_storage addrStore; +}; + +struct IpAdrV6 { + public: + IpAdrV6(std::string ipStr, uint16_t port); + IpAdrV6(in6_addr ip, uint16_t port); + IpAdrV6(uint16_t port); + IpAdrV6(sockaddr_in6 ipV6data); + + static constexpr int IpProtocol = IPPROTO_IPV6; + static constexpr int ProtocolFamily = AF_INET6; + /// @brief StackFamily e.g. AF_INET or AF_INET6 + sa_family_t getProtocolFamily() const { return addrStore.ss_family; } + + std::string getIp() { + struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore; + return inet6_ntoa(ipV6->sin6_addr); + + } + uint16_t getPort() const { + struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore; + return ntohs(ipV6->sin6_port); + }; + struct sockaddr *asSockAddrPtr() const { return (struct sockaddr *)&addrStore; } + + static constexpr int Size = sizeof(sockaddr_storage); + private: + struct sockaddr_storage addrStore; +}; + +/** + * @brief Simplified abstraction for sockets + */ +class SocketBase { + public: + /// @brief Get if stream is active + /// @return true if active + virtual bool isActive() { + return active; + } + + IpInfo getIpInfo() { + auto *netIf = esp_netif_get_default_netif(); + esp_netif_ip_info_t ip; + uint8_t mac[NETIF_MAX_HWADDR_LEN]; + ESP_ERROR_CHECK(esp_netif_get_ip_info(netIf, &ip)); + ESP_ERROR_CHECK(esp_netif_get_mac(netIf, mac)); + + IpInfo ips{ + .address = Ip{ip.ip}, + .subnet = Ip(ip.netmask), + .gateway = Ip(ip.netmask), + .macAddress = Mac(mac)}; + return ips; + } + + protected: + bool active; +}; diff --git a/components/sockets/include/tcpSockets.hpp b/components/sockets/include/tcpSockets.hpp new file mode 100644 index 0000000..93444f9 --- /dev/null +++ b/components/sockets/include/tcpSockets.hpp @@ -0,0 +1,307 @@ +/** + * @file tcpSockets.hpp + * @author Gustice + * @brief Function wrapper for ESP-IDF socket implementation + * + * @copyright Copyright (c) 2024 + */ + +#pragma once + +#include "esp_log.h" +#include "etherCommon.hpp" +#include "utils.hpp" +#include +#include + +/** + * @brief TCP-Socket wrapper for ESP-IDF socket implementation + */ +class TcpSocket { + public: + /// @brief Constructor + /// @note Socket not active until call of attach method + /// @param addr Server/Client address + TcpSocket(const IpAdr &addr) + : Local(addr) { + _listenSockId = socket(addr.ProtocolFamily, SOCK_STREAM, addr.IpProtocol); + if (_listenSockId < 0) { + ESP_LOGE("TCP", "Unable to create socket: errno %d", errno); + throw CoreException("Unable to create socket"); + } + + if (addr.ProtocolFamily == IPPROTO_IP) { + int opt = 1; + setsockopt(_listenSockId, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + } + + if constexpr (USE_IPV4 && USE_IPV6) { + int opt = 1; + // Note that by default IPV6 binds to both protocols, it is must be disabled + // if both protocols used at the same time (used in CI) + setsockopt(_listenSockId, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); + } + + ESP_LOGI("TCP", "Socket created"); + } + + /// @brief Destructor + ~TcpSocket() { + close(_listenSockId); + } + + /// @brief Attach to configured socket + /// @note Blocks until successful or failed + /// @return true if successful + bool bindTo() { + int err = bind(_listenSockId, Local.asSockAddrPtr(), sizeof(sockaddr_storage)); + if (err != 0) { + ESP_LOGE("TCP", "Socket unable to bind: errno %d", errno); + ESP_LOGE("TCP", "IPPROTO: %d", Local.ProtocolFamily); + throw CoreException("Socket unable to bind"); + } + ESP_LOGI("TCP", "Socket bound, port %d", Local.getPort()); + + err = listen(_listenSockId, 1); + if (err != 0) { + ESP_LOGE("TCP", "Error occurred during listen: errno %d", errno); + throw CoreException("Error occurred during listen"); + } + return true; + } + + /// @brief Get socket id for stream-operations + /// @return Id of socket + int getSocketId() { + return _listenSockId; + } + + /// @brief Address of this station (server perspective) + const IpAdr &Local; + + private: + int _listenSockId; +}; + +/** + * @brief TCP session + */ +class TcpSession : public SocketBase, public CharStream { + public: + /** + * @brief Configuration structure for Session + */ + struct Config { + int keepAlive; + int keepIdle; + int keepInterval; + int keepCount; + }; + + /// @brief Constructor + /// @note Constructor blocks + /// @param config Configuration for session + /// @param listener reference to socket + TcpSession(Config &config, TcpSocket &listener) : _other(IPADDR_ANY, 0) { + socklen_t addr_len = _other.Size; + _socket = accept(listener.getSocketId(), _other.asSockAddrPtr(), &addr_len); + if (_socket < 0) { + ESP_LOGE("TCP", "Unable to accept connection: errno %d", errno); + active = false; + throw CoreException("Unable to accept connection"); + } + + // Set tcp keepalive option + setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, &config.keepAlive, sizeof(int)); + setsockopt(_socket, IPPROTO_TCP, TCP_KEEPIDLE, &config.keepIdle, sizeof(int)); + setsockopt(_socket, IPPROTO_TCP, TCP_KEEPINTVL, &config.keepInterval, sizeof(int)); + setsockopt(_socket, IPPROTO_TCP, TCP_KEEPCNT, &config.keepCount, sizeof(int)); + + std::string addrStr; + if (_other.ProtocolFamily == listener.Local.ProtocolFamily) { + if (_other.getProtocolFamily() == PF_INET) { + addrStr = _other.getIp(); + // } else if (_other.addrStore.ss_family == PF_INET6) { + // inet6_ntoa_r(((struct sockaddr_in6 *)&_other.addrStore)->sin6_addr, addrStr, sizeof(addrStr) - 1); + } else { + throw CoreException("Unknown case"); + } + } else { + ESP_LOGW("TCP", "Wrong IP-version request"); + active = false; + } + + ESP_LOGI("TCP", "Socket accepted ip address: %s", addrStr.c_str()); + + active = true; + } + + /// @brief Destructor + ~TcpSession() { + if (_socket < 0) + return; + + ESP_LOGD("TCP", "Tearing down socket"); + shutdown(_socket, 0); + } + + /// @brief Get IP of remote + /// @return + IpAdr getSource() { + return _other; + } + + /// @brief Read string + /// @note This call blocks until data is received + /// @return read string + std::string read() override { + auto data = readRaw(); + if (data.size() == 0) { + return ""; + } + + std::string ret((const char *)&data.data()[0]); + return ret; + } + + /// @brief Write character to socket + /// @param b character to write + void write(char c) override { + if (!isActive()) { + return; + } + + int written = send(_socket, &c, 1, 0); + if (written < 0) { + ESP_LOGE("TCP", "Error occurred during sending: errno %d", errno); + } + } + + /// @brief Write string to specific socket + /// @param message payload + /// @param dest Destination for message + void write(std::string message) override { + if (!isActive()) { + return; + } + + int written = send(_socket, message.c_str(), message.size(), 0); + if (written < 0) { + ESP_LOGE("TCP", "Error occurred during sending: errno %d", errno); + } + } + + IpAdr getOther() { + return _other; + } + + protected: + int _socket = 0; + IpAdr _other; + + std::vector readRaw() { + uint8_t data[128]; + int len = recv(_socket, data, sizeof(data) - 1, 0); + + if (len < 0) { + ESP_LOGE("TCP", "Error occurred during receiving: errno %d", errno); + active = false; + return {}; + } + + data[len] = 0; + if (len == 0) { + ESP_LOGW("TCP", "Connection closed"); + active = false; + return {}; + } + + ESP_LOGD("TCP", "Received %d bytes: %s", len, data); + std::vector vec(data, data + len + 1); + return vec; + } +}; + +constexpr TcpSession::Config DefaultTcpConfig{ + .keepAlive = 1, + .keepIdle = 5, + .keepInterval = 5, + .keepCount = 3, +}; + +constexpr uint16_t TelnetPort = 23; + +/** + * @brief TCP-Socket for Telnet-Applications + */ +class TelnetServer { + + /** + * @brief Specialization of TCP-Session for Telnet + */ + class TelnetSession : public TcpSession { + + public: + // Constructor + TelnetSession(Config &config, TcpSocket &listener) : TcpSession(config, listener) { + } + + /// @brief Read string + /// @note This call blocks until data is received + /// @return read string + std::string read() override { + auto data = readRaw(); + if (data.size() == 0) { + return ""; + } + + data.pop_back(); // 0-termination is not needed here + + int i = 0; + while (data[i] == 0xFF) { // while telnet command + i += 3; // skip this command + } + + if (i == data.size()) { // only configuration + send(_socket, data.data(), data.size(), 0); + return ""; + } + + std::string ret((const char *)&data[i]); + return ret; + } + }; + + public: + /// @brief Constructor + /// @param addrFamily IPV4 or IPV6 + TelnetServer(int addrFamily) + : Local(addrFamily == AF_INET ? IpAdr(INADDR_ANY, TelnetPort) : IpAdr(TelnetPort)), + Config(DefaultTcpConfig) { + } + + /** + * @brief Start server + * + * @param initCall Callback for shell init + * @param cyclicCall Callback for cyclic polling + */ + void run(std::function initCall, std::function cyclicCall) { + TcpSocket socket(Local); + socket.bindTo(); + while (true) { + // Run telnet-session as long socket is active else restart socket + ESP_LOGI("Telnet", "Socket waiting for connect"); + TelnetSession session(Config, socket); + + initCall(session); + while (session.isActive()) { + cyclicCall(session); + } + } + } + + private: + IpAdr Local; + TcpSession::Config Config; +}; diff --git a/components/sockets/include/udpSockets.hpp b/components/sockets/include/udpSockets.hpp new file mode 100644 index 0000000..3f6682c --- /dev/null +++ b/components/sockets/include/udpSockets.hpp @@ -0,0 +1,303 @@ +/** + * @file udpSockets.hpp + * @author Gustice + * @brief UDP-Socket wrapper for ESP-IDF socket implementation + * + * @copyright Copyright (c) 2024 + */ +#pragma once + +#include "Task.hpp" +#include "esp_log.h" +#include "etherCommon.hpp" +#include "utils.hpp" +#include +#include +#include + +constexpr bool UDP_WITH_TIMEOUT = false; + +/** + * @brief UDP-Socket wrapper for ESP-IDF socket implementation + */ +class UdpSocket : public ByteStream, public SocketBase { + public: + /// @brief Constructor for UDP-Socket-Server + /// @note Socket not active until call of attach method + /// @param addr Server/Client address + UdpSocket(const IpAdr &addr) + : Local(addr), _other(INADDR_ANY, 0) { + _listenSockId = socket(addr.ProtocolFamily, SOCK_DGRAM, addr.IpProtocol); + if (_listenSockId < 0) { + ESP_LOGE("UDP", "Unable to create socket: errno %d", errno); + throw CoreException("Unable to create socket"); + } + ESP_LOGD("UDP", "Socket created"); + + if constexpr (NETBUF_RECVINFO && !USE_IPV6) { + int enable = 1; + lwip_setsockopt(_listenSockId, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable)); + } + + if constexpr (USE_IPV4 && USE_IPV6) { + if (addr.ProtocolFamily == AF_INET6) { + // Note that by default IPV6 binds to both protocols, it is must be disabled + // if both protocols used at the same time (used in CI) + int opt = 1; + setsockopt(_listenSockId, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + setsockopt(_listenSockId, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); + } + } + + if constexpr (UDP_WITH_TIMEOUT) { // Set timeout if desired + struct timeval timeout { + .tv_sec = 10, + .tv_usec = 0, + }; + setsockopt(_listenSockId, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + } + } + + /// @brief Destructor + ~UdpSocket() { + active = false; + ESP_LOGD("UDP", "Shutting down socket and restarting..."); + shutdown(_listenSockId, 0); + close(_listenSockId); + } + + /// @brief Attach to configured socket + /// @note Blocks until successful or failed + /// @return true if successful + bool bindTo() { + active = false; + int err = bind(_listenSockId, Local.asSockAddrPtr(), sizeof(sockaddr_storage)); + if (err < 0) { + ESP_LOGE("UDP", "Socket unable to bind: errno %d", errno); + throw CoreException("Socket unable to bind"); + } + ESP_LOGI("UDP", "Socket bound, port %d", Local.getPort()); + + if constexpr (NETBUF_RECVINFO && !USE_IPV6) { + _iov.iov_base = _rxBuffer; + _iov.iov_len = sizeof(_rxBuffer); + _msg.msg_control = _cmsg_buf; + _msg.msg_controllen = sizeof(_cmsg_buf); + _msg.msg_flags = 0; + _msg.msg_iov = &_iov; + _msg.msg_iovlen = 1; + _msg.msg_name = _other.asSockAddrPtr(); + _msg.msg_namelen = _other.Size; + } + + active = true; + return active; + } + + /// @brief Read from socket + /// @note This call blocks until data is received + /// @return read bytes + std::vector read() override { + char buffer[256]; + int len = 0; + if constexpr (NETBUF_RECVINFO && !USE_IPV6) { + len = recvmsg(_listenSockId, &_msg, 0); + } else { + socklen_t socklen = _other.Size; + len = recvfrom(_listenSockId, buffer, sizeof(buffer) - 1, 0, _other.asSockAddrPtr(), &socklen); + } + if (len < 0) { + ESP_LOGE("UDP", "recvfrom failed: errno %d", errno); + return {}; + } + + std::string addrStr; + // Get the sender's ip address as string + if (Local.ProtocolFamily == PF_INET) { + addrStr = _other.getIp(); + + if constexpr (NETBUF_RECVINFO && !USE_IPV6) { + struct cmsghdr *cmsgtmp; + for (cmsgtmp = CMSG_FIRSTHDR(&_msg); cmsgtmp != NULL; cmsgtmp = CMSG_NXTHDR(&_msg, cmsgtmp)) { + if (cmsgtmp->cmsg_level == IPPROTO_IP && cmsgtmp->cmsg_type == IP_PKTINFO) { + struct in_pktinfo *pktinfo; + pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsgtmp); + ESP_LOGI("UDP", "dest ip: %s", inet_ntoa(pktinfo->ipi_addr)); + } + } + } + } // else if (Local.ProtocolFamily == PF_INET6) { + // inet6_ntoa_r(((struct sockaddr_in6 *)&_other.addrStore)->sin6_addr, addrStr, sizeof(addrStr) - 1); + // } + + ESP_LOGD("UDP", "Received %d bytes from %s:", len, addrStr.c_str()); + std::vector ret(buffer, buffer + len); + return ret; + } + + /// @brief Write character to socket + /// @param b character to write + void write(uint8_t b) override {} + + /// @brief Write bytes to socket + /// @param data payload as series of bytes + void write(std::vector data) override { + write(&data[0], data.size()); + } + + /// @brief Write string to specific socket + /// @param message payload + /// @param dest Destination for message + void write(std::string &message, IpAdr *dest = nullptr) { + auto data = message.c_str(); + write((uint8_t *)data, message.size() + 1, dest); + } + + /// @brief Write string to specific socket or to last source + /// @param data payload + /// @param size size of data + /// @param addr Destination for message (if nullptr it will be send to last source) + void write(uint8_t *data, uint size, IpAdr *dest = nullptr) { + int err; + if (dest == nullptr) { + err = sendto(_listenSockId, data, size, 0, _other.asSockAddrPtr(), sizeof(sockaddr_storage)); + } else { + struct sockaddr_in *ipV4 = (struct sockaddr_in *)dest->asSockAddrPtr(); + // ESP_LOGI("UDP", "EthIP: %s", inet_ntoa(ipV4->sin_addr.s_addr)); + // ESP_LOGI("UDP", "PORT: %i", ntohs(ipV4->sin_port)); + err = sendto(_listenSockId, data, size, 0, dest->asSockAddrPtr(), sizeof(sockaddr_storage)); + } + + if (err < 0) { + ESP_LOGE("UDP", "Error occurred during sending: errno %d", errno); + active = false; + } + } + + /// @brief Standard destination address + const IpAdr &Local; + + /// @brief query if stream is still active + /// @return true if active + bool isValid() { + return _listenSockId >= 0; + } + + IpAdr getOther() { + return _other; + } + + private: + int _listenSockId; // os handle for socket + IpAdr _other; // Remote Station + char _rxBuffer[128]; + + // Only used if NETBUF_RECVINFO is active + iovec _iov; + msghdr _msg; + u8_t _cmsg_buf[CMSG_SPACE(sizeof(struct in_pktinfo))]; +}; + +/** + * @brief + * @details according to RFC-5424 + * https://datatracker.ietf.org/doc/html/rfc5424 + */ +class Syslog { + public: + static constexpr uint16_t SyslogPort = 514; + static constexpr std::string_view NilValue = "-"; + + enum class Severity { + Emergency = 0, + Alert = 1, + Critical = 2, + Error = 3, + Warning = 4, + Notice = 5, + Informational = 6, + Debug = 7, + }; + static constexpr uint8_t SeverityMask = 0x000F; + + enum class Facility { + kern = 0 << 3, + User = 1 << 3, + Mail = 2 << 3, + Daemon = 3 << 3, + Auth = 4 << 3, + Syslog = 5 << 3, + Lpr = 6 << 3, + News = 7 << 3, + Uucp = 8 << 3, + Cron = 9 << 3, + Authpriv = 10 << 3, + Ftp = 11 << 3, + Ntp = 12 << 3, + LogAudit = 13 << 3, + LogAlert = 14 << 3, + Clock = 15 << 3, + Local0 = 16 << 3, + Local1 = 17 << 3, + Local2 = 18 << 3, + Local3 = 19 << 3, + Local4 = 20 << 3, + Local5 = 21 << 3, + Local6 = 22 << 3, + Local7 = 23 << 3, + }; + + static void log(Severity severity, std::string message) { + if (_Socket == nullptr) { + return; + } + + char buffer[512]; + // V T H A P M D Msg + // alternatively with BOM flag UTF8 \xEF\xBB\xBF + auto pos = sprintf(buffer, "<%i>1 - %s %s - - - %s", + static_cast(_Facility) | static_cast(severity), + _HostName.c_str(), + _AppName.c_str(), + message.c_str()); + _Socket->write((uint8_t *)buffer, pos, &_Target); + } + + template + static void logv(Severity severity, const char *fmt, Params&&... args) { + if (_Socket == nullptr) { + return; + } + + char buffer[512]; + // V T H A P M D Msg + // alternatively with BOM flag UTF8 \xEF\xBB\xBF + auto pos = sprintf(buffer, "<%i>1 - %s %s - - - ", + static_cast(_Facility) | static_cast(severity), + _HostName.c_str(), + _AppName.c_str()); + pos += sprintf(&buffer[pos], fmt, std::forward (args)...); + + _Socket->write((uint8_t *)buffer, pos, &_Target); + } + + static void setupTask(IpAdr target) { + if (_Socket) { + return; + } + _Facility = Facility::Syslog; + _HostName = "ESP"; + _AppName = "App"; + _Target = IpAdr{target.getIp(), SyslogPort}; + + static fos::Task syslogTask(SyslogTask, "SysLog", 4096); + } + + private: + static void SyslogTask(); + static Facility _Facility; + static std::string _HostName; + static std::string _AppName; + static UdpSocket *_Socket; + static IpAdr _Target; +}; \ No newline at end of file diff --git a/components/sockets/src/etherCommon.cpp b/components/sockets/src/etherCommon.cpp new file mode 100644 index 0000000..70ebc70 --- /dev/null +++ b/components/sockets/src/etherCommon.cpp @@ -0,0 +1,68 @@ +/** + * @file etherCommon.cpp + * @author Gustice + * @brief Helper Functions for Ethernet usage + * + * @copyright Copyright (c) 2024 + */ + +#include "etherCommon.hpp" +#include + +IpAdr::IpAdr(std::string ipStr, uint16_t port) + : IpAdr(ipaddr_addr(ipStr.c_str()), port) { +} + +IpAdr::IpAdr(uint32_t ip, uint16_t port) { + struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore; + ipV4->sin_len = sizeof(sockaddr_in); + ipV4->sin_addr.s_addr = ip; + ipV4->sin_family = AF_INET; + ipV4->sin_port = htons(port); +} + +IpAdr::IpAdr(uint16_t port) { + struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore; + ipV4->sin_len = sizeof(sockaddr_in); + ipV4->sin_addr.s_addr = INADDR_ANY; + ipV4->sin_family = AF_INET; + ipV4->sin_port = htons(port); +} + +IpAdr::IpAdr(sockaddr_in ipV4data) { + struct sockaddr_storage *store = (struct sockaddr_storage *)&ipV4data; + assert(ipV4data.sin_family == AF_INET); + addrStore = *store; +} + + +IpAdrV6::IpAdrV6(std::string ipStr, uint16_t port) { + struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore; + ipV6->sin6_len = sizeof(sockaddr_in6); + inet6_aton(ipStr.c_str(), &ipV6->sin6_addr); + ipV6->sin6_family = AF_INET6; + ipV6->sin6_port = htons(port); +} + +IpAdrV6::IpAdrV6(in6_addr ip, uint16_t port) { + struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore; + ipV6->sin6_len = sizeof(sockaddr_in6); + memcpy(&ip, &ipV6->sin6_addr, sizeof(in6_addr)); + ipV6->sin6_family = AF_INET6; + ipV6->sin6_port = htons(port); +} + +IpAdrV6::IpAdrV6(uint16_t port) { + struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore; + ipV6->sin6_len = sizeof(sockaddr_in6); + // ip6_addr_set_any() + bzero(&ipV6->sin6_addr.un, sizeof(ipV6->sin6_addr.un)); + ipV6->sin6_family = AF_INET6; + ipV6->sin6_port = htons(port); +} + +IpAdrV6::IpAdrV6(sockaddr_in6 ipV6data) { + struct sockaddr_storage *store = (struct sockaddr_storage *)&ipV6data; + assert(ipV6data.sin6_family == AF_INET); + addrStore = *store; +} diff --git a/components/sockets/src/tcpSockets.cpp b/components/sockets/src/tcpSockets.cpp new file mode 100644 index 0000000..09bbb01 --- /dev/null +++ b/components/sockets/src/tcpSockets.cpp @@ -0,0 +1,9 @@ +/** + * @file tcpSockets.cpp + * @author Gustice + * @brief Function frames for handling sockets + * + * @copyright Copyright (c) 2024 + */ + +#include "tcpSockets.hpp" \ No newline at end of file diff --git a/components/sockets/src/udpSocekts.cpp b/components/sockets/src/udpSocekts.cpp new file mode 100644 index 0000000..e50eee9 --- /dev/null +++ b/components/sockets/src/udpSocekts.cpp @@ -0,0 +1,35 @@ +/** + * @file udpSockets.cpp + * @author Gustice + * @brief Function frames for handling sockets + * + * @copyright Copyright (c) 2024 + */ + +#include "udpSockets.hpp" + +UdpSocket * Syslog::_Socket; +Syslog::Facility Syslog::_Facility; +std::string Syslog::_HostName; +std::string Syslog::_AppName; +IpAdr Syslog::_Target(INADDR_ANY, 0); + +void Syslog::SyslogTask() { + ESP_LOGI("SysLog", "Starting Syslog Task"); + try { + while (true) { + IpAdr ip(INADDR_ANY, SyslogPort); + UdpSocket socket(ip); + _Socket = &socket; + socket.bindTo(); + + while (socket.isActive()) { + (void)socket.read(); + // ignore all incomming data + } + } + _Socket = nullptr; + } catch (const std::exception &e) { + ESP_LOGE("SysLog", "%s", e.what()); + } +} diff --git a/components/tftpShell/CMakeLists.txt b/components/tftpShell/CMakeLists.txt new file mode 100644 index 0000000..77b2c2a --- /dev/null +++ b/components/tftpShell/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRC_DIRS "extern" + INCLUDE_DIRS "include" "extern" +) diff --git a/components/tftpShell/extern/embedded_cli.c b/components/tftpShell/extern/embedded_cli.c new file mode 100644 index 0000000..cffbf53 --- /dev/null +++ b/components/tftpShell/extern/embedded_cli.c @@ -0,0 +1,1156 @@ +#include +#include + +#include "embedded_cli.h" + +#define CLI_TOKEN_NPOS 0xffff + +#define UNUSED(x) (void)x + +#define PREPARE_IMPL(t) EmbeddedCliImpl *impl = (EmbeddedCliImpl *)t->_impl + +#define IS_FLAG_SET(flags, flag) (((flags) & (flag)) != 0) + +#define SET_FLAG(flags, flag) ((flags) |= (flag)) + +#define UNSET_U8FLAG(flags, flag) ((flags) &= (uint8_t) ~(flag)) + +/** + * Marks binding as candidate for autocompletion + * This flag is updated each time getAutocompletedCommand is called + */ +#define BINDING_FLAG_AUTOCOMPLETE 1u + +/** + * Indicates that rx buffer overflow happened. In such case last command + * that wasn't finished (no \r or \n were received) will be discarded + */ +#define CLI_FLAG_OVERFLOW 0x01u + +/** + * Indicates that initialization is completed. Initialization is completed in + * first call to process and needed, for example, to print invitation message. + */ +#define CLI_FLAG_INIT_COMPLETE 0x02u + +/** + * Indicates that CLI structure and internal structures were allocated with + * malloc and should bre freed + */ +#define CLI_FLAG_ALLOCATED 0x04u + +/** + * Indicates that CLI structure and internal structures were allocated with + * malloc and should bre freed + */ +#define CLI_FLAG_ESCAPE_MODE 0x08u + +/** + * Indicates that CLI in mode when it will print directly to output without + * clear of current command and printing it back + */ +#define CLI_FLAG_DIRECT_PRINT 0x10u + +/** + * Indicates that live autocompletion is enabled + */ +#define CLI_FLAG_AUTOCOMPLETE_ENABLED 0x20u + +typedef struct EmbeddedCliImpl EmbeddedCliImpl; +typedef struct AutocompletedCommand AutocompletedCommand; +typedef struct FifoBuf FifoBuf; +typedef struct CliHistory CliHistory; + +struct FifoBuf { + char *buf; + /** + * Position of first element in buffer. From this position elements are taken + */ + uint16_t front; + /** + * Position after last element. At this position new elements are inserted + */ + uint16_t back; + /** + * Size of buffer + */ + uint16_t size; +}; + +struct CliHistory { + /** + * Items in buffer are separated by null-chars + */ + char *buf; + + /** + * Total size of buffer + */ + uint16_t bufferSize; + + /** + * Index of currently selected element. This allows to navigate history + * After command is sent, current element is reset to 0 (no element) + */ + uint16_t current; + + /** + * Number of items in buffer + * Items are counted from top to bottom (and are 1 based). + * So the most recent item is 1 and the oldest is itemCount. + */ + uint16_t itemsCount; +}; + +struct EmbeddedCliImpl { + /** + * Invitation string. Is printed at the beginning of each line with user + * input + */ + const char *invitation; + + CliHistory history; + + /** + * Buffer for storing received chars. + * Chars are stored in FIFO mode. + */ + FifoBuf rxBuffer; + + /** + * Buffer for current command + */ + char *cmdBuffer; + + /** + * Size of current command + */ + uint16_t cmdSize; + + /** + * Total size of command buffer + */ + uint16_t cmdMaxSize; + + CliCommandBinding *bindings; + + /** + * Flags for each binding. Sizes are the same as for bindings array + */ + uint8_t *bindingsFlags; + + uint16_t bindingsCount; + + uint16_t maxBindingsCount; + + /** + * Total length of input line. This doesn't include invitation but + * includes current command and its live autocompletion + */ + uint16_t inputLineLength; + + /** + * Stores last character that was processed. + */ + char lastChar; + + /** + * Flags are defined as CLI_FLAG_* + */ + uint8_t flags; +}; + +struct AutocompletedCommand { + /** + * Name of autocompleted command (or first candidate for autocompletion if + * there are multiple candidates). + * NULL if autocomplete not possible. + */ + const char *firstCandidate; + + /** + * Number of characters that can be completed safely. For example, if there + * are two possible commands "get-led" and "get-adc", then for prefix "g" + * autocompletedLen will be 4. If there are only one candidate, this number + * is always equal to length of the command. + */ + uint16_t autocompletedLen; + + /** + * Total number of candidates for autocompletion + */ + uint16_t candidateCount; +}; + +static EmbeddedCliConfig defaultConfig; + +/** + * Number of commands that cli adds. Commands: + * - help + */ +static const uint16_t cliInternalBindingCount = 1; + +static const char *lineBreak = "\r\n"; + +/** + * Navigate through command history back and forth. If navigateUp is true, + * navigate to older commands, otherwise navigate to newer. + * When history end is reached, nothing happens. + * @param cli + * @param navigateUp + */ +static void navigateHistory(EmbeddedCli *cli, bool navigateUp); + +/** + * Process escaped character. After receiving ESC+[ sequence, all chars up to + * ending character are sent to this function + * @param cli + * @param c + */ +static void onEscapedInput(EmbeddedCli *cli, char c); + +/** + * Process input character. Character is valid displayable char and should be + * added to current command string and displayed to client. + * @param cli + * @param c + */ +static void onCharInput(EmbeddedCli *cli, char c); + +/** + * Process control character (like \r or \n) possibly altering state of current + * command or executing onCommand callback. + * @param cli + * @param c + */ +static void onControlInput(EmbeddedCli *cli, char c); + +/** + * Parse command in buffer and execute callback + * @param cli + */ +static void parseCommand(EmbeddedCli *cli); + +/** + * Setup bindings for internal commands, like help + * @param cli + */ +static void initInternalBindings(EmbeddedCli *cli); + +/** + * Show help for given tokens (or default help if no tokens) + * @param cli + * @param tokens + * @param context - not used + */ +static void onHelp(EmbeddedCli *cli, char *tokens, void *context); + +/** + * Show error about unknown command + * @param cli + * @param name + */ +static void onUnknownCommand(EmbeddedCli *cli, const char *name); + +/** + * Return autocompleted command for given prefix. + * Prefix is compared to all known command bindings and autocompleted result + * is returned + * @param cli + * @param prefix + * @return + */ +static AutocompletedCommand getAutocompletedCommand(EmbeddedCli *cli, const char *prefix); + +/** + * Prints autocompletion result while keeping current command unchanged + * Prints only if autocompletion is present and only one candidate exists. + * @param cli + */ +static void printLiveAutocompletion(EmbeddedCli *cli); + +/** + * Handles autocomplete request. If autocomplete possible - fills current + * command with autocompleted command. When multiple commands satisfy entered + * prefix, they are printed to output. + * @param cli + */ +static void onAutocompleteRequest(EmbeddedCli *cli); + +/** + * Removes all input from current line (replaces it with whitespaces) + * And places cursor at the beginning of the line + * @param cli + */ +static void clearCurrentLine(EmbeddedCli *cli); + +/** + * Write given string to cli output + * @param cli + * @param str + */ +static void writeToOutput(EmbeddedCli *cli, const char *str); + +/** + * Returns true if provided char is a supported control char: + * \r, \n, \b or 0x7F (treated as \b) + * @param c + * @return + */ +static bool isControlChar(char c); + +/** + * Returns true if provided char is a valid displayable character: + * a-z, A-Z, 0-9, whitespace, punctuation, etc. + * Currently only ASCII is supported + * @param c + * @return + */ +static bool isDisplayableChar(char c); + +/** + * How many elements are currently available in buffer + * @param buffer + * @return number of elements + */ +static uint16_t fifoBufAvailable(FifoBuf *buffer); + +/** + * Return first character from buffer and remove it from buffer + * Buffer must be non-empty, otherwise 0 is returned + * @param buffer + * @return + */ +static char fifoBufPop(FifoBuf *buffer); + +/** + * Push character into fifo buffer. If there is no space left, character is + * discarded and false is returned + * @param buffer + * @param a - character to add + * @return true if char was added to buffer, false otherwise + */ +static bool fifoBufPush(FifoBuf *buffer, char a); + +/** + * Copy provided string to the history buffer. + * If it is already inside history, it will be removed from it and added again. + * So after addition, it will always be on top + * If available size is not enough (and total size is enough) old elements will + * be removed from history so this item can be put to it + * @param history + * @param str + * @return true if string was put in history + */ +static bool historyPut(CliHistory *history, const char *str); + +/** + * Get item from history. Items are counted from 1 so if item is 0 or greater + * than itemCount, NULL is returned + * @param history + * @param item + * @return true if string was put in history + */ +static const char *historyGet(CliHistory *history, uint16_t item); + +/** + * Remove specific item from history + * @param history + * @param str - string to remove + * @return + */ +static void historyRemove(CliHistory *history, const char *str); + +/** + * Return position (index of first char) of specified token + * @param tokenizedStr - tokenized string (separated by \0 with + * \0\0 at the end) + * @param pos - token position (counted from 1) + * @return index of first char of specified token + */ +static uint16_t getTokenPosition(const char *tokenizedStr, uint16_t pos); + +EmbeddedCliConfig *embeddedCliDefaultConfig(void) { + defaultConfig.rxBufferSize = 64; + defaultConfig.cmdBufferSize = 64; + defaultConfig.historyBufferSize = 128; + defaultConfig.cliBuffer = NULL; + defaultConfig.cliBufferSize = 0; + defaultConfig.maxBindingCount = 8; + defaultConfig.enableAutoComplete = true; + defaultConfig.invitation = "> "; + return &defaultConfig; +} + +uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config) { + uint16_t bindingCount = (uint16_t)(config->maxBindingCount + cliInternalBindingCount); + return (uint16_t)(CLI_UINT_SIZE * + (BYTES_TO_CLI_UINTS(sizeof(EmbeddedCli)) + + BYTES_TO_CLI_UINTS(sizeof(EmbeddedCliImpl)) + + BYTES_TO_CLI_UINTS(config->rxBufferSize * sizeof(char)) + + BYTES_TO_CLI_UINTS(config->cmdBufferSize * sizeof(char)) + + BYTES_TO_CLI_UINTS(config->historyBufferSize * sizeof(char)) + + BYTES_TO_CLI_UINTS(bindingCount * sizeof(CliCommandBinding)) + + BYTES_TO_CLI_UINTS(bindingCount * sizeof(uint8_t)))); +} + +EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config) { + EmbeddedCli *cli = NULL; + + uint16_t bindingCount = (uint16_t)(config->maxBindingCount + cliInternalBindingCount); + + size_t totalSize = embeddedCliRequiredSize(config); + + bool allocated = false; + if (config->cliBuffer == NULL) { + config->cliBuffer = (CLI_UINT *)malloc(totalSize); // malloc guarantees alignment. + if (config->cliBuffer == NULL) + return NULL; + allocated = true; + } else if (config->cliBufferSize < totalSize) { + return NULL; + } + + CLI_UINT *buf = config->cliBuffer; + + memset(buf, 0, totalSize); + + cli = (EmbeddedCli *)buf; + buf += BYTES_TO_CLI_UINTS(sizeof(EmbeddedCli)); + + cli->_impl = (EmbeddedCliImpl *)buf; + buf += BYTES_TO_CLI_UINTS(sizeof(EmbeddedCliImpl)); + + PREPARE_IMPL(cli); + impl->rxBuffer.buf = (char *)buf; + buf += BYTES_TO_CLI_UINTS(config->rxBufferSize * sizeof(char)); + + impl->cmdBuffer = (char *)buf; + buf += BYTES_TO_CLI_UINTS(config->cmdBufferSize * sizeof(char)); + + impl->bindings = (CliCommandBinding *)buf; + buf += BYTES_TO_CLI_UINTS(bindingCount * sizeof(CliCommandBinding)); + + impl->bindingsFlags = (uint8_t *)buf; + buf += BYTES_TO_CLI_UINTS(bindingCount); + + impl->history.buf = (char *)buf; + impl->history.bufferSize = config->historyBufferSize; + + if (allocated) + SET_FLAG(impl->flags, CLI_FLAG_ALLOCATED); + + if (config->enableAutoComplete) + SET_FLAG(impl->flags, CLI_FLAG_AUTOCOMPLETE_ENABLED); + + impl->rxBuffer.size = config->rxBufferSize; + impl->rxBuffer.front = 0; + impl->rxBuffer.back = 0; + impl->cmdMaxSize = config->cmdBufferSize; + impl->bindingsCount = 0; + impl->maxBindingsCount = (uint16_t)(config->maxBindingCount + cliInternalBindingCount); + impl->lastChar = '\0'; + impl->invitation = config->invitation; + + initInternalBindings(cli); + + return cli; +} + +EmbeddedCli *embeddedCliNewDefault(void) { + return embeddedCliNew(embeddedCliDefaultConfig()); +} + +void embeddedCliReceiveChar(EmbeddedCli *cli, char c) { + PREPARE_IMPL(cli); + + if (!fifoBufPush(&impl->rxBuffer, c)) { + SET_FLAG(impl->flags, CLI_FLAG_OVERFLOW); + } +} + +void embeddedCliProcess(EmbeddedCli *cli) { + if (cli->writeChar == NULL) + return; + + PREPARE_IMPL(cli); + + if (!IS_FLAG_SET(impl->flags, CLI_FLAG_INIT_COMPLETE)) { + SET_FLAG(impl->flags, CLI_FLAG_INIT_COMPLETE); + writeToOutput(cli, impl->invitation); + } + + while (fifoBufAvailable(&impl->rxBuffer)) { + char c = fifoBufPop(&impl->rxBuffer); + + if (IS_FLAG_SET(impl->flags, CLI_FLAG_ESCAPE_MODE)) { + onEscapedInput(cli, c); + } else if (impl->lastChar == 0x1B && c == '[') { + // enter escape mode + SET_FLAG(impl->flags, CLI_FLAG_ESCAPE_MODE); + } else if (isControlChar(c)) { + onControlInput(cli, c); + } else if (isDisplayableChar(c)) { + onCharInput(cli, c); + } + + printLiveAutocompletion(cli); + + impl->lastChar = c; + } + + // discard unfinished command if overflow happened + if (IS_FLAG_SET(impl->flags, CLI_FLAG_OVERFLOW)) { + impl->cmdSize = 0; + impl->cmdBuffer[impl->cmdSize] = '\0'; + UNSET_U8FLAG(impl->flags, CLI_FLAG_OVERFLOW); + } +} + +bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding) { + PREPARE_IMPL(cli); + if (impl->bindingsCount == impl->maxBindingsCount) + return false; + + impl->bindings[impl->bindingsCount] = binding; + + ++impl->bindingsCount; + return true; +} + +void embeddedCliPrint(EmbeddedCli *cli, const char *string) { + if (cli->writeChar == NULL) + return; + + PREPARE_IMPL(cli); + + // remove chars for autocompletion and live command + if (!IS_FLAG_SET(impl->flags, CLI_FLAG_DIRECT_PRINT)) + clearCurrentLine(cli); + + // print provided string + writeToOutput(cli, string); + writeToOutput(cli, lineBreak); + + // print current command back to screen + if (!IS_FLAG_SET(impl->flags, CLI_FLAG_DIRECT_PRINT)) { + writeToOutput(cli, impl->invitation); + writeToOutput(cli, impl->cmdBuffer); + impl->inputLineLength = impl->cmdSize; + + printLiveAutocompletion(cli); + } +} + +void embeddedCliFree(EmbeddedCli *cli) { + PREPARE_IMPL(cli); + if (IS_FLAG_SET(impl->flags, CLI_FLAG_ALLOCATED)) { + // allocation is done in single call to malloc, so need only single free + free(cli); + } +} + +void embeddedCliTokenizeArgs(char *args) { + if (args == NULL) + return; + + // for now only space, but can add more later + const char *separators = " "; + + // indicates that arg is quoted so separators are copied as is + bool quotesEnabled = false; + // indicates that previous char was a slash, so next char is copied as is + bool escapeActivated = false; + int insertPos = 0; + + int i = 0; + char currentChar; + while ((currentChar = args[i]) != '\0') { + ++i; + + if (escapeActivated) { + escapeActivated = false; + } else if (currentChar == '\\') { + escapeActivated = true; + continue; + } else if (currentChar == '"') { + quotesEnabled = !quotesEnabled; + currentChar = '\0'; + } else if (!quotesEnabled && strchr(separators, currentChar) != NULL) { + currentChar = '\0'; + } + + // null chars are only copied once and not copied to the beginning + if (currentChar != '\0' || (insertPos > 0 && args[insertPos - 1] != '\0')) { + args[insertPos] = currentChar; + ++insertPos; + } + } + + // make args double null-terminated source buffer must be big enough to contain extra spaces + args[insertPos] = '\0'; + args[insertPos + 1] = '\0'; +} + +const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos) { + uint16_t i = getTokenPosition(tokenizedStr, pos); + + if (i != CLI_TOKEN_NPOS) + return &tokenizedStr[i]; + else + return NULL; +} + +char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos) { + uint16_t i = getTokenPosition(tokenizedStr, pos); + + if (i != CLI_TOKEN_NPOS) + return &tokenizedStr[i]; + else + return NULL; +} + +uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token) { + if (tokenizedStr == NULL || token == NULL) + return 0; + + uint16_t size = embeddedCliGetTokenCount(tokenizedStr); + for (uint16_t i = 1; i <= size; ++i) { + if (strcmp(embeddedCliGetToken(tokenizedStr, i), token) == 0) + return i; + } + + return 0; +} + +uint16_t embeddedCliGetTokenCount(const char *tokenizedStr) { + if (tokenizedStr == NULL || tokenizedStr[0] == '\0') + return 0; + + int i = 0; + uint16_t tokenCount = 1; + while (true) { + if (tokenizedStr[i] == '\0') { + if (tokenizedStr[i + 1] == '\0') + break; + ++tokenCount; + } + ++i; + } + + return tokenCount; +} + +static void navigateHistory(EmbeddedCli *cli, bool navigateUp) { + PREPARE_IMPL(cli); + if (impl->history.itemsCount == 0 || + (navigateUp && impl->history.current == impl->history.itemsCount) || + (!navigateUp && impl->history.current == 0)) + return; + + clearCurrentLine(cli); + + writeToOutput(cli, impl->invitation); + + if (navigateUp) + ++impl->history.current; + else + --impl->history.current; + + const char *item = historyGet(&impl->history, impl->history.current); + // simple way to handle empty command the same way as others + if (item == NULL) + item = ""; + uint16_t len = (uint16_t)strlen(item); + memcpy(impl->cmdBuffer, item, len); + impl->cmdBuffer[len] = '\0'; + impl->cmdSize = len; + + writeToOutput(cli, impl->cmdBuffer); + impl->inputLineLength = impl->cmdSize; + + printLiveAutocompletion(cli); +} + +static void onEscapedInput(EmbeddedCli *cli, char c) { + PREPARE_IMPL(cli); + + if (c >= 64 && c <= 126) { + // handle escape sequence + UNSET_U8FLAG(impl->flags, CLI_FLAG_ESCAPE_MODE); + + if (c == 'A' || c == 'B') { + // treat \e[..A as cursor up and \e[..B as cursor down + // there might be extra chars between [ and A/B, just ignore them + navigateHistory(cli, c == 'A'); + } + } +} + +static void onCharInput(EmbeddedCli *cli, char c) { + PREPARE_IMPL(cli); + + // have to reserve two extra chars for command ending (used in tokenization) + if (impl->cmdSize + 2 >= impl->cmdMaxSize) + return; + + impl->cmdBuffer[impl->cmdSize] = c; + ++impl->cmdSize; + impl->cmdBuffer[impl->cmdSize] = '\0'; + + cli->writeChar(cli, c); +} + +static void onControlInput(EmbeddedCli *cli, char c) { + PREPARE_IMPL(cli); + + // process \r\n and \n\r as single \r\n command + if ((impl->lastChar == '\r' && c == '\n') || (impl->lastChar == '\n' && c == '\r')) + return; + + if (c == '\r' || c == '\n') { + // try to autocomplete command and then process it + onAutocompleteRequest(cli); + + writeToOutput(cli, lineBreak); + + if (impl->cmdSize > 0) + parseCommand(cli); + impl->cmdSize = 0; + impl->cmdBuffer[impl->cmdSize] = '\0'; + impl->inputLineLength = 0; + impl->history.current = 0; + + writeToOutput(cli, impl->invitation); + } else if ((c == '\b' || c == 0x7F) && impl->cmdSize > 0) { + // remove char from screen + cli->writeChar(cli, '\b'); + cli->writeChar(cli, ' '); + cli->writeChar(cli, '\b'); + // and from buffer + --impl->cmdSize; + impl->cmdBuffer[impl->cmdSize] = '\0'; + } else if (c == '\t') { + onAutocompleteRequest(cli); + } +} + +static void parseCommand(EmbeddedCli *cli) { + PREPARE_IMPL(cli); + + bool isEmpty = true; + + for (int i = 0; i < impl->cmdSize; ++i) { + if (impl->cmdBuffer[i] != ' ') { + isEmpty = false; + break; + } + } + // do not process empty commands + if (isEmpty) + return; + // push command to history before buffer is modified + historyPut(&impl->history, impl->cmdBuffer); + + char *cmdName = NULL; + char *cmdArgs = NULL; + bool nameFinished = false; + + // find command name and command args inside command buffer + for (int i = 0; i < impl->cmdSize; ++i) { + char c = impl->cmdBuffer[i]; + + if (c == ' ') { + // all spaces between name and args are filled with zeros + // so name is a correct null-terminated string + if (cmdArgs == NULL) + impl->cmdBuffer[i] = '\0'; + if (cmdName != NULL) + nameFinished = true; + + } else if (cmdName == NULL) { + cmdName = &impl->cmdBuffer[i]; + } else if (cmdArgs == NULL && nameFinished) { + cmdArgs = &impl->cmdBuffer[i]; + } + } + + // we keep two last bytes in cmd buffer reserved so cmdSize is always by 2 + // less than cmdMaxSize + impl->cmdBuffer[impl->cmdSize + 1] = '\0'; + + if (cmdName == NULL) + return; + + // try to find command in bindings + for (int i = 0; i < impl->bindingsCount; ++i) { + if (strcmp(cmdName, impl->bindings[i].name) == 0) { + if (impl->bindings[i].binding == NULL) + break; + + if (impl->bindings[i].tokenizeArgs) + embeddedCliTokenizeArgs(cmdArgs); + // currently, output is blank line, so we can just print directly + SET_FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT); + impl->bindings[i].binding(cli, cmdArgs, impl->bindings[i].context); + UNSET_U8FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT); + return; + } + } + + // command not found in bindings or binding was null + // try to call default callback + if (cli->onCommand != NULL) { + CliCommand command; + command.name = cmdName; + command.args = cmdArgs; + + // currently, output is blank line, so we can just print directly + SET_FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT); + cli->onCommand(cli, &command); + UNSET_U8FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT); + } else { + onUnknownCommand(cli, cmdName); + } +} + +static void initInternalBindings(EmbeddedCli *cli) { + CliCommandBinding b = {"help", "Print list of commands", true, NULL, onHelp}; + embeddedCliAddBinding(cli, b); +} + +static void onHelp(EmbeddedCli *cli, char *tokens, void *context) { + UNUSED(context); + PREPARE_IMPL(cli); + + if (impl->bindingsCount == 0) { + writeToOutput(cli, "Help is not available"); + writeToOutput(cli, lineBreak); + return; + } + + uint16_t tokenCount = embeddedCliGetTokenCount(tokens); + if (tokenCount == 0) { + for (int i = 0; i < impl->bindingsCount; ++i) { + writeToOutput(cli, " * "); + writeToOutput(cli, impl->bindings[i].name); + writeToOutput(cli, lineBreak); + if (impl->bindings[i].help != NULL) { + cli->writeChar(cli, '\t'); + writeToOutput(cli, impl->bindings[i].help); + writeToOutput(cli, lineBreak); + } + } + } else if (tokenCount == 1) { + // try find command + const char *helpStr = NULL; + const char *cmdName = embeddedCliGetToken(tokens, 1); + bool found = false; + for (int i = 0; i < impl->bindingsCount; ++i) { + if (strcmp(impl->bindings[i].name, cmdName) == 0) { + helpStr = impl->bindings[i].help; + found = true; + break; + } + } + if (found && helpStr != NULL) { + writeToOutput(cli, " * "); + writeToOutput(cli, cmdName); + writeToOutput(cli, lineBreak); + cli->writeChar(cli, '\t'); + writeToOutput(cli, helpStr); + writeToOutput(cli, lineBreak); + } else if (found) { + writeToOutput(cli, "Help is not available"); + writeToOutput(cli, lineBreak); + } else { + onUnknownCommand(cli, cmdName); + } + } else { + writeToOutput(cli, "Command \"help\" receives one or zero arguments"); + writeToOutput(cli, lineBreak); + } +} + +static void onUnknownCommand(EmbeddedCli *cli, const char *name) { + writeToOutput(cli, "Unknown command: \""); + writeToOutput(cli, name); + writeToOutput(cli, "\". Write \"help\" for a list of available commands"); + writeToOutput(cli, lineBreak); +} + +static AutocompletedCommand getAutocompletedCommand(EmbeddedCli *cli, const char *prefix) { + AutocompletedCommand cmd = {NULL, 0, 0}; + + size_t prefixLen = strlen(prefix); + + PREPARE_IMPL(cli); + if (impl->bindingsCount == 0 || prefixLen == 0) + return cmd; + + for (int i = 0; i < impl->bindingsCount; ++i) { + const char *name = impl->bindings[i].name; + size_t len = strlen(name); + + // unset autocomplete flag + UNSET_U8FLAG(impl->bindingsFlags[i], BINDING_FLAG_AUTOCOMPLETE); + + if (len < prefixLen) + continue; + + // check if this command is candidate for autocomplete + bool isCandidate = true; + for (size_t j = 0; j < prefixLen; ++j) { + if (prefix[j] != name[j]) { + isCandidate = false; + break; + } + } + if (!isCandidate) + continue; + + impl->bindingsFlags[i] |= BINDING_FLAG_AUTOCOMPLETE; + + if (cmd.candidateCount == 0 || len < cmd.autocompletedLen) + cmd.autocompletedLen = (uint16_t)len; + + ++cmd.candidateCount; + + if (cmd.candidateCount == 1) { + cmd.firstCandidate = name; + continue; + } + + for (size_t j = impl->cmdSize; j < cmd.autocompletedLen; ++j) { + if (cmd.firstCandidate[j] != name[j]) { + cmd.autocompletedLen = (uint16_t)j; + break; + } + } + } + + return cmd; +} + +static void printLiveAutocompletion(EmbeddedCli *cli) { + PREPARE_IMPL(cli); + + if (!IS_FLAG_SET(impl->flags, CLI_FLAG_AUTOCOMPLETE_ENABLED)) + return; + + AutocompletedCommand cmd = getAutocompletedCommand(cli, impl->cmdBuffer); + + if (cmd.candidateCount == 0) { + cmd.autocompletedLen = impl->cmdSize; + } + + // print live autocompletion (or nothing, if it doesn't exist) + for (size_t i = impl->cmdSize; i < cmd.autocompletedLen; ++i) { + cli->writeChar(cli, cmd.firstCandidate[i]); + } + // replace with spaces previous autocompletion + for (size_t i = cmd.autocompletedLen; i < impl->inputLineLength; ++i) { + cli->writeChar(cli, ' '); + } + impl->inputLineLength = cmd.autocompletedLen; + cli->writeChar(cli, '\r'); + // print current command again so cursor is moved to initial place + writeToOutput(cli, impl->invitation); + writeToOutput(cli, impl->cmdBuffer); +} + +static void onAutocompleteRequest(EmbeddedCli *cli) { + PREPARE_IMPL(cli); + + AutocompletedCommand cmd = getAutocompletedCommand(cli, impl->cmdBuffer); + + if (cmd.candidateCount == 0) + return; + + if (cmd.candidateCount == 1 || cmd.autocompletedLen > impl->cmdSize) { + // can copy from index cmdSize, but prefix is the same, so copy everything + memcpy(impl->cmdBuffer, cmd.firstCandidate, cmd.autocompletedLen); + if (cmd.candidateCount == 1) { + impl->cmdBuffer[cmd.autocompletedLen] = ' '; + ++cmd.autocompletedLen; + } + impl->cmdBuffer[cmd.autocompletedLen] = '\0'; + + writeToOutput(cli, &impl->cmdBuffer[impl->cmdSize]); + impl->cmdSize = cmd.autocompletedLen; + impl->inputLineLength = impl->cmdSize; + return; + } + + // with multiple candidates when we already completed to common prefix + // we show all candidates and print input again + // we need to completely clear current line since it begins with invitation + clearCurrentLine(cli); + + for (int i = 0; i < impl->bindingsCount; ++i) { + // autocomplete flag is set for all candidates by last call to + // getAutocompletedCommand + if (!(impl->bindingsFlags[i] & BINDING_FLAG_AUTOCOMPLETE)) + continue; + + const char *name = impl->bindings[i].name; + + writeToOutput(cli, name); + writeToOutput(cli, lineBreak); + } + + writeToOutput(cli, impl->invitation); + writeToOutput(cli, impl->cmdBuffer); + + impl->inputLineLength = impl->cmdSize; +} + +static void clearCurrentLine(EmbeddedCli *cli) { + PREPARE_IMPL(cli); + size_t len = impl->inputLineLength + strlen(impl->invitation); + + cli->writeChar(cli, '\r'); + for (size_t i = 0; i < len; ++i) { + cli->writeChar(cli, ' '); + } + cli->writeChar(cli, '\r'); + impl->inputLineLength = 0; +} + +static void writeToOutput(EmbeddedCli *cli, const char *str) { + size_t len = strlen(str); + + for (size_t i = 0; i < len; ++i) { + cli->writeChar(cli, str[i]); + } +} + +static bool isControlChar(char c) { + return c == '\r' || c == '\n' || c == '\b' || c == '\t' || c == 0x7F; +} + +static bool isDisplayableChar(char c) { + return (c >= 32 && c <= 126); +} + +static uint16_t fifoBufAvailable(FifoBuf *buffer) { + if (buffer->back >= buffer->front) + return (uint16_t)(buffer->back - buffer->front); + else + return (uint16_t)(buffer->size - buffer->front + buffer->back); +} + +static char fifoBufPop(FifoBuf *buffer) { + char a = '\0'; + if (buffer->front != buffer->back) { + a = buffer->buf[buffer->front]; + buffer->front = (uint16_t)(buffer->front + 1) % buffer->size; + } + return a; +} + +static bool fifoBufPush(FifoBuf *buffer, char a) { + uint16_t newBack = (uint16_t)(buffer->back + 1) % buffer->size; + if (newBack != buffer->front) { + buffer->buf[buffer->back] = a; + buffer->back = newBack; + return true; + } + return false; +} + +static bool historyPut(CliHistory *history, const char *str) { + size_t len = strlen(str); + // each item is ended with \0 so, need to have that much space at least + if (history->bufferSize < len + 1) + return false; + + // remove str from history (if it's present) so we don't get duplicates + historyRemove(history, str); + + size_t usedSize; + // remove old items if new one can't fit into buffer + while (history->itemsCount > 0) { + const char *item = historyGet(history, history->itemsCount); + size_t itemLen = strlen(item); + usedSize = ((size_t)(item - history->buf)) + itemLen + 1; + + size_t freeSpace = history->bufferSize - usedSize; + + if (freeSpace >= len + 1) + break; + + // space not enough, remove last element + --history->itemsCount; + } + if (history->itemsCount > 0) { + // when history not empty, shift elements so new item is first + memmove(&history->buf[len + 1], history->buf, usedSize); + } + memcpy(history->buf, str, len + 1); + ++history->itemsCount; + + return true; +} + +static const char *historyGet(CliHistory *history, uint16_t item) { + if (item == 0 || item > history->itemsCount) + return NULL; + + // items are stored in the same way (separated by \0 and counted from 1), + // so can use this call + return embeddedCliGetToken(history->buf, item); +} + +static void historyRemove(CliHistory *history, const char *str) { + if (str == NULL || history->itemsCount == 0) + return; + char *item = NULL; + uint16_t itemPosition; + for (itemPosition = 1; itemPosition <= history->itemsCount; ++itemPosition) { + // items are stored in the same way (separated by \0 and counted from 1), + // so can use this call + item = embeddedCliGetTokenVariable(history->buf, itemPosition); + if (strcmp(item, str) == 0) { + break; + } + item = NULL; + } + if (item == NULL) + return; + + --history->itemsCount; + if (itemPosition == (history->itemsCount + 1)) { + // if this is a last element, nothing is remaining to move + return; + } + + size_t len = strlen(item); + size_t remaining = (size_t)(history->bufferSize - (item + len + 1 - history->buf)); + // move everything to the right of found item + memmove(item, &item[len + 1], remaining); +} + +static uint16_t getTokenPosition(const char *tokenizedStr, uint16_t pos) { + if (tokenizedStr == NULL || pos == 0) + return CLI_TOKEN_NPOS; + uint16_t i = 0; + uint16_t tokenCount = 1; + while (true) { + if (tokenCount == pos) + break; + + if (tokenizedStr[i] == '\0') { + ++tokenCount; + if (tokenizedStr[i + 1] == '\0') + break; + } + + ++i; + } + + if (tokenizedStr[i] != '\0') + return i; + else + return CLI_TOKEN_NPOS; +} diff --git a/components/tftpShell/extern/embedded_cli.h b/components/tftpShell/extern/embedded_cli.h new file mode 100644 index 0000000..abfebd4 --- /dev/null +++ b/components/tftpShell/extern/embedded_cli.h @@ -0,0 +1,312 @@ +#ifndef EMBEDDED_CLI_H +#define EMBEDDED_CLI_H + + +#ifdef __cplusplus + +extern "C" { +#else + +#include + +#endif + +// cstdint is available only since C++11, so use C header +#include + +// used for proper alignment of cli buffer +#if UINTPTR_MAX == 0xFFFF +#define CLI_UINT uint16_t +#elif UINTPTR_MAX == 0xFFFFFFFF +#define CLI_UINT uint32_t +#elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFu +#define CLI_UINT uint64_t +#else +#error unsupported pointer size +#endif + +#define CLI_UINT_SIZE (sizeof(CLI_UINT)) +// convert size in bytes to size in terms of CLI_UINTs (rounded up +// if bytes is not divisible by size of single CLI_UINT) +#define BYTES_TO_CLI_UINTS(bytes) \ + (((bytes) + CLI_UINT_SIZE - 1)/CLI_UINT_SIZE) + +typedef struct CliCommand CliCommand; +typedef struct CliCommandBinding CliCommandBinding; +typedef struct EmbeddedCli EmbeddedCli; +typedef struct EmbeddedCliConfig EmbeddedCliConfig; + + +struct CliCommand { + /** + * Name of the command. + * In command "set led 1 1" "set" is name + */ + const char *name; + + /** + * String of arguments of the command. + * In command "set led 1 1" "led 1 1" is string of arguments + * Is ended with double 0x00 char + * Use tokenize functions to easily get individual tokens + */ + char *args; +}; + +/** + * Struct to describe binding of command to function and + */ +struct CliCommandBinding { + /** + * Name of command to bind. Should not be NULL. + */ + const char *name; + + /** + * Help string that will be displayed when "help " is executed. + * Can have multiple lines separated with "\r\n" + * Can be NULL if no help is provided. + */ + const char *help; + + /** + * Flag to perform tokenization before calling binding function. + */ + bool tokenizeArgs; + + /** + * Pointer to any specific app context that is required for this binding. + * It will be provided in binding callback. + */ + void *context; + + /** + * Binding function for when command is received. + * If null, default callback (onCommand) will be called. + * @param cli - pointer to cli that is calling this binding + * @param args - string of args (if tokenizeArgs is false) or tokens otherwise + * @param context + */ + void (*binding)(EmbeddedCli *cli, char *args, void *context); +}; + +struct EmbeddedCli { + /** + * Should write char to connection + * @param cli - pointer to cli that executed this function + * @param c - actual character to write + */ + void (*writeChar)(EmbeddedCli *cli, char c); + + /** + * Called when command is received and command not found in list of + * command bindings (or binding function is null). + * @param cli - pointer to cli that executed this function + * @param command - pointer to received command + */ + void (*onCommand)(EmbeddedCli *cli, CliCommand *command); + + /** + * Can be used for any application context + */ + void *appContext; + + /** + * Pointer to actual implementation, do not use. + */ + void *_impl; +}; + +/** + * Configuration to create CLI + */ +struct EmbeddedCliConfig { + /** + * Invitation string. Is printed at the beginning of each line with user + * input + */ + const char *invitation; + + /** + * Size of buffer that is used to store characters until they're processed + */ + uint16_t rxBufferSize; + + /** + * Size of buffer that is used to store current input that is not yet + * sended as command (return not pressed yet) + */ + uint16_t cmdBufferSize; + + /** + * Size of buffer that is used to store previously entered commands + * Only unique commands are stored in buffer. If buffer is smaller than + * entered command (including arguments), command is discarded from history + */ + uint16_t historyBufferSize; + + /** + * Maximum amount of bindings that can be added via addBinding function. + * Cli increases takes extra bindings for internal commands: + * - help + */ + uint16_t maxBindingCount; + + /** + * Buffer to use for cli and all internal structures. If NULL, memory will + * be allocated dynamically. Otherwise this buffer is used and no + * allocations are made + */ + CLI_UINT *cliBuffer; + + /** + * Size of buffer for cli and internal structures (in bytes). + */ + uint16_t cliBufferSize; + + /** + * Whether autocompletion should be enabled. + * If false, autocompletion is disabled but you still can use 'tab' to + * complete current command manually. + */ + bool enableAutoComplete; +}; + +/** + * Returns pointer to default configuration for cli creation. It is safe to + * modify it and then send to embeddedCliNew(). + * Returned structure is always the same so do not free and try to use it + * immediately. + * Default values: + *
    + *
  • rxBufferSize = 64
  • + *
  • cmdBufferSize = 64
  • + *
  • historyBufferSize = 128
  • + *
  • cliBuffer = NULL (use dynamic allocation)
  • + *
  • cliBufferSize = 0
  • + *
  • maxBindingCount = 8
  • + *
  • enableAutoComplete = true
  • + *
+ * @return configuration for cli creation + */ +EmbeddedCliConfig *embeddedCliDefaultConfig(void); + +/** + * Returns how many space in config buffer is required for cli creation + * If you provide buffer with less space, embeddedCliNew will return NULL + * This amount will always be divisible by CLI_UINT_SIZE so allocated buffer + * and internal structures can be properly aligned + * @param config + * @return + */ +uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config); + +/** + * Create new CLI. + * Memory is allocated dynamically if cliBuffer in config is NULL. + * After CLI is created, override function pointers to start using it + * @param config - config for cli creation + * @return pointer to created CLI + */ +EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config); + +/** + * Same as calling embeddedCliNew with default config. + * @return + */ +EmbeddedCli *embeddedCliNewDefault(void); + +/** + * Receive character and put it to internal buffer + * Actual processing is done inside embeddedCliProcess + * You can call this function from something like interrupt service routine, + * just make sure that you call it only from single place. Otherwise input + * might get corrupted + * @param cli + * @param c - received char + */ +void embeddedCliReceiveChar(EmbeddedCli *cli, char c); + +/** + * Process rx/tx buffers. Command callbacks are called from here + * @param cli + */ +void embeddedCliProcess(EmbeddedCli *cli); + +/** + * Add specified binding to list of bindings. If list is already full, binding + * is not added and false is returned + * @param cli + * @param binding + * @return true if binding was added, false otherwise + */ +bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding); + +/** + * Print specified string and account for currently entered but not submitted + * command. + * Current command is deleted, provided string is printed (with new line) after + * that current command is printed again, so user can continue typing it. + * @param cli + * @param string + */ +void embeddedCliPrint(EmbeddedCli *cli, const char *string); + +/** + * Free allocated for cli memory + * @param cli + */ +void embeddedCliFree(EmbeddedCli *cli); + +/** + * Perform tokenization of arguments string. Original string is modified and + * should not be used directly (only inside other token functions). + * Individual tokens are separated by single 0x00 char, double 0x00 is put at + * the end of token list. After calling this function, you can use other + * token functions to get individual tokens and token count. + * + * Important: Call this function only once. Otherwise information will be lost if + * more than one token existed + * @param args - string to tokenize (must have extra writable char after 0x00) + * @return + */ +void embeddedCliTokenizeArgs(char *args); + +/** + * Return specific token from tokenized string + * @param tokenizedStr + * @param pos (counted from 1) + * @return token + */ +const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos); + +/** + * Same as embeddedCliGetToken but works on non-const buffer + * @param tokenizedStr + * @param pos (counted from 1) + * @return token + */ +char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos); + +/** + * Find token in provided tokens string and return its position (counted from 1) + * If no such token is found - 0 is returned. + * @param tokenizedStr + * @param token - token to find + * @return position (increased by 1) or zero if no such token found + */ +uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token); + +/** + * Return number of tokens in tokenized string + * @param tokenizedStr + * @return number of tokens + */ +uint16_t embeddedCliGetTokenCount(const char *tokenizedStr); + +#ifdef __cplusplus +} +#endif + + +#endif //EMBEDDED_CLI_H diff --git a/components/tftpShell/extern/license b/components/tftpShell/extern/license new file mode 100644 index 0000000..8993f81 --- /dev/null +++ b/components/tftpShell/extern/license @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Sviatoslav Kokurin (funbiscuit) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/components/tftpShell/extern/readme.md b/components/tftpShell/extern/readme.md new file mode 100644 index 0000000..a1cce1f --- /dev/null +++ b/components/tftpShell/extern/readme.md @@ -0,0 +1,7 @@ +# Embedded CLI + +Implementation is taken from `funbiscuit` @ [github](https://github.com/funbiscuit/embedded-cli) + +- Only source and header where integrated. +- Full project is included in `external/embedded-cli` + diff --git a/components/tftpShell/include/cliWrapper.hpp b/components/tftpShell/include/cliWrapper.hpp new file mode 100644 index 0000000..088dc4d --- /dev/null +++ b/components/tftpShell/include/cliWrapper.hpp @@ -0,0 +1,161 @@ +/** + * @file cliWrapper.hpp + * @author Gustice + * @brief Wrapping Command-Line-Interfaces with custom endpoints + * @details Command-Line-Interface is based on the implementation of + * https://github.com/funbiscuit/embedded-cli + * + * @copyright Copyright (c) 2024 + */ + +#pragma once + +#include "embedded_cli.h" +#include +#include +#include +#include "utils.hpp" + +/* Module configuration */ +static constexpr int CLI_RX_BUFFER_SIZE = 64; +static constexpr int CLI_CMD_BUFFER_SIZE = 64; // to also accept URLs for Updates +static constexpr int CLI_HISTORY_SIZE = 16; +static constexpr int CLI_BINDING_COUNT = 8; +static constexpr std::string_view CliWelcomeString{ + "** Smart-DMX-Bridge **\n" + " send 'help' for help-page\n" + " NOTE: Syslog-messages are sent to client of this session\n"}; + +/** + * @brief Wrapper for c-implementation of CLI + */ +class Cli { + public: + /// @brief Type for callback arguments + using CArgs = std::vector &; + /// @brief Type for bind callback + using BindCb = std::function; + /// @brief Type for command callback + using CommandCb = std::function; + + /// @brief Parameter structure for command-binding + struct Binding { + std::string_view name; + std::string_view help_output; + BindCb callback; + }; + + /// @brief Constructor + /// @param port Reference to serial port + /// @param cmd command callback + Cli(CharStream &port, CommandCb cmd) + : _port(port), _commandCb(cmd) { + EmbeddedCliConfig *config = embeddedCliDefaultConfig(); + // NOTE: Note setting cliBuffer allows Cli to allocate needed buffer in correct size + // config->cliBuffer = cliBuffer; + // config->cliBufferSize = CLI_BUFFER_SIZE; + config->rxBufferSize = CLI_RX_BUFFER_SIZE; + config->cmdBufferSize = CLI_CMD_BUFFER_SIZE; + config->historyBufferSize = CLI_HISTORY_SIZE; + config->maxBindingCount = CLI_BINDING_COUNT; + cli = embeddedCliNew(config); + + if (cli == NULL) { + ESP_LOGE("CLI", "Cli was not created, requires %d bytes. Check sizes!", + embeddedCliRequiredSize(config)); + return; + } + cli->onCommand = genericCallback; + cli->writeChar = writeToken; + // appContext is a user-end variable and is bound to current instance + cli->appContext = this; + } + + /// @brief Destructor + ~Cli() { + embeddedCliFree(cli); + } + + /// @brief Create command binding + bool addBinding(Binding bind) { + BindCb *cb = new BindCb{bind.callback}; + + CliCommandBinding cBinding{.name = bind.name.begin(), + .help = bind.help_output.begin(), + .tokenizeArgs = true, + // NOTE: this hard reference only works because we only provide + // an add-function without remove + .context = (void *)(cb), + .binding = boundCallback}; + return embeddedCliAddBinding(cli, cBinding); + } + + /// @brief Static method to generate c-compatible function-pointer for bound-command-callback + /// @details - receives and wraps arguments in string, + /// - determines object according to appContext + /// - calls appropriate method of appropriate object + static void boundCallback(EmbeddedCli *cli, char *args, void *context) { + if (context == nullptr) { + return; + ESP_LOGE("CLI", "No context in callback available"); + } + ESP_LOGD("CLI", "Received command:"); + + BindCb &cb = *((BindCb *)context); + uint len = embeddedCliGetTokenCount(args); + std::vector sArgs; + while (len-- > 0) { + const char *const arg = embeddedCliGetToken(args, 1); + ESP_LOGD("CLI", " arg: %s", arg); + sArgs.emplace_back(arg); + // shift position to end of this element to save time + args += sArgs.back().size() + 1; // needs also to be shifted by null-delimiter + } + cb(sArgs); + } + + /// @brief Static method to generate c-compatible function-pointer for command-callback + /// @details - receives and wraps arguments in string, + /// - determines object according to appContext + /// - calls command-callback of appropriate object + static void genericCallback(EmbeddedCli *cli, CliCommand *command) { + std::string name(command->name); + ESP_LOGI("CLI", "Received generic command: %s", name.c_str()); + + embeddedCliTokenizeArgs(command->args); + std::vector sArgs; + for (int i = 1; i <= embeddedCliGetTokenCount(command->args); ++i) { + sArgs.emplace_back(embeddedCliGetToken(command->args, i)); + ESP_LOGI("CLI", " arg %d : %s", i, sArgs.back().c_str()); + } + auto &commandCb = static_cast(cli->appContext)->_commandCb; + commandCb(name, sArgs); + } + + /// @brief Process buffer and trigger commands + void process() { + auto r = _port.read(); + for (auto &&c : r) { + embeddedCliReceiveChar(cli, c); + } + embeddedCliProcess(cli); + } + + /// @brief publicly exposed write method + void write(std::string output) { + _port.write(output); + } + + private: + /// @brief Reference to hardware port + CharStream &_port; + /// @brief Callback for generic command + CommandCb _commandCb; + EmbeddedCli *cli = nullptr; + + /// @brief Write callback + static void writeToken(EmbeddedCli *embeddedCli, char c) { + static_cast(embeddedCli->appContext)->_port.write(c); + } +}; + diff --git a/main/watchMain.cpp b/main/watchMain.cpp index 2d226a0..7ac2fd0 100644 --- a/main/watchMain.cpp +++ b/main/watchMain.cpp @@ -32,6 +32,9 @@ #include "PwmExpanderPca9685.hpp" #include "StepperDrv8833.hpp" #include "stepperMotor.hpp" +#include "cliWrapper.hpp" +#include "udpSockets.hpp" +#include "tcpSockets.hpp" static const char *TAG = "Watch"; static esp_eth_handle_t s_eth_handle = NULL; @@ -41,6 +44,103 @@ extern "C" { // This switch allows the ROS C-implementation to find this main void app_main(void); } + +std::unique_ptr shellPtr; + +/// @brief Callback for update start +/// @param args callback arguments +void startUpdate(Cli::CArgs args) { + ESP_LOGI(TAG, "Invoking Update"); + if (args.size() != 1) { + shellPtr->write("Need URL to application'\n"); + return; + } + // @ToDo: Start Update here +} + +/// @brief Callback for commands that are not directly wired +/// @param name command name +/// @param args callback arguments +void onCommand(std::string name, Cli::CArgs args) { + ESP_LOGI(TAG, "Invoking: %s", name.c_str()); +} + +void showLogServerConfig(Cli::CArgs args) { + ESP_LOGI(TAG, "on showLogServerConfig:"); + // auto &store = NvsApi::getInstance(); + // auto logger = store.getValue("syslogDst"); + // shellPtr->write(std::string{"syslog destination = '"} + logger + "'\n"); +} + +void setLogServerConfig(Cli::CArgs args) { + ESP_LOGI(TAG, "on showLogServerConfig:"); + if (args.size() != 1) { + shellPtr->write("Need IP address to syslog server'\n"); + return; + } + // auto &store = NvsApi::getInstance(); + // shellPtr->write(std::string{"setting syslog destination to = '"} + args[0] + "'\n"); + // store.setValue("syslogDst", args[0]); +} + +/** + * @brief Telnet socket via TCP/IP + * + * @details The ini-file for TeraTerm only changes CRReceive and CRSend values both to "=CR" + * @param arg ignored task argument + */ +static void telnetSocketTask(int addrFamily) { + ESP_LOGI(TAG, "Starting Telnet task"); + try { + TelnetServer telnet(addrFamily); + telnet.run( + [](TcpSession &session) { + shellPtr = std::make_unique(session, [](std::string n, Cli::CArgs a) { onCommand(n, a); }); + shellPtr->addBinding( + {"update", "Start OTA-Update-Process", + [](Cli::CArgs a) { startUpdate(a); }}); + shellPtr->addBinding( + {"logserver_show", "Shows log server config ip", + [](Cli::CArgs a) { showLogServerConfig(a); }}); + shellPtr->addBinding( + {"logserver_set", "Set log server config ip", + [](Cli::CArgs a) { setLogServerConfig(a); }}); + }, + [](TcpSession &session) { + shellPtr->process(); + Syslog::setupTask(session.getOther()); + }); + } catch (const std::exception &e) { + ESP_LOGE("TCP", "%s", e.what()); + } + + vTaskDelete(NULL); +} + +void startServerTasks() { + static int ipVersion = AF_INET; + static fos::TaskT telnetSocket(telnetSocketTask, ipVersion, "tcpServer", 4096); + // static TaskT dmxNode(dmxSocketTask, ipVersion, "dmxNode", 4096); + // static Task otaUpdate(SystemFunctions::OtaTaskEntry, "otaTask", 8192); + + // initialise_mdns(); + // netbiosns_init(); + // netbiosns_set_name(CONFIG_EXAMPLE_MDNS_HOST_NAME); + // WebServer::init(FileSystem::getInstance()); + // WebServer::getInstance(FileSystem::getInstance()).registerCallback(printSysState); + + // auto &store = NvsApi::getInstance(); + // auto broker = store.getValue("mqttBroker"); + // if (broker != "") { + // MqttClient::init(broker); + // } + + // auto logger = store.getValue("syslogDst"); + // if (logger != "") { + // Syslog::setupTask(IpAdr{logger, 0}); + // } +} + /** Event handler for IP_EVENT_ETH_GOT_IP */ static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) @@ -54,6 +154,7 @@ static void got_ip_event_handler(void *arg, esp_event_base_t event_base, ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask)); ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw)); ESP_LOGI(TAG, "~~~~~~~~~~~"); + startServerTasks(); } // Event handler for Ethernet diff --git a/sdkconfig b/sdkconfig index 5ec0bf9..e7bdbcb 100644 --- a/sdkconfig +++ b/sdkconfig @@ -585,7 +585,8 @@ CONFIG_COMPILER_FLOAT_LIB_FROM_RVFPLIB=y CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 # CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set CONFIG_COMPILER_HIDE_PATHS_MACROS=y -# CONFIG_COMPILER_CXX_EXCEPTIONS is not set +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 # CONFIG_COMPILER_CXX_RTTI is not set CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y # CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set @@ -1412,7 +1413,7 @@ CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y # LWIP # CONFIG_LWIP_ENABLE=y -CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +CONFIG_LWIP_LOCAL_HOSTNAME="ChaosWatch" # CONFIG_LWIP_NETIF_API is not set CONFIG_LWIP_TCPIP_TASK_PRIO=18 # CONFIG_LWIP_TCPIP_CORE_LOCKING is not set @@ -2001,7 +2002,8 @@ CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y # CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set # CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 -# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_CXX_EXCEPTIONS=y +CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 CONFIG_STACK_CHECK_NONE=y # CONFIG_STACK_CHECK_NORM is not set # CONFIG_STACK_CHECK_STRONG is not set diff --git a/sdkconfig.defaults b/sdkconfig.defaults index c86f880..2d141da 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -1,3 +1,9 @@ +# +# Compiler options +# +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_LWIP_LOCAL_HOSTNAME="ChaosWatch" + # # Example Ethernet Configuration # @@ -21,15 +27,21 @@ CONFIG_EXAMPLE_ETH_SPI_PHY_ADDR0=1 # # Ethernet # -# CONFIG_ETH_SPI_ETHERNET_W5500 is not set +CONFIG_ETH_SPI_ETHERNET_W5500=y # # Wi-Fi # # CONFIG_ESP_WIFI_GMAC_SUPPORT is not set +# +# LWIP +# +CONFIG_LWIP_LOCAL_HOSTNAME="ChaosWatch" + # # Serial flasher config # CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +