From 2327d92c6586a54dbf10c9aab02ff8bc31db9fdb Mon Sep 17 00:00:00 2001 From: Frank Voorburg Date: Mon, 11 Dec 2017 16:19:05 +0000 Subject: [PATCH] Refs #318. Added TCP/IP support to LibOpenBLT and BootCommander. git-svn-id: https://svn.code.sf.net/p/openblt/code/trunk@388 5dc33758-31d5-4daf-9ae8-b24bf3d40d73 --- Host/BootCommander.exe | Bin 54784 -> 56832 bytes Host/Source/BootCommander/main.c | 88 ++++- Host/Source/LibOpenBLT/CMakeLists.txt | 5 +- Host/Source/LibOpenBLT/netaccess.h | 56 +++ Host/Source/LibOpenBLT/openblt.c | 30 +- Host/Source/LibOpenBLT/openblt.h | 16 + Host/Source/LibOpenBLT/port/linux/netaccess.c | 332 +++++++++++++++++ .../LibOpenBLT/port/windows/netaccess.c | 349 ++++++++++++++++++ Host/Source/LibOpenBLT/xcptpnet.c | 291 +++++++++++++++ Host/Source/LibOpenBLT/xcptpnet.h | 63 ++++ Host/Source/LibOpenBLT/xcptpuart.c | 2 +- Host/libopenblt.dll | Bin 157184 -> 165376 bytes 12 files changed, 1224 insertions(+), 8 deletions(-) create mode 100644 Host/Source/LibOpenBLT/netaccess.h create mode 100644 Host/Source/LibOpenBLT/port/linux/netaccess.c create mode 100644 Host/Source/LibOpenBLT/port/windows/netaccess.c create mode 100644 Host/Source/LibOpenBLT/xcptpnet.c create mode 100644 Host/Source/LibOpenBLT/xcptpnet.h diff --git a/Host/BootCommander.exe b/Host/BootCommander.exe index 4cf80bf0b948a5cea4181fb9ac5808d33be9528e..7c2d139a7380457d6c3b2c61feb2611d5ed0a7ee 100644 GIT binary patch delta 14320 zcmeHteOOf0+V`Gez)=T51w}&~kx^1aX7+r4F{Y#VK>~u29|eV;i4-^r>Pcy^(UL|= zTb%MOD+%jZVbOtOXr9E6#8YUdo*qxpiOxfvL3nE{Qc7~3-`X37&3@{A|9G$K`Rm1N z-FxrfeXsj#t#z-p_gGOEQgJ-wfF%CHTgfY!{=arcDj6wJFia7E5*|!hMB*jWB}%eL zGCTY_!(?!>xv&_m@9HHV3Qev z>y?HJQj^3T$1ttjUyiqXxwpr)uhGE-q7$z>7|$7*rN{TGj$r`D{ADQo6b~1Oq&N4& zBI!IHJ|Kb@>X>27eU@(D9382WO>}pNv_ZBt3aGem-4VnB)yu4OGVQ2dH9e>dNvPtxJ7sjk1Lyox+3A> zRU-Hr%Vi}`1YhRin4?3L9OvO3LTNW0ZXVw!5`T(^3qcDdm#^3 zh@>?f@lHa0sLx3KO^ z;qeOKLU;rZw~F8&P#saFlqZKuxACw`1b@K8J`wDGi^u1QlK31Cr<@w9yn=@-MDRmA z+$U0!#l!6)X$uc0o`!C>pzJ2_c(*9!;XJ&BiVLJ~%;v&p&J2aW@Nk~ULpu-GaUotZKB-%i-%i9@JBqHCQ{Ov?B=xW5#cZL;-#XLKhDEfMbgDQd`2Xl%fqg-Lmi(3 znC^cjTvwkaKaH3+rbE>8msvzTa;$raC=*v_F-+HZ1YIX$oB#uGg2=B!6i$~{ruTTt zdzjMD&U^{Oc*+$_*%V06kTQ(xBAjz2a2{H()9qh@nq7i^5OXtSv5=c5@_!k)o#UX% z>r_-a`@HFW5$}%n_GGyl1%h&@nL*7v!A+BTdoo?~S_bn#*&``C2kg6K-kuCso`~I@ zM2Chd&kUp5w~6@YlLoZQsrG#$_FI(Q<7yPZbnkwS!a04OM-)t{(%S>~ylIL|SF1pb z`c?w5E;mi}MFEE@xQAB|apbN@>h)Ctn|3tPjQ#2 zx}%mi(UVxEv%AviuXOfQI(unSdt8y{+=Ka|8vZ$x1}uk|r`#X1xyYOD489{0@Y>9st8CV(g=gg9-W?M0L|6!QygQJv(+6jR^KT=ae@*D?s^%07&c7dL49vd?P@nX-p`l#`x9bny z97Sag%~OVmeH3gAtH;QcPR0PI_jyNWx$*=;beO8C4lX1CgC=<^?afaMnLE=}B4E>g ztHJJE3de&{JWZphhnQDnUT(r}*Uxm2|x!&NQf zx_{5JVKOr^%he=eFXOnbHUUg6S}E-8^=ye^O6l-19}SH>7JM%CauFj0jLsh!>aR;d zdBUyn--%%U1I%FJkJH5GLe8b-6b&Z+J;-um)O;()9Nc7H0<-H8Zuazm?H94#V0)cC z-0X4F>C;2g&9n~2K=ZbODxuRyCScIu=kVQ8@=((JWwM zaCg3Yv&g!A8>f?xA}7whO>^B0A=g1paxm96NP5a6`5h%d#f;}6 zRtFM@9uyllog|JSe&+kAjp^knujbU~+mnsEp-<>KbKO^(> zhLp*@Igw|jlCIag{)9Zqkf-OTk%OaWN!!np%cG~nYEffc%mtY45SdP%Mk2?|RF{Gm zam0BGsqjW^&k~yc24-M-t$^*Dmq6Byu}g1HC3Rz_C2dnW7+)yFa#z5546NtkfsPVZ zFtY>P?m!?Q$Ne59xjn`l`w(?!3w5U(M3{+d2_$=Lf{xp3>BzsAz%VHIp7K6EhmX>p z@{iGT8;G#y-UM=ZZ27$XfCZik1Gzbs3uLi??aPVp&!KD+i-}G5tb-quqNtcv!R`Jq zx^?~m0boKO;;yKCf^z|PT`?#3-K?5dg zAe1-I$Qj7F^(#|gOwd4(?JJo=9MMtk^r?-GVvDm~$vt&QxhG!=_tYJNrzX0T9zMr-v%{}c z=JW<@{w*+LTt*1G>Oh36BVS2A9zRok4lsuH=C3UC3CiFyA7J}7$B~&aaS|C>6cg*d znB`N)(L~61HkXA%%MI_653W?E_jXCUnmX^L^Viv%apAsnLw?c5lHv`e>G>tW`i(k{60Y!c#||6@J4km- zyjv|pCm)TrRTQm+8pA<8Vuvma#?m7z(|w+DA9h?&NyLskWEgiL5j$F-qcf5U^%Tkj z(OPaaY2kSvp$ZGn9BM4@6BxS!LQ^}!fn3njMRgphnlM+r3b%2R%EL$ZO{v zl$?O?h%ZJM-+G7$<13-@F_#cuDTr|D%$-F3IdPr3k8?D*R)vlpgHwEXLAEdE4zl%* zX$z{MOc1rO*Mod7thB-Pd2*s4yd1yh6vE4L?Yl%)#R@9&IP~nCjL1-t_y4wm@9r4L z-=KkXu>n+=EY|^%fmhJ8^G&{l9sF$rTYtsCKPL1?5Q+h!5h$pEx=ZAuFoI-iz`z@* z|7`=qe#O8Vu>lk#oIW`N?%)hKhB!M<^ELALZyO*G!^o8g$KZNQFoIkpcwZal#F>5- zO|&#Ud@dB*QwVoL8QfsfpvoO_B%I3c{M)0$TbvzKxN8`0aK$T+-|OtCyeq167ToGg zq+}T+I`0M%uf9G6DV&|HbawjTgsT-qI6E)Vbl`Llu_H{vJWO9+@nVJ7sTOLJIqE}6 z^yYN0tz0BK>%1lDl0WIZC6nxO-lE&IC0!m-b&>kktjfM+yd#d3B!@m9`Fiqw?)E^ym-AI1(BkZ)6Nekroo?J# zId9^{PLYY1<}BY2-v$CGyBH>5@=V|9@8~mL`@1pBSwUEFofE5c`f&6BEYI+ne(aa* z@TPY_SwDR01}{lYDW9By$;uh0dn9(bP~Y2Ma#bc4p$b1P=0=l>Df8V*Okz}HdK{W3 zke~&W!I?BkyK zT)fLI6jwn|!9se}@9}x5hMqul3NntzZFoGUL%tFRjIEI^m zg?O23%ka6zkf-9uOLNAMKgDNizP%L;YFE${{}yt3?7+BOfc_R|D=6dpgv^+#mA*2X z8@$ zK`Hr;C{z>NpGQzv@hx$;r+hb48s;fKz-*>pC=)RsTxsa)YUcV6X7bU|beteuM1?oG z=qqkDX4ru&S5(7bH1zPY45ETe&cox-OimQcp6OH=TD;Ec44*QBysuh4>7(9Y7{>)+ z&?pW>663U_+-FBIjLEqx!t*GOvQ^HCjH;?qY~Ju^SC-EWCXH_AIzXPkQp&|x!N-_M zV|2i0R}+Y^yv0V6rRAGspIEp#jA2R{{Qkk2Ckfod-ve^Yf4n4Las`*c_A!=A?Yy2!S~ z1+h~H^*4(2_mVFYC%CPGfau)3fENmZ)f0I9O{=ef>3eY`xA0%%iXazAo*9^!3QS^*UeI-sKO2muu>wgD9_tN|6B3;`A0+yW|kAc9>#=xEg*dQnw#lzz);cuCtR zQPKCmGU*SeJ4Yi?trfPHvA;59lM9(b2^#*L4Sl(dY}Vi9J_JsoBoJMPzNqzufk5;U z+}6gnJD3_uYLh^`;a35Aqx-(ZK0UgojXs3?>4V%SiN<(#(+XnCgIT6*l&kq(I2C=4 zSLrK(fX7w)aXOrD(V}oIc{? z%yF?SefjT0bqGKkxokQ&Bxl_T{W*Kve3!&e4qB`v*%JQiB1^@lv)Xz=vWK-wBt=huxm~3=Vbnz_AuC~?f8-;-8E5)OWIxY z+~>YPlEcqX8r@9kq~nz8nkcn3g09^i-OgRYuW*-1_1q=2j=QK1ahF*KaoPWL=v7?2 zPNue%yUJ=W;#%dD*S68;oo#(MS%${oUV?u$U}iJ5S1=GQq31w{%b4bt&{o{h$<`8j zwg2uYEiO0(w>;JcM2d$d4#CG4E<2Uk;k_JkvK`AI?Q?Sf+{OR1fps7-8qQ!ecmChQ zp+A%%B9b-F_&@7y`+xHmpHT*qPsTb^#?s6ObBv$K_QK8O&Y7Ba;9Z}&I-bHpei~Z% zZ)^kK_*-jyC`I__Ht<)|{UvE|&MBZU{gF4E-n4{vgVI0xeU5%rf>=MF-n4{LDIp*4 z9G;(Lo5y-(e_VnZiPw-y1$VdlwC7v^S^}#5ev&(Xn>lhh95Z33L!G3>GoU(vDIZWB z1cf0AL70}tfQr`hfU2IOjeas}LDi6TeC8s*)EnMjut5^eNzqynP@$Y8FN}GBuC2Sj zalBVLnfsQAZ|#9V=~!<##czI6iD~FyI-f@vRXIJ_!j4_+e~wrKJqy4x_N!#y^-_b_F8kBvjh29VMOSM$@@ z1`h2)Rb>jDu3dX@BMn5;Q1Qz^f00HnQnF}5JO@VcexV+6TN}5bMN?WC9otUcT$D02 zHQoJH@ViA5+*ArRHx_y-ogJR?jzHNM`cfE(mSGphICSnp5(?$Mf8&EG9#LKKjFn3} zBF_Gf+)f(Q(_WR{o>`UC!M!x{PG$J$7gz2K-bP=``duB(`O2M=`gdirZ}CagzZFZ0 z`B7_yX93kkKlyY?+%RT7GJ|?~Ysutk!RlCHlhIU`MzYQPchZv8VVQT55wiEV z7{pMBZY*2pUxjP9e>+fC(s#tn2=-Q>-pvy80w7l=mxONBn<862t z+q{ylxCjxyZIOU_XR zM0H`F#c zP!$!HywJfcTu6>pO$>W&k%Kw9h`Y^Q>|h>VOnwF$o$Fw%xn$x~aUr=Y(Q*9;65^|m zeP&My>DZ$sC3{NAI{Zf0OtMF=%wCx8T$+=;dIUp?_mq)Hd<#z`r6H<~BghGoL;9YX zfghD-KeJgvpLde|Btfd$NS>+IlkI;@3z;=*1X=t?9XVIMLpB28QjnbcV>T(>KbE}w zTqyBB8&BrHFqW)&ZaPsudyI^E?jG{$bNBc2!{Uq9lq!XPv+T;;xf#mhB4zg6jM)n_ zl1DUz?5mMa9Reyht|={jsOWbam5Ey(EV&;#?li;ykM}=DOf@5^Zq@wa4azlJ^4FKF zTccE)G|6hcF&3H2N6%#plU_9CS%3j18 zUAvUD)hs11)S5`aE92eyww0Um*OjeV-S2F+YmIVYhBCjPV8fb?84;K}0E~3d|l*(BpzoHkBE9nm}%A&IM z53JchRmjRVu1O+KK7U6DI+D!i#>#C+L>hH!OzH*+Id{|b6*`JQIm&X zeVP=$I*Q0&8%2J8^_z$x`9rS0d`(Su{3(H(3q7^d$;W>(k!^>B&k^L)L!Sw;*>>d| zK3ubsVK70&vDlRv@Y<|YxyqE(&9`2Ej2wS!Pspr|>&b~XULA(9V$jHYZ|wzUYnc5; zsDvmECx)N{VGnPQ=pRffLaiH!c{&U${f&kL@8(HyvnuP6pBSb9Z8_R} zG%Z>p8vQ(`K>NOzVcO8nqSc{2f>wZ*h2}s@L5o8hf%fxF{IK3jzLMJAP554R3au5b z1MLQy>;}V(L5oMzqRmHJf%X8}Ti}=C`Xt&*Xmw~!XlKwapnZnsM;ieX@o0Lq6=)Bj ztwVbRZ5P@zXa{dHEYpaa3uvFAU85ijJdd^q?Qyg%Xhmpi(N>@p-hkg($y(gTqTTL+ z-)OC9$IuRY|D`@`j-5(!f-L9LIMG?cVd%Ou$ip{=te?(6!SdbwegVX{GG&>O6V zbi*=3fuYc_)v(=g!tjBi!*I>;iy_1qWt?bSU|eF%GCpK1Ha=tAXMD%_uJNMrQ{#8W zAC03-Q6`mXrfG>O%e2PyuxW?sNz;p_TGKyG9VVH1qtbuB^&xAqwZi(i^?>y?>rv|o>))-PTl=gM+b~<4Ex~59&9yyXbJ@1qw%eY! z9k4anj@qu;+HCE%4x7)`ZS&iDY$NTX?bGb;B>Nou0{bI&w|%F*#{QbU&i;-4raeEk zGWDs{>eP=?+fo@N!*pXZK9Ew8vODE`%B7U6DZ|v$)HBs;^)dBF>OOU}W~RofDbZAF zUe?rWIyAp%#Vinru+TUqk&^Bs&wKClpol=*qTdrH_*5&K= z>0Z~(*5~RA^hfmX=}+oqhN*@`L$aaV@RZ?-;cLSLqsEwHEH(bY=r-Oo%1w8h?l%!r zo#~Y6vT2xEY1W$;n75j%&2O0BGk<56TjDIUEvqc~mPe4mvzD(d*DQX^9BYKwI8*gw(hgyIV0!(|SZ=H~HW|-gVzn84 z#vUVMQkdY41Dhev&duyur1KJcmHj8%!Cqti>G$<#^q2JRFZ4Zni6P7|(lFXE-Vke; zVwi52ZO|Fk8Xm>~yleOp?)?G>M;dj;RBi>VFcuis8GjE~cN$+X)*9b5o;H4Dykh*! z_!V6K&iIQl#5B@0#uQ_kV#+Wr$6&2BtuvLGwwqoz%{MPLuQnere`D@4|6z}Rd);OEW=CBpo%5A4?KiKqk zVt?NLvwcbGj?{yxpQa*184R-z8Je78OIeZfSW11$wUnL|4F>Hs_21Q>sc)#Qn!7Y< zn!7dGniZOSjZ0Ik*@S^~@6dQPMDv2CR`a%|N%L3DY0Y`fe`q>2w>07GICce_$F619 zu^ZSetebtDtzxU$7uZ+W*V%X2qwGlx#wYB5uwSuVETaw8nzat?eC-l#mNr*=zjm#5 zy|z@lRr{E>QoC3CzqB>lI_*2!ceN+AKVSu@bw*vLF5j(tRCiE!Sl6ICqdTv=tov5i zs|(Zb&_Ao+uYW^dk8wJ#pKn-fSZ&y3pv9)mc*7_$jWUfxxiOpOBfW2!{$l#n^rgvX zx^DW(bQ>!o!W?H#G8@fl=5+Hi^SvlFUh|9QCi4mNCG&N2ubHt7v&33776WRHd%oq* zmZO&QmVa3~Ex%ag)-)tP%euw-sC9?+AQsGrC@tSwr8c>36oz1}ZI`XucGxz;KF&VX zuD09k_t_t`m)W1NAG9B{|6q?w-Ilrwb>l#4U20S6nbeD^U!*d5*tju5k`!|aNjaRN zQm0~2-c}!1pH*MDM|~Nilc>3lr9@L)&mLiqv!~c|>;?7;`x)ELe#3UN-?P2!ZC0io up^emz)5dD!wTaqft#(Ml@6#4&3$-QMP1xKS&3&jyBpvNu#A_ zk;jTUntBeIrz|*%rYHPJt%If=AGPB23@5TdIOidwq@?Ekt-Z%#*ss0MAMbTNe?8;1 z?!ET!zSsS=*1Ff;8#f${*|0xmrzmaazrOtq6aEDlv1p)3#xP|7mhs?*X(UZFRwO4g zM3drM7$%>>oeX1>Y#W^vBVrixnn)KLV3=;vw$Z~$r${=W|058Qqm>b{_-@e(GEqEy z&`VhW4xo)k+e+q$N5uuugE8g^c}hG=4V_Fsw9{x&KNrKeCa!vD*~$kQX1R2 zO?BtRW|4OUdfczr-!aGE<<6~lF(E}}i;MA{l32Tf->Vr0@L2);B@b5%;4?g&^q0O$ z4)E{>0sOL>>CY^(b_BPn$rj0Q?@R&zA%0ks0G`Fe7X+}8hf@yrwXNh~r2vlOVRsbl zrSaZixp;R(;S~OYhp!6YQ#>p`)Ys;pc{pDHZ|C970@%yLXT1X4&Et}WzRC-DSSo-m zJlrUN$MA5L0G9Ev>u{8AFP-j5T==qR9PpPs91y@~czA(8`2ilzJQAfGN?zvSasm7_ z4>ug~_7#7W$2SXX7V$9ieqU)j4{s2_8DS*?4-zyertk! z+WeS@Jp#Cnhc5`=8XitQ)>la-4>uljdHZa<6+B)q$ktpQZW6@l;$il$eU*>n;SB2L#facsR4s#dxFZ&SO08 z0Uiy{y)L7KItpf3HcsTh)U*#8gc#mM35A*O^ zf%F?Z+$ezkJX}pP?u{zD9v)9V+1KVm9^NE?r}3~%FwK*Ac%1-F=HVs*Eb{VrhXB8R zH@_$Z6Zdx>-XoZaqdc5^s;}dFdAL*nzre%UT*epmo;r{7c&$LZl!u!I6Lb#`%RlUE zQv;ZuF5_`sc(RGSJTPNOk6`G(#*>Q!6TM3W({dK4Z2MhU3o=2b-vI;g5&^t}!gOup=;52IqZ`AjsjPWS_T~Of9 zl@+*4qQt23Qi!$RPgA`hibEAl=M^LzoFb=QFNtE)fthq59k(HhLB}P4v52O(BPgLN zzFv5e>`Y4bLbOI8dNGqmCauchr)>}>PvjX5D)#8w@5O)y!GN6@(0&gXX9bMsD1!y# zl7O*-GN_y0fPhm>jd!^_1h9j`bSZWE)^{?VVW6UfXa3I6h0-Zbct(lE)TRT8EU)Es zdCAXUc|8LXQuHpb8)L&_!~9)dcP+>5-DLj(Zu=AAwbkWr60i?~jaA_@GoDeHzv3Rh zIN#kIB}8Xo8`VL>3q&#KEUcxYyD;8gASrZroQXQ!=(u@cw>=lVNvdcR(zA$S9Y}N( z6Cuk5OZUi5j@cW;V5&$)_eh?sDz6(7rvwuJl>ugPx7TN3JQ;K$cKdTB1wd##m!gE} zhCB&jE}mc%gT}LuYC$~R{sJjBU=){zvzg~o)mY>5kNR$)6_o2m3cLMND!);Mha8CJfN>rS>=`-s z%t>_pUy~OlcNJr6Z++Lq59y>~$i!Jo04E$Q4q*}dWm^kEiX8zL zN-OpAt#pQ|Z1gCj*uj`FP<~3{>yD|A`iteK#iI5%+y8{XC2;M@bfQa~AYS%2@?hfV zlp!%NT@Ll`R)Oh3>11c(-D;+Zn$Ed}O!$*F7Dk&s4KpykG>RQeO(Q*t4)N8|#Fmti z)sgCAf?uaGOvyZ0Plk09#?VrQmCSU3+Yt(dr06rF$O}o9l{Akn+y<22Hm zl&)`~anoh4qG!xFdwS1}JT5Mc*rj3O6?CV6wi zK_3<&#m8I3a4$yoG!~FDVq`Byw{81{i(=-l=jf@3k;qyIUQUgQ0*6Z@&eeQ(Q8*Px zL=5RaG@&c0KbccKmh*;{)*WzePG+*2iT`I`eF=Vyv&7ZkYmT(3l35lMN z?GQu0^wIhH_eheJJbwDJUr&IQAB8OUCV-3-B!D}@J?H2;85qg%zf(zL@=!0$7G3&H zP51`k>&Tijcgy-qhX*nY(?GvWeD~I_omX6p_Cu`dGdWi`!ILvN&EVn^sNwEf=`5bf z3GV0aI#4y32D*73J=ws?^*T0 ziq+*SS9$UtC=Y+V;ZsL)5=W3DNrSwDB#1H>fw+%?M3;*$>K-0U@kjv~f1bQ5ufw;# zgYhJQ;z45$47N2;R#%xcq|kAbMeEA@7#LkwQmKin)WpnB`kL65LM$Vus+R$dT1n9h zO$}YhqdJNmtQt<<9+5Gvc#)kL?dTym z#fKMR2NQ;o%#j&hIXCK#yRVcV)u&oDV9NOe3*i-a`c5FcLU$6UB~;N)JLUg0U(G3<6uBR&PQg+@_R3f(FHz0p5FS=(ED9aHuijk2lc zj^?#@FnZyxaHO%}NM+P$<^}R?YShVcH??5oEo9x%0%t&S2LlU)22hd;-8#;IH?mR= zA@;VDe5Ew~x`C|_!wi>4FW}(NNCYLwdgoQF9amUI1Kq}to{hyJ6~jHJ^v+ZcROKWb zjHmLIA3YU^GDd~l`=f&^9$h>AIl-zaNo^D0R$C_ZEvF~^)DVZkeCb4?B+AjMl=tbn z&4pR_QIP1Jf9x*u!074XfP(BBJw`N}oF6?l{widu@_O3i{EF5KM4Xmp=%hn0z?YDs zk$aY+2l`hCExg#6jVCC#3aG$HhXCZVzl-7Psc^Xc=azgSur ze6TYV!pj~K3rt!N)P76v@k-a>FYYLe!0T>sVpTao9ISw)`N21T2urs5^ID-S45xMT zlH|mg=XHaFfBh%ixCg$3-9SyhiP|0;+zSS#nK?jH)%!m3dJ^B4I9z80VkA6Zr|1GA3~}!(!4)Yfvw=VTRD5Z zFAjM_7<$~$geLiOE+I=z$lQg(;4^rXhB4lk?=c>BmT}s9XHAa5QHc`a_OKG7$6J_~ z<57v?{zTAjZO`NOS6yFDWs7$r zYt{L|(m|v?bK%J2++O2$Nmi6<>hZM%GI3m%_;Ng1Fm8nSgLv}9xRK^!>R@Ia9Bc%M zE^doy9*!-;frlrXT8R19Z(tIheH}eyMn^swr*I_I_s)kLCQ>mbYSI&M`>WpH7(~*> zuN6NfBQ@jaitZ;}r7Q~rGOMVZ^Hxq0jtL~oe z9oDNqoeP&=uQWRjjG+tKBW{mvdz981`np$!)m0NENmoo#Ur%EHU!DYIu>Fq#T*_*` z_eNGzc2wf1_2X1-ALN1`fuMR}GlUeTNGaI`KCNj%6qsJrPtlwn{^|Fq-o*rm0V%=L z@5WQjh#H08O-1n7+aI^5iXn0n6N)Kh78+bkBl@?gj_&7ZEl2lobSFn^IJ$$Q)f}aJB&4EI zhE()nIi#Y+Hl(73HKd|57E;k;E~KJIKJ4O94c(>MN-wHvj?!-y+@D~Z>SmehMJoEX zQz8EDWZPh*qiKWvCeB-?VpJJZCPKr%)3Gs1;G@#;_g+hv%=>+?0yEsAAc2~Ur~g9)!<#t^d>%+-bjNY1!l2>ZXWwlm}M#kx#e@zk%ReJVt1c-3Jxg|BewlpVsn+}E%*9(D@mfvd(q?klnjtQ4CN0*)f#uN1 zRG7}h;yz?G$hwgnx1=e%eq9spsTA z`zET+Gty13!OerXC{Vs-NVB*Mu2P(4xSo&NAAboQzk)`DRMJbnKc0qG;8)P#3WG;K z=8l`)J#frtXFf}%{>paITWp60s>e>9W-9uH6s{IB#y&xNTb82n{&;$ePZ39jvh>Bb z2ZbU8ZNz6k+b3ro>EWE+@0cPAkRqpzeCde4vq;EM#EDEB9C@?GBhg&Hz2ORZ%b5z^ z$IjuPUpvRf(Rj~R#V3D86t36AxYWDK#I;w5a`FsC^ysR}ZafU^I&h`VJ9&hN3f4?n zB*q-}Oeuh>SyQV(uTEVG>dbyi78$y${tEdaJJ1hycH5dsbIx!vF5NlVUghUVa(oJ< zieyShCQ+)FQ)-uiF5Rp!ahLck?lMx!U1BBNMa6KJ2|bvXnipeP-1&2u-Ray_vO5#k zEjiNNI(lE(+=EBF*m8)9@NXBGMa=Fj+@6V5g2qXh#xt?$92-wFv23_^m{z1mFEVGI z)&UXYhQ2#%pm;h348+ovS7c!pFvvE)OlJ)kf|Aqdmb7*f%C z9#S3PXnlbEb;g!H`}ouaeyi8rfA1<$JSRnKMM#Bm^5x9r#dL4Yy5@S{Gm86ihA--& zkSEa}Pw|@W1w~uswz{KjRTW-zfcdkqm3%`D=4i}c$&%lS#00bbcCzmA7%~nQYqBj z6zHkSY4xpd4OI-GFNq1&F+0C{&LvaDloD zCJd*^>z-SY>z3en+)~+r;+#)UG)aKGQ!syA#KL|4ktWE_bu);ejB%I#4nFi~@@GSc#HE4n@cmVIhs7Lg}-YaGe>!Sa`FW-r%gYosOk^QG4tzinxmiH*2;)U32kJkrcX-`{V%m zX^uC1lN2Bu=d$!*52;!?8sumPM>{#%#nEn#_HdNpp1-6Vm2p(g(Ik#0b2NpcbhbjO zT#jaQ)WuO7M`^b3tQVk(SmM>9D}w+|lqI7*jMNR`adB#z2CD&r`PDWsBc zlmV^VR+64fHcW`G`%C%Hq8QgySKaZ-xR{s&Q(Sc|Vv#6DIVaq?PzY9eoLF=F{~(I7 zcyrNYzelS?2fy1qJW4WnNMnk?E2^xU^W+Qt`q6`}{Ik028%{_tlXY)bE{>6ZaoN_H ze%bEYKKokc%8fn$s8d#zio}5uSn4N^DTNc9H(O~3w#m+IN&TM6aWOCFkR#iM_xoWQ z%Jp=vTRYvwyg!}%1oWXI7qhvD4F6qfOzm7c@Iw(vsV*nE+dX7+wT?7yFCw~^(n({r zXTbcTd-8H-7Z)uYK*n!RjZv*0K%BpS9Dg$TV7r%0+rEa_c0dpVWII?1xD46#a;|v7 z1hROCfh52D3~Ak=qYw$cTqFkf?N^SHX*&m#*{_TxWv|X9TV5GM>T42lA4?ivEv;Kn zvr{V1U$ye#RSzszu71eld1S@It4Yen@hcad9rHKiX%0DWQUYY%~!5@ zXw~XRR<2O0C$f0`Az%J+dtC8~M^~(Td2Djz z;y;hSeP5UUwp`So8#rqv$=|0St^-RrX2*M5$^LiQffH6QXVyMgP8<24x`OPlA1PjK zC++WTi-8a1{=aM|uhsP@wRIavQ~frv$IeN0y!Y;aC3a@QVmqU%V5pRMiJg#x=CJOR zgyst_X8!DZ7t#oV7hEJnM%E=Z)by*%{-9J`_+%TyEJ2%tHVsXOmWh^vCPTXsWSC~O z(`ftAouCGY8qiLmok#l|trKkkOr)V1(B`2nMq7sVB-$pl-=XdKhS4(h z=$uFU9PK&FP|7Kulbqm8>{pW3jVJ-vP8o-W^!d^gTnZUNuL;pAb$j<*){_9+) zixC_@412<0r-& z#%|+a(_JRkWH8M(6`NL=Jf=#M-?ZQKp6Nr=-%PEh>!zPgF=nfIvU!<#mATUFH@|M) zV?Ja)WOM~Sz%a@i`%Pot@I?`($W4*^Z z+q&55wr;RKYkl3i$9mLy!uogXXV&kmKU!tBAvTrmZkxlFZTqclqiwsb#`do5knOtd zzii*z2HS_)Q|xo>58E%=f3*K%mpH7BnT}5#osJ%d-8sdX>wMg~-TA6>mlKOgiOr46 zc-G1;V=LKL+5K#Q_5R2vsFms>^>VdOU9CQ=Zc*PNn~y=v(w-4JN}fL#3hCaK_MN z_|!1NIN!Jn8NFr<85O1s(>&8^Q_6EV#5vh-N|IfadO=2gqrR-AnadtiXBKs!$IU=5_&Q;G+uU0>y_N(`*y&0M;&0ftu z%>m87HC>v1TBSBir_p8W7U|CGF6p{;zv$%p@%nr91^UPI&*@*+H|m@8SM{y>PJNGF zYDh9LE|e8KmknU$>`*p^O=B}*RL5Fa7dwre$riFD>>_pvTgH~NYuI({)9fbp1-6nC95*_?=^i*YUbzk7J+XT}OlC z1IKB{Imac(7mlwS?T+spA&1l%?@V-#bdGhJo%cEyI+r^4IS)7wI*&R}IL|siab9-* z!`bR=ciwRRNasBt4@8&>5gW%2##D@E$Fgdy_I$ROeV*OQZf9%QH`rSC9rhr5j6KEv zjs2AMe#W-2ZS1#f4=YmlR}WTC#a`H`-lX28eph{7-KG9n9ivIojMR+LI5fGMIhsqF zYnl$tPZ~zsPb<@I*S@O#v-Y%Bsn5`7>8D|pEYdI4SLmPDZ%2B6(#IMy43n@UCL8WC z%reY1+;3Q6SZCN|*kO3hu-ova;j-88h2dWYsWHKrW}IxyHRc=V8kZTLHa=(EX54NR zo8n9(O&XKglxHe2?LhM1FrCHLxn$}x{b(9)R+$~_w+m<@Zhn6drF3SMxRO=FJll7{#)!J$8u}W=8Hl=NXt-$7e-u9L4XIq8+Ma)ac zE^`cZq&mi6e%3lBqkPaLGo@T+v90>CGIj`?%%-yG?0A-C4Xllw!sfEG*dlfwx6zid ytJt;dQ|vQ+68<(@&mLhL+0$$jQqF8(m@LeeL@n1Pk@zp}lU!w6+1 XCP on RS232.\n"); printf(" xcp_can -> XCP on CAN.\n"); printf(" xcp_usb -> XCP on USB.\n"); + printf(" xcp_net -> XCP on TCP/IP.\n"); printf("\n"); printf("XCP version 1.0 settings (xcp):\n"); printf(" -t1=[timeout] Command response timeout in milliseconds as a 16-bit\n"); @@ -511,6 +508,12 @@ static void DisplayProgramUsage(void) printf("XCP on USB settings (xcp_usb):\n"); printf(" No additional settings needed.\n"); printf("\n"); + printf("XCP on TCP/IP settings (xcp_net):\n"); + printf(" -a=[value] The IP address or hostname of the target to connect to.\n"); + printf(" For example 192.168.178.23 (Mandatory).\n"); + printf(" -p=[value] The TCP port number to use, as a 16-bit value (Default\n"); + printf(" = 1000).\n"); + printf("\n"); printf("Program settings:\n"); printf(" -sm Silent mode switch. When specified, only minimal\n"); printf(" information is written to the output (Optional).\n"); @@ -606,6 +609,9 @@ static void DisplayTransportInfo(uint32_t transportType, void const * transportS case BLT_TRANSPORT_XCP_V10_USB: printf("XCP on USB\n"); break; + case BLT_TRANSPORT_XCP_V10_NET: + printf("XCP on TCP/IP\n"); + break; default: printf("Unknown\n"); break; @@ -688,6 +694,34 @@ static void DisplayTransportInfo(uint32_t transportType, void const * transportS printf(" -> No additional settings required.\n"); break; } + case BLT_TRANSPORT_XCP_V10_NET: + { + /* Check settings pointer. */ + assert(transportSettings); + if (transportSettings == NULL) /*lint !e774 */ + { + /* No valid settings present. */ + printf(" -> Invalid settings specified\n"); + } + else + { + tBltTransportSettingsXcpV10Net * xcpNetSettings = + (tBltTransportSettingsXcpV10Net *)transportSettings; + + /* Output the settings to the user. */ + printf(" -> Address: "); + if (xcpNetSettings->address != NULL) + { + printf("%s\n", xcpNetSettings->address); + } + else + { + printf("Unknown\n"); + } + printf(" -> Port: %hu \n", xcpNetSettings->port); + } + break; + } default: printf(" -> No settings specified\n"); break; @@ -974,7 +1008,8 @@ static uint32_t ExtractTransportTypeFromCommandLine(int argc, char const * const { { .name = "xcp_rs232", .value = BLT_TRANSPORT_XCP_V10_RS232 }, { .name = "xcp_can", .value = BLT_TRANSPORT_XCP_V10_CAN }, - { .name = "xcp_usb", .value = BLT_TRANSPORT_XCP_V10_USB } + { .name = "xcp_usb", .value = BLT_TRANSPORT_XCP_V10_USB }, + { .name = "xcp_net", .value = BLT_TRANSPORT_XCP_V10_NET } }; /* Set the default transport type in case nothing was specified on the command line. */ @@ -1182,6 +1217,49 @@ static void * ExtractTransportSettingsFromCommandLine(int argc, * layer. */ break; + /* -------------------------- XCP on TCP/IP ---------------------------------- */ + case BLT_TRANSPORT_XCP_V10_NET: + /* The following transport layer specific command line parameters are supported: + * -a=[value] -> The IP address or hostname of the target to connect to. + * -p=[value] -> The TCP port number to use. + */ + /* Allocate memory for storing the settings and check the result. */ + result = malloc(sizeof(tBltTransportSettingsXcpV10Net)); + assert(result != NULL); + if (result != NULL) /*lint !e774 */ + { + /* Create typed pointer for easy reading. */ + tBltTransportSettingsXcpV10Net * netSettings = + (tBltTransportSettingsXcpV10Net *)result; + /* Set default values. */ + netSettings->address = NULL; + netSettings->port = 1000; + /* Loop through all the command line parameters, just skip the 1st one because + * this is the name of the program, which we are not interested in. + */ + for (paramIdx = 1; paramIdx < argc; paramIdx++) + { + /* Is this the -a=[name] parameter? */ + if ( (strstr(argv[paramIdx], "-a=") != NULL) && + (strlen(argv[paramIdx]) > 3) ) + { + /* Store the pointer to the network address. */ + netSettings->address = &argv[paramIdx][3]; + /* Continue with next loop iteration. */ + continue; + } + /* Is this the -p=[value] parameter? */ + if ( (strstr(argv[paramIdx], "-p=") != NULL) && + (strlen(argv[paramIdx]) > 3) ) + { + /* Extract the port value. */ + sscanf(&argv[paramIdx][3], "%hu", &(netSettings->port)); + /* Continue with next loop iteration. */ + continue; + } + } + } + break; /* -------------------------- Unknown ------------------------------------------ */ default: /* Noting to extract. */ diff --git a/Host/Source/LibOpenBLT/CMakeLists.txt b/Host/Source/LibOpenBLT/CMakeLists.txt index 42f1327b..a56a37a7 100644 --- a/Host/Source/LibOpenBLT/CMakeLists.txt +++ b/Host/Source/LibOpenBLT/CMakeLists.txt @@ -145,7 +145,8 @@ set( # "make openblt_static" to individually build the static library. # Note that when you link your own application to the static library of LibOpenBLT under # Unix, you need to also link the LibUsb and LibDL libraries by adding usb-1.0 and dl to -# the linker library dependencies. +# the linker library dependencies. Under Windows, you need to also link the Winsock +# library by adding ws2_32 to the linker library dependencies. if(BUILD_STATIC) add_library(openblt_static STATIC ${LIB_SRCS}) SET_TARGET_PROPERTIES(openblt_static PROPERTIES OUTPUT_NAME openblt CLEAN_DIRECT_OUTPUT 1) @@ -175,6 +176,8 @@ if(BUILD_SHARED) else() SET_TARGET_PROPERTIES(openblt_shared PROPERTIES OUTPUT_NAME openblt CLEAN_DIRECT_OUTPUT 1) endif() + # Link the Winsock library + target_link_libraries(openblt_shared ws2_32) endif(BUILD_SHARED) # Only generate the PC-lint taget if the option is enabled. Use "make openblt_LINT" to diff --git a/Host/Source/LibOpenBLT/netaccess.h b/Host/Source/LibOpenBLT/netaccess.h new file mode 100644 index 00000000..1b30da54 --- /dev/null +++ b/Host/Source/LibOpenBLT/netaccess.h @@ -0,0 +1,56 @@ +/************************************************************************************//** +* \file netaccess.h +* \brief TCP/IP network access header file. +* \ingroup NetAccess +* \internal +*---------------------------------------------------------------------------------------- +* C O P Y R I G H T +*---------------------------------------------------------------------------------------- +* Copyright (c) 2017 by Feaser http://www.feaser.com All rights reserved +* +*---------------------------------------------------------------------------------------- +* L I C E N S E +*---------------------------------------------------------------------------------------- +* This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or +* modify it under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* OpenBLT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You have received a copy of the GNU General Public License along with OpenBLT. It +* should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy. +* +* \endinternal +****************************************************************************************/ +/************************************************************************************//** +* \defgroup NetAccess TCP/IP Network Access +* \brief This module implements a generic TCP/IP network access client driver. +* \ingroup Session +****************************************************************************************/ +#ifndef NETACCESS_H +#define NETACCESS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************************** +* Function prototypes +****************************************************************************************/ +void NetAccessInit(void); +void NetAccessTerminate(void); +bool NetAccessConnect(char const * address, uint16_t port); +void NetAccessDisconnect(void); +bool NetAccessSend(uint8_t const * data, uint32_t length); +bool NetAccessReceive(uint8_t * data, uint32_t * length, uint32_t timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* NETACCESS_H */ +/********************************* end of netaccess.h **********************************/ + diff --git a/Host/Source/LibOpenBLT/openblt.c b/Host/Source/LibOpenBLT/openblt.c index a0f78042..c8e07251 100644 --- a/Host/Source/LibOpenBLT/openblt.c +++ b/Host/Source/LibOpenBLT/openblt.c @@ -41,6 +41,7 @@ #include "xcptpuart.h" /* XCP UART transport layer */ #include "xcptpcan.h" /* XCP CAN transport layer */ #include "xcptpusb.h" /* XCP USB transport layer */ +#include "xcptpnet.h" /* XCP TCP/IP transport layer */ /**************************************************************************************** @@ -119,7 +120,8 @@ LIBOPENBLT_EXPORT void BltSessionInit(uint32_t sessionType, assert(sessionType == BLT_SESSION_XCP_V10); assert( (transportType == BLT_TRANSPORT_XCP_V10_RS232) || \ (transportType == BLT_TRANSPORT_XCP_V10_CAN) || \ - (transportType == BLT_TRANSPORT_XCP_V10_USB) ); + (transportType == BLT_TRANSPORT_XCP_V10_USB) || \ + (transportType == BLT_TRANSPORT_XCP_V10_NET) ); /* Initialize the correct session. */ if (sessionType == BLT_SESSION_XCP_V10) /*lint !e774 */ @@ -207,6 +209,32 @@ LIBOPENBLT_EXPORT void BltSessionInit(uint32_t sessionType, /* Link the transport layer to the XCP loader settings. */ xcpLoaderSettings.transport = XcpTpUsbGetTransport(); } + else if (transportType == BLT_TRANSPORT_XCP_V10_NET) + { + /* Verify transportSettings parameters because the XCP NET transport layer + * requires them. + */ + assert(transportSettings != NULL); + /* Only continue if the transportSettings parameter is valid. */ + if (transportSettings != NULL) /*lint !e774 */ + { + /* Cast transport settings to the correct type. */ + tBltTransportSettingsXcpV10Net * bltTransportSettingsXcpV10NetPtr; + bltTransportSettingsXcpV10NetPtr = + (tBltTransportSettingsXcpV10Net * )transportSettings; + /* Convert transport settings to the format supported by the XCP NET transport + * layer. It was made static to make sure it doesn't get out of scope when + * used in xcpLoaderSettings. + */ + static tXcpTpNetSettings xcpTpNetSettings; + xcpTpNetSettings.address = bltTransportSettingsXcpV10NetPtr->address; + xcpTpNetSettings.port = bltTransportSettingsXcpV10NetPtr->port; + /* Store transport layer settings in the XCP loader settings. */ + xcpLoaderSettings.transportSettings = &xcpTpNetSettings; + /* Link the transport layer to the XCP loader settings. */ + xcpLoaderSettings.transport = XcpTpNetGetTransport(); + } + } /* Perform actual session initialization. */ SessionInit(XcpLoaderGetProtocol(), &xcpLoaderSettings); } diff --git a/Host/Source/LibOpenBLT/openblt.h b/Host/Source/LibOpenBLT/openblt.h index e6010c63..8bec7887 100644 --- a/Host/Source/LibOpenBLT/openblt.h +++ b/Host/Source/LibOpenBLT/openblt.h @@ -108,6 +108,11 @@ LIBOPENBLT_EXPORT char const * BltVersionGetString(void); */ #define BLT_TRANSPORT_XCP_V10_USB ((uint32_t)2u) +/** \brief Transport layer for the XCP v1.0 protocol that uses TCP/IP for data + * exchange. + */ +#define BLT_TRANSPORT_XCP_V10_NET ((uint32_t)3u) + /**************************************************************************************** * Type definitions @@ -160,6 +165,17 @@ typedef struct t_blt_transport_settings_xcp_v10_can uint32_t useExtended; /**< Boolean to configure 29-bit CAN identifiers. */ } tBltTransportSettingsXcpV10Can; +/** \brief Structure layout of the XCP version 1.0 RS232 transport layer settings. The + * portName field is platform dependent. On Linux based systems this should be + * the filename of the tty-device, such as "/dev/tty0". On Windows based systems + * it should be the name of the COM-port, such as "COM1". + */ +typedef struct t_blt_transport_settings_xcp_v10_net +{ + char const * address; /**< Target IP-address or hostname on the network. */ + uint16_t port; /**< TCP port to use. */ +} tBltTransportSettingsXcpV10Net; + /**************************************************************************************** * Function prototypes diff --git a/Host/Source/LibOpenBLT/port/linux/netaccess.c b/Host/Source/LibOpenBLT/port/linux/netaccess.c new file mode 100644 index 00000000..449426ce --- /dev/null +++ b/Host/Source/LibOpenBLT/port/linux/netaccess.c @@ -0,0 +1,332 @@ +/************************************************************************************//** +* \file port/linux/netaccess.c +* \brief TCP/IP network access source file. +* \ingroup NetAccess +* \internal +*---------------------------------------------------------------------------------------- +* C O P Y R I G H T +*---------------------------------------------------------------------------------------- +* Copyright (c) 2017 by Feaser http://www.feaser.com All rights reserved +* +*---------------------------------------------------------------------------------------- +* L I C E N S E +*---------------------------------------------------------------------------------------- +* This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or +* modify it under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* OpenBLT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You have received a copy of the GNU General Public License along with OpenBLT. It +* should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy. +* +* \endinternal +****************************************************************************************/ + +/**************************************************************************************** +* Include files +****************************************************************************************/ +#include /* for assertions */ +#include /* for standard integer types */ +#include /* for NULL declaration */ +#include /* for boolean type */ +#include /* for string utilities */ +#include /* for close */ +#include /* for socket */ +#include /* for inet_addr */ +#include /* for hostent */ +#include "netaccess.h" /* TCP/IP network access module */ + + +/**************************************************************************************** +* Macro definitions +****************************************************************************************/ +/** \brief Constant value that indicates that the network socket is invalid. */ +#define NETACCESS_INVALID_SOCKET (-1) + + +/**************************************************************************************** +* Local data declarations +****************************************************************************************/ +/** \brief The socket that is used as an endpoint for the TCP/IP network + * communication. + */ +static int netAccessSocket; + + +/************************************************************************************//** +** \brief Initializes the network access module. +** +****************************************************************************************/ +void NetAccessInit(void) +{ + /* Invalidate the socket. */ + netAccessSocket = NETACCESS_INVALID_SOCKET; +} /*** end of NetAccessInit ***/ + + +/************************************************************************************//** +** \brief Terminates the network access module. +** +****************************************************************************************/ +void NetAccessTerminate(void) +{ + /* Make sure to disconnect form the server. */ + NetAccessDisconnect(); +} /*** end of NetAccessTerminate ***/ + + +/************************************************************************************//** +** \brief Connects to the TCP/IP server at the specified address and the given port. +** \param address The address of the server. This can be a hostname (such as +** mydomain.com) or an IP address (such as 127.0.0.1). +** \param port The port number on the server to connect to. +** \return True if successful, false otherwise. +** +****************************************************************************************/ +bool NetAccessConnect(char const * address, uint16_t port) +{ + bool result = false; + struct addrinfo hints = { 0 }; + struct addrinfo * serverinfo = NULL; + struct sockaddr_in serverIPv4 = { 0 }; + struct sockaddr_in6 serverIPv6 = { 0 }; + bool serverIPv4found = false; + bool serverIPv6found = false; + + /* Check parameters. */ + assert(address != NULL); + assert(port > 0); + + /* Start by invalidating the socket. */ + netAccessSocket = NETACCESS_INVALID_SOCKET; + + /* Only continue with valid parameters. */ + if ( (address != NULL) && (port > 0) ) /*lint !e774 */ + { + /* Set result to true and only reset it to false upon detection of a problem. */ + result = true; + + /* Initialize hints structure to aid in hostname resolving. Note that AF_UNSPEC is + * used to support both IPv4 and IPv6. + */ + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = (int)SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + /* Attempt to resolve the hostname. This converts the hostname to an IP address, if + * it wasn't already an IP address. + */ + if (getaddrinfo(address, NULL, &hints, &serverinfo) != 0) + { + /* Could not resolve the hostname. */ + result = false; + } + /* Sanity check on the pointer that should now be initialized and contain data. */ + else + { + if (serverinfo == NULL) + { + result = false; + } + } + /* The serverinfo pointer now points to an array with results of the hostname + * resolving. We only need one, so grab the first valid one. Prefer IPv4 over IPv6. + */ + if (result) + { + /* Point to the first entry. */ + struct addrinfo * entry = serverinfo; + /* Loop over the entries until a valid one was found. */ + while (entry != NULL) + { + /* Does this entry contain an IPv4 address? */ + if (entry->ai_family == AF_INET) + { + /* Copy this one for later usage. */ + memcpy (&serverIPv4, entry->ai_addr, entry->ai_addrlen); + serverIPv4.sin_family = AF_INET; + serverIPv4.sin_port = htons(port); + /* Set flag so we know which socket address variable to use later on. */ + serverIPv4found = true; + /* No need to go over the other entries, since we found a valid one. */ + break; + } + /* Does this entry contain an IPv6 address? */ + if (entry->ai_family == AF_INET6) + { + /* Copy this one for later usage. */ + memcpy (&serverIPv6, entry->ai_addr, entry->ai_addrlen); + serverIPv6.sin6_family = AF_INET6; + serverIPv6.sin6_port = htons(port); + /* Set flag so we know which socket address variable to use later on. */ + serverIPv6found = true; + /* No need to go over the other entries, since we found a valid one. */ + break; + } + /* Move on to the next one. */ + entry = entry->ai_next; + } + } + /* Check that a valid entry was found. */ + if (result) + { + if ( (!serverIPv4found) && (!serverIPv6found) ) + { + result = false; + } + } + /* Create the socket. */ + if (result) + { + /* Create the socket based on the family type. */ + if (serverIPv4found) + { + netAccessSocket = socket(serverIPv4.sin_family, (int)SOCK_STREAM, IPPROTO_TCP); + } + else + { + netAccessSocket = socket(serverIPv6.sin6_family, (int)SOCK_STREAM, IPPROTO_TCP); + } + /* Check the socket. */ + if (netAccessSocket < 0) + { + /* Could not create the socket. */ + netAccessSocket = NETACCESS_INVALID_SOCKET; + result = false; + } + } + /* Connect the socket. */ + if (result) + { + /* Get the socket address pointer based on the family type. */ + struct sockaddr * server_address; + if (serverIPv4found) + { + server_address = (struct sockaddr *)&serverIPv4; /*lint !e740 */ + } + else + { + server_address = (struct sockaddr *)&serverIPv6; /*lint !e740 */ + } + /* Attempt to connect. */ + if (connect(netAccessSocket, server_address, sizeof(struct sockaddr)) < 0) + { + /* Could not connect. Close the socket and negate result value. */ + close(netAccessSocket); + netAccessSocket = NETACCESS_INVALID_SOCKET; + result = false; + } + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of NetAccessConnect ***/ + + +/************************************************************************************//** +** \brief Disconnects from the TCP/IP server. +** +****************************************************************************************/ +void NetAccessDisconnect(void) +{ + /* Close the socket if it is open. */ + if (netAccessSocket >= 0) + { + close(netAccessSocket); + netAccessSocket = NETACCESS_INVALID_SOCKET; + } +} /*** end of NetAccessDisconnect ***/ + + +/************************************************************************************//** +** \brief Sends data to the TCP/IP server. +** \param data Pointer to byte array with data to send. +** \param length Number of bytes to send. +** \return True if successful, false otherwise. +** +****************************************************************************************/ +bool NetAccessSend(uint8_t const * data, uint32_t length) +{ + bool result = false; + + /* Check parameters. */ + assert(data != NULL); + assert(length > 0); + + /* Only continue with valid parameters. */ + if ( (data != NULL) && (length > 0) ) /*lint !e774 */ + { + /* Only continue with a valid socket. */ + if (netAccessSocket >= 0) + { + /* Attempt to send the data. */ + if (send(netAccessSocket, data, length, 0) >= 0) + { + /* Successfully send the data. */ + result = true; + } + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of NetAccessSend ***/ + + +/************************************************************************************//** +** \brief Receives data from the TCP/IP server in a blocking manner. +** \param data Pointer to byte array to store the received data. +** \param length Holds the max number of bytes that can be stored into the byte +** array. This function also overwrites this value with the number of bytes +** that were actually received. +** \param timeout Timeout in milliseconds for the data reception. +** \return True if successful, false otherwise. +** +****************************************************************************************/ +bool NetAccessReceive(uint8_t * data, uint32_t * length, uint32_t timeout) +{ + bool result = false; + struct timeval tv; + ssize_t receivedLen; + + /* Check parameters. */ + assert(data != NULL); + assert(length != NULL); + assert(timeout > 0); + + /* Only continue with valid parameters. */ + if ( (data != NULL) && (length != NULL) && (timeout > 0) ) /*lint !e774 */ + { + /* Only continue with a valid socket. */ + if (netAccessSocket >= 0) + { + /* Configure the timeout for the receive operation. */ + tv.tv_sec = (__time_t)(timeout / 1000u); + tv.tv_usec = (timeout % 1000u) * 1000u; + if (setsockopt(netAccessSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(struct timeval)) >= 0) + { + /* Attempt to receive data. */ + receivedLen = recv(netAccessSocket, data, *length, 0); + /* Process the result. Everything < 0 indicate that an error occured. A value of + * zero is also treated as an error, since data was expected. + */ + if (receivedLen > 0) + { + /* Store the number of received bytes. */ + *length = (uint32_t)receivedLen; + /* Successfully received data. */ + result = true; + } + } + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of NetAccessReceive ***/ + + +/*********************************** end of netaccess.c ********************************/ + diff --git a/Host/Source/LibOpenBLT/port/windows/netaccess.c b/Host/Source/LibOpenBLT/port/windows/netaccess.c new file mode 100644 index 00000000..824f1576 --- /dev/null +++ b/Host/Source/LibOpenBLT/port/windows/netaccess.c @@ -0,0 +1,349 @@ +/************************************************************************************//** +* \file port/windows/netaccess.c +* \brief TCP/IP network access source file. +* \ingroup NetAccess +* \internal +*---------------------------------------------------------------------------------------- +* C O P Y R I G H T +*---------------------------------------------------------------------------------------- +* Copyright (c) 2017 by Feaser http://www.feaser.com All rights reserved +* +*---------------------------------------------------------------------------------------- +* L I C E N S E +*---------------------------------------------------------------------------------------- +* This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or +* modify it under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* OpenBLT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You have received a copy of the GNU General Public License along with OpenBLT. It +* should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy. +* +* \endinternal +****************************************************************************************/ + +/**************************************************************************************** +* Include files +****************************************************************************************/ +#include /* for assertions */ +#include /* for standard integer types */ +#include /* for NULL declaration */ +#include /* for boolean type */ +#include /* for WinSock2 definitions */ +#include /* for WinSock2 TCP/IP protocol extensions */ +#include "netaccess.h" /* TCP/IP network access module */ + + +/**************************************************************************************** +* Local data declarations +****************************************************************************************/ +/** \brief Boolean flag to keep track if the Winsock library is initialized. */ +static bool winsockInitialized; + +/** \brief The socket that is used as an endpoint for the TCP/IP network + * communication. + */ +static SOCKET netAccessSocket; + + +/************************************************************************************//** +** \brief Initializes the network access module. +** +****************************************************************************************/ +void NetAccessInit(void) +{ + WSADATA wsa; + + /* Invalidate the socket. */ + netAccessSocket = INVALID_SOCKET; + + /* Init locals. */ + winsockInitialized = false; + + /* Attempt to initialize the Winsock library. */ + if (WSAStartup(MAKEWORD(2,2),&wsa) == 0) + { + /* Update flag. */ + winsockInitialized = true; + } +} /*** end of NetAccessInit ***/ + + +/************************************************************************************//** +** \brief Terminates the network access module. +** +****************************************************************************************/ +void NetAccessTerminate(void) +{ + /* Make sure to disconnect form the server. */ + NetAccessDisconnect(); + + /* Cleanup the Winsock library, if it was initialized. */ + if (winsockInitialized) + { + /* Update flag. */ + winsockInitialized = false; + /* Free Winsock resources. */ + (void)WSACleanup(); + } +} /*** end of NetAccessTerminate ***/ + + +/************************************************************************************//** +** \brief Connects to the TCP/IP server at the specified address and the given port. +** \param address The address of the server. This can be a hostname (such as +** mydomain.com) or an IP address (such as 127.0.0.1). +** \param port The port number on the server to connect to. +** \return True if successful, false otherwise. +** +****************************************************************************************/ +bool NetAccessConnect(char const * address, uint16_t port) +{ + bool result = false; + struct addrinfo hints = { 0 }; + struct addrinfo * serverinfo = NULL; + struct sockaddr_in serverIPv4 = { 0 }; + struct sockaddr_in6 serverIPv6 = { 0 }; + bool serverIPv4found = false; + bool serverIPv6found = false; + + /* Check parameters. */ + assert(address != NULL); + assert(port > 0); + + /* Start by invalidating the socket. */ + netAccessSocket = INVALID_SOCKET; + + /* Only continue with valid parameters and an initialized Winsock. */ + if ( (address != NULL) && (port > 0) && (winsockInitialized) ) /*lint !e774 */ + { + /* Set result to true and only reset it to false upon detection of a problem. */ + result = true; + + /* Initialize hints structure to aid in hostname resolving. Note that AF_UNSPEC is + * used to support both IPv4 and IPv6. + */ + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = (int)SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + /* Attempt to resolve the hostname. This converts the hostname to an IP address, if + * it wasn't already an IP address. + */ + if (getaddrinfo(address, NULL, &hints, &serverinfo) != 0) + { + /* Could not resolve the hostname. */ + result = false; + } + /* Sanity check on the pointer that should now be initialized and contain data. */ + else + { + if (serverinfo == NULL) + { + result = false; + } + } + /* The serverinfo pointer now points to an array with results of the hostname + * resolving. We only need one, so grab the first valid one. Prefer IPv4 over IPv6. + */ + if (result) + { + /* Point to the first entry. */ + struct addrinfo * entry = serverinfo; + /* Loop over the entries until a valid one was found. */ + while (entry != NULL) + { + /* Does this entry contain an IPv4 address? */ + if (entry->ai_family == AF_INET) + { + /* Copy this one for later usage. */ + memcpy (&serverIPv4, entry->ai_addr, entry->ai_addrlen); + serverIPv4.sin_family = AF_INET; + serverIPv4.sin_port = htons(port); + /* Set flag so we know which socket address variable to use later on. */ + serverIPv4found = true; + /* No need to go over the other entries, since we found a valid one. */ + break; + } + /* Does this entry contain an IPv6 address? */ + if (entry->ai_family == AF_INET6) + { + /* Copy this one for later usage. */ + memcpy (&serverIPv6, entry->ai_addr, entry->ai_addrlen); + serverIPv6.sin6_family = AF_INET6; + serverIPv6.sin6_port = htons(port); + /* Set flag so we know which socket address variable to use later on. */ + serverIPv6found = true; + /* No need to go over the other entries, since we found a valid one. */ + break; + } + /* Move on to the next one. */ + entry = entry->ai_next; + } + } + /* Check that a valid entry was found. */ + if (result) + { + if ( (!serverIPv4found) && (!serverIPv6found) ) + { + result = false; + } + } + /* Create the socket. */ + if (result) + { + /* Create the socket based on the family type. */ + if (serverIPv4found) + { + netAccessSocket = socket(serverIPv4.sin_family, (int)SOCK_STREAM, (int)IPPROTO_TCP); + } + else + { + netAccessSocket = socket(serverIPv6.sin6_family, (int)SOCK_STREAM, (int)IPPROTO_TCP); + } + /* Check the socket. */ + if (netAccessSocket == INVALID_SOCKET) + { + /* Could not create the socket. */ + result = false; + } + } + /* Connect the socket. */ + if (result) + { + /* Get the socket address pointer based on the family type. */ + struct sockaddr * server_address; + if (serverIPv4found) + { + server_address = (struct sockaddr *)&serverIPv4; /*lint !e740 */ + } + else + { + server_address = (struct sockaddr *)&serverIPv6; /*lint !e740 */ + } + /* Attempt to connect. */ + if (connect(netAccessSocket, server_address, sizeof(struct sockaddr)) != 0) + { + /* Could not connect. Close the socket and negate result value. */ + (void)closesocket(netAccessSocket); + netAccessSocket = INVALID_SOCKET; + result = false; + } + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of NetAccessConnect ***/ + + +/************************************************************************************//** +** \brief Disconnects from the TCP/IP server. +** +****************************************************************************************/ +void NetAccessDisconnect(void) +{ + /* Only perform disconnect with an initialized Winsock. */ + if (winsockInitialized) + { + /* Close the socket if it is open. */ + if (netAccessSocket != INVALID_SOCKET) + { + (void)closesocket(netAccessSocket); + netAccessSocket = INVALID_SOCKET; + } + } +} /*** end of NetAccessDisconnect ***/ + + +/************************************************************************************//** +** \brief Sends data to the TCP/IP server. +** \param data Pointer to byte array with data to send. +** \param length Number of bytes to send. +** \return True if successful, false otherwise. +** +****************************************************************************************/ +bool NetAccessSend(uint8_t const * data, uint32_t length) +{ + bool result = false; + + /* Check parameters. */ + assert(data != NULL); + assert(length > 0); + + /* Only continue with valid parameters and an initialized Winsock. */ + if ( (data != NULL) && (length > 0) && (winsockInitialized) ) /*lint !e774 */ + { + /* Only continue with a valid socket. */ + if (netAccessSocket != INVALID_SOCKET) + { + /* Attempt to send the data. */ + if (send(netAccessSocket, (char const *)data, (int)length, 0) != SOCKET_ERROR) + { + /* Successfully send the data. */ + result = true; + } + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of NetAccessSend ***/ + + +/************************************************************************************//** +** \brief Receives data from the TCP/IP server in a blocking manner. +** \param data Pointer to byte array to store the received data. +** \param length Holds the max number of bytes that can be stored into the byte +** array. This function also overwrites this value with the number of bytes +** that were actually received. +** \param timeout Timeout in milliseconds for the data reception. +** \return True if successful, false otherwise. +** +****************************************************************************************/ +bool NetAccessReceive(uint8_t * data, uint32_t * length, uint32_t timeout) +{ + bool result = false; + int tv; + int receivedLen; + + /* Check parameters. */ + assert(data != NULL); + assert(length != NULL); + assert(timeout > 0); + + /* Only continue with valid parameters and an initialized Winsock. */ + if ( (data != NULL) && (length != NULL) && (timeout > 0) && + (winsockInitialized) ) /*lint !e774 */ + { + /* Only continue with a valid socket. */ + if (netAccessSocket != INVALID_SOCKET) + { + /* Configure the timeout for the receive operation. */ + tv = (int)timeout; + if (setsockopt(netAccessSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(int)) != SOCKET_ERROR) + { + /* Attempt to receive data. */ + receivedLen = recv(netAccessSocket, (char *)data, (int)*length, 0); + /* Process the result. Everything < 0 indicate that an error occured. A value of + * zero is also treated as an error, since data was expected. + */ + if ((receivedLen != SOCKET_ERROR) && (receivedLen > 0)) + { + /* Store the number of received bytes. */ + *length = (uint32_t)receivedLen; + /* Successfully received data. */ + result = true; + } + } + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of NetAccessReceive ***/ + + +/*********************************** end of netaccess.c ********************************/ + diff --git a/Host/Source/LibOpenBLT/xcptpnet.c b/Host/Source/LibOpenBLT/xcptpnet.c new file mode 100644 index 00000000..bd7e0f94 --- /dev/null +++ b/Host/Source/LibOpenBLT/xcptpnet.c @@ -0,0 +1,291 @@ +/************************************************************************************//** +* \file xcptpnet.c +* \brief XCP TCP/IP transport layer source file. +* \ingroup XcpTpNet +* \internal +*---------------------------------------------------------------------------------------- +* C O P Y R I G H T +*---------------------------------------------------------------------------------------- +* Copyright (c) 2017 by Feaser http://www.feaser.com All rights reserved +* +*---------------------------------------------------------------------------------------- +* L I C E N S E +*---------------------------------------------------------------------------------------- +* This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or +* modify it under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* OpenBLT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You have received a copy of the GNU General Public License along with OpenBLT. It +* should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy. +* +* \endinternal +****************************************************************************************/ + +/**************************************************************************************** +* Include files +****************************************************************************************/ +#include /* for assertions */ +#include /* for standard integer types */ +#include /* for NULL declaration */ +#include /* for boolean type */ +#include /* for standard library */ +#include /* for string library */ +#include "session.h" /* Communication session module */ +#include "xcploader.h" /* XCP loader module */ +#include "xcptpnet.h" /* XCP TCP/IP transport layer */ +#include "netaccess.h" /* TCP/IP network access module */ + + +/**************************************************************************************** +* Function prototypes +****************************************************************************************/ +static void XcpTpNetInit(void const * settings); +static void XcpTpNetTerminate(void); +static bool XcpTpNetConnect(void); +static void XcpTpNetDisconnect(void); +static bool XcpTpNetSendPacket(tXcpTransportPacket const * txPacket, + tXcpTransportPacket * rxPacket, uint16_t timeout); + + +/**************************************************************************************** +* Local constant declarations +****************************************************************************************/ +/** \brief XCP transport layer structure filled with TCP/IP specifics. */ +static const tXcpTransport netTransport = +{ + XcpTpNetInit, + XcpTpNetTerminate, + XcpTpNetConnect, + XcpTpNetDisconnect, + XcpTpNetSendPacket +}; + + +/**************************************************************************************** +* Local data declarations +****************************************************************************************/ +/** \brief The settings to use in this transport layer. */ +static tXcpTpNetSettings tpNetSettings; + +/** \brief Command receive object (CRO) counter. This counter starts at 1 with each new + * connection and is sent with each command packet. The counter gets incremented + * for each command packet, allowing the server to determine the correct order + * for the received commands. + */ +static uint32_t tpNetCroCounter; + + +/***********************************************************************************//** +** \brief Obtains a pointer to the transport layer structure, so that it can be +** linked to the XCP protocol module. +** \return Pointer to transport layer structure. +** +****************************************************************************************/ +tXcpTransport const * XcpTpNetGetTransport(void) +{ + return &netTransport; +} /*** end of XcpTpNetGetTransport ***/ + + +/************************************************************************************//** +** \brief Initializes the transport layer. +** \param settings Pointer to settings structure. +** \return None. +** +****************************************************************************************/ +static void XcpTpNetInit(void const * settings) +{ + char * netAddress; + + /* Reset transport layer settings. */ + tpNetSettings.address = NULL; + tpNetSettings.port = 0; + + /* Check parameters. */ + assert(settings != NULL); + + /* Only continue with valid parameters. */ + if (settings != NULL) /*lint !e774 */ + { + /* Shallow copy the transport layer settings for layer usage. */ + tpNetSettings = *((tXcpTpNetSettings *)settings); + /* The address is a pointer and it is not guaranteed that it stays valid so we need + * to deep copy this one. note the +1 for '\0' in malloc. + */ + assert(((tXcpTpNetSettings *)settings)->address != NULL); + if (((tXcpTpNetSettings *)settings)->address != NULL) /*lint !e774 */ + { + netAddress = malloc(strlen(((tXcpTpNetSettings *)settings)->address) + 1); + assert(netAddress != NULL); + if (netAddress != NULL) /*lint !e774 */ + { + strcpy(netAddress, ((tXcpTpNetSettings *)settings)->address); + tpNetSettings.address = netAddress; + } + } + } + /* Initialize the network access module. */ + NetAccessInit(); +} /*** end of XcpTpNetInit ***/ + + +/************************************************************************************//** +** \brief Terminates the transport layer. +** +****************************************************************************************/ +static void XcpTpNetTerminate(void) +{ + /* Terminate the network access module. */ + NetAccessTerminate(); + /* Release memory that was allocated for storing the network address. */ + if (tpNetSettings.address != NULL) + { + free((char *)tpNetSettings.address); + } + /* Reset transport layer settings. */ + tpNetSettings.address = NULL; + tpNetSettings.port = 0; +} /*** end of XcpTpNetTerminate ***/ + + +/************************************************************************************//** +** \brief Connects to the transport layer. +** \return True is connected, false otherwise. +** +****************************************************************************************/ +static bool XcpTpNetConnect(void) +{ + bool result = false; + + /* Check transport layer settings. */ + assert(tpNetSettings.address != NULL); + assert(tpNetSettings.port != 0); + + /* Initialize the CRO counter. */ + tpNetCroCounter = 1; + + /* Only continue if the transport layer settings are valid. */ + if ( (tpNetSettings.address != NULL) && (tpNetSettings.port != 0) ) /*lint !e774 */ + { + /* Connect via the network access module. */ + result = NetAccessConnect(tpNetSettings.address, tpNetSettings.port); + } + /* Give the result back to the caller. */ + return result; +} /*** end of XcpTpNetConnect ***/ + + +/************************************************************************************//** +** \brief Disconnects from the transport layer. +** +****************************************************************************************/ +static void XcpTpNetDisconnect(void) +{ + /* Disconnect via the network access module. */ + NetAccessDisconnect(); +} /*** end of XcpTpNetDisconnect ***/ + + +/************************************************************************************//** +** \brief Transmits an XCP packet on the transport layer and attempts to receive the +** response packet within the specified timeout. +** \param txPacket Pointer to the packet to transmit. +** \param rxPacket Pointer where the received packet info is stored. +** \param timeout Maximum time in milliseconds to wait for the reception of the +** response packet. +** \return True is successful and a response packet was received, false otherwise. +** +****************************************************************************************/ +static bool XcpTpNetSendPacket(tXcpTransportPacket const * txPacket, + tXcpTransportPacket * rxPacket, uint16_t timeout) +{ + bool result = false; + uint16_t byteIdx; + /* netBuffer is static to lower the stack load. +4 because the CRO counter value is + * added at the start of the packet. + */ + static uint8_t netBuffer[XCPLOADER_PACKET_SIZE_MAX + 4]; + + /* Check parameters. */ + assert(txPacket != NULL); + assert(rxPacket != NULL); + + /* Only continue with valid parameters. */ + if ( (txPacket != NULL) && (rxPacket != NULL) ) /*lint !e774 */ + { + /* Set result value to okay and only change it from now on if an error occurred. */ + result = true; + /* Prepare the XCP packet for transmission via TCP/IP. This is basically the same + * as the XCP packet data but just the CRO counter of the packet is added to the + * first four bytes. + */ + netBuffer[0] = (uint8_t)tpNetCroCounter; + netBuffer[1] = (uint8_t)(tpNetCroCounter >> 8); + netBuffer[2] = (uint8_t)(tpNetCroCounter >> 16); + netBuffer[3] = (uint8_t)(tpNetCroCounter >> 24); + /* Increment the CRO counter for the next packet. */ + tpNetCroCounter++; + /* Copy the actual packet data. */ + for (byteIdx=0; byteIdxlen; byteIdx++) + { + netBuffer[byteIdx + 4] = txPacket->data[byteIdx]; + } + /* Send the packet. */ + if (!NetAccessSend(netBuffer, txPacket->len + 4)) + { + result = false; + } + + /* Only continue if the packet was successfully sent. */ + uint32_t netRxLength = 0; + if (result) + { + /* Reset the length of the received packet data. */ + rxPacket->len = 0; + /* Set the maximum allowed length of the response packet. */ + netRxLength = sizeof(netBuffer)/sizeof(netBuffer[0]); + /* Attempt to receive the response within the specified timeout. */ + if (!NetAccessReceive(netBuffer, &netRxLength, timeout)) + { + result = false; + } + } + /* Only continue if a response packet was received. */ + if (result) + { + /* Validate the response length. It must at least have a DTO counter (32-bits) and + * one byte in the response data. It can also not be longer than the maximum + * allowed size, based on the size of the buffer. + */ + if ( (netRxLength < 5) || + (netRxLength > (sizeof(netBuffer)/sizeof(netBuffer[0]))) ) + { + /* Invalid length. */ + result = false; + } + } + /* Only continue if the response packet has a valid length. */ + if (result) + { + /* The first four bytes contain a DTO counter in which we are not really + * interested. + */ + rxPacket->len = (uint8_t)(netRxLength - 4); + /* Copy the received packet data. */ + for (byteIdx=0; byteIdxlen; byteIdx++) + { + rxPacket->data[byteIdx] = netBuffer[byteIdx + 4]; + } + } + } + /* Give the result back to the caller. */ + return result; +} /*** end of XcpTpNetSendPacket ***/ + + +/*********************************** end of xcptpnet.c *********************************/ diff --git a/Host/Source/LibOpenBLT/xcptpnet.h b/Host/Source/LibOpenBLT/xcptpnet.h new file mode 100644 index 00000000..18060e58 --- /dev/null +++ b/Host/Source/LibOpenBLT/xcptpnet.h @@ -0,0 +1,63 @@ +/************************************************************************************//** +* \file xcptpnet.h +* \brief XCP TCP/IP transport layer header file. +* \ingroup XcpTpNet +* \internal +*---------------------------------------------------------------------------------------- +* C O P Y R I G H T +*---------------------------------------------------------------------------------------- +* Copyright (c) 2017 by Feaser http://www.feaser.com All rights reserved +* +*---------------------------------------------------------------------------------------- +* L I C E N S E +*---------------------------------------------------------------------------------------- +* This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or +* modify it under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* OpenBLT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You have received a copy of the GNU General Public License along with OpenBLT. It +* should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy. +* +* \endinternal +****************************************************************************************/ +/************************************************************************************//** +* \defgroup XcpTpNet XCP TCP/IP transport layer +* \brief This module implements the XCP transport layer for TCP/IP. +* \ingroup XcpLoader +****************************************************************************************/ +#ifndef XCPTPNET_H +#define XCPTPNET_H + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************************** +* Type definitions +****************************************************************************************/ +/** \brief Layout of structure with settings specific to the XCP transport layer module + * for TCP/IP. + */ +typedef struct t_xcp_tp_net_settings +{ + char const * address; /**< Target IP-address or hostname on the network. */ + uint16_t port; /**< TCP port to use. */ +} tXcpTpNetSettings; + + +/*************************************************************************************** +* Function prototypes +****************************************************************************************/ +tXcpTransport const * XcpTpNetGetTransport(void); + +#ifdef __cplusplus +} +#endif + +#endif /* XCPTPNET_H */ +/*********************************** end of xcptpnet.h *********************************/ diff --git a/Host/Source/LibOpenBLT/xcptpuart.c b/Host/Source/LibOpenBLT/xcptpuart.c index ec3634ea..dffe6126 100644 --- a/Host/Source/LibOpenBLT/xcptpuart.c +++ b/Host/Source/LibOpenBLT/xcptpuart.c @@ -227,7 +227,7 @@ static bool XcpTpUartSendPacket(tXcpTransportPacket const * txPacket, bool result = false; uint16_t byteIdx; /* uartBuffer is static to lower the stack load. +1 because the first byte for an XCP - * packet on the UART transport layer contains the packet lenght. + * packet on the UART transport layer contains the packet length. */ static uint8_t uartBuffer[XCPLOADER_PACKET_SIZE_MAX + 1]; uint32_t responseTimeoutTime = 0; diff --git a/Host/libopenblt.dll b/Host/libopenblt.dll index bb13a9621a6dddf82d4d90bd2154e2590a4d6fe9..b00053c0eb9a26eb4f75e56af8f691fa04a5efb5 100644 GIT binary patch literal 165376 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 Q_>}k0ivceNnh*y54;{r$v;Y7A delta 40938 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�!;5&G)<@?*oXtcd|x{v9-R<3kOX&*#l}}UDsrH5 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