From 7e6e442e0ddfe28e5c1a24e65d488e34f0217fa5 Mon Sep 17 00:00:00 2001 From: TomAwezome Date: Fri, 27 Mar 2020 14:06:16 -0400 Subject: [PATCH] added more Net code still WIP, included Tom palettes, hasty alteration to 3D model editor slider to allow higher precision. --- ...o => Zenith-latest-2020-03-27-14_00_22.iso | Bin 34662400 -> 34738176 bytes src/Home/MakeHome.CC | 0 src/Home/Net/ARP.CC | 11 +- src/Home/Net/{ => Docs}/MappingShrineAlgs.DD | Bin src/Home/Net/{ => Docs}/NetFuncSummary.DD | 28 +- src/Home/Net/{ => Docs}/NetQueueDiagram.DD | Bin src/Home/Net/{ => Docs}/NetworkingNotes.DD | 33 +- src/Home/Net/Docs/SocketMapping.DD | Bin 0 -> 1312 bytes src/Home/Net/Docs/SocketNotes.DD | 182 ++++++ src/Home/Net/Docs/StrToAddressFSM.DD | Bin 0 -> 516 bytes src/Home/Net/Docs/UDPTreeNotes.DD | 43 ++ src/Home/Net/Docs/ZenithStackNotes.DD | Bin 0 -> 294 bytes src/Home/Net/Ethernet.CC | 12 +- src/Home/Net/ICMP.CC | 83 +++ src/Home/Net/IPV4.CC | 324 +++++++++ src/Home/Net/NetHandlerTask.CC | 26 +- src/Home/Net/NetQueue.CC | 25 +- src/Home/Net/PCNet.CC | 365 ++++++++--- src/Home/Net/Sockets.CC | 488 ++++++++++++++ src/Home/Net/Tests/Test.CC | 94 +++ src/Home/Net/Tests/Test2.CC | 220 +++++++ src/Home/Net/Tests/Test3.CC | Bin 0 -> 5817 bytes src/Home/Net/Tests/Test4.CC | Bin 0 -> 5914 bytes src/Home/Net/Tests/Test5.CC | Bin 0 -> 4465 bytes src/Home/Net/Tests/Test6.CC | 90 +++ src/Home/Net/Tests/Test7.CC | 129 ++++ src/Home/Net/UDP.CC | 614 ++++++++++++++++++ src/Home/Net/ZenithStackNotes.DD | Bin 199 -> 0 bytes src/Home/Registry.CC | 10 - src/Zenith/Ctrls/CtrlsA.CC | 6 +- src/Zenith/Gr/GrPalette.CC | 26 + 31 files changed, 2665 insertions(+), 144 deletions(-) rename Zenith-latest-2020-03-20-19_32_59.iso => Zenith-latest-2020-03-27-14_00_22.iso (99%) mode change 100644 => 100755 mode change 100644 => 100755 src/Home/MakeHome.CC rename src/Home/Net/{ => Docs}/MappingShrineAlgs.DD (100%) rename src/Home/Net/{ => Docs}/NetFuncSummary.DD (65%) rename src/Home/Net/{ => Docs}/NetQueueDiagram.DD (100%) rename src/Home/Net/{ => Docs}/NetworkingNotes.DD (56%) create mode 100755 src/Home/Net/Docs/SocketMapping.DD create mode 100755 src/Home/Net/Docs/SocketNotes.DD create mode 100755 src/Home/Net/Docs/StrToAddressFSM.DD create mode 100755 src/Home/Net/Docs/UDPTreeNotes.DD create mode 100755 src/Home/Net/Docs/ZenithStackNotes.DD create mode 100755 src/Home/Net/ICMP.CC create mode 100755 src/Home/Net/IPV4.CC create mode 100755 src/Home/Net/Sockets.CC create mode 100755 src/Home/Net/Tests/Test.CC create mode 100755 src/Home/Net/Tests/Test2.CC create mode 100755 src/Home/Net/Tests/Test3.CC create mode 100755 src/Home/Net/Tests/Test4.CC create mode 100755 src/Home/Net/Tests/Test5.CC create mode 100755 src/Home/Net/Tests/Test6.CC create mode 100755 src/Home/Net/Tests/Test7.CC create mode 100755 src/Home/Net/UDP.CC delete mode 100755 src/Home/Net/ZenithStackNotes.DD delete mode 100644 src/Home/Registry.CC diff --git a/Zenith-latest-2020-03-20-19_32_59.iso b/Zenith-latest-2020-03-27-14_00_22.iso old mode 100644 new mode 100755 similarity index 99% rename from Zenith-latest-2020-03-20-19_32_59.iso rename to Zenith-latest-2020-03-27-14_00_22.iso index 052cb0ec656a67fffac595fe72540b683124446c..2cafc90309f7f6dcdefd41aa27c1b65a8524371a GIT binary patch delta 115256 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

3)LpnDl@jwro`&o&JYQ21!>Y-W~_Kqn` z>_h%i{lBvNf3NJ$Nqnn^1Jn75mmXkOCDvU=%hn`DbIMFfUE+FOPYUVc#5F1%B(G`b zUQp)`)hEVy^mKH*(t7d1cxmyn#3@6%IU~6>v8RK(7ZQ*5Xu@Gv;=vx#zm>Qn=#T$f z`efpw|B$P1=O^_ny=szHT%Gyw;AztYQ*f}DF2qW1wxsKN)PKS4_a&VpI)8Z*)A>^S zilhS(_`ACX>apit16^>H&i5Mpuv-5h{>vHN#6ilH-DoKNPdDk~k^YL@$CG~Cm4vTm zGUQhBMb(nq2T2P!*5{H(KubSNI_CCAmZv^XI?2&J7x=`NNe^k(g^!8FOTS907!Z3M zxih@(pDc!S`;2kC!*?^ zTfHweH*VRT& zra#Jry~N7odp)D$gJ>{tD-P|sGkXMP{CR+80G|C#(om}{i`pxf_-yO@`l6``Zm99Y zYh`aru)J|$a=gH2rtryGd}elbJRG($XNo11?#e&%P;GT-QAsWDliS&Uq)cFdzv!Vd3mT~Anv#-l zobn%Y$YN!3&l-~l;g7CX`VWMR8!4`blwk`JU2ac}je|>s;Zeq#O8>|B4E4Xfn|wyZ z$5K94r%Q))A5%t$92hCPn+R3{R;FNLV@JeyB~Z&|%7%k$4Nj>Pej1P7?w!;5fW~40mPx-gt;*{zh zb`qDQJf`t_8RnfHYDkIe(TILU%0R!XT>|D_u6TCW?Kg6nX7Z+#cX~K~A*J+iVf)RL zTmR5Tt9Mfl^=Mr8ams-nj>p2AtIdz$=FxFR_)T)J z)j#RZS94D%INZ!V9p?8of6Qqk3d{Cn!ED$Eea)~T zw5Yc9M)<=_$pG^_kDknP1euf6^dQE<3&Uci)M4hkbUkaOBF!K54eS1&pQLuPxj?16 zaKV78w4iKeist84^1s{rWtyA3dbIyzhWU?GlOl7^s>ytFKQ#wi|O?~mZWyg_7mNxEnY znN!oc9+$Dl%{OygzEt&udAeF7FVerg(cD+%qB9TU+ZGKyWuE~p&{;|m{s5H$v zP_Dk(Z2n?+^hH$+p~sTw?&K5nC&rE)LLwlXx;#dMw?&bT=?aC zpjf?p&g{{{WnC^chO;&gx{i*?7_RcC=AC{N`!HPMOU>H`QtZWWfe$rr@unCtoHKx$ z*Y&5ktsliLA5WLVV`E9Y`I{u)zHDE0JGZ2flbX-DSXyr`jFXC8 zQm3etXlqjkjOj`JH>ch<`p?wk2+g~f_M4hJ{xeem5mUw+5OL|8HT7tJ{zNV%T;DP0 z!~>zv%k=sRFX_x*Qxo0kI=bVp;vP-4UuE)W>dl<8(Ega+dhuhaStozzo|lyuE}q22 zi|5q{zpx)M-7TnebaPY?(XPb)=e-WM~@kH_a%0{uW7x(Wz` zaVbH=q$gLUP2*O0C9PhaX5S~U+8)ID)oJ1V_XkoomE=!Z@VtHS-dEFb0tTl;%xOP4 zZUYZ)zW$Xoe4aRc%AEYk*p)m#j@=8s8o}uZb9zzU%88Kwdr{!Dm^gi|FwCT$9(h8w z51bxI3s<=Y;f)WZjqR^zk-Lj+L9g!Zx(C7}ByK@C>_H%LXC$|gV@dFY4cYq_;PMaz z;K`icq%LUnsYm=ktTk!j-d@ZpfJuDI5A$}XrMRu&cCEf9O)u$l#q(Di!bLhUJ#PzUOjF zZAoP@*#TW`sV%H7wiHM3nYDa{#Zp|um%u#;5z^NA;-g$pT{Dp_VqL(NE)))+W*=v4f zUG4C(#f7znd;%}q@Zr2EMiy7BE1J(Qs;uLulvI~iSUP#ZmI|@2$`pg|uMnsH^+4*m z-@W>rzfwGptNNtt@cLEaGx{lcQ*zQKq^70fTmLE+d*wVywca|?tidCFb8l(`=Rpk! zEk8aCHpE!I%^j_ZjKu2IVhCjE@YUjwFy2yLRlA5rsV2e@8JRhUFRPqOZc*TCEBVs$ zsxnKtrJ`2g@hz*xAn^9cYB5k_07|@XwK!}bUs%HzR`5Vly@)TZfXp&asl z;3wo|WsQb3g`!he&BB2bsnVtg#H}2@^C2}lqO!VT3m*tJkY{rZ;BfZ=Rf%JN|4;L5We77B#?dZtD#cZNJ;YtXKWUe@q#U44M=ff zi#RQa6s{7k=GjpfKBuy}HWbg;EQaWla|FJqraE*qo&`p{{8_Pz z@o;nCA=7FIEnf7Tct22hY!#yi@*r7_LS~*anXjoTEV7VT&DbgqiUpU_+S0yR^-Kp$o$eRwV4xBAkCo;mqLt!o5XBf+awxY zVLwS^m>vi0gkt!5lb8UUG?Hk+Ws(@D3X89H`DOYV5gYm)b;tK3_MC9>bUJ>)~qdTCI z_T2#yNZKLh6XPtb-YHH|6;|N9oni{eyk@6Z2dMWhIR+zliLc-huZzBJ5C_xnk#QzH zWb046fOX=lvc9mqDr=kNuZqLeVUY%WYPaY+I43!EGI*#es(@POP=_MwOmWv4XW{x? zqQ6XJx)hPgfhg)yMDSbI$k)X@5O>MzvYy%Uy2yhv(dSN=3l9F|b#XPg*1RDG;Njbk zdE-rQhz9)P8)Akc>owP8Dcq|U=FhI1GsjXb*LX)!15+SAd6%IBX-#j6LH;bpwU_0T zYvB5 zPE*ic|FLzOC~zJ1-XFiW4T}HwABy841oKB?fm{py;KChyGY0h)?sKv%lS|LmV) zsv5dGc<2dnj6nW+P9Nx6j-!(odzZ?CO)`1W4Bbv;G-{O2{4H&i2H#&3k%r4lBmAV5 zff0i_<;H=6D(mhRmF) zF$9^ja}0RVhO~-72Ix7^OhAFKr<*?UtbdH--!hjpZA=@+^}et!hMw6w#AtA2YU(a6 z3XLGg$)I&3ag~K)(CzI^kFQu zH8N>!NomD=qI)xm?j!?_iqeG&1!;(YYC&c7e4Z$0qWf^Ev z=$65C-#{i0h{`KrkW*Y)RKr7WiBU&D?Da6Nf^^_R@c7MXgVbQgcW+J$lXh=Tt5bPL zz-UTIk?P7Ka_}ZN0{%^L?+b}zOg-LKjL+^*yGHc{!oG*np6qVg(@`Eo%;quq|1vTCPOk#kx=L)S2uFDE@~5C#lSeL5z0pn+-D9#&b_3UWRVm9L zd3>7ba(V%?hv9n;Zr&jJ;QKZ}C;F);MQ?9ZSxK_nC$Dn=O_S3fbXC<^@bJuZxVxpN z<2HP5M*4T&H4(_MC~aVIdctjKD%W&<`iK3b4@;mA(~bFxnsgr*{L!;%mdjR@o=xR1 zO8>|+w)^MviSVs-bNR}|k3#!L2C-Bng#;e?eqqO%y1UcE`^PFoq!cJbcsp^L6ilt; z`*rP3=ZVuY#feP|$*gh0J?Y`9w}SD!d(y|MnuGC1f?I;|w*;RE#$ihVp9goqoHu@Q zX?h|Th>sIA9EaWuC?1y*l#8DvXf{3$NHuQ=4qFC3>xSTY1TPqZHxhjN5d1B{_ksI; zfY*RK!Rx`D;OD@d;J3kjIpBA}o!}$jPH-Ez6Z|u{uK@fDxC17Vt$@%4e4L;-9C|;X zak!MAsrX5Pu7epykZk@?9JUgCHVnn{2)=J9-birMQ2Z^yuY>z4z)OeWc?549hBpH4 z-#Uz@tk+Vt{IMgYsyPdvUj@mxITVNeHT~DF^93?5ydzs#?$ESfygEIc8;PG>4bn}+ z#|fH?Lood3PD@{s7>k;7;(@WG)i$x8P3j88Yz*_&m4+=1lm>2SKVF>G*@`D-*8%B=^VZ zcU@!M6Yg~4o+T=|U2Z7*hKsA@uMRU9!JR%S$_L;xpcd(~A(HatnK^tGpHenI)p8>< z7evVJvrr4O*dTNk#A`S;2gB{qBw(%{UOqW8wQQEZ;B^T>bQ7Gf8|WXvZXrg`QP!lh z)f+f?>F8FXB>08one>C*yq&3UMz^;b4bJG{l4Nwo*Zqh6*7^fW0G#|(4qSUbE@O9h zPLda9Jbxwbe)3^S2zNhKX1p8PqxIG`Jxz^G8H;pMdB*tp+;3bnCS~jh%2xW;m%|9L z#X|Iu*?t1g3ldc6ck@c(PBLWqO1_6Pn%s=>%nn`V+77u1qKDBD=3dEx6Hl!fPkD?g zEuP!6%e5R5_{R)R|2wZ6xscJ*-KB#vd$t38NM`U{t7R^k5(9=u8qf=0E5n2egu=4K=R$Xma-Z- z$u3PFPwC^YlRYdZ_blcak-4mJK@GY137K;XYv!Y{TJkohno$^pDF5{ znc>_N{N#a5xp^dL1`d5EGf`Cy{gHPv$Es?eze4cM&|e|=&R{95IdemgHeP+48LQSA zS)T~Qn#-Q1qnU1|KeMg7Ts2qRu59?-K5-gkS!m&JHW!Crz1O%0`vp6U*`~9OHeR+c z=^E+Hn^S{0&>9g4`r}P~#_jFdwIc!JntG4x{D<6vmjdN-y~ia$#`RE+*t#dhKGeRe zi#(Ag}c*3yY3xdu5f0I3s>C-?7fEnjuz2m-38B{+zlw`E$w&=hnbw z0ihkbR&h_QEP@}m%hTE9HUYaXR@}2-HppCJDVi^q!m)#k-Ngy1`BTPc=H!c+S!oC^ zcXk$l{0o5j1@9L#=#a0nNC-p|oc)uP_cBXMR89ic;uXd_a4vy4e20!o{O3nLt8 zMYfOI{l|I%`^GK#UvHiC)-%pz%Bf6x>;D}0S$8_U>hat8ijJ{K&R$r~2Tm;j6yk|zcr3;dCQI>%=psb@IrFO@Lr-9BApEkxLp{Ld z7@DGuBce@k1#bmwhhL=(NcJuVze)hGd2z=0mhS9DtLz@xUEL#laQuBEtUWxt17g!i z+l7~H5CT5(>88sa-65n(u9-6mYf4frlJrgja;moWaPCv}b>h2ZQ3$hlDmwzLpq!G{Zyhk+# z{L};d&I;4ZxyHPfb%ze$vNZD6OO2Yl_fOmXS>3vu@9V7H(Z6GSA4YeP!UW2|?1N+N zog_s=p+@?@h&Bzymr5U{&ZL#jEvbbxRPsUL<8v+bAMet^hmFtVYtk9L)n55VL>Z%i z&qa**>5PE`qpmVc5eWEnse0oBD4#u@_^>m+GDCK-53wu1iAp4~x5o@rV)l-ZD?6rB zMvC3XS37+3Z;A^2&d25E68SzUW0I0vAc-AP@{a6xdv;g?XFoplYDfKQhd=8#lm0gd z5Gs3t=FG9Z_M9QZbCNSs`;P3dyVI*ZnC%yuL2AsuajM+0^UY28q&vq!M^C`lUC|sv z=p%M3aBasH{v1aQ0pH{ye<6Xp2%<9MBtzo_%YRl^V#(R96Qr|SCzjop-quFtg$jH0 z=}rsuNk^y|wKL2J$wIrA;tB8|05811(I5mUwvTR>IYFJT7zuXvoggnlaH|@~_YNokGW+ax#^fD2=Nb0&#gFL$6;kz$~ zztg!6F5x;dt8sbfV3LxejKo*LeVv18V8JTEv?*p53{+t`7!vuuq&~x=!T0K1eKH+F z+vCJl$IJ>>>JJvVorEWez=U^XXJo=FX;IZpc4YR5u~UO|O?T(cbFso+U{qSBG%Gw4 zMV_OXSTUs*HW?Bt#a+pkx*FJcU3{@U?42i9O4#WYepAzvv-8s@C!^URRC(5iQRO)R z+BGi-z~`rM*@Aq-v(gHC0G@*hCi#(*xQjgi%X7o~|D!7+oB&5-RL4SLjXW0?bzxb0 z1>he;VU0X@EH@q2$aCezTtKR>Imk1fb^(|iY_)n-Zn&x`6jsB5=XMB#;9X?t8}M!j zgW&z(PVoESPVk4|z8df$a3}aMxD$L7+zD<0cMSMTa3}cdP-!F1?cXc8=c`Xn+fwkU zEx7}6!O`4QY1aDOI&DO^uUC^}?dbiP+`*UcoOl1%SPnvM%bo3U1*4fH&Dfp0Xi!g1 zL429pSFMUilGiu6(ZWX#<|cci znH7Pc2oJGIEW6{j6S*R6G&8QH$a5cTTV4j`)$_H%&-k0ob6%>$SUFb!Zjzzl$ErL&)2 z_sKCX9d8QI1ZaOeMGN_}>r)5Ap*j0Mxcpt>$?g`cd&PUoFtqU*+Qu}{_{$SL_I;Hw zt4ZhaR_*|u_4aU<>TIBP(Y~eEXNG(4?}{kJ?O86FTfKW`NdGR~iAM6avuB~m?1|6Y zxYc{W=K+PYjuKoH{GOTN{ohlF9Q7*RPMi)Y19|53@&gOW<{skIn`7-8iy3uF80iz< ze}v-1LZwcN-vVC~)y!8Xr|rb)2N&k#N@ZnX5vOuj=F~}yguOGvRqwmvd3$G$Rej}( zHxm3$SNtu(|8m7)`vA9tJ7B04PwtzU$Z7F$g1m6(+kkv=DM3;ANrH;-aX_kSEe_ic zKI^o29>KS2@kWAIXz{lMuLk!6fVY7=!8^g7;Mc*O-~-_P&dguCPFA@cvFD0ouHnVa z5S2!sIYUN18_RQKnH*Cf*1Sk4mdY8z>!qySHw*9}~QUd1$Dk*Rh5C ztuPKR4bWth>k07T08Ow>m}L{Lw+Z<+p}-~-+JxCQp~xl_+XRbEm}3*>+Jq9DP-+wA z*@XEvq0ASYs0&un7;^gokXx-)zFeHeszzSZ5O+u?dgb zgumN_$85smHetO@c)})ZunAAvgpD@gDVy-LP1s};p0Np=ZNe6t@T^UE&L(WN3IDJO z&)bA1n;_YQZ8qTroA9Ddc*!QbY!kNIgja0B4x6ykChW2auiAvyY{Kg{;SHOx+a|ne z6ZY7Iw`{^*o3PI&yloTq+k^u);T@aMY!lwK3Gdm2_ie%lHsM2?@R3b8XcG?EgpX~) zCpO_zn{e1B9I**UZNg_Zp~WVAZWF$+318ZTuWZ8CHsP2}_{Ju*+Jxga;ai*VPn&SU zCY-bh-`RxkZ9Nr+Kwp4<0Q~_50C)rV z01O211@Hs#2N(o!4ZvUk9v}c95FiL37+?s%P=H|oApoHOVF2L(MBd>55de_@MgS8) z6hJgU3_vVE9KZ;Gcz}@rqX0$&i~&dh7z>aHkOYtnkOE)^NCij(5CPHwG5|6G#sQ26 z$O6a)m;f*lAO~O)z+`|a0J#8p08;^`0Za#&0dOtAbpSH~W&vCekPlD*PzW#^pa`HC zzydG_U@kxjKq%20B#4k1K>`8y8vteO8^=G{sM3}z&!v<0qzA@25=w1a)1>8_XDg1SOxG` zfYksPpb=mVzykme0z3rpH-Lu$)&i^pcm&{4fWHGg2Jkq*dVnVYHUK;cuo2)XfTsaA z0Xze+8DI;*vjEQlYz6oS!1Dl202079fENH>1b7MHWq|DfuK?@-*a@%;;8lRv0A2@p z17J76n*e(N-U8SQun*vEfc*dm0Nw#;26z|XJ%INCJ^=U-;3I&80EYlR2KWTvQ-H$& zM*xljdO6D%DOsetBu+*gvw2v*X+am8vKAK<+rFRjc08 z8;dIIDrysqLS%@S-jj&Zg;fzR;#+XBY4OEEAME7qPnj4o&AR0WmoyC3alDi zmMUTp)FuUF0Wm5|U`67XEF_{XDw>HlHc2a!Nt&cnr!pk2P1TEyod&e&7^_aycukwm z`27((wshJNXW|5C+G1RzEA7~G?|Tn{;IfMtX4?1To%7y3_nrIhd-vXR?>YCJK#~TA z)nELXxN4FHM%ymD|LDUT6es0}n)m zhChCLLvBKY&FK7~M5cKIr>y+AeB|vwdM}@|J&uPG~fyQWJoHkB0 zf%6g?{pZz5{=8Xf;;A)J2L_g8*1@kMF;!rJ6dhcR z8&o1d%QwA2;u7kaK?3W%1GNth(`k?V;vJOVtRM#_PQ(Z-mK>C~NZn>Vt0+JqHTCUs7*A)Gg&ctU1RCT|Esa%F- z0iParZJzwh2C`;Zs;fd(oi@p`CUKPQ7s5-ITZa6a%h9ps4dwEk`s=rH8>CoMvdB@7 z)&bKFYsNO%wEm_yGaZkr4^-DGYnFPs>#LrQQf2gKg;9H*{MZd8Dx(ZfA4dLTu1vkA zo){HAI!FCJQADCYARzzPTxeH0J6Nz=kG(+{4b*N3UlvF*Kp4g|*ys(=Hn@=u3obCg zrswF7e%39OC6VcJ`~!j?l#U0S_79p{G1(TR~Q5ydZ8w;Z85Uu-`kr^~tNOpg&_*h5E+uwuOb@$Vb% z!+bwPpTG;ke;RL z>!A63VXHJ&@jK0HbE~LJ)aa~Uzp7?~!>#@dHWTcY^45-#?M5656}tB0{_>t9xXTFD zG~|4o9R5UITA1ybi;n#Y3f(1+%C+dBQ29BUJ~u%WOBrQ?eUaKhsO1@Bg4muYh<&z8 zsxO*sf<}AA@a^g2wbO2wUtIpzSbPTZC?3jGE;AAbMoML?OkXg;jW~%gbGre~Na5-r zdsV%iee+Ed-1*f;?(U2T!y>ZwwruR?P?(h!&7VDInYUnW8FA(Fd)NA=7kk!ui^^s% zDD;+k$}oBD)kdM5cW_!f3fwM_*E2hxaF14a-Trys{MfBzZ=8HC19qI_>jVWr2LKYt!cBk9F$ebey`3yZvq++_vcMd-Yew13*S z29N=Neg*OtHPe2nfBvUj<>kU@SYe3=smO)C1n5?*%v*sjR^4UZxdmw7a7Iq&q~hph z;Kw_`o}M^GPF5R2P87tA_&jLg@XiJ0H^p%fBy7(@-H&d@a2dnjT z6f7$IIwnbkK!NPUt*f}lnM5fT21Q+qunZ{f_r*s=gLlMz3h;#OzMen7%?w8(TModI zerbk%(P4#a!bz6}rjHufNGOzi9cw{-*_S%nf=CW@qj<^@Nutg;i&zfz37ANm1x%wI z0%lQXJWu})^$D0mn+43J9Rkjy&IF!bN__%W&}IRvXorALQ)ePiUrK!(Lam;)AmWaq zUH<{7lG{f1(AoyOJ<$S}!mYQd7C1F!2%DPVtQak-Snx>;92#z4ELV1xCeY(8Hb`LU zFI!-DICDq81zIDB9?ZI%EihLyiY>SpfP&56ZGlaRj=^lDSn&52NHx*%*nPcoU)`6V zza>jJYk?2K{aUT~Wg}Y}`-cU_aBEd&yJmq`&}D}bSe9)IV2cSa7#g-NDB#wOykX+wK}YZ9xQ`nCNMzKsM=Vnu*`pr>C_7`7P22gdvSU zPU~4(lof7{_|&T^^t$~$Q&G?yWXG4>|B~(3G!V;Tv#qdYI6a53z#J=l^4Qlg=-#sq zFkz(mXAMv|LOfnsNO=Eln3^s=HYyPxn`p4n3jP9htaK=!s(eBITz5IwZ@UOTtLbe@ zIyVaB;?J>dGJN-9bv9l1mKD}(j-$EYvy-N!_E;px&g!3A%~h%7ZfcO9GA=;sx!o)Z z2}UlS6Z!@|a>mguR%i{U+udyqTl4b=>(JCpcgLEGR%m>nlIxB8E}|v|hUEX_?n~NZ zU=Ii33#@NU#mJnUT{5yCzkuot8tqIU&RAF-NFN-8PtS@)xE|oig+g z+U)idHu$0IL5~Nxelthz+YiaZZ$Dj!sV71VFRHh4cKdq|#b<;hb#i+|4s zMQBYJ^ylTIgVX2+fhSl=5>YuvmkMazRyUEVMTI7-eKlm8VulW(r z_m)n~Axc*DQK@x(A0-pZyJ6L={2reg4{vXVnBhardware_type = EndianU16(HTYPE_ETHERNET); header->protocol_type = EndianU16(ETHERTYPE_IPV4); @@ -83,6 +84,9 @@ I64 ARPSend(U16 operation, return 0; } + + + CARPHash *ARPCacheFindByIP(U32 ip_address) { U8 *ip_string = MStrPrint("%d", ip_address); @@ -183,9 +187,10 @@ I64 ARPHandler(CEthernetFrame *ethernet_frame) if (header->target_protocol_addr == arp_globals.local_ipv4) ARPSend(ARP_REPLY, header->sender_hardware_addr, - EthernetGetMAC, + EthernetGetMAC(), arp_globals.local_ipv4, - header->sender_hardware_addr, header->sender_protocol_addr); + header->sender_hardware_addr, + header->sender_protocol_addr); break; case ARP_REPLY: ARPCachePut(EndianU32(header->sender_protocol_addr), header->sender_hardware_addr); diff --git a/src/Home/Net/MappingShrineAlgs.DD b/src/Home/Net/Docs/MappingShrineAlgs.DD similarity index 100% rename from src/Home/Net/MappingShrineAlgs.DD rename to src/Home/Net/Docs/MappingShrineAlgs.DD diff --git a/src/Home/Net/NetFuncSummary.DD b/src/Home/Net/Docs/NetFuncSummary.DD similarity index 65% rename from src/Home/Net/NetFuncSummary.DD rename to src/Home/Net/Docs/NetFuncSummary.DD index 038827b8..3f41ff81 100755 --- a/src/Home/Net/NetFuncSummary.DD +++ b/src/Home/Net/Docs/NetFuncSummary.DD @@ -36,7 +36,7 @@ NetQueue Ethernet - EthernetFrameParse (Needs refining!) + EthernetFrameParse (has a fixme) ARP @@ -48,14 +48,31 @@ ARP ARPSetIPV4Address -Sockets - +Sockets (just finite state modifiers.) + SocketStateErr + SocketAccept + SocketClose + SocketBind + SocketConnect + SocketListen + SocketReceive + SocketReceiveFrom + SocketSend + SocketSendTo IPV4 - + IPV4Checksum + GetMACAddressForIP + IPV4PacketAllocate + IPV4PacketFinish (alias for EthernetFrameFinish) + IPV4GetAddress + IPV4SetAddress + IPV4SetSubnet + IPV4ParsePacket ICMP - + ICMPSendReply + ICMPHandler TCP @@ -69,5 +86,6 @@ DNS NetHandlerTask NetHandlerTask HandleNetQueueEntry (4 million context swaps per second!) + IPV4Handler NetConfig \ No newline at end of file diff --git a/src/Home/Net/NetQueueDiagram.DD b/src/Home/Net/Docs/NetQueueDiagram.DD similarity index 100% rename from src/Home/Net/NetQueueDiagram.DD rename to src/Home/Net/Docs/NetQueueDiagram.DD diff --git a/src/Home/Net/NetworkingNotes.DD b/src/Home/Net/Docs/NetworkingNotes.DD similarity index 56% rename from src/Home/Net/NetworkingNotes.DD rename to src/Home/Net/Docs/NetworkingNotes.DD index d13aaf63..2b0a5583 100755 --- a/src/Home/Net/NetworkingNotes.DD +++ b/src/Home/Net/Docs/NetworkingNotes.DD @@ -11,6 +11,8 @@ Departures from Shrine: Many file global vars have been condensed into global classes. + Sockets are non-standard. + Stack progress: (# done, ~ WIP, . N/A) # PCNet-II Driver @@ -18,21 +20,27 @@ Stack progress: (# done, ~ WIP, . N/A) # NetQueue - no NetHandler currently - ~ Ethernet - - need frame parse method + # Ethernet + - double check. # ARP (Address Resolution Protocol) - double check. - . Sockets - - thinking I should nix Sockets and - work out a STREAMS protocol instead. - Need more docs on STREAMS. + ~ Sockets + - Implemented a Finite State Machine + through Socket function calls. + Sockets themselves do nothing, + all calls simply do/don't alter + socket state. Protocols will + need to detect socket states + and respond appropriately. - . IPV4 (Internet Protocol version 4) + # IPV4 (Internet Protocol version 4) + - double check, some FIXMEs. - . ICMP (Internet Control Message Protocol) + # ICMP (Internet Control Message Protocol) + - ? has FIXMEs. . TCP (Transmission Control Protocol) @@ -53,4 +61,13 @@ Stack progress: (# done, ~ WIP, . N/A) made so we can just do a switch (ethertype) and then directly call the appropriate handler. + - As of ICMP, this seems fine to do. IPV4 Handler needed + to do something very similar to what NetHandler does, + so for simplicity IPV4Handler was moved to NetHandler + file, as it does a switch with protocol types that + are defined in files after IPV4.CC is included. This should + likely be the convention for Handlers that need to process + data from an all-knowing perspective (i.e. NetHandler file is + included last.) + . NetConfig \ No newline at end of file diff --git a/src/Home/Net/Docs/SocketMapping.DD b/src/Home/Net/Docs/SocketMapping.DD new file mode 100755 index 0000000000000000000000000000000000000000..ee344da467b6e1faee0eafd327a0545279c41323 GIT binary patch literal 1312 zcmcgsL2DC16rPHR<{%zKA=%Cq;=bP`nH}CD+w>!AFP#PF7PY#r( zW+%#nx&L*sK9*iD_OgWcvHnRYCxD?j;lh?$G_tOo9b(=uTsL8uP9;{@giwW0l##HO z*Gv@HTxCT|7aGl*Ce%*Fc9?nVaJmQxDOJ-k?+y4JU^&zQ^B#h205URGF>f67Gi{C5 zO2T^tn|-TcmDn_xcNEGqC|2pf8RqTB{$sLt7Udh{%wL8^)jtQW z0{J=IZUe7@h=h6E)_^wfmV^skVGQdh5{~2c05}O8!1?)uAdi4!z!V94Ksvx-V2y+y z=+R?r$nXI|;TFg~pebU}usqbfILr#`P_6;RX)0Jbzr=}%Ac#R{N6*0LCG7Wh@wpHC z`@|=GNj~DvVZXUc96gbb^8U!5Chdno_8OMP(vp(8{!-^5glzm<2zBRcZwS(N6J+`D zC>{9G@iRi!xOGAYA`WX92dQ@+c2XLxdPq4K3iHnwK+?JD$%GbXncl(c6}*{pR%hpv{K*^Esq3{;T3vJu991MHwE(EtDd literal 0 HcmV?d00001 diff --git a/src/Home/Net/Docs/SocketNotes.DD b/src/Home/Net/Docs/SocketNotes.DD new file mode 100755 index 00000000..f01b0cef --- /dev/null +++ b/src/Home/Net/Docs/SocketNotes.DD @@ -0,0 +1,182 @@ + +Sockets... not planning to conform to unix-y socket standards unless absolutely required. +All over socket code is these global vars, lowercase functions.. its a mess. + +Shrine does some pretty gnarly stuff to do their NativeSockets. + +They have a class, called a CSocket, which is just a bunch of function pointers. + +Then, they have another, called CSocketClass, which is a single direction list of +a domain, a type, some padding (...), and a pointer to a CSocket.. + +I don't even want to get into the CAddrResolver which is a yet another +function pointer to some resolver function. + +Then, the socketclass and addrresolver are made into globals! + +When they try to find a socket class, they have to loop through all +of the defined socket classes until they find one that matches the +params of domain and type.. + + +Since UDP is regarded apparently as more simple than TCP, +looking at Shrine's UDP code gives a little insight into +what's going on without getting too caught up in high +level intricacies. + +When UDP registers their socketclass , they pass in the #defined +type and domain, then pass in the socketclass itself. + +All the typical socket functions as defined in shrine, +basically just take in this socketclass and then redirect +execution to the function defined in the socketclass. + + + +At first, I thought "oh, why not just make the args a class!" but +this doesn't address the need to store functions as members, which +is a little niggerlicious still. + +So I need a better way to do these socket function calls, without +having to rely on a pointer magic backend. The immediate thought +I have, is to ditch general use socket functions and enforce +application specific function definitions. In a way, that might +kinda end up meaning that a hell of a lot of the other way +of doing things would be stripped. + +Maybe, analyze what passed variables are manipulated, and hardcode +functions with higher specificity. + + +... + +/* Zenith Sockets are non-standard. + +---------------------- +Shrine implementation: + +in_addr +sockaddr +sockaddr_in +addrinfo + +inet_aton +inet_ntoa + +socket +accept +close +bind +connect +listen +recv +recvfrom +send +sendto +setsockopt +getaddrinfo +freeaddrinfo + +AddrInfoCopy +gai_strerror +create_connection +RegisterSocketClass +---------------------- +Zenith implementation: + + + +---------------------- + +*/ + + + +/* I think a class for a socket is a good idea. + But i do not think that the class should have + function pointers. + + A CSocket is literally just a list of function pointers. + + In Shrine, for example, the only UDP socket functions + that actually do anything are UDPSocketBind, UDPSocketClose, + UDPSocketRecvFrom, UDPSocketSendTo, and UDPSocketSetSockopt. + The rest just make the compiler happy by saying no_warn on the + args ... + + If not function pointers, how would it work? + + STREAMS / XTI / TPI lookin sexy rn ngl haha. + + Maybe a hybrid of the alternatives and sockets. + + One thought is to reserve sockets as some unique class thing, + and have functions that take args that go to a switch statement + to determine which code to next execute. + + Perhaps, two Socket related files would make more sense, + one which defines some low-level things, and another that + (like NetHandler) is all-knowing and would discern based + on a switch statement which socket-related functions to run. + If doing this, must make sure that UDP/TCP/etc won't + need to know things only the SocketHandler or whatever we'd + call that would know. + + At the root of ShrineSockets, all sockets made in later + files must be put into a RegisterSocketClass call, + which keeps track of which functions go where based on + the searched-for domain and type. So, I'd think a + beginning to an unfuck would be, if it ends up needed, + using a hash table, maybe the key would be (domain << 64 | type) + as a string, assuming the args are still I64. + + I fight and I fight.. these bold ideas might be forced + to take place only after I've un-fucked sockets code alone. + Then maybe after massive unfucking/refucking, it can be more + quickly discerened what the better way to go from there is. + + + + + +*/ + + //at the end of the day , one distinction MUST be made... + //What IS a socket ? ... + +.... + + +--------------------- + +Thoughts. + + +Sense in SocketNextState(CSocket *socket, U8 state) ? + +Sense in having two socket states, current and requested? + With one, if we try to modify the value, we modify it. + There's no way of conveying or interacting with + higher-defined (TCP, UDP, etc) socket functions. + With a current and requested state, we would be + able to show both what the socket is doing right + now, and what the user/code has requested the + socket to change to. Say, there is a failure + in the higher level code when it sees a socket + and its requested next state: it will process + appropriate higher-level code and then ask for + another Socket State change accordingly or something. + + + + + + + + + + + + + + diff --git a/src/Home/Net/Docs/StrToAddressFSM.DD b/src/Home/Net/Docs/StrToAddressFSM.DD new file mode 100755 index 0000000000000000000000000000000000000000..715f71c4f9a8d8efab6abc1e35bb864be3bfd6c7 GIT binary patch literal 516 zcmZ9I%}N4M6vwZ%yX}HDGeQ@UCc(t7zMxtJMec;42qYTxnnOAxGgnYr6!Z)&+Vu$H z6Z8Pl6SN5G0ot_+`;8%m1HW_r_s^Vr&OPblYBse~*h^*iE4ziXNQmF>0}<w7#NFH*7uE0BC996=0F%px2m>>h*=tn9dvJ2h^~&Lp!{3AKi#Z``SfS@fTDg8h zg}SRdestination_address, frame, 6); // 6 ? Why 6? Need a #define... + MemCopy(frame_out->destination_address, frame, MAC_ADDRESS_LENGTH); - MemCopy(frame_out->source_address, frame + 6, 6); // Ridiculous, what are those + MemCopy(frame_out->source_address, frame + MAC_ADDRESS_LENGTH, MAC_ADDRESS_LENGTH); - frame_out->ethertype = frame[13] | (frame[12] << 8); // This would be readable with #defines + frame_out->ethertype = frame[ETHERNET_ETHERTYPE_OFFSET+1] | (frame[ETHERNET_ETHERTYPE_OFFSET] << 8); - frame_out->data = frame + 14; // Fuck you Shrine + frame_out->data = frame + ETHERNET_DATA_OFFSET; - frame_out->length = length - 14 + 4; //... He has a comment literally just saying "??". Wow. + frame_out->length = length - ETHERNET_MAC_HEADER_LENGTH + 4; //... He has a comment literally just saying "??". Wow. } EthernetInitGlobals; \ No newline at end of file diff --git a/src/Home/Net/ICMP.CC b/src/Home/Net/ICMP.CC new file mode 100755 index 00000000..4a55d684 --- /dev/null +++ b/src/Home/Net/ICMP.CC @@ -0,0 +1,83 @@ +#include "IPV4" + +#define ICMP_TYPE_ECHO_REPLY 0 +#define ICMP_TYPE_ECHO_REQUEST 8 + +class CICMPHeader // Shrine's use of id and seq indicate this header is for Address Mask Reply/Request +{ + U8 type; + U8 code; + U16 checksum; + + U16 identifier; + U16 sequence_number; +}; + + +I64 ICMPSendReply(U32 destination_ip_address, + U16 identifier, + U16 sequence_number, + U16 request_checksum, + U8 *payload, + I64 length) +{ + U8 *frame; + I64 de_index; + CICMPHeader *header; + + de_index = IPV4PacketAllocate(&frame, + IP_PROTOCOL_ICMP, + IPV4GetAddress(), + destination_ip_address, + sizeof(CICMPHeader) + length); + if (de_index < 0) + { + ZenithLog("ICMP Send Reply failed to allocate IPV4 packet.\n"); + return de_index; + } + + header = frame; + + + header->type = ICMP_TYPE_ECHO_REPLY; + + header->code = 0; // why is 0 okay? + + header->checksum = EndianU16(EndianU16(request_checksum) + 0x0800);// this is awful. Shrine says hack alert. + + header->identifier = identifier; + + header->sequence_number = sequence_number; + + MemCopy(frame + sizeof(CICMPHeader), payload, length); + + IPV4PacketFinish(de_index); + //return IPV4PacketFinish +} + +I64 ICMPHandler(CIPV4Packet *packet) +{ + CICMPHeader *header; + + if (packet->length < sizeof(CICMPHeader)) + { + ZenithLog("ICMP Handler caught wrong IPV4 length.\n"); + return -1; + } + + header = packet->data; + + if (header->type == ICMP_TYPE_ECHO_REQUEST && header->code == 0)//why zero? need more #defines in here + { + ARPCachePut(packet->source_ip_address, packet->ethernet_frame->source_address); + + ICMPSendReply(packet->source_ip_address, + header->identifier, + header->sequence_number, + header->checksum, + packet->data + sizeof(CICMPHeader), + packet->length - sizeof(CICMPHeader)); // wut + } + + return 0; +} \ No newline at end of file diff --git a/src/Home/Net/IPV4.CC b/src/Home/Net/IPV4.CC new file mode 100755 index 00000000..85ac1813 --- /dev/null +++ b/src/Home/Net/IPV4.CC @@ -0,0 +1,324 @@ +#include "ARP" + +#define IPV4_ERR_ADDR_INVALID -200001 +#define IPV4_ERR_HOST_UNREACHABLE -200002 + +//i'd like to know why 64 was chosen +#define IPV4_TTL 64 + +//Look up IP Protocol Numbers online to see many more. +#define IP_PROTOCOL_ICMP 0x01 +#define IP_PROTOCOL_TCP 0x06 +#define IP_PROTOCOL_UDP 0x11 + + +class CIPV4Packet +{ + CEthernetFrame *ethernet_frame; + + U32 source_ip_address; + U32 destination_ip_address; + + U8 protocol; + + U8 padding[7]; + + U8 *data; + + I64 length; +}; + +class CIPV4Header +{ // note: U4's in some U8s. + U8 version_ihl; //Version for IPV4 is 4. IHL=Internet Header Length + + U8 dscp_ecn; // DSCP=Differentiated Services Code Point. ECN=Explicit Congestion Notification + + U16 total_length; // min 20B max 65535 + + U16 identification; + + U16 flags_fragment_offset; // flags first(?) 3 bits. fragment offset min 0 max 65528 + // flag: bit 0: reserved must be 0. bit 1: don't fragment. bit 2: more fragments + + U8 time_to_live; // specified in seconds, wikipedia says nowadays serves as a hop count + + U8 protocol; + + U16 header_checksum; + + U32 source_ip_address; + U32 destination_ip_address; +} + +class CIPV4Globals +{ // _be indicates Big Endian + U32 local_ip; + U32 local_ip_be; + + U32 ipv4_router_address; + U32 ipv4_subnet_mask; + +} ipv4_globals; + +U0 InitIPV4Globals() +{ + ipv4_globals.local_ip = 0; + ipv4_globals.local_ip_be = 0; + ipv4_globals.ipv4_router_address = 0; + ipv4_globals.ipv4_subnet_mask = 0; +} + + +// For now, trusting Shrine's implement +// of this. Shrine links back to +// http://stackoverflow.com/q/26774761/2524350 + +U16 IPV4Checksum(U8* header, I64 length) +{ //todo. make names clearer, and better comments. + I64 nleft = length; + U16 *w = header; + I64 sum = 0; + + while (nleft > 1) + { + sum += *(w++); + nleft -= 2; + } + + // "mop up an odd byte, if necessary" + if (nleft == 1) + { + sum += ((*w) & 0x00FF); + } + + // "add back carry outs from top 16 bits to low 16 bits" + sum = (sum >> 16) + (sum & 0xFFFF); // "add hi 16 to low 16" + sum += (sum >> 16); // add carry + return (~sum) & 0xFFFF; + +} + + + +I64 GetMACAddressForIP(U32 ip_address, U8 **mac_out) +{ + CARPHash *entry; + I64 retries; + I64 attempt; +/* + switch (ip_address) + { + case 0: + return IPV4_ERR_ADDR_INVALID; + case 0xFFFFFFFF: + *mac_out = ethernet_globals.ethernet_broadcast; + return 0; + } +*/ + + if (ip_address == 0) + { + ZenithLog("Get MAC for IP failed. Address = 0\n"); + return IPV4_ERR_ADDR_INVALID; + } + if (ip_address == 0xFFFFFFFF) + { + ZenithLog("Get MAC for IP requested and returning ethernet broadcast\n"); + *mac_out = ethernet_globals.ethernet_broadcast; + return 0; + } + + + // "outside this subnet; needs routing" + if ((ip_address & ipv4_globals.ipv4_subnet_mask) != + (ipv4_globals.local_ip & ipv4_globals.ipv4_subnet_mask)) + { + // Shrine recurses here... and says FIXME infinite loop if mis-configured... + } + else // "local network" + { + entry = ARPCacheFindByIP(ip_address); + + if (entry) + { + *mac_out = entry->mac_address; + return 0; + } + //else, not in cache, need to request it + + // "Up to 4 retries, 500 ms each" + retries = 4; + while (retries) + { + attempt = 0; + for (attempt = 0; attempt < 50; attempt++) + { + Sleep(10); + entry = ARPCacheFindByIP(ip_address); + if (entry) break; + } + + if (entry) + { + *mac_out = entry->mac_address; + return 0; + } + + retries--; + } + + //Shrine does some in_addr mess to log error + ZenithLog("Failed to resolve address %d",ip_address); + return IPV4_ERR_HOST_UNREACHABLE; + } +} + +I64 IPV4PacketAllocate(U8 **frame_out, + U8 protocol, + U32 source_ip_address, + U32 destination_ip_address, + I64 length) +{ + U8 *ethernet_frame; + U8 *destination_mac_address; + + I64 error; + I64 de_index; + + I64 internet_header_length; + + CIPV4Header *header; + + + error = GetMACAddressForIP(destination_ip_address, &destination_mac_address); + if (error < 0) + { + ZenithLog("IPV4 Packet Allocate failed to get MAC for destination.\n"); + return error; + } + + de_index = EthernetFrameAllocate(ðernet_frame, + EthernetGetMAC(), + destination_mac_address, + ETHERTYPE_IPV4, + sizeof(CIPV4Header) + length); + if (de_index < 0) + { + ZenithLog("IPV4 Ethernet Frame Allocate failed.\n"); + return de_index; + } + + internet_header_length = 5;// ... why. need a #define + + + header = ethernet_frame; + + + header->version_ihl = internet_header_length | (4 << 4);// whaaat the fuck is this? #define! + + header->dscp_ecn = 0; // a clear define of what this actually means would be good + + header->total_length = EndianU16(internet_header_length * 4 + length); //...why? + + header->identification = 0;// define would be clearer + + header->flags_fragment_offset = 0; // define would be clearer + + header->time_to_live = IPV4_TTL; + + header->protocol = protocol; + + header->header_checksum = 0; // why is 0 ok? + + header->source_ip_address = EndianU32(source_ip_address); + + header->destination_ip_address = EndianU32(destination_ip_address); + + header->header_checksum = IPV4Checksum(header, internet_header_length + 4);//why the 4's... + + *frame_out = ethernet_frame + sizeof(CIPV4Header); + return de_index; +} + +U0 IPV4PacketFinish(I64 de_index) //alias for EthernetFrameFinish +{ + EthernetFrameFinish(de_index); +} + +U32 IPV4GetAddress() +{ + return ipv4_globals.local_ip; +} + +U0 IPV4SetAddress(U32 ip_address) +{ + ipv4_globals.local_ip = ip_address; + ipv4_globals.local_ip_be = EndianU32(ip_address); + + ARPSetIPV4Address(ip_address); + +} + +U0 IPV4SetSubnet(U32 router_address, U32 subnet_mask) +{ + ipv4_globals.ipv4_router_address = router_address; + ipv4_globals.ipv4_subnet_mask = subnet_mask; +} + +//I64 +U0 IPV4ParsePacket(CIPV4Packet *packet_out, CEthernetFrame *ethernet_frame) +{ + //...if ethertype not ipv4 error? + + //Shrine says FIXME check ethernet_frame length !! ... we need to know what's appropriate + + CIPV4Header *header = ethernet_frame->data; + I64 header_length = (header->version_ihl & 0x0F) * 4;//this Has to go. at least abstract or something.. + U16 total_length = EndianU16(header->total_length); + + packet_out->ethernet_frame = ethernet_frame; + + packet_out->source_ip_address = EndianU32(header->source_ip_address); + packet_out->destination_ip_address = EndianU32(header->destination_ip_address); + + packet_out->protocol = header->protocol; + + + packet_out->data = ethernet_frame->data + header_length; + + packet_out->length = total_length - header_length; + +// return 0; +} + +// IPV4 handler moved to NetHandlerTask file. + + + + + + + + + + + + + + + + + + + + + + + + + + + + +InitIPV4Globals; \ No newline at end of file diff --git a/src/Home/Net/NetHandlerTask.CC b/src/Home/Net/NetHandlerTask.CC index 38edf7f8..87910951 100755 --- a/src/Home/Net/NetHandlerTask.CC +++ b/src/Home/Net/NetHandlerTask.CC @@ -1,3 +1,23 @@ +U0 IPV4Handler(CEthernetFrame *ethernet_frame) +{ + CIPV4Packet packet; + + IPV4ParsePacket(&packet, ethernet_frame); + + ARPCachePut(packet.source_ip_address, ethernet_frame->source_address); + + switch (packet.protocol) + { + case IP_PROTOCOL_ICMP: + ICMPHandler(&packet); + break; + case IP_PROTOCOL_TCP: + break; + case IP_PROTOCOL_UDP: + break; + } + +} U0 HandleNetQueueEntry(CNetQueueEntry *entry) { @@ -10,6 +30,9 @@ U0 HandleNetQueueEntry(CNetQueueEntry *entry) case ETHERTYPE_ARP: ARPHandler(ðernet_frame); break; + case ETHERTYPE_IPV4: + IPV4Handler(ðernet_frame); + break; } } @@ -25,7 +48,8 @@ U0 NetHandlerTask(I64) } else { - LBts(&Fs->task_flags, TASKf_SUSPENDED); + LBts(&Fs->task_flags, TASKf_IDLE); + //ZenithLog("IDLE: NetHandler\n"); Yield; } diff --git a/src/Home/Net/NetQueue.CC b/src/Home/Net/NetQueue.CC index cf088035..97df23d1 100755 --- a/src/Home/Net/NetQueue.CC +++ b/src/Home/Net/NetQueue.CC @@ -1,7 +1,11 @@ -//Shrine mentions possibly using two FIFOs (in our case, Queues) for pending and empty frames. -//If logical to implement, perhaps Zenith NetQueue code should do something similar to that idea. +/* Shrine mentions possibly using two FIFOs + (in our case, Queues) for pending and + empty frames. If logical to implement, + perhaps Zenith NetQueue code should + do something similar to that idea. */ -//Each Ethernet Frame will be represented as an entry in a CQueue. +/* Each Ethernet Frame will be represented + as an entry in a CQueue. */ class CNetQueueEntry:CQueue { I64 length; //todo: change to packet_length? @@ -16,17 +20,21 @@ class CNetQueueEntry:CQueue itself, the Head. */ CQueue *net_queue; // no QueueRemove the Head! only Entries! -//Net Handler Task is set idle and active depending on if entries in Net Queue. See NetHandlerTask.CC +/* Net Handler Task is set idle and active depending + on if entries in Net Queue. See NetHandlerTask.CC */ CTask *net_handler_task = NULL; + U0 NetQueueInit() { net_queue = CAlloc(sizeof(CQueue)); QueueInit(net_queue); } + CNetQueueEntry *NetQueuePull() -{//Returns a pointer to a CNetQueueEntry, or NULL pointer if Net Queue is empty. +{/* Returns a pointer to a CNetQueueEntry, + or NULL pointer if Net Queue is empty. */ CNetQueueEntry *entry; if (net_queue->next != net_queue) @@ -35,11 +43,15 @@ CNetQueueEntry *NetQueuePull() QueueRemove(entry); } else // Queue is empty if head->next is head itself. + { entry = NULL; + } return entry; } + + U0 NetQueuePushCopy(U8 *data, I64 length) {/* Pushes a copy of the packet data and length into the Net Queue. The NetQueueEntry is inserted @@ -53,8 +65,9 @@ U0 NetQueuePushCopy(U8 *data, I64 length) QueueInsert(entry, net_queue->last); //Set Net Handler Task active. + ZenithLog("ACTIVE: NetHandler\n"); if (net_handler_task) - LBtr(&net_handler_task->task_flags, TASKf_SUSPENDED); + LBtr(&net_handler_task->task_flags, TASKf_IDLE); } diff --git a/src/Home/Net/PCNet.CC b/src/Home/Net/PCNet.CC index 64fd1f8f..7557af17 100755 --- a/src/Home/Net/PCNet.CC +++ b/src/Home/Net/PCNet.CC @@ -13,8 +13,8 @@ - Clear documentation. */ -#include "Net/Net.HH" -#include "Net/NetQueue" +#include "Net.HH" +#include "NetQueue" #define PCNET_DEVICE_ID 0x2000 #define PCNET_VENDOR_ID 0x1022 @@ -68,8 +68,8 @@ #define PCNET_CTRL_STOP 2 #define PCNET_CTRL_RINT 10 -#define PCNET_RX_BUFF_COUNT 32 // Linux & Shrine Driver use 32 and 8 for these, we could allow more if wanted. -#define PCNET_TX_BUFF_COUNT 8 +#define PCNET_RX_BUFF_COUNT 32 // Linux & Shrine Driver use 32 and 8 for +#define PCNET_TX_BUFF_COUNT 8 // these, we could allow more if wanted. #define PCNET_DESCRIPTORf_ENP 24 #define PCNET_DESCRIPTORf_STP 25 @@ -81,7 +81,7 @@ class CPCNet { CPCIDev *pci; - U64 mac_address; //MAC address is first 6 bytes of PCNet EEPROM (page # ?) + U8 mac_address[6]; //MAC address is first 6 bytes of PCNet EEPROM (page # ? ) I64 current_rx_de_index; // Current Receive DE being processed. Gets incremented, wrapped to 0 at max of PCNET_RX_BUFF_COUNT. I64 current_tx_de_index; // Current Transmit DE being processed. Gets incremented, wrapped to 0 at max of PCNET_TX_BUFF_COUNT. @@ -97,29 +97,36 @@ class CPCNet } pcnet; // pcnet is the global variable we store all of this into. class CPCNetDescriptorEntry -{/* AMD PCNet datasheet p.1-991 & p.1-994 NOTE: chart typo on 1-994, see ONES and BCNT on 1-995. - TX and RX DE's are the same size (16-Bytes) and structure, but have different registers and functions. - The RX and TX DE buffers of the CPCNet class are allocated to a certain amount of these DEs. */ +{/* AMD PCNet datasheet p.1-991 & p.1-994 NOTE: chart typo on 1-994, see ONES and BCNT on 1-995. + TX and RX DE's are the same size (16-Bytes) and structure, + but have different registers and functions. + The RX and TX DE buffers of the CPCNet class + are allocated to a certain amount of these DEs. */ U32 buffer_addr; U32 status1; U32 status2; U32 reserved; }; + CPCIDev *PCNetPCIDevFind() {// Find and return PCNetII card as a CPCIDev pointer. return PCIDevFind(,,PCNET_VENDOR_ID,PCNET_DEVICE_ID); } U32 PCNetGetIOBase() -{/* Return memory IO base address of PCNet card. Bits 0-4 are not for the IO base, so an AND NOT 0x1F ignores those bits. */ +{/* Return memory IO base address + of PCNet card. Bits 0-4 are not + for the IO base, so an AND with + ~0x1F ignores those bits. */ U32 io_base = pcnet.pci->base[0] & ~0x1F; return io_base; } U0 PCNetReset() -{/* Reads the 32- and 16-bit RESET registers, which, - regardless of which mode the card is in, will reset it back to 16-bit mode. */ +{/* Reads the 32- and 16-bit RESET registers, + which, regardless of which mode the card is in, + will reset it back to 16-bit mode. */ InU32(PCNetGetIOBase + PCNET_DW_RESET); InU16(PCNetGetIOBase + PCNET_WD_RESET); Busy(5); // OSDev says minimum 1 æS @@ -127,31 +134,49 @@ U0 PCNetReset() U0 PCNetEnter32BitMode() {/* AMD PCNet datasheet p. 1-930 - Summary: A 32-bit write (while in 16-bit mode) to RDP will cause 16-bit mode exit and immediate entry into 32-bit mode. */ + Summary: A 32-bit write (while in 16-bit mode) + to RDP will cause 16-bit mode exit + and immediate enter into 32-bit mode. */ OutU32(PCNetGetIOBase + PCNET_DW_RDP, 0); + +} + +U0 PCNetWriteRAP(U32 value) +{/* AMD PCNet datasheet p. 1-952 + Summary: Register Address Pointer register + value will indicate which CSR / BCR register + we want to access in RDP / BDP. */ + OutU32(PCNetGetIOBase + PCNET_DW_RAP, value); } U0 PCNetWriteCSR(U32 csr, U32 value) {/* AMD PCNet datasheet p. 1-952 - Summary: Control and Status Registers are accessed via the RDP (Register Data Port). - Which CSR is selected is based on the value in the RAP (Register Address Port). */ - OutU32(PCNetGetIOBase + PCNET_DW_RAP, csr); + Summary: Control and Status Registers are + accessed via the RDP (Register Data Port). + Which CSR is selected is based on the value + in the RAP. */ + PCNetWriteRAP(csr); OutU32(PCNetGetIOBase + PCNET_DW_RDP, value); } U32 PCNetReadCSR(U32 csr) {/* AMD PCNet datasheet p. 1-952 - Summary: Control and Status Registers are accessed via the RDP (Register Data Port). - Which CSR is selected is based on the value in the RAP. */ - OutU32(PCNetGetIOBase + PCNET_DW_RAP, csr); + Summary: Control and Status Registers are + accessed via the RDP (Register Data Port). + Which CSR is selected is based on the value + in the RAP. */ + PCNetWriteRAP(csr); return InU32(PCNetGetIOBase + PCNET_DW_RDP); } U0 PCNetSetSWStyle() {/* AMD PCNet datasheet p. 1-968 - In CSR58 (Software Style), the 8-bit SWSTYLE register dictates interpretation of certain bits in the CSR space, - and widths of descriptors and initialization block. - In PCNet-PCI mode, CSR4 bits function as defined in the datasheet, and TMD1[29] functions as ADD_FCS. */ + In CSR58 (Software Style), the 8-bit + SWSTYLE register dictates interpretation of certain + bits in the CSR space, and widths of descriptors and + initialization block. In PCINet-PCI mode, CSR4 bits + function as defined in the datasheet , and TMD1[29] + functions as ADD_FCS. */ U32 csr = PCNetReadCSR(PCNET_CSR_SOFTWARESTYLE); csr &= ~0xFF; // clears first 8 bits: SWSTYLE 8-bit register. @@ -163,23 +188,35 @@ U0 PCNetSetSWStyle() U0 PCNetGetMAC() {/* AMD PCNet datasheet p. 1-887, 1-931, 1-937 MAC address stored at first 6 bytes of PCNet EEPROM. - EEPROM addresses shadow-copied to APROM at hardware init. APROM accessible at first 16 bytes of PCI IO space. */ + EEPROM addresses shadow-copied to APROM at hardware init. + APROM accessible at first 16 bytes of PCI IO space. */ I64 i; for (i = 0; i < 6; i++) - pcnet.mac_address.u8[i] = InU8(PCNetGetIOBase + i); + { + pcnet.mac_address[i] = InU8(PCNetGetIOBase + i); + } } -U0 PCNetInitDescriptorEntry(CPCNetDescriptorEntry *entry, U32 buffer_addr, I64 is_rx) +U0 PCNetInitDescriptorEntry(CPCNetDescriptorEntry *entry, U32 buffer_address, I64 is_rx) { - entry->buffer_addr = buffer_addr; + entry->buffer_addr = buffer_address; + /* AMD PCNet datasheet p.1-991. - BCNT is the usable buffer length, expressed as first 12 bits of 2s-complement of desired length. - Bits 0-11 of a DE are for the buffer byte count (BCNT),and bits 12-15 of a DE must be written all ones (ONES)*/ - entry->status1 |= -ETHERNET_MIN_FRAME_SIZE & 0xFFF; //Sets BCNT reg (first 12 bits) in DE TMD1/RMD1, as 2s complement of the desired length. + BCNT is the usable buffer length, expressed as first + 12 bits of 2s-complement of desired length. + Bits 0-11 of a DE are for the buffer byte count (BCNT), + and bits 12-15 of a DE must be written all ones (ONES) */ + U16 buffer_byte_count = -ETHERNET_FRAME_SIZE; // Sets up as 2s complement of the desired length. + buffer_byte_count &= 0x0FFF; // Masks 0 over everything except bits 0-11. + + entry->status1 |= buffer_byte_count; // Sets BCNT reg (first 12 bits) in DE TMD1/RMD1. entry->status1 |= 0xF000; // Sets bits 12-15 (ONES) in DE TMD1/RMD1 as all ones. + //if this is a Receive DE, give ownership to the card so the PCNet can fill them. - if (is_rx) Bts(&entry->status1, PCNET_DESCRIPTORf_OWN); + if (is_rx) + Bts(&entry->status1, PCNET_DESCRIPTORf_OWN); ClassRep(entry); + } U0 PCNetAllocateBuffers() @@ -187,82 +224,131 @@ U0 PCNetAllocateBuffers() I64 de_index; // used in for loops for TX and RX DE access. /* AMD PCNet datasheet p.1-913, p.1-990 - When SSIZE32 = 1, Descriptor Ring Entry Base Address must be on 16-byte boundary. (TDRA[3:0] = 0, RDRA[3:0] = 0) */ - pcnet.rx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_RX_BUFF_COUNT, 16, Fs->code_heap); - pcnet.tx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_TX_BUFF_COUNT, 16, Fs->code_heap); + When SSIZE32=1, Descriptor Ring Entry Base Address + must be on 16-byte boundary. (TDRA[3:0]=0, RDRA[3:0]=0) */ + pcnet.rx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_RX_BUFF_COUNT, + 16, + Fs->code_heap); + pcnet.tx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_TX_BUFF_COUNT, + 16, + Fs->code_heap); //Shrine does a check and returns -1 here, if the end of either buffer exceeds 0x100000000 pcnet.rx_de_buffer = dev.uncached_alias + pcnet.rx_de_buffer_phys; // we want uncached pcnet.tx_de_buffer = dev.uncached_alias + pcnet.tx_de_buffer_phys; // access to these. - pcnet.rx_buffer_addr = CAlloc(ETHERNET_MIN_FRAME_SIZE * PCNET_RX_BUFF_COUNT, Fs->code_heap);//Shrine has a TODO to figure out - pcnet.tx_buffer_addr = CAlloc(ETHERNET_MIN_FRAME_SIZE * PCNET_TX_BUFF_COUNT, Fs->code_heap);//if these should be uncached too. - //note, p.1-991,1-994: RBADR is only 32 bits wide. + pcnet.rx_buffer_addr = CAlloc(ETHERNET_FRAME_SIZE * PCNET_RX_BUFF_COUNT, //Shrine has a TODO to figure out + Fs->code_heap); + pcnet.tx_buffer_addr = CAlloc(ETHERNET_FRAME_SIZE * PCNET_TX_BUFF_COUNT, //if these should be uncached too. + Fs->code_heap); //note, p.1-991,1-994: RBADR is only 32 bits wide. //Shrine does a check and returns -1 here, if the end of either buffer exceeds 0x100000000 + CPCNetDescriptorEntry *entry = pcnet.rx_de_buffer; for (de_index = 0; de_index < PCNET_RX_BUFF_COUNT; de_index++) + { PCNetInitDescriptorEntry(&entry[de_index], pcnet.rx_buffer_addr, TRUE); // TRUE for is_rx. + } entry = pcnet.tx_de_buffer; for (de_index = 0; de_index < PCNET_TX_BUFF_COUNT; de_index++) - PCNetInitDescriptorEntry(&entry[de_index], pcnet.tx_buffer_addr, FALSE); // FALSE for is_rx. + { + PCNetInitDescriptorEntry(&entry[de_index], pcnet.tx_buffer_addr, FALSE); // FALSE for is_rx. + } + } U0 PCNetDirectInit() {/* AMD PCNet datasheet p. 1-1021 - Instead of setting up an initialization block, - direct writes to the necessary CSRs can be used to manually initialize the PCNet card. */ + Instead of setting up initialization block, + direct writes to the necessary CSRs can be + used to manually initialize the PCNet card. */ /* AMD PCNet datasheet p.1-991 - If Logical Address Filter is set as all 0, all incoming logical addresses are rejected. Disables multicast. */ + If Logical Address Filter is set as + all 0, all incoming logical addresses + are rejected. Disables multicast. */ PCNetWriteCSR(PCNET_CSR_LADRF0, 0); PCNetWriteCSR(PCNET_CSR_LADRF1, 0); PCNetWriteCSR(PCNET_CSR_LADRF2, 0); PCNetWriteCSR(PCNET_CSR_LADRF3, 0); - /* AMD PCNet datasheet p.1-960, 1-961 - The Physical Address is the MAC. - The first 16 bits of CSRs 12-14 are for the Physical Address, the upper bits are reserved, written 0 read undefined.*/ + /* The Physical Address is the MAC. + AMD PCNet datasheet p.1-960, 1-961 + The first 16 bits of CSRs 12-14 are + for the Physical Address, the upper bits + are reserved, written 0 read undefined. - PCNetWriteCSR(PCNET_CSR_PADR0, pcnet.mac_address.u16[0]); - PCNetWriteCSR(PCNET_CSR_PADR1, pcnet.mac_address.u16[1]); - PCNetWriteCSR(PCNET_CSR_PADR2, pcnet.mac_address.u16[2]); + The OR and bit-shift of 8 allows writing + separate U8 values in the correct locations + of the CSR. */ + PCNetWriteCSR(PCNET_CSR_PADR0, + pcnet.mac_address[0] | (pcnet.mac_address[1] << 8)); + PCNetWriteCSR(PCNET_CSR_PADR1, + pcnet.mac_address[2] | (pcnet.mac_address[3] << 8)); + PCNetWriteCSR(PCNET_CSR_PADR2, + pcnet.mac_address[4] | (pcnet.mac_address[5] << 8)); /* AMD PCNet datasheet p.1-961, 1-962, 1-963 - Refer to datasheet for specifics. Most relevant, when setting Mode to 0, promiscuous mode is is disabled, - TX and RX enabled, enable RX broadcast and unicast. */ + Refer to datasheet for specifics. + Most relevant, when setting Mode to 0, + promiscuous mode is is disabled, TX and + RX enabled, enable RX broadcast and unicast. */ PCNetWriteCSR(PCNET_CSR_MODE, 0); /* AMD PCNet datasheet p.1-964 - CSR 24 and 25 need to be filled with the lower and upper 16 bits, respectively, of the address of the RX packet ring. Likewise for - CSR 30 and 31 for the TX packet ring.*/ + CSR 24 and 25 need to be filled + with the lower and upper 16 bits, + respectively, of the address of + the RX packet ring. Likewise for + CSR 30 and 31 for the TX packet ring. - PCNetWriteCSR(PCNET_CSR_BADRL, pcnet.rx_buffer_addr.u16[0]); - PCNetWriteCSR(PCNET_CSR_BADRU, pcnet.rx_buffer_addr.u16[1]); + 0xFFFF AND on address will leave + only lower 16 bits remaining. - PCNetWriteCSR(PCNET_CSR_BADTL, pcnet.tx_buffer_addr.u16[0]); - PCNetWriteCSR(PCNET_CSR_BADTU, pcnet.tx_buffer_addr.u16[1]); + Bitshift right of 16 will replace + first 16 bits with upper 16 bits, + remaining bits cleared.*/ + PCNetWriteCSR(PCNET_CSR_BADRL, + pcnet.rx_buffer_addr & 0xFFFF); + PCNetWriteCSR(PCNET_CSR_BADRU, + pcnet.rx_buffer_addr >> 16); + + PCNetWriteCSR(PCNET_CSR_BADTL, + pcnet.tx_buffer_addr & 0xFFFF); + PCNetWriteCSR(PCNET_CSR_BADTU, + pcnet.tx_buffer_addr >> 16); /* AMD PCNet datasheet p. 1-967 - Default value at hardware init is all 0. - Standard init block process sets this, but if doing directly it is imperative to manually set it 0. */ + Default value at hardware init is + all 0. Standard init block process + sets this, but if doing directly + it is imperative to manually set it 0. */ PCNetWriteCSR(PCNET_CSR_POLLINT, 0); /* AMD PCNet datasheet p. 1-970 - Receive and Transmit Ring Length CSRs bits 0-15 need to be set as the 2s complement of the ring length. - The AND with 0xFFFF clears the upper Reserved bits, which are to be written as zeroes read undefined. */ - PCNetWriteCSR(PCNET_CSR_RXRINGLEN, -PCNET_RX_BUFF_COUNT & 0xFFFF); - PCNetWriteCSR(PCNET_CSR_TXRINGLEN, -PCNET_TX_BUFF_COUNT & 0xFFFF); + Receive and Transmit Ring Length CSRs + bits 0-15 need to be set as the 2s complement + of the ring length. The AND with 0xFFFF clears + the upper Reserved bits, which are to be written + as zeroes read undefined. */ + PCNetWriteCSR(PCNET_CSR_RXRINGLEN, + -PCNET_RX_BUFF_COUNT & 0xFFFF); + PCNetWriteCSR(PCNET_CSR_TXRINGLEN, + -PCNET_TX_BUFF_COUNT & 0xFFFF); + + } U0 PCNetSetInterruptCSR() {/* AMD PCNet datasheet p.1-952, 1-953, 1-954, 1-955, 1-956, 1-957 Refer to datasheet for specifics on the Interrupt Masks. Most of these, when set 0, allow interrupts to be set in CSR0. - We set Big-Endian disabled, RX interrupts enabled, Init Done interrupt disabled, and TX interrupt disabled. */ + We set Big-Endian disabled, RX interrupts + enabled, Init Done interrupt disabled, and TX interrupt + disabled. */ U32 csr = PCNetReadCSR(PCNET_CSR_INTERRUPTS); Btr(&csr, PCNET_INT_BSWP); @@ -276,7 +362,9 @@ U0 PCNetSetInterruptCSR() U0 PCNetEnableTXAutoPad() {/* AMD PCNet datasheet p.1-958 - Setting bit 11 (Auto Pad Transmit) allows short transmit frames to be automatically extended to 64 bytes. */ + Setting bit 11 (Auto Pad Transmit) allows + shoft transmit frames to be automatically + extended to 64 bytes. */ U32 csr = PCNetReadCSR(PCNET_CSR_FEATURECTRL); Bts(&csr, PCNET_FEATURE_APADXMT); @@ -287,7 +375,11 @@ U0 PCNetEnableTXAutoPad() U0 PCNetExitConfigMode() {/* AMD PCNet datasheet p.1-954 - PCNet controller can be started after configuring by ensuring INIT and STOP are cleared and START bit is set in CSR0. */ + PCNet controller can be started + after configuring by ensuring INIT + and STOP are cleared and START bit + is set, in Status and Control Register + (CSR0). */ U32 csr = PCNetReadCSR(PCNET_CSR_CTRLSTATUS); Btr(&csr, PCNET_CTRL_INIT); @@ -295,22 +387,25 @@ U0 PCNetExitConfigMode() Bts(&csr, PCNET_CTRL_STRT); - PCNetWriteCSR(PCNET_CSR_CTRLSTATUS, csr); } I64 PCNetDriverOwns(CPCNetDescriptorEntry* entry) -{/* Returns whether the value of the OWN bit of the Descriptor Entry is zero. - If 0, driver owns, if 1, PCNet card owns it. */ +{/* Returns whether the value of the OWN bit of the + Descriptor Entry is zero. If 0, driver owns, + if 1, PCNet card owns it. */ return !Bt(&entry->status1, PCNET_DESCRIPTORf_OWN); + } I64 PCNetAllocateTransmitPacket(U8 **packet_buffer_out, I64 length) -{/* Transmits the packet at the current TX DE index. - The packet_buffer_out is a pointer, since we modify its value, ending with returning the index of the DE we just processed. - Length is validated to fit in BCNT. - The increment of the current TX DE index is done by assigning it the value of incrementing it AND the max DE index - 1. - This will increment it as well as wrap back to 0 if we hit the max DE index. */ +{/* Transmits the packet at the current TX DE index. The packet_buffer_out + is a pointer, since we modify its value, ending with returning the + index of the DE we just processed. Length is validated to fit in BCNT. + The increment of the current TX DE index is done by assigning it the + value of incrementing it AND the max DE index-1. This will increment it + as well as wrap back to 0 if we hit the max DE index. */ + U16 buffer_byte_count; I64 de_index = pcnet.current_tx_de_index; if (length > 0xFFF) @@ -319,6 +414,7 @@ I64 PCNetAllocateTransmitPacket(U8 **packet_buffer_out, I64 length) throw('PCNet'); } + CPCNetDescriptorEntry *entry = &pcnet.tx_de_buffer[de_index]; if (!PCNetDriverOwns(entry)) @@ -328,27 +424,36 @@ I64 PCNetAllocateTransmitPacket(U8 **packet_buffer_out, I64 length) } Bts(&entry->status1, PCNET_DESCRIPTORf_STP); + Bts(&entry->status1, PCNET_DESCRIPTORf_ENP); /* AMD PCNet datasheet p.1-991. - BCNT is the usable buffer length, expressed as first 12 bits of 2s-complement of desired length. + BCNT is the usable buffer length, expressed as first + 12 bits of 2s-complement of desired length. Bits 0-11 of a DE are for the buffer byte count (BCNT), and bits 12-15 of a DE must be written all ones (ONES) */ - entry->status1 |= -length & 0xFFF; // Sets BCNT reg (first 12 bits) in DE TMD1, as 2s complement of the desired length. - entry->status1 |= 0xF000; // Sets bits 12-15 (ONES) in DE TMD1 as all ones. + buffer_byte_count = -length; // Sets up as 2s complement of the desired length. + buffer_byte_count &= 0x0FFF; // Masks 0 over everything except bits 0-11. - pcnet.current_tx_de_index = (pcnet.current_tx_de_index + 1) & (PCNET_TX_BUFF_COUNT - 1); + entry->status1 |= buffer_byte_count; // Sets BCNT reg (first 12 bits) in DE TMD1. + entry->status1 |= 0xF000; // Sets bits 12-15 (ONES) in DE TMD1 as all ones. - *packet_buffer_out = pcnet.tx_buffer_addr + (de_index * ETHERNET_MIN_FRAME_SIZE); + pcnet.current_tx_de_index = (pcnet.current_tx_de_index + 1) + & (PCNET_TX_BUFF_COUNT - 1); + + *packet_buffer_out = pcnet.tx_buffer_addr + (de_index * ETHERNET_FRAME_SIZE); return de_index; + } U0 PCNetFinishTransmitPacket(I64 de_index) {/* Release ownership of the packet to the PCNet card by setting the OWN bit to 1. */ CPCNetDescriptorEntry *entry = &pcnet.tx_de_buffer[de_index]; + Bts(&entry->status1, PCNET_DESCRIPTORf_OWN); + } U0 EthernetFrameFinish(I64 de_index) @@ -357,38 +462,48 @@ U0 EthernetFrameFinish(I64 de_index) } I64 PCNetReceivePacket(U8 **packet_buffer_out, U16 *packet_length_out) -{/* Receives the packet at the current RX DE index. - Parameters are both pointers, since we modify the value at the packet_buffer_out, - and at the packet_length, ending with returning the index of the DE we just processed. - The MCNT is stored at the first two bytes of the RMD2. - We AND with 0xFFFF to only take in those first two bytes: that is the packet_length. - The increment of the current RX DE index is done by assigning it the value of incrementing it AND the max DE index - 1. - This will increment it as well as wrap back to 0 if we hit the max DE index. */ - ZenithLog("PCNet received packet. %X , %X",packet_buffer_out, packet_length_out); +{/* Receives the packet at the current RX DE index. Parameters + are both pointers, since we modify the value at the packet_buffer_out, + and at the packet_length, ending with returning the index of the DE + we just processed. + The MCNT is stored at the first two bytes of the RMD2. We AND with + 0xFFFF to only take in those first two bytes: that is the packet_length. + The increment of the current RX DE index is done by assigning it the + value of incrementing it AND the max DE index-1. This will increment it + as well as wrap back to 0 if we hit the max DE index. */ + ZenithErr("PCNet received packet. %X , %X",packet_buffer_out,packet_length_out); I64 de_index = pcnet.current_rx_de_index; CPCNetDescriptorEntry *entry = &pcnet.rx_de_buffer[de_index]; + U16 packet_length = entry->status2 & 0xFFFF; - pcnet.current_rx_de_index = (pcnet.current_rx_de_index + 1) & (PCNET_RX_BUFF_COUNT - 1); + pcnet.current_rx_de_index = (pcnet.current_rx_de_index + 1) + & (PCNET_RX_BUFF_COUNT - 1); - *packet_buffer_out = pcnet.rx_buffer_addr + (de_index * ETHERNET_MIN_FRAME_SIZE); + + *packet_buffer_out = pcnet.rx_buffer_addr + (de_index * ETHERNET_FRAME_SIZE); *packet_length_out = packet_length; return de_index; + + } U0 PCNetReleaseReceivePacket(I64 de_index) {/* Release ownership of the packet to the PCNet card by setting the OWN bit to 1. */ CPCNetDescriptorEntry *entry = &pcnet.rx_de_buffer[de_index]; + Bts(&entry->status1, PCNET_DESCRIPTORf_OWN); } interrupt U0 PCNetIRQ() {// todo: comments explaining process...maybe reimplement interrupt handling altogether. + U8 *packet_buffer; U16 packet_length; I64 de_index; + U32 csr = PCNetReadCSR(PCNET_CSR_CTRLSTATUS); //"Interrupt Reason: %X , %b\n",csr,csr; @@ -396,12 +511,12 @@ interrupt U0 PCNetIRQ() while (PCNetDriverOwns(&entry[pcnet.current_rx_de_index])) { - "%X", pcnet.current_rx_de_index; + "%X",pcnet.current_rx_de_index; de_index = PCNetReceivePacket(&packet_buffer, &packet_length); if (de_index >= 0) // necessary? check increment logic in PCNetReceivePacket. { - ZenithLog("Pushing copy into Net Queue, Releasing Receive Packet"); + ZenithErr("Pushing copy into Net Queue, Releasing Receive Packet"); NetQueuePushCopy(packet_buffer, packet_length); PCNetReleaseReceivePacket(de_index); } @@ -415,26 +530,26 @@ interrupt U0 PCNetIRQ() } U0 PCIRerouteInterrupts(I64 base) -{// todo: comments explaining process, maybe better var names +{ // todo: comments explaining process, maybe better var names I64 i; U8 *da = dev.uncached_alias + IOAPIC_REG; U32 *_d = dev.uncached_alias + IOAPIC_DATA; for (i = 0; i < 4; i++) { - *da = IOREDTAB + i * 2 + 1; + *da = IOREDTAB + i*2 + 1; *_d = dev.mp_apic_ids[INT_DEST_CPU] << 24; - *da = IOREDTAB + i * 2; + *da = IOREDTAB + i*2; *_d = 0x4000 + base + i; } } U0 PCNetSetupInterrupts() { // todo: comments explaining process - IntEntrySet(I_USER + 0, &PCNetIRQ, IDTET_IRQ); - IntEntrySet(I_USER + 1, &PCNetIRQ, IDTET_IRQ); - IntEntrySet(I_USER + 2, &PCNetIRQ, IDTET_IRQ); - IntEntrySet(I_USER + 3, &PCNetIRQ, IDTET_IRQ); + IntEntrySet(I_USER+0, &PCNetIRQ, IDTET_IRQ); + IntEntrySet(I_USER+1, &PCNetIRQ, IDTET_IRQ); + IntEntrySet(I_USER+2, &PCNetIRQ, IDTET_IRQ); + IntEntrySet(I_USER+3, &PCNetIRQ, IDTET_IRQ); PCIRerouteInterrupts(I_USER); } @@ -446,33 +561,58 @@ U0 PCNetInit() if (!pcnet.pci) return; // if we don't find the card, quit. - //Clear command register of PCNet PCI device, set IO Enable and Bus Master Enable bits of the register. - PCIWriteU16(pcnet.pci->bus, pcnet.pci->dev, pcnet.pci->fun, PCI_REG_COMMAND, PCNET_CMDF_IOEN | PCNET_CMDF_BMEN); + /* Clear command register of PCNet + PCI device, set IO Enable and Bus + Master Enable bits of the register. */ + PCIWriteU16(pcnet.pci->bus, + pcnet.pci->dev, + pcnet.pci->fun, + PCI_REG_COMMAND, + PCNET_CMDF_IOEN | PCNET_CMDF_BMEN); PCNetReset; + PCNetEnter32BitMode; + PCNetSetSWStyle; + PCNetGetMAC; // OSDev has code ensuring auto selected connection... + PCNetAllocateBuffers; + PCNetDirectInit; + PCNetSetInterruptCSR; + PCNetEnableTXAutoPad; + PCNetExitConfigMode; + PCNetSetupInterrupts; + + Sleep(100);//? necessary? ClassRep(&pcnet); "pcnet->rx_de_buffer: %X\n",pcnet.rx_de_buffer; "pcnet->tx_de_buffer: %X\n",pcnet.tx_de_buffer; "pcnet->rx_de_buffer_phys: %X\n",pcnet.rx_de_buffer_phys; "pcnet->rx_de_buffer_phys: %X\n",pcnet.tx_de_buffer_phys; + + } -I64 EthernetFrameAllocate(U8 **packet_buffer_out, U8 *source_address, U8 *destination_address, U16 ethertype, I64 packet_length) -{/* Allocate an Ethernet Frame for transmit. - The source and destination addresses are copied to the Frame, as well as the ethertype. - The packet_buffer_out parameter has the value at its pointer set to the payload of the Ethernet Frame. */ +I64 EthernetFrameAllocate( U8 **packet_buffer_out, + U8 *source_address, + U8 *destination_address, + U16 ethertype, + I64 packet_length) +{/* Allocate an Ethernet Frame for transmit. The source + and destination addresses are copied to the Frame, + as well as the ethertype. The packet_buffer_out + parameter has the value at its pointer set to the + payload of the Ethernet Frame. */ //todo: un magic number the rest of this U8 *ethernet_frame; @@ -482,7 +622,7 @@ I64 EthernetFrameAllocate(U8 **packet_buffer_out, U8 *source_address, U8 *destin if (packet_length < ETHERNET_MIN_FRAME_SIZE) packet_length = ETHERNET_MIN_FRAME_SIZE; - de_index = PCNetAllocateTransmitPacket(ðernet_frame, 14 + packet_length); + de_index = PCNetAllocateTransmitPacket(ðernet_frame, ETHERNET_MAC_HEADER_LENGTH + packet_length); if (de_index < 0) { @@ -493,16 +633,35 @@ I64 EthernetFrameAllocate(U8 **packet_buffer_out, U8 *source_address, U8 *destin MemCopy(ethernet_frame, destination_address, MAC_ADDRESS_LENGTH); MemCopy(ethernet_frame + MAC_ADDRESS_LENGTH, source_address, MAC_ADDRESS_LENGTH); - *ðernet_frame[ETHERNET_ETHERTYPE_OFFSET](U16 *) = ethertype; + ethernet_frame[ETHERNET_ETHERTYPE_OFFSET] = ethertype << 8; + ethernet_frame[ETHERNET_ETHERTYPE_OFFSET + 1] = ethertype & 0xFF; *packet_buffer_out = ethernet_frame + ETHERNET_MAC_HEADER_LENGTH; return de_index; } -U64 EthernetGetMAC() +U8 *EthernetGetMAC() { return pcnet.mac_address; } PCNetInit; + + + + + + + + + + + + + + + + + + diff --git a/src/Home/Net/Sockets.CC b/src/Home/Net/Sockets.CC new file mode 100755 index 00000000..da3cd4ea --- /dev/null +++ b/src/Home/Net/Sockets.CC @@ -0,0 +1,488 @@ +/* docs.idris-lang.org/en/latest/st/examples.html + beej.us/guide/bgnet/html/ + + Sockets are non-standard, a simple + Finite State Machine. The functions' + only args are the socket. Socket functions + requiring more parameters should be + defined at the protocol level. + + The state machine exists to allow + protocol code to execute code in + the appropriate order. When calling + a socket function, code can use + the modified/unmodified states to + determine next procedure. + + I included some code for IPV6, currently + unused. */ + +#define SOCKET_STATE_READY 0 +#define SOCKET_STATE_BIND_REQ 1 +#define SOCKET_STATE_CONNECT_REQ 2 +#define SOCKET_STATE_BOUND 3 +#define SOCKET_STATE_LISTEN_REQ 4 +#define SOCKET_STATE_LISTENING 5 +#define SOCKET_STATE_OPEN 6 +#define SOCKET_STATE_CLOSE_REQ 7 +#define SOCKET_STATE_CLOSED 8 + +#define SOCKET_STREAM 1 +#define SOCKET_DATAGRAM 2 +#define SOCKET_RAW 3 + +#define AF_INET 2 +#define AF_INET6 10 + +#define INET_ADDRSTRLEN 16 //pubs.opengroup.com netinit/in.h +#define INET6_ADDRSTRLEN 46 + +#define INET_MIN_ADDRSTRLEN 7 // ex: len of 0.0.0.0 +#define INET6_MIN_ADDRSTRLEN 2 // ie: len of :: + + +#define IP_PARSE_STATE_NUM 0 +#define IP_PARSE_STATE_DOT 1 + + +class CSocketAddress +{ + U16 family; // 'address family, AF_xxx' + + U8 data[14]; // '14 bytes of protocol address' +}; + +class CIPV4Address +{ + U32 address; // 'in Network Byte order' ... Big Endian +}; + +class CIPV6Address +{ + U8 address[16]; //a clear #define would be nice +}; + +class CIPAddressStorage +{// class specifically meant to be generic casted either IPV4 or IPV6 Address. + U8 padding[16]; +}; + +class CSocketAddressIPV4 +{ + I16 family; // 'AF_INET' + U16 port; // 'in Network Byte order' ... Big Endian + + CIPV4Address address; + + U8 zeroes[8]; //'same size as socket address' +}; + +class CSocketAddressIPV6 +{ + U16 family; // 'AF_INET6' + U16 port; // 'in Network Byte order'... Big Endian + + U32 flow_info; + + CIPV6Address address; + + U32 scope_id; +}; + +class CSocketAddressStorage +{/* 'designed to be large enough to + hold both IPV4 and IPV6 structures.' */ + + U16 family; + + U8 padding[26]; + +}; + +class CAddressInfo +{ + I32 flags; + I32 family; + I32 socket_type; + I32 protocol; + + I64 address_length; + + CSocketAddress *address; + + U8 *canonical_name; + + CAddressInfo *next; +}; + +class CSocket +{ + U8 state; + + U16 type; + U16 domain; +}; + +Bool IPV4AddressParse(U8 *string, U32* destination) +{ +// U8* lexable_string; +// lexable_string = StrReplace(string, ",", ","); // swap any commas with an unexpected value + + U8 *lexable_string = StrReplace(string, ".", ","); // swap dots with commas since Lex is easier with them. + + CCompCtrl* cc = CompCtrlNew(lexable_string); + //Bts(&cc->opts, OPTf_DECIMAL_ONLY); + + cc->opts |= 1 << OPTf_DECIMAL_ONLY; + + I64 tk; + + I64 state = IP_PARSE_STATE_NUM; + U32 temp_destination = 0; + + I64 current_section = 0; // IPV4 address has 4 total sections + + while (tk = Lex(cc)) + { + switch (state) + { + case IP_PARSE_STATE_NUM: + switch (tk) + { + case TK_I64: + if (cc->cur_i64 > 255 || cc->cur_i64 < 0) + { + ZenithErr("Invalid value, must be 0 - 255.\n"); + return FALSE; + } + if (current_section > 3) + { + ZenithErr("IP Address can only have 4 sections.\n"); + return FALSE; + } + + temp_destination |= cc->cur_i64 << (current_section * 8); + current_section++; + + state = IP_PARSE_STATE_DOT; + + break; + + default: + ZenithErr("Expected decimal. \n"); + return FALSE; + } + break; + + case IP_PARSE_STATE_DOT: + switch (tk) + { + case ',': + state = IP_PARSE_STATE_NUM; + break; + + default: + ZenithErr("Expected dot. \n"); + return FALSE; + } + break; + } + } + + temp_destination = EndianU32(temp_destination); // store the address in Network Byte Order (Big-Endian) + *destination = temp_destination; + "\n\n%X\n\n",temp_destination; + return TRUE; +} + +I64 PresentationToNetwork(I64 address_family, U8 *string, CIPAddressStorage *destination) +{/* Converts IP string to internet address class, our inet_pton(). + Destination written as CIPV4Address or CIPV6Address depending + on value of address_family. + The destination address is the generic class, functions + calling this method must cast their classes in the params. + + TODO: test it more... clarify above comment? is casting shit needed if we do it like this?.. + if we declare the possible address classes, then just set them to where the + destination is from param, wouldn't that suffice fully? I noticed that it wrote fine to + a pointer to CIPV4Address without any complaints that it wasn't CIPAddressStorage .. */ + + //CCompCtrl *cc = CompCtrlNew(string); + + CIPV4Address *ipv4_address; + CIPV6Address *ipv6_address; + + I64 string_length = StrLen(string); + + switch (address_family) + { + case AF_INET: + if (string_length > INET_ADDRSTRLEN || string_length < INET_MIN_ADDRSTRLEN) + { + ZenithErr("IP to Socket Address failed: Invalid Input String Size.\n"); + return -1; + } + ipv4_address = destination; + + IPV4AddressParse(string, &ipv4_address->address); + + break; + + case AF_INET6: + if (string_length > INET6_ADDRSTRLEN || string_length < INET6_MIN_ADDRSTRLEN) + { + ZenithErr("IP to Socket Address failed: Invalid Input String Size.\n"); + return -1; + } + ipv6_address = destination; + + + Debug("IPV6 support not implemented yet.\n"); + + + break; + + default: + ZenithErr("IP to Socket Address failed: Invalid Address Family.\n"); + return -1; + } + + //CompCtrlDel(cc); + return 0; + +} + +U8 *NetworkToPresentation(I64 address_family, CIPAddressStorage *source) +{ // converts socket address to IP string, our inet_ntop. Taking Shrine approach of function returns U8* . + +// I64 i; + + U8* ip_string; +// U8* ip_string[INET_ADDRSTRLEN]; + CIPV4Address *ipv4_source; + CIPV4Address *ipv6_source; + + switch (address_family) + { + case AF_INET: + + ipv4_source = source; + + + StrPrint(ip_string, "%d.%d.%d.%d", + ipv4_source->address.u8[3], + ipv4_source->address.u8[2], + ipv4_source->address.u8[1], + ipv4_source->address.u8[0]); + + break; + case AF_INET6: + + ipv6_source = source; + + Debug("IPV6 support not implemented yet.\n"); + + break; + default: + ZenithErr("Socket Address to IP failed: Invalid Address Family.\n"); + break; + } + + + + return ip_string; +} + + +CSocket *Socket(U16 domain, U16 type) +{ + CSocket *socket = CAlloc(sizeof(CSocket)); + + socket->domain = domain; + socket->type = type; + + socket->state = SOCKET_STATE_READY; + + return socket; +} + +U0 SocketStateErr(U8 *request, U8 state) +{ + U8 *state_string; + switch (state) + { + case SOCKET_STATE_READY: + state_string = StrNew("READY"); + break; + case SOCKET_STATE_BIND_REQ: + state_string = StrNew("BIND REQUEST"); + break; + case SOCKET_STATE_CONNECT_REQ: + state_string = StrNew("CONNECT REQUEST"); + break; + case SOCKET_STATE_BOUND: + state_string = StrNew("BOUND"); + break; + case SOCKET_STATE_LISTEN_REQ: + state_string = StrNew("LISTEN REQUEST"); + break; + case SOCKET_STATE_LISTENING: + state_string = StrNew("LISTENING"); + break; + case SOCKET_STATE_OPEN: + state_string = StrNew("OPEN"); + break; + case SOCKET_STATE_CLOSE_REQ: + state_string = StrNew("CLOSE REQUEST"); + break; + case SOCKET_STATE_CLOSED: + state_string = StrNew("CLOSED"); + break; + } + ZenithErr("Socket attempted %s while in %s state.\n", request, state_string); +} + + + + +U0 SocketAccept(CSocket *socket) +{ + switch (socket->state) + { + case SOCKET_STATE_LISTENING: + /* Socket expected to stay listening. + At protocol level, a new socket 'connected' + to this one is expected to be made. */ + return; + + default: + SocketStateErr("ACCEPT", socket->state); + break; + } +} + +U0 SocketClose(CSocket* socket) +{ + switch (socket->state) + { + case SOCKET_STATE_LISTENING: + case SOCKET_STATE_OPEN: + /* Sockets can only be closed if + they were opened or listening + to incoming connections. */ + socket->state = SOCKET_STATE_CLOSE_REQ; + break; + + default: + SocketStateErr("CLOSE", socket->state); + break; + } +} + +U0 SocketBind(CSocket* socket) +{ + switch (socket->state) + { + case SOCKET_STATE_READY: + /* Sockets can only be bound + if they are in initial state. */ + socket->state = SOCKET_STATE_BIND_REQ; + break; + + default: + SocketStateErr("BIND", socket->state); + break; + } +} + +U0 SocketConnect(CSocket* socket) +{ + switch (socket->state) + { + case SOCKET_STATE_READY: + /* Sockets can only be connected + if they are in initial state. */ + socket->state = SOCKET_STATE_CONNECT_REQ; + break; + + default: + SocketStateErr("CONNECT", socket->state); + break; + } +} + +U0 SocketListen(CSocket* socket) +{ + switch (socket->state) + { + case SOCKET_STATE_BOUND: + /* A socket must be bound to + set it to listening. */ + socket->state = SOCKET_STATE_LISTEN_REQ; + break; + + default: + SocketStateErr("LISTEN", socket->state); + break; + } +} + +U0 SocketReceive(CSocket* socket) +{ + switch (socket->state) + { + case SOCKET_STATE_OPEN: + /* Sockets can only send/recv when + they have been connected to. */ + break; + + default: + SocketStateErr("RECEIVE", socket->state); + break; + } +} + +U0 SocketReceiveFrom(CSocket* socket) +{ + switch (socket->state) + { + case SOCKET_STATE_OPEN: + /* Sockets can only send/recv when + they have been connected to. */ + break; + + default: + SocketStateErr("RECEIVE FROM", socket->state); + break; + } +} + +U0 SocketSend(CSocket* socket) +{ + switch (socket->state) + { + case SOCKET_STATE_OPEN: + /* Sockets can only send/recv when + they have been connected to. */ + break; + + default: + SocketStateErr("SEND", socket->state); + break; + } +} + +U0 SocketSendTo(CSocket* socket) +{ + switch (socket->state) + { + case SOCKET_STATE_OPEN: + /* Sockets can only send/recv when + they have been connected to. */ + break; + + default: + SocketStateErr("SEND TO", socket->state); + break; + } +} \ No newline at end of file diff --git a/src/Home/Net/Tests/Test.CC b/src/Home/Net/Tests/Test.CC new file mode 100755 index 00000000..cd813b3f --- /dev/null +++ b/src/Home/Net/Tests/Test.CC @@ -0,0 +1,94 @@ + + + + + + + +//#define IP_PARSE_STATE_FIRST_NUM 0 +#define IP_PARSE_STATE_NUM 1 +#define IP_PARSE_STATE_DOT 2 +#define IP_PARSE_STATE_ERROR 3 + + +I64 IPV4AddressParse(U8 *string, U32 destination) +{//destination output in network order, only write on success + + I64 i; + I64 parse_state = IP_PARSE_STATE_NUM; + U32 temp_destination = 0; + + I64 current_chunk_index = 0; + U8 *digit_buffer = StrNew(""); + + // chunk is just IPV4 num 0 through 3. on last chunk, don't go to DOT state. + + for (i = 0; i < 16; i++) //use the #define in Sockets + { + switch (parse_state) + { + case IP_PARSE_STATE_NUM: + switch (string[i]) + { + case '.': + parse_state = IP_PARSE_STATE_DOT; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + StrPrint(digit_buffer, "%s%c", digit_buffer, string[i]); + break; + + default: + ZenithErr("IPV4 Parse Failure: Unexpected char.\n"); + parse_state = IP_PARSE_STATE_ERROR; + break; + + + } + break; + case IP_PARSE_STATE_DOT: + "%s, %X\n", digit_buffer, Str2I64(digit_buffer); + if (Str2I64(digit_buffer) > 255) + { + ZenithErr("IPV4 Parse Failure: Chunk exceeds 0 - 255 range.\n"); + parse_state = IP_PARSE_STATE_ERROR; + break; + } + + + temp_destination |= Str2I64(digit_buffer) << (current_chunk_index * 8); + + StrCopy(digit_buffer, ""); // clear digit buffer + + + current_chunk_index++; + if (current_chunk_index > 3) + { + ZenithErr("IPV4 Parse Failure: Too many dots in address string.\n"); + parse_state = IP_PARSE_STATE_ERROR; + break; + } + + parse_state = IP_PARSE_STATE_NUM; + + break; + + case IP_PARSE_STATE_ERROR: + ZenithErr("IPV4 Parse Failure: Invalid Address String.\n"); + return -1; // error state! + } + } + + return 0; + + +} \ No newline at end of file diff --git a/src/Home/Net/Tests/Test2.CC b/src/Home/Net/Tests/Test2.CC new file mode 100755 index 00000000..90028739 --- /dev/null +++ b/src/Home/Net/Tests/Test2.CC @@ -0,0 +1,220 @@ + + + + + + + +//#define IP_PARSE_STATE_FIRST_NUM 0 +#define IP_PARSE_STATE_NUM 1 +#define IP_PARSE_STATE_DOT 2 +#define IP_PARSE_STATE_ERROR 3 + + + + + + + + +/* + +U8 *StrDotReplace(U8* source_string) +{ + I64 i; + + U8 *result = StrNew(source_string); + + for (i = 0; i < StrLen(result); i++) + { + if (result[i] == ',') + { // we're using commas internally for Lexing. + result[i] = '.'; + } // so if we see a comma, set to dot, break lexer. + else if (result[i] == '.') + { // Lex eats '.' as F64. Comma will split Lex tokens + result[i] = ','; + } + } + + return result; +} +*/ + +Bool IPV4AddressParse(U8 *string, U32* destination) +{ +// U8* lexable_string; +// lexable_string = StrReplace(string, ",", ","); // swap any commas with an unexpected value + + U8 *lexable_string = StrReplace(string, ".", ","); // swap dots with commas since Lex is easier with them. + + CCompCtrl* cc = CompCtrlNew(lexable_string); + //Bts(&cc->opts, OPTf_DECIMAL_ONLY); + + cc->opts |= 1 << OPTf_DECIMAL_ONLY; + + I64 tk; + + I64 state = IP_PARSE_STATE_NUM; + U32 temp_destination = 0; + + I64 current_section = 0; // IPV4 address has 4 total sections + + while (tk = Lex(cc)) + { + switch (state) + { + case IP_PARSE_STATE_NUM: + switch (tk) + { + case TK_I64: + if (cc->cur_i64 > 255 || cc->cur_i64 < 0) + { + ZenithErr("Invalid value, must be 0 - 255.\n"); + return FALSE; + } + if (current_section > 3) + { + ZenithErr("IP Address can only have 4 sections.\n"); + return FALSE; + } + + temp_destination |= cc->cur_i64 << (current_section * 8); + current_section++; + + state = IP_PARSE_STATE_DOT; + + break; + + default: + ZenithErr("Expected decimal. \n"); + return FALSE; + } + break; + + case IP_PARSE_STATE_DOT: + switch (tk) + { + case ',': + state = IP_PARSE_STATE_NUM; + break; + + default: + ZenithErr("Expected dot. \n"); + return FALSE; + } + break; + } + } + + temp_destination = EndianU32(temp_destination); // store the address in Network Byte Order (Big-Endian) + *destination = temp_destination; + "\n\n%X\n\n",temp_destination; + return TRUE; +} + + + + + + + + + + + + + + + + + + + + + + + + +I64 IPV4AddressParseOOOOOOOOOOPS(U8 *string, U32 destination) +{//destination output in network order, only write on success + + I64 i; + I64 parse_state = IP_PARSE_STATE_NUM; + U32 temp_destination = 0; + + I64 current_chunk_index = 0; + U8 *digit_buffer = StrNew(""); + destination = 'nigggg'; + + // chunk is just IPV4 num 0 through 3. on last chunk, don't go to DOT state. + + for (i = 0; i < 16; i++) //use the #define in Sockets + { + switch (parse_state) + { + case IP_PARSE_STATE_NUM: + switch (string[i]) + { + case '.': + parse_state = IP_PARSE_STATE_DOT; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + StrPrint(digit_buffer, "%s%c", digit_buffer, string[i]); + break; + + default: + ZenithErr("IPV4 Parse Failure: Unexpected char.\n"); + parse_state = IP_PARSE_STATE_ERROR; + break; + + + } + break; + case IP_PARSE_STATE_DOT: + "%s, %X\n", digit_buffer, Str2I64(digit_buffer); + if (Str2I64(digit_buffer) > 255) + { + ZenithErr("IPV4 Parse Failure: Chunk exceeds 0 - 255 range.\n"); + parse_state = IP_PARSE_STATE_ERROR; + break; + } + + + temp_destination |= Str2I64(digit_buffer) << (current_chunk_index * 8); + + StrCopy(digit_buffer, ""); // clear digit buffer + + + current_chunk_index++; + if (current_chunk_index > 3) + { + ZenithErr("IPV4 Parse Failure: Too many dots in address string.\n"); + parse_state = IP_PARSE_STATE_ERROR; + break; + } + + parse_state = IP_PARSE_STATE_NUM; + + break; + + case IP_PARSE_STATE_ERROR: + ZenithErr("IPV4 Parse Failure: Invalid Address String.\n"); + return -1; // error state! + } + } + + return 0; + + +} \ No newline at end of file diff --git a/src/Home/Net/Tests/Test3.CC b/src/Home/Net/Tests/Test3.CC new file mode 100755 index 0000000000000000000000000000000000000000..f8bbcb56b3c3cbaf1d475c8552b9148af7c50a1f GIT binary patch literal 5817 zcmb_fYm6jS6&?hXZp+0G!jCZ|cPDEyGxp4|JS1V5hGh&g32qkHHOgyJ-F2tCnChzD zs$0`LyR1%(j~Iy>Hzq48h$6{C;v*#d0TRMf@xfE0{4voG6CY6?31EWyedktHR}DSG z#@Oj|@44rmdtUdPdvDE6_q6FoUUEr&f2{T1$kR)qZ;HV_H+Fj!${x?F&Xx&R`r&Z) znpCG%X+kOU3xSKK=j!#jIdz2}y79WADQeM=+iBp&N|=&aVb~w3#aO#WH`L;uAc)#3 z)C0Ac+g>m&g^7;MXr_i*@q<9Exk0K`Pxo3nZcw@D>QI^3UDI)*Il=&TwA@5{D#8>f zCp&HB={3vES*MS+4e1R7V@N_rTRx zHqqCA2AkDUk8H4Tsp(K3GV@gu z37~_q3bWWIr|L%- zNFTUll0C~R_`5tFujpR>!lnz~at(ahZzX47+MTsiv6CIqBM<>jzp*7OSV~HP~50aZS>V znWk)*x>PI{2^k5DH5+TxE@PPV;08_v76^9~^R+G1Vx~|?b8~}cnprB;j$$>dc(W3H zFh$))G2#|?GG&r1|C6>X&1zJKKP%A4X{8bNXa%UBG^AC_NjFLZZy9@&q=7+($<|3k zWMxaszT&8}95Ui|7#s^GitWKDk-}kxvjYldm9IM3$%H!|_4=u?T%M1y$@f3eJcveY zNfF(AOPKjv??#E8VMgF-4f2G6ma56tZWrW|5Q$zP1r<@s_&oRJ8f1C!z@xfl$){1( zAqSx6Dc7cxt&={lQS3ohf&Iu24bDZxl$QzxR*E3ooQ0w?c__d`OV5+pI&Ljt4dlks zPRof+XUUdo-_oV&GHc-q^XLAQN_N6hN7fPNo|!#RyS17HSO-syznmL zxzWPIXcbw46fTbX+-A9#cgicn)|OE#Q5t%-mJ8EVMS{oXEnDD+X5~h2p?x zP|MM&cwUrN;n&FUX7!cDSF-Y;eW=&~mqI3YE8;P{T!r%;K9xerTQ<+hk|ge)y+TC- z;#!)VX=X3I%H?hucUCy%4yOiMG&KEP4azMVE@5(14I0i3?U8G?s4>3WC9nC&P9imM z*HMyaN^Z!T3Acm&?yyf} ze1&j{$Rm@zVkimU0C$_QuJF?aDV6u4jr*y}oMk zaDX@MReYNSJ|1@iz8j!$KvgdJT)ACv`G<+ouDmonTx=Z*SQY^{(8!yhlw%Rp-THONfGESiY~cU_a`}v;;T={3k1fDlFM91O792nUZ8S^tGdl_ zdzLS#-51UmU(Nm>3Vp_^o$f8NQ-JUQXK!3xd-=^~Ds)gnf@uOnQ^ zBUKS0TX7ODA^x;uq>OEpZr-oT*uK)Si<_uo;1=YW=JvBRka;1$UDxevOkBFVInlUc zX<_%SkqNmaaqN^a`#baWF~Lt6bAAKLhX8*8TmW$HN3$331VEGURW!E(?gbnpp@rrI z;PZf=k+6W~4}dn{Z4!Qh=5D}~fJaIA0h&Jo9ss;a!p}3p@6i8?glEtsfIk7gLc(9s z+zvP$Mn6o#4j{h(vI{*Yo!8M^4tPHZpCsX2AU6TI7=21Y56BT98|aUd@F^fP-9t}X zoG)aA@l_=J7)=bg4{(Tt+t7Rmz&;Koqk`#7K@-vh|ZVIZ_yWuRlRT?(t(7e1?P$*1rNILjMK{Ye4=67(a_^K~>#mSgA{b?2Kv9tCnIdIrMDk-V4GegZQtIDSozD}g)$vmVb{>F6MIL qoB(3b|C9upelvg^%m+tdhPHed{RRog&>RP_ylAb%M9j@5KK5VnxL!d3 literal 0 HcmV?d00001 diff --git a/src/Home/Net/Tests/Test4.CC b/src/Home/Net/Tests/Test4.CC new file mode 100755 index 0000000000000000000000000000000000000000..bd639bdfb632ecd81d52ec19eec2cb0c1f1bd560 GIT binary patch literal 5914 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 literal 0 HcmV?d00001 diff --git a/src/Home/Net/Tests/Test6.CC b/src/Home/Net/Tests/Test6.CC new file mode 100755 index 00000000..fc9695e8 --- /dev/null +++ b/src/Home/Net/Tests/Test6.CC @@ -0,0 +1,90 @@ +/* +CUDPSocket *s = UDPSocket(AF_INET); + +CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4)); + +ClassRep(addr); +Class:"CSocketAddressIPV4" +15A984E30 $FG,2$family :$FG$0000 +15A984E32 $FG,2$port :$FG$0000 +15A984E34 $TR-C,"address"$ +$ID,2$Class:"CIPV4Address" +15A984E34 $FG,2$address :$FG$00000000 +$ID,-2$15A984E38 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00 + +0.000360s +addr->family = AF_INET; +0.000006s ans=0x00000002=2 +addr->port = EndianU16(0xDEAF); +0.000007s ans=0x0000AFDE=45022 +PresentationToNetwork(AF_INET,"102.33.6.1",&addr->address); + + +66210601 + +0.000027s ans=0x00000000=0 +ClassRep(addr); +Class:"CSocketAddressIPV4" +15A984E30 $FG,2$family :$FG$0002 +15A984E32 $FG,2$port :$FG$AFDE +15A984E34 $TR-C,"address"$ +$ID,2$Class:"CIPV4Address" +15A984E34 $FG,2$address :$FG$66210601 +$ID,-2$15A984E38 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00 + +0.000385s +UDPSocketBind(s, addr); +0.000008s ans=0x00000000=0 +ClassRep(s); +Class:"CUDPSocket" +15A982C58 $TR,"socket"$ +$ID,2$Class:"CSocket" +15A9931E8 $FG,2$state :$FG$03 +15A9931E9 $FG,2$type :$FG$0002 +15A9931EB $FG,2$domain :$FG$0002 +$ID,-2$15A982C60 $FG,2$receive_timeout_ms :$FG$0000000000000000 +15A982C68 $FG,2$receive_max_timeout :$FG$0000000000000000 +15A982C70 $FG,2$receive_buffer :$FG$ +15A982C78 $FG,2$receive_len :$FG$0000000000000000 +15A982C80 $TR-C,"receive_address"$ +$ID,2$Class:"CSocketAddressIPV4" +15A982C80 $FG,2$family :$FG$0002 +15A982C82 $FG,2$port :$FG$0000 +15A982C84 $TR,"address"$ +$ID,2$$ID,-2$15A982C88 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00 +$ID,-2$15A982C90 $FG,2$bound_to :$FG$DEAF + +0.000526s +ClassRep(&s->receive_address); +Class:"CSocketAddressIPV4" +15A982C80 $FG,2$family :$FG$0002 +15A982C82 $FG,2$port :$FG$0000 +15A982C84 $TR-C,"address"$ +$ID,2$Class:"CIPV4Address" +15A982C84 $FG,2$address :$FG$66210601 +$ID,-2$15A982C88 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00 + +0.000323s + +*/ + +CUDPSocket *s = UDPSocket(AF_INET); +CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4)); + +ClassRep(s); +ClassRep(addr); + +"\nSetting addr family to AF_INET and port to 0xDEAF in B.E., then P to N with 102.33.6.1\n"; +addr->family = AF_INET; +addr->port = EndianU16(0xDEAF); +PresentationToNetwork(AF_INET,"102.33.6.1",&addr->address); + +ClassRep(addr); + +"\nUDPSocket bind with socket to addr\n"; +UDPSocketBind(s, addr); + +ClassRep(s); +ClassRep(&s->receive_address); + +UDPSocketClose(s); \ No newline at end of file diff --git a/src/Home/Net/Tests/Test7.CC b/src/Home/Net/Tests/Test7.CC new file mode 100755 index 00000000..a38868ef --- /dev/null +++ b/src/Home/Net/Tests/Test7.CC @@ -0,0 +1,129 @@ +/* +CUDPSocket *s = UDPSocket(AF_INET); + +CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4)); + +ClassRep(addr); +Class:"CSocketAddressIPV4" +15A984E30 $FG,2$family :$FG$0000 +15A984E32 $FG,2$port :$FG$0000 +15A984E34 $TR-C,"address"$ +$ID,2$Class:"CIPV4Address" +15A984E34 $FG,2$address :$FG$00000000 +$ID,-2$15A984E38 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00 + +0.000360s +addr->family = AF_INET; +0.000006s ans=0x00000002=2 +addr->port = EndianU16(0xDEAF); +0.000007s ans=0x0000AFDE=45022 +PresentationToNetwork(AF_INET,"102.33.6.1",&addr->address); + + +66210601 + +0.000027s ans=0x00000000=0 +ClassRep(addr); +Class:"CSocketAddressIPV4" +15A984E30 $FG,2$family :$FG$0002 +15A984E32 $FG,2$port :$FG$AFDE +15A984E34 $TR-C,"address"$ +$ID,2$Class:"CIPV4Address" +15A984E34 $FG,2$address :$FG$66210601 +$ID,-2$15A984E38 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00 + +0.000385s +UDPSocketBind(s, addr); +0.000008s ans=0x00000000=0 +ClassRep(s); +Class:"CUDPSocket" +15A982C58 $TR,"socket"$ +$ID,2$Class:"CSocket" +15A9931E8 $FG,2$state :$FG$03 +15A9931E9 $FG,2$type :$FG$0002 +15A9931EB $FG,2$domain :$FG$0002 +$ID,-2$15A982C60 $FG,2$receive_timeout_ms :$FG$0000000000000000 +15A982C68 $FG,2$receive_max_timeout :$FG$0000000000000000 +15A982C70 $FG,2$receive_buffer :$FG$ +15A982C78 $FG,2$receive_len :$FG$0000000000000000 +15A982C80 $TR-C,"receive_address"$ +$ID,2$Class:"CSocketAddressIPV4" +15A982C80 $FG,2$family :$FG$0002 +15A982C82 $FG,2$port :$FG$0000 +15A982C84 $TR,"address"$ +$ID,2$$ID,-2$15A982C88 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00 +$ID,-2$15A982C90 $FG,2$bound_to :$FG$DEAF + +0.000526s +ClassRep(&s->receive_address); +Class:"CSocketAddressIPV4" +15A982C80 $FG,2$family :$FG$0002 +15A982C82 $FG,2$port :$FG$0000 +15A982C84 $TR-C,"address"$ +$ID,2$Class:"CIPV4Address" +15A982C84 $FG,2$address :$FG$66210601 +$ID,-2$15A982C88 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00 + +0.000323s + +*/ + +"\nMaking new socket\n"; +CUDPSocket *s = UDPSocket(AF_INET); +CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4)); + +ClassRep(s); +ClassRep(addr); + +"\nSetting addr family to AF_INET and port to 0xDEAF in B.E., then P to N with 102.33.6.1\n"; +addr->family = AF_INET; +addr->port = EndianU16(0xDEAF); +PresentationToNetwork(AF_INET,"102.33.6.1",&addr->address); + +ClassRep(addr); + +"\nUDPSocket bind with socket to addr\n"; +UDPSocketBind(s, addr); + +ClassRep(s); +ClassRep(&s->receive_address); + +"\nGlobal Tree:\n"; +ClassRep(udp_globals.bound_socket_tree); +"\nGlobal Tree: Port 0xDEAF Queue\n"; +ClassRep(udp_globals.bound_socket_tree->queue); + +/// + +"\nMaking new socket\n"; +CUDPSocket *s2 = UDPSocket(AF_INET); +CSocketAddressIPV4 *addr2 = CAlloc(sizeof(CSocketAddressIPV4)); + +ClassRep(s2); +ClassRep(addr2); + +"\nSetting addr2 family to AF_INET and port to 0xDEAF in B.E., then P to N with 104.32.3.66\n"; +addr2->family = AF_INET; +addr2->port = EndianU16(0xDEAF); +PresentationToNetwork(AF_INET,"104.32.3.66",&addr2->address); + +ClassRep(addr2); + +"\nUDPSocket bind with socket2 to addr2\n"; +UDPSocketBind(s2, addr2); + +ClassRep(s2); +ClassRep(&s2->receive_address); + +"\nGlobal Tree:\n"; +ClassRep(udp_globals.bound_socket_tree); +"\nGlobal Tree: Port 0xDEAF Queue\n"; +ClassRep(udp_globals.bound_socket_tree->queue); + +"\nClosing first socket\n"; +UDPSocketClose(s); + +"\nGlobal Tree:\n"; +ClassRep(udp_globals.bound_socket_tree); +"\nGlobal Tree: Port 0xDEAF Queue\n"; +ClassRep(udp_globals.bound_socket_tree->queue); \ No newline at end of file diff --git a/src/Home/Net/UDP.CC b/src/Home/Net/UDP.CC new file mode 100755 index 00000000..03d86efb --- /dev/null +++ b/src/Home/Net/UDP.CC @@ -0,0 +1,614 @@ +//#include "IPV4" +#include "ICMP" // this is wrong and only doing this because we're just in dev right now. probably need approach like Shrine, MakeNet, idk. +#include "Sockets" + +#define UDP_MAX_PORT 65535 + +class CUDPHeader +{ + U16 source_port; + U16 destination_port; + + U16 length; + U16 checksum; +}; + +class CUDPSocket +{ + CSocket* socket; + + I64 receive_timeout_ms; + I64 receive_max_timeout; + + U8 *receive_buffer; + I64 receive_len; + + CSocketAddressIPV4 receive_address; // should this change to Storage class for IPV6 later ? + + U16 bound_to; // represents the currently bound port +}; + + + + +//////////////////////////////////////////////////// +// UDP Bound Socket Tree Classes & Functions + +class CUDPTreeQueue +{ // next, last for CQueue implementation. + CUDPTreeQueue *next; + CUDPTreeQueue *last; + + CUDPSocket* socket; +}; + +class CUDPTreeNode +{ + I64 port; + + CUDPTreeNode *left; + CUDPTreeNode *right; + + CUDPTreeQueue* queue; +}; + +CUDPTreeNode *UDPTreeNodeInit() +{ // init new empty tree/node. + CUDPTreeNode *tree_node = CAlloc(sizeof(CUDPTreeNode)); + + return tree_node; +} + +U0 UDPTreeNodeAdd(CUDPTreeNode *node, CUDPTreeNode *tree) +{ // using temp and last allows avoiding recursion and non-growing stack issues. + CUDPTreeNode *temp_tree = tree; + CUDPTreeNode *last_tree = temp_tree; + + while (temp_tree) + { + if (node->port < temp_tree->port) + { // if node smaller, go left + last_tree = temp_tree; + temp_tree = temp_tree->left; + } + else + { // if node equal or larger, go right + last_tree = temp_tree; + temp_tree = temp_tree->right; + } // at the end of this, this _should_ result in last_tree + } // being the resulting tree to store the node inside of. i guess recompute the direction and set. + + if (node->port < last_tree->port)// if node smaller, go left + last_tree->left = node; + else // if node equal or larger, go right + last_tree->right = node; +} + +CUDPTreeNode *UDPTreeNodeParamAdd(I64 node_port, CUDPTreeNode *tree) +{ // add a node using params, return pointer to the node + CUDPTreeNode *result = UDPTreeNodeInit; + result->port = node_port; + + UDPTreeNodeAdd(result, tree); + + return result; +} + +CUDPTreeNode *UDPTreeNodeParamInit(I64 port) +{ + CUDPTreeNode *result = UDPTreeNodeInit; + result->port = port; + + return result; +} + +CUDPTreeNode *UDPTreeNodeFind(I64 port, CUDPTreeNode *tree) +{ + CUDPTreeNode *temp_tree = tree; + + while (temp_tree) + { + if (port < temp_tree->port) // if value smaller, go left + temp_tree = temp_tree->left; + else if (port > temp_tree->port) // if value larger, go right + temp_tree = temp_tree->right; + else // if value equal, match! i guess? + break; + } + return temp_tree; // ! NULL if not found. +} + +CUDPTreeNode *UDPTreeNodePop(I64 port, CUDPTreeNode *tree) +{ // mimics TreeNodeFind. pops whole sub-tree, original tree loses whole branch. + CUDPTreeNode *parent_tree = tree; + CUDPTreeNode *temp_tree = parent_tree; + Bool is_left = FALSE; + Bool is_right = FALSE; + + while (temp_tree) + { + if (port < temp_tree->port) + { + parent_tree = temp_tree; + temp_tree = temp_tree->left; + is_right = FALSE; + is_left = TRUE; + } + else if (port > temp_tree->port) + { + parent_tree = temp_tree; + temp_tree = temp_tree->right; + is_right = TRUE; + is_left = FALSE; + } + else + break; + } + + if (temp_tree) + { //if we found it, clear its parents link to the node + if (is_left) + { + parent_tree->left = NULL; + } + else if (is_right) + { + parent_tree->right = NULL; + } + } + + return temp_tree; // NULL if not found. +} + +CUDPTreeNode *UDPTreeNodeSinglePop(I64 port, CUDPTreeNode *tree) +{ // pop whole sub-tree, then add back in its sub-trees. TODO: should we leave the pointers in the node or clear them ? + CUDPTreeNode *node = UDPTreeNodePop(port, tree); + CUDPTreeNode *left = node->left; + CUDPTreeNode *right = node->right; + + if (node) + { + if (left) + { + UDPTreeNodeAdd(left, tree); + } + if (right) + { + UDPTreeNodeAdd(right, tree); + } + } + +// ... see the TODO ^ +// node->left = NULL; +// node->right = NULL; + + return node; +} + +U0 UDPTreeNodeFree(CUDPTreeNode *node) +{ // only clears and frees the node. !! if node has subtrees, they will be left floating. use with caution to avoid memory leaks + // ... uh.. what to do with the inner CTreeQueue floating around ..? we need to fix that too right? + // .. what does CQueue functions give us. QueueRemove is our best bet, i guess it will just try to swap around the next last ptrs. +} + +U0 UDPTreeNodeQueueInit(CUDPTreeNode *node) +{ + node->queue = CAlloc(sizeof(CUDPTreeQueue)); + QueueInit(node->queue); +} + +U0 UDPTreeNodeQueueAdd(CUDPSocket* socket, CUDPTreeNode *node) +{ + CUDPTreeQueue *new_entry; + + if (!node->queue) + { + UDPTreeNodeQueueInit(node); + node->queue->socket = socket; + } + else + { + new_entry = CAlloc(sizeof(CUDPTreeQueue)); + QueueInit(new_entry); + new_entry->socket = socket; + QueueInsert(new_entry, node->queue->last); + } +} + +// refactored to UDPTreeNodeQueueSocketFind for Socket-call level functions9 +CUDPTreeQueue *UDPTreeNodeQueueSocketFind(CUDPSocket* socket, CUDPTreeNode *node) +{ + + CUDPTreeQueue *temp_queue; + + if (node->queue) + { + + if (node->queue->socket == socket) + return node->queue; + + temp_queue = node->queue->next; + while (temp_queue != node->queue) + { + if (temp_queue->socket == socket) + return temp_queue; + temp_queue = temp_queue->next; + } + } + + return NULL; +} + +CUDPTreeQueue *UDPTreeNodeQueueFind(U32 address, CUDPTreeNode *node) +{ // address should be pulled from an instance of CIPV4Address (todo.. double check what bit order we're in ?) + + CUDPTreeQueue *temp_queue; + + if (node->queue) + { + if (node->queue->socket->receive_address.address == address) + return node->queue; + + temp_queue = node->queue->next; + while (temp_queue != node->queue) + { + if (temp_queue->socket->receive_address.address == address) + return temp_queue; + + temp_queue = temp_queue->next; + } + } + return NULL; + +} + +CUDPTreeQueue *UDPTreeNodeQueueSocketSinglePop(CUDPSocket *socket, CUDPTreeNode *node) +{ // search by socket, pop a single UDPTreeQueue off the node, return popped queue. + CUDPTreeQueue *temp_queue = UDPTreeNodeQueueSocketFind(socket, node); + + if (temp_queue) + QueueRemove(temp_queue); // links between queue entries pop out this and stitch back together. popped entry might have old links? + + return temp_queue; // if not found, NULL. +} + + +CUDPTreeQueue *UDPTreeNodeQueueSinglePop(U32 address, CUDPTreeNode *node) +{ // pop a single UDPTreeQueue off the node, return popped queue. + CUDPTreeQueue *temp_queue = UDPTreeNodeQueueFind(address, node); + + if (temp_queue) + QueueRemove(temp_queue); // links between queue entries pop out this and stitch back together. popped entry might have old links? + + return temp_queue; // if not found, NULL. +} + +// end UDP Bound Socket functions & classes +//////////////////////////////////////////////////// + + + + + + + + + +class CUDPGlobals +{ + + CUDPTreeNode* bound_socket_tree; + +} udp_globals; + + +U0 UDPGlobalsInit() +{ + udp_globals.bound_socket_tree = NULL; +} + +I64 UDPPacketAllocate(U8** frame_out, + U32 source_ip, + U16 source_port, + U32 destination_ip, + U16 destination_port, + I64 length) +{ + U8 *ethernet_frame; + I64 de_index; + CUDPHeader* header; + + de_index = IPV4PacketAllocate(ðernet_frame, + IP_PROTOCOL_UDP, + source_ip, + destination_ip, + sizeof(CUDPHeader) + length); + if (de_index < 0) + { + ZenithLog("UDP Ethernet Frame Allocate failed.\n"); + return de_index; + } + + header = ethernet_frame; + + header->source_port = EndianU16(source_port); + header->destination_port = EndianU16(destination_port); + header->length = EndianU16(sizeof(CUDPHeader) + length); + header->checksum = 0; + + *frame_out = ethernet_frame + sizeof(CUDPHeader); + +} + +U0 UDPPacketFinish(I64 de_index) +{ // alias for IPV4PacketFinish, alias for EthernetFrameFinish, alias for driver send packet + IPV4PacketFinish(de_index); +} + +I64 UDPParsePacket(U16 *source_port_out, + U16 *destination_port_out, + U8 **data_out, + I64 *length_out, + CIPV4Packet *packet) +{ + + // check ip protocol? probably redundant + + CUDPHeader *header = packet->data; + + // Shrine has FIXME, validate packet length! + + *source_port_out = EndianU16(header->source_port); + *destination_port_out = EndianU16(header->destination_port); + + *data_out = packet->data + sizeof(CUDPHeader); + *length_out = packet->length - sizeof(CUDPHeader); + + return 0; + +} + +//CUDPSocket *UDPSocket(U16 domain, U16 type) // should this even be allowed? why not just UDPSocket; ? it could just know its domain and type. +CUDPSocket *UDPSocket(U16 domain) +{ + U16 type = SOCKET_DATAGRAM; + + if (domain != AF_INET) + Debug("Non IPV4 UDP Sockets not implemented yet !\n"); + +// if (type != SOCKET_DATAGRAM) +// Debug("UDP Sockets must be of type SOCKET DATAGRAM"); // maybe just return null if wrong type and ZenithErr + + CUDPSocket *udp_socket = CAlloc(sizeof(CUDPSocket)); + + udp_socket->socket = Socket(domain, type); + + udp_socket->receive_address.family = domain; // should be INET (or INET6 i guess) + + return udp_socket; + +} + +I64 UDPSocketBind(CUDPSocket *udp_socket, CSocketAddress *address_in) // I64 addr_len ?? does it really matter that much +{ + //if we put in addr len do a check its valid for ipv4 and ipv6 based on family + + CUDPTreeNode *temp_node; + CSocketAddressIPV4 *ipv4_socket_addr; + U16 port; + + switch (udp_socket->socket->state) + { + case SOCKET_STATE_READY: // Socket State machine must be in init state + break; + + default: + ZenithErr("Unsuccessful UDP Socket Bind: Socket state-machine must be in READY state.\n"); + return -1; + } + + if (udp_socket->bound_to) + { + ZenithErr("Attempted UDP Socket Bind while UDP socket currently bound."); + return -1; + } + + if (address_in->family != AF_INET) Debug("Non IPV4 socket binds not implemented !"); + + ipv4_socket_addr = address_in; + udp_socket->receive_address.address.address = ipv4_socket_addr->address.address; // bind socket to address in parameter. + udp_socket->receive_address.port = ipv4_socket_addr->port; // ... consistency would say keep in Big Endian ... + + port = EndianU16(ipv4_socket_addr->port); // port member should be Big Endian, so now we're going L.E (?) + + if (udp_globals.bound_socket_tree) + { + // look for our port. + temp_node = UDPTreeNodeFind(port, udp_globals.bound_socket_tree); + + if (temp_node) + { // if we find we have bound sockets at port, check address before adding to queue + if (UDPTreeNodeQueueFind(udp_socket->receive_address.address.address, temp_node)) + { + ZenithErr("Attempted UDP Socket Bind at an address already in Bound Socket Tree !\n"); + return -1; + } + else + { // if no address match, free to add socket to the node queue + UDPTreeNodeQueueAdd(udp_socket, temp_node); + } + } + else + { // if we get no node back from port search, we didn't find it and are free to add a new node. + temp_node = UDPTreeNodeParamAdd(port, udp_globals.bound_socket_tree); // add new node with port, return its *. + UDPTreeNodeQueueAdd(udp_socket, temp_node); + } + } + else // if no bound sockets, we init the tree as a new node + { + udp_globals.bound_socket_tree = UDPTreeNodeParamInit(port); //... shouuuld be in L.E .. ? + UDPTreeNodeQueueAdd(udp_socket, udp_globals.bound_socket_tree); // add the udp socket to the port queue + // maybe more checks to do before this, dunno rn. + } + + udp_socket->bound_to = port; + + SocketBind(udp_socket->socket); // Advance Socket state-machine to BIND REQ state. + + switch (udp_socket->socket->state) + { + case SOCKET_STATE_BIND_REQ: // if BIND request success, set BOUND. + udp_socket->socket->state = SOCKET_STATE_BOUND; + break; + + default: + ZenithErr("Unsuccessful UDP Socket Bind: Misconfigured Socket state-machine.\n"); + return -1; + } + + return 0; +} + +I64 UDPSocketClose(CUDPSocket *udp_socket) +{ // close, pop, and free the socket from the bound tree. + CUDPTreeNode *node; + CUDPTreeQueue *queue; + + node = UDPTreeNodeFind(udp_socket->bound_to, udp_globals.bound_socket_tree); + + if (node) + queue = UDPTreeNodeQueueSocketFind(udp_socket, node); + else + { + Debug("Didn't find node at socket during UDPSocketClose!\n"); + return -1; + } + + if (queue) + { + if (queue == queue->next == queue->last) + { // queue is alone. Means port only has this address bound. + Debug("queue==next==last"); + } + else + { // queue has other addresses bound at this port. only Free the queue entry, after removing it. +// QueueRemove(queue); // QueueRemove acted strange and did not move the links?..... +// UDPTreeNodeQueueSocketSinglePop(udp_socket, node); /// ???? wtf is going on haha + +// Free(udp_socket->socket); +// Free(udp_socket);// is it even possible to free a param? +// Free(queue); + } + } + else + { + Debug("Didn't find queue at socket during UDPSocketClose!\n"); + return -1; + } + + + + return 0; +} + +// UDPSocketConnect (Shrine just has FIXME: 'implement') + +// UDPSocketReceiveFrom + +// UDPSocketSendTo + +// UDPSocketSetOpt ? + +// UDPHandle + +// so i guess the socket functions would just act on the socket state machine. +// ZenithErr and return fail vals if socket FSM improperly used. +// if we're using a global for the bit map and socket pointer map, be careful +// with how Free is done. + +UDPGlobalsInit; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Home/Net/ZenithStackNotes.DD b/src/Home/Net/ZenithStackNotes.DD deleted file mode 100755 index 20618498c7372fcdf8721bbadc90cc86bd04f639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199 zcmY!h4$x7uF|