From 03a7c09f0334e36f450f2623b18e514550313732 Mon Sep 17 00:00:00 2001 From: wangyuyao <46612431+Aurical@users.noreply.github.com> Date: Wed, 7 Sep 2022 16:03:01 +0800 Subject: [PATCH] Add in SSL, UDP, SoftAP features --- .../ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP.zip | Bin 24031 -> 30929 bytes .../Ameba_ATCMD_ESP/atcmd_core.cpp | 8 +- .../Ameba_ATCMD_ESP/atcmd_tcpip.cpp | 810 +++++++++++++++--- .../Ameba_ATCMD_ESP/atcmd_tcpip.h | 16 +- .../Ameba_ATCMD_ESP/atcmd_tls_certs.h | 12 + .../Ameba_ATCMD_ESP/atcmd_uart.cpp | 63 +- .../Ameba_ATCMD_ESP/atcmd_wifi.cpp | 594 ++++++++++++- .../Ameba_ATCMD_ESP/atcmd_wifi.h | 1 + Ameba_misc/ESP_ATCMD_on_AmebaD/readme.md | 35 +- 9 files changed, 1369 insertions(+), 170 deletions(-) create mode 100644 Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tls_certs.h diff --git a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP.zip b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP.zip index 23eb67b073b7f2f2e7a1c8a22127c9af480edd26..6554e7d476a406dba2f593267b528660367736e9 100644 GIT binary patch delta 29174 zcmV)bK&ijqy8+Sh0S-`00|XQR00000iFhYfkq#;YMRq5VJM0n6b|+QDzFOhq3jhFY zE&u=?lPo$Je`9ZQWiDfIaIG3?bJ|AqduRF|HtxicK(@h0nlW`c1j~*mz6!WW>UcD= zSd7LXQAs$aDgV9i?Wqe4b~TBC-FNRh_C5H;54_=|N6DI-c%z=IW0ppI2#0Rs z4=I`WLGsQ?2p1!IbZ#g8>t?rgcIdRfI3KQ#j_kJ6Ir+=JY0Sk+(pcTaSoOC#W{wvm zLiQ|5pL(HORu1MY z;SJn~RDh<52TS0Y1hIb?&>lhfn8F+2yi;$Gtu3-shqvti4*cP7*$0|q`PHK<89`OeuXWY4bX%@G~ENw{l zAC24qPPIY;`W;rJ6ii%Z5fH;DScwB~yoU|Mc}+@;EaYSdgKUj;i4}xBYLWHaIWfvN z3uI2*+ab*hPAY(FDgPhRK&qhDd?83bj0~TdpZMfA1cf)%YTEEQBds&JZnVXJcbK-~(PpexQ_Fsz z7!K&=d!w;|41}**gcewXX2o9rftX-F$^9?x)Cqa*lV0YjL zcSx4JjzM1H_a4dXo)CHjK7$9{%6e`5Oo zTEqTgx30SO>VnWm>P-^*GGX+`B9`8srE3UE)F?@E@L$yY$ne4-P6(#C3P}|V0ok7f zo|B|LBz_pk%)kLzhMaq;0oWh4SsFA6@2|IOB^wnl98X~xXl0fGftK;CjNQl`Wj3-o z8s6f&%vMC>F=!0iXd|6418+Pve^3CHQLNL8WAM55pC>IF)j74#H|nNLg?Llw&@tvT z3RFDmKhPfd2oZS=(|)Kvu>aBBWJDo#fXGSAi3qijIlL9T*ACWX+F;u0eN+dnQ`f$ef3_+flcvQ4 zQy6kp(B-=(At2927Nlxu#S zF~de(iI-I$K@}zhfC{4cRta#B6+j%arw*C+?kG=5VV^*LMg)JQ#2pU7k(pNsLPh^r`ItlsEjg-8 zB%+}$ZhEMw{y#MArpW$cJRC3MY&wcme8R6lRMA1{;XaFpCA(NY`Vt^bX5{Mn+sVQnKXo*^ z)zQqN7#RiAu?F~>UeOcIFWQ|;<_F7MduDU#Tzo1J;dXN5IpIlMd=ifHQg(K`ebHWq zBLjRc(^+ZfFK&>ze-p^`(enJG2j8P?hQo14wl=dxlda`P6mSA4iPg17(9nb*)jX){-czG6ID%LSqvWjl1#ske=M|1E_UUy#95h(_iHgh zFf+1{1Ac2QEB5DMmX#>+ee6$HYH(9VLZU(-PDpv|%C0?28&SFl1@56o;~3=)`zeFM z>v%|$l%r%Pp|#>ws(gB>x3z~I!?_zJ(?*qui!tK|eu80;Dj8NC%zfdAkJZZs{1x0LPaG*I8dM9@8H zeR3}C_Q}PebJj5%D@v`!OnIPox+P`LIL`7ereuv1e`xCnMj8kw1py&J4iG1AOYUvx z)sHxk=Q0_^sD*A%&*;~{V72Yt%C1FtCz!~u12Cqus|2-nPGa+1CaXD2z0xj`_;=i?} zTVyYsf5@pRH!PdN)nNr(+wEa`sbvPji0ZMg5A<*V8;J$qw5Z5uaKN!JwJoF`lqK;! zP90`D<0r{0ot<$Q10jhdwnuaXmuy1_YLS{n%BY!Ha3mt&WoM+;a_teUs5(>Z@0rya zrOlV-0e|Ll(IwwNQYyP)X>YcVoz{7m4*--2e_ruSZ|1HZAkcW|dJyN+(QP>74JipG z!yz+a;fA3IvX0q$W8zpNyLer;c$4E?n4mzkCJEKknmAu>i@b$~hFGoEO}SAz;YFvv zMr+;3u~jLISFJ9-sY)rz#}HzZ6VU{+Hx*5S(?EHP-BzK&FXFD{P>ZgO zsu9FU-vhcZWdq{!GkdFhSpaUNl~B_Gu88^}N1Bh*`us=pGQ|yGGyCmMS{5xzC+xG< zfF>zvssLS-NC6!BF?^#EQ1~k7vw!4c)WQe24#JI-0i{J^rCDL~8$2dXbCHfYB;k+G z+C@X^y7niGb8&)n62Q@oTs*6^GJsvCOs*S*uQnbyos-%M8h>9Uz`yNnQrp?D*Z23{ znc|*Upx8P5q)0yOZtiSuZ|v>uY~1a=d$+suws*Vx>pV=n+hYtR{)ifTZ+Gjo=9>Gj zW8L*T&SA4_I}X0kP%u64FT`NC=4ja(R=ppQ9E@8i~ z_7StxyUTtZQ-53DDeU?R@@RiR#HGcluM~%WqpMyu+ud{+&WCbZSM7z6@zRi*=AnJG zB+VQo>XRYY`PykW&&qB52afAWYZ67+%(S6Z^aE@HKT>y0I{rU=d~gPfqsd6Grh}4` z|2=mUMIRP``>%V}>mE4%<0UlI$22*LC`H0Z*D&?EV1H_Z-)BLV%r{c^PWPg1FVa34 zU}vtrP{M8uyY~_N)?+iRsHC-=stj~r{rIA`q}1`#QbSyo;k#Z2LW_uSj*y>F0mqq=#g37IO`@)~tR&kprjF4yD~oB@pG~{| z6zwV|az&nj=~5tNc0|XQR z000O8MRq5%Hw}FP5nFdBRYC@Q;^Qv>05uu`03VYqIvJC|F9v^OaB%D$X;Ujj@Viz1 zhk00HB2f~+3m#ZVf~MpFhTx5$Jw1KQ@rF0)kLClHtvF$SJm~fPnfvbb%ImKs6T<$~o2I7RdP6V0$SCOb-B}n= zDy9zy?vV9*{po*b9^n7iUvuw5cQ#=w2P^Dn$l*=~-VpO@SO^}_ngpRUQJ{44=+>L| zCT<8hhLD&{TKA`6+UvmYzjP6tnKYQBzILWw+G`K$&U)YLU%w3I%0bay4?W1@jn{y0 zsEWKACjNhH?2Mp>sS^a@^~|4NU9)iJOoFi&1OPb&@W7i4eS(|=_}YVPR{%J;_HXgm zwI76Rl}-Iw2w8_%FJcbA+05;`-i(Wsf%A2cUmZk`d*5BX>gJ1#CfSgSG-$$bA(8 z<$Zy+#y8qCc6}Ntz-#EwXES#aj_#3C3ijp(0FMaEUbLc-6NJ6c>%XMhWJ6V_Am_oG z8%S*sYj>UCMRR)BlnD8c&yS9Fq6KoW2w-#>CC}WkGrb16pISV;>jjWO zWG8>z#E=|$K!YA)4n?9*BQFTQ+wUH{X!c(0f89GcYPXKNy@QkE;|tO950j0$>s`)= zqD=STw0+v^eBJxx{P3{V>7BK|ZC$8t?|jXMGk=W2<<_6Qtg!&<#eP2D?{w3lhbHuH zK6C;`pa%e2*7f>pH~t7%6^N(0&#XuADYSnS^Lqo4#hZA7JX|_fQmFVgNuVmugow}B z9S3e$!FM%Q2XwrjTz{BWuv*>WMey;RJrw4{0btQS{&N4Q-Rzy69DI)Qw+Y!^X~Z<{ zgkA;TCTmZ2Yy^{J8bmMA}>xYL`_WU{fpvrz=kduF6*$zLfA40|| z+uLIwLN@HWOX#xs%!Q5uG(6}Mm~31&@^9It7X}eH&B+0HK2o2*;=h5MIF{EXw?~W1P8v;-=yAYR#;c?p#L*Rx&oLf*&OzhP zwKat@hxDCy0ohWyh^pfs!s(seJfvR9x#$kJcrqVpACif&TQqvP#(1*eWks zLnKsfvCd131P)U47!0JzRZV|)L;u>DvA6wsIGu;8EBRwD+M`sC?ecG4YaV+DfKf<0 zF_x?EWF(D-M5Q9>u)7Pq$I}w!S6KJj3m6)R-jL4)*G>Rx9iyU9d9+e@6u9as z+MYDpi9XaMl%s{PMe={KXAE!`NbaQL_a@-L8yL;;N^yOZrz-VoiV%A{!d87}1R}5^ zxP0P{+%e3Zf#Q4d;tZZ-e~Bde5*Ubq6wb6hrnAC31(&_8AnMY*-1O!Y=DimhpC}hq zAdl!Ttpwmpn)h{X-E@SSFTlw{6a%Us-tmDma&O#Gjget5B*A|WG%Jw^)^j`!lqN}* z*!wB;;-v$MnlpA4Vtjl-Ra&oe$Nr5AqKEX4sWXG@Frl+sXEuQ;O6e@EJJ3GBbd?~v zHi(ERXs*7x~#6i`ug;w)9roU`bLpdH2=UK z(4^0;Nd{6?NKppmSmWIdn6CTK?J!J1V+#V%!boU*<-ybs{qYjSMc*GNibnJpshE2c zuDVL0O*3O&?y-ZaSC3bmpZnUq8_dVk9?~J`g?HP5)efwUZEeM0f1i&FboE<(ULqRYZ>F259gPzd#xx(x@oCMU=sIz}T!Y zK_Hsd-dYiG;-55fHg=3`d6d~m$qs1Yw6B{DND4Eqvwz$?dBIkaQd0|rM8YeQ-8qfs z9V|F2qn&>SBb0CFW=1=mFy79^npSyc^u+2adzM7I3d27K$z4*^Y-^WeJ2HSzof#ms zQisnrWAAUV!H_jRWFItG-(g$u?~=J&?EOvl;RkHPVc>v!Z^$-2!qnlAv3K8ncO?!p zpeZBPaR4|;A|k;vNXF*D7wBA=xeEI3CWx`kw;O-&r?%_LCkG8oF(j$AYbPY5!M&Th zeNar0VmJ>E-C2SJCbDbT)BG_CGI;4t8B75)j(-~IL?;BO4o%=E^M`EZ`kF;3q7`}= zr4glm6QcyV_IwwCt**jf z{QZB{M{f~pRo-&UwBcLTDm)@M^P|CSP>~H2oK7nShi!m7>@7uvG(uo28{ch^AniA5 zAWQS}ZpgeFIU*Z_D&S%PW;I`z`fLx6v&w)TKD%m?_|o0 zXTX5aO{SR16C%lxfl!$j(4e~q!8!!LO>lqh4$@KME!}?oHA|_BGiv^Pl{v@jkD8)| z)Grd@WIIsnL~9TLxG%1#vFY0WZtSBq8q$0QfY|*Mozw&=o|kG%1n!Ah-4(n%bs*oak8DcP%40~5ut#gHw)0q3Mhk?gLdzHI^usu z8^K~@)CRAFzU<7k-<^fv;3BxDk62WE@vf+4m!Xrm*EuCT zDP0>*+#%iSbWS>nKIES)&+%$@z2tv^)iP@#Vw{~F0mhmN@GPj1w6`Ioh+sqa4qVsp z03DcJ8Km!|gWyr1w;6t;J1r%xT7idm_VB4{Lc#d2LaRccwtOJNEv2^}4C0vC49qRYc7i zdvW$D^jN0_gw0a(*jhP6Rx72#rHIHywU(|9Yl^*N(HPxz=74<_=Q(WG^ArJ~M+|Q? z1_ZES7;%TB!seT%EaJ_b=vAeneV01IFBl1&?L)^K@wjFnhpAxmHwS<07mgu4d_O~< zGN$Q~o>1VPVE$IoYkxQl424@rEkts5FEnnQdV38`txy{EgvolbS?YG?g?XYK@?`KI z$L*pT8R~jkOESjZZc?tA6VU!>L_LE!f&6Gs82MDA>p89-$b%GD?$k~PY2N#4AKYVK zb=ut)+0MDyIuO4#B%^P~6qFI9ize8Sfm4IWx|OgKJ) zRvKTqGJwdaSkE}0>0II!!zsiW3}&ELl9AEFhaDq+wyM&i%V*TfYLp=A?Yp3n^oGvZ z8zEix55XjEb+a;xAM}-L;b4YLAjpbP`Z|ONTi(3m%g}R%P8a%x({8a=2g*=_%CNRV zQ?h`J-tfsZ1}A^)J-wP>*d_QCUEYwDTQdHW2f|vSE$xDh-HtsjFDok zN*iF6#s;21lpHNJIIioQyh8@mFBe1c?V|U+@=@wo!KD1LQnMgt>c9-`zDviLN;xXF5ZAwra5Q!Uk0$Oxe@RJjOT7vRXPTK z?eO$@(88-O21ZDcA zMMSpCg-}SW@~BeiDMZWPUtrkvqMkqn?#Dj*hK!~D7!>rU#ZxK4DYxuCY6rMrBjfAp zPHN;vFi8+Xi;Sqvj_H9Ew9j@zzKwLh5sDU;)~kOOBg!qxp&Q&{aU;+j4ti+vpcy$F z1bX>w%xPKEzD$Il^ka57ZcpXPjJYtA0*z~8WLWY2PblsQGf1Ik>N1DT-nXsJNo7^E zzPb}jJ8N|d^~M@YD;(au8{S~(6Dei9pYw!`I($@o8KF?`_;c{#@R=$@7asvd3lYQh z2O5958gZ!nyBgjIZ1qBq3X7Zd(Ej#WNeY1XGEJj9)`3F`G{!T$t=JY?h+jLYv4DDf zq{p$3z}QRC6)-`P4|7_y$=u0Oi(p#ggR9s_y`-A6H_=E;@~Fd+08UKl9AU*AsBy|; zzk^KU>I0+Ax{l{LRcJ#grf6l>^Wl?wP!WGB5tFTwL;keY`aG>UFTfQ_3kZCdudoQ_ zQ!=urt_%8<>yTH$WP_L!741{Lw!P=v0A+I86Nv}f^{O6r+NDNSfn1l^i=|hvK#7DV zu$B;)d^U%L5~%;7AcAjPC*ck_jMS=NUYw6zT;DZ=-r?dFkL1q@N z8@1>Mt4Y`l9L3{C6?_de0;dQhNQ#v=&>nzu;dH%(Ht3((ID*pehpvqK!P!4zW z1gkqGVDSiKc68FPye zQ&%AQvS^yo(o8ouH)gAsO!0;M0u42`B1Ofpvg?@QW&X((YNGNUh7Xe6sl(4kTloAQ zE&Dui&`!EC7%8!~mcMSNQLo#q+%ZVn9^#Q2qfr_inKPtK$vRdNi;sUm5l9A)d`-XR z@Jsb8eA40yqL;EaKPuKPF{Z@HTp9;+$;6w38U*@cwQIO$_JgC7vsN>?a4TzzO9ZS0 zX|^YG)r1(AWfx0ew1u-eA*3T9nq%WK(99g&Y?zGTP!{#@oTZ+$q%zPkBV|N&`RJp@ z-WW7#0iuj7T6pO^NvwY}HHC!+L3G8}Fg^MwIDkYlabQB?XPbT50XY?jTUAXq7DAgX zO3MA!RYft;y6jKdSzD^`1rx^>k8a zf`sSNN5KPyrZjDf=buG~TG?T?=n#8lje5zUg$+;#pq6gJ_jp1R9)v|l?Sk*ZsTA6= z+^c0V&XSF?Gm(F*OQYoW$^x_C66fg$aqptIFDl$#l?rVoZ4|A)#wTxcsh78{Uj`X6&+NY;k>OKJwxK18aTaSJ*|H` z$qY3{w$I1+rYvFCdwJ%}Zcs4q<*)!#>b5 zVH&BT|NYp-&Zd6py3-zrs~bc45W=4Kgg@28^6cbDNN)TM#~ZdzL7IQr0$YLeks6u& z5iqv{9GN?6$hHK!-8@3bc1@5q+vT?G$8sbPBSwE=!k-hB!Oya^IEESfX#X24B3q)} zIry^Me$hGsm|`?5pyGKEsAtDb8>lu@tr^D?QubBO>n3Z6c{}r?Ff5q&dpqTOVDd$r>n$r}(`arcEs}l>+5oUKVp~#ta z>i~b`bLhT1tE|Lt1=*)B1+uyT96xI2ANZ0F4HAv{@_c>MmXhnK)H5)E`;H**7`sti zVZaX_OkQkIqe}%2At^C<1(hplJy0D}S#iHbT9%|J$T=f23C+w=P2F`*_;Hf*bDJ=; zdXsz34qp`}iN$#RLPvPU@=jWUCcM*FbXR|Qq-8WCL{EXtW@DZef5%5t?(HFwxiAm9 zyfg)W*NnSVYMhii7H|faR~$(4`iNo}l#Tc_yvDP8{m~$;P1Q2gMQE3vU*V^e6|VKI z)}p6?(zfsT8w|bZmY99Ok513uV}jO9FPM1M!k$6w@s>c;ig*)jA@3BPOI=ABU(kOA zLkDhC3`7?S>eFtG_L(q|G?{W`t(iR#a@|#uKJ;;4R`gySJdgPrpy31ElIX&H zUIP=Hp1vx^gjJPq`cWZk zh)5oO1KXs&IB$1aP5b?E3siD^a@?{IEvzEBA6lvTe|1%kiqGA9^*Xg{dA5H?W9FDD zRxy;dLij3YtOk<^x)pH+Mc1$1TXQ%eqzD9%{h5541OqEI;aF7gWPIYUlu){fB_4o z82I#v8H5yzv4^IRg%pb`NCAHuV_XMAzzeD}1Vxf)iTX?0`P>ogX)Os{hVe3X-n?-( zbNTitxS~zl?49}@TyOR@69*u^1Av#^#8+Yoe%*8`>kc+rqK6E(e-pf5<}Ao&+VjQE zIFvqxS0D0fbqO2v((*-;iV<{5A?H{imYT?c6$5A-|W~1mUMq4P4(o1;>Hnpc@xDY+(e%pGGxUOWSC_h!g|C(4CL0ON1KoY z&;8v5FOqG2-Ha#WKqu5wBFT_e97+B^EX@8HVPVT?-rpofxY*nsS(I-o79fcsa7uS0 zLghGmY570S8d!tN^+kF0A)FQMkM#u=k}dO=>MM;d8OaRhqsV`JEndiTr4MMvs$}gD zfTNUo5Y`?d=mdQ0W`IvK1(ujcanS35=fMS?#C$XJ-!) zh$gmtQkF(yOhA^~{#}OU;Y;m+j@zfR)E%=sgkq zJvd!&pK=gzaQ=UUs0J!Wc+c{HtglD6i?}`L(}VWuS$DsCekN{shoexUH%XpchTyu! z;~uF_m=e~(B^(`BXCa(|$xpp#Pw3`xd@TV0ifgKh=&pOHXGdjvj<+BTY=(7`sPTzf37OHQ@z3e;{!7RiHW?i zs}EG}t!=ym3EFG)hDK7+Zs6W{eFz=R;HMWT^c1l75{<}dI491n!_Vn4TME>V)j^kt z@+7D(cCLSd=wQ+D0o;q!zlPy=i?3tDl8g^_@D=h6o?p=Jw)c;=6XR1rqh)N?TfSm| z#V~C(+ozep^$qLhp$_^M>Fk3XJaL4B)uwb$8W;IKS#4wxns|7t(9RHHJUM=Hk9QZd zpWK-*QlP|u;)!$#tmi}9=1~JLwe4GWe%SQ3EO37qMXd78hdGE}J&-51^E0%MKh4U~ zY@aD8*?je$9n->BVshcabX(ptBOq!83c3KSPV)FO>JB`QJ?3=di6oEY`}jBwT`U7H zm%#8#VGb!POh9^>d!mr;nE2+z?NqLcQECOUt(4@xL`COKtBTH>53L9$0Go;+TbYXD zgGYaGir%W7+7H~0vpB3qcmA%a$EWq8;Kx!oTBst8f~M(#g_?>QTw;T|{DK$oPmMXR zrpa4l+%;pill$t@Q?!j)6Sx*$5tY;!)vPKMx#r1>{q}LXBG&;Q_y~`}VNHLGD%~+y z)ln@?mEV27GT>Rz@Lt*buJ+uUGU)kRfA)WJ?;>4^lc>NOaipl0Gx+l6J1s5tJD>Ir zj=L9-@8|lfSWaHClu7y^v)L7E#47@oU7)0^!Zdga|0UxAfk+;#ed@szy=W2(nXyzHPyJ&LuH!_2O$%*bWFp;IL$9{ z;EI!j8q^-T<0+j;**u2Ry2W6Y%L9KkFn0#rkAHV!@7OL5V3a@TRKsGI_gIkdL)l}{ zu*Se$OtKj&>_Hc9B{>VgB5OkbfJm}*8RQ}%PEg!dH|XJWIpaS@+$D5favA?3QW<8H zPB&dBx1>)B&AEtCT_fLXH(~|W(8`Ki-U^D;X~g3hxUEKRG)7wC;{C<>*Sml9s>rGZ zzeEw_K6b*&UhVk`g`zlr;j?(eaf;Ic?x|q3r(*8{9#?281IAt1yNKZN0(?r|6fn^! z2T@cSd8nYLg|(Y|wdEm2VXY^o6d^%RkcM{lUNO1)PeLv`%=q@ur=^+d$iEM8Y~d0as|xs8lvz{@;c*9-W*RdRo)!vAP~XM`vG z-{X~*L8z&OeXmXw&Wqlt##{5FYQ_OPqm~G~|C8Y)w{@$^$WxZj?Z1=1wD3gh#j+5-Smqc$=&CYH%-DI^@D; zYc9$He%>qz{Jd!o9F&%Fz@b2)9brpNn*%vC8Zo=DG&^^=qVgF`^|8x8#gZNtzga+M zAWa9be4AG1xHD1~4BtZm%O(^Jql|!O8tjpBU~xupO!V2Kn8<(d5sj9J$E?vPa%sXh zQnZ>PX7N(w>3t>Is9LnTclNHu%PsreI;CW{{Hbxt5w1jZ)dvc*r7$IKDNbPACvIo$O>nmC~CFCk;kXh+xXF_vH zoVWNn+AaI2%%^{-B3s#gp+DLj5Zz&xO&cV-80x!lkJN)z68zYl@&IV+2eU(^(k4u} z6tcW$LzcLd750z0*f(l28Nfx>bO|X7!qB9jS~@O=lop~PQm@29$p;$)NRpZ4z@q%8 zEz45;CyQx`Am3<#uoOD>?bZ<@D%bdV9K2*ohn+1HWCp0c88vU|do{L4qNoa~bRnAm`NuV6r_LVIlE zkq1r$Kk;)RIsU#4Ef$sI@Be${czl&&-c>`H0gu*kGZF95huSFqWnjQwh1WO+E?wen zCKehV<08UQEc`)|5jZ}}3B4WnLMMH{0;%2Syv~0;NYC~o$6z%wVQDwA0kxyWfF96& zXF?67d$&oT$CmkRXVvqauINJ6^j)sU`cd4{xlU>AK91arQGbX}I_6H(ch6b-d;VrV z=FOM`_`L-kz_~u=jaU_13s&*JWAn)!o4d<#Y&Nw_E$exdgErnnITm+CmDr2jh1!kX zh01>!GIq1lf?T#$cH930H!^-TIHM9x}Eb@H67~?oZSa;Yj$}bx*~F&P9IU8Q#_UhZIswGW|HSY%wiN-$oXPt@-G*J0z}|2;D=4wXlYnro=XYyID>gvP2)T>wdyb{vArp51>gD3eL6y82gq zN>Vw%F&F&bp+^$TsF~cE_A&>B(xl8XiLsF=u^`hETxh=h)lk1S_^+i80N#38 zB@tc!+|WNa_0KK-Q+B}reEyVtZl#(0h?)rxsi&(ZU(DUvz1$g`X8mE{96gDugo@FB zY^o*+dWekr#0;TVTB?6lK>_mm7Kd_(@6x`z$2M8{rUvd+if^d@3-)3yYJ)-}40}>T z#ninL7D9#_vcY!=J=_J!14Zb8)LY$W^T`Aq;g{|Z+}iOAoiHb!?0<_M$^fc?c>9fJW27#ufMH9HZY0V z3$TQpE7y#ENjQIY!h*7HuOyd~4LmqaG0R$obt>*k+|y2TtK;y4nbF4=q z*_3o@ADpv15SKxgUFi$VYJa)_$|PL)wzXK-E*Ohdy_X#rj@*%q@{H@%qfu0{Bo0Mc z#La!+gzX5n$9=+ZJ%iHucgr&R0waXWnGA|jCQrld8V*=F5M@fHJM z*-4Wt-RxVq$~Ej_o--v$MzSCM|tvU$UL?*dEV#Zrg@JzV|ue5}v^2n_K9X?xB%H=6}?spdB^) z(cpi`3z1bHd)2DN)oeU(j=zd(z`cP_WEiED%a7++gyg^;U&g}m_ql#ZPDf^`$GqR% zYdQSox@|^oho+U|uDdqusE6G_xAe)*DRp^a;|2fXw17u%CrSlqa@b|prnn#z*}sU$uQFU#cJvdWL{yw3qp5Xw~HR(@PI6C84AN&DTE5B$-i&a^9IojN|Y^Z4=$nOcAG zEJiekD@CW)dP2a9H_W6N_Y6D-2LS#RO`(>+Q8HRH_zyH(IZS4w-(lzeJ>+I^1OZWx z`(She0Tb&3nwC9Q4nce9>eTmarh^3z`C%*CyWj}@V_EZM;mS&dD{>;Bt{J%=2! zls_9_f4WYndQzOpxjG6cx+7p(g?E1hiZ_xBtqxHJ<+Gs)Trl)ILd-21vjE*K5;G6V zSL2?RDeQDPTm8~K!|*}es(#AmtdQJn>X2w?=Hyeeu9D;%kPRlFWAp8JqFgE|1>gep zM_Nrf3f`Q)04?mlGOJWF^6HXWLVhlbGTO&@46;qe3TlCp!Lb$jGSx12{K|iQE;PFJ zK@gYTdqu6bPpqms<_okVI)=Df`9sPvbPF0L49_>#IDtjs+6mJy<$mbOm+(#Z@wV>^ zFE2d%bd3>vJBy8PoJ;&i=MqiU204MtH%q}SdGw=CHuU8*JMMD^P1`830yUTM+C z1iRr~ycy%s!Ns#qR+^s6wa{7h3@530s?P(;h$emz?pV&!q zv)h`m#D$Iaaya;S3|1)pK}e+|N*(rlFW~v6O8w+^#8$AR3oKzb`B`nVyOfD z-@Pl8jva>3?mgYkG1PzdY-lVr3MxonxL zQJGK|f|`d%pg=EQheV+r+zCV!xXe+Tp-5Vjgjj4vQko?7sC02oOJTl|3k~TUa5qDi z^#0=n4Wr!M2N{1fo4v~daM$$3XP7R3G!L9{kn~b|?$V^_TmqJM6=xFK&ImxYv4)6U z?jAl6!oq_GinYseCn196BE26x!5<8ACy<9QY8HdX4tNaUuFxba{K``(|^*k6#s~X@msUh+wk43Z}a&~`;rH+o{)>Hl6F}@#MP9!b^ z0W}axIXBoSij2PVayokmMA~fonC}_%QQcPxSoMmVVFYP>I}C*-dW8mHT-+M3Pk2;k zKdGQ$k%z674bQnpkIaH+mjpTK+Yg!iT_D%>u>ZV!_If~(JL94nyCb#j4zQMBA4tJ= zE?=;nYa)Nxj;yhVU>WW1ZPkn!OPN!imrQ~P>w?54=E|u+IYgPyN9fkdJgrr!=_eRr zSr&g<-2unlZbg(Re0T9L{}rT>=M#-3Lc9bWnOwEjR?L*CutsUiYP)UvAP69E1 zYE&W$no$FPV?eA9H7KF-5;M(%8hNRyRW~XjGZTL+t&$p8wIeB?x)t^k!V-B&5+1lH za*WZmW!IUhmDa4?@U@!LZk7eB%1q6)hSbO^%V{^u3{GaIW?IueDa<&4o1ayGOP_vfBDnJ%iv#HuZ=*rirHk1r@rUTBRpNq z=Ln$>4aJ+UCcfw3a4~&wBkVb-dQRRBPb;bZf`T=^Ti)Nh0S>*du4RvIM^|Oq*}`${O3_Djb>797j>U&PJPdunN9PE7i(*4Eiis2gXOc2(#{V@87QTzU z`D7Y-E4OiNg1dPDsIhK>p&P8+03hq%f3a6G?b>Uj3Sw$f%AwsQ_xPx)sh191V)&Mg zt1yT0(2{HtS^@plqFhNMiA;Bd0#e?MtPZ7WjOwQzm?&1ytO_!%Q49l7&WQJTBo+#2JXW%1RuLFd5M+%v@?aZ;NPy_e{b!RJs=@d zTc(hRGln>nL*l9dul;G<4ef4Ne+|#rHcqQklS)_yJZ6hEBjaY2pc)3bRHL)Y4zaMz zhFG>K<3!1J#%ueW?7IP#mObB9w3VFh$~7eCIp%_!6N{dX4@4Dv<{JJt_Kfz${|8V@ z0|XQR000O8%~=dp@&xN*c>n+aGywnrApigXL2YGXVP8R1Lrp|qMN?2OlRr8Je{^hf zUt?u*baO6fjmpN&fD!Wc-IHJ+WS7NwKA`r1>Y{M;#F#+n^Zmt62l(lASBLOJpoWl0|XQR000O8B6ufN*hA4!C2f5LP+Uv5 zHBNAMcbDMq?!j$vmtX;cgu&f4z~Jug!QF$q1$TG*x%a)V-u>$SGpFX%?%BP1b@x-AqH`wmE9|qp{S`GO~z-J@W;OpZ$m7 zKxuq-a*U(Mt2}+3(lOPOXc)x2LOeS>#+47>*7~0YEIufe!i1 z4dQ4xBkL2XzPolh;YjGH=5r#!p7u~P6Fm%ke%AgWBN9;vd7k5WMkVb2k_prqw2sx57x?Ea)BShO9xxqtSr~4}_FDm{Qev z>4EX-8mL2AzkskJr7LKVuj5^aD8>8DjQxKMFPGqrTQ}fO!1mDvU;z5EqLmH-jANOP zuYK4ow4vNiIx>G^dCiUx*u;BQ`6;)%yWhY?*G4X7R9HaQtWX$O2j}`QFpoO6XZ&N% zsai=C*~Ylzfg~gol%=7~)#>5*OW-Kw0;>haW`1(QSrVBuI*eH5Q*@dh? zg&Y9Eiy~t&p1yW~rGkI*{_YH^V(RVK>pcG}j*xq3Jr@p1)Hj#aL)2a0=RXw22z!#$ z#EcLzZb|HvP$=L+ha*Qjp)6kpwaG-1dwgQmBYyB;t9BiF4$RB^5-D6 zNmEq>%z$zBq&R97O2m$#;J85&LU4f&b*$=p`v%<|P)tRd)*TIdS|@cyN-+)CJYIK1 zE#MIM5u>)LApVr>`S1XnL%LMC$^F^5vuo%Tj5lu}2ZxP!iFl>}@Meh7Ngo>skKGT^ zPMhISvJGllC~o%tHl48}+Hgdfd+SbCe;4MtPzlcb9Y zeI)cZ(e?%A3W!C4WimCBgy5l;DEy@E1LdCgFLiXOPRmItu79?T(I&$YCj*1i!5!QP z2R5x!B6r4azC2z(Y&S0%c0RwXTaLYvx?{kM?HL`ToXC@kJmsGBNR<$;dzm8zGfXHX z6EI}!J0ky~bad-_Lz*A;Zs>cTT6fsaItUeCGSAyqgK$IE$Nmg!r&K=$oi~9qo5Llr z^GXdK^bI%~{Nh|k7QN@^Fj96g2J%_q2ex0$Mjpccu%Smz&PYQ-F(8+g%Kl+y{cX*w zcWWl++lpfQ=ESFO_X&xSA~L?hljs$%H5JNSrpgI3r`@-KDYhK%{K|S2NL3i(=Ih2( z`)L+x)Gma7YLsic^0;Pm$ObI3dCX13#Zxde=9oqqx@4PBA1jpwNJe(B;whS}Pk^li zXW88vDM9(MFHN(q?r*csG14%|$s=~0PV^y(7Mgm4>zJeY0k1ScIXQ%E&mLm6{1vFB zwubV@q(>9h8iB1HjcOZ3Zbz;9<5ZiaSDemQC<7D?MUd5ffcFH;yKe7dI0!YUqeA~c zvKht7BRj3(G}m6qDC=lRx>B1yF5qz5Fqe}0Lw=NcNPb144ur;V&I^|6A}(1*hPpDu zC#9(E7CkszU;6lEh225##1JAqPw41^@CbW1;b++ic<;I4CZ z4;^--?wpjN7wh1tcJOBI7b4!m++bbRsKdF8Ay-u92KZ!#z}`hON-apsk3VWxwzw;R z13VlUWNuIAkF^qlfzNQScTu>5Mm-#Jxi(vEj3+4A4I8s~ki1!fwtH$RcBxwDs>H;5 zTkWxKE8~-z6viPTdTM6{iEV2x48&|Q%OpB0N>)SmyZ*5O0t760B@qk2GOKTG(lG}J z*)j8{H8%$0X9tK_A*wf&??Azb)CaK>?N7gci0VH|sqNog>VVn{U=rbdH8~q)78??j zXzrzhQzuqt6J7mGejs-Bu5+>Ghv3g;dRRE8WeF)ne$hza{4VsuTPh~UG#B(6sARt7 znG~h1C%M*K$?aO_0&svfaDp5zLnkm$QHhthOAqpu^zFB&-J8n(<) zN`R2V5OxDmlLb3aGtkI@N+%`e4J9TZ#6lzxWAT}iQEXR?l01les^R!TdVStR8J zG0V_5SkNiL;O-?fBr{{i<6JPanzj$CK~?Ze7cKIgckD=(noz*1(YC2@2NNBwX5vo0 zi<z3YT^n9kE=xmi&>_8wi}2 z*8E3}Oqp>SHzB;N$E|c$AOwGCTg763&j6;h+(bB)5Im6MpQ8}#!rlizE0eiIj&+Ra zB9mWu`nHlrzCi(VuU6OJ*403@0GQOl7z)XjWwp7zMH=OhynwxtowN3KtR&ed$<{Q- z9NBDyz3fD_2cOTQG)9Uo_5~2Xfz7JmhXf2sB*@D}zR4~E?@RYq@(@P#V?z20wa z6{WbooU@;KnmXda6?8}Ib?VY9x>K0w@wI2QXEyChO20@2q*GaIESQbfQUY)Rnv17) z{m3@{70st#`|Yc#nh^Ay0^03|R65E_y&T^bG=$d9U>ag3mN)*fulUd%Yt)hOwSuFW zc+hCvAI~k~Q-Xof&;4&Yq68MeC*UcOwHC#P>iGPTM9SO5zwToy0V%yWesi=4an-rx z4(oPoep6=X?8CLi4=WW%&p+9H-M`5ewY9dEJ(ZSLp7{ueKj-z6e$({#018|@C+Iv} z-j1}L|Agb7VwF?;d0#Y@m~XV~h>A?Kajq;K0rlSgtn}bUJYiyK4(xx#0+RN8M@2%@ z{3AN6;wV6=eez&N+;>gM^f`4B`}NTTJFO%y-Rfv?O90vyYD#K$vsD-{D2y3Z%r1E@ z5^Qzwi^>pZFvQYlKM448w7ctuJ?u`O5kZ?Ne7&#qJ}^iQ0u(h=^ut6vLLK3W%VDTm zToV8V@B)9zh{rm5x}9|j0C{;n(lFUmaXVTN-@aOT);Pg^JXqHcc?DXE1J)A#!UtPa zy492CU;11!_U73amI57!;N=(~spPJ?$dcZT&F`uL@UAvJhESJAfhW0uT#~*w!rWI|bQHX3Ln0z@I~GFN8QX&mI=rU@;N>^W32|X?(&V5< zXvGlXM%?k&NBl{OZ3C9zUdMqenIlFKT2r)1@XBu4bQoD@_EaS1MlQO+bE~*TC$cvh z;qVZQV7QqaS+G6i_hzK4EF+AoX^EyD3QZmZuv}Vj5UKaxTkEux#nb5-ywSkddHz+P z>oV{2gzzCTaa)5V5G0}H#N$}~v!PWr`gx2q{hALSv1d^^-pV)dC~SMPV)DtO?YPqp z7?FVoi|$)9ybr@q)`*Aze(=UCl0o#8u|1(v^h07`^~DXpCwqH4!SXFm{VR4P#!oB4 zhl`$~*CCW`d%UGp|3#h9hRZEWDY>iy8HqgF)DX4^g)TR0py^y_R&)reONy!_$(MXd z{v%~B)O0h_#vMoR`wVW3%^FbJt7xK8LGRmYCvl`regok_dL^Vrx34g~>60lBd?osV zD5C8IIhe-jKF2RHF-v4Tu?s=a80bg%nk%w|EVD?h5l2{tF=|wHo&@Xl zEo7uD?ErokzKaM;A^uP$jFr!Q`@%0s4Klf0PE1JlFg)UM*vo&#! zic%{uMx;rYL5duY2pK!JQBV2d`bK891sju-;qE3N(AnF&8R+(1#ogz5d^qUf+i@<# zW$;B+PfHc2j{V%%Za!Nv12hB(_@} z$SDOfP_NNKH(>tkH;l|vYz}0bqj%Th2r&kP6bbrH#9eq^y3)eC0LZJY-v#r^9drC1a&C%f$SYvo zSy5koNjzM*niy0VEX#y4y*I@JV%*Dqxj9||y3s`cu~JF{oJM|R_Q^ZIv9hVu7OjGA z-N#V!{8V~9exS)^^^AyF@%bXzwVPXvW+f{K>|F;Fwr)i?*kAy5MKA7{@qup4-_37h zX4uYWe(8PWoH)}>3Hc>Ouuru5w?|<>zSA$LDay){5ia>rO&P0JuDvBzs_*6q7?AKb zKq->m3-UTNW18xCpK0CVmJ(%Fi71Q_+EHV=G&|2?r_&^9THSI8X8bu#H`%OABn~#r zP#Q1%g*a-nwWUJ_MO;&DU)M5S3l(RWEy4l0TKgWMrQ=&ZSdvaSDI6Op=d*a)hJ$wEnqWeYp2q>+V zc{3~1no2E{KA04lZ($fj>#a;d+rZzCZD3 zobrCPsnvAq?qzuS6Fcm3wipTCOv-_6sCT=rcjnBx$bCqyQASQ4+GoVq;}@Y*U?pI% zcD&e>F{;X)FI=Mt=9{RwiVk0~C2;raa*MTFd1kT5nhvx#`8oz%>UZvXvh4X%{Wnd6 zw?8Ka#5xA_fZ8Sa)J7d5{zez4hAazJ0P{QcV`j`-S@x(t*t;F|cys zUf$;scZXY|p3kC>r<6L8;!>ING)8=*yaBSGj)%>Zc6xOrv@rKMwJpT8JGk)!g@y_= zikq9rY$aUH+(qU?4;b0F+4;p&SC=4BkJWzyB85NiX1ROC3kaR|C`9oFvVw`cExE6W zzd!6~C&1700%7=UFoYd^voGCiGEf^B%%Qpk#%^LA>+G{dX7kKZi8?;d#BEU-Lk$>LZSIE`!-74NTQfR;-ZOw>VfNbB$YS+rjFBKk+dp_J6Z~8336; z&GS;#FsuBwNpLPwDXqo*-O0U>9kC!Yja;S!Fkb(YDt;6`cMX&c9gQ+k^Yb|<)+d1d zv7jSvmE4DrB}yn^U50?ohliZAtj&}vajX+yciVtJJNy_r+ul*J7Yxz+rp#)O^sS6% zSRrw6A)DXd@@=&lz40I?vJM&P2D;I=3PO_f30-2xXNr zkG)&rJG4~3Q;n_H{Uu8Ylo6MI6PTNpCGV>=Ua6e3<=Q38S^xBNoL=@kOfG?gQ-%of zdF~J1u_o7y+JSLIihpN8FY6mC4;X740&|Id@mx59O9*iA*-F!z2PNRMg%%CAOii9`9aI6yt;kJT_zTOGDB zwee3;^$p+yNX$sOHFnmdy~8RC3pensCJv$ZL!kcd?yOab-&cek=u^WbOI9*yaD|_S zH1+xktKEUjV>}OSUkMI|atB^n&`IoB&De?ySbi`Qb=kTGXAMSh_V3Q%q&KavXxOv* zOBe)komde2+`%`RHs5%~jV#FT6OQ!$Y4&&*G-nsHr(5!gS1CqUNcVojaFf;D=FF4;q;HIRgu3ZJ%evOmqo2ivM?@2vn!%3m-8Fj`GN=OtrOR1?E9vV@;m z8hOM%V6R0RMI136z1O3_2w979*#;96+Aw8sE(tC{ts+@U_;hYDh1Wh~sgrvn zbsK=zzA|}BN5!$&$_C{mc%mhsiCY5z>)r{o`{6Ir-pD4!HUGXxZ|qw2T%c23}x z8u_L9tnyX`$}ypnKnV^X*pk`&nQXMdr1w!NMjYGz(zFyD9BPLgxrYo6>Vt~3Ehs#2 zwCWGUANtC!uLigOF*}A<{UCpOnp2>x?#dsSl z1zo2B0cx8O8!$7Q`-35{C8J4aIds7Opn}!8+LT13h&(L7j9isOe%2zSj5uj}w)9+} z61U{nVp)>A0V+U2m@`XW`r(v;=eG#S1ljRIxkPu@ycC+h%WUcy&#UYn1`OveF18F7 zjV`4ui5>>3P6coY4@|X1ul*{0>A1Tqz-JG?#(vg7 zdbI(}N5~u9CQ0_0GK#(RDCND~%=S5(vpUP-)rORIrY*B5KT}0iQ>*nT+LJ391K=y% z=IVC~XW3NdVw56N^$!)@Ot@E|4GhUznicxA90{gCZDS1*Pa8l+WH@d7z;Z^6a3qv{ z1!?o6euq(#)S!Ek+=~g|CBlui|MV7*5=_`HrCmMp3cPZz;^@Zs?*^KgWj#z2Ws!Ri z_b$J0v}%J)a*tyC&CWvVmdpy7?BQ@Ho)#p0>esFyyB5DsNY-zMeMr%$VhJP6IV#%M z2uB0?uKKO^FEH!BJK0-QCb}mfVfce9CV>ymu4`Kq>}v+>%HLT5hvTXG>c148M0usG zp_^w-QdfhonI9>pPPcodbdbrtLjY8&x;)?7Hni`bK3XW&M(2iJR#Q&k|yCx zVec;?9+%lfmn2skVC9#GUOX|ILjiZsS!X!B--v@v$hhevYM}s0{u8?6s}-GLF{U2g({usYU`BP1;6RS!1u=Fz6qyueO^*At(ytJEg_`^ zhVG#W!m<^ve4_2mIhBd}##Nf3;8<$N!ovK~r z^9h+Y6|fE^_WN#b&7}cq>IzC2pe_YdVl!K7+oe;#7_b#`{632q$e}c&CTD&H_mvM- zMd3)$ccg6i_JuMV9id1e4}VKTjh}v8G`VYn{tg)Yl@$XrF1&jur`!*gb10J`hq_tN z9mOdD!tx`{4WWv>`Q1Ff?T~7K>-tY~!7SZo;}N6IJlqVoAtxJGFct6J0VY3h=4j5) z%Lqv)#jej>>GH_7VgytE*R+uRrZ9WGXo0lp7lB{7BRKq-16Wlx%C{xUT&*LI7br8& zKlVR446hXm?~I4aOcQ+C$D>|P_+ocEW+vSbrq@b$EMMc*Qo$s*MM;M#zmSziyM%1v zgVcDxJ`A3w81W?EQ`cBXd;*B(1sxV=tT-Cv7C!aUtL^z1C&}g>ufPa=$ zVUP7L7QX|}UUot{E$F__`D~$08pyjDvEB%T0=LFSv2=h4+`Ok5#&vQ+w;)8hFZb(_ zGnWQ#j(R-{?2_<8n!PK-CULE(bSlg<5u^yrVv;Eb>XO%!iKU~WD}9lvi3V)oW|t4G zGO?oLx}QLGQ&JG)YHl!>$WrO3`Jc*Qt8jnkBhpsuI)gq1PdV$KxPj?lu`R*6|i8V?DUh;b4E>KyJ(5-DLos-K{i(Z6cz?a0omvcgF^XDX3)YX z4$0mJa1a}7RW}@XgDE4>NCw6F;pUqwXZo)VEGFI z{i@_ktDT1G%U+|ew*}y*RLxR7@M;Js??(;u3T;A~1nCaKK9U!sx8q7Ksr_b?q!U)D zwjCKQ!|hu|qzd@-<0cNK0?sdv9BR+%8pk=gh0^}6d$Z+Qv`2!#G1hYEC@9&@oOggd zC0Joup#qsC8nKTjhiVeDMCh@woIXe4Rd)#HrV+08PzMmniA=hLPAtq#I!Ol{;ur@P zFN{5cfBNn&f`O#o*?i<7D2J)OCxPrx&HT`lkp&`7RHIxRlMx-0N?*sd5B_bD?vyQP zX$u(~9l7X~x%I8f$K){mww`Mj#x-Qb8)d3-+B1f19YntTRW=hMKh6E?RemI6q&P?c zMx%0fF%?V?=8e&Own3F3mPs3k*kgr1Zv!UgR+OeZv#31c3AP?lGXCnc^7A&7d5cf@ z4^GBSvzQpQkiz39P5v^sLTPrTz_yp)T=A}#6hi?!&v|?1bSUs{Vj;{bF-N4Z=j9o5#Z*}VaNIJUlFJt67ei8_q^YMU<@&y_^ zwTbV=bNd)&LSTNk+NJ0~Y*=fjizdQey6-M+i)k`g#z`K@r>u-s3d)v7G+B)CQ}&gb z_BPGH!opsLC>-S2T{droZ8VlovJk%-P*N;Yn8#+Zu|x0{mgC?GzO_g){azhoTUI?E zc*V)u|H&N&uHDiVmq9##&p=tIO05U}s;`?eqU)Z2ZzW6dRW*Db$fXkIHsZsIzhY$3C@yfRHH}-!%>m| z{=~i5>*3J!#D$Q@Z;25SpmJDnbRnj>M8JX0pU-}o&3D8YCZ*EvY{u6xvy3)Z6Byt_ zwf{oDzMgFQR;%;~)PS$}6RUh&YbSf}YSCF{VInl5i{(Xn9eZ1_rnq>UJX4Jlc!R}X zAtXv9>s8kVX_LI@u;~RWQD$yjtN-k%mj1^7J>66Un@3+-T+~MnQ4ZSpAfzB_ctST- z=QkJiah+j@hxUYLE!$ILnvDpTQm8x(tBkc!vg0@tOekmyxW&c3>EFb+Ixjr%U7lXs z7_E5wmwsztXH?kT!Inb3wT0HSa)Cw_h)VWOr$twmW}vO!+}{zArH&v-fdA0%v&692 zID>gP1NuoDn)+MedaTiEq^}mai1?a(Z%+*yzT$y-R|J_tB-+VjUcVJvThK7uK?v;< z70((!>3K~Xkp2*q`o4)uL3RALJ-dfH(Zk9R;M3_iQ6@vT0NYz&Dnlh$Dbb8fQ*20- z5eMh|zB6~Zq`PH4>QR4!U(Y%}LLdM!U^bw$9I~^PCEJn%`r)9$^ltLib9Lmi1LR0K zi=ZzVOK8cwnE6l_B(ErkwA2toQGW#ZWs(nP17-UOQ1Qe7Qi!gp1_N6G_Pgo#-At|c zO62mT#o6oQB{L^i#q^(N&aD%y_@ZnMZu_@PYovY_J!Qh+A)HhQkj?{++X?mUWl**R zNY%$eLN7U&IUU)D_v~oePNbv}1gvVnF&4IQo&e-Og!NnGjG*5hkF2iZ=ELq5fFNH; zk-m}_VD6&=md%dz#+V~2_NMdD7iJw!l+ZyttnHwle1!86^T)vQ=6sH9Kj<`p%}bF8 zST1ef#h6p-Zhm75J4|PAN|rb9hYYXlCmW@*GCc_M5Cy*%V(g)FX;zD&+L^OulU6cg z;l_>R&x`Md!vAxT1XO}<6Ur0J<~@HV0k0f*aC|a2afpYs0xqOvdh?apG~JVu+V~85 zo8wfD4}rrwPfZu@fWG@qT#4%z@?Q;Ahkj{Vi$f}v!3q2k@)u)h3Jfk~$*vpfUXM0gyW$%*z)pYPbc}tc+clx%Q7@UbN~%Lv zweyKIiCm(&v2{{^d`KUIU;f1u%CO+lhw2w=+K^Bk%wtoR;L%rBSln_G?bB>glB<`u zh0+*}NCke{+2Ik|R~));a|`hR;5J~l6nUd7ax4MhMlyN4*tUu++@9m}PVs;+*B@YT zA_so-pjZ#k-IeCQxy5~3ur9pzez(mQ8rAa<4;k}$(Y2_{sG-AYV58TbU0EB91*a#N zWmue6`<27Z>2Oe_VT(BSwD61qJHvQxoM!Tj3_BxzPpIU9R|(oTS)6nR>eauf`<6@o zJU|kmz9Z6ptUiyh?x1^)@{z{!i?zNJ)Ub#*Gp5V3f?{%6h+OZxi&EsEvsDYp7)XxM zq>7-kiD;%~K-sVsQ{Bb59o6h?;6Ry6K_MNMAf!NO1nL0>X99vasAg;B2 zg>FU&v4#YgRSt*1SY-Ku+$!8C$U|^>PsV;?XWA#p3494$r*)7v;XlWAWk&c#vUlpQ zu(HTy+FUc`WKe3#(z!~cAv5V*!$S(UhrL)E62c7nPDB(UR^Q438XE1Ii)fYdnWi_2 zU5#Q2bs`hK)u?PC(G^HX8r_U;Rye2Vs!p(Hj;6I5zWm@}We3DBaFr^s$`_H2GwjO(_uav1b6yABuMX9nm z4I0o1Kkt)x*v|LyS58|O-Q1ER9^YRR$ zn?0?WZ4f_ytvN@!Yxii$Ki*o%zzN!W67eLWt{28Y5IWriHXa6$u1Rk64qo{&JDsdF zcLoznu9#T8g&W_};6pT9HfXh$JC6vN^8{%hE&kcK>iQF3gvmQ2!w-gV5rrf|=FT7y zumjjBn3s~C#*{J3F*7wojrQh3wB-nqx2CnJoeGWd&~7wBqv-g>m}G~wKL+v6 zVj0r{dQBw)o)gPkYG#cCBpCRo>yYT1Df!$(;HOI}dJal)4Uc#*AbMn}wKJB8GO4(K z$LBr~#V=GY7$0nLmsrUWxw})V{m}<|{vD%BN_Pz)GLVy zft4EG94F4ZA8zxq&RZIO?Wgz1m2m4?>1GsG&vNJm)T~v2id7cDb2F~#20bajm6$U} zmVck>RqMe*LbMOiyPI!)MNFE4@*eE^V#la8W~eBXW#&C{Qodbuh8 zlX+bQVx`YkSWcgWiACPMEJ4sajG-eu&96Z!qT@O2)K0b=&ZyB%qpEvoocpbWb1Idz zoW9Fm|IZZ%W0LBEA$GRx>QjX_WxJyEVxKaZBF0$XFJE}36~{bm}+U0Z0c@;9cd&Xl%9}AxZ9u(wLJ1Pi+3JxsV*D_F(cxK0ka{Zz+9s(Yw zA-{eKz667asc4w8LB*6@5Hpgb%0J!ex6}bEFa@o%blQ7;Uy2nuY24bn#i8{V!&s$c z_kB3j&?tj1&r3Yk38#=2@lEbj4)c*!P=~H1K}EZ65YAALBGoQ0t$^*6K$vR+B4iBD z7#zjv1CpbiwTR;1bR2c~f|gub<3z+_P(?8lwMN@&U`1OK{k?i!A6VT zfMWMlPcx=%Dp0PX^3w#DGeJJ&({1zqMQVTdnKAj6>eqA>dl=qN;eoEJMR~)b6v^rn z&loZ0%^y%EGcX$LE-Fd)JQf!6z+c`_71Z|~>0uP~=A%1Zvzbf97Q;KKBwOKB(Pw` zDGNkw!;WfjUUJPzVGl&$ynDdEqLu?k>eFFpo%fj zdhn3W?U!c8Xs?)I;AL5QDPd=;PW-kO_o(yBQ6c`|(o?)-`TPnS+^nZIHK(roNTr8M zTWK6^JNrRdv_4}gmx+KuE!y^FZ4_<{Ng*0<&HA1NAHXAwSbnTLQ-Ij8*iyqMhRb}V z|4K`CV{pQ&z3pbeddtFkPktSxR`h8xJvZhTny&M8-I4qzFy_Qu!&$9nr>(2-OxGJ$ zrbuso_A-U8GSyRhZvUA5Hlh?|V*dc`j^TkfW^)B5?&?XbP45f9%AG_;&&s`w0YN9@ z2&l2OQA7_lLvJI-3J{b0R5)4;+kIo0i~m=V=tmLNUqz7JH*clBlu9@G>LwDCvK5@? z!jARq<+f9mZ%%x(SB_%4H z)sn>0DIWBt5OZcVm{VEK?{6kRPV48Fh=g@K;z4@!mMR*@7FymuPQ1dem~iU52Fv?h zll}a#zpH9c8S~=pXjIJMziYrPt{G!_a7=;$;fLjoLn7ao&cLDnfJnKn*;r2faqDO< z&=1yFnH;R?0yhqGZ@50P*%5~(mN*;j-pTfu|DyPgsFnTXvWxwRI8|SSBzEr2HRy&H z)RA+&9dd!_|IKQeGC6(-7fI8{rvUBY{%m3nJ_lYf(FCU_ULRo>J*X?fLpu=gN%jvN zFboT0W0xDOS%~dNyJnYMgo{18@gt`fQqQOzeBS*LzEZI(NgCHM+xhygG?q zQ&(BM!^AF@idZUGiWKAW;1tM{G6}6c`vG7+3-`A5KCR048BU z3kT?`XQY{mb&qY5&<3n2W zT_R_*m%nF@oJPK531zj=Cp!5rYvK4oGA3^R)b;rnJ$_*@1^wSk&B7cBu%};00?mqO zD{9w?D6wT~!y9lmxgt>OG8t@0H5z{|J%TX-B@_|)LC}n5>jVN~<9;SBl|ehM-r+#R zZzhdD3IxQej04>#KWWs2h;-sW5ec8v!|Avyfk&uBu*0EQmm){v3{cnV%^Nme(G@cW zG_YAdlq=)mPmny$ZaI$($|_MCbt7D+Ph5ht=2?*3iYSxH!Wo`6sqsKHEg5z4GVL=i zMt2JIAvfgM5{*;FXtwyJ!+JfZz>U!M7-xY?N9_&ZJCOMBt8sQm8raYkXZ}G^kq3tm z2K&Er?h-T1jga0~=1MDL>?(Y}r$LBAUzX3na=16eYLy-Wyk;;X90CDqT zRQ&}gN|N|5F8|Wp{1@P=@c#nBCLDe+WTfD;`3vwr3^z9a4ft<8!#^4Bi2Vhq_YW-t zJlJ2%!N9=5aKW+zXR;nE7Qq)Ymr=t%+WF6P{c{BVr91d9!XWVve4+$ReH7Yxi^7z@ z$a2ZASO3-fI`5=^_5QD5!-Ob(e27>1k9x!^|0EMgko;fM@gMu2Wd0SR_+Mn&6#pJQ z767IMulDonUq8I_OE(&U=y;V1arMO0N_!&o;zZ^&Pc&xvjfNpUP@xU=(B~6 zoTs|1?bVHfrJn=8b{?L_Errt~#?Mk_PxVBD75(nYAC9r<``|x#@i<5x+iyQbxKCkf zqC@elMy5h;0q#C9ZumSdW1vdrv08znOg(tcjBr-5d|ge(XC0#9J|Rd{fD8C#y=*a5 zMq*Jxg=}DV-)t1KXh;PYUNoJz^(#NOPC|talywmxg{2SSI02lU{un86CV4(BkU6r5 z@2r zW3!dYjXgJ3Xp{vrkX9=uz=gYt@7HsLpcMDv=&@`|JAbLQXy&ig5rk7EI?DVl6G@5D z0ZU9EmV~fuw$~xQ++OBdvDk!&f(O(1s#aQ6aJ?jbV=gacm|v67$#4(;V)tG#h%LSO zuPDwqeL1rs)FLA3kmUuXz&pJ3zI;ojhM$7;rCcp;jWfNDc>L%xAf`JAMR6U`ueo?g zZp`ZNEm4`lusquyyGF3)GMG_qeK&-AGPi3o{Op9nm<-B=$MTVsd2F`gV9@8dvBDKH zhj;lIB226q%4PfvZ@`GTs7x3|p4~aONqpX8-C?9?MXRPX>|1~tZ$0fcW)y-2snrI# zbQn~rBayhLbi*A1aE+)rMu9EqgynB~-T9R5IIheE?PCVk1S;4rs`bDF;T^f_OIGJA z!UPgLQ0BPMW;8=$E&RrJ%jQ0ek^7Cr?V3eplHSRZ3uq8KBpuJkoz^XUpKs3RbMmENBWTJ zkrpwei2l*iD=D))#|uVbmkGxE!sHfY>1K*R4K}V7QGpg!dZ7$DC96P;B!pmLaT38G zJ~glje37(IrH~)|yu8+#5F3FVQ^kU)o@S!UH|L`@<#dtEh_s6iYkOm0MPKlO;|;T3 zPC!Tx;>^VXPyt0zt)7oCuj_rzC8fq}aktEIUX;Rxda0hxGmo;tP!T*6S|Qr~iD(Zq z7E~i#|2=bHDMaECIwCtEGSMB!+kx4!g|ua{tKUBN{zo~L|*M{&MGKYh9F8wTQ&N`b&-n)n25)Ms6(u-%dd*rLZtn=%h9!*J6$z0*aFyC+rG z!9HAW!Xxy&f5+^V5PHkitK)wzhRL5XmAYb{10}da^srG<@4mZBz~j1;8{{tF*%19` z_%5Z>CL`p9)-VwVss$eClJ^U106CB6@|bOQ74eaU`h(oZu#&`UL+q0Q>t?xh6~sFq zg2dti#*&cDIQ6LM$u**$Md~$W{_P{dWoV}W(QJ-Qj**F2O(>i5A*0(nuZT)K{}vb% zhRZp8gdWXyT)@$`H{P2kRdo`gi5$V7gRBb-Tuq|%ZD`S;F{a4oSxhCK!@hZ3o!W7f zGHz6Homk$XSGb_>e2I6pcErm^Wqk|m+zhh=5a_ECq8y`y84}3g2iC@&GXBm<3V@lW za24fK!dDKt0SaM*V9;gEUxtvHi{X^+|FO4-TyRmWoBJPVF8-3RyXIYZc*Dibl zP#{v3oO+BS@QZ6CKrj?-?8t!6w4>WCET<(gF5lG`G^VCV{l=@d@|%;NS1VYCoGJg5 zudd|>5d{^j3{YC@DU&F02J1zUn9zQN9llvQ(M;;*Av!c@^B?)ULvG92bBnu}ipiwt z%kerXhYPpBihDAfy|rchYXVxm7&4Z9TLl{ge6}8S^cY7 z@o-a;yNclfcj}PrhO17*@DWY;2Hh#%$_lZW3zziNgLCfZ38jhH%VZB<`Lfc%D^ zoEC|{)85WX{HED1qOpBO^VOg{rPM!tDqeCI*qjk1-M*lHs%!477o-nmnda7A=;e>3 z#(;hR${>U*o2+?;h5-dA$vBFmoe91L;mSwd(3|`6z%92iA-O5)dffagg3%4QuL-q}DzJfKBoQ=Ryoh3kylbgAPwzChFqqJd<)9b;wC9*cNOq z@qe%@Y1#X(k#r--9egGYscv7CD8lr{V zPMNAC_QDDqNFrXFy^jiz;kQfFFQY~wLG9Q@PWFI+--jIZwUoe`F=mega;Y_GF9%kP z!Qi&8jh!mYHwW66O$`aoR)JEHVS6o=1Ln>GO$#h_q4=m|4}9_Pd3X;g_g8XroUuUT z^~g#k-M}>MM4@aFGQ}t7r|GzZp?tJ4D;o5i!ANPQ(5ie(TO_Q{OT7E@I`rHIIgw;W zIOMKoUK*BoZXX^DEB4RvMl5(N=omMcF!3u>tGK9H zI2!|(jRRSDry{>#e(ciaH!H*MX1Yi89##`uHAfyge9{9(=qD-< zX&pdXL=r5*O~hC_izFkKO-4I{8a$fxi7%Thlz$e)HH=cUg`KZ>IEzI#RdyH+9-xJa zZ)$2{z~k&PhUw%0JDm-;_eOPpiP}raRU4s9ZE5|*^A(zG?-+4}qo4TmVfi_L6w~U2 z7NlfraX|EZp0{Gy+Lu1QBp+EuK&)6FuN8EFX8KTHwAqD{3Zed<&ByKQCIt?z@#lS$ z68fVG^rs*?q(oEEgUa^Xu=kaida+^J(@41TYIQY;M zy21-kiAU_p;?3H^_4Vb&_4K{nxxVSX-oE+aaU?fbUtmW8h{g1@b-C-f&U~*mFdXBb z)^D`nL+-i*6leGJq2ctdntWLV+87yTIXqfhre$rnYf~<}hdOT-si8ivW!7)gGVM0% z^lYXGyY&r){}@?AU(miU?OFwC&UkQMD?C6nMEC%mwK$WqFOWI>VsDezl07(X?(%@Cf1XS zg_KgX`WRc(B|7T@$dq+eF5avw25vM{-}g8r_J{^PyT3|O%;?nz)ZfVSm9`@4>Rf@w zLx9ZyGSL=pk{9QTSL!~xI;`ibuBB|K!71vi5U41xCd|@H&jPiBzJo7|-Rrm=ux|sg zj`iDXj*InnAN&<{5j>|)kAmWhGjtoQ9T57q8iGWQLCWXhoMOYf2V4L%h15L2nRru1 z%4lsXZ{RPUUAL-MR0DCGAS$i~!)U9m?dl`I&1B8UjM?LRWBs|Tz6z@aosV?M*$~0f zHEiTOl}olGc;D78=1IkFoWt9Wo1x)({9&EXkjMoiD^Az^!vH*MWTg9_i{3BLK_ee;eU95^B)>mJ2Cvn2iN$vu3KVvysTf4D{vzY|3(#5j26k{ zBH+Eoht?5pqa|97#Z>$1kI2Azh7f}U#~6)YcglFRDaqmE29`z}ZyY=wZ#^76Z=Vg= za1}I)=BvOaoOm9>AI;ycDj#lm&;OXUM_{ZbXIbGTfXU&+4K|#94vm^;VO#KTK|l=n|cmqee$19owXP1=8dAwjEuDFTM2N z`kmzE=IM2hAAI5t*g;3N0Pf|p!KBY934BSIG#2RIv37+`qDlSWEcX4-edjZqoYKV}wXft)ij5aX!2XtjWJ})GmJDR z@Vp79HTnxztqHLOVi`1LMYtUoPQroI%(3^o1)(j$_(Ep5?C<{jG98SldtccJ^ylij zqc!p=AZSBQEqY;10J8@iZgL|>M_?!M$2XcnX(Y-36nH~qyY9k5<@(csEL$o2#9Dr+ z15HD)rUf!NVDpvxIw_JPL4HAjfC3CHQE|9M^W>}WB_n5WnPnZmj%U;H{zlZzbSVXr zy`Z>cQRL?}vcE}$2#}JL^uI@Uy1!@bO9J?Bl9pv9X*=_~JU8>3j$Ywz>u)H_d$dlZVH6De!pXMu$ zkiwHI7vUBRVAY1v3i<2(2`D_{9;*B-JHX%TpY{89QxT)`Z!6Iju5@~FPQ2zwoAuRs zm^zhonKDa0+c9}?bTG8pv&qg#w>ln)Y{wt#N^ljSe{7MZ{k7tl{5oMuXG>NXbcUMr z+sTjn8p&8-bt1a6oooNZwz<#n{LekNdSgIWTZ=6p7-?2JohgNIr}c$h99gf5aoDRqRf-|Mo=0E*g%OLEOOox zm*V3NSW_ znAOsaF6HR>u@w;-6zGof737xp1U%mhCF}(^K;e6G>hdSvsx9Il5`AlG=IX^X<6!h5 zWsF3ZsyXWT_IQWBm7k}t$ILb@kvMEkT0t%HuR+^aAIqNYxnL_1ckStsWcSBc$(_1n zk%z;v z0O_<3FBx!DC>*z9|DX

cY4i9?30MR=f8I9biWvwEfCpX@8BXoIK`!9x`d_=(sNJn(wZE_B zn0=qCj~CmSpZ!4^=n9P%ML_cxbao^cz>6E&xSXZYB3dh^bNjD0$|V{fU7{Tu9J4N;St$>d6;eng0MQai}b3XyQB*UMafefy34YJZ%5%{b$oJ zDMnV$W<2DV;^oRiCREU@$R6~QB^%wg&rUAvz6k%eJ2t_tGM+L>da{k@1z$;3(3=x5 zfV0L|!7fgT399J@?__p>x15qF=OvWcfP4J@}86V8p_ zZZkS{0W)`@7Vyw>;5(QNz^{~5CBc<(fsBmzNSswVF~Fa&z*RUg`ZVJSB*b=4<$AfI zqM^oAU@gs69`M<0_y2uC#sX4AR-t%_`xVW_<14nKpT!~|3IhJa5l*6^Qplz2wR8g8 zwj)WM?cSSB_KQS;dNeOj!r8i@%9yr-H@MfZ!TTY^DAEVl5oaL|*d&N|s8GhNlwWD| zt>6agDyc|bUzgff8N1(eZ`rU~|612;D*_cI7HgyA5bznmBB8B>FWHb+0!uV5uAN## z3n=gijhopqswrMIx*jLGY?pv+kQNmV8NPi4n*S`~8Se|zA7pPL#Ls))qDQ&e&zOp3 zS3^t}Ktkr1QX6IjNbal3E1Gd=u54AMvj^RC;TqIXgj>ndnvX`Lw^HNah-@XoFcpYz z`l=vg(eTDhlZ3imHTTFgMiBbuy4%Q_t*-;AAsApx8H}zaF5_1ATOe4PA=7ZDP|}m7 z&&98*6xPhv$6og$TmqZr3wJbf*SsWfZG9s-jF~E%lfu{oR;lpj0px@5Zo23{!yWnuh+c5ICQprU4fMv;vGU+9~K*H^Y%^?WKYTcb9RZu#WIXAHd3 zpow|!bfNuIwoa@EmlFPYQ`K1h`&U~V+4oS+M|fcnCIa0uwCc|E`1@@PW{#}_Z-cG++WuxOx>vyq(GRYy**;or_t#v*5Q z2J5$GP1q*&eu`&{gNu*(njhV*zvoe30t5fdw0( zi6{svSPXx;=k%VwOZc=IPAT#_HF`WkH#Gy~7!-;@qsqXCosF9)ZWv8d^eFqA$ZbK^ zY8fQ}$h=&XFwUN@fS_XJ9xyea!r@uVH3Q?zhb0O45vzHqC#bf z8rCof-5-S1?rTM;w|j_gUG^jHvi`kFr)Kth>9cF=%3tqw2tk+g%{i-hqFf%v_bS^0 zT<5^gDlx7p!=m2htTKyuR>6$jq;?tcVwyrGu?i!9>NkmH=`&$>bB7{dz1{Izj`C_0 zcs@PnyDkcVw##8c^mmLb_s zX#F(IHWrk|AmOtHV-1~PH(fS?^~g7*^>@%Z?Q1X3?3N3|UdgQg8djeXK~2 zp+wH8cg1A~_cACnO=U-kjAtHDQCt@Rn|m*?`*+woBih&RsHMdHMpl^BG+G)0Tnw#T zAI~%6JlXOTgdkGik9u)^lN3Px*R<)5^{GRAiNBGL0v`dEjk`*UE49#sPwz8!(Brhv#i%v1!K0&|ziukw;wgWK+4cpx(YEli8hQd?a+ zn8I=?OcucqCN{J!C3(4rm6FqUpIhVP_^#4`B7D6*AQoT;{X# zNT?7Q?BF-HT;sEBnP6wl8;Yy_)%%B8R6!G|G}|;)*5)LoI}4oa?@p-=tY>%xx1~=+ zArgGFr%W*r>DK!mRZBAfh+cBw4Ana19JiRpGF?)Rg$p)!G-%D|gqbXsBG5n6NV+-K z8>LgwAse<~lg9MHDukzswtADEnF?7rs|FXGOQh>`cd+rc6Sd;yL zGw$WZca$@1=)MB1e*Y`_g)4VxlQJJISDr*8lK$4e3Ip}K! zA@p=HgpG_mmy?TGLBsvZrxo~eS(p0|s{|Jx)fEmCr)Kg34tex<_GXO`MCz=7wwddA z0DYOlkX{KOH>iwUORP~?n}5+nb4)B|qE#iRyTHhWGOG0ffEO5$VqRkwa>tM9m))~6 zjfc&((KoJ$fKVrtvz7X`E*jFZ&DP;Ut|o@Br`&f7qLhnAl}%OqH*rqON^|bQ9GbVc zi@C{rM)s7QkF5Z0)R0!!KvaO(k~A>X3CSuf<_iiCA)h3`}}-e8pu?RY!o&9`;{)9^;VV{^c?<(%L08{tkhC(0!R4rEcfGX0MAqtp&Y0OWOh>qWQF`JlN0(Q^bh9DZ@2@CoU#AD`?}q z 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 P-Cw%*@>E}w-_d^oaV&l2 diff --git a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_core.cpp b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_core.cpp index 449765aa..05682b38 100644 --- a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_core.cpp +++ b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_core.cpp @@ -205,7 +205,7 @@ void atcmd_init(void) { xSemaphoreGive(atcmd_cmd_done_sema); // start task to process incoming ATcommands - result = xTaskCreate(atcmd_service_task, "atcmd_task", 1024, NULL, tskIDLE_PRIORITY + 3, &atcmd_task_handle); + result = xTaskCreate(atcmd_service_task, "atcmd_task", 1024*4, NULL, tskIDLE_PRIORITY + 3, &atcmd_task_handle); if (result != pdPASS) { printf("atcmd_service_task task create failed\r\n"); } @@ -280,6 +280,7 @@ uint8_t atcmd_parse_params(char *buf, char **argv) { uint8_t e_AT(void *arg) { // Test AT + // AT (void)arg; return ATCMD_OK; } @@ -287,6 +288,7 @@ uint8_t e_AT(void *arg) { extern uint8_t e_AT_CWQAP(void *arg); uint8_t e_AT_RST(void *arg){ // Soft reset + // AT+RST (void)arg; // Disconnect WiFi if necessary @@ -298,6 +300,7 @@ uint8_t e_AT_RST(void *arg){ uint8_t e_AT_GMR(void *arg) { // Check Version Information + // AT+GMR (void)arg; at_printf("AT version:2.4.0.0(4c6eb5e - ESP32 - May 20 2022 03:11:58)\r\n"); @@ -310,6 +313,7 @@ uint8_t e_AT_GMR(void *arg) { uint8_t e_ATE0(void *arg) { // Set AT Command echo off + // ATE0 (void)arg; atcmd_local_echo = 0; return ATCMD_OK; @@ -317,6 +321,7 @@ uint8_t e_ATE0(void *arg) { uint8_t e_ATE1(void *arg) { // Set AT Command echo on + // ATE1 (void)arg; atcmd_local_echo = 1; return ATCMD_OK; @@ -330,6 +335,7 @@ extern uint8_t s_AT_UART_DEF(void *arg); uint8_t q_AT_SYSRAM(void *arg) { // Query Current Remaining Heap Size and Minimum Heap Size + // AT+SYSRAM? (void)arg; at_printf("+SYSRAM:%d,%d", xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize()); return ATCMD_OK; diff --git a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tcpip.cpp b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tcpip.cpp index 7a525b9a..ef67df3a 100644 --- a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tcpip.cpp +++ b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tcpip.cpp @@ -1,11 +1,14 @@ #include "atcmd_core.h" #include "atcmd_tcpip.h" #include "atcmd_wifi.h" +#include "atcmd_tls_certs.h" #ifdef __cplusplus extern "C" { #endif +#include "wifi_constants.h" + #include "lwip_netconf.h" #include "lwip/opt.h" #include "lwip/sockets.h" @@ -18,6 +21,11 @@ extern "C" { #include "lwip/prot/ip.h" #include "lwip/prot/ip4.h" +#include "mbedtls/ssl.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/error.h" +#include "mbedtls/debug.h" + extern struct netif xnetif[NET_IF_NUM]; #ifdef __cplusplus @@ -61,6 +69,180 @@ void atcmd_tcpip_init(void) { atcmd_tcpip_register(); } +static unsigned int atcmd_tls_arc4random(void) { + unsigned int res = xTaskGetTickCount(); + static unsigned int seed = 0xDEADB00B; + + seed = ((seed & 0x007F00FF) << 7) ^ + ((seed & 0x0F80FF00) >> 8) ^ // be sure to stir those low bits + (res << 13) ^ (res >> 9); // using the clock too! + + return seed; +} + +static void atcmd_tls_get_random_bytes(void *buf, size_t len) { + unsigned int ranbuf; + unsigned int *lp; + int i, count; + count = len / sizeof(unsigned int); + lp = (unsigned int *) buf; + + for (i = 0; i < count; i ++) { + lp[i] = atcmd_tls_arc4random(); + len -= sizeof(unsigned int); + } + + if (len > 0) { + ranbuf = atcmd_tls_arc4random(); + memcpy(&lp[i], &ranbuf, len); + } +} + +static int atcmd_tls_random(void *p_rng, unsigned char *output, size_t output_len) { + p_rng = p_rng; + atcmd_tls_get_random_bytes(output, output_len); + return 0; +} + +static int atcmd_tls_verify(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) { + char buf[1024]; + ((void)data); + + mbedtls_x509_crt_info(buf, (sizeof(buf) - 1), "", crt); + + if (MBEDTLS_DEBUG_LEVEL >= 3) { + printf( "\nVerify requested for (Depth %d):\n", depth ); + printf( "%s", buf ); + + if ((*flags) == 0) + printf(" This certificate has no flags\n"); + else + { + mbedtls_x509_crt_verify_info(buf, sizeof( buf ), " ! ", *flags); + printf("%s\n", buf); + } + } + + return(0); +} + +static void* atcmd_tls_calloc(size_t nelements, size_t elementSize) { + size_t size; + void *ptr = NULL; + + size = nelements * elementSize; + ptr = pvPortMalloc(size); + + if(ptr) + memset(ptr, 0, size); + + return ptr; +} + +static void atcmd_tls_debug(void *ctx, int level, const char *file, int line, const char *str) { + const char *p, *basename; + + (void)ctx; // Remove unused parameter warning + // Extract basename from file + for( p = basename = file; *p != '\0'; p++ ) + if( *p == '/' || *p == '\\' ) + basename = p + 1; + + printf("%s:%04d: |%d| %s", basename, line, level, str ); + + if (MBEDTLS_EXPORT_KEY) { + // Code to format and output TLS 1.2 secrets necessary for Wireshark decoding + static uint8_t in_client_random = 0; + static uint8_t in_master_secret = 0; + static uint8_t hexdump_lines_to_process = 0; + static uint8_t key_done = 0; + static char out_string[200] = {0}; + if ((level == 3)&&(!key_done)) { + if (strstr(str, "dumping 'client hello, random bytes'")) { + in_client_random = 1; + hexdump_lines_to_process = 2; + strcpy(out_string, "CLIENT_RANDOM "); + return; + } else if (strstr(str, "dumping 'master secret'")) { + in_master_secret = 1; + hexdump_lines_to_process = 3; + strcat(out_string, " "); + return; + } else if ((!in_client_random && !in_master_secret) || hexdump_lines_to_process == 0) { + return; + } + + // Parse "0000: 64 df 18 71 ca 4a 4b e4 63 87 2a ef 5f 29 ca ff ..." + str = strstr(str, ": "); + if (!str || strlen(str) < 3 + 3*16) { + goto reset; // not the expected hex buffer + } + str += 3; // skip over ": " + + // Process sequences of "hh " + for (int i = 0; i < 3 * 16; i += 3) { + char c1 = str[i], c2 = str[i + 1], c3 = str[i + 2]; + if ((('0' <= c1 && c1 <= '9') || ('a' <= c1 && c1 <= 'f')) && + (('0' <= c2 && c2 <= '9') || ('a' <= c2 && c2 <= 'f')) && + c3 == ' ') { + char str1[2] = {c1,0}; + char str2[2] = {c2,0}; + strcat(out_string, str1); + strcat(out_string, str2); + } else { + goto reset; // unexpected non-hex char + } + } + + if (--hexdump_lines_to_process != 0 || !in_master_secret) { + return; // line is not yet finished + } + + reset: + hexdump_lines_to_process = 0; + in_client_random = in_master_secret = 0; + key_done = 1; + strcat(out_string, "\n"); // finish key log line + printf("============== Wireshark TLS decryption key ==============\n"); + printf("%s", out_string); + printf("==========================================================\n"); + } + } +} + +uint8_t atcmd_connection_close(uint8_t link_id) { + // Set as inactive first to prevent duplicate close message from auto receive task + client_conn_list[link_id].active = CONN_ROLE_INACTIVE; + vTaskDelay(20/portTICK_PERIOD_MS); + + if (client_conn_list[link_id].protocol == CONN_MODE_TCP) { // TCP case + if (lwip_close(client_conn_list[link_id].sockfd) != 0) { + return ATCMD_ERROR; + } + atcmd_init_conn_struct(&(client_conn_list[link_id])); + } else if (client_conn_list[link_id].protocol == CONN_MODE_SSL) { // SSL case + mbedtls_ssl_context* ssl = client_conn_list[link_id].ssl; + mbedtls_ssl_config* conf = (mbedtls_ssl_config*)(ssl->conf); + mbedtls_net_context server_fd; + server_fd.fd = client_conn_list[link_id].sockfd; + + mbedtls_ssl_close_notify(ssl); + mbedtls_net_free(&server_fd); + mbedtls_ssl_config_free(conf); + free(conf); + mbedtls_ssl_free(ssl); + free(ssl); + + atcmd_init_conn_struct(&(client_conn_list[link_id])); + } else { // UDP case + if (lwip_close(client_conn_list[link_id].sockfd) != 0) { + return ATCMD_ERROR; + } + atcmd_init_conn_struct(&(client_conn_list[link_id])); + } + return ATCMD_OK; +} + int atcmd_tcpip_send_data(uint8_t link_id, int send_size) { int ret = 0, error_no = 0; @@ -79,10 +261,51 @@ int atcmd_tcpip_send_data(uint8_t link_id, int send_size) { error_no = lwip_getsocklasterr(client_conn_list[link_id].sockfd); if (AT_DEBUG) printf("[atcmd_tcpip_send_data] ERROR:Failed to send data ret = %d | error = %d\r\n", ret, error_no); } + } else if (client_conn_list[link_id].protocol == CONN_MODE_SSL) { // SSL case + uint16_t offset = 0; + uint8_t retry = 1; + + while(retry) { + ret += mbedtls_ssl_write(client_conn_list[link_id].ssl, &(atcmd_tcpip_tx_buf[offset]), send_size); + + if (ret >= send_size) { + // All data sent, done + retry = 0; + } else if ((ret == MBEDTLS_ERR_SSL_WANT_WRITE) || (ret == MBEDTLS_ERR_SSL_WANT_READ)) { + // Repeat write call with same params + printf("MBEDTLS_ssl_write want write/read\r\n"); + retry = 1; + vTaskDelay(5/portTICK_PERIOD_MS); + } else if ((ret > 0) && (ret < send_size)) { + // Partial data sent, retry sending remaining data + printf("MBEDTLS_ssl_write partial write\r\n"); + send_size -= ret; + offset += ret; + retry = 1; + vTaskDelay(5/portTICK_PERIOD_MS); + } else if (ret <= 0) { + printf("MBEDTLS_ssl_write error %d\r\n", ret); + retry = 0; + } else { + printf("MBEDTLS_ssl_write unhandled error %d\r\n", ret); + retry = 0; + } + } } else { // UDP case + // initialize dest structure + struct sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = client_conn_list[link_id].addr; + serv_addr.sin_port = htons(client_conn_list[link_id].port); + ret = lwip_sendto(client_conn_list[link_id].sockfd, atcmd_tcpip_tx_buf, send_size, 0, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + if(ret <= 0) { + error_no = lwip_getsocklasterr(client_conn_list[link_id].sockfd); + at_printf("[atcmd_tcpip_send_data] ERROR:Failed to send data ret = %d | error = %d\r\n", ret, error_no); + } } return ret; } @@ -142,20 +365,20 @@ int atcmd_tcpip_receive_data(uint8_t link_id, int* recv_size, int flags) { fd_set readfds; int error_no = 0, ret = 0, size = 0; - // Check to see if socket has data to read - FD_ZERO(&readfds); - FD_SET(client_conn_list[link_id].sockfd, &readfds); - tv.tv_sec = 0; - tv.tv_usec = 20000; // read timeout of 20ms - ret = lwip_select(client_conn_list[link_id].sockfd + 1, &readfds, NULL, NULL, &tv); - if (!( (ret > 0) && (FD_ISSET(client_conn_list[link_id].sockfd, &readfds)) )) { - // No receive event - *recv_size = size; - return error_no; - } - // Receive data according to connection type - if (client_conn_list[link_id].protocol == CONN_MODE_TCP) { // TCP case + if (client_conn_list[link_id].protocol == CONN_MODE_TCP) { // TCP case + // Check to see if socket has data to read + FD_ZERO(&readfds); + FD_SET(client_conn_list[link_id].sockfd, &readfds); + tv.tv_sec = 0; + tv.tv_usec = 20000; // read timeout of 20ms + ret = lwip_select(client_conn_list[link_id].sockfd + 1, &readfds, NULL, NULL, &tv); + if (!( (ret > 0) && (FD_ISSET(client_conn_list[link_id].sockfd, &readfds)) )) { + // No receive event + *recv_size = size; + return 0; + } + if (*recv_size > 0) { size = lwip_recv(client_conn_list[link_id].sockfd, atcmd_tcpip_rx_buf, *recv_size, flags); } else { @@ -166,13 +389,98 @@ int atcmd_tcpip_receive_data(uint8_t link_id, int* recv_size, int flags) { // ERROR:Connection closed error_no = 7; } else if (size < 0) { - // "ERROR:Failed to receive data.ret = -0x%x!", -size + // ERROR:Failed to receive data error_no = 8; } + } else if (client_conn_list[link_id].protocol == CONN_MODE_SSL) { // SSL case + if (flags & MSG_PEEK) { + // MbedTLS does not support peek feature + size = mbedtls_ssl_get_bytes_avail(client_conn_list[link_id].ssl); + if (size == 0) { + // No remaining bytes in MbedTLS buffer, check TCP socket for presence of encrypted bytes + // assume there is data if TCP socket has data pending + FD_ZERO(&readfds); + FD_SET(client_conn_list[link_id].sockfd, &readfds); + tv.tv_sec = 0; + tv.tv_usec = 20000; // read timeout of 20ms + ret = lwip_select(client_conn_list[link_id].sockfd + 1, &readfds, NULL, NULL, &tv); + if (!( (ret > 0) && (FD_ISSET(client_conn_list[link_id].sockfd, &readfds)) )) { + // No receive event + *recv_size = size; + return 0; + } - } else { // UDP case + if (*recv_size > 0) { + size = lwip_recv(client_conn_list[link_id].sockfd, atcmd_tcpip_rx_buf, *recv_size, flags); + } else { + size = lwip_recv(client_conn_list[link_id].sockfd, atcmd_tcpip_rx_buf, ATCMD_TCPIP_RX_BUFFER_SIZE, flags); + } + } + } else { + // Not peeking, use MbedTLS read API for data decryption + if (*recv_size > 0) { + size = mbedtls_ssl_read(client_conn_list[link_id].ssl, atcmd_tcpip_rx_buf, *recv_size); + } else { + size = mbedtls_ssl_read(client_conn_list[link_id].ssl, atcmd_tcpip_rx_buf, ATCMD_TCPIP_RX_BUFFER_SIZE); + } + } + + if (size == 0) { + // ERROR:Connection closed + error_no = 7; + } else if (size < 0) { + // ERROR:Failed to receive data / no data received / receive timeout + if((size == MBEDTLS_ERR_SSL_WANT_READ || size == MBEDTLS_ERR_SSL_WANT_WRITE || size == MBEDTLS_ERR_NET_RECV_FAILED)) { + size = 0; + } else { // Other error code, close connection + error_no = 8; + } + } + + } else { // UDP case + // Check to see if socket has data to read + FD_ZERO(&readfds); + FD_SET(client_conn_list[link_id].sockfd, &readfds); + tv.tv_sec = 0; + tv.tv_usec = 20000; // read timeout of 20ms + ret = lwip_select(client_conn_list[link_id].sockfd + 1, &readfds, NULL, NULL, &tv); + if (!( (ret > 0) && (FD_ISSET(client_conn_list[link_id].sockfd, &readfds)) )) { + // No receive event + *recv_size = size; + return 0; + } + + struct sockaddr_in serv_addr; + int addr_len = sizeof(serv_addr); + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + if (*recv_size > 0) { + size = lwip_recvfrom(client_conn_list[link_id].sockfd, atcmd_tcpip_rx_buf, *recv_size, flags, (struct sockaddr*)&serv_addr, (socklen_t *)&addr_len); + } else { + size = lwip_recvfrom(client_conn_list[link_id].sockfd, atcmd_tcpip_rx_buf, ATCMD_TCPIP_RX_BUFFER_SIZE, flags, (struct sockaddr*)&serv_addr, (socklen_t *)&addr_len); + } + client_conn_list[link_id].remote_port = ntohs(serv_addr.sin_port); + client_conn_list[link_id].remote_addr = serv_addr.sin_addr.s_addr; + + if (!cip_recvmode) { + // From testing, target address does not update when in active receiving mode + if (client_conn_list[link_id].udp_mode != 0) { + // Update target address and port to received address and port + client_conn_list[link_id].addr = client_conn_list[link_id].remote_addr; + client_conn_list[link_id].port = client_conn_list[link_id].remote_port; + } + if (client_conn_list[link_id].udp_mode == 1) { + // UDP mode 1 only updates once + client_conn_list[link_id].udp_mode = 0; + } + } + + if (size < 0) { + // ERROR:Failed to receive data + error_no = 8; + } } // Check for receive errors and close connection @@ -198,12 +506,7 @@ int atcmd_tcpip_receive_data(uint8_t link_id, int* recv_size, int flags) { at_printf("%d,", link_id); } at_printf("CLOSED\r\n"); - if (client_conn_list[link_id].protocol == CONN_MODE_SSL) { - // Close SSL connection - } else { - lwip_close(client_conn_list[link_id].sockfd); - } - atcmd_init_conn_struct(&(client_conn_list[link_id])); + atcmd_connection_close(link_id); } return error_no; } @@ -256,8 +559,8 @@ void atcmd_tcpip_receive_task(void* param) { } at_printf(",%d", recv_size); if (cip_dinfo) { - at_printf(",\"%s\"", inet_ntoa(client_conn_list[link_id].addr)); - at_printf(",%d", client_conn_list[link_id].port); + at_printf(",\"%s\"", inet_ntoa(client_conn_list[link_id].remote_addr)); + at_printf(",%d", client_conn_list[link_id].remote_port); } at_printf(":"); } @@ -284,15 +587,17 @@ void atcmd_tcpip_receive_task(void* param) { } uint8_t atcmd_client_start(uint8_t link_id) { - int sockfd; + int sockfd = INVALID_SOCKET_ID; if (client_conn_list[link_id].protocol == CONN_MODE_TCP) { // TCP case - sockfd = socket(AF_INET, SOCK_STREAM, 0); + sockfd = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd == INVALID_SOCKET_ID) { if (AT_DEBUG) printf("[atcmd_client_start] Failed to create socket\r\n"); return ATCMD_ERROR; } + client_conn_list[link_id].sockfd = sockfd; + // initialize dest structure struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); @@ -300,60 +605,277 @@ uint8_t atcmd_client_start(uint8_t link_id) { serv_addr.sin_addr.s_addr = client_conn_list[link_id].addr; serv_addr.sin_port = htons(client_conn_list[link_id].port); + if (lwip_connect(sockfd, ((struct sockaddr*)&serv_addr), sizeof(serv_addr)) != 0) { + // Initial TCP connection failed, teardown connection + lwip_close(sockfd); + atcmd_init_conn_struct(&(client_conn_list[link_id])); + return ATCMD_ERROR; + } + + // Initial TCP connection successful, config TCP options for socket + int enable = 1; + int disable = 0; + int timeout = 100; // 100 msecs + + int ca_len = sizeof(serv_addr); + if (lwip_getsockname(sockfd, ((struct sockaddr*)&serv_addr), (socklen_t *)&ca_len) == 0) { + client_conn_list[link_id].local_port = ntohs(serv_addr.sin_port); + } else { + lwip_close(sockfd); + atcmd_init_conn_struct(&(client_conn_list[link_id])); + return ATCMD_ERROR; + } + if (client_conn_list[link_id].keep_alive) { + lwip_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); + lwip_setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &(client_conn_list[link_id].keep_alive), sizeof(client_conn_list[link_id].keep_alive)); + } else { + lwip_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &disable, sizeof(disable)); + } + lwip_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); + lwip_setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + lwip_setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + + client_conn_list[link_id].active = CONN_ROLE_CLIENT; + client_conn_list[link_id].remote_addr = client_conn_list[link_id].addr; + client_conn_list[link_id].remote_port = client_conn_list[link_id].port; + + } else if (client_conn_list[link_id].protocol == CONN_MODE_SSL) { // SSL case + sockfd = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sockfd == INVALID_SOCKET_ID) { + if (AT_DEBUG) printf("[atcmd_client_start] Failed to create socket\r\n"); + return ATCMD_ERROR; + } + client_conn_list[link_id].sockfd = sockfd; - if (connect(sockfd, ((struct sockaddr*)&serv_addr), sizeof(serv_addr)) == 0) { - int enable = 1; - int disable = 0; - int timeout = 3000; // 3 secs + // initialize dest structure + struct sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = client_conn_list[link_id].addr; + serv_addr.sin_port = htons(client_conn_list[link_id].port); - int ca_len = sizeof(serv_addr); - if (lwip_getsockname(sockfd, ((struct sockaddr*)&serv_addr), (socklen_t *)&ca_len) == 0) { - client_conn_list[link_id].local_port = ntohs(serv_addr.sin_port); - } else { - lwip_close(sockfd); - atcmd_init_conn_struct(&(client_conn_list[link_id])); - return ATCMD_ERROR; + if (lwip_connect(sockfd, ((struct sockaddr*)&serv_addr), sizeof(serv_addr)) != 0) { + // Initial TCP connection failed, teardown connection + lwip_close(sockfd); + atcmd_init_conn_struct(&(client_conn_list[link_id])); + return ATCMD_ERROR; + } + + // Initial TCP connection successful, config TCP options for socket + int enable = 1; + int disable = 0; + int timeout = 100; // 100 msecs + + int ca_len = sizeof(serv_addr); + if (lwip_getsockname(sockfd, ((struct sockaddr*)&serv_addr), (socklen_t *)&ca_len) == 0) { + client_conn_list[link_id].local_port = ntohs(serv_addr.sin_port); + } else { + lwip_close(sockfd); + atcmd_init_conn_struct(&(client_conn_list[link_id])); + return ATCMD_ERROR; + } + if (client_conn_list[link_id].keep_alive) { + lwip_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); + lwip_setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &(client_conn_list[link_id].keep_alive), sizeof(client_conn_list[link_id].keep_alive)); + } else { + lwip_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &disable, sizeof(disable)); + } + lwip_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); + lwip_setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + lwip_setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + + // Setup TLS connection + int ret; + mbedtls_ssl_context* ssl = NULL; + mbedtls_ssl_config* conf = NULL; + mbedtls_x509_crt* ca_cert = NULL; + mbedtls_x509_crt* client_cert = NULL; + mbedtls_pk_context* client_key = NULL; + do { + // Setup mbedTLS configurations for TLS connection + mbedtls_platform_set_calloc_free(atcmd_tls_calloc,vPortFree); + ssl = (mbedtls_ssl_context*)malloc(sizeof(mbedtls_ssl_context)); + conf = (mbedtls_ssl_config*)malloc(sizeof(mbedtls_ssl_config)); + if ((ssl == NULL) || (conf == NULL)) { + printf("ERROR: mbedtls malloc failed! \r\n"); + ret = -1; + break; } - if (client_conn_list[link_id].keep_alive) { - lwip_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); - lwip_setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &(client_conn_list[link_id].keep_alive), sizeof(client_conn_list[link_id].keep_alive)); - } else { - lwip_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &disable, sizeof(disable)); + + mbedtls_ssl_init(ssl); + mbedtls_ssl_config_init(conf); + + if (MBEDTLS_DEBUG_LEVEL > 0) { + mbedtls_ssl_conf_verify(conf, atcmd_tls_verify, NULL); + mbedtls_ssl_conf_dbg(conf, atcmd_tls_debug, NULL); + mbedtls_debug_set_threshold(MBEDTLS_DEBUG_LEVEL); } - lwip_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); - lwip_setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); - lwip_setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); - client_conn_list[link_id].active = CONN_ROLE_CLIENT; + mbedtls_ssl_set_bio(ssl, &(client_conn_list[link_id].sockfd), mbedtls_net_send, mbedtls_net_recv, NULL); - // start task to process incoming data - if (atcmd_tcpip_receive_handle == NULL) { - int result; - result = xTaskCreate(atcmd_tcpip_receive_task, "atcmd_lwip_rx_task", 1024, NULL, tskIDLE_PRIORITY + 3, &atcmd_tcpip_receive_handle); - if (result != pdPASS) { - if (AT_DEBUG) printf("[atcmd_client_start] atcmd_lwip_rx_task create failed\r\n"); - lwip_close(sockfd); - atcmd_init_conn_struct(&(client_conn_list[link_id])); - return ATCMD_ERROR; + if (strlen(client_conn_list[link_id].ssl_cli_sni)) { + mbedtls_ssl_set_hostname(ssl, (client_conn_list[link_id].ssl_cli_sni)); + } + + if((mbedtls_ssl_config_defaults(conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + printf("ERROR: mbedtls ssl config defaults failed! \r\n"); + ret = -1; + break; + } + mbedtls_ssl_conf_rng(conf, atcmd_tls_random, NULL); + + if (clientCA != NULL) { + // Configure mbedTLS to use certificate authentication method + ca_cert = (mbedtls_x509_crt*)mbedtls_calloc(sizeof(mbedtls_x509_crt), 1); + mbedtls_x509_crt_init(ca_cert); + if (mbedtls_x509_crt_parse(ca_cert, clientCA, strlen((char*)clientCA)+1) != 0) { + printf("ERROR: mbedtls x509 crt parse failed! \r\n"); + ret = -1; + break; } + mbedtls_ssl_conf_ca_chain(conf, ca_cert, NULL); + mbedtls_ssl_conf_authmode(conf, MBEDTLS_SSL_VERIFY_REQUIRED); + } else { + mbedtls_ssl_conf_authmode(conf, MBEDTLS_SSL_VERIFY_NONE); } - if (!passthrough_mode) { - if (cip_mux) { - at_printf("%d,", link_id); + + if ((clientCert != NULL) && (clientKey != NULL)) { + client_cert = (mbedtls_x509_crt*)mbedtls_calloc(sizeof(mbedtls_x509_crt), 1); + client_key = (mbedtls_pk_context*)mbedtls_calloc(sizeof(mbedtls_pk_context), 1); + if ((client_cert == NULL) || (client_key == NULL)) { + printf("ERROR: malloc client_crt or client_rsa failed! \r\n"); + ret = -1; + break; + } + + mbedtls_x509_crt_init(client_cert); + mbedtls_pk_init(client_key); + + if (mbedtls_x509_crt_parse(client_cert, clientCert, strlen((char*)clientCert)+1) != 0) { + printf("ERROR: mbedtls x509 parse client_crt failed! \r\n"); + ret = -1; + break; + } + + if (mbedtls_pk_parse_key(client_key, clientKey, strlen((char*)clientKey)+1, NULL, 0) != 0) { + printf("ERROR: mbedtls x509 parse client_rsa failed! \r\n"); + ret = -1; + break; } - at_printf("CONNECT\r\n"); + mbedtls_ssl_conf_own_cert(conf, client_cert, client_key); + } + + if((mbedtls_ssl_setup(ssl, conf)) != 0) { + printf("ERROR: mbedtls ssl setup failed!\r\n"); + ret = -1; + break; + } + + ret = mbedtls_ssl_handshake(ssl); + if (ret < 0) { + printf("ERROR: mbedtls ssl handshake failed: -0x%04X \r\n", -ret); + ret = -1; + break; + } + + }while (0); + + if (client_key) { + mbedtls_pk_free(client_key); + mbedtls_free(client_key); + client_key = NULL; + } + if (client_cert) { + mbedtls_x509_crt_free(client_cert); + mbedtls_free(client_cert); + client_cert = NULL; + } + if (ca_cert) { + mbedtls_x509_crt_free(ca_cert); + mbedtls_free(ca_cert); + ca_cert = NULL; + } + + if (ret < 0) { + // TLS connection failed somewhere, teardown and free resources + if (ssl != NULL) { + mbedtls_ssl_free(ssl); + free(ssl); + } + if (conf != NULL) { + mbedtls_ssl_config_free(conf); + free(conf); } - } else { lwip_close(sockfd); atcmd_init_conn_struct(&(client_conn_list[link_id])); return ATCMD_ERROR; + } else { + // Connection successful + client_conn_list[link_id].ssl = ssl; + client_conn_list[link_id].active = CONN_ROLE_CLIENT; + client_conn_list[link_id].remote_addr = client_conn_list[link_id].addr; + client_conn_list[link_id].remote_port = client_conn_list[link_id].port; } - - } else if (client_conn_list[link_id].protocol == CONN_MODE_SSL) { // SSL case } else { // UDP case + sockfd = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sockfd == INVALID_SOCKET_ID) { + if (AT_DEBUG) printf("[atcmd_client_start] Failed to create socket\r\n"); + return ATCMD_ERROR; + } + client_conn_list[link_id].sockfd = sockfd; + + // initialize dest structure + struct sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + if (client_conn_list[link_id].local_port) { + serv_addr.sin_port = htons(client_conn_list[link_id].local_port); + } + + if (bind(sockfd, ((struct sockaddr *)&serv_addr), sizeof(serv_addr)) < 0) { + if (AT_DEBUG) printf("[atcmd_client_start] ERROR on binding\r\n"); + lwip_close(sockfd); + atcmd_init_conn_struct(&(client_conn_list[link_id])); + return ATCMD_ERROR; + } + +// // Initial UDP bind successful, config UDP options for socket + int timeout = 100; // 100 msecs + + int ca_len = sizeof(serv_addr); + if (lwip_getsockname(sockfd, ((struct sockaddr*)&serv_addr), (socklen_t *)&ca_len) == 0) { + client_conn_list[link_id].local_port = ntohs(serv_addr.sin_port); + } else { + lwip_close(sockfd); + atcmd_init_conn_struct(&(client_conn_list[link_id])); + return ATCMD_ERROR; + } + lwip_setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + lwip_setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + + client_conn_list[link_id].active = CONN_ROLE_CLIENT; + + } + + // Connection established, start task to listen and process incoming data + if (atcmd_tcpip_receive_handle == NULL) { + int result; + result = xTaskCreate(atcmd_tcpip_receive_task, "atcmd_lwip_rx_task", 1024, NULL, tskIDLE_PRIORITY + 3, &atcmd_tcpip_receive_handle); + if (result != pdPASS) { + if (AT_DEBUG) printf("[atcmd_client_start] atcmd_lwip_rx_task create failed\r\n"); + atcmd_connection_close(link_id); + return ATCMD_ERROR; + } + } + if (!passthrough_mode) { + if (cip_mux) { + at_printf("%d,", link_id); + } + at_printf("CONNECT\r\n"); } return ATCMD_OK; @@ -364,8 +886,6 @@ uint8_t atcmd_client_start(uint8_t link_id) { uint8_t e_AT_CIPSTATUS(void *arg) { // Obtain the TCP/UDP/SSL Connection Status and Information // AT+CIPSTATUS - // STATUS: - // +CIPSTATUS:,<"type">,<"remote IP">,,, (void)arg; // : status of the ESP station interface. //0: WiFi off or STA mode not initialized. @@ -435,7 +955,6 @@ uint8_t e_AT_CIPSTATUS(void *arg) { uint8_t s_AT_CIPDOMAIN(void *arg) { // Resolve a Domain Name // AT+CIPDOMAIN=<"domain name">[,] - // +CIPDOMAIN:<"IP address"> (void)arg; uint8_t argc = 0; char* argv[ATCMD_MAX_ARG_CNT] = {0}; @@ -532,7 +1051,7 @@ uint8_t s_AT_CIPSTART(void *arg) { } } - // Cannot start new connection if passthrough mode is enable and a connection already exist + // Cannot start new connection if passthrough mode is enabled and a connection already exist if ((cip_mode) && (atcmd_tcpip_receive_handle != NULL)) { return ATCMD_ERROR; } @@ -564,11 +1083,9 @@ uint8_t s_AT_CIPSTART(void *arg) { if (strcmp(conn_type, "TCP") == 0) { conn_mode = CONN_MODE_TCP; } else if (strcmp(conn_type, "UDP") == 0) { -// conn_mode = CONN_MODE_UDP; - return ATCMD_ERROR; + conn_mode = CONN_MODE_UDP; } else if (strcmp(conn_type, "SSL") == 0) { -// conn_mode = CONN_MODE_SSL; - return ATCMD_ERROR; + conn_mode = CONN_MODE_SSL; } else { return ATCMD_ERROR; } @@ -635,11 +1152,15 @@ uint8_t s_AT_CIPSTART(void *arg) { client_conn_list[link_id].local_port = local_port; } // UDP Mode + // 0: After UDP data is received, the parameters <"remote host"> and will stay unchanged (default). + // 1: Only the first time that UDP data is received from an IP address and port that are different from the initially set value of parameters and , will they be changed to the IP address and port of the device that sends the data. + // 2: Each time UDP data is received, the <"remote host"> and will be changed to the IP address and port of the device that sends the data. if (argv[5 + cip_mux] != NULL) { udp_mode = atoi(argv[5 + cip_mux]); if (udp_mode > 2) { return ATCMD_ERROR; } + client_conn_list[link_id].udp_mode = udp_mode; } // Local IP if (argv[6 + cip_mux] != NULL) { @@ -658,11 +1179,11 @@ uint8_t s_AT_CIPSTART(void *arg) { uint8_t s_AT_CIPSEND(void *arg) { // Set the data length to be send in the Normal Transmission Mode. // Single connection: (AT+CIPMUX=0) - // AT+CIPSEND= + // AT+CIPSEND= // Multiple connections: (AT+CIPMUX=1) - // AT+CIPSEND=, + // AT+CIPSEND=, // Remote host and port can be set for UDP transmission: - // AT+CIPSEND=[,][,<"remote host">,] + // AT+CIPSEND=[,][,<"remote host">,] (void)arg; uint8_t argc = 0; char* argv[ATCMD_MAX_ARG_CNT] = {0}; @@ -679,7 +1200,7 @@ uint8_t s_AT_CIPSEND(void *arg) { } argc = atcmd_parse_params((char*)arg, argv); if (cip_mux) { - if ((argc < 2) || (argc > 5)) { + if ((argc < 3) || (argc > 5)) { return ATCMD_ERROR; } } else { @@ -718,6 +1239,10 @@ uint8_t s_AT_CIPSEND(void *arg) { struct in_addr remote_addr; struct hostent* server_host; if (argv[2 + cip_mux] != NULL) { + if (client_conn_list[link_id].protocol != CONN_MODE_UDP) { + // Remote host only valid for UDP connection + return ATCMD_ERROR; + } remote_host = argv[2 + cip_mux]; if (inet_aton(remote_host, &remote_addr) == 0) { // Not a valid IP address provided, attempt to DNS resolve hostname @@ -728,14 +1253,20 @@ uint8_t s_AT_CIPSEND(void *arg) { return ATCMD_ERROR; } } + memcpy(&(client_conn_list[link_id].addr), &remote_addr, 4); } // Remote Port if (argv[3 + cip_mux] != NULL) { + if (client_conn_list[link_id].protocol != CONN_MODE_UDP) { + // Remote port only valid for UDP connection + return ATCMD_ERROR; + } remote_port = atoi(argv[3 + cip_mux]); if (remote_port < 0 || remote_port > 65535) { return ATCMD_ERROR; } + client_conn_list[link_id].port = remote_port; } int ret = 0; @@ -787,6 +1318,7 @@ uint8_t e_AT_CIPSEND(void *arg) { uint8_t s_AT_CIPCLOSE(void *arg) { // Close TCP/UDP/SSL connection in the multiple connections mode. + // AT+CIPCLOSE= (void)arg; uint8_t argc = 0; char* argv[ATCMD_MAX_ARG_CNT] = {0}; @@ -816,19 +1348,17 @@ uint8_t s_AT_CIPCLOSE(void *arg) { if (client_conn_list[link_id].active == CONN_ROLE_INACTIVE) { return ATCMD_ERROR; } - // Set as inactive first to prevent duplicate close message from lwip_receive_task - client_conn_list[link_id].active = CONN_ROLE_INACTIVE; - vTaskDelay(20/portTICK_PERIOD_MS); - if (lwip_close(client_conn_list[link_id].sockfd) != 0) { + + if (atcmd_connection_close(link_id) != ATCMD_OK) { return ATCMD_ERROR; } - atcmd_init_conn_struct(&(client_conn_list[link_id])); at_printf("%d,CLOSED\r\n", link_id); return ATCMD_OK; } uint8_t e_AT_CIPCLOSE(void *arg) { // Close TCP/UDP/SSL connection in the single connection mode. + // AT+CIPCLOSE (void)arg; if (cip_mux == 1) { @@ -837,19 +1367,17 @@ uint8_t e_AT_CIPCLOSE(void *arg) { if (client_conn_list[0].active == CONN_ROLE_INACTIVE) { return ATCMD_ERROR; } - // Set as inactive first to prevent duplicate close message from lwip_receive_task - client_conn_list[0].active = CONN_ROLE_INACTIVE; - vTaskDelay(20/portTICK_PERIOD_MS); - if (lwip_close(client_conn_list[0].sockfd) != 0) { + + if (atcmd_connection_close(0) != ATCMD_OK) { return ATCMD_ERROR; } - atcmd_init_conn_struct(&(client_conn_list[0])); at_printf("CLOSED\r\n"); return ATCMD_OK; } uint8_t e_AT_CIFSR(void *arg) { // Obtain the Local IP Address and MAC Address + // AT+CIFSR (void)arg; //<”APIP”>: IPv4 address of Wi-Fi softAP interface //<”APIP6LL”>: Linklocal IPv6 address of Wi-Fi softAP interface @@ -860,14 +1388,34 @@ uint8_t e_AT_CIFSR(void *arg) { //<”STAIP6LL”>: Linklocal IPv6 address of Wi-Fi station interface //<”STAIP6GL”>: Global IPv6 address of Wi-Fi station interface //<”STAMAC”>: MAC address of Wi-Fi station interface + + extern rtw_mode_t cw_mode; uint8_t* mac = LwIP_GetMAC(&xnetif[0]); uint8_t* ip = LwIP_GetIP(&xnetif[0]); - if (cwstate != CWSTATE_CONNECTEDIP) { - at_printf("+CIFSR:STAIP,\"%d.%d.%d.%d\"\r\n", 0, 0, 0, 0); - } else { - at_printf("+CIFSR:STAIP,\"%d.%d.%d.%d\"\r\n", ip[0], ip[1], ip[2], ip[3]); + + if (cw_mode == RTW_MODE_AP) { + at_printf("+CIFSR:APIP,\"%d.%d.%d.%d\"\r\n", ip[0], ip[1], ip[2], ip[3]); + at_printf("+CIFSR:APMAC,\"%02x:%02x:%02x:%02x:%02x:%02x\"\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } else if (cw_mode == RTW_MODE_STA) { + if (cwstate != CWSTATE_CONNECTEDIP) { + at_printf("+CIFSR:STAIP,\"%d.%d.%d.%d\"\r\n", 0, 0, 0, 0); + } else { + at_printf("+CIFSR:STAIP,\"%d.%d.%d.%d\"\r\n", ip[0], ip[1], ip[2], ip[3]); + } + at_printf("+CIFSR:STAMAC,\"%02x:%02x:%02x:%02x:%02x:%02x\"\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } else if (cw_mode == RTW_MODE_STA_AP) { + uint8_t* ap_mac = LwIP_GetMAC(&xnetif[1]); + uint8_t* ap_ip = LwIP_GetIP(&xnetif[1]); + + at_printf("+CIFSR:APIP,\"%d.%d.%d.%d\"\r\n", ap_ip[0], ap_ip[1], ap_ip[2], ap_ip[3]); + at_printf("+CIFSR:APMAC,\"%02x:%02x:%02x:%02x:%02x:%02x\"\r\n", ap_mac[0], ap_mac[1], ap_mac[2], ap_mac[3], ap_mac[4], ap_mac[5]); + if (cwstate != CWSTATE_CONNECTEDIP) { + at_printf("+CIFSR:STAIP,\"%d.%d.%d.%d\"\r\n", 0, 0, 0, 0); + } else { + at_printf("+CIFSR:STAIP,\"%d.%d.%d.%d\"\r\n", ip[0], ip[1], ip[2], ip[3]); + } + at_printf("+CIFSR:STAMAC,\"%02x:%02x:%02x:%02x:%02x:%02x\"\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } - at_printf("+CIFSR:STAMAC,\"%02x:%02x:%02x:%02x:%02x:%02x\"\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return ATCMD_OK; } @@ -997,6 +1545,78 @@ uint8_t s_AT_CIPDINFO(void *arg) { return ATCMD_OK; } +uint8_t q_AT_CIPSSLCSNI(void *arg) { + // Query the SNI configuration of each connection. + // AT+CIPSSLCSNI? + (void)arg; + + for (uint8_t link_id = 0; link_id < ATCMD_MAX_CLIENT_CONN; link_id++) { + if (strlen(client_conn_list[link_id].ssl_cli_sni)) { + at_printf("+CIPSSLCSNI:%d,\"%s\"\r\n", link_id, client_conn_list[link_id].ssl_cli_sni); + } + } + return ATCMD_OK; +} + +uint8_t s_AT_CIPSSLCSNI(void *arg) { + // Set the SNI configuration of each connection. + // Single connection: (AT+CIPMUX=0) + // AT+CIPSSLCSNI=<"sni"> + // Multiple connections: (AT+CIPMUX=1) + // AT+CIPSSLCSNI=,<"sni"> + (void)arg; + uint8_t argc = 0; + char* argv[ATCMD_MAX_ARG_CNT] = {0}; + uint8_t link_id = 0; // Arg 1 + char* sni = NULL; // Arg 2 + + if (!arg) { + return ATCMD_ERROR; + } + argc = atcmd_parse_params((char*)arg, argv); + if (cip_mux) { + if (argc != 3) { + return ATCMD_ERROR; + } + } else { + if (argc != 2) { + return ATCMD_ERROR; + } + } + + // Link ID + if (cip_mux) { + if (argv[1] != NULL) { + link_id = atoi(argv[1]); + if (link_id >= ATCMD_MAX_CLIENT_CONN + 1) { + return ATCMD_ERROR; + } + } else { + return ATCMD_ERROR; + } + } + + // Server Name Indication + if (argv[1 + cip_mux] != NULL) { + sni = argv[1 + cip_mux]; + if (strlen(sni) > 64) { + return ATCMD_ERROR; + } + } else { + return ATCMD_ERROR; + } + + if (link_id == ATCMD_MAX_CLIENT_CONN) { // Set SNI for all connections + for (link_id = 0; link_id < ATCMD_MAX_CLIENT_CONN; link_id++) { + strcpy(client_conn_list[link_id].ssl_cli_sni, sni); + } + } else { // Set SNI for specified connection + strcpy(client_conn_list[link_id].ssl_cli_sni, sni); + } + + return ATCMD_OK; +} + uint8_t q_AT_CIPRECONNINTV(void *arg) { // Query the automatic connect interval for the Wi-Fi Passthrough Mode. // AT+CIPRECONNINTV? @@ -1006,7 +1626,7 @@ uint8_t q_AT_CIPRECONNINTV(void *arg) { } uint8_t s_AT_CIPRECONNINTV(void *arg) { - // Set the automatic reconnecting interval when TCP/UDP/SSL transmission breaks in the Wi-Fi Passthrough Mode. + // Set the automatic reconnecting interval when TCP/SSL transmission breaks in the Wi-Fi Passthrough Mode. // AT+CIPRECONNINTV= (void)arg; uint8_t argc = 0; @@ -1131,13 +1751,14 @@ uint8_t s_AT_CIPRECVDATA(void *arg) { at_printf("+CIPRECVDATA:"); at_printf("%d,", data_len); if (cip_dinfo) { - at_printf("\"%s\",", inet_ntoa(client_conn_list[link_id].addr)); - at_printf("%d,", client_conn_list[link_id].port); + at_printf("\"%s\",", inet_ntoa(client_conn_list[link_id].remote_addr)); + at_printf("%d,", client_conn_list[link_id].remote_port); } at_print_data(atcmd_tcpip_rx_buf, data_len); + // Allow data in UART buffer to clear + vTaskDelay(20/portTICK_PERIOD_MS); // set to zero to trigger new pending data message client_conn_list[link_id].recv_data_len = 0; - vTaskDelay(20/portTICK_PERIOD_MS); } else { return ATCMD_ERROR; } @@ -1333,10 +1954,6 @@ uint8_t s_AT_CIPDNS(void *arg) { dns_setserver(0, &dns1_addr); dns_setserver(1, &dns2_addr); -// struct ip_addr dns; -// IP4_ADDR(ip_2_ip4(&dns), 8, 8, 8, 8); -// dns_setserver(0, &dns); - return ATCMD_OK; } @@ -1350,6 +1967,7 @@ atcmd_command_t atcmd_tcpip_commands[] = { {"AT+CIPMUX", NULL, q_AT_CIPMUX, s_AT_CIPMUX, NULL, {NULL, NULL}}, {"AT+CIPMODE", NULL, q_AT_CIPMODE, s_AT_CIPMODE, NULL, {NULL, NULL}}, {"AT+CIPDINFO", NULL, q_AT_CIPDINFO, s_AT_CIPDINFO, NULL, {NULL, NULL}}, + {"AT+CIPSSLCSNI", NULL, q_AT_CIPSSLCSNI, s_AT_CIPSSLCSNI, NULL, {NULL, NULL}}, {"AT+CIPRECONNINTV", NULL, q_AT_CIPRECONNINTV, s_AT_CIPRECONNINTV, NULL, {NULL, NULL}}, {"AT+CIPRECVMODE", NULL, q_AT_CIPRECVMODE, s_AT_CIPRECVMODE, NULL, {NULL, NULL}}, {"AT+CIPRECVDATA", NULL, NULL, s_AT_CIPRECVDATA, NULL, {NULL, NULL}}, diff --git a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tcpip.h b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tcpip.h index d28954b0..c8e1aa60 100644 --- a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tcpip.h +++ b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tcpip.h @@ -14,12 +14,15 @@ extern "C" { #define ATCMD_MAX_CLIENT_CONN 5 #define INVALID_SOCKET_ID (-1) -#define ATCMD_TCPIP_TX_BUFFER_SIZE 8192 -#define ATCMD_TCPIP_RX_BUFFER_SIZE 2920 +#define ATCMD_TCPIP_RX_BUFFER_SIZE 2920 // RX from network, sent to UART +#define ATCMD_TCPIP_TX_BUFFER_SIZE 8192 // RX from UART, send to network #define DEFAULT_DNS_1 "1.1.1.1" #define DEFAULT_DNS_2 "8.8.8.8" +#define MBEDTLS_DEBUG_LEVEL 0 +#define MBEDTLS_EXPORT_KEY 0 + typedef enum { CONN_MODE_TCP = 0, CONN_MODE_UDP = 1, @@ -37,12 +40,17 @@ typedef struct atcmd_client_conn atcmd_conn_role_t active; int sockfd; atcmd_conn_mode_t protocol; - uint32_t addr; + uint32_t addr; // Target address and port to send to uint16_t port; - uint32_t local_addr; + uint32_t local_addr; // Local address and port of connection uint16_t local_port; + uint32_t remote_addr; // Remote address and port of received packet + uint16_t remote_port; uint32_t keep_alive; uint16_t recv_data_len; // used to keep track of received data pending read in passive receive mode + uint16_t udp_mode; + mbedtls_ssl_context* ssl; + char ssl_cli_sni[64]; // SSL Server name indication } atcmd_client_conn_t; struct timeval { diff --git a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tls_certs.h b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tls_certs.h new file mode 100644 index 00000000..9a551d27 --- /dev/null +++ b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_tls_certs.h @@ -0,0 +1,12 @@ +#ifndef ATCMD_TLS_CERTS_H +#define ATCMD_TLS_CERTS_H + +const unsigned char* clientCA = NULL; +const unsigned char* clientCert = NULL; +const unsigned char* clientKey = NULL; + +const unsigned char* serverCA = NULL; +const unsigned char* serverCert = NULL; +const unsigned char* serverKey = NULL; + +#endif diff --git a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_uart.cpp b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_uart.cpp index ec2c72c7..87149719 100644 --- a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_uart.cpp +++ b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_uart.cpp @@ -156,6 +156,7 @@ void atcmd_uart_irq_disable(void) { //---------------------------- Commands for basic ATCMD functionality ----------------------------// uint8_t q_AT_UART(void *arg) { // Query current UART configuration + // AT+UART? (void)arg; at_printf("+UART:%d,%d,%d,%d,%d\r\n",\ atcmd_uart_baudrate, atcmd_uart_databits, atcmd_uart_stopbits, atcmd_uart_paritybits, atcmd_uart_flow_ctrl); @@ -164,6 +165,7 @@ uint8_t q_AT_UART(void *arg) { uint8_t q_AT_UART_CUR(void *arg) { // Query current UART configuration + // AT+UART_CUR? (void)arg; at_printf("+UART_CUR:%d,%d,%d,%d,%d\r\n",\ atcmd_uart_baudrate, atcmd_uart_databits, atcmd_uart_stopbits, atcmd_uart_paritybits, atcmd_uart_flow_ctrl); @@ -172,6 +174,7 @@ uint8_t q_AT_UART_CUR(void *arg) { uint8_t s_AT_UART_CUR(void *arg) { // Set UART configuration + // AT+UART_CUR=,,,, uint8_t argc = 0; char *argv[ATCMD_MAX_ARG_CNT] = {0}; u32 baud = 0; @@ -241,6 +244,7 @@ uint8_t s_AT_UART_CUR(void *arg) { uint8_t q_AT_UART_DEF(void *arg) { // Query default UART configuration, saved in flash + // AT+UART_DEF? (void)arg; at_printf("+UART_DEF:%d,%d,%d,%d,%d\r\n",\ atcmd_uart_baudrate, atcmd_uart_databits, atcmd_uart_stopbits, atcmd_uart_paritybits, atcmd_uart_flow_ctrl); @@ -249,6 +253,7 @@ uint8_t q_AT_UART_DEF(void *arg) { uint8_t s_AT_UART_DEF(void *arg) { // Set default UART configuration, saved in flash + // AT+UART_DEF=,,,, uint8_t argc = 0; char *argv[ATCMD_MAX_ARG_CNT] = {0}; u32 baud = 0; @@ -284,34 +289,34 @@ uint8_t s_AT_UART_DEF(void *arg) { return ATCMD_ERROR; } - atcmd_uart_baudrate = baud; - atcmd_uart_databits = databits; - if (stopbits == 1) { - atcmd_uart_stopbits = 1; - } else { - atcmd_uart_stopbits = 2; - } - atcmd_uart_paritybits = (SerialParity)parity; - switch (flowcontrol) { - case 0: { - atcmd_uart_flow_ctrl = FlowControlNone; - break; - } - case 1: { - atcmd_uart_flow_ctrl = FlowControlRTS; - break; - } - case 2: { - atcmd_uart_flow_ctrl = FlowControlCTS; - break; - } - case 3: { - atcmd_uart_flow_ctrl = FlowControlRTSCTS; - break; - } - } - at_printf("\r\nOK\r\n"); - vTaskDelay(5/portTICK_PERIOD_MS); // delay to allow data in UART FIFO to clear out - atcmd_uart_reinit(); +// atcmd_uart_baudrate = baud; +// atcmd_uart_databits = databits; +// if (stopbits == 1) { +// atcmd_uart_stopbits = 1; +// } else { +// atcmd_uart_stopbits = 2; +// } +// atcmd_uart_paritybits = (SerialParity)parity; +// switch (flowcontrol) { +// case 0: { +// atcmd_uart_flow_ctrl = FlowControlNone; +// break; +// } +// case 1: { +// atcmd_uart_flow_ctrl = FlowControlRTS; +// break; +// } +// case 2: { +// atcmd_uart_flow_ctrl = FlowControlCTS; +// break; +// } +// case 3: { +// atcmd_uart_flow_ctrl = FlowControlRTSCTS; +// break; +// } +// } +// at_printf("\r\nOK\r\n"); +// vTaskDelay(5/portTICK_PERIOD_MS); // delay to allow data in UART FIFO to clear out +// atcmd_uart_reinit(); return ATCMD_OK; } diff --git a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_wifi.cpp b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_wifi.cpp index d96978df..7a72fe43 100644 --- a/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_wifi.cpp +++ b/Ameba_misc/ESP_ATCMD_on_AmebaD/Ameba_ATCMD_ESP/atcmd_wifi.cpp @@ -11,6 +11,7 @@ extern "C" { #include "wifi_structures.h" #include "lwip_netconf.h" #include "inet.h" +#include "dhcp/dhcps.h" extern struct netif xnetif[NET_IF_NUM]; @@ -20,9 +21,13 @@ extern struct netif xnetif[NET_IF_NUM]; uint8_t cw_dhcp = 3; uint8_t cw_autoconn = 1; +rtw_mode_t cw_mode = RTW_MODE_STA; uint8_t static_ip[4] = {0, 0, 0, 0}; uint8_t static_gw[4] = {0, 0, 0, 0}; uint8_t static_nm[4] = {0, 0, 0, 0}; +uint8_t ap_ip[4] = {192, 168, 5, 1}; +uint8_t ap_gw[4] = {192, 168, 5, 1}; +uint8_t ap_nm[4] = {255, 255, 255, 0}; char cw_hostname[32] = {"ameba"}; TaskHandle_t wifi_scan_task_handle = NULL; SemaphoreHandle_t wifi_scan_complete_sema = NULL; @@ -30,6 +35,10 @@ rtw_scan_result_t* wifi_scan_result_buffer = NULL; uint8_t wifi_scan_result_count = 0; char password[65] = {0}; rtw_network_info_t wifi = {{0,{0}},{0},0,0,0,0}; +char ap_password[65] = {"password"}; +rtw_ap_info_t ap = {{8,{"Ameba_AP"}},RTW_SECURITY_OPEN,(unsigned char*)ap_password,8,1}; +uint8_t ap_hidden_ssid = 0; +uint8_t ap_max_conn = AP_MAX_STA_NUM; atcmd_cwstate_t cwstate = CWSTATE_INITIAL; typedef struct atcmd_cwjap_params{ @@ -125,7 +134,7 @@ void atcmd_wifi_init(void) { atcmd_wifi_register(); if (wifi.ssid.len != 0) { - atcmd_wifi_connect(15); +// atcmd_wifi_connect(15); } } @@ -426,13 +435,90 @@ uint8_t atcmd_wifi_connect(uint32_t timeout_sec) { return ATCMD_OK; } + +uint8_t atcmd_ap_start(void) { + struct netif* pnetif; + + if(wext_set_sta_num(ap_max_conn) != 0){ + printf("ERROR : Set AP max conn failed\r\n"); + return ATCMD_ERROR; + } + if (ap_hidden_ssid) { + if (wifi_start_ap_with_hidden_ssid((char*)ap.ssid.val, ap.security_type, (char*)ap.password, ap.ssid.len, ap.password_len, ap.channel) < 0) { + printf("ERROR : Start AP failed\r\n"); + return ATCMD_ERROR; + } + } else { + if (wifi_start_ap((char*)ap.ssid.val, ap.security_type, (char*)ap.password, ap.ssid.len, ap.password_len, ap.channel) < 0) { + printf("ERROR : Start AP failed\r\n"); + return ATCMD_ERROR; + } + } + + if(cw_mode == RTW_MODE_STA_AP) + pnetif = &xnetif[1]; + else + pnetif = &xnetif[0]; + + struct ip_addr ip_add; + struct ip_addr netmask; + struct ip_addr gateway; + IP4_ADDR(ip_2_ip4(&ip_add), ap_ip[0], ap_ip[1], ap_ip[2], ap_ip[3]); + IP4_ADDR(ip_2_ip4(&netmask), ap_nm[0], ap_nm[1] , ap_nm[2], ap_nm[3]); + IP4_ADDR(ip_2_ip4(&gateway), ap_gw[0], ap_gw[1], ap_gw[2], ap_gw[3]); + netif_set_addr(pnetif, ip_2_ip4(&ip_add), ip_2_ip4(&netmask),ip_2_ip4(&gateway)); + + if (cw_dhcp & 0x02) { + dhcps_init(pnetif); + } + + return ATCMD_OK; +} + +uint8_t atcmd_wifi_change_mode(rtw_mode_t new_mode, uint8_t auto_connect, uint8_t restart) { + if ((new_mode != cw_mode) || (restart != 0)) { + if ((cw_mode == RTW_MODE_AP) || (cw_mode == RTW_MODE_STA_AP)) { + dhcps_deinit(); + } + // Switch WiFi Mode + wifi_off(); + vTaskDelay(20); + if (new_mode == RTW_MODE_NONE) { + return ATCMD_OK; + } else { + if (wifi_on(new_mode) < 0){ + printf("ERROR : Wifi change mode failed\r\n"); + return ATCMD_ERROR; + } + } + + cw_mode = new_mode; + + // Configure WiFi STA mode + if (auto_connect) { + if ((cw_mode == RTW_MODE_STA) || (cw_mode == RTW_MODE_STA_AP)) { + if (wifi.ssid.len != 0) { + atcmd_wifi_connect(15); + } + } + } + + // Configure WiFi AP mode + if ((cw_mode == RTW_MODE_AP) || (cw_mode == RTW_MODE_STA_AP)) { + atcmd_ap_start(); + } + } + + return ATCMD_OK; +} + //---------------------------- Commands for wifi ATCMD functionality ----------------------------// uint8_t q_AT_CWMODE(void *arg) { // Query the Wi-Fi mode of ESP devices. // AT+CWMODE? (void)arg; - at_printf("+CWMODE:%d\r\n", 1); + at_printf("+CWMODE:%d\r\n", cw_mode); return ATCMD_OK; } @@ -442,8 +528,8 @@ uint8_t s_AT_CWMODE(void *arg) { (void)arg; uint8_t argc = 0; char* argv[ATCMD_MAX_ARG_CNT] = {0}; - uint8_t mode = 0; // Arg 1 - uint8_t auto_connect = 0; // Arg 2 + rtw_mode_t new_mode = 0; // Arg 1 + uint8_t auto_connect = 1; // Arg 2 if (!arg) { return ATCMD_ERROR; @@ -453,8 +539,8 @@ uint8_t s_AT_CWMODE(void *arg) { return ATCMD_ERROR; } - mode = atoi(argv[1]); - if (mode > 3) { + new_mode = atoi(argv[1]); + if (new_mode > 3) { return ATCMD_ERROR; } @@ -465,11 +551,12 @@ uint8_t s_AT_CWMODE(void *arg) { } } - return ATCMD_OK; + return atcmd_wifi_change_mode(new_mode, auto_connect, 0); } uint8_t q_AT_CWSTATE(void *arg) { // Query Wi-Fi state and Wi-Fi information + // AT+CWSTATE? (void)arg; at_printf("+CWSTATE:%d,\"%s\"\r\n", cwstate, (char *)wifi.ssid.val); return ATCMD_OK; @@ -477,12 +564,17 @@ uint8_t q_AT_CWSTATE(void *arg) { uint8_t q_AT_CWJAP(void *arg) { // Query currently connected AP information + // AT+CWJAP? (void)arg; char ssid[33]; uint8_t bssid[ETH_ALEN] = {0}; int channel = 0; int rssi = 0; + if ((cw_mode != RTW_MODE_STA) && (cw_mode != RTW_MODE_STA_AP)) { + return ATCMD_OK; + } + // No information if not connected if (wext_get_ssid(WLAN0_NAME, (unsigned char *)ssid) < 0) { // not connected yet @@ -517,6 +609,10 @@ uint8_t s_AT_CWJAP(void *arg) { uint16_t jap_timeout = 15; // Arg 8 uint8_t pmf = 0; // Arg 9 + if ((cw_mode != RTW_MODE_STA) && (cw_mode != RTW_MODE_STA_AP)) { + return ATCMD_ERROR; + } + if (!arg) { return ATCMD_ERROR; } @@ -635,7 +731,11 @@ uint8_t s_AT_CWJAP(void *arg) { uint8_t e_AT_CWJAP(void *arg) { // Connect to previously targeted AP + // AT+CWJAP (void)arg; + if ((cw_mode != RTW_MODE_STA) && (cw_mode != RTW_MODE_STA_AP)) { + return ATCMD_ERROR; + } // Check for existing saved SSID, error if none if (wifi.ssid.len == 0) { return ATCMD_ERROR; @@ -646,6 +746,7 @@ uint8_t e_AT_CWJAP(void *arg) { uint8_t q_AT_CWRECONNCFG(void *arg) { // Query autoconnection setting. + // AT+CWRECONNCFG? (void)arg; at_printf("+CWRECONNCFG:%d,%d\r\n", cwjap_params.reconn_int, cwjap_params.reconn_cnt); return ATCMD_OK; @@ -653,7 +754,7 @@ uint8_t q_AT_CWRECONNCFG(void *arg) { uint8_t s_AT_CWRECONNCFG(void *arg) { // Automatically Connect to an AP When Powered on. - // AT+CWAUTOCONN= + // AT+CWRECONNCFG=, (void)arg; uint8_t argc = 0; char* argv[ATCMD_MAX_ARG_CNT] = {0}; @@ -695,6 +796,7 @@ uint8_t s_AT_CWRECONNCFG(void *arg) { uint8_t s_AT_CWLAP(void *arg) { // Scan for APs with specific parameters + // AT+CWLAP=[,,,,,] (void)arg; // scan_type, scan_time_min, scan_time_max parameters are ignored uint8_t argc = 0; @@ -706,6 +808,10 @@ uint8_t s_AT_CWLAP(void *arg) { // uint16_t scan_min = 0; // Arg 5 // uint16_t scan_max = 0; // Arg 6 + if ((cw_mode != RTW_MODE_STA) && (cw_mode != RTW_MODE_STA_AP)) { + return ATCMD_ERROR; + } + if (!arg) { return ATCMD_ERROR; } @@ -777,7 +883,13 @@ uint8_t s_AT_CWLAP(void *arg) { uint8_t e_AT_CWLAP(void *arg) { // List all APs + // AT+CWLAP (void)arg; + + if ((cw_mode != RTW_MODE_STA) && (cw_mode != RTW_MODE_STA_AP)) { + return ATCMD_ERROR; + } + // Allocate sufficient memory space to store all results wifi_scan_result_count = 0; if (wifi_scan_result_buffer != NULL) { @@ -808,11 +920,16 @@ uint8_t e_AT_CWLAP(void *arg) { uint8_t e_AT_CWQAP(void *arg) { // Disconnect from AP + // AT+CWQAP (void)arg; char ssid[33]; uint8_t timeout = 20; int ret = RTW_SUCCESS; + if ((cw_mode != RTW_MODE_STA) && (cw_mode != RTW_MODE_STA_AP)) { + return ATCMD_ERROR; + } + if (wext_get_ssid(WLAN0_NAME, (unsigned char *)ssid) < 0) { // not connected yet return ATCMD_OK; @@ -840,6 +957,255 @@ uint8_t e_AT_CWQAP(void *arg) { return ATCMD_OK; } +uint8_t q_AT_CWSAP(void *arg) { + // Query the configuration parameters of SoftAP. + // AT+CWSAP? + (void)arg; + uint8_t ecn = 0; + + if ((ap.security_type & WPA2_SECURITY) && (ap.security_type & WPA_SECURITY)) { + ecn = 4; + } else if (ap.security_type & WPA2_SECURITY) { + ecn = 3; + } else if (ap.security_type & WPA_SECURITY) { + ecn = 2; + } else if (ap.security_type == RTW_SECURITY_OPEN) { + ecn = 0; + } + + at_printf("+CWSAP:\"%s\",", ap.ssid.val); + if (ecn != 0) { + at_printf("\"%s\",", ap_password); + } else { + at_printf("\"\","); + } + at_printf("%d,%d,%d,%d\r\n", ap.channel, ecn, ap_max_conn, ap_hidden_ssid); + + return ATCMD_OK; +} + +uint8_t s_AT_CWSAP(void *arg) { + // Set the configuration of SoftAP. + // AT+CWSAP=,,,[,][,