From 97542a4099a3c89e8e27043bc63fe31d1ab076f0 Mon Sep 17 00:00:00 2001 From: wbzhu Date: Mon, 11 May 2026 09:00:27 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../advanced_usage/asn1_snmp.doctree | Bin 0 -> 64272 bytes .../advanced_usage/automaton.doctree | Bin 0 -> 44799 bytes .../.doctrees/advanced_usage/cbor.doctree | Bin 0 -> 17853 bytes .../advanced_usage/fwdmachine.doctree | Bin 0 -> 22902 bytes .../.doctrees/advanced_usage/index.doctree | Bin 0 -> 3173 bytes .../advanced_usage/pipetools.doctree | Bin 0 -> 67770 bytes doc/_build/dummy/.doctrees/backmatter.doctree | Bin 0 -> 5151 bytes .../dummy/.doctrees/build_dissect.doctree | Bin 0 -> 145645 bytes .../dummy/.doctrees/development.doctree | Bin 0 -> 60192 bytes doc/_build/dummy/.doctrees/environment.pickle | Bin 0 -> 256288 bytes doc/_build/dummy/.doctrees/extending.doctree | Bin 0 -> 14072 bytes doc/_build/dummy/.doctrees/functions.doctree | Bin 0 -> 5759 bytes .../.doctrees/gui_application_design.doctree | Bin 0 -> 121814 bytes doc/_build/dummy/.doctrees/index.doctree | Bin 0 -> 7144 bytes .../dummy/.doctrees/installation.doctree | Bin 0 -> 70462 bytes .../dummy/.doctrees/introduction.doctree | Bin 0 -> 31538 bytes .../dummy/.doctrees/layers/automotive.doctree | Bin 0 -> 277213 bytes .../dummy/.doctrees/layers/bluetooth.doctree | Bin 0 -> 89170 bytes .../dummy/.doctrees/layers/dcerpc.doctree | Bin 0 -> 63904 bytes .../dummy/.doctrees/layers/dcom.doctree | Bin 0 -> 21079 bytes .../dummy/.doctrees/layers/dotnet.doctree | Bin 0 -> 5275 bytes .../dummy/.doctrees/layers/gssapi.doctree | Bin 0 -> 29514 bytes .../dummy/.doctrees/layers/http.doctree | Bin 0 -> 31423 bytes .../dummy/.doctrees/layers/index.doctree | Bin 0 -> 3278 bytes .../dummy/.doctrees/layers/kerberos.doctree | Bin 0 -> 150799 bytes .../dummy/.doctrees/layers/ldap.doctree | Bin 0 -> 42810 bytes .../dummy/.doctrees/layers/netflow.doctree | Bin 0 -> 12868 bytes .../dummy/.doctrees/layers/pnio.doctree | Bin 0 -> 16249 bytes .../dummy/.doctrees/layers/sctp.doctree | Bin 0 -> 7699 bytes doc/_build/dummy/.doctrees/layers/smb.doctree | Bin 0 -> 52887 bytes doc/_build/dummy/.doctrees/layers/tcp.doctree | Bin 0 -> 10625 bytes .../dummy/.doctrees/layers/tuntap.doctree | Bin 0 -> 52585 bytes doc/_build/dummy/.doctrees/routing.doctree | Bin 0 -> 22436 bytes .../dummy/.doctrees/troubleshooting.doctree | Bin 0 -> 38891 bytes doc/_build/dummy/.doctrees/usage.doctree | Bin 0 -> 252604 bytes doc/scapy/gui_application_design.rst | 647 ++++++++++++++++++ doc/scapy/index.rst | 1 + 37 files changed, 648 insertions(+) create mode 100644 doc/_build/dummy/.doctrees/advanced_usage/asn1_snmp.doctree create mode 100644 doc/_build/dummy/.doctrees/advanced_usage/automaton.doctree create mode 100644 doc/_build/dummy/.doctrees/advanced_usage/cbor.doctree create mode 100644 doc/_build/dummy/.doctrees/advanced_usage/fwdmachine.doctree create mode 100644 doc/_build/dummy/.doctrees/advanced_usage/index.doctree create mode 100644 doc/_build/dummy/.doctrees/advanced_usage/pipetools.doctree create mode 100644 doc/_build/dummy/.doctrees/backmatter.doctree create mode 100644 doc/_build/dummy/.doctrees/build_dissect.doctree create mode 100644 doc/_build/dummy/.doctrees/development.doctree create mode 100644 doc/_build/dummy/.doctrees/environment.pickle create mode 100644 doc/_build/dummy/.doctrees/extending.doctree create mode 100644 doc/_build/dummy/.doctrees/functions.doctree create mode 100644 doc/_build/dummy/.doctrees/gui_application_design.doctree create mode 100644 doc/_build/dummy/.doctrees/index.doctree create mode 100644 doc/_build/dummy/.doctrees/installation.doctree create mode 100644 doc/_build/dummy/.doctrees/introduction.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/automotive.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/bluetooth.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/dcerpc.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/dcom.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/dotnet.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/gssapi.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/http.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/index.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/kerberos.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/ldap.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/netflow.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/pnio.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/sctp.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/smb.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/tcp.doctree create mode 100644 doc/_build/dummy/.doctrees/layers/tuntap.doctree create mode 100644 doc/_build/dummy/.doctrees/routing.doctree create mode 100644 doc/_build/dummy/.doctrees/troubleshooting.doctree create mode 100644 doc/_build/dummy/.doctrees/usage.doctree create mode 100644 doc/scapy/gui_application_design.rst diff --git a/doc/_build/dummy/.doctrees/advanced_usage/asn1_snmp.doctree b/doc/_build/dummy/.doctrees/advanced_usage/asn1_snmp.doctree new file mode 100644 index 0000000000000000000000000000000000000000..5c6e4160ce0de1d7fa28154e2c0fbda9e37d10c9 GIT binary patch literal 64272 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 LkNP`1)$%_8Q=R1M literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/advanced_usage/cbor.doctree b/doc/_build/dummy/.doctrees/advanced_usage/cbor.doctree new file mode 100644 index 0000000000000000000000000000000000000000..290863d5064dd4c14f376c96e4cf15681c3e6d43 GIT binary patch literal 17853 zcmeHPeQX@Zb*DZ^iKIw<*mR9pPS;do9S`K0q$pWZW2KZtsj6kFAgRPIMJ)I3=5CkT z+r8|ENZN7X0u2xXix!2iN!q$?5THQ+ECS@8q6pBUfKi}8+dm5wX^(ytxnhf8*N=ef$@V*4Sr6OELl~V1j3zyG%u(}G z=CJvadCWW?&ELNM=!>rRc-d_Pn_%V9yPMT1zh<U!uKP4TE! zb-jw`gY}o+JQ`NbAqimy$?2pZ8u_AW1e)b*1jy$lB4fqU7!mL`C3#ykU#3BupT=}L_e4S&W;=8X5p7sO!OSjoYwZ-Cl+R3Gw{{GY}O20 zvz)+l>mkpr7P!o&*6`exwoFJ|^tHC|{5DPg6H(FbxSkf6LaVv0wk-moQI*vIDH4uJ z^d+>VJGZo}*XFKOlKCIRL>o=dXq$?Nm^WBD#-a~?WF~*`!EgRZY-aG^><#{(HP18b zgP5_;?Ke-?e0R)*e(h0Jyd!+Me*C zA`I>XNs*z|jlj?vuGfNCZJI7#YPvL!KFwY{#b`zoggK@eu^UsE^%|7Na+(@Gs0${8 z$roPDG@PbLNLCGB)HT=PS^oLu2aB?03ueQvX!lr@>!Ja*tV4Fvxz-E^&%TC^tR~fg znJT2CW!NNRV5g2J>s>LadBX5r$Ee!dSv9nl6f15)v|urW<6Bi*Xq#4Gx?#W^IRdmX zdhH3M5)!95qUi<}0~E{K*fU&H*ndp8W(u-=I0YH*Cil?<3w>&iAaYhsidnGThJCV} znjO|5tk)BJd5AYMUe^!D>xjZ@Uo>0|Z5TM+wqWLyH8L~sebFG*=vE+FN_F%zY-%3NC&}_U;KN2cO|_q~ zuKgr%o!!MlCxhkv-e7qTaBRU`jYY5a$bQzvmfD5ba}`U>=xKBn>}N*3*o;y`muQ18 zKQW`x>*exd5z7MRGlY4uO%dvy;LrzAn&SEK>j@yfxXvp0{J5bc{~Sl zcYZT~KT^pb4DcCr^Nn5rriQx#o-UVfZqr&12h3>}<#`s^E*YEZ!cs)`kBi8jDN;;8 zJ*9S};Q1%&fcMAqyNsun!?PQY8Jgc!i64ySS%CCMy`V`AccXcwTqb{b4>T_U&4(*@ zrmkwjp+&Qvv@2rzy)KyM>86;f#R{f>uMT^EOuyg75bVuqhV1v%E)GWa902>x3WfmatEp((x;0$HpwZbN%w*dg`{gk_b4qIwXsDY#5P?e1WFp95_F)eFAVa5uiQrbyNK>( zl32%$@qH||rx;ePZCKteY{$D`dom%LAbXs8QEwCw`yy>fY3vW*FIg5V6C}#~4_+$t>*6 zW-=dW+hsm#K**vEZh|-MNE)_l1jVU&9aD4Db!}ld{-ox>XPneRC*B}U@~(*A!@4=u zcH!xvO{)s0aHwE)qU!&CpWJ21%p~FbetjlF@#0=kDDq(!CTVZrNj}U(KJMh?{cypknKI-e_v6@2VB6fZ z96wfZ2fG6(IaIt#9#}xZBmimOsTzSdI)F(8f7w zZo8Y>W!b!7K=$HV9}{fO#77OPf^^lVO$q8M@1YRq_9*R zT3`-CV*Inu|nl0|LDqGEA2uHWx@TnM*rd@F6u%)A7jXCG$9ZXrGtkVJ8iz~x9- zysV8yd(>vEeJAbgAQy~S`*zxzS^H_QmTteJ8I=L8W;hhkLhQ;y;HpMUVmYRDfs_Mb zxcc=pS}}dtHx&c+rzuMZ4Iq)3SCoNZ0DaMjji`iY*m~7=Ymb$v!P{Ry+ShmE#tj+Y z^YF1h*6%0j+m zeraj`ogwPrg2vM*G#T6Bunt#X_0!(y-cf08c7odkv^$C$DczRFe@?(8$=+WXmu=Z> z#%&dbO4W;xwx(zBKa2lM7rAksvzm+e4u`#ct}r|=>6FJ&TBTXs1xIOSVsfZbsc2pP zy1y`czzew{2Bs&(_{5qV9e5i098+bMR#)Xiaf()F)^1E{d(_QlbxKdG7rCd4Yd6-` zZqWGi;I!a?a|xdWWRk+-Lfg`sDl=T5zcv30{0iP}lm5P;y*EyWF5}m=@p;>-iSbEo z96??@Uz(<`e58P<6)zO;$KJ*>5ml7unTSFe{tt<$TxMWy{~AWx-{W{L8r*8x*L@tm z;2iZNAK-XT1UZ8ne@V7oo*Z4s8Es{)ALFG3`ooL;RI>hMi+J@Dsr%Y^xa6*Q+Jkda~hYoD%?1`M>^>sF*x5#w)cu~Se z4zIw78%Agck^s3=xyBWGaeX~r-AmZjezqqrl{Nj}(lN_59c%Q7c#T$8=V&M!SOWfl z9H~O<2$pblgR`ekx$)oB>!&uTbAUV+m3v1^>95H9MjseRBjl%ZP_3^gd=D#VE5yz{ zHS?d|f5wzP_GkF{9K-S4CS0FUxaKN_?1iUh{m6BNOp&`x^AoQ=apDvA?sY|J!s^Cf zVLDUz|CxNPUR)~Z0>_fy;BBN|!aLH#h$||l*eXp7U7Wc(Yg|;^f!kyZ$cw8z=Cz%T zgQ(LXE^Yo0QcZj&sacF#1+7Xk;n+)KGW80gS3`5NOv!>m`vE0Ylv1J$f6=*_7$W~5 znWCSd5vQxoHHh9`tIfvuAP*aVM>?KgV6NrFM%mW9u#i}L#l6V(E}6Mks}%{p)2kww zi3Bg?SjkE7+aH7T19l`Nfi;~SVCnnIg2dgtpz(kN7BAR-ME_a5V)2WmQz5|yb#-P^ zo1N4yO=@$K+U3~Uj$cVs;p*k-nd#Z-OVe}Hm%FG0Eh<@esR-*m>BA}=8Y_2$HF9#{aUwYh`iTc6vXwT-yQFG_tLWsCC%KQ4 zd!91=$cLnCBCT#U*h5585oMettIPh4GM9IwXB$W^Z!>MpF7G=zmv@|FKgDMHLVDQ2mM6fsv%y)VGD~#dfvhv(;1A) z)8fPX_hX*8p<4HF@%F-jgeB$RkHT1voG=_dHXF3!^ ze|}S1-6^uVQ>oRRl2$hhY9#HtTj%}EK3UU!%>KDsoT$7-Su{v;4v|lr82fHjnTh}H zp6w@@_-~}G*~C-cTy~I9u-lIC>F6M1D6{aavRb+r>~5(T_&-Ry?A5@(t1wzL@PC*Z z_(jPc`?fL$9J3(}N;g+}m3-;7No^J0^y=kF?aJC(>{B1?7fa&OAnoa6vhy#!Oj1f# zOdK)9Q%k{WDL5@_3&>`t4e{x~duhNv4M^W~tK(T5KMqUJURI0L@U&7+`5ADeSI0wN zcpTEMw=8FTjr(|?S?jj>NE~uvNsjhlY+^dHxhY zdNM@|rDA*13Rcp6%?|1MK4xch!Sf*0xVBWs^}uH0&lQU^U+zD9)-f_X|B<$4!}Gg2 z!!s*$WzxGVzR{oqBpJYvPKd~Hm)Vnt4OP-${j2nX;AX=$Te!=ahDCOewW<)tANOd)u@Kqh%sUOpyIK3Oku@3#Bd zXL^0uoQR$eJS67Yd_7Qy(l)XGlh=p)12-`2&YOMFP|`-!@fgze+9T0uE%fMevYuW= zK9!WnE6LGGUj!89!DZ^R>FsKy%iuDXQ}09rUyyeTlB;9UFs?AR!$5Bvf!SF!M;D{P zrf`s_6(FY%56~sKFymp^b6vE=F}AhUF$bby$ptQ>Ige326hDTZji-^ot?6_^N44Ol zA8dQuo#g6}0o!Xq0aCv~~E6bRFCW!Ixx-umzXsw0GB& zTh{0=ej&bgy%?SH;97XNDkq)@AbKg|z)C3KQ__G{>Er}R+qQNj|OkJJACA&^^K3}P`oa-#-!owsGtF@pL zosPlJsPr?HPV{o2ra)rDfFedxOB5cGx6jKC6S9Suqcb!Hh@X$FuC~n?AtN?!+lEMk z0^)UKh#bWQ`{+3jD~MOu>!Jbw!j$jWBTatSCBaL0U)ZVXLjD5{*gW0=k*#;eG*0#c3{KT_MTI zirT&i>#mNh7qmJfwkR1=SKnj_t0DdgyDo+Ov|hJ-+yEvJVlGc4ng*>s1t5Ir7OO3Y zj>OG$8YW-#r{6Q+-yDd=cn2eJYn0xc%Jn%CbU&=-o4r6Jz#aDPbaet#N^I02XT$jU z1Lg57sX3(8X+rpIt7Qe9x6WLQPV!m>RRvq4ZPC`#R1*T^6qV2oYMoRwdeIDmwtxM# z*HG+N+S|xgaVoCYe616WQA_3pEp==>ARZu!gdx$M`t0-^=48}s)C%OOBqRT7LY|gk zQkloXt5_mva38?qH8W`0=xi`Bs?g#(49}YT+@v$PYP@wbuIy``I0 z)3osq{W)&a{nO|aE*3-4zZAl%_`vKrewL3ql1+_xdS1qza{7e<;Yoc5;0!0;ejK(t zm@TVCDuA#!AN=s|JUs3|dT=+Q4nwdFLZ%P_nvT`b>akAw2DXmf8(DDf{A~QY1`u1j zAx+~>bWAu;==UO`lhP=`3P324sbE3H0zELirht*dZ$+GiY(U5Y=oaJvIY6{7MCWp) z@)smf3~G(^VSwy1etZJuC#X<ew6rjI|PkAJ6+f2EHJdhJzwgwY`NBZH>+MF{R`hkM!K9(I`e4pZJ?syj?^ zhpFu_rJc~!=v`va9A@s!vSf`iv?Xhl5G`4wJT1u@9bZY-D9R~Wqs=+9#@=!mji}

jhtpyf b$6B2lxSyLpTO}ySef|QMPH^ literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/advanced_usage/fwdmachine.doctree b/doc/_build/dummy/.doctrees/advanced_usage/fwdmachine.doctree new file mode 100644 index 0000000000000000000000000000000000000000..012054b7990d8a136cf4fc5f9135bf3dd53aad80 GIT binary patch literal 22902 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 LgzyBY(&PUCym|8L literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/advanced_usage/pipetools.doctree b/doc/_build/dummy/.doctrees/advanced_usage/pipetools.doctree new file mode 100644 index 0000000000000000000000000000000000000000..840c3f4dcfbdf7b26e04234679fd168c5ee524ae GIT binary patch literal 67770 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 E7wP4c=>Px# literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/build_dissect.doctree b/doc/_build/dummy/.doctrees/build_dissect.doctree new file mode 100644 index 0000000000000000000000000000000000000000..285935ff552530646e911e1894cc9b938d07abcf GIT binary patch literal 145645 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 R_tv4E!5|BDjqJ4K{s#@&UY!5{ literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/development.doctree b/doc/_build/dummy/.doctrees/development.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b06ea657093a47ead1a59c8290215bf8d922ff5e GIT binary patch literal 60192 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%0A8|N|tAe489crndu2?Nj^_<01?!J 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 literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/functions.doctree b/doc/_build/dummy/.doctrees/functions.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2e7500388a0c8b7faa821c89b3e9e066ed87311a GIT binary patch literal 5759 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 z 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 VpZvr4YKqCM5QOF(O>=Mh{{Z@RWX}Kq literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/index.doctree b/doc/_build/dummy/.doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e5a78c0516f4f2ccdcd977ba3350f64db20503fc GIT binary patch literal 7144 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

(m< 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 literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/introduction.doctree b/doc/_build/dummy/.doctrees/introduction.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2a90c74cda82e48e497052fc16c188d5088c8a0e GIT binary patch literal 31538 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 ToZyx$N2iN}v22>+`C$J67^2DN literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/layers/bluetooth.doctree b/doc/_build/dummy/.doctrees/layers/bluetooth.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f038646157ca0fa4a0cfad4d9401209c6982f10a GIT binary patch literal 89170 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 literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/layers/dcom.doctree b/doc/_build/dummy/.doctrees/layers/dcom.doctree new file mode 100644 index 0000000000000000000000000000000000000000..52d60044a02bbc1e00bae934331439f1136f6486 GIT binary patch literal 21079 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 Leh88yV<9pR+*u?W8j;>z_;00?U_a#PoTd4 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 Vpvc6(=vPy(khLKACqNnR{R4cUA~pa3 literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/layers/kerberos.doctree b/doc/_build/dummy/.doctrees/layers/kerberos.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8c92f4b444721c79cb6e788def6624bbcfdb070e GIT binary patch literal 150799 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 literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/layers/ldap.doctree b/doc/_build/dummy/.doctrees/layers/ldap.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d73db3651223f3a831b8cae0898f46e9ac04c267 GIT binary patch literal 42810 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 literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/layers/netflow.doctree b/doc/_build/dummy/.doctrees/layers/netflow.doctree new file mode 100644 index 0000000000000000000000000000000000000000..58ac206f4688e7bf1c745ba4def5b710825297dc GIT binary patch literal 12868 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 Zzu_e|!E1*ui;7J-JAIju1(DQi{|oo7V%Gow literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/layers/pnio.doctree b/doc/_build/dummy/.doctrees/layers/pnio.doctree new file mode 100644 index 0000000000000000000000000000000000000000..80c5b325aa7f225bf76db149c048faf4ad169715 GIT binary patch literal 16249 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 literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/layers/sctp.doctree b/doc/_build/dummy/.doctrees/layers/sctp.doctree new file mode 100644 index 0000000000000000000000000000000000000000..26bf7d9a7247a001baa3b61240d10112488f6308 GIT binary patch literal 7699 zcmd5>TW=h<6_z8du9j@sRxgPiux;wvtyi)e$4K1PXkjO<>%{df=>-lZ+!^kY((G_L zK*aA> z!u#c~`6y58kKD~!-lMNVuE=NQl6+21%hT?a8&@CxRA*cFb=Ph~l!w3C?zBzF)4{{? zf5=q*iupif1|K$E&kRI9b89;9;Ajgt1Mg)#mCe(CLZ@fSFwQa&bE_VSc)$iaPkPK| zJTa-xY}+$sX{N0Xb<9HD@2Es!N=IAC9$de}coMNUZ`@&_#4e!~nKD*|W}Q8dg1vL^ zwd?G~Z@+wneOFqWnyVWdR%@783EN%QS#Lwye!P)&Lz;B$oq(ytW;)74VVJU>xw3`< z4`HTKGA@Hi7)zL~`CoqJ%Ef*8(WTpld95vyd#$RN=mM z0Gk!LJSQ*H`TVJ7Ezf#l#H)bYU*x2P4hcuI4E??c(K7L-)K0g{B zV1J{~;%j>u#dDo7N<|wpit;I%awDP(2p)#@xk^AtGhqB%myIrw|nX0l}r0>N0VqfI&Eoq9>cRn7Kc8N zj^P>=9+>W;u<*)2<)cI8EkDY>!Dp<=Mx6PeX?*!u3_panYpnWIOBQJC@|Wf=2S~Mk z`9WehZBxha4*X8ip3{X&BFS!Y~zW%rO6a+%P|QdhZ-X%rV>SpPZNL$k~jT?$5Xa z7%>s+C;=l|^LU%;ctQE3%)pW8@;tT-nd5bpDZoDx%@y^ zYWFdlt_?or@Q#N18mWWQTbVJgUadXs#V1(YaK=v#cYxK#0jAtCDh3p6anOl%xYfKh zt-pZ!EW)uuso5oVjcwq@8g7_@*+8+kL094pI#^J`tv<|Qmz9Q)&u+F*3YgA+iSi3O z{rIX$IrS*!eeH>2fI|4@G?rD>?8(N-M%C=K$F)n&&q~uwlw6JM>+w7?()XT<&B$Dv zr$}Jmh$yeEd!M)oxwX-`HfOMUSUGMS#6(e~(g_{}}tUmKB#@901 zqd|vuxfO%9aT-wn0SgTnK37uE zl<<^*?v_E_MaF*Bf~u{2+a1jWwI_mDpp9GHdd4kE9uGfTZdL3kDAID9n|e~qwIb2U zd)RITy=7|aVn!x-ACv`HBouTcl20Y1HZ&_yc_qEVE#$V_ez|QFexDv63`CacERXj#bJZacOxjg*!7bGQnc=H}G4xQu2L#_380}iN)?Xoxwm+5-N&)R$h*I z(#v^IK;sdz^fP!sa3{%YvnYr}3h-bXYECpBCH&I_2si5S!RzRBkFYh6oE#5;@uWw%hj}i zj_DsV=)K0jsZ=oD#R+iHPr8t!!u1IPy2(4^)4o6~ARW3SJ|SA|$BsYrui+m{q_lqemJ^OI}3FV#J4kj;8Juu>=f z;h}JAG}Ft1nSm93zynNCw28F+^^2EblNEHKC)g8Z#Qto}9`bN$ zV|fQ&93k-Bns;a56a8Qq9EJx7%DQpa9XEr-@|{4co{aGa|Cxz3x!zIJ=Wz62=7=f= zN;>Jk%g+FXF|W*XX80ge`a2Zv)rr+xd0N0)s!uKuqigDYn%=Rt3ZSRo5fK8x0F0zS zFMrpqg_yM!2sBjVph|Rp^VRCb4X};7WoX=W(;|69?_ucI`=Ep<08pwVQgAUFlrIDx zLaV?AKxW}v$PKsvQeJQ;#;D~B8OY$)9ez;jpB~phpCe-V``qROgo@3(RI=QYKaobJ zn{N>1$3*#1{#|}7Kg!(#t+0a>N5G4SU$ZOk?Mk!jXYa}ncjd>sxnwlznJt0YO_|Eg zl9L0czsZl}zjC+SG@_ShIaGiG9SAa(6!Q0aUU~2)rJRTN3>dxN(eV%$e(I?S;&ArD z7{`TrSHI}Q7=MKQ%;H7FtxfW(Mv#CJjY7Qr;eu7TB|P<^8%}3H2%NiK(cg!*-YxFI wEAXHQ|H3n-0fNd6he2tKyfh?x5G<)AjPr;<*Q2-4#fV~V6=JBa$UE!*0cw2@_5c6? literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/layers/smb.doctree b/doc/_build/dummy/.doctrees/layers/smb.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ce9806f24b9a6bf2dc4e8cd68cca98be685d2a36 GIT binary patch literal 52887 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

@eTngmgToUlmAr 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 ABme*a literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/layers/tuntap.doctree b/doc/_build/dummy/.doctrees/layers/tuntap.doctree new file mode 100644 index 0000000000000000000000000000000000000000..47af5d72a8dc5d9506e7a5e082c2172df96c32fc GIT binary patch literal 52585 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

U+tOG$ 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 literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/troubleshooting.doctree b/doc/_build/dummy/.doctrees/troubleshooting.doctree new file mode 100644 index 0000000000000000000000000000000000000000..fdff2cc78aa34cc860e7aba071d3c786091f3826 GIT binary patch literal 38891 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 T+_5rQC`J~X7o3TSy%qlnhdNi3 literal 0 HcmV?d00001 diff --git a/doc/_build/dummy/.doctrees/usage.doctree b/doc/_build/dummy/.doctrees/usage.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f7f72ee95f011a23a11bc275bcd098c210074224 GIT binary patch literal 252604 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 E0a7U#tpET3 literal 0 HcmV?d00001 diff --git a/doc/scapy/gui_application_design.rst b/doc/scapy/gui_application_design.rst new file mode 100644 index 00000000000..a16df21abee --- /dev/null +++ b/doc/scapy/gui_application_design.rst @@ -0,0 +1,647 @@ +*********************** +Scapy GUI 设计文档 +*********************** + +目标与范围 +========== + +本文档定义一个基于 Scapy library 的图形化桌面应用方案。首个可交付版本采用 +PySide6 / Qt,平台优先级为 Windows。GUI 不重新实现协议栈,而是把现有 Scapy +能力封装为可视化、可组合、可回放、可分析的工作流。 + +目标如下: + +* 让用户无需记忆 Scapy CLI 与 Python API,也能完成数据包构建、发送、抓包、离线分析。 +* 保持与 Scapy 原生对象模型一致,核心后端仍然使用 ``Packet``、``PacketList``、 + ``AsyncSniffer``、``PcapReader``、``AnsweringMachine``、``Automaton`` 等能力。 +* 充分吸收 Scapy 已支持的协议层、扩展协议与自动化能力,而不是只做一个简化的抓包器。 +* 为后续扩展协议插件、自动化向导、数据分析视图预留架构空间。 + +首版不做的事情: + +* 不在 GUI 内重新实现一套独立的协议描述语言。 +* 不追求替代 Wireshark 的全部高级分析器功能。 +* 不在首版覆盖所有平台差异;先把 Windows + Npcap 路线设计清楚。 + + +Scapy 能力盘点 +=============== + +本节只记录与 GUI 设计直接相关的能力面。 + +核心能力 +-------- + +Scapy 的主定位已经很清楚:创建、发送、捕获、解析、操作网络数据包。其入口既可以是 +交互式 shell,也可以是 Python library。 + +与 GUI 最直接相关的后端能力包括: + +* 数据包对象模型:``scapy/packet.py`` + + * ``Packet`` 支持层叠组合、字段展示、摘要、探索、``ls()``、``explore()``。 + * GUI 可直接围绕 ``Packet`` 建立协议树、字段编辑器、十六进制预览与构建预览。 + +* 字段系统:``scapy/fields.py`` + + * 字段类型丰富,适合生成动态表单。 + * 可基于字段元数据构建通用的属性编辑面板,而不是为每个协议单独手工写表单。 + +* 发送与收发:``scapy/sendrecv.py`` + + * 提供 ``send()``、``sendp()``、``sr()``、``sr1()``、``sniff()``、``AsyncSniffer``。 + * GUI 可以统一抽象为发送任务、请求/响应任务、实时抓包任务。 + +* 包列表与结果集:``scapy/plist.py`` + + * ``PacketList``、``SndRcvList`` 适合映射到 GUI 的包列表、会话列表、请求响应配对视图。 + +* 会话重组:``scapy/sessions.py`` + + * ``DefaultSession``、``IPSession`` 等机制可作为离线分析和流视图的基础。 + +* 应答机:``scapy/ansmachine.py`` + + * 适合做“自动回复器”“协议仿真器”“交互式服务工具”。 + +* 自动机:``scapy/automaton.py`` + + * 可支撑复杂协议流程、状态机式交互、客户端向导和半自动测试工具。 + +* 接口管理:``scapy/interfaces.py`` + + * 提供统一接口抽象、接口展示、L2/L3 socket 选择。 + * GUI 可基于该层构建接口选择器,而不直接依赖平台命令行。 + +* PCAP / PCAPNG 读写:``scapy/utils.py`` + + * 提供 ``rdpcap()``、``wrpcap()``、``PcapReader``、``PcapWriter`` 等。 + * GUI 需要同时支持离线文件分析和实时捕获结果保存。 + +协议与扩展能力 +-------------- + +GUI 不应只覆盖 IP/TCP/UDP/ICMP。仓库中已有大量现成协议支持: + +* 常见网络层与传输层:``scapy/layers/inet.py``、``inet6.py``、``l2.py``、``dns.py``、 + ``dhcp.py``、``http.py``、``tls/``、``quic.py``、``sctp.py``、``vxlan.py``。 +* 无线与近场:``dot11.py``、``bluetooth.py``、``zigbee.py``、``dot15d4.py``。 +* 工业、企业与系统协议:``netflow.py``、``ldap.py``、``kerberos.py``、``smb.py``、 + ``smb2.py``、``dcerpc.py``、``gssapi.py``。 +* 专项协议与扩展:``scapy/contrib/`` 下包含大量扩展协议,尤其是 automotive、工业、 + 专用封装和研究型协议。 + +文档覆盖面也证明 GUI 需要“分层浏览 + 插件发现”的信息架构,而不是固定几个页面: + +* 通用使用:``doc/scapy/usage.rst`` +* 自动机:``doc/scapy/advanced_usage/automaton.rst`` +* 层级协议专题:``doc/scapy/layers/`` + +平台约束 +-------- + +Windows 路线必须正视以下事实: + +* 抓包和部分注入能力依赖 Npcap / libpcap 路线,相关逻辑分布在 + ``scapy/arch/libpcap.py`` 与 ``scapy/arch/windows/__init__.py``。 +* 部分操作需要管理员权限。 +* 原始 802.11、接口模式切换、底层 socket 能力会受驱动、Npcap 特性与网卡硬件限制。 + + +产品定位 +======== + +建议产品名暂定为 ``Scapy Studio``。它不是“把 shell 套个窗口”,而是一个基于任务与工作区 +模型的协议实验台。 + +目标用户包括: + +* 网络工程师 +* 安全研究人员 +* 协议开发与测试人员 +* 教学与实验场景用户 + +核心使用场景包括: + +* 从模板构造和发送常见报文,如 ARP、ICMP、DNS、TCP、HTTP、DHCP。 +* 构建多层复杂数据包,逐字段可视化编辑,实时查看原始字节与校验字段变化。 +* 对指定接口进行实时抓包、过滤、暂停、保存和二次分析。 +* 导入 pcap / pcapng,做离线浏览、搜索、会话聚合和协议树分析。 +* 基于 Scapy 现有自动机、应答机、contrib 协议实现专项工具页。 + + +总体架构 +======== + +分层架构 +-------- + +建议采用四层结构: + +1. 表现层 + + * Qt 主窗口、停靠面板、协议树、表格、十六进制视图、图表与任务面板。 + +2. 应用服务层 + + * 统一封装抓包任务、发送任务、离线分析任务、导出任务、协议模板任务。 + +3. Scapy 适配层 + + * 对 ``sendrecv``、``interfaces``、``utils``、``packet``、``plist``、``sessions``、 + ``ansmachine``、``automaton`` 提供稳定的 GUI 调用接口。 + +4. 运行时与系统层 + + * 线程、权限、日志、配置、缓存、文件系统、Npcap 检测、外部工具集成。 + +建议的 Python 包布局如下: + +:: + + scapy_gui/ + app/ + bootstrap.py + dependency_check.py + ui/ + main_window.py + widgets/ + dialogs/ + models/ + services/ + packet_builder_service.py + capture_service.py + replay_service.py + pcap_analysis_service.py + interface_service.py + automation_service.py + template_service.py + adapters/ + scapy_packet_adapter.py + scapy_capture_adapter.py + scapy_interface_adapter.py + scapy_pcap_adapter.py + domain/ + workspace.py + task.py + capture_profile.py + packet_template.py + plugins/ + builtin/ + contrib/ + +线程与任务模型 +-------------- + +GUI 不能把 Scapy 的阻塞调用直接放在主线程里。建议: + +* Qt 主线程仅负责界面更新。 +* ``sniff()``、``sr()``、``sr1()``、pcap 读取、批量发送等操作放入后台 worker。 +* 实时抓包优先用 ``AsyncSniffer`` 或 worker 封装的长生命周期任务。 +* 通过 Qt signal / slot 把包到达事件、状态更新、错误信息回传到 UI。 +* 对长任务统一抽象为 ``TaskHandle``,支持开始、暂停、取消、导出结果。 + +这样做的原因是:Scapy 的核心 API 偏脚本式与阻塞式,GUI 需要把它转换为事件驱动模型。 + + +信息架构 +======== + +主窗口建议采用多工作区布局: + +* 左侧:资源导航 + + * 接口 + * 协议模板 + * 最近工程 + * 插件工具 + +* 中央:主工作区标签页 + + * 包构建器 + * 实时抓包 + * 离线分析 + * 请求/响应任务 + * 自动化工具 + +* 右侧:属性与详情面板 + + * 字段编辑器 + * 包协议树 + * 原始十六进制 + * 任务参数 + +* 底部:运行与日志面板 + + * 任务状态 + * 错误输出 + * Scapy 警告 + * 统计信息 + + +核心功能模块 +============ + +1. 协议浏览器 +-------------- + +该模块用于把 Scapy 的协议面公开给用户。 + +需求: + +* 浏览内置层 ``scapy/layers/`` 与可选扩展 ``scapy/contrib/``。 +* 展示协议简介、字段列表、默认值、上层/下层组合关系。 +* 支持搜索协议名、字段名、别名。 +* 支持从协议浏览器直接新建模板。 + +实现建议: + +* 以 ``Packet`` 子类为核心索引。 +* 利用 ``ls()``、``explore()`` 以及字段描述生成可视化说明。 +* 对 ``contrib`` 协议使用按需加载,避免启动期全部导入。 + +2. 包构建器 +----------- + +这是首版最重要的模块之一。 + +能力要求: + +* 通过协议树逐层添加,如 Ether / IP / TCP / Raw。 +* 字段编辑器支持自动值、手动值、随机值、枚举值、十六进制值。 +* 即时显示: + + * ``show()`` 风格结构视图 + * 原始字节预览 + * 十六进制视图 + * 长度、校验和、摘要 + +* 支持模板保存、克隆、导入导出。 +* 支持单包发送、批量发送、模糊测试模式。 + +实现建议: + +* 数据模型内部直接持有 ``Packet`` 实例。 +* 界面编辑后重建或局部更新 ``Packet``。 +* 对自动计算字段使用“自动”态,不强行提前固化。 + +3. 发送与请求响应任务 +--------------------- + +围绕 ``send()``、``sendp()``、``sr()``、``sr1()`` 建立统一任务面板。 + +应支持: + +* L2 / L3 发送模式切换 +* 目标接口选择 +* 发送次数、间隔、超时、重试参数 +* 回包展示与请求响应关联 +* 将结果保存为会话记录 + +建议把返回结果分成三种视图: + +* 发送日志 +* 应答包列表 +* 未应答包列表 + +4. 实时抓包工作台 +----------------- + +围绕 ``sniff()`` 与 ``AsyncSniffer`` 建立 Wireshark 风格但更偏实验的工作台。 + +最低功能集: + +* 选择接口 +* BPF 过滤器输入 +* 开始、停止、暂停、继续 +* 包表格 +* 选中包的协议树与十六进制详情 +* 保存为 pcap / pcapng + +增强功能: + +* 自定义列 +* 颜色规则 +* 统计摘要 +* 从抓到的包一键复制到包构建器继续编辑 + +5. 离线分析工作台 +----------------- + +围绕 ``rdpcap()``、``PcapReader``、``PacketList`` 建立文件分析功能。 + +建议支持: + +* 打开大型 pcap / pcapng 文件 +* 懒加载 / 分页读取 +* 过滤、检索、收藏 +* 会话视图、协议分布、端点统计 +* 导出筛选结果 + +首版建议优先采用流式读取,而不是一次性把大文件全部载入内存。 + +6. 会话与流重组视图 +------------------- + +基于 ``sessions.py`` 做更高层展示: + +* IP 分片重组 +* TCP 流视图 +* 请求/响应聚合 +* 特定协议的事务视图 + +此模块是抓包与离线分析的桥梁,也是后续加入 HTTP、DNS、SMB、NetFlow 等专题面板的基础。 + +7. 自动化与协议工具页 +--------------------- + +Scapy 的价值不止抓包。GUI 应留出自动化工具页: + +* 应答机工具页:基于 ``AnsweringMachine`` 创建自动回复任务。 +* 自动机工具页:把 ``Automaton`` 封装为向导式流程操作。 +* 研究型协议工具:针对 SMB、Kerberos、Automotive、802.11 等协议提供专题页。 + +首版不必全部实现,但架构必须留出统一注册入口。 + + +Windows 优先设计要点 +==================== + +依赖检查 +-------- + +应用启动时应显式检测: + +* 是否安装 Npcap +* 当前进程是否具备管理员权限 +* 当前接口是否支持目标模式 +* Scapy 可选依赖是否可用 + +不满足条件时,不要让用户在任务执行后才看到底层 traceback,而应在 GUI 中提前给出诊断信息。 + +接口体验 +-------- + +Windows 下接口名称、描述、GUID、Npcap 名称可能不同。界面应同时展示: + +* 友好名称 +* 描述 +* GUID / Npcap 标识 +* IPv4 / IPv6 地址 +* 能力标签,如可抓包、可发送、无线模式可用性 + +权限模型 +-------- + +建议把高风险操作分级: + +* 普通级:离线分析、模板编辑、pcap 浏览 +* 提权级:实时抓包、原始发送、无线注入、接口模式调整 + +并在界面中明确显示当前会话权限状态。 + + +数据模型设计 +============ + +建议定义以下领域对象: + +* ``PacketTemplate`` + + * 保存用户构建的包模板、字段覆盖、标签与说明。 + +* ``CaptureProfile`` + + * 保存接口、过滤器、会话类型、输出路径、列配置。 + +* ``TaskRecord`` + + * 记录发送、抓包、分析任务的参数、状态、开始结束时间、错误信息。 + +* ``WorkspaceDocument`` + + * 统一抽象“一个打开的工作标签”,便于恢复会话和保存工程。 + +这些对象应与具体 UI 控件解耦,便于后续做自动化测试与工程保存。 + + +插件与扩展策略 +============== + +由于 ``scapy/contrib/`` 能力很多,GUI 不能把所有工具写死在主程序里。建议: + +* 设计 ``ToolPlugin`` 接口,用于注册新工具页、新协议专题页、新模板集。 +* 内置插件覆盖高频能力:Packet Builder、Live Capture、Pcap Analyzer。 +* 扩展插件覆盖专题协议:Automotive、SMB、Kerberos、802.11、Bluetooth。 +* 对 contrib 协议采用按需导入 + 能力检测,避免启动变慢和无谓依赖失败。 + + +开发阶段规划 +============ + +M1: 最小可用版本 +---------------- + +* 主窗口与工作区框架 +* 接口浏览 +* 包构建器 +* ``send()`` / ``sendp()`` / ``sr1()`` 基础任务 +* ``AsyncSniffer`` 实时抓包 +* 包详情树 + 十六进制视图 + +M2: 分析增强版本 +---------------- + +* 离线 pcap / pcapng 分析 +* 会话聚合 +* 过滤、搜索、自定义列 +* 模板管理与工程保存 + +M3: 高阶协议与自动化版本 +------------------------ + +* 应答机工具页 +* 自动机工具页 +* 专题插件体系 +* contrib 协议浏览器 + + +测试与验证策略 +=============== + +建议测试拆成四层: + +* 领域层测试 + + * 模板、任务、配置对象序列化与反序列化。 + +* 适配层测试 + + * 对 Scapy 封装接口做单元测试,验证包构建、参数传递、错误处理。 + +* UI 交互测试 + + * 对关键流程做 Qt 自动化测试,例如新建包、编辑字段、开始抓包、停止任务。 + +* 集成测试 + + * 在具备 Npcap 的 Windows 环境验证接口发现、抓包、发送、pcap 导入导出。 + +对首版最关键的验收标准如下: + +* 用户能通过 GUI 构建常见多层数据包并发送。 +* 用户能选择接口并完成实时抓包。 +* 用户能打开 pcap 并浏览协议树与十六进制视图。 +* 用户能把抓到的包复制回构建器进行二次编辑。 + + +后续实现建议 +============ + +按照当前仓库情况,推荐的实施顺序是: + +1. 先在仓库外或新增子项目中实现独立 GUI 包,避免直接侵入 Scapy 现有核心模块。 +2. 第一阶段只封装稳定后端接口,不改动 Scapy 包解析与发送主逻辑。 +3. 先打通三条主链路:包构建、实时抓包、离线分析。 +4. 完成插件注册机制后,再逐步纳入 contrib 协议工具页。 + +这样可以最大限度复用 Scapy 现有能力,同时避免 GUI 开发反向污染核心 library。 + + +当前状态 +======== + +截至当前轮设计,项目状态如下: + +已完成事项 +---------- + +* 已完成对 Scapy 文档主入口、核心源码模块和协议层目录的定向探索。 +* 已确认首个 GUI 版本采用 ``PySide6 / Qt``,平台优先级为 Windows。 +* 已识别 GUI 首版最关键的后端锚点:``packet.py``、``fields.py``、``sendrecv.py``、 + ``plist.py``、``sessions.py``、``interfaces.py``、``ansmachine.py``、``automaton.py``。 +* 已识别 Windows 关键约束:Npcap / libpcap、管理员权限、接口能力差异、无线注入限制。 +* 已在 Scapy 文档目录中加入本文档入口。 +* 已完成一次 Sphinx 文档构建验证,确认本文档可被当前文档系统收录。 + +当前结论 +-------- + +* GUI 可以作为独立应用层实现,Scapy 继续作为底层 library。 +* 首版应聚焦三条主链路,而不是同时铺开所有专题协议页面: + + * 包构建与发送 + * 实时抓包 + * 离线 pcap 分析 + +* ``contrib``、``AnsweringMachine``、``Automaton`` 等能力应纳入总体架构,但不应阻塞 M1。 + +当前未完成事项 +-------------- + +* 尚未建立 GUI 子项目目录与运行入口。 +* 尚未定义 PySide6 具体页面结构、Qt Model 设计与信号通信接口。 +* 尚未把 Scapy 后端能力封装成稳定的 GUI service / adapter 层。 +* 尚未建立用于 Windows 的依赖检查、权限检查与 Npcap 诊断模块。 +* 尚未完成任何界面原型、交互稿或可运行演示。 + +当前已知阻塞与注意事项 +---------------------- + +* 文档构建时,仓库自带的 ``scapy_doc`` 扩展会尝试调用 ``tox -e apitree``。 +* 在当前本地环境中,如未设置 ``SCAPY_APITREE=0``,Sphinx 构建可能因缺少 ``tox`` 而失败。 +* 仓库当前还存在一个与本文档无关的既有 warning:``doc/scapy/index.rst`` 中引用了不存在的 + ``api/scapy`` 页面。 + + +后续步骤规划 +============ + +建议后续工作按以下顺序推进。 + +第 1 步:细化实现规格 +--------------------- + +* 在本文档基础上补一版“实现规格文档”。 +* 明确主窗口布局、页面流转、核心组件树、Qt signal / slot 边界。 +* 明确 ``services``、``adapters``、``domain`` 三层的最小接口。 + +交付物: + +* 页面结构图 +* 模块职责表 +* 最小类图或接口清单 + +第 2 步:建立 GUI 子项目骨架 +------------------------------ + +* 新增独立 GUI 子项目目录,避免污染现有 Scapy 核心包。 +* 建立 PySide6 应用启动入口、主窗口、基础日志与配置目录。 +* 打通最小启动链路,保证应用可打开空白主窗口。 + +交付物: + +* 可运行的 ``main`` 入口 +* 基础主窗口 +* 项目目录骨架 + +第 3 步:打通 M1 三条主链路 +----------------------------- + +优先顺序如下: + +1. 接口浏览与依赖诊断 +2. 包构建器 +3. 发送 / ``sr1()`` 任务 +4. ``AsyncSniffer`` 实时抓包 +5. 抓包详情树与十六进制视图 +6. pcap 导入与基础离线浏览 + +这一阶段的目标不是把界面做全,而是尽快得到一个“能构包、能发包、能抓包、能看包”的闭环。 + +第 4 步:建立可扩展后端抽象 +--------------------------- + +* 把 Scapy 调用统一封装进 adapter / service。 +* 统一错误处理、任务生命周期、日志格式和结果模型。 +* 为后续插件机制预留注册接口。 + +交付物: + +* ``capture_service`` +* ``packet_builder_service`` +* ``interface_service`` +* ``pcap_analysis_service`` + +第 5 步:补测试与 Windows 验证 +------------------------------ + +* 为纯 Python 领域对象和 adapter 层补单元测试。 +* 为最关键 GUI 流程补冒烟测试。 +* 在真实 Windows + Npcap 环境下验证接口发现、发送、抓包、pcap 导出。 + +交付物: + +* 可重复运行的最小测试集 +* Windows 环境验证记录 + +第 6 步:进入 M2 / M3 增强阶段 +-------------------------------- + +* 增加离线分析增强能力,如过滤、搜索、自定义列、会话聚合。 +* 增加工程保存、模板库、插件注册。 +* 再逐步引入 ``AnsweringMachine``、``Automaton`` 与专题协议工具页。 + +建议的近期执行顺序 +------------------ + +如果下一轮直接开始编码,建议采用下面的短周期顺序: + +1. 建立 GUI 子项目骨架。 +2. 完成主窗口与接口浏览面板。 +3. 完成包构建器最小可用版。 +4. 接入 ``send()`` / ``sr1()``。 +5. 接入 ``AsyncSniffer``。 +6. 接入 pcap 文件打开与详情浏览。 + +达到以上六步后,再决定是否立即进入 M2,还是先做界面打磨与稳定性修复。 \ No newline at end of file diff --git a/doc/scapy/index.rst b/doc/scapy/index.rst index a7ae7f7d082..dcd7f437ed4 100644 --- a/doc/scapy/index.rst +++ b/doc/scapy/index.rst @@ -24,6 +24,7 @@ Scapy's documentation is under a `Creative Commons Attribution - Non-Commercial installation usage + gui_application_design advanced_usage/index.rst routing From fcfd922fd9e86f0fbdcb919da5d17eb9456da322 Mon Sep 17 00:00:00 2001 From: wbzhu Date: Mon, 11 May 2026 17:31:53 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=A6=96=E7=89=88GUI?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- doc/_build/dummy/.doctrees/environment.pickle | Bin 256288 -> 256288 bytes .../.doctrees/gui_application_design.doctree | Bin 121814 -> 135324 bytes doc/scapy/gui_application_design.rst | 184 ++- gui/README.rst | 50 + gui/ScapyStudio.spec | 51 + gui/build_release_package.bat | 21 + gui/pyproject.toml | 25 + gui/scripts/build_windows_release_package.ps1 | 82 + gui/scripts/build_windows_validation_exe.ps1 | 71 + gui/scripts/windows_npcap_validation.ps1 | 110 ++ gui/src/packet_studio/__init__.py | 5 + gui/src/packet_studio/__main__.py | 5 + gui/src/packet_studio/adapters/__init__.py | 4 + .../adapters/scapy_packet_adapter.py | 58 + .../adapters/scapy_pcap_adapter.py | 39 + gui/src/packet_studio/app.py | 51 + gui/src/packet_studio/domain/__init__.py | 18 + gui/src/packet_studio/domain/packet_models.py | 37 + gui/src/packet_studio/domain/task_models.py | 91 ++ .../packet_studio/domain/workspace_models.py | 63 + gui/src/packet_studio/main_window.py | 566 +++++++ gui/src/packet_studio/runtime/__init__.py | 1 + .../packet_studio/runtime/dependency_check.py | 89 ++ gui/src/packet_studio/runtime/paths.py | 41 + gui/src/packet_studio/services/__init__.py | 1 + .../services/interface_service.py | 84 ++ .../services/packet_builder_service.py | 992 +++++++++++++ .../services/pcap_analysis_service.py | 42 + .../services/send_task_service.py | 247 +++ .../services/tool_registry_service.py | 41 + .../services/workspace_document_service.py | 42 + gui/src/packet_studio/validation_launcher.py | 76 + gui/src/packet_studio/widgets/__init__.py | 1 + .../widgets/automation_tools_widget.py | 96 ++ .../widgets/offline_analysis_widget.py | 362 +++++ .../widgets/packet_builder_widget.py | 1319 +++++++++++++++++ .../packet_studio/widgets/send_task_widget.py | 652 ++++++++ gui/tests/test_gui_smoke.py | 323 ++++ .../test_packet_builder_service_import.py | 99 ++ gui/tests/test_pcap_analysis_service.py | 63 + gui/tests/test_scapy_packet_adapter.py | 52 + gui/tests/test_send_task_service.py | 185 +++ gui/tests/test_tool_registry_service.py | 22 + gui/tests/test_workspace_document_service.py | 59 + 45 files changed, 6353 insertions(+), 70 deletions(-) create mode 100644 gui/README.rst create mode 100644 gui/ScapyStudio.spec create mode 100644 gui/build_release_package.bat create mode 100644 gui/pyproject.toml create mode 100644 gui/scripts/build_windows_release_package.ps1 create mode 100644 gui/scripts/build_windows_validation_exe.ps1 create mode 100644 gui/scripts/windows_npcap_validation.ps1 create mode 100644 gui/src/packet_studio/__init__.py create mode 100644 gui/src/packet_studio/__main__.py create mode 100644 gui/src/packet_studio/adapters/__init__.py create mode 100644 gui/src/packet_studio/adapters/scapy_packet_adapter.py create mode 100644 gui/src/packet_studio/adapters/scapy_pcap_adapter.py create mode 100644 gui/src/packet_studio/app.py create mode 100644 gui/src/packet_studio/domain/__init__.py create mode 100644 gui/src/packet_studio/domain/packet_models.py create mode 100644 gui/src/packet_studio/domain/task_models.py create mode 100644 gui/src/packet_studio/domain/workspace_models.py create mode 100644 gui/src/packet_studio/main_window.py create mode 100644 gui/src/packet_studio/runtime/__init__.py create mode 100644 gui/src/packet_studio/runtime/dependency_check.py create mode 100644 gui/src/packet_studio/runtime/paths.py create mode 100644 gui/src/packet_studio/services/__init__.py create mode 100644 gui/src/packet_studio/services/interface_service.py create mode 100644 gui/src/packet_studio/services/packet_builder_service.py create mode 100644 gui/src/packet_studio/services/pcap_analysis_service.py create mode 100644 gui/src/packet_studio/services/send_task_service.py create mode 100644 gui/src/packet_studio/services/tool_registry_service.py create mode 100644 gui/src/packet_studio/services/workspace_document_service.py create mode 100644 gui/src/packet_studio/validation_launcher.py create mode 100644 gui/src/packet_studio/widgets/__init__.py create mode 100644 gui/src/packet_studio/widgets/automation_tools_widget.py create mode 100644 gui/src/packet_studio/widgets/offline_analysis_widget.py create mode 100644 gui/src/packet_studio/widgets/packet_builder_widget.py create mode 100644 gui/src/packet_studio/widgets/send_task_widget.py create mode 100644 gui/tests/test_gui_smoke.py create mode 100644 gui/tests/test_packet_builder_service_import.py create mode 100644 gui/tests/test_pcap_analysis_service.py create mode 100644 gui/tests/test_scapy_packet_adapter.py create mode 100644 gui/tests/test_send_task_service.py create mode 100644 gui/tests/test_tool_registry_service.py create mode 100644 gui/tests/test_workspace_document_service.py diff --git a/.gitignore b/.gitignore index 97e2a8a8be6..104587288c3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ coverage.xml __pycache__/ doc/scapy/_build doc/scapy/api -.idea \ No newline at end of file +.idea +gui/release \ No newline at end of file diff --git a/doc/_build/dummy/.doctrees/environment.pickle b/doc/_build/dummy/.doctrees/environment.pickle index a94c76f512e7d23a4e6e3ca3a859b38277799bc0..d0a1efea107eadf742e62c63eeeb72371c4d77e7 100644 GIT binary patch delta 3067 zcmY+GdrVvR700>$5fk$=;6QlU2AdEd*w}=S4uZiv3<+IQNS2_aDZAJguw(8;_BBhU zrS5C#Mw}(%Sh?ylHCZ~l!$76aqBt@ozLU>k^WWP8Qk8MZ} zOZnzSzeIh>QOAVPln|2WdQ-E?UfQN8S)K`p`HFdeJXV@a@DW*>qn#H7_-d;O^tKwf z&{|cU8XTURqAT(V3qnwupA$qm4r4oOqUnAm*LI}+E%JP^_I4%pKkDX;6JzP9h=(LY; z+aPMM=NjM#b~9&$6}yqM!5=B>;D7d(Xbs8932djJw~O~?4~6CUH{;{+fFWHeS$~rm zL&lI!ie-R632^7U*=ExkfnXH1FJ;?h&9- zY$HBQHZ$u~npMrrpoce{^&qtC;f7Ps6~RB9b=+1cYBxf}Spr?{#he9px7S8%v&P5r z=yT09ugEUuCtgjaZl(1|tYp$Bc}0Fsn3Sc-d0w84pNuyJw&B=LL<8bW2qR(_!iMN2 zInkUz9fth~cE>{)u{#~Xh+W<&M(mdMV#Mxb3?p_U2QX?z970qhjv!2k#}F+D9>H$J zhfymcI*n{X_z{(eIYcpH9$`j=5nB)oh(^Q-1iPMZV8pH>ijf}iB%&Pg45A3}EMhBS z5wVGEr96Y71#u3+ZUiu5$A2Cp>$0xnsJ0RhXfaRJuHJuQOv?^wG4HmtdLJI|nuoFO zD(G@m!oRvk$+^Hg&^J;CXS#Pl!>$I<(%QXl2VCv0qO}ib%?yEE<;oY=h!+0fF;ZcM zm0e$fJw3y$NqJ8#e7C0!nq1pJ8rT3g+)geRKI`qH{7-+EHaC|(#cE~Wby5Q!w~^J# zmCBDvaZ+v{aKqgm7wqk|vIf8IX@{cT4r;tPPzfu2RvH!e4}*(EE3V;0%UY;+8x-4h zk`JfcR#pS4;fiL$v;L?CCGoS$8?=M~&w7|i^a<=dK!CHDi*VqA_8Pq}HP&Lh0<-S7q zt9){cSm9@bozOVMgF3WBS4e-VjtThH=5$%WyQZ^@|@KKa-ns209r;2thG4Y z1fLJv;ho{_aL>~Ts~#JiAFig;8R&FVoMHKgBhTo$8t6M@fjtvJj;0S;mOq-f!WFQ; zjOVoUB^aK*c=*|ihdK4)Va=g8sQa_eMe1%)zMV(hT#fR9R#T-s@Q{4ZU3Hd0?V)IX;&D6g3~z50|Tu{PH2bt~yfdml6;guaO`XMZEPg59Wi z6djmUUf<8@)YHwRJC#vCZXsLHMq0@c{G7Cr8T?$eleM(zl7kGSMSeSRrN!iSVoHm6 z$GZ9dwu5b2ZQDs^(eZXSUDR6-kzc08lU>9{g-R=PnEd2~#-KWS$VA$4(X~z#_pK8r z-At$-_7iW~{>UKt2}d6mKAMjvoXSfc`e+R4N3+Zs)a7AvlUmfJ-S~VA%JX~3r&OLD zCEGB4QIH@Xlms%1(HP=B8OBai={yjYst-+lU3a4H_bEgl}D4Xz><5mSi&A|6L9Aih9+2jN0|57CL}LIeikD&&Qjfk5l|Ba|XIgQal#CgP1h%zi#5I;rv Lp?dT-`HklPJVk9f delta 3068 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 delta 5005 zcmb_gdsLNG7IzTWlKy=FQQo= zvJFK=V3w;UlO{!hbWzK)hk>smbD1Puu*@>cY-*S}d+!egTl3G%{B!R9zVq9^{XCEF zZYd4^q{@<42)zR8KJT_C6ks7t$NYsb%?P(y(Yg>aAp%1eLOQlCglF(V5j3csHpBVu zU~7e~Teqy6vjVcBv*NR2#)m3n;(a#kzX_(PY8x)z1T$2v%{bbYWa*=_qH^}xjOB;D zBJZ&o$vI(F6JCbNppFVCj6g?hhILrD(a^6awikn2owK2PGsLMD!9) zVX(St!!w(~ss16jNWxta9LpLvdkZ|FLhR&FP+ywwftC3XXJJ{-Cwm~uIQw;^g^7>w z3$KN}GTq%(0x_V5+Km^Z2CGE7;ru>Q|KNfEn56E!_O@u7sD-(U0^CM<&fa~iIBT=4&+_U|lhDZz7j&vmNNG0B3H z;`&(PVp-z4ZHyV$t^)s>4b3$*&2MdN-B@tGWJk;TZ7o~Z>R6hq%bTlTYTbFzzow|= zh1c5-ZfGkl@Yfc$mRB2ATllik{|f{8DupZfFL@%xm99azO3*F}`kVyKa)Dj?k{f8L zekg^$SoWsw$7r)T)gE{lg7K08@y3|Qo2-G3E)5zh4Z3%aUXD=?^z4P<5RGXYAqp4o z)h+KP%^@ncZDRgeP1I;M<+k46%LmQGL{x!4aY*Xza1a)g81y2n1qQxhGy zZXetZ4~hlM_>`E1MRpV15F)IbkRs)aasNtp8;xVjdGm>8_D)e)Cnn;Q5Q z#3&76qw!?)d5MeV2#2JegACLUr4Whh-i7-h7EiqkTj2x|Nz#vYO?rYdn!X6f58s2C z&?G3mRC^_ni4G`jEp}Xc7$(4_>qHm35)GB=4SyfTsQ@Rgd>`&tfll7#Q-})y@jxmf@Qhgfu%Db9E}g*fJ$^4O=F{R{zu?dsZQB$owD7^ z%+EA)_q7?`a-I0t5#1rRsJ9`9{U%L<9~^*t*=5mFR>m{R7!;6T2+8NE{=awOlVpmRrm?nEa%6&rCE75z;@Bf__hlF8x#& zV6+PxKGlbRn#;%-+8=v2f>W<#-BSh(GIXcr1>!b5FF?cs|jdXD#zHiip znC-&w&%gtZi*xtTe5PPoHnEkT!E{(Em~n)8MldXoba5eqnEYf>sczG^Pr;oxmSK!p zhLq1C9ZJMsz5j(4ySkKOJRbWT#=|RY%<=#kD|X@5X%L6YPV1{otqWf}4M}i7yqQRV zYVn5UDv9oe;d-k$>cU;ab#{&mb`oLh1-Hih~{`;$|Uc*(6>!3KCRLH@-DW-!`mnbRN-Ln;VmE)tT?@ z=JHq`BHiw8-WFJ9-EiD=7KW(=H-3B;MyMn=Zy}QjJ6NzRn|AV=V6_@6m??xw6Aa5$ zTpl|Xd02T#N)@|!Mm%i`st8JKg*jdglT zec|T)^t9Vp5M!hJnkAF88^s*UC&i`bzJfvapMJ}9?NvlUqydGIov*XM3j5F3*=G^^ zkHXHf$)5i;cq76+zwtBNgNO8#>hB>x^`n_9Z)jJ-ny9~FeLqfQK z=IexzyL$<91?7?JFN^Ce7jX{tK@-vramGq( zXwTi(`Q&ZSPP_jbLbIG`b_os3OcRdJwu6UB4qYel@Y~KL?+M9sMDmW1uq?h{dAq(b zX5Ad>JY|v$(4WZ`|N4rSRTYj-zMPJwyMQsjF&54dqg3*A?8X@?Qkj$bNP{0bPx5EN zX|s?k$b&}lfMxL@ILb6;oQZQgtzOCRfAAvA%Qq4SsALu8<&9LoqK}a!xI}lZUgTD# zn$!d@ADOEOsT+pZVOoGn4ZY!ZxD?j}sCyxy>O_F5QL5Nm^G#sYk5DnR+{}>I&^<6Wkfrd@`E|Ig5 zhI#tj$eBaK!+Oo)HLAp0W-$4p~H!oxE?5CFG&VG{q9LdS` z;>wK#H3~rW{Mi^nD~D)0JC7rW>DxOcw|VnGOyKnKnTk UvL1BVMu(^$)NU`PgsR8?4KmuO+5i9m diff --git a/doc/scapy/gui_application_design.rst b/doc/scapy/gui_application_design.rst index a16df21abee..313b494f8c1 100644 --- a/doc/scapy/gui_application_design.rst +++ b/doc/scapy/gui_application_design.rst @@ -11,16 +11,18 @@ PySide6 / Qt,平台优先级为 Windows。GUI 不重新实现协议栈,而 目标如下: -* 让用户无需记忆 Scapy CLI 与 Python API,也能完成数据包构建、发送、抓包、离线分析。 +* 让用户无需记忆 Scapy CLI 与 Python API,也能完成数据包构建、发送、离线分析。 * 保持与 Scapy 原生对象模型一致,核心后端仍然使用 ``Packet``、``PacketList``、 - ``AsyncSniffer``、``PcapReader``、``AnsweringMachine``、``Automaton`` 等能力。 -* 充分吸收 Scapy 已支持的协议层、扩展协议与自动化能力,而不是只做一个简化的抓包器。 + ``PcapReader``、``AnsweringMachine``、``Automaton`` 等能力。 +* 充分吸收 Scapy 已支持的协议层、扩展协议与自动化能力,而不是只做一个简化的协议浏览器。 +* 实时抓包由 Wireshark 等专业工具承担,GUI 负责消费 ``pcap`` / ``pcapng`` 结果并继续分析、回放与编辑。 * 为后续扩展协议插件、自动化向导、数据分析视图预留架构空间。 首版不做的事情: * 不在 GUI 内重新实现一套独立的协议描述语言。 * 不追求替代 Wireshark 的全部高级分析器功能。 +* 不在 GUI 内提供实时抓包页面;当前 Qt 路线在高吞吐抓包场景下容易卡死,交互稳定性与性能都不如 Wireshark。 * 不在首版覆盖所有平台差异;先把 Windows + Npcap 路线设计清楚。 @@ -50,7 +52,7 @@ Scapy 的主定位已经很清楚:创建、发送、捕获、解析、操作 * 发送与收发:``scapy/sendrecv.py`` * 提供 ``send()``、``sendp()``、``sr()``、``sr1()``、``sniff()``、``AsyncSniffer``。 - * GUI 可以统一抽象为发送任务、请求/响应任务、实时抓包任务。 + * GUI 当前聚焦发送任务、请求/响应任务;实时采集改由 Wireshark 负责,必要时再消费其导出的 ``pcap`` / ``pcapng`` 文件。 * 包列表与结果集:``scapy/plist.py`` @@ -76,7 +78,7 @@ Scapy 的主定位已经很清楚:创建、发送、捕获、解析、操作 * PCAP / PCAPNG 读写:``scapy/utils.py`` * 提供 ``rdpcap()``、``wrpcap()``、``PcapReader``、``PcapWriter`` 等。 - * GUI 需要同时支持离线文件分析和实时捕获结果保存。 + * GUI 需要重点支持离线文件分析、筛选结果导出,以及与 Wireshark 抓包文件的互操作。 协议与扩展能力 -------------- @@ -102,7 +104,7 @@ GUI 不应只覆盖 IP/TCP/UDP/ICMP。仓库中已有大量现成协议支持: Windows 路线必须正视以下事实: -* 抓包和部分注入能力依赖 Npcap / libpcap 路线,相关逻辑分布在 +* 外部抓包链路和部分注入能力依赖 Npcap / libpcap 路线,相关逻辑分布在 ``scapy/arch/libpcap.py`` 与 ``scapy/arch/windows/__init__.py``。 * 部分操作需要管理员权限。 * 原始 802.11、接口模式切换、底层 socket 能力会受驱动、Npcap 特性与网卡硬件限制。 @@ -125,8 +127,7 @@ Windows 路线必须正视以下事实: * 从模板构造和发送常见报文,如 ARP、ICMP、DNS、TCP、HTTP、DHCP。 * 构建多层复杂数据包,逐字段可视化编辑,实时查看原始字节与校验字段变化。 -* 对指定接口进行实时抓包、过滤、暂停、保存和二次分析。 -* 导入 pcap / pcapng,做离线浏览、搜索、会话聚合和协议树分析。 +* 使用 Wireshark 等外部工具抓包后,导入 ``pcap`` / ``pcapng``,做离线浏览、搜索、会话聚合和协议树分析。 * 基于 Scapy 现有自动机、应答机、contrib 协议实现专项工具页。 @@ -144,7 +145,7 @@ Windows 路线必须正视以下事实: 2. 应用服务层 - * 统一封装抓包任务、发送任务、离线分析任务、导出任务、协议模板任务。 + * 统一封装发送任务、离线分析任务、导出任务、协议模板任务。 3. Scapy 适配层 @@ -170,7 +171,6 @@ Windows 路线必须正视以下事实: models/ services/ packet_builder_service.py - capture_service.py replay_service.py pcap_analysis_service.py interface_service.py @@ -178,13 +178,12 @@ Windows 路线必须正视以下事实: template_service.py adapters/ scapy_packet_adapter.py - scapy_capture_adapter.py scapy_interface_adapter.py scapy_pcap_adapter.py domain/ workspace.py task.py - capture_profile.py + analysis_profile.py packet_template.py plugins/ builtin/ @@ -196,9 +195,8 @@ Windows 路线必须正视以下事实: GUI 不能把 Scapy 的阻塞调用直接放在主线程里。建议: * Qt 主线程仅负责界面更新。 -* ``sniff()``、``sr()``、``sr1()``、pcap 读取、批量发送等操作放入后台 worker。 -* 实时抓包优先用 ``AsyncSniffer`` 或 worker 封装的长生命周期任务。 -* 通过 Qt signal / slot 把包到达事件、状态更新、错误信息回传到 UI。 +* ``sr()``、``sr1()``、pcap 读取、批量发送等操作放入后台 worker。 +* 通过 Qt signal / slot 把进度、状态更新、错误信息回传到 UI。 * 对长任务统一抽象为 ``TaskHandle``,支持开始、暂停、取消、导出结果。 这样做的原因是:Scapy 的核心 API 偏脚本式与阻塞式,GUI 需要把它转换为事件驱动模型。 @@ -219,7 +217,6 @@ GUI 不能把 Scapy 的阻塞调用直接放在主线程里。建议: * 中央:主工作区标签页 * 包构建器 - * 实时抓包 * 离线分析 * 请求/响应任务 * 自动化工具 @@ -304,26 +301,24 @@ GUI 不能把 Scapy 的阻塞调用直接放在主线程里。建议: * 应答包列表 * 未应答包列表 -4. 实时抓包工作台 ------------------ +4. 外部抓包导入工作流 +---------------------- -围绕 ``sniff()`` 与 ``AsyncSniffer`` 建立 Wireshark 风格但更偏实验的工作台。 +实时采集不再由 GUI 承担,而是明确采用 Wireshark 等专业工具完成。 + +这样调整的原因是:当前图形界面在高吞吐抓包场景下容易卡死,UI 响应性与持续采集稳定性不够理想;与其继续在 Qt 页面内承载实时抓包,不如把 GUI 资源集中到构包、发送和离线分析。 最低功能集: -* 选择接口 -* BPF 过滤器输入 -* 开始、停止、暂停、继续 -* 包表格 -* 选中包的协议树与十六进制详情 -* 保存为 pcap / pcapng +* 引导用户使用 Wireshark 抓包并导出 ``pcap`` / ``pcapng`` +* 提供最近文件、快捷导入和失败诊断入口 +* 支持从导入结果一键跳转到离线分析页 增强功能: -* 自定义列 -* 颜色规则 -* 统计摘要 -* 从抓到的包一键复制到包构建器继续编辑 +* 监视指定目录中的最新抓包文件 +* 记住最近一次导入来源与过滤条件 +* 为 Wireshark 导出失败、文件占用、格式不兼容提供更明确提示 5. 离线分析工作台 ----------------- @@ -350,12 +345,12 @@ GUI 不能把 Scapy 的阻塞调用直接放在主线程里。建议: * 请求/响应聚合 * 特定协议的事务视图 -此模块是抓包与离线分析的桥梁,也是后续加入 HTTP、DNS、SMB、NetFlow 等专题面板的基础。 +此模块是外部抓包文件与离线分析的桥梁,也是后续加入 HTTP、DNS、SMB、NetFlow 等专题面板的基础。 7. 自动化与协议工具页 --------------------- -Scapy 的价值不止抓包。GUI 应留出自动化工具页: +Scapy 的价值不止离线解析与发包。GUI 应留出自动化工具页: * 应答机工具页:基于 ``AnsweringMachine`` 创建自动回复任务。 * 自动机工具页:把 ``Automaton`` 封装为向导式流程操作。 @@ -377,6 +372,8 @@ Windows 优先设计要点 * 当前接口是否支持目标模式 * Scapy 可选依赖是否可用 +如果用户希望在同机通过 Wireshark 抓包,也可以顺带提示 Npcap 状态;但这属于外部采集链路诊断,而不是 GUI 内部实时抓包前置条件。 + 不满足条件时,不要让用户在任务执行后才看到底层 traceback,而应在 GUI 中提前给出诊断信息。 接口体验 @@ -388,7 +385,7 @@ Windows 下接口名称、描述、GUID、Npcap 名称可能不同。界面应 * 描述 * GUID / Npcap 标识 * IPv4 / IPv6 地址 -* 能力标签,如可抓包、可发送、无线模式可用性 +* 能力标签,如可发送、L2/L3 可用性、无线模式可用性 权限模型 -------- @@ -396,7 +393,7 @@ Windows 下接口名称、描述、GUID、Npcap 名称可能不同。界面应 建议把高风险操作分级: * 普通级:离线分析、模板编辑、pcap 浏览 -* 提权级:实时抓包、原始发送、无线注入、接口模式调整 +* 提权级:原始发送、无线注入、接口模式调整 并在界面中明确显示当前会话权限状态。 @@ -410,13 +407,13 @@ Windows 下接口名称、描述、GUID、Npcap 名称可能不同。界面应 * 保存用户构建的包模板、字段覆盖、标签与说明。 -* ``CaptureProfile`` +* ``AnalysisProfile`` - * 保存接口、过滤器、会话类型、输出路径、列配置。 + * 保存导入来源、过滤器、最近文件、会话类型和列配置。 * ``TaskRecord`` - * 记录发送、抓包、分析任务的参数、状态、开始结束时间、错误信息。 + * 记录发送、导入、分析任务的参数、状态、开始结束时间、错误信息。 * ``WorkspaceDocument`` @@ -431,7 +428,7 @@ Windows 下接口名称、描述、GUID、Npcap 名称可能不同。界面应 由于 ``scapy/contrib/`` 能力很多,GUI 不能把所有工具写死在主程序里。建议: * 设计 ``ToolPlugin`` 接口,用于注册新工具页、新协议专题页、新模板集。 -* 内置插件覆盖高频能力:Packet Builder、Live Capture、Pcap Analyzer。 +* 内置插件覆盖高频能力:Packet Builder、Send Task、Pcap Analyzer。 * 扩展插件覆盖专题协议:Automotive、SMB、Kerberos、802.11、Bluetooth。 * 对 contrib 协议采用按需导入 + 能力检测,避免启动变慢和无谓依赖失败。 @@ -446,7 +443,7 @@ M1: 最小可用版本 * 接口浏览 * 包构建器 * ``send()`` / ``sendp()`` / ``sr1()`` 基础任务 -* ``AsyncSniffer`` 实时抓包 +* ``pcap`` / ``pcapng`` 导入与基础离线浏览 * 包详情树 + 十六进制视图 M2: 分析增强版本 @@ -481,18 +478,17 @@ M3: 高阶协议与自动化版本 * UI 交互测试 - * 对关键流程做 Qt 自动化测试,例如新建包、编辑字段、开始抓包、停止任务。 + * 对关键流程做 Qt 自动化测试,例如新建包、编辑字段、导入 ``pcap``、复制到构建器。 * 集成测试 - * 在具备 Npcap 的 Windows 环境验证接口发现、抓包、发送、pcap 导入导出。 + * 在具备 Npcap 的 Windows 环境验证接口发现、发送、``pcap`` 导入导出,以及 Wireshark 到 GUI 的导入链路。 对首版最关键的验收标准如下: * 用户能通过 GUI 构建常见多层数据包并发送。 -* 用户能选择接口并完成实时抓包。 -* 用户能打开 pcap 并浏览协议树与十六进制视图。 -* 用户能把抓到的包复制回构建器进行二次编辑。 +* 用户能打开 Wireshark 生成的 ``pcap`` 并浏览协议树与十六进制视图。 +* 用户能把离线分析中的包复制回构建器进行二次编辑。 后续实现建议 @@ -502,7 +498,7 @@ M3: 高阶协议与自动化版本 1. 先在仓库外或新增子项目中实现独立 GUI 包,避免直接侵入 Scapy 现有核心模块。 2. 第一阶段只封装稳定后端接口,不改动 Scapy 包解析与发送主逻辑。 -3. 先打通三条主链路:包构建、实时抓包、离线分析。 +3. 先打通两条主链路:包构建与发送、离线分析。 4. 完成插件注册机制后,再逐步纳入 contrib 协议工具页。 这样可以最大限度复用 Scapy 现有能力,同时避免 GUI 开发反向污染核心 library。 @@ -523,35 +519,84 @@ M3: 高阶协议与自动化版本 * 已识别 Windows 关键约束:Npcap / libpcap、管理员权限、接口能力差异、无线注入限制。 * 已在 Scapy 文档目录中加入本文档入口。 * 已完成一次 Sphinx 文档构建验证,确认本文档可被当前文档系统收录。 +* 已建立独立 GUI 子项目骨架:``gui/``。 +* 已建立最小运行入口、主窗口、运行时目录与依赖检查模块。 +* 已在当前开发环境安装 GUI 运行依赖,并完成一次真实 Qt 主窗口离屏创建验证。 +* 已完成第 3 步的第一部分:主窗口已接入接口浏览面板、接口刷新动作和选中详情联动。 +* 已把接口浏览改为后台线程加载,并补充加载中、失败态和更细的能力标签展示。 +* 已完成接口浏览成功路径与失败路径的离屏验证。 +* 已接入包构建器最小可用版,支持常见协议层添加、字段编辑、摘要预览、结构预览和十六进制预览。 +* 已增强包构建器,支持层上移/下移、字段搜索过滤以及 ``Raw.load`` 的多行专用编辑。 +* 已进一步增强包构建器,支持模板保存/加载和层拖拽重排。 +* 已为部分常见字段接入专用编辑控件,当前覆盖枚举下拉、单比特布尔开关和字节字段单行编辑。 +* 已进一步增强字段编辑体验,当前协议枚举支持搜索选择,IPv4、IPv6 和 MAC 地址字段支持专用输入与基础合法性校验。 +* 已把常见二层协议补入包构建器可选层,当前覆盖 ``Dot1Q``、``Dot1AD``、``MACControlPause`` 和 ``MACControlClassBasedFlowControl``。 +* 已为集合型字段补入专用编辑入口;当前 ``IP.options``、``DNS.qd/an/ns/ar`` 等字段会显示多行集合编辑入口,而不是只暴露单行文本框。 +* 已继续深化集合字段编辑器:当前普通字面量列表支持列表项增删改和顺序调整,``DNS.qd`` 支持按 question 子项编辑 ``qname``、``qtype`` 和 ``qclass``。 +* 已为 ``IP.options`` 接入协议感知模板编辑器;当前支持按模板添加 ``NOP``、``EOL``、``RR``、``LSRR``、``SSRR``、``Timestamp``、``Router Alert`` 和 ``Security``,并在提交时转换为真实 ``IPOption_*`` 对象。 +* 已接入发送任务面板,当前支持从包构建器载入当前数据包,执行 ``send()``、``sendp()`` 和 ``sr1()``,并展示请求包结构、十六进制预览、发送日志和应答详情。 +* 已确认并执行功能收缩:由于 GUI 在实时抓包场景下容易卡死、性能跟不上,当前已移除实时抓包页面,改为明确推荐使用 Wireshark 抓包后再导入 GUI 做离线分析。 +* 已接入离线分析工作台,当前支持打开 ``pcap`` / ``pcapng``、后台按上限读取、关键字过滤、包列表浏览、结构/十六进制详情,以及把离线数据包复制回包构建器。 +* 主窗口已完成包构建器、发送任务、离线分析三类核心页面的联动;接口列表会同步提供给发送任务页面,离线分析页也已打通“复制到包构建器”的回流链路。 +* 已接入最小自动化工具入口页和内置工具注册表;当前可以从统一入口跳转到包构建器、发送任务和离线分析页。 +* 已为发送任务、离线分析、导入构包和工具注册表补入针对性单元测试;当前 ``gui/tests/`` 下已有对应最小回归用例。 +* 已开始第 4 步的第一批后端抽象收敛:当前已新增 ``packet_studio/adapters`` 与 ``packet_studio/domain`` 目录起点。 +* 已把数据包摘要、结构预览、十六进制格式化和复制逻辑收敛到 ``ScapyPacketAdapter``,并让 ``send_task_service``、``packet_builder_service`` 依赖该适配层,而不是各自内联实现。 +* 已继续推进第 4 步的第二批抽象收敛:当前离线分析链路已切到统一记录模型,``PcapPacketRecord`` 会携带 ``preview``、``sourceText`` 和 ``protocolName`` 等展示语义。 +* 已新增 ``ScapyPcapAdapter``,用于把 pcap 读取结果收敛为领域记录;``offline_analysis_widget`` 当前已改为消费服务返回的记录对象,而不是在 UI 内再次调用 ``summary()``、``show()`` 和手写 hexdump 逻辑。 +* 已继续推进第 4 步的第三批抽象收敛:当前 ``SendTaskResult``、``PcapLoadResult`` 与 ``TaskError`` 已统一沉淀到 ``domain/task_models.py``,不再分散定义在各个 service 模块里。 +* 发送任务与离线分析 worker 的失败通道当前已开始传递 ``TaskError``,成功结果也会携带统一的 ``summaryText`` / ``logText``;这使 widget 逐步转为消费领域结果对象,而不是自行拼装摘要文案。 +* 已继续推进第 4 步的第四批抽象收敛:当前已新增统一生命周期状态模型 ``TaskPhase`` / ``TaskState``,并开始用于发送任务与离线分析两条主链路的状态表达。 +* 当前 ``send_task_widget`` 和 ``offline_analysis_widget`` 已开始通过共享 ``TaskState`` 更新状态标签;这使运行中、成功、失败等状态不再完全依赖各页面散落的硬编码字符串。 +* 已继续推进第 4 步的第五批抽象收敛:当前已新增更高层领域对象 ``TaskRecord``、``WorkspacePanelSnapshot`` 和 ``WorkspaceDocument``,并补入 ``WorkspaceDocumentService`` 负责构建工作区快照与任务记录。 +* 主窗口当前已开始实际维护 ``WorkspaceDocument`` 和最近任务记录:各核心面板会提供工作区快照,主窗口会把状态消息沉淀为 ``TaskRecord``,并在欢迎页展示当前工作区概览,而不是只保留零散日志文本。 当前结论 -------- * GUI 可以作为独立应用层实现,Scapy 继续作为底层 library。 -* 首版应聚焦三条主链路,而不是同时铺开所有专题协议页面: +* 按当前实现复核,第 3 步已经打通新的 M1 核心闭环:用户已经可以完成包构建与发送、导入 Wireshark 生成的 ``pcap`` 做离线分析,并把离线数据包回流到包构建器继续编辑。 +* 针对第 3 步当前实现的最小回归测试已通过;从代码与测试两侧复核,更准确的判断应是“M1 功能项已具备,后续进入增强、抽象与验收阶段”,而不是继续把第 3 步表述为仅在离线分析加入后才算完成。 +* 首版应聚焦两条主链路,而不是同时铺开所有专题协议页面: * 包构建与发送 - * 实时抓包 * 离线 pcap 分析 * ``contrib``、``AnsweringMachine``、``Automaton`` 等能力应纳入总体架构,但不应阻塞 M1。 +* 当前未完成项已从“主链路是否可用”转为“主链路如何增强和工程化”,后续重点应转向第 4 步和第 5 步,而不是继续把第 3 步表述为未完成。 +* 按当前实现复核,第 4 步已经从“可开始”进入“主体已落地”的阶段:``adapters``、``domain``、统一任务结果 / 错误模型、统一任务生命周期状态、``WorkspaceDocumentService`` 与最小 ``ToolRegistryService`` 均已存在,且对应聚焦单元测试当前已通过。 +* 但第 4 步暂不宜直接标记为“已完成关闭”。当前仍存在两类明显收尾项:其一,包预览抽象尚未彻底收口,``packet_builder_widget`` 仍在 UI 层直接调用 ``summary()``、``show()`` 和手写十六进制格式化,``send_task_widget`` 仍直接访问 ``sendTaskService._packetAdapter`` 私有成员;其二,插件注册仍停留在最小内置注册表,尚未形成真正的插件接口、生命周期和装配约束。 +* 因此,更准确的结论应是:第 4 步主体能力已经具备,当前进入收尾与验收阶段;下一步重点不是再证明抽象是否必要,而是把剩余 UI 泄漏边界收口,并决定哪些约束必须达到后才把第 4 步正式关闭。 +* 在这个前提下,第 5 步已经具备启动条件,而且适合立即开始;当前最缺的不是新的抽象目录,而是 GUI 冒烟测试、真实 Windows 发送验证,以及 Wireshark 到 GUI 的导入链路验证,用这些验证结果反向暴露第 4 步剩余的边界问题。 当前未完成事项 -------------- -* 尚未建立 GUI 子项目目录与运行入口。 -* 尚未定义 PySide6 具体页面结构、Qt Model 设计与信号通信接口。 -* 尚未把 Scapy 后端能力封装成稳定的 GUI service / adapter 层。 -* 尚未建立用于 Windows 的依赖检查、权限检查与 Npcap 诊断模块。 -* 尚未完成任何界面原型、交互稿或可运行演示。 +* 当前仓库中虽然已经建立 ``gui/`` 子项目、运行入口和核心页面,但尚未补“实现规格文档”,页面结构图、模块职责表和更明确的 Qt Model / signal-slot 边界仍未固化为文档。 +* 当前 ``service``、``adapter`` 与 ``domain`` 三层已经形成可运行骨架,``TaskRecord``、``WorkspaceDocument`` 等领域模型也已实际接入主窗口;但配置对象、工程保存格式和更完整的领域边界仍未独立抽象。 +* 当前包预览抽象尚未完全收口:``packet_builder_service`` 虽已提供统一摘要、结构和十六进制接口,但 ``packet_builder_widget`` 仍在 UI 内直接消费 Scapy ``Packet`` 预览;``send_task_widget`` 也仍通过 ``sendTaskService._packetAdapter`` 访问私有实现,说明第 4 步还有最后一段 UI/服务边界需要收束。 +* 当前包构建器仍只覆盖固定常见层,尚未支持协议自动发现、字段分组和更复杂的专用编辑器。 +* 当前接口浏览已经具备后台加载和失败态展示,但尚未加入取消、超时与更完整的恢复策略。 +* 当前接口浏览仍未加入分页、搜索、用户可控排序与更丰富的接口诊断信息。 +* 当前发送任务和离线分析已可交互使用,但仍缺少更高阶的结果视图,例如未应答列表、会话聚合、自定义列、颜色规则、统计面板和导出筛选结果。 +* 当前离线分析虽然已采用 ``PcapReader`` 按上限流式读取,但仍未提供真正的懒加载分页、超大文件持续浏览和更细粒度的读取进度反馈。 +* 当前包构建器已经支持模板持久化、拖拽重排、可搜索枚举以及一部分字段专用控件;集合字段方面已经覆盖 ``IP.options`` 模板编辑、普通字面量列表和 ``DNS.qd`` question 列表,但仍未支持更复杂的 ``PacketListField`` / 嵌套结构控件与模板库管理。 +* 当前自动化工具页仍只是最小入口页与内置注册表,尚未实现真正的插件发现、插件生命周期管理和专题工具装配。 +* 当前测试已覆盖 ``ScapyPacketAdapter``、``WorkspaceDocumentService``、``packet_builder_service`` 导入链路,以及发送、离线分析、工具注册表等聚焦单元测试;但仍缺少关键 GUI 交互冒烟测试,以及真实 Windows 环境下的接口发现、发送、Wireshark 抓包文件导入链路验证。 +* 当前仓库中未发现 IEC 61850 ``GOOSE`` / ``SV`` 的现成协议实现文件或已注册层,因此 GUI 当前不能提供专用的 GOOSE / SV 构建器页面;这部分前置依赖是后端协议层实现,而不是单纯的 UI 缺口。 当前已知阻塞与注意事项 ---------------------- * 文档构建时,仓库自带的 ``scapy_doc`` 扩展会尝试调用 ``tox -e apitree``。 * 在当前本地环境中,如未设置 ``SCAPY_APITREE=0``,Sphinx 构建可能因缺少 ``tox`` 而失败。 -* 仓库当前还存在一个与本文档无关的既有 warning:``doc/scapy/index.rst`` 中引用了不存在的 - ``api/scapy`` 页面。 +* 当前 GUI 子项目使用 ``PySide6-Essentials`` 即可满足骨架阶段需求,无需完整 ``PySide6`` addons。 +* 在当前 Windows 离屏验证中,Qt 输出了字体目录 warning;这不会阻塞骨架开发,但后续打包阶段需要 + 明确字体部署策略。 +* Scapy 接口发现依赖完整初始化链;当前实现需要先触发 ``scapy.all`` 导入,再调用 + ``get_working_ifaces()`` 才能稳定获得接口列表。 +* 原始发送和部分接口能力在 Windows 下仍直接受 Npcap、管理员权限、网卡驱动和接口模式限制;如果用户在同机配合 Wireshark 抓包,也同样受该链路约束;当前单元测试不能替代真实环境验收。 +* 当前第 4 步已具备主体实现;后续收尾时仍需避免在现有 ``service`` 之上再叠一层只做转发的薄封装,而应优先消化已经暴露出的 UI / 服务边界泄漏,例如包预览逻辑重复和对私有适配器成员的直接访问。 后续步骤规划 @@ -585,19 +630,18 @@ M3: 高阶协议与自动化版本 * 基础主窗口 * 项目目录骨架 -第 3 步:打通 M1 三条主链路 ------------------------------ +第 3 步:打通 M1 核心闭环并建立离线分析 +--------------------------------------- 优先顺序如下: 1. 接口浏览与依赖诊断 2. 包构建器 3. 发送 / ``sr1()`` 任务 -4. ``AsyncSniffer`` 实时抓包 -5. 抓包详情树与十六进制视图 -6. pcap 导入与基础离线浏览 +4. ``pcap`` 导入与基础离线浏览 +5. 包详情树与十六进制视图 -这一阶段的目标不是把界面做全,而是尽快得到一个“能构包、能发包、能抓包、能看包”的闭环。 +这一阶段的目标不是把界面做全,而是尽快得到一个“能构包、能发包、能导入 Wireshark 抓包文件、能看包”的闭环。 第 4 步:建立可扩展后端抽象 --------------------------- @@ -606,19 +650,24 @@ M3: 高阶协议与自动化版本 * 统一错误处理、任务生命周期、日志格式和结果模型。 * 为后续插件机制预留注册接口。 +当前评估:主体实现已经落地,但暂不建议直接关闭。原因是稳定的 ``adapter`` / ``domain`` / 结果模型边界已经存在,且聚焦单元测试已能覆盖其核心路径;不过 UI 层仍残留少量预览逻辑和私有适配器访问,插件注册也尚未演进到真正的插件契约,因此更准确的状态应是“第 4 步进入收尾与验收阶段”。 + 交付物: -* ``capture_service`` * ``packet_builder_service`` * ``interface_service`` * ``pcap_analysis_service`` +* ``send_task_service`` 与统一的任务结果 / 错误模型 +* 最小 ``tool_registry_service`` 到插件注册接口的演进方案 第 5 步:补测试与 Windows 验证 ------------------------------ * 为纯 Python 领域对象和 adapter 层补单元测试。 * 为最关键 GUI 流程补冒烟测试。 -* 在真实 Windows + Npcap 环境下验证接口发现、发送、抓包、pcap 导出。 +* 在真实 Windows 环境下验证接口发现、发送,以及 Wireshark 抓包文件导入链路。 + +当前评估:可以开始,并且建议立即开始。原因是第 5 步的第一部分实际上已经起步,当前仓库中已经存在多组针对 adapter / domain / service 的聚焦单元测试;接下来最有价值的是补 GUI 冒烟测试与真实 Windows 集成验证,用验证结果决定第 4 步何时可以正式关闭。 交付物: @@ -637,11 +686,8 @@ M3: 高阶协议与自动化版本 如果下一轮直接开始编码,建议采用下面的短周期顺序: -1. 建立 GUI 子项目骨架。 -2. 完成主窗口与接口浏览面板。 -3. 完成包构建器最小可用版。 -4. 接入 ``send()`` / ``sr1()``。 -5. 接入 ``AsyncSniffer``。 -6. 接入 pcap 文件打开与详情浏览。 - -达到以上六步后,再决定是否立即进入 M2,还是先做界面打磨与稳定性修复。 \ No newline at end of file +1. 为接口浏览补取消、超时、排序、搜索与更完整的恢复策略。 +2. 为发送任务和离线分析补 GUI 冒烟测试,并整理一套真实 Windows 发送验证与 Wireshark 导入验证脚本。 +3. 把当前 ``service`` 进一步沉淀为更明确的 ``adapter`` / ``domain`` 分层,补齐 ``TaskRecord``、``AnalysisProfile``、``WorkspaceDocument`` 等对象。 +4. 继续增强包构建器的协议发现、字段分组和复杂字段编辑器。 +5. 在主链路稳定后,再决定是先推进 M2 的分析增强,还是先补插件注册与自动化工具框架。 \ No newline at end of file diff --git a/gui/README.rst b/gui/README.rst new file mode 100644 index 00000000000..4a0bdd73c76 --- /dev/null +++ b/gui/README.rst @@ -0,0 +1,50 @@ +Scapy Studio GUI +================ + +这是 Scapy 图形化应用的独立子项目骨架。 + +当前阶段目标: + +* 保持与主仓库解耦 +* 提供最小可运行的 PySide6 启动入口 +* 建立主窗口、运行时目录和依赖检查基础设施 + +运行方式:: + + python -m packet_studio + +Windows 验证脚本:: + + powershell -ExecutionPolicy Bypass -File scripts\windows_npcap_validation.ps1 -SkipManualChecklist + +另一台 Windows 电脑如何测试 +--------------------------- + +有两种方式: + +1. 源码方式 + + * 拷贝整个仓库,至少保留 ``scapy/`` 根目录和 ``gui/`` 子项目。 + * 目标机需要安装 Python 3.9+。 + * 安装 GUI 依赖:``python -m pip install -e .``。 + * 如需 ``sendp`` 或更完整的网卡发现,目标机还需要安装 Npcap;部分功能建议管理员权限运行。 + * 然后在 ``gui/`` 目录执行现有脚本:``powershell -ExecutionPolicy Bypass -File scripts\windows_npcap_validation.ps1``。 + +2. 打包方式 + + * 正式发布时,仅发布 ``onedir`` 版本,双击后会直接打开 Scapy Studio 主界面。 + * 先在开发机安装 PyInstaller:``python -m pip install pyinstaller``。 + * 在 ``gui/`` 目录执行:``powershell -ExecutionPolicy Bypass -File scripts\build_windows_validation_exe.ps1``。 + * 默认生成 ``onedir`` 版本,稳定性更高;产物目录为 ``dist\ScapyStudio``,主程序为 ``ScapyStudio.exe``。 + * 目标机仍建议安装 Npcap;若要做 ``sendp`` 或接口相关验证,通常仍建议管理员权限运行。 + +Windows 发布打包:: + + powershell -ExecutionPolicy Bypass -File scripts\build_windows_release_package.ps1 + +也可以直接双击 ``build_release_package.bat`` 重新打包并生成 zip 发布包。该发布包会包含 ``ScapyStudio`` 目录和安装说明 ``ScapyStudio-Windows-Guide.txt``。 + +说明: + +* 仅拷贝 ``windows_npcap_validation.ps1`` 到另一台机器通常不够,因为它依赖 Python、Scapy、PySide6 以及仓库源码结构。 +* 本次正式发布仅支持 ``onedir`` 目录形式,不提供单文件 exe。 \ No newline at end of file diff --git a/gui/ScapyStudio.spec b/gui/ScapyStudio.spec new file mode 100644 index 00000000000..c73692a614f --- /dev/null +++ b/gui/ScapyStudio.spec @@ -0,0 +1,51 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_all + +datas = [] +binaries = [] +hiddenimports = ['scapy.all'] +tmp_ret = collect_all('PySide6') +datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] + + +a = Analysis( + ['src\\packet_studio\\__main__.py'], + pathex=['src', '..'], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='ScapyStudio', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='ScapyStudio', +) diff --git a/gui/build_release_package.bat b/gui/build_release_package.bat new file mode 100644 index 00000000000..a633bb0024d --- /dev/null +++ b/gui/build_release_package.bat @@ -0,0 +1,21 @@ +@echo off +setlocal + +cd /d "%~dp0" + +set "NO_PAUSE=" +if /I "%~1"=="--no-pause" set "NO_PAUSE=1" + +powershell -ExecutionPolicy Bypass -File ".\scripts\build_windows_release_package.ps1" +set "EXIT_CODE=%ERRORLEVEL%" + +echo. +if not "%EXIT_CODE%"=="0" ( + echo Build failed with exit code %EXIT_CODE%. +) else ( + echo Release package completed successfully. +) + +if not defined NO_PAUSE pause + +exit /b %EXIT_CODE% \ No newline at end of file diff --git a/gui/pyproject.toml b/gui/pyproject.toml new file mode 100644 index 00000000000..ae40dc0bbce --- /dev/null +++ b/gui/pyproject.toml @@ -0,0 +1,25 @@ +[build-system] +requires = ["setuptools>=68.0.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "scapy-studio-gui" +version = "0.1.0" +description = "Scapy 的图形化工作台骨架" +readme = "README.rst" +requires-python = ">=3.9" +dependencies = [ + "PySide6-Essentials>=6.7,<7", + "scapy", +] + +[project.scripts] +packet-studio = "packet_studio.__main__:main" +packet-studio-validation = "packet_studio.validation_launcher:main" + +[tool.setuptools.package-dir] +"" = "src" + +[tool.setuptools.packages.find] +where = ["src"] +include = ["packet_studio*"] \ No newline at end of file diff --git a/gui/scripts/build_windows_release_package.ps1 b/gui/scripts/build_windows_release_package.ps1 new file mode 100644 index 00000000000..da9cec79436 --- /dev/null +++ b/gui/scripts/build_windows_release_package.ps1 @@ -0,0 +1,82 @@ +param( + [string]$PythonExe = "python" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$guiRoot = Split-Path -Parent $scriptDir +$pyprojectPath = Join-Path $guiRoot "pyproject.toml" +$releaseDir = Join-Path $guiRoot "release" +$releaseNoteSource = Join-Path $guiRoot "release\ScapyStudio-Windows-Guide.txt" +$packageStageDir = Join-Path $releaseDir "ScapyStudio-package" + +function Get-PackageVersion +{ + param( + [string]$ProjectFilePath + ) + + $versionLine = Select-String -Path $ProjectFilePath -Pattern '^version = "([^"]+)"$' | Select-Object -First 1 + if (-not $versionLine) + { + throw "Failed to read version from $ProjectFilePath" + } + + return $versionLine.Matches[0].Groups[1].Value +} + +Push-Location $guiRoot +try +{ + $version = Get-PackageVersion -ProjectFilePath $pyprojectPath + + & (Join-Path $scriptDir "build_windows_validation_exe.ps1") -PythonExe $PythonExe + if ($LASTEXITCODE -ne 0) + { + throw "Executable build failed." + } + + if (-not (Test-Path $releaseDir)) + { + New-Item -ItemType Directory -Path $releaseDir | Out-Null + } + + $zipPath = Join-Path $releaseDir ("ScapyStudio-{0}-windows-x64.zip" -f $version) + $hashPath = "$zipPath.sha256.txt" + + foreach ($path in @($zipPath, $hashPath, $packageStageDir)) + { + if (Test-Path $path) + { + Remove-Item $path -Recurse -Force + } + } + + New-Item -ItemType Directory -Path $packageStageDir | Out-Null + Copy-Item -Path ".\dist\ScapyStudio" -Destination $packageStageDir -Recurse + Copy-Item -Path $releaseNoteSource -Destination $packageStageDir + + Compress-Archive -Path "$packageStageDir\*" -DestinationPath $zipPath -CompressionLevel Optimal + + $hash = (Get-FileHash $zipPath -Algorithm SHA256).Hash + @( + "SHA256 $(Split-Path -Leaf $zipPath)", + $hash + ) | Set-Content -Path $hashPath -Encoding utf8 + + Write-Host "" + Write-Host "Release package created: $zipPath" + Write-Host "SHA256 file created: $hashPath" + Write-Host "Bundled guide file: $(Join-Path $packageStageDir (Split-Path -Leaf $releaseNoteSource))" + Write-Host "SHA256: $hash" +} +finally +{ + if (Test-Path $packageStageDir) + { + Remove-Item $packageStageDir -Recurse -Force + } + Pop-Location +} \ No newline at end of file diff --git a/gui/scripts/build_windows_validation_exe.ps1 b/gui/scripts/build_windows_validation_exe.ps1 new file mode 100644 index 00000000000..3c0cf54ad32 --- /dev/null +++ b/gui/scripts/build_windows_validation_exe.ps1 @@ -0,0 +1,71 @@ +param( + [string]$PythonExe = "python", + [switch]$OneFile +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$guiRoot = Split-Path -Parent $scriptDir + +Push-Location $guiRoot +try +{ + & $PythonExe -m PyInstaller --version 2>$null | Out-Null + if ($LASTEXITCODE -ne 0) + { + throw "PyInstaller not found. Run: $PythonExe -m pip install pyinstaller" + } + + foreach ($legacyPath in @( + "$guiRoot\dist\ScapyStudioValidation", + "$guiRoot\build\ScapyStudioValidation", + "$guiRoot\ScapyStudioValidation.spec" + )) + { + if (Test-Path $legacyPath) + { + try + { + Remove-Item $legacyPath -Recurse -Force + } + catch + { + Write-Warning "Failed to remove legacy artifact: $legacyPath" + } + } + } + + $distMode = if ($OneFile) { "--onefile" } else { "--onedir" } + & $PythonExe -m PyInstaller ` + --noconfirm ` + --clean ` + $distMode ` + --windowed ` + --name ScapyStudio ` + --paths src ` + --paths .. ` + --collect-all PySide6 ` + --hidden-import scapy.all ` + src\packet_studio\__main__.py + if ($LASTEXITCODE -ne 0) + { + throw "PyInstaller build failed with exit code $LASTEXITCODE" + } + + Write-Host "" + Write-Host "Build completed. Output path: $guiRoot\dist\ScapyStudio" + if ($OneFile) + { + Write-Host "Single-file exe: $guiRoot\dist\ScapyStudio.exe" + } + else + { + Write-Host "Distribute the whole directory and launch ScapyStudio.exe." + } +} +finally +{ + Pop-Location +} \ No newline at end of file diff --git a/gui/scripts/windows_npcap_validation.ps1 b/gui/scripts/windows_npcap_validation.ps1 new file mode 100644 index 00000000000..c693a06713e --- /dev/null +++ b/gui/scripts/windows_npcap_validation.ps1 @@ -0,0 +1,110 @@ +param( + [string]$PythonExe = "python", + [switch]$SkipAutomatedTests, + [switch]$SkipGuiSmokeTests, + [switch]$SkipManualChecklist +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Write-Step +{ + param( + [string]$Title + ) + + Write-Host "" + Write-Host "== $Title ==" +} + +function Test-IsAdministrator +{ + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal]::new($identity) + return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +function Get-NpcapStatus +{ + $service = Get-Service -Name "npcap" -ErrorAction SilentlyContinue + if ($null -ne $service) + { + return "installed, service status: $($service.Status)" + } + + $registryPaths = @( + "HKLM:\SOFTWARE\Npcap", + "HKLM:\SOFTWARE\WOW6432Node\Npcap" + ) + foreach ($path in $registryPaths) + { + if (Test-Path $path) + { + return "installed, registry key found" + } + } + + return "not detected" +} + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$guiRoot = Split-Path -Parent $scriptDir + +Push-Location $guiRoot +try +{ + $env:PYTHONPATH = "src;.." + + Write-Step "1. Check prerequisites" + $pythonVersion = & $PythonExe --version + Write-Host "Python: $pythonVersion" + Write-Host "Administrator: $(if (Test-IsAdministrator) { 'yes' } else { 'no' })" + Write-Host "Npcap: $(Get-NpcapStatus)" + Write-Host "Working directory: $guiRoot" + + if (-not $SkipAutomatedTests) + { + Write-Step "2. Run focused automated tests" + $automatedTests = @( + "tests.test_scapy_packet_adapter", + "tests.test_send_task_service", + "tests.test_pcap_analysis_service", + "tests.test_tool_registry_service", + "tests.test_workspace_document_service" + ) + & $PythonExe -m unittest @automatedTests + } + + if (-not $SkipGuiSmokeTests) + { + Write-Step "3. Run offscreen GUI smoke tests" + $env:QT_QPA_PLATFORM = "offscreen" + & $PythonExe -m unittest tests.test_gui_smoke + Remove-Item Env:QT_QPA_PLATFORM -ErrorAction SilentlyContinue + } + + if (-not $SkipManualChecklist) + { + Write-Step "4. Launch GUI for manual validation" + $process = Start-Process -FilePath $PythonExe -ArgumentList "-m", "packet_studio" -WorkingDirectory $guiRoot -PassThru + Write-Host "Manual checklist:" + Write-Host "1. Verify dependency summary, log directory and environment text on the welcome page." + Write-Host "2. Open the interfaces page, refresh the list, and record whether the target adapter is discovered." + Write-Host "3. In packet builder, add IP/ICMP or Ether/ARP and verify summary, structure and hex preview update together." + Write-Host "4. In send task, load the current packet from builder and verify send, sendp or sr1 basic execution paths." + Write-Host "5. In offline analysis, open a pcap or pcapng and verify list, search, details and copy back to builder." + Write-Host "6. On a real Windows plus Npcap host, record admin status, Npcap version, adapter model and any failure symptom." + Write-Host "" + Write-Host "Close the GUI window, then return here and press Enter to finish the script." + Read-Host | Out-Null + if (-not $process.HasExited) + { + Write-Warning "GUI process is still running. Close it manually if needed." + } + } +} +finally +{ + Pop-Location +} diff --git a/gui/src/packet_studio/__init__.py b/gui/src/packet_studio/__init__.py new file mode 100644 index 00000000000..24b122af71a --- /dev/null +++ b/gui/src/packet_studio/__init__.py @@ -0,0 +1,5 @@ +"""Scapy Studio GUI package.""" + +__all__ = ["__version__"] + +__version__ = "0.1.0" \ No newline at end of file diff --git a/gui/src/packet_studio/__main__.py b/gui/src/packet_studio/__main__.py new file mode 100644 index 00000000000..4f627d46239 --- /dev/null +++ b/gui/src/packet_studio/__main__.py @@ -0,0 +1,5 @@ +from packet_studio.app import main + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/gui/src/packet_studio/adapters/__init__.py b/gui/src/packet_studio/adapters/__init__.py new file mode 100644 index 00000000000..6b512290ea6 --- /dev/null +++ b/gui/src/packet_studio/adapters/__init__.py @@ -0,0 +1,4 @@ +from packet_studio.adapters.scapy_packet_adapter import ScapyPacketAdapter +from packet_studio.adapters.scapy_pcap_adapter import ScapyPcapAdapter + +__all__ = ["ScapyPacketAdapter", "ScapyPcapAdapter"] \ No newline at end of file diff --git a/gui/src/packet_studio/adapters/scapy_packet_adapter.py b/gui/src/packet_studio/adapters/scapy_packet_adapter.py new file mode 100644 index 00000000000..235f5910ae8 --- /dev/null +++ b/gui/src/packet_studio/adapters/scapy_packet_adapter.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from typing import Any + +from packet_studio.domain.packet_models import CapturedPacketRecord, PacketPreview + + +class ScapyPacketAdapter: + """提供 Packet 预览和复制的稳定适配接口。""" + + def clonePacket(self, packet: Any | None) -> Any | None: + if packet is None: + return None + return packet.copy() + + def buildPreview(self, packet: Any) -> PacketPreview: + return PacketPreview( + summary=self.buildSummary(packet), + structure=self.buildStructureDump(packet), + hexdump=self.buildHexdump(packet), + ) + + def buildCapturedRecord(self, packet: Any) -> CapturedPacketRecord: + packetCopy = self.clonePacket(packet) + return CapturedPacketRecord( + packet=packetCopy, + sourceText=self.buildSourceText(packet), + protocolName=self.buildPrimaryProtocolName(packet), + preview=self.buildPreview(packetCopy), + ) + + def buildSummary(self, packet: Any) -> str: + return packet.summary() + + def buildSourceText(self, packet: Any) -> str: + return str(getattr(packet, "sniffed_on", "") or "") + + def buildPrimaryProtocolName(self, packet: Any) -> str: + for protocolName in ["ARP", "DNS", "ICMP", "ICMPv6Unknown", "TCP", "UDP", "IPv6", "IP", "Raw"]: + if packet.haslayer(protocolName): + return protocolName + return packet.__class__.__name__ + + def buildStructureDump(self, packet: Any) -> str: + return packet.show(dump=True) + + def buildHexdump(self, packet: Any) -> str: + payload = bytes(packet) + if not payload: + return "" + + lines = [] + for offset in range(0, len(payload), 16): + chunk = payload[offset:offset + 16] + hexPart = " ".join(f"{byte:02x}" for byte in chunk) + asciiPart = "".join(chr(byte) if 32 <= byte <= 126 else "." for byte in chunk) + lines.append(f"{offset:04x} {hexPart:<47} {asciiPart}") + return "\n".join(lines) \ No newline at end of file diff --git a/gui/src/packet_studio/adapters/scapy_pcap_adapter.py b/gui/src/packet_studio/adapters/scapy_pcap_adapter.py new file mode 100644 index 00000000000..8897d73eb00 --- /dev/null +++ b/gui/src/packet_studio/adapters/scapy_pcap_adapter.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from packet_studio.adapters.scapy_packet_adapter import ScapyPacketAdapter +from packet_studio.domain.packet_models import PcapPacketRecord + + +class ScapyPcapAdapter: + """提供 pcap 记录到领域模型的稳定适配接口。""" + + def __init__(self, packetAdapter: ScapyPacketAdapter | None = None) -> None: + self._packetAdapter = packetAdapter or ScapyPacketAdapter() + + def buildPacketRecord(self, index: int, packet: Any) -> PcapPacketRecord: + packetCopy = self._packetAdapter.clonePacket(packet) + return PcapPacketRecord( + index=index, + timestampText=self.formatTimestamp(getattr(packet, "time", None)), + sourceText=self._packetAdapter.buildSourceText(packet), + protocolName=self._packetAdapter.buildPrimaryProtocolName(packet), + preview=self._packetAdapter.buildPreview(packetCopy), + packet=packetCopy, + ) + + def formatTimestamp(self, rawTime: Any) -> str: + if rawTime is None: + return "" + + try: + timestamp = float(rawTime) + except Exception: + return str(rawTime) + + try: + return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] + except Exception: + return str(rawTime) \ No newline at end of file diff --git a/gui/src/packet_studio/app.py b/gui/src/packet_studio/app.py new file mode 100644 index 00000000000..eb5b3ca7583 --- /dev/null +++ b/gui/src/packet_studio/app.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import logging +import sys +from pathlib import Path +from typing import Sequence +from packet_studio.runtime.dependency_check import collect_environment +from packet_studio.runtime.paths import ensure_runtime_directories + + +def configure_logging(log_dir: Path) -> Path: + """初始化最小日志输出。""" + log_file = log_dir / "packet_studio.log" + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(name)s %(message)s", + handlers=[ + logging.FileHandler(log_file, encoding="utf-8"), + logging.StreamHandler(sys.stdout), + ], + ) + return log_file + + +def main(argv: Sequence[str] | None = None) -> int: + """启动 GUI 应用。""" + try: + from PySide6 import QtWidgets + except ModuleNotFoundError: + print( + "未安装 PySide6,无法启动 Scapy Studio。\n" + "请先在 gui 子项目环境中执行: pip install -e .", + file=sys.stderr, + ) + return 1 + + from packet_studio.main_window import MainWindow + + runtime_dirs = ensure_runtime_directories() + log_file = configure_logging(runtime_dirs.log_dir) + logging.getLogger(__name__).info("日志文件: %s", log_file) + + app_argv = list(argv) if argv is not None else sys.argv + application = QtWidgets.QApplication(app_argv) + application.setApplicationName("Scapy Studio") + application.setOrganizationName("Scapy Studio") + + environment = collect_environment(runtime_dirs) + window = MainWindow(environment=environment, log_file=log_file) + window.show() + return application.exec() \ No newline at end of file diff --git a/gui/src/packet_studio/domain/__init__.py b/gui/src/packet_studio/domain/__init__.py new file mode 100644 index 00000000000..011cc05a880 --- /dev/null +++ b/gui/src/packet_studio/domain/__init__.py @@ -0,0 +1,18 @@ +from packet_studio.domain.packet_models import CapturedPacketRecord, PacketPreview, PcapPacketRecord +from packet_studio.domain.task_models import CaptureStopResult, PcapLoadResult, SendTaskResult, TaskError, TaskPhase, TaskState +from packet_studio.domain.workspace_models import TaskRecord, WorkspaceDocument, WorkspacePanelSnapshot + +__all__ = [ + "PacketPreview", + "CapturedPacketRecord", + "PcapPacketRecord", + "TaskError", + "SendTaskResult", + "CaptureStopResult", + "PcapLoadResult", + "TaskPhase", + "TaskState", + "TaskRecord", + "WorkspacePanelSnapshot", + "WorkspaceDocument", +] \ No newline at end of file diff --git a/gui/src/packet_studio/domain/packet_models.py b/gui/src/packet_studio/domain/packet_models.py new file mode 100644 index 00000000000..009bf822403 --- /dev/null +++ b/gui/src/packet_studio/domain/packet_models.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + + +@dataclass(frozen=True) +class PacketPreview: + summary: str + structure: str + hexdump: str + + +@dataclass(frozen=True) +class CapturedPacketRecord: + packet: Any + sourceText: str + protocolName: str + preview: PacketPreview + + @property + def summary(self) -> str: + return self.preview.summary + + +@dataclass(frozen=True) +class PcapPacketRecord: + index: int + timestampText: str + sourceText: str + protocolName: str + preview: PacketPreview + packet: Any + + @property + def summary(self) -> str: + return self.preview.summary \ No newline at end of file diff --git a/gui/src/packet_studio/domain/task_models.py b/gui/src/packet_studio/domain/task_models.py new file mode 100644 index 00000000000..2042a5d8c98 --- /dev/null +++ b/gui/src/packet_studio/domain/task_models.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Optional + +from packet_studio.domain.packet_models import PacketPreview, PcapPacketRecord + + +class TaskPhase(str, Enum): + IDLE = "idle" + RUNNING = "running" + SUCCEEDED = "succeeded" + FAILED = "failed" + PAUSED = "paused" + STOPPED = "stopped" + + +@dataclass(frozen=True) +class TaskState: + phase: TaskPhase + statusText: str + + @classmethod + def idle(cls, statusText: str) -> "TaskState": + return cls(TaskPhase.IDLE, statusText) + + @classmethod + def running(cls, statusText: str) -> "TaskState": + return cls(TaskPhase.RUNNING, statusText) + + @classmethod + def succeeded(cls, statusText: str) -> "TaskState": + return cls(TaskPhase.SUCCEEDED, statusText) + + @classmethod + def failed(cls, statusText: str) -> "TaskState": + return cls(TaskPhase.FAILED, statusText) + + @classmethod + def paused(cls, statusText: str) -> "TaskState": + return cls(TaskPhase.PAUSED, statusText) + + @classmethod + def stopped(cls, statusText: str) -> "TaskState": + return cls(TaskPhase.STOPPED, statusText) + + +@dataclass(frozen=True) +class TaskError: + message: str + logText: str = "" + state: TaskState = TaskState.failed("任务失败。") + + @property + def summaryText(self) -> str: + return self.message + + +@dataclass(frozen=True) +class SendTaskResult: + mode: str + sentCount: int + packetPreview: PacketPreview + answerPreview: Optional[PacketPreview] + unansweredCount: int + summaryText: str + logText: str + state: TaskState = TaskState.succeeded("发送任务执行完成。") + + +@dataclass(frozen=True) +class CaptureStopResult: + capturedCount: int + storedResultCount: int + summaryText: str + logText: str = "" + state: TaskState = TaskState.stopped("抓包已停止。") + + +@dataclass(frozen=True) +class PcapLoadResult: + filePath: str + packetRecords: list[PcapPacketRecord] + summaryText: str + logText: str = "" + state: TaskState = TaskState.succeeded("离线抓包文件加载完成。") + + @property + def packetCount(self) -> int: + return len(self.packetRecords) \ No newline at end of file diff --git a/gui/src/packet_studio/domain/workspace_models.py b/gui/src/packet_studio/domain/workspace_models.py new file mode 100644 index 00000000000..4d8af8de1d7 --- /dev/null +++ b/gui/src/packet_studio/domain/workspace_models.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from packet_studio.domain.task_models import TaskPhase, TaskState + + +@dataclass(frozen=True) +class TaskRecord: + sequenceNumber: int + sourceTitle: str + message: str + phase: TaskPhase + detailText: str = "" + + +@dataclass(frozen=True) +class WorkspacePanelSnapshot: + panelId: str + title: str + taskState: TaskState + itemCount: int = 0 + detailText: str = "" + + +@dataclass(frozen=True) +class WorkspaceDocument: + activeTabTitle: str + openTabTitles: list[str] + panelSnapshots: list[WorkspacePanelSnapshot] + taskRecords: list[TaskRecord] + interfaceCount: int + interfaceSummaryText: str + + @property + def taskCount(self) -> int: + return len(self.taskRecords) + + def to_multiline_text(self) -> str: + lines = [ + "工作区状态概览", + "", + f"当前页签: {self.activeTabTitle}", + f"已打开页签数: {len(self.openTabTitles)}", + f"接口数量: {self.interfaceCount}", + f"接口摘要: {self.interfaceSummaryText}", + f"任务记录数: {self.taskCount}", + ] + + if self.taskRecords: + latestTask = self.taskRecords[-1] + lines.append( + f"最近任务: #{latestTask.sequenceNumber} {latestTask.sourceTitle} / {latestTask.phase.value} / {latestTask.message}" + ) + + lines.append("") + lines.append("面板快照") + for snapshot in self.panelSnapshots: + detailText = f" / {snapshot.detailText}" if snapshot.detailText else "" + lines.append( + f"- {snapshot.title}: {snapshot.taskState.phase.value} / {snapshot.taskState.statusText} / 项目数 {snapshot.itemCount}{detailText}" + ) + return "\n".join(lines) \ No newline at end of file diff --git a/gui/src/packet_studio/main_window.py b/gui/src/packet_studio/main_window.py new file mode 100644 index 00000000000..4ba27b932f1 --- /dev/null +++ b/gui/src/packet_studio/main_window.py @@ -0,0 +1,566 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Optional + +from PySide6 import QtCore, QtGui, QtWidgets + +from packet_studio.domain.task_models import TaskPhase, TaskState +from packet_studio.domain.workspace_models import WorkspaceDocument, WorkspacePanelSnapshot, TaskRecord +from packet_studio.runtime.dependency_check import AppEnvironment +from packet_studio.services.interface_service import InterfaceRecord, InterfaceService +from packet_studio.services.workspace_document_service import WorkspaceDocumentService +from packet_studio.widgets.automation_tools_widget import AutomationToolsWidget +from packet_studio.widgets.offline_analysis_widget import OfflineAnalysisWidget +from packet_studio.widgets.packet_builder_widget import PacketBuilderWidget +from packet_studio.widgets.send_task_widget import SendTaskWidget + + +class InterfaceLoadWorker(QtCore.QObject): + finished = QtCore.Signal(int, bool, list) + failed = QtCore.Signal(int, str) + + def __init__( + self, + interfaceService: InterfaceService, + generation: int, + initialLoad: bool, + ) -> None: + super().__init__() + self.interfaceService = interfaceService + self.generation = generation + self.initialLoad = initialLoad + + @QtCore.Slot() + def run(self) -> None: + try: + records = self.interfaceService.loadInterfaces() + except Exception as exc: + self.failed.emit(self.generation, str(exc)) + return + self.finished.emit(self.generation, self.initialLoad, records) + + +class MainWindow(QtWidgets.QMainWindow): + """Scapy Studio 的最小主窗口。""" + + def __init__(self, environment: AppEnvironment, log_file: Path) -> None: + super().__init__() + self.environment = environment + self.log_file = log_file + self.interfaceService = InterfaceService() + self.interfaceRecords: list[InterfaceRecord] = [] + self.interfaceLoadThread: Optional[QtCore.QThread] = None + self.interfaceLoadWorker: Optional[InterfaceLoadWorker] = None + self.interfaceLoadGeneration = 0 + self.interfaceLoadActive = False + self.workspaceDocumentService = WorkspaceDocumentService() + self.taskRecords: list[TaskRecord] = [] + self.workspaceDocument: Optional[WorkspaceDocument] = None + + self.setWindowTitle("Scapy Studio") + self.resize(1440, 900) + + self._setup_ui() + self._populate_environment() + QtCore.QTimer.singleShot(0, self._start_initial_interface_load) + + def _setup_ui(self) -> None: + self.leftNav = QtWidgets.QListWidget() + self.leftNav.addItems( + [ + "欢迎", + "接口", + "包构建器", + "发送任务", + "离线分析", + "自动化工具", + ] + ) + self.leftNav.setCurrentRow(0) + + self.workspace_tabs = QtWidgets.QTabWidget() + self.leftNav.currentRowChanged.connect(self.workspace_tabs.setCurrentIndex) + self.workspace_tabs.currentChanged.connect(self.leftNav.setCurrentRow) + self.workspace_tabs.currentChanged.connect(self._handle_workspace_tab_changed) + self.packetBuilderTab = PacketBuilderWidget() + self.sendTaskTab = SendTaskWidget() + self.offlineAnalysisTab = OfflineAnalysisWidget() + self.automationToolsTab = AutomationToolsWidget() + self.packetBuilderTab.createStreamRequested.connect(self.sendTaskTab.addStreamFromPacket) + self.packetBuilderTab.saveStreamRequested.connect(self._handle_save_stream_to_send_task) + self.sendTaskTab.editPacketRequested.connect(self._handle_edit_send_stream_packet) + self.offlineAnalysisTab.importPacketRequested.connect(self._handle_import_capture_packet) + self.automationToolsTab.openToolRequested.connect(self._handle_open_tool_tab) + self.workspace_tabs.addTab(self._build_welcome_tab(), "欢迎") + self.workspace_tabs.addTab(self._build_interfaces_tab(), "接口") + self.workspace_tabs.addTab(self.packetBuilderTab, "包构建器") + self.workspace_tabs.addTab(self.sendTaskTab, "发送任务") + self.workspace_tabs.addTab(self.offlineAnalysisTab, "离线分析") + self.workspace_tabs.addTab(self.automationToolsTab, "自动化工具") + + splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal) + splitter.addWidget(self.leftNav) + splitter.addWidget(self.workspace_tabs) + splitter.setStretchFactor(0, 0) + splitter.setStretchFactor(1, 1) + self.setCentralWidget(splitter) + + details_dock = QtWidgets.QDockWidget("详情", self) + details_dock.setAllowedAreas( + QtCore.Qt.DockWidgetArea.RightDockWidgetArea + ) + details_dock.setWidget(self._build_details_panel()) + self.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, details_dock) + + log_dock = QtWidgets.QDockWidget("运行与日志", self) + log_dock.setAllowedAreas( + QtCore.Qt.DockWidgetArea.BottomDockWidgetArea + ) + log_dock.setWidget(self._build_log_panel()) + self.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, log_dock) + self.sendTaskTab.statusMessage.connect( + lambda message: self._handle_panel_status_message( + "发送任务", + self.sendTaskTab.buildWorkspaceSnapshot, + message, + ) + ) + self.offlineAnalysisTab.statusMessage.connect( + lambda message: self._handle_panel_status_message( + "离线分析", + self.offlineAnalysisTab.buildWorkspaceSnapshot, + message, + ) + ) + self.automationToolsTab.statusMessage.connect( + lambda message: self._handle_panel_status_message( + "自动化工具", + self.automationToolsTab.buildWorkspaceSnapshot, + message, + ) + ) + + status_bar = self.statusBar() + status_bar.showMessage("GUI 子项目骨架已启动") + self._refresh_workspace_document() + + def closeEvent(self, event: QtGui.QCloseEvent) -> None: + if self.interfaceLoadActive or self.sendTaskTab.hasRunningTask() or self.offlineAnalysisTab.isLoading(): + QtWidgets.QMessageBox.warning( + self, + "后台任务仍在运行", + "请等待接口刷新、发送任务或离线分析完成后再关闭窗口。", + ) + event.ignore() + return + if self.interfaceLoadThread is not None and self.interfaceLoadThread.isRunning(): + self.interfaceLoadThread.quit() + self.interfaceLoadThread.wait(3000) + super().closeEvent(event) + + def _build_interfaces_tab(self) -> QtWidgets.QWidget: + widget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(widget) + + topBar = QtWidgets.QHBoxLayout() + self.interfaceSummaryLabel = QtWidgets.QLabel("正在加载接口列表...") + self.interfaceSummaryLabel.setWordWrap(True) + self.refreshInterfacesButton = QtWidgets.QPushButton("刷新接口") + self.refreshInterfacesButton.clicked.connect(self._handle_refresh_interfaces) + topBar.addWidget(self.interfaceSummaryLabel, 1) + topBar.addWidget(self.refreshInterfacesButton) + + self.interfaceLoadingBar = QtWidgets.QProgressBar() + self.interfaceLoadingBar.setRange(0, 0) + self.interfaceLoadingBar.setVisible(False) + self.interfaceLoadingBar.setTextVisible(False) + + self.interfaceTable = QtWidgets.QTableWidget(0, 7) + self.interfaceTable.setHorizontalHeaderLabels( + ["名称", "描述", "网络名", "MAC", "IPv4", "IPv6", "能力"] + ) + self.interfaceTable.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers + ) + self.interfaceTable.setSelectionBehavior( + QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows + ) + self.interfaceTable.setSelectionMode( + QtWidgets.QAbstractItemView.SelectionMode.SingleSelection + ) + self.interfaceTable.verticalHeader().setVisible(False) + self.interfaceTable.horizontalHeader().setStretchLastSection(True) + self.interfaceTable.horizontalHeader().setSectionResizeMode( + 0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents + ) + self.interfaceTable.horizontalHeader().setSectionResizeMode( + 1, QtWidgets.QHeaderView.ResizeMode.Stretch + ) + self.interfaceTable.itemSelectionChanged.connect(self._handle_interface_selection) + + layout.addLayout(topBar) + layout.addWidget(self.interfaceLoadingBar) + layout.addWidget(self.interfaceTable, 1) + return widget + + def _build_welcome_tab(self) -> QtWidgets.QWidget: + widget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(widget) + + title = QtWidgets.QLabel("Scapy Studio") + title.setObjectName("titleLabel") + title.setStyleSheet("font-size: 28px; font-weight: 700;") + + subtitle = QtWidgets.QLabel( + "当前阶段已打通接口浏览、包构建与发送、离线分析主链路。" + ) + subtitle.setWordWrap(True) + + self.environment_summary = QtWidgets.QTextEdit() + self.environment_summary.setReadOnly(True) + self.workspace_summary = QtWidgets.QPlainTextEdit() + self.workspace_summary.setReadOnly(True) + + layout.addWidget(title) + layout.addWidget(subtitle) + layout.addWidget(self.environment_summary, 1) + layout.addWidget(self.workspace_summary, 1) + return widget + + def _build_placeholder_tab(self, message: str) -> QtWidgets.QWidget: + widget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(widget) + label = QtWidgets.QLabel(message) + label.setWordWrap(True) + label.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) + layout.addWidget(label) + layout.addStretch(1) + return widget + + def _build_details_panel(self) -> QtWidgets.QWidget: + widget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(widget) + + label = QtWidgets.QLabel("后续这里会承载字段编辑器、协议树和任务参数。") + label.setWordWrap(True) + layout.addWidget(label) + + self.details_list = QtWidgets.QTreeWidget() + self.details_list.setHeaderLabels(["项目", "状态"]) + layout.addWidget(self.details_list, 1) + return widget + + def _build_log_panel(self) -> QtWidgets.QWidget: + widget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(widget) + + self.log_view = QtWidgets.QPlainTextEdit() + self.log_view.setReadOnly(True) + self.log_view.setPlainText( + "运行日志面板已建立。\n" + f"日志文件: {self.log_file}" + ) + layout.addWidget(self.log_view) + return widget + + def _populate_environment(self) -> None: + self.environment_summary.setPlainText(self.environment.to_multiline_text()) + self._set_details_items( + [ + ("Scapy", self.environment.scapy_version or "未检测到"), + ("PySide6", self.environment.pyside6_version or "运行时导入"), + ("Npcap", self.environment.npcap_status), + ("管理员权限", "是" if self.environment.is_elevated else "否"), + ("配置目录", str(self.environment.config_dir)), + ("日志目录", str(self.environment.log_dir)), + ] + ) + self._refresh_workspace_document() + + def _start_initial_interface_load(self) -> None: + self._start_interface_load(initialLoad=True) + + def _start_interface_load(self, initialLoad: bool = False) -> None: + if self.interfaceLoadActive: + self.interfaceSummaryLabel.setText("接口加载仍在进行中,请稍候。") + return + + self.interfaceLoadGeneration += 1 + generation = self.interfaceLoadGeneration + self.interfaceLoadActive = True + self._set_interface_loading_state(True) + if initialLoad: + self.interfaceSummaryLabel.setText("正在后台加载接口列表...") + else: + self.interfaceSummaryLabel.setText("正在后台刷新接口列表...") + self.log_view.appendPlainText("开始后台加载接口列表。") + self._append_task_record("接口", self.interfaceSummaryLabel.text(), TaskPhase.RUNNING) + self._refresh_workspace_document() + + thread = QtCore.QThread(self) + worker = InterfaceLoadWorker(self.interfaceService, generation, initialLoad) + worker.moveToThread(thread) + thread.started.connect(worker.run) + worker.finished.connect(self._on_interfaces_loaded) + worker.failed.connect(self._on_interfaces_failed) + worker.finished.connect(thread.quit) + worker.failed.connect(thread.quit) + thread.finished.connect(worker.deleteLater) + thread.finished.connect(self._on_interface_thread_finished) + + self.interfaceLoadThread = thread + self.interfaceLoadWorker = worker + thread.start() + + def _populate_interface_table(self) -> None: + self.interfaceTable.clearContents() + self.interfaceTable.setRowCount(len(self.interfaceRecords)) + for rowIndex, interfaceRecord in enumerate(self.interfaceRecords): + values = [ + interfaceRecord.name, + interfaceRecord.description, + interfaceRecord.networkName, + interfaceRecord.mac, + interfaceRecord.ipv4, + interfaceRecord.ipv6, + interfaceRecord.capabilitySummary, + ] + for columnIndex, value in enumerate(values): + item = QtWidgets.QTableWidgetItem(value) + item.setData(QtCore.Qt.ItemDataRole.UserRole, rowIndex) + self.interfaceTable.setItem(rowIndex, columnIndex, item) + + def _handle_refresh_interfaces(self) -> None: + self.statusBar().showMessage("正在刷新接口列表...") + self._start_interface_load() + + def _handle_interface_selection(self) -> None: + selectedItems = self.interfaceTable.selectedItems() + if not selectedItems: + return + rowIndex = selectedItems[0].data(QtCore.Qt.ItemDataRole.UserRole) + interfaceRecord = self.interfaceRecords[rowIndex] + self._show_interface_details(interfaceRecord) + + def _show_interface_details(self, interfaceRecord: InterfaceRecord) -> None: + self._set_details_items( + [ + ("名称", interfaceRecord.name), + ("描述", interfaceRecord.description), + ("网络名", interfaceRecord.networkName), + ("Provider", interfaceRecord.provider), + ("索引", str(interfaceRecord.index)), + ("MAC", interfaceRecord.mac or ""), + ("IPv4", interfaceRecord.ipv4 or ""), + ("IPv6", interfaceRecord.ipv6 or ""), + ("Flags", interfaceRecord.flags), + ("状态", "可用" if interfaceRecord.isValid else "不可用"), + ("能力", interfaceRecord.capabilitySummary), + ] + ) + + @QtCore.Slot(object) + def _handle_import_capture_packet(self, packet: object) -> None: + try: + self.packetBuilderTab.setEditingStreamMode(False) + self.packetBuilderTab.loadPacket(packet) + except Exception as exc: + self.statusBar().showMessage(f"导入数据包失败: {exc}", 5000) + self.log_view.appendPlainText(f"导入数据包失败: {exc}") + return + + self.workspace_tabs.setCurrentWidget(self.packetBuilderTab) + self.statusBar().showMessage("已将数据包复制到包构建器。", 5000) + self.log_view.appendPlainText("已将数据包复制到包构建器。") + + @QtCore.Slot(object) + def _handle_edit_send_stream_packet(self, packet: object) -> None: + selectedTemplateId = self.sendTaskTab.getCurrentSelectedTemplateId() + if selectedTemplateId is None: + self.statusBar().showMessage("当前没有选中的流模板。", 5000) + return + try: + self.sendTaskTab.beginEditingStream(selectedTemplateId) + self.packetBuilderTab.setEditingStreamMode(True) + self.packetBuilderTab.loadPacket(packet) + except Exception as exc: + self.packetBuilderTab.setEditingStreamMode(False) + self.statusBar().showMessage(f"载入流模板到包构建器失败: {exc}", 5000) + self.log_view.appendPlainText(f"载入流模板到包构建器失败: {exc}") + return + + self.workspace_tabs.setCurrentWidget(self.packetBuilderTab) + self.statusBar().showMessage("已跳转到包构建器,可继续复杂编辑。", 5000) + self.log_view.appendPlainText("已从发送任务跳转到包构建器继续编辑。") + + @QtCore.Slot(object) + def _handle_save_stream_to_send_task(self, packet: object) -> None: + saved = self.sendTaskTab.saveEditedStream(packet) + if not saved: + self.statusBar().showMessage("当前没有可回写的流模板,请先从发送任务进入编辑。", 5000) + self.log_view.appendPlainText("保存回流模板失败:当前没有编辑中的流模板。") + return + + self.packetBuilderTab.setEditingStreamMode(False) + self.workspace_tabs.setCurrentWidget(self.sendTaskTab) + self.statusBar().showMessage("已保存回当前流模板。", 5000) + self.log_view.appendPlainText("已将包构建器修改保存回当前流模板。") + + @QtCore.Slot(str) + def _handle_open_tool_tab(self, targetTabTitle: str) -> None: + for index in range(self.workspace_tabs.count()): + if self.workspace_tabs.tabText(index) == targetTabTitle: + self.workspace_tabs.setCurrentIndex(index) + self.statusBar().showMessage(f"已打开工具页: {targetTabTitle}", 5000) + return + + self.statusBar().showMessage(f"未找到工具页: {targetTabTitle}", 5000) + + @QtCore.Slot(int, bool, list) + def _on_interfaces_loaded( + self, + generation: int, + initialLoad: bool, + records: list[InterfaceRecord], + ) -> None: + if generation != self.interfaceLoadGeneration: + return + + self.interfaceRecords = records + self.sendTaskTab.setInterfaceRecords(records) + self._populate_interface_table() + count = len(self.interfaceRecords) + if count == 0: + self.interfaceSummaryLabel.setText( + "当前没有发现可用接口。请确认 Npcap、权限和网络适配器状态。" + ) + self.statusBar().showMessage("接口扫描完成,但没有可用接口。", 5000) + if initialLoad: + self.log_view.appendPlainText("未发现可用接口。") + self._append_task_record("接口", self.interfaceSummaryLabel.text(), TaskPhase.SUCCEEDED) + self._refresh_workspace_document() + return + + self.interfaceSummaryLabel.setText(f"已发现 {count} 个可用接口。") + self.statusBar().showMessage(f"接口列表已刷新,共 {count} 个接口。", 5000) + self.log_view.appendPlainText(f"接口扫描完成,共 {count} 个可用接口。") + self.interfaceTable.selectRow(0) + self._append_task_record("接口", self.interfaceSummaryLabel.text(), TaskPhase.SUCCEEDED) + self._refresh_workspace_document() + + @QtCore.Slot(int, str) + def _on_interfaces_failed(self, generation: int, message: str) -> None: + if generation != self.interfaceLoadGeneration: + return + + self.interfaceRecords = [] + self.sendTaskTab.setInterfaceRecords([]) + self.interfaceTable.clearContents() + self.interfaceTable.setRowCount(0) + self.interfaceSummaryLabel.setText(f"接口加载失败: {message}") + self.statusBar().showMessage("接口加载失败", 5000) + self.log_view.appendPlainText(f"接口加载失败: {message}") + self._set_details_items( + [ + ("接口状态", "加载失败"), + ("失败原因", message), + ] + ) + self._append_task_record("接口", f"接口加载失败: {message}", TaskPhase.FAILED) + self._refresh_workspace_document() + + def _on_interface_thread_finished(self) -> None: + self.interfaceLoadActive = False + self._set_interface_loading_state(False) + self.interfaceLoadThread = None + self.interfaceLoadWorker = None + self._refresh_workspace_document() + + def _set_interface_loading_state(self, isLoading: bool) -> None: + self.refreshInterfacesButton.setEnabled(not isLoading) + self.interfaceTable.setEnabled(not isLoading) + self.interfaceLoadingBar.setVisible(isLoading) + + def _set_details_items(self, items: list[tuple[str, str]]) -> None: + self.details_list.clear() + for name, value in items: + item = QtWidgets.QTreeWidgetItem([name, value]) + self.details_list.addTopLevelItem(item) + + def _handle_panel_status_message( + self, + sourceTitle: str, + snapshotBuilder: callable, + message: str, + ) -> None: + self.log_view.appendPlainText(message) + snapshot = snapshotBuilder() + self._append_task_record(sourceTitle, message, snapshot.taskState.phase, snapshot.detailText) + self._refresh_workspace_document() + + def _handle_workspace_tab_changed(self, _index: int) -> None: + self._refresh_workspace_document() + + def _append_task_record( + self, + sourceTitle: str, + message: str, + phase: TaskPhase, + detailText: str = "", + ) -> None: + record = self.workspaceDocumentService.createTaskRecord( + sequenceNumber=len(self.taskRecords) + 1, + sourceTitle=sourceTitle, + message=message, + phase=phase, + detailText=detailText, + ) + self.taskRecords.append(record) + if len(self.taskRecords) > 50: + self.taskRecords = self.taskRecords[-50:] + + def _collect_workspace_snapshots(self) -> list[WorkspacePanelSnapshot]: + interfaceStatusText = "接口页尚未初始化。" + interfaceCount = len(self.interfaceRecords) + if hasattr(self, "interfaceSummaryLabel"): + interfaceStatusText = self.interfaceSummaryLabel.text() + + if self.interfaceLoadActive: + interfaceState = TaskState.running(interfaceStatusText) + elif "失败" in interfaceStatusText: + interfaceState = TaskState.failed(interfaceStatusText) + elif interfaceCount: + interfaceState = TaskState.succeeded(interfaceStatusText) + else: + interfaceState = TaskState.idle(interfaceStatusText) + + snapshots = [ + WorkspacePanelSnapshot( + panelId="interfaces", + title="接口", + taskState=interfaceState, + itemCount=interfaceCount, + detailText=interfaceStatusText, + ), + self.packetBuilderTab.buildWorkspaceSnapshot(), + self.sendTaskTab.buildWorkspaceSnapshot(), + self.offlineAnalysisTab.buildWorkspaceSnapshot(), + self.automationToolsTab.buildWorkspaceSnapshot(), + ] + return snapshots + + def _refresh_workspace_document(self) -> None: + activeIndex = self.workspace_tabs.currentIndex() + activeTabTitle = self.workspace_tabs.tabText(activeIndex) if activeIndex >= 0 else "欢迎" + openTabTitles = [self.workspace_tabs.tabText(index) for index in range(self.workspace_tabs.count())] + self.workspaceDocument = self.workspaceDocumentService.buildWorkspaceDocument( + activeTabTitle=activeTabTitle, + openTabTitles=openTabTitles, + panelSnapshots=self._collect_workspace_snapshots(), + taskRecords=self.taskRecords, + interfaceCount=len(self.interfaceRecords), + interfaceSummaryText=self.interfaceSummaryLabel.text() if hasattr(self, "interfaceSummaryLabel") else "", + ) + if hasattr(self, "workspace_summary"): + self.workspace_summary.setPlainText(self.workspaceDocument.to_multiline_text()) diff --git a/gui/src/packet_studio/runtime/__init__.py b/gui/src/packet_studio/runtime/__init__.py new file mode 100644 index 00000000000..69e6e96441d --- /dev/null +++ b/gui/src/packet_studio/runtime/__init__.py @@ -0,0 +1 @@ +"""Runtime helpers for Scapy Studio.""" \ No newline at end of file diff --git a/gui/src/packet_studio/runtime/dependency_check.py b/gui/src/packet_studio/runtime/dependency_check.py new file mode 100644 index 00000000000..8eea83395b9 --- /dev/null +++ b/gui/src/packet_studio/runtime/dependency_check.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import ctypes +import os +from dataclasses import dataclass +from pathlib import Path +from typing import Optional + +from packet_studio.runtime.paths import RuntimeDirectories + + +@dataclass(frozen=True) +class AppEnvironment: + scapy_version: Optional[str] + pyside6_version: Optional[str] + npcap_status: str + is_elevated: bool + config_dir: Path + log_dir: Path + cache_dir: Path + + def to_multiline_text(self) -> str: + lines = [ + "Scapy Studio 运行环境概览", + "", + f"Scapy 版本: {self.scapy_version or '未检测到'}", + f"PySide6 版本: {self.pyside6_version or '运行时导入'}", + f"Npcap 状态: {self.npcap_status}", + f"管理员权限: {'是' if self.is_elevated else '否'}", + f"配置目录: {self.config_dir}", + f"日志目录: {self.log_dir}", + f"缓存目录: {self.cache_dir}", + ] + return "\n".join(lines) + + +def collect_environment(runtime_dirs: RuntimeDirectories) -> AppEnvironment: + return AppEnvironment( + scapy_version=_detect_scapy_version(), + pyside6_version=_detect_pyside6_version(), + npcap_status=_detect_npcap_status(), + is_elevated=_is_elevated(), + config_dir=runtime_dirs.config_dir, + log_dir=runtime_dirs.log_dir, + cache_dir=runtime_dirs.cache_dir, + ) + + +def _detect_scapy_version() -> Optional[str]: + try: + import scapy # type: ignore + + return getattr(scapy, "VERSION", None) + except Exception: + return None + + +def _detect_pyside6_version() -> Optional[str]: + try: + import PySide6 # type: ignore + + return getattr(PySide6, "__version__", None) + except Exception: + return None + + +def _detect_npcap_status() -> str: + if os.name != "nt": + return "当前不是 Windows 环境" + + program_files = os.environ.get("ProgramFiles", r"C:\Program Files") + candidates = [ + Path(program_files) / "Npcap", + Path(os.environ.get("SystemRoot", r"C:\Windows")) / "System32" / "Npcap", + ] + for candidate in candidates: + if candidate.exists(): + return f"已检测到: {candidate}" + return "未检测到 Npcap 安装目录" + + +def _is_elevated() -> bool: + if os.name != "nt": + return os.getuid() == 0 if hasattr(os, "getuid") else False + + try: + return bool(ctypes.windll.shell32.IsUserAnAdmin()) + except Exception: + return False \ No newline at end of file diff --git a/gui/src/packet_studio/runtime/paths.py b/gui/src/packet_studio/runtime/paths.py new file mode 100644 index 00000000000..9a98e2cc157 --- /dev/null +++ b/gui/src/packet_studio/runtime/paths.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import os +from dataclasses import dataclass +from pathlib import Path + + +@dataclass(frozen=True) +class RuntimeDirectories: + config_dir: Path + log_dir: Path + cache_dir: Path + + +def _base_data_dir() -> Path: + app_data = os.environ.get("APPDATA") + if app_data: + return Path(app_data) + return Path.home() / ".config" + + +def _base_cache_dir() -> Path: + local_app_data = os.environ.get("LOCALAPPDATA") + if local_app_data: + return Path(local_app_data) + return Path.home() / ".cache" + + +def ensure_runtime_directories() -> RuntimeDirectories: + base_config_dir = _base_data_dir() / "ScapyStudio" + base_log_dir = base_config_dir / "logs" + base_cache_dir = _base_cache_dir() / "ScapyStudio" + + for directory in [base_config_dir, base_log_dir, base_cache_dir]: + directory.mkdir(parents=True, exist_ok=True) + + return RuntimeDirectories( + config_dir=base_config_dir, + log_dir=base_log_dir, + cache_dir=base_cache_dir, + ) \ No newline at end of file diff --git a/gui/src/packet_studio/services/__init__.py b/gui/src/packet_studio/services/__init__.py new file mode 100644 index 00000000000..f81e3bf5324 --- /dev/null +++ b/gui/src/packet_studio/services/__init__.py @@ -0,0 +1 @@ +"""Application services for Scapy Studio.""" \ No newline at end of file diff --git a/gui/src/packet_studio/services/interface_service.py b/gui/src/packet_studio/services/interface_service.py new file mode 100644 index 00000000000..2ed67b88371 --- /dev/null +++ b/gui/src/packet_studio/services/interface_service.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import List + + +@dataclass(frozen=True) +class InterfaceRecord: + name: str + description: str + networkName: str + mac: str + ipv4: str + ipv6: str + provider: str + isValid: bool + flags: str + index: int + + @property + def isLoopback(self) -> bool: + loweredName = self.name.lower() + loweredDescription = self.description.lower() + loweredNetworkName = self.networkName.lower() + return ( + "loopback" in loweredName + or "loopback" in loweredDescription + or "loopback" in loweredNetworkName + ) + + @property + def capabilityTags(self) -> list[str]: + tags = [] + if self.isValid: + tags.append("可用") + else: + tags.append("不可用") + if self.isLoopback: + tags.append("回环") + if self.ipv4: + tags.append("IPv4") + if self.ipv6: + tags.append("IPv6") + if self.mac: + tags.append("L2") + if self.provider: + tags.append(self.provider) + return tags + + @property + def capabilitySummary(self) -> str: + return " / ".join(self.capabilityTags) + + +class InterfaceService: + """封装 Scapy 接口发现流程。""" + + def loadInterfaces(self) -> List[InterfaceRecord]: + import scapy.all as scapy # noqa: F401 + from scapy.config import conf + from scapy.interfaces import get_working_ifaces + + conf.ifaces.reload() + interfaces = get_working_ifaces() + records = [] + for interface in interfaces: + ipv4 = interface.ip or "" + ipv6 = ", ".join(interface.ips[6]) if interface.ips[6] else "" + records.append( + InterfaceRecord( + name=interface.name or interface.description or interface.network_name, + description=interface.description or interface.name, + networkName=interface.network_name, + mac=interface.mac or "", + ipv4=ipv4, + ipv6=ipv6, + provider=interface.provider.name, + isValid=interface.is_valid(), + flags=str(getattr(interface, "flags", "")), + index=int(interface.index), + ) + ) + records.sort(key=lambda interfaceRecord: interfaceRecord.name.lower()) + return records \ No newline at end of file diff --git a/gui/src/packet_studio/services/packet_builder_service.py b/gui/src/packet_studio/services/packet_builder_service.py new file mode 100644 index 00000000000..63ef5d05d7e --- /dev/null +++ b/gui/src/packet_studio/services/packet_builder_service.py @@ -0,0 +1,992 @@ +from __future__ import annotations + +import ast +import importlib +import json +import pkgutil +import re +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Callable, Dict, List, Optional, Tuple + +from packet_studio.adapters.scapy_packet_adapter import ScapyPacketAdapter + + +@dataclass(frozen=True) +class AvailableLayer: + key: str + label: str + packetClassName: str + category: str + + +@dataclass(frozen=True) +class LayerFieldRecord: + name: str + fieldType: str + defaultValue: str + currentValue: str + editorKind: str + choices: Tuple[Tuple[str, str], ...] = () + placeholderText: str = "" + collectionKind: str = "" + + +@dataclass(frozen=True) +class LayerRecord: + index: int + name: str + summary: str + + +class PacketBuilderService: + """最小包构建器服务。""" + + CATEGORY_COMMON = "常用层" + CATEGORY_WIRELESS = "无线与近场" + CATEGORY_INDUSTRIAL = "工业与企业" + CATEGORY_CONTRIB = "Contrib 扩展" + CATEGORY_OTHER = "其他" + CATEGORY_ORDER: Tuple[str, ...] = ( + CATEGORY_COMMON, + CATEGORY_WIRELESS, + CATEGORY_INDUSTRIAL, + CATEGORY_CONTRIB, + CATEGORY_OTHER, + ) + + _COMMON_MODULE_NAMES: Tuple[str, ...] = ( + "inet", + "inet6", + "l2", + "dns", + "dhcp", + "dhcp6", + "http", + "tls", + "quic", + "sctp", + "vxlan", + "ntp", + "rip", + "ppp", + "tftp", + "radius", + "l2tp", + "isakmp", + ) + _WIRELESS_MODULE_NAMES: Tuple[str, ...] = ( + "bluetooth", + "dot11", + "zigbee", + "dot15d4", + "sixlowpan", + "nfc", + "ubertooth", + ) + _INDUSTRIAL_MODULE_NAMES: Tuple[str, ...] = ( + "netflow", + "ldap", + "kerberos", + "smb", + "smb2", + "dcerpc", + "gssapi", + "ntlm", + "msrpce", + "pnio", + "modbus", + "tacacs", + ) + + _LEGACY_LAYER_DEFINITIONS: Tuple[Tuple[str, str, str], ...] = ( + ("ether", "Ethernet", "Ether"), + ("arp", "ARP", "ARP"), + ("ip", "IPv4", "IP"), + ("ipv6", "IPv6", "IPv6"), + ("tcp", "TCP", "TCP"), + ("udp", "UDP", "UDP"), + ("icmp", "ICMP", "ICMP"), + ("dns", "DNS", "DNS"), + ("dot1q", "802.1Q VLAN", "Dot1Q"), + ("dot1ad", "802.1ad QinQ", "Dot1AD"), + ("mac_pause", "802.3x Pause Frame", "MACControlPause"), + ("mac_gate", "802.3 Gate Control", "MACControlGate"), + ("mac_report", "802.3 Report Control", "MACControlReport"), + ("mac_register_req", "802.3 Register Request", "MACControlRegisterReq"), + ("mac_register", "802.3 Register", "MACControlRegister"), + ("mac_register_ack", "802.3 Register Acknowledge", "MACControlRegisterAck"), + ( + "mac_pfc", + "802.1Qbb Priority Flow Control", + "MACControlClassBasedFlowControl", + ), + ("raw", "Raw Payload", "Raw"), + ) + + _DISCOVERY_PACKAGE_NAMES: Tuple[str, ...] = ("scapy.layers", "scapy.contrib") + _DISCOVERY_MODULE_PREFIXES: Tuple[str, ...] = ("scapy.layers.", "scapy.contrib.") + _EXCLUDED_PACKET_CLASS_NAMES: Tuple[str, ...] = ( + "Packet", + "NoPayload", + "Padding", + "ASN1_Packet", + "MACControl", + ) + + _COMMON_ETHER_TYPE_CHOICES: Tuple[Tuple[int, str], ...] = ( + (0x0800, "IPv4"), + (0x0806, "ARP"), + (0x86DD, "IPv6"), + (0x8100, "802.1Q VLAN"), + (0x88A8, "802.1ad QinQ"), + (0x8808, "Ethernet PAUSE"), + (0x8809, "Slow Protocols"), + (0x8847, "MPLS Unicast"), + (0x8848, "MPLS Multicast"), + (0x8863, "PPPoE Discovery"), + (0x8864, "PPPoE Session"), + (0x88CC, "LLDP"), + (0x88E5, "MACsec"), + (0x88F7, "PTP"), + ) + + def __init__(self) -> None: + import scapy.all as scapy + from scapy.packet import Packet + from scapy.layers.inet import ( + IPOption_EOL, + IPOption_LSRR, + IPOption_NOP, + IPOption_RR, + IPOption_Router_Alert, + IPOption_SSRR, + IPOption_Security, + IPOption_Timestamp, + ) + from scapy.contrib.mac_control import ( + MACControlGate, + MACControlClassBasedFlowControl, + MACControlPause, + MACControlRegister, + MACControlRegisterAck, + MACControlRegisterReq, + MACControlReport, + ) + + self._scapy = scapy + self._packetBaseClass = Packet + self._availableLayerTypes: Dict[str, Callable[[], Any]] = {} + self._availableLayerClasses: Dict[str, type[Any]] = {} + self._availableLayers: List[AvailableLayer] = [] + self._layerKeysByClass: Dict[type[Any], str] = {} + + legacyLayerClasses: Dict[str, type[Any]] = { + "Ether": scapy.Ether, + "ARP": scapy.ARP, + "IP": scapy.IP, + "IPv6": scapy.IPv6, + "TCP": scapy.TCP, + "UDP": scapy.UDP, + "ICMP": scapy.ICMP, + "DNS": scapy.DNS, + "Dot1Q": scapy.Dot1Q, + "Dot1AD": scapy.Dot1AD, + "MACControlPause": MACControlPause, + "MACControlGate": MACControlGate, + "MACControlReport": MACControlReport, + "MACControlRegisterReq": MACControlRegisterReq, + "MACControlRegister": MACControlRegister, + "MACControlRegisterAck": MACControlRegisterAck, + "MACControlClassBasedFlowControl": MACControlClassBasedFlowControl, + "Raw": scapy.Raw, + } + + for key, label, className in self._LEGACY_LAYER_DEFINITIONS: + self._registerAvailableLayer(key, label, legacyLayerClasses[className]) + + self._loadOptionalProtocolModules() + self._registerDiscoveredLayers() + self._layers: List[Any] = [] + self._ipOptionFactories: Dict[str, Callable[..., Any]] = { + "NOP": IPOption_NOP, + "EOL": IPOption_EOL, + "RR": IPOption_RR, + "LSRR": IPOption_LSRR, + "SSRR": IPOption_SSRR, + "Timestamp": IPOption_Timestamp, + "RouterAlert": IPOption_Router_Alert, + "Security": IPOption_Security, + } + self._packetAdapter = ScapyPacketAdapter() + + def listAvailableLayers(self) -> List[AvailableLayer]: + return list(self._availableLayers) + + def listAvailableLayerCategories(self) -> List[str]: + presentCategories = {layer.category for layer in self._availableLayers} + return [category for category in self.CATEGORY_ORDER if category in presentCategories] + + def _registerAvailableLayer( + self, + key: str, + label: str, + packetType: type[Any], + ) -> None: + if packetType in self._layerKeysByClass: + return + + self._availableLayerTypes[key] = packetType + self._availableLayerClasses[key] = packetType + category = self._categorizePacketType(packetType) + self._availableLayers.append(AvailableLayer(key, label, packetType.__name__, category)) + self._layerKeysByClass[packetType] = key + + def _loadOptionalProtocolModules(self) -> None: + for packageName in self._DISCOVERY_PACKAGE_NAMES: + self._importProtocolModules(packageName) + + def _importProtocolModules(self, packageName: str) -> None: + try: + package = importlib.import_module(packageName) + except Exception: + return + + packagePath = getattr(package, "__path__", None) + if packagePath is None: + return + + moduleNames = sorted( + moduleInfo.name + for moduleInfo in pkgutil.walk_packages(packagePath, prefix=packageName + ".") + ) + + for moduleName in moduleNames: + if moduleName.endswith(".__main__"): + continue + try: + importlib.import_module(moduleName) + except Exception: + continue + + def _registerDiscoveredLayers(self) -> None: + discoveredPacketTypes = sorted( + self._iterDiscoveredPacketTypes(), + key=lambda packetType: (packetType.__module__, packetType.__name__), + ) + + for packetType in discoveredPacketTypes: + if not self._shouldExposePacketType(packetType): + continue + if packetType in self._layerKeysByClass: + continue + + key = self._buildUniqueLayerKey(packetType) + label = self._buildLayerLabel(packetType) + self._registerAvailableLayer(key, label, packetType) + + legacyKeys = {key for key, _, _ in self._LEGACY_LAYER_DEFINITIONS} + legacyLayers = [layer for layer in self._availableLayers if layer.key in legacyKeys] + discoveredLayers = [layer for layer in self._availableLayers if layer.key not in legacyKeys] + discoveredLayers.sort(key=lambda layer: (layer.label.casefold(), layer.packetClassName.casefold())) + self._availableLayers = legacyLayers + discoveredLayers + + def _iterDiscoveredPacketTypes(self) -> List[type[Any]]: + discoveredPacketTypes: Dict[str, type[Any]] = {} + + for module in list(sys.modules.values()): + moduleName = getattr(module, "__name__", "") + if not moduleName.startswith(self._DISCOVERY_MODULE_PREFIXES): + continue + + moduleDictionary = getattr(module, "__dict__", None) + if not isinstance(moduleDictionary, dict): + continue + + for value in moduleDictionary.values(): + if not isinstance(value, type): + continue + if not issubclass(value, self._packetBaseClass): + continue + discoveredPacketTypes[f"{value.__module__}.{value.__name__}"] = value + + return list(discoveredPacketTypes.values()) + + def _shouldExposePacketType(self, packetType: type[Any]) -> bool: + if packetType.__name__ in self._EXCLUDED_PACKET_CLASS_NAMES: + return False + if packetType.__name__.startswith("_"): + return False + if packetType.__module__ == "scapy.packet": + return packetType is self._scapy.Raw + if not packetType.__module__.startswith(self._DISCOVERY_MODULE_PREFIXES): + return False + + try: + packetType() + except Exception: + return False + + return True + + def _buildUniqueLayerKey(self, packetType: type[Any]) -> str: + baseKey = self._normalizeLayerKey(packetType.__name__) + if baseKey not in self._availableLayerTypes: + return baseKey + + moduleParts = [ + self._normalizeLayerKey(part) + for part in packetType.__module__.split(".") + if part and part not in {"scapy", "layers", "contrib"} + ] + for modulePart in reversed(moduleParts): + candidate = f"{baseKey}_{modulePart}" + if candidate not in self._availableLayerTypes: + return candidate + + suffix = 2 + while True: + candidate = f"{baseKey}_{suffix}" + if candidate not in self._availableLayerTypes: + return candidate + suffix += 1 + + def _buildLayerLabel(self, packetType: type[Any]) -> str: + className = packetType.__name__ + rawDisplayName = getattr(packetType, "name", "") + displayName = rawDisplayName.strip() if isinstance(rawDisplayName, str) else "" + if not displayName or displayName == className: + return className + return f"{displayName} ({className})" + + def _categorizePacketType(self, packetType: type[Any]) -> str: + if packetType in self._layerKeysByClass: + existingKey = self._layerKeysByClass[packetType] + legacyKeys = {key for key, _, _ in self._LEGACY_LAYER_DEFINITIONS} + if existingKey in legacyKeys: + return self.CATEGORY_COMMON + + moduleName = packetType.__module__ + moduleParts = moduleName.split(".") + moduleLeaf = moduleParts[-1] if moduleParts else "" + + if moduleName.startswith("scapy.contrib."): + return self.CATEGORY_CONTRIB + if moduleLeaf in self._WIRELESS_MODULE_NAMES: + return self.CATEGORY_WIRELESS + if moduleLeaf in self._INDUSTRIAL_MODULE_NAMES: + return self.CATEGORY_INDUSTRIAL + if moduleLeaf in self._COMMON_MODULE_NAMES: + return self.CATEGORY_COMMON + return self.CATEGORY_OTHER + + def _normalizeLayerKey(self, value: str) -> str: + normalizedValue = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", value) + normalizedValue = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", normalizedValue) + normalizedValue = re.sub(r"[^0-9A-Za-z]+", "_", normalizedValue).strip("_").lower() + if not normalizedValue: + normalizedValue = "layer" + if normalizedValue[0].isdigit(): + normalizedValue = f"layer_{normalizedValue}" + return normalizedValue + + def reset(self) -> None: + self._layers = [] + + def addLayer(self, key: str) -> None: + packetFactory = self._availableLayerTypes[key] + self._layers.append(packetFactory()) + + def removeLayer(self, index: int) -> None: + del self._layers[index] + + def moveLayer(self, sourceIndex: int, targetIndex: int) -> None: + layer = self._layers.pop(sourceIndex) + self._layers.insert(targetIndex, layer) + + def reorderLayers(self, sourceIndexes: List[int]) -> None: + self._layers = [self._layers[index] for index in sourceIndexes] + + def getLayerRecords(self) -> List[LayerRecord]: + records = [] + for index, layer in enumerate(self._layers): + records.append( + LayerRecord( + index=index, + name=layer.name, + summary=layer.summary(), + ) + ) + return records + + def getFieldRecords(self, layerIndex: int) -> List[LayerFieldRecord]: + layer = self._layers[layerIndex] + records = [] + for field in layer.fields_desc: + currentValue = layer.getfieldval(field.name) + editorKind, choices, placeholderText, collectionKind = self._describeFieldEditor(layer, field) + records.append( + LayerFieldRecord( + name=field.name, + fieldType=field.__class__.__name__, + defaultValue=self._formatFieldValue(layer, field, field.default), + currentValue=self._formatFieldValue(layer, field, currentValue), + editorKind=editorKind, + choices=choices, + placeholderText=placeholderText, + collectionKind=collectionKind, + ) + ) + return records + + def getFieldValue(self, layerIndex: int, fieldName: str) -> str: + layer = self._layers[layerIndex] + field = layer.get_field(fieldName) + return self._formatFieldValue(layer, field, layer.getfieldval(fieldName)) + + def getFieldNativeValue(self, layerIndex: int, fieldName: str) -> Any: + layer = self._layers[layerIndex] + return layer.getfieldval(fieldName) + + def exportTemplate(self) -> Dict[str, Any]: + layers = [] + for layer in self._layers: + layerKey = self._resolveLayerKey(layer) + serializedFields = {} + for fieldName, currentValue in layer.fields.items(): + defaultValue = layer.default_fields.get(fieldName) + serializedValue = self._serializeFieldValue(layer, fieldName, currentValue) + defaultSerializedValue = self._serializeFieldValue(layer, fieldName, defaultValue) + if serializedValue == defaultSerializedValue: + continue + serializedFields[fieldName] = serializedValue + layers.append( + { + "key": layerKey, + "fields": serializedFields, + } + ) + return { + "version": 1, + "layers": layers, + } + + def importTemplate(self, payload: Dict[str, Any]) -> None: + layers = payload.get("layers", []) + self.reset() + for layerDefinition in layers: + layerKey = layerDefinition["key"] + self.addLayer(layerKey) + layerIndex = len(self._layers) - 1 + for fieldName, rawValue in layerDefinition.get("fields", {}).items(): + self.setSerializedFieldValue(layerIndex, fieldName, rawValue) + + def saveTemplate(self, filePath: str) -> None: + payload = self.exportTemplate() + Path(filePath).write_text( + json.dumps(payload, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + + def loadTemplate(self, filePath: str) -> None: + payload = json.loads(Path(filePath).read_text(encoding="utf-8")) + self.importTemplate(payload) + + def importPacket(self, packet: Any) -> None: + importedLayers: List[Any] = [] + + currentLayer = packet.copy() + while currentLayer is not None and currentLayer.__class__.__name__ != "NoPayload": + nextLayer = getattr(currentLayer, "payload", None) + + if not self._shouldSkipImportedLayer(currentLayer): + layerCopy = currentLayer.copy() + layerCopy.remove_payload() + self._appendImportedLayer(importedLayers, layerCopy) + + if nextLayer is None or nextLayer.__class__.__name__ == "NoPayload": + break + currentLayer = nextLayer + + self._layers = importedLayers + + def _appendImportedLayer(self, importedLayers: List[Any], layer: Any) -> None: + self._resolveLayerKey(layer) + importedLayers.append(layer) + + def _shouldSkipImportedLayer(self, layer: Any) -> bool: + if self._canResolveLayerKey(layer): + return False + + payload = getattr(layer, "payload", None) + if payload is None or payload.__class__.__name__ == "NoPayload": + return False + + publicFields = [field for field in layer.fields_desc if not field.name.startswith("_")] + return len(publicFields) == 0 + + def setFieldValue(self, layerIndex: int, fieldName: str, rawValue: str) -> None: + layer = self._layers[layerIndex] + if rawValue == "": + layer.delfieldval(fieldName) + return + + field = layer.get_field(fieldName) + parsedValue = self._parseFieldValue(rawValue, field.default) + parsedValue = self._coerceFieldValue(layer, fieldName, field, parsedValue) + layer.setfieldval(fieldName, parsedValue) + + def setSerializedFieldValue(self, layerIndex: int, fieldName: str, rawValue: Any) -> None: + if isinstance(rawValue, str): + self.setFieldValue(layerIndex, fieldName, rawValue) + return + + layer = self._layers[layerIndex] + field = layer.get_field(fieldName) + parsedValue = self._coerceFieldValue(layer, fieldName, field, rawValue) + layer.setfieldval(fieldName, parsedValue) + + def buildSummary(self) -> str: + packet = self.buildPacket() + if packet is None: + return "尚未添加任何协议层。" + return self._packetAdapter.buildSummary(packet) + + def buildHexdump(self) -> str: + packet = self.buildPacket() + if packet is None: + return "" + return self._packetAdapter.buildHexdump(packet) + + def buildStructureDump(self) -> str: + packet = self.buildPacket() + if packet is None: + return "尚未添加任何协议层。" + return self._packetAdapter.buildStructureDump(packet) + + def buildPacket(self) -> Optional[Any]: + if not self._layers: + return None + + packet = self._layers[0].copy() + for layer in self._layers[1:]: + packet = packet / layer.copy() + return packet + + def _parseFieldValue(self, rawValue: str, defaultValue: Any) -> Any: + stripped = rawValue.strip() + if isinstance(defaultValue, bytes): + if stripped.startswith("b'") or stripped.startswith('b"'): + return ast.literal_eval(stripped) + return rawValue.encode("utf-8") + + if stripped.lower() in {"true", "false"}: + return stripped.lower() == "true" + + try: + return ast.literal_eval(stripped) + except Exception: + pass + + if isinstance(defaultValue, int): + return int(stripped, 0) + + return rawValue + + def _coerceFieldValue( + self, + layer: Any, + fieldName: str, + field: Any, + value: Any, + ) -> Any: + if layer.__class__ is self._scapy.IP and fieldName == "options": + return self._coerceIpOptionList(value) + + if layer.__class__ is self._scapy.DNS and fieldName == "qd": + return self._coerceDnsQuestionList(value) + + return value + + def _coerceDnsQuestionList(self, value: Any) -> Any: + if not isinstance(value, list): + return value + + questions = [] + for item in value: + if isinstance(item, self._scapy.DNSQR): + questions.append(item) + continue + + if isinstance(item, dict): + questions.append( + self._scapy.DNSQR( + qname=item.get("qname", ""), + qtype=item.get("qtype", "A"), + qclass=item.get("qclass", "IN"), + ) + ) + continue + + if isinstance(item, str): + questions.append( + self._scapy.DNSQR( + qname=item, + qtype="A", + qclass="IN", + ) + ) + continue + + questions.append(item) + + return questions + + def _serializeDnsQuestionList(self, value: Any) -> Any: + if not isinstance(value, list): + return self._valueToEditorText(value) + + questions = [] + for item in value: + if isinstance(item, self._scapy.DNSQR): + qname = getattr(item, "qname", b"") + if isinstance(qname, bytes): + qname = qname.decode("utf-8", errors="replace") + questions.append( + { + "qname": str(qname).rstrip("."), + "qtype": self._dnsQuestionFieldLabel(item, "qtype", "A"), + "qclass": self._dnsQuestionFieldLabel(item, "qclass", "IN"), + } + ) + continue + + if isinstance(item, dict): + questions.append( + { + "qname": str(item.get("qname", "")), + "qtype": str(item.get("qtype", "A")), + "qclass": str(item.get("qclass", "IN")), + } + ) + continue + + if isinstance(item, str): + questions.append( + { + "qname": item, + "qtype": "A", + "qclass": "IN", + } + ) + continue + + questions.append(self._valueToEditorText(item)) + + return questions + + def _dnsQuestionFieldLabel(self, packet: Any, fieldName: str, fallback: str) -> str: + try: + field = packet.get_field(fieldName) + except Exception: + return fallback + + fieldValue = getattr(packet, fieldName, fallback) + label = field.i2repr(packet, fieldValue) + if isinstance(label, str) and label: + return label + return self._valueToEditorText(fieldValue) or fallback + + def _coerceIpOptionList(self, value: Any) -> Any: + if not isinstance(value, list): + return value + + options = [] + for item in value: + if item.__class__.__name__.startswith("IPOption"): + options.append(item) + continue + + if isinstance(item, str): + factory = self._ipOptionFactories.get(item) + if factory is not None: + options.append(factory()) + continue + + if isinstance(item, dict): + optionType = str(item.get("type", "")).strip() + factory = self._ipOptionFactories.get(optionType) + if factory is None: + options.append(item) + continue + + optionFields = self._extractIpOptionFields(item) + options.append(factory(**optionFields)) + continue + + options.append(item) + + return options + + def _serializeIpOptionList(self, value: Any) -> Any: + if not isinstance(value, list): + return self._valueToEditorText(value) + + options = [] + for item in value: + className = item.__class__.__name__ + if className == "IPOption_NOP": + options.append({"type": "NOP"}) + elif className == "IPOption_EOL": + options.append({"type": "EOL"}) + elif className in {"IPOption_RR", "IPOption_LSRR", "IPOption_SSRR"}: + options.append( + { + "type": className.removeprefix("IPOption_"), + "routers": list(getattr(item, "routers", [])), + } + ) + elif className == "IPOption_Timestamp": + options.append( + { + "type": "Timestamp", + "flg": str(getattr(item, "flg", "timestamp_only")), + "internet_address": str(getattr(item, "internet_address", "0.0.0.0")), + "timestamp": int(getattr(item, "timestamp", 0)), + } + ) + elif className == "IPOption_Router_Alert": + options.append({"type": "RouterAlert"}) + elif className == "IPOption_Security": + options.append( + { + "type": "Security", + "security": int(getattr(item, "security", 0)), + "compartment": int(getattr(item, "compartment", 0)), + "handling_restrictions": int(getattr(item, "handling_restrictions", 0)), + "transmission_control_code": str(getattr(item, "transmission_control_code", "xxx")), + } + ) + elif isinstance(item, dict): + options.append(dict(item)) + else: + options.append(self._valueToEditorText(item)) + + return options + + def _extractIpOptionFields(self, payload: Dict[str, Any]) -> Dict[str, Any]: + optionFields = dict(payload) + optionFields.pop("type", None) + + routers = optionFields.get("routers") + if isinstance(routers, str): + optionFields["routers"] = [router.strip() for router in routers.split(",") if router.strip()] + + if "timestamp" in optionFields: + optionFields["timestamp"] = int(optionFields["timestamp"]) + if "pointer" in optionFields: + optionFields["pointer"] = int(optionFields["pointer"]) + if "flg" in optionFields: + flgValue = optionFields["flg"] + flgMap = { + "timestamp_only": 0, + "timestamp_and_ip_addr": 1, + "prespecified_ip_addr": 3, + } + if isinstance(flgValue, str) and flgValue in flgMap: + optionFields["flg"] = flgMap[flgValue] + if "security" in optionFields: + optionFields["security"] = int(optionFields["security"]) + if "compartment" in optionFields: + optionFields["compartment"] = int(optionFields["compartment"]) + if "handling_restrictions" in optionFields: + optionFields["handling_restrictions"] = int(optionFields["handling_restrictions"]) + + return optionFields + + def _resolveLayerKey(self, layer: Any) -> str: + exactMatch = self._findExactLayerKey(layer) + if exactMatch is not None: + return exactMatch + + for key, packetType in self._availableLayerClasses.items(): + if isinstance(layer, packetType): + return key + raise ValueError(f"Unsupported layer type: {layer.__class__.__name__}") + + def _canResolveLayerKey(self, layer: Any) -> bool: + try: + self._resolveLayerKey(layer) + return True + except ValueError: + return False + + def _findExactLayerKey(self, layer: Any) -> Optional[str]: + for key, packetType in self._availableLayerClasses.items(): + if layer.__class__ is packetType: + return key + return None + + def _serializeFieldValue(self, layer: Any, fieldName: str, value: Any) -> Any: + if layer.__class__ is self._scapy.IP and fieldName == "options": + return self._serializeIpOptionList(value) + + if layer.__class__ is self._scapy.DNS and fieldName == "qd": + return self._serializeDnsQuestionList(value) + + return self._valueToEditorText(value) + + def _describeFieldEditor( + self, + layer: Any, + field: Any, + ) -> Tuple[str, Tuple[Tuple[str, str], ...], str, str]: + if self._isMacField(field): + return "mac", (), "示例: 00:11:22:33:44:55", "" + + if self._isIPv4Field(field): + return "ipv4", (), "示例: 192.168.1.10", "" + + if self._isIPv6Field(field): + return "ipv6", (), "示例: 2001:db8::10", "" + + if self._isCollectionField(field): + collectionKind = self._describeCollectionKind(field) + return ( + "collection", + (), + "支持 Python 字面量列表/元组,例如 []、['value']。复杂 PacketList 当前优先支持清空或保持默认值。", + collectionKind, + ) + + choices = self._extractFieldChoices(layer, field) + if choices: + placeholderText = "" + if self._isEtherTypeField(layer, field): + placeholderText = "支持 0x0800、0x86DD、0x8808 等十六进制 Ethertype。" + return "enum", choices, placeholderText, "" + + if self._isBooleanField(field): + return "bool", (), "", "" + + if isinstance(field.default, bytes): + return "bytes", (), "支持直接输入文本,或输入 b'\\x00\\x01' 形式的字节串。", "" + + return "text", (), "", "" + + def _extractFieldChoices(self, layer: Any, field: Any) -> Tuple[Tuple[str, str], ...]: + unwrappedField = self._unwrapField(field) + mapping = getattr(unwrappedField, "i2s", None) + choices = [] + seenKeys: set[str] = set() + + if self._isEtherTypeField(layer, field): + for key, label in self._COMMON_ETHER_TYPE_CHOICES: + keyText = self._formatChoiceValue(layer, field, key) + choices.append((keyText, f"{label} ({keyText})")) + seenKeys.add(keyText) + + if mapping: + for key, label in mapping.items(): + keyText = self._formatChoiceValue(layer, field, key) + if keyText in seenKeys: + continue + choices.append((keyText, f"{label} ({keyText})")) + seenKeys.add(keyText) + + return tuple(choices) + + def _formatFieldValue(self, layer: Any, field: Any, value: Any) -> str: + if self._isEtherTypeField(layer, field) and isinstance(value, int): + return self._formatHexValue(value) + return self._valueToEditorText(value) + + def _formatChoiceValue(self, layer: Any, field: Any, value: Any) -> str: + if self._isEtherTypeField(layer, field) and isinstance(value, int): + return self._formatHexValue(value) + return self._valueToEditorText(value) + + def _formatHexValue(self, value: int) -> str: + return f"0x{value:04X}" + + def _isBooleanField(self, field: Any) -> bool: + unwrappedField = self._unwrapField(field) + if type(unwrappedField.default) is bool: + return True + + size = getattr(unwrappedField, "size", None) + return ( + size == 1 + and unwrappedField.default in {0, 1} + and not getattr(unwrappedField, "i2s", None) + ) + + def _isMacField(self, field: Any) -> bool: + unwrappedField = self._unwrapField(field) + return self._fieldInheritsFrom(unwrappedField, "MACField") + + def _isIPv4Field(self, field: Any) -> bool: + unwrappedField = self._unwrapField(field) + return ( + self._fieldInheritsFrom(unwrappedField, "IPField") + or field.name in {"psrc", "pdst"} + ) + + def _isIPv6Field(self, field: Any) -> bool: + unwrappedField = self._unwrapField(field) + return self._fieldInheritsFrom(unwrappedField, "IP6Field") + + def _isCollectionField(self, field: Any) -> bool: + unwrappedField = self._unwrapField(field) + return ( + isinstance(unwrappedField.default, (list, tuple)) + or unwrappedField.__class__.__name__ in { + "FieldListField", + "PacketListField", + "_DNSPacketListField", + } + ) + + def _describeCollectionKind(self, field: Any) -> str: + unwrappedField = self._unwrapField(field) + if field.name == "options" and unwrappedField.__class__.__name__ == "PacketListField": + return "ip_options" + + if unwrappedField.__class__.__name__ == "_DNSPacketListField" and field.name == "qd": + return "dns_questions" + + if isinstance(unwrappedField.default, (list, tuple)): + return "literal_list" + + return "raw" + + def _unwrapField(self, field: Any) -> Any: + return getattr(field, "fld", field) + + def _isEtherTypeField(self, layer: Any, field: Any) -> bool: + if field.name != "type": + return False + + return layer.__class__ in { + self._scapy.Ether, + self._scapy.Dot1Q, + self._scapy.Dot1AD, + } + + def _fieldInheritsFrom(self, field: Any, className: str) -> bool: + return any(base.__name__ == className for base in field.__class__.__mro__) + + def _valueToEditorText(self, value: Any) -> str: + if value is None: + return "" + if isinstance(value, bytes): + try: + return value.decode("utf-8") + except UnicodeDecodeError: + return repr(value) + if isinstance(value, str): + return value + if isinstance(value, (int, float, bool)): + return str(value) + return repr(value) \ No newline at end of file diff --git a/gui/src/packet_studio/services/pcap_analysis_service.py b/gui/src/packet_studio/services/pcap_analysis_service.py new file mode 100644 index 00000000000..c7df4709f2f --- /dev/null +++ b/gui/src/packet_studio/services/pcap_analysis_service.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from typing import Any + +from packet_studio.adapters.scapy_pcap_adapter import ScapyPcapAdapter +from packet_studio.domain.packet_models import PcapPacketRecord +from packet_studio.domain.task_models import PcapLoadResult, TaskState + + +class PcapAnalysisService: + """封装 pcap/pcapng 基础读取流程。""" + + def __init__( + self, + pcapReaderFactory: Any | None = None, + pcapAdapter: ScapyPcapAdapter | None = None, + ) -> None: + if pcapReaderFactory is None: + from scapy.utils import PcapReader + + self._pcapReaderFactory = PcapReader + else: + self._pcapReaderFactory = pcapReaderFactory + self._pcapAdapter = pcapAdapter or ScapyPcapAdapter() + + def loadPackets(self, filePath: str, maxPackets: int = 500) -> PcapLoadResult: + packetRecords: list[PcapPacketRecord] = [] + with self._pcapReaderFactory(filePath) as reader: + for index, packet in enumerate(reader, start=1): + packetRecords.append(self._pcapAdapter.buildPacketRecord(index, packet)) + if maxPackets > 0 and index >= maxPackets: + break + + summaryText = f"离线抓包文件加载完成,共 {len(packetRecords)} 个数据包。" + + return PcapLoadResult( + filePath=filePath, + packetRecords=packetRecords, + summaryText=summaryText, + logText=summaryText, + state=TaskState.succeeded("离线抓包文件加载完成。"), + ) diff --git a/gui/src/packet_studio/services/send_task_service.py b/gui/src/packet_studio/services/send_task_service.py new file mode 100644 index 00000000000..3b8ec0c4bab --- /dev/null +++ b/gui/src/packet_studio/services/send_task_service.py @@ -0,0 +1,247 @@ +from __future__ import annotations + +import time +from typing import Any, Callable, Optional + +from packet_studio.adapters.scapy_packet_adapter import ScapyPacketAdapter +from packet_studio.domain.packet_models import PacketPreview +from packet_studio.domain.task_models import SendTaskResult, TaskState + + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class SendTaskRequest: + mode: str + sendStrategy: str = "burst" + interfaceName: str = "" + count: int = 1 + intervalSeconds: float = 0.0 + timeoutSeconds: float = 1.0 + retryCount: int = 0 + + +class SendTaskService: + """封装 send、sendp 和 sr1 的最小服务层。""" + + def __init__( + self, + scapyModule: Any | None = None, + packetAdapter: ScapyPacketAdapter | None = None, + ) -> None: + if scapyModule is None: + import scapy.all as scapy + + self._scapy = scapy + else: + self._scapy = scapyModule + self._packetAdapter = packetAdapter or ScapyPacketAdapter() + + def buildPacketPreview(self, packet: Any | None) -> Optional[PacketPreview]: + if packet is None: + return None + packetCopy = self._packetAdapter.clonePacket(packet) + return self._packetAdapter.buildPreview(packetCopy) + + def execute( + self, + request: SendTaskRequest, + packetOrPackets: Any | list[Any] | None, + stopRequested: Callable[[], bool] | None = None, + sleep: Callable[[float], None] | None = None, + ) -> SendTaskResult: + packets = self._normalize_packets(packetOrPackets) + if not packets: + raise ValueError("当前没有可发送的数据包,请先在包构建器中添加协议层。") + if request.sendStrategy not in {"burst", "continuous"}: + raise ValueError(f"不支持的发送策略: {request.sendStrategy}") + if request.mode == "sr1" and request.sendStrategy == "continuous": + raise ValueError("sr1 当前仅支持 burst 模式,不支持 continuous 持续发送。") + + shouldStop = stopRequested or (lambda: False) + sleepFn = sleep or time.sleep + packetPreview = self._packetAdapter.buildPreview( + self._packetAdapter.clonePacket(packets[0]) + ) + interfaceName = request.interfaceName.strip() + streamCount = len(packets) + logLines = [ + f"执行模式: {request.mode}", + f"发送策略: {request.sendStrategy}", + f"流数量: {streamCount}", + ] + + if request.mode == "send": + sentCount, stoppedEarly = self._run_send_loop( + packets=packets, + request=request, + interfaceName=interfaceName, + shouldStop=shouldStop, + sleepFn=sleepFn, + ) + logLines.extend( + [ + "底层发送: send (L3)", + f"发送轮次: {'持续发送' if request.sendStrategy == 'continuous' else request.count}", + f"发送数量: {sentCount}", + f"发送间隔: {request.intervalSeconds:.3f}s", + ] + ) + if interfaceName: + logLines.append("L3 send 由 Scapy 路由自动选择接口,未显式传入 iface。") + state = TaskState.stopped("发送任务已停止。") if stoppedEarly else TaskState.succeeded("发送任务执行完成。") + summaryPrefix = "已停止" if stoppedEarly else "已完成" + summaryText = ( + f"模式: {request.mode},{summaryPrefix}发送 {sentCount} 个数据包," + f"共 {streamCount} 条流,未应答 0 个。" + ) + return SendTaskResult( + mode=request.mode, + sentCount=sentCount, + packetPreview=packetPreview, + answerPreview=None, + unansweredCount=0, + summaryText=summaryText, + logText="\n".join(logLines), + state=state, + ) + + if request.mode == "sendp": + sentCount, stoppedEarly = self._run_send_loop( + packets=packets, + request=request, + interfaceName=interfaceName, + shouldStop=shouldStop, + sleepFn=sleepFn, + ) + logLines.extend( + [ + "底层发送: sendp (L2)", + f"发送轮次: {'持续发送' if request.sendStrategy == 'continuous' else request.count}", + f"发送数量: {sentCount}", + f"发送间隔: {request.intervalSeconds:.3f}s", + f"发送接口: {interfaceName or '自动'}", + ] + ) + state = TaskState.stopped("发送任务已停止。") if stoppedEarly else TaskState.succeeded("发送任务执行完成。") + summaryPrefix = "已停止" if stoppedEarly else "已完成" + summaryText = ( + f"模式: {request.mode},{summaryPrefix}发送 {sentCount} 个数据包," + f"共 {streamCount} 条流,未应答 0 个。" + ) + return SendTaskResult( + mode=request.mode, + sentCount=sentCount, + packetPreview=packetPreview, + answerPreview=None, + unansweredCount=0, + summaryText=summaryText, + logText="\n".join(logLines), + state=state, + ) + + if request.mode == "sr1": + sentCount, unansweredCount, answerPreview, stoppedEarly = self._run_sr1_loop( + packets=packets, + request=request, + shouldStop=shouldStop, + ) + logLines.extend( + [ + "底层发送: sr1 (L3 请求/响应)", + f"发送轮次: {request.count}", + f"发送数量: {sentCount}", + f"超时时间: {request.timeoutSeconds:.3f}s", + f"重试次数: {request.retryCount}", + ] + ) + if interfaceName: + logLines.append("L3 sr1 由 Scapy 路由自动选择接口,未显式传入 iface。") + logLines.append("收到应答。" if answerPreview is not None else "未收到应答。") + state = TaskState.stopped("发送任务已停止。") if stoppedEarly else TaskState.succeeded("发送任务执行完成。") + summaryPrefix = "已停止" if stoppedEarly else "已完成" + summaryText = ( + f"模式: {request.mode},{summaryPrefix}发送 {sentCount} 个数据包," + f"共 {streamCount} 条流,未应答 {unansweredCount} 个。" + ) + return SendTaskResult( + mode=request.mode, + sentCount=sentCount, + packetPreview=packetPreview, + answerPreview=answerPreview, + unansweredCount=unansweredCount, + summaryText=summaryText, + logText="\n".join(logLines), + state=state, + ) + + raise ValueError(f"不支持的发送模式: {request.mode}") + + def _normalize_packets(self, packetOrPackets: Any | list[Any] | None) -> list[Any]: + if packetOrPackets is None: + return [] + if isinstance(packetOrPackets, list): + return [packet for packet in packetOrPackets if packet is not None] + return [packetOrPackets] + + def _run_send_loop( + self, + packets: list[Any], + request: SendTaskRequest, + interfaceName: str, + shouldStop: Callable[[], bool], + sleepFn: Callable[[float], None], + ) -> tuple[int, bool]: + sentCount = 0 + completedRounds = 0 + + while True: + for packet in packets: + if shouldStop(): + return sentCount, True + sendArgs: dict[str, Any] = { + "count": 1, + "inter": 0.0, + "verbose": False, + "return_packets": True, + } + if request.mode == "sendp" and interfaceName: + sendArgs["iface"] = interfaceName + sendMethod = self._scapy.sendp if request.mode == "sendp" else self._scapy.send + sentPackets = sendMethod(self._packetAdapter.clonePacket(packet), **sendArgs) + sentCount += len(sentPackets or []) + if request.intervalSeconds > 0 and not shouldStop(): + sleepFn(request.intervalSeconds) + + completedRounds += 1 + if request.sendStrategy == "burst" and completedRounds >= request.count: + return sentCount, False + + def _run_sr1_loop( + self, + packets: list[Any], + request: SendTaskRequest, + shouldStop: Callable[[], bool], + ) -> tuple[int, int, PacketPreview | None, bool]: + sentCount = 0 + unansweredCount = 0 + answerPreview: PacketPreview | None = None + + for _roundIndex in range(request.count): + for packet in packets: + if shouldStop(): + return sentCount, unansweredCount, answerPreview, True + answerPacket = self._scapy.sr1( + self._packetAdapter.clonePacket(packet), + timeout=request.timeoutSeconds, + retry=request.retryCount, + verbose=False, + ) + sentCount += 1 + if answerPacket is None: + unansweredCount += 1 + continue + answerPreview = self._packetAdapter.buildPreview(answerPacket) + + return sentCount, unansweredCount, answerPreview, False diff --git a/gui/src/packet_studio/services/tool_registry_service.py b/gui/src/packet_studio/services/tool_registry_service.py new file mode 100644 index 00000000000..1672f0d3a0c --- /dev/null +++ b/gui/src/packet_studio/services/tool_registry_service.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ToolRegistration: + toolId: str + title: str + description: str + targetTabTitle: str + category: str + + +class ToolRegistryService: + """提供最小内置工具注册表。""" + + def listTools(self) -> list[ToolRegistration]: + return [ + ToolRegistration( + toolId="packet-builder", + title="包构建器", + description="构建多层数据包,逐字段编辑并实时查看结构与十六进制预览。", + targetTabTitle="包构建器", + category="核心工作流", + ), + ToolRegistration( + toolId="send-task", + title="发送任务", + description="执行 send、sendp、sr1 任务,并查看请求响应结果。", + targetTabTitle="发送任务", + category="核心工作流", + ), + ToolRegistration( + toolId="offline-analysis", + title="离线分析", + description="打开 pcap 或 pcapng 文件,浏览离线抓包结果并复制回构建器。", + targetTabTitle="离线分析", + category="核心工作流", + ), + ] \ No newline at end of file diff --git a/gui/src/packet_studio/services/workspace_document_service.py b/gui/src/packet_studio/services/workspace_document_service.py new file mode 100644 index 00000000000..fc6657de463 --- /dev/null +++ b/gui/src/packet_studio/services/workspace_document_service.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from packet_studio.domain.task_models import TaskPhase +from packet_studio.domain.workspace_models import TaskRecord, WorkspaceDocument, WorkspacePanelSnapshot + + +class WorkspaceDocumentService: + """构建工作区快照与任务记录。""" + + def createTaskRecord( + self, + sequenceNumber: int, + sourceTitle: str, + message: str, + phase: TaskPhase, + detailText: str = "", + ) -> TaskRecord: + return TaskRecord( + sequenceNumber=sequenceNumber, + sourceTitle=sourceTitle, + message=message, + phase=phase, + detailText=detailText, + ) + + def buildWorkspaceDocument( + self, + activeTabTitle: str, + openTabTitles: list[str], + panelSnapshots: list[WorkspacePanelSnapshot], + taskRecords: list[TaskRecord], + interfaceCount: int, + interfaceSummaryText: str, + ) -> WorkspaceDocument: + return WorkspaceDocument( + activeTabTitle=activeTabTitle, + openTabTitles=list(openTabTitles), + panelSnapshots=list(panelSnapshots), + taskRecords=list(taskRecords), + interfaceCount=interfaceCount, + interfaceSummaryText=interfaceSummaryText, + ) \ No newline at end of file diff --git a/gui/src/packet_studio/validation_launcher.py b/gui/src/packet_studio/validation_launcher.py new file mode 100644 index 00000000000..fa5635a4b22 --- /dev/null +++ b/gui/src/packet_studio/validation_launcher.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import logging +import sys +from pathlib import Path +from typing import Sequence + +from packet_studio.app import configure_logging +from packet_studio.main_window import MainWindow +from packet_studio.runtime.dependency_check import AppEnvironment, collect_environment +from packet_studio.runtime.paths import ensure_runtime_directories + + +def _build_checklist_text(environment: AppEnvironment, log_file: Path) -> str: + lines = [ + "Scapy Studio Windows 验收启动器", + "", + f"Scapy 版本: {environment.scapy_version or '未检测到'}", + f"PySide6 版本: {environment.pyside6_version or '未检测到'}", + f"Npcap 状态: {environment.npcap_status}", + f"管理员权限: {'是' if environment.is_elevated else '否'}", + f"日志文件: {log_file}", + "", + "建议人工验收清单:", + "1. 在欢迎页确认环境摘要、日志目录和依赖状态。", + "2. 打开接口页,刷新并确认目标网卡是否可见。", + "3. 在包构建器中添加 IP/ICMP 或 Ether/ARP,确认摘要、结构和十六进制联动。", + "4. 在发送任务页验证 send、sendp 或 sr1 基本路径。", + "5. 在离线分析页打开 pcap 或 pcapng,验证列表、过滤、详情和复制回构包。", + "", + "说明:", + "- sendp 和部分接口能力仍依赖 Npcap、管理员权限和网卡驱动。", + "- 如果目标机只做人工验收,优先使用此启动器;自动化测试仍建议在开发机源码环境运行。", + ] + return "\n".join(lines) + + +def main(argv: Sequence[str] | None = None) -> int: + try: + from PySide6 import QtWidgets + except ModuleNotFoundError: + print( + "未安装 PySide6,无法启动 Windows 验收启动器。\n" + "请先在 gui 子项目环境中执行: pip install -e .", + file=sys.stderr, + ) + return 1 + + runtime_dirs = ensure_runtime_directories() + log_file = configure_logging(runtime_dirs.log_dir) + logging.getLogger(__name__).info("日志文件: %s", log_file) + + app_argv = list(argv) if argv is not None else sys.argv + application = QtWidgets.QApplication(app_argv) + application.setApplicationName("Scapy Studio Validation") + application.setOrganizationName("Scapy Studio") + + environment = collect_environment(runtime_dirs) + messageBox = QtWidgets.QMessageBox() + messageBox.setWindowTitle("Scapy Studio Windows 验收") + messageBox.setIcon(QtWidgets.QMessageBox.Icon.Information) + messageBox.setText("将启动 Scapy Studio GUI,并显示当前机器的验收前置信息。") + messageBox.setDetailedText(_build_checklist_text(environment, log_file)) + launchButton = messageBox.addButton("启动 GUI 验收", QtWidgets.QMessageBox.ButtonRole.AcceptRole) + messageBox.addButton("取消", QtWidgets.QMessageBox.ButtonRole.RejectRole) + messageBox.exec() + if messageBox.clickedButton() is not launchButton: + return 0 + + window = MainWindow(environment=environment, log_file=log_file) + window.show() + return application.exec() + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/gui/src/packet_studio/widgets/__init__.py b/gui/src/packet_studio/widgets/__init__.py new file mode 100644 index 00000000000..3e7a0f4e98f --- /dev/null +++ b/gui/src/packet_studio/widgets/__init__.py @@ -0,0 +1 @@ +"""UI widgets for Scapy Studio.""" \ No newline at end of file diff --git a/gui/src/packet_studio/widgets/automation_tools_widget.py b/gui/src/packet_studio/widgets/automation_tools_widget.py new file mode 100644 index 00000000000..b06066869cb --- /dev/null +++ b/gui/src/packet_studio/widgets/automation_tools_widget.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +from PySide6 import QtCore, QtWidgets + +from packet_studio.domain.task_models import TaskState +from packet_studio.domain.workspace_models import WorkspacePanelSnapshot +from packet_studio.services.tool_registry_service import ToolRegistration, ToolRegistryService + + +class AutomationToolsWidget(QtWidgets.QWidget): + """最小自动化工具入口页。""" + + openToolRequested = QtCore.Signal(str) + statusMessage = QtCore.Signal(str) + + def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: + super().__init__(parent) + self.toolRegistryService = ToolRegistryService() + self.currentTaskState = TaskState.idle("请选择一个工具入口。") + self._setup_ui() + self._populate_tools() + + def buildWorkspaceSnapshot(self) -> WorkspacePanelSnapshot: + return WorkspacePanelSnapshot( + panelId="automation-tools", + title="自动化工具", + taskState=self.currentTaskState, + itemCount=self.toolList.topLevelItemCount(), + detailText=self.statusLabel.text(), + ) + + def _setup_ui(self) -> None: + rootLayout = QtWidgets.QVBoxLayout(self) + + titleLabel = QtWidgets.QLabel("自动化工具") + titleLabel.setStyleSheet("font-size: 22px; font-weight: 700;") + subtitleLabel = QtWidgets.QLabel( + "当前阶段先提供最小工具注册入口。后续可在这里接入插件发现、专题协议工具页和自动机向导。" + ) + subtitleLabel.setWordWrap(True) + + self.toolList = QtWidgets.QTreeWidget() + self.toolList.setHeaderLabels(["工具", "分类", "说明"]) + self.toolList.header().setStretchLastSection(True) + self.toolList.itemSelectionChanged.connect(self._handle_selection_changed) + + actionLayout = QtWidgets.QHBoxLayout() + self.openToolButton = QtWidgets.QPushButton("打开选中工具") + self.openToolButton.clicked.connect(self._handle_open_tool) + self.statusLabel = QtWidgets.QLabel("请选择一个工具入口。") + self.statusLabel.setWordWrap(True) + actionLayout.addWidget(self.openToolButton) + actionLayout.addWidget(self.statusLabel, 1) + + rootLayout.addWidget(titleLabel) + rootLayout.addWidget(subtitleLabel) + rootLayout.addWidget(self.toolList, 1) + rootLayout.addLayout(actionLayout) + + def _populate_tools(self) -> None: + self.toolList.clear() + for tool in self.toolRegistryService.listTools(): + item = QtWidgets.QTreeWidgetItem([tool.title, tool.category, tool.description]) + item.setData(0, QtCore.Qt.ItemDataRole.UserRole, tool.targetTabTitle) + self.toolList.addTopLevelItem(item) + if self.toolList.topLevelItemCount() > 0: + self.toolList.setCurrentItem(self.toolList.topLevelItem(0)) + self._update_button_state() + + def _handle_selection_changed(self) -> None: + selectedItems = self.toolList.selectedItems() + if not selectedItems: + self.statusLabel.setText("请选择一个工具入口。") + self.currentTaskState = TaskState.idle(self.statusLabel.text()) + self._update_button_state() + return + + selectedTitle = selectedItems[0].text(0) + self.statusLabel.setText(f"已选择工具: {selectedTitle}") + self.currentTaskState = TaskState.idle(self.statusLabel.text()) + self._update_button_state() + + def _handle_open_tool(self) -> None: + selectedItems = self.toolList.selectedItems() + if not selectedItems: + self.statusLabel.setText("请先选择一个工具入口。") + return + + targetTabTitle = str(selectedItems[0].data(0, QtCore.Qt.ItemDataRole.UserRole)) + self.openToolRequested.emit(targetTabTitle) + self.statusLabel.setText(f"正在打开工具: {selectedItems[0].text(0)}") + self.currentTaskState = TaskState.running(self.statusLabel.text()) + self.statusMessage.emit(f"从自动化工具页打开: {selectedItems[0].text(0)}") + + def _update_button_state(self) -> None: + self.openToolButton.setEnabled(bool(self.toolList.selectedItems())) \ No newline at end of file diff --git a/gui/src/packet_studio/widgets/offline_analysis_widget.py b/gui/src/packet_studio/widgets/offline_analysis_widget.py new file mode 100644 index 00000000000..086fb156ac7 --- /dev/null +++ b/gui/src/packet_studio/widgets/offline_analysis_widget.py @@ -0,0 +1,362 @@ +from __future__ import annotations + +import collections +from typing import Optional + +from PySide6 import QtCore, QtGui, QtWidgets + +from packet_studio.domain.packet_models import PcapPacketRecord +from packet_studio.domain.task_models import PcapLoadResult, TaskError, TaskState +from packet_studio.domain.workspace_models import WorkspacePanelSnapshot +from packet_studio.services.pcap_analysis_service import ( + PcapAnalysisService, +) + + +class OfflineAnalysisWorker(QtCore.QObject): + finished = QtCore.Signal(object) + failed = QtCore.Signal(object) + + def __init__( + self, + pcapAnalysisService: PcapAnalysisService, + filePath: str, + maxPackets: int, + ) -> None: + super().__init__() + self.pcapAnalysisService = pcapAnalysisService + self.filePath = filePath + self.maxPackets = maxPackets + + @QtCore.Slot() + def run(self) -> None: + try: + result = self.pcapAnalysisService.loadPackets(self.filePath, self.maxPackets) + except Exception as exc: + self.failed.emit(TaskError(message=str(exc), logText=str(exc))) + return + self.finished.emit(result) + + +class OfflineAnalysisWidget(QtWidgets.QWidget): + """最小离线 pcap 分析工作台。""" + + statusMessage = QtCore.Signal(str) + importPacketRequested = QtCore.Signal(object) + + def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: + super().__init__(parent) + self.pcapAnalysisService = PcapAnalysisService() + self.packetRecords: list[PcapPacketRecord] = [] + self.visiblePacketIndexes: list[int] = [] + self.currentFilePath = "" + self.workerThread: Optional[QtCore.QThread] = None + self.worker: Optional[OfflineAnalysisWorker] = None + self.currentTaskState = TaskState.idle("准备就绪。") + + self._setup_ui() + self._update_button_state() + + def isLoading(self) -> bool: + return self.workerThread is not None + + def closeEvent(self, event: QtGui.QCloseEvent) -> None: + if self.isLoading(): + QtWidgets.QMessageBox.warning( + self, + "离线分析仍在加载", + "请等待当前离线加载完成后再关闭窗口。", + ) + event.ignore() + return + super().closeEvent(event) + + def buildWorkspaceSnapshot(self) -> WorkspacePanelSnapshot: + detailText = self.currentFilePath or self.summaryLabel.text() + return WorkspacePanelSnapshot( + panelId="offline-analysis", + title="离线分析", + taskState=self.currentTaskState, + itemCount=len(self.packetRecords), + detailText=detailText, + ) + + def _setup_ui(self) -> None: + rootLayout = QtWidgets.QVBoxLayout(self) + + controlsLayout = QtWidgets.QGridLayout() + controlsLayout.addWidget(QtWidgets.QLabel("文件"), 0, 0) + self.filePathEdit = QtWidgets.QLineEdit() + self.filePathEdit.setReadOnly(True) + controlsLayout.addWidget(self.filePathEdit, 0, 1, 1, 3) + + controlsLayout.addWidget(QtWidgets.QLabel("读取上限"), 1, 0) + self.maxPacketsSpin = QtWidgets.QSpinBox() + self.maxPacketsSpin.setRange(1, 1000000) + self.maxPacketsSpin.setValue(500) + controlsLayout.addWidget(self.maxPacketsSpin, 1, 1) + + controlsLayout.addWidget(QtWidgets.QLabel("显示过滤"), 1, 2) + self.searchEdit = QtWidgets.QLineEdit() + self.searchEdit.setPlaceholderText("按摘要、时间、接口、协议关键字搜索") + self.searchEdit.textChanged.connect(self._handle_search_changed) + controlsLayout.addWidget(self.searchEdit, 1, 3) + + actionLayout = QtWidgets.QHBoxLayout() + self.openFileButton = QtWidgets.QPushButton("打开 pcap") + self.reloadButton = QtWidgets.QPushButton("重新加载") + self.copyToBuilderButton = QtWidgets.QPushButton("复制到包构建器") + self.statusLabel = QtWidgets.QLabel("准备就绪。") + self.statusLabel.setWordWrap(True) + self.openFileButton.clicked.connect(self._handle_open_file) + self.reloadButton.clicked.connect(self._handle_reload) + self.copyToBuilderButton.clicked.connect(self._handle_copy_to_builder) + actionLayout.addWidget(self.openFileButton) + actionLayout.addWidget(self.reloadButton) + actionLayout.addWidget(self.copyToBuilderButton) + actionLayout.addWidget(self.statusLabel, 1) + + contentSplitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal) + + tablePane = QtWidgets.QWidget() + tableLayout = QtWidgets.QVBoxLayout(tablePane) + self.summaryLabel = QtWidgets.QLabel("尚未打开离线抓包文件。") + self.summaryLabel.setWordWrap(True) + tableLayout.addWidget(self.summaryLabel) + self.statsLabel = QtWidgets.QLabel("") + self.statsLabel.setWordWrap(True) + tableLayout.addWidget(self.statsLabel) + self.packetTable = QtWidgets.QTableWidget(0, 4) + self.packetTable.setHorizontalHeaderLabels(["序号", "时间", "接口", "摘要"]) + self.packetTable.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) + self.packetTable.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) + self.packetTable.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection) + self.packetTable.verticalHeader().setVisible(False) + self.packetTable.horizontalHeader().setStretchLastSection(True) + self.packetTable.itemSelectionChanged.connect(self._handle_packet_selection_changed) + tableLayout.addWidget(self.packetTable, 1) + + detailPane = QtWidgets.QWidget() + detailLayout = QtWidgets.QVBoxLayout(detailPane) + self.packetDetailSummary = QtWidgets.QLineEdit() + self.packetDetailSummary.setReadOnly(True) + detailLayout.addWidget(self.packetDetailSummary) + detailTabs = QtWidgets.QTabWidget() + self.packetStructureEdit = QtWidgets.QPlainTextEdit() + self.packetStructureEdit.setReadOnly(True) + self.packetHexdumpEdit = QtWidgets.QPlainTextEdit() + self.packetHexdumpEdit.setReadOnly(True) + detailTabs.addTab(self.packetStructureEdit, "结构") + detailTabs.addTab(self.packetHexdumpEdit, "十六进制") + detailLayout.addWidget(detailTabs, 1) + + contentSplitter.addWidget(tablePane) + contentSplitter.addWidget(detailPane) + contentSplitter.setStretchFactor(0, 1) + contentSplitter.setStretchFactor(1, 1) + + rootLayout.addLayout(controlsLayout) + rootLayout.addLayout(actionLayout) + rootLayout.addWidget(contentSplitter, 1) + + def _handle_open_file(self) -> None: + filePath, _ = QtWidgets.QFileDialog.getOpenFileName( + self, + "打开离线抓包文件", + "", + "Capture Files (*.pcap *.pcapng *.cap *.pcap.gz *.pcapng.gz);;All Files (*)", + ) + if not filePath: + return + self._start_loading(filePath) + + def _handle_reload(self) -> None: + if not self.currentFilePath: + self.statusLabel.setText("请先打开一个离线抓包文件。") + return + self._start_loading(self.currentFilePath) + + def _handle_copy_to_builder(self) -> None: + selectedItems = self.packetTable.selectedItems() + if not selectedItems: + self.statusLabel.setText("请先选择一个离线数据包。") + return + + rowIndex = int(selectedItems[0].data(QtCore.Qt.ItemDataRole.UserRole)) + record = self.packetRecords[rowIndex] + self.importPacketRequested.emit(record.packet.copy()) + self.statusLabel.setText("已将选中离线数据包发送到包构建器。") + self.statusMessage.emit("已将选中离线数据包复制到包构建器。") + + def _handle_search_changed(self, _text: str) -> None: + self._rebuild_packet_table() + + def _start_loading(self, filePath: str) -> None: + if self.isLoading(): + self.statusLabel.setText("离线文件仍在加载中,请稍候。") + return + + self.currentFilePath = filePath + self.filePathEdit.setText(filePath) + self._apply_task_state(TaskState.running("正在后台加载离线抓包文件...")) + self.summaryLabel.setText("正在读取离线抓包文件...") + self._set_loading_state(True) + + thread = QtCore.QThread(self) + worker = OfflineAnalysisWorker( + self.pcapAnalysisService, + filePath, + int(self.maxPacketsSpin.value()), + ) + worker.moveToThread(thread) + thread.started.connect(worker.run) + worker.finished.connect(self._on_load_finished) + worker.failed.connect(self._on_load_failed) + worker.finished.connect(thread.quit) + worker.failed.connect(thread.quit) + thread.finished.connect(worker.deleteLater) + thread.finished.connect(self._on_thread_finished) + + self.workerThread = thread + self.worker = worker + thread.start() + + @QtCore.Slot(object) + def _on_load_finished(self, result: PcapLoadResult) -> None: + self.currentFilePath = result.filePath + self.filePathEdit.setText(result.filePath) + self.packetRecords = list(result.packetRecords) + self.packetDetailSummary.clear() + self.packetStructureEdit.clear() + self.packetHexdumpEdit.clear() + self._rebuild_packet_table() + self._apply_task_state(result.state) + self.statusMessage.emit(result.summaryText) + + @QtCore.Slot(object) + def _on_load_failed(self, error: TaskError) -> None: + self.packetRecords = [] + self.packetTable.clearContents() + self.packetTable.setRowCount(0) + self.packetDetailSummary.clear() + self.packetStructureEdit.clear() + self.packetHexdumpEdit.clear() + self.summaryLabel.setText(f"离线抓包文件加载失败: {error.summaryText}") + self.statsLabel.clear() + self._apply_task_state(error.state) + self.statusMessage.emit(f"离线抓包文件加载失败: {error.summaryText}") + + def _on_thread_finished(self) -> None: + self.workerThread = None + self.worker = None + self._set_loading_state(False) + self._update_button_state() + + def _handle_packet_selection_changed(self) -> None: + selectedItems = self.packetTable.selectedItems() + self._update_button_state() + if not selectedItems: + return + sourceIndex = int(selectedItems[0].data(QtCore.Qt.ItemDataRole.UserRole)) + record = self.packetRecords[sourceIndex] + self.packetDetailSummary.setText(record.preview.summary) + self.packetStructureEdit.setPlainText(record.preview.structure) + self.packetHexdumpEdit.setPlainText(record.preview.hexdump) + + def _set_loading_state(self, isLoading: bool) -> None: + self.openFileButton.setEnabled(not isLoading) + self.reloadButton.setEnabled(not isLoading and bool(self.currentFilePath)) + self.maxPacketsSpin.setEnabled(not isLoading) + self.packetTable.setEnabled(not isLoading) + + def _update_button_state(self) -> None: + hasSelection = self.packetTable.currentRow() >= 0 + isLoading = self.isLoading() + self.copyToBuilderButton.setEnabled(hasSelection and not isLoading) + self.reloadButton.setEnabled((not isLoading) and bool(self.currentFilePath)) + + def _apply_task_state(self, state: TaskState) -> None: + self.currentTaskState = state + self.statusLabel.setText(state.statusText) + + def _rebuild_packet_table(self) -> None: + previousSourceIndex = self._current_selected_source_index() + self.visiblePacketIndexes = self._filtered_packet_indexes() + self.packetTable.clearContents() + self.packetTable.setRowCount(len(self.visiblePacketIndexes)) + + for rowIndex, sourceIndex in enumerate(self.visiblePacketIndexes): + record = self.packetRecords[sourceIndex] + values = [ + str(record.index), + record.timestampText, + record.sourceText, + record.summary, + ] + for columnIndex, value in enumerate(values): + item = QtWidgets.QTableWidgetItem(value) + item.setData(QtCore.Qt.ItemDataRole.UserRole, sourceIndex) + self.packetTable.setItem(rowIndex, columnIndex, item) + + self._refresh_summary_labels() + + if not self.visiblePacketIndexes: + self.packetDetailSummary.clear() + self.packetStructureEdit.clear() + self.packetHexdumpEdit.clear() + return + + if previousSourceIndex in self.visiblePacketIndexes: + self.packetTable.selectRow(self.visiblePacketIndexes.index(previousSourceIndex)) + return + + self.packetTable.selectRow(0) + + def _filtered_packet_indexes(self) -> list[int]: + query = self.searchEdit.text().strip().lower() + if not query: + return list(range(len(self.packetRecords))) + + visibleIndexes = [] + for index, record in enumerate(self.packetRecords): + haystack = "\n".join([ + record.summary, + record.timestampText, + record.sourceText, + record.protocolName, + ]).lower() + if query in haystack: + visibleIndexes.append(index) + return visibleIndexes + + def _refresh_summary_labels(self) -> None: + totalCount = len(self.packetRecords) + visibleCount = len(self.visiblePacketIndexes) + if totalCount == 0: + self.summaryLabel.setText("尚未打开离线抓包文件。") + self.statsLabel.clear() + return + + if visibleCount == totalCount: + self.summaryLabel.setText( + f"已从 {self.currentFilePath} 读取 {totalCount} 个数据包。" + ) + else: + self.summaryLabel.setText( + f"已从 {self.currentFilePath} 读取 {totalCount} 个数据包,当前显示 {visibleCount} 个。" + ) + + counter = collections.Counter( + self.packetRecords[index].protocolName + for index in self.visiblePacketIndexes + ) + statsText = "、".join( + f"{protocol}: {count}" for protocol, count in counter.most_common(4) + ) + self.statsLabel.setText(f"基础统计: {statsText}" if statsText else "") + + def _current_selected_source_index(self) -> int | None: + selectedItems = self.packetTable.selectedItems() + if not selectedItems: + return None + return int(selectedItems[0].data(QtCore.Qt.ItemDataRole.UserRole)) diff --git a/gui/src/packet_studio/widgets/packet_builder_widget.py b/gui/src/packet_studio/widgets/packet_builder_widget.py new file mode 100644 index 00000000000..8fbb1dfd4c8 --- /dev/null +++ b/gui/src/packet_studio/widgets/packet_builder_widget.py @@ -0,0 +1,1319 @@ +from __future__ import annotations + +import ast +import ipaddress +import re +from pathlib import Path + +from PySide6 import QtCore, QtGui, QtWidgets + +from packet_studio.domain.task_models import TaskState +from packet_studio.domain.workspace_models import WorkspacePanelSnapshot +from packet_studio.services.packet_builder_service import AvailableLayer, LayerFieldRecord, PacketBuilderService + + +class LayerListWidget(QtWidgets.QListWidget): + orderChanged = QtCore.Signal() + + def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: + super().__init__(parent) + self.setDragDropMode(QtWidgets.QAbstractItemView.DragDropMode.InternalMove) + self.setDefaultDropAction(QtCore.Qt.DropAction.MoveAction) + + def dropEvent(self, event: QtGui.QDropEvent) -> None: + super().dropEvent(event) + self.orderChanged.emit() + + +class PacketBuilderWidget(QtWidgets.QWidget): + """最小可用包构建器。""" + + packetChanged = QtCore.Signal(object) + createStreamRequested = QtCore.Signal(object) + saveStreamRequested = QtCore.Signal(object) + + def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: + super().__init__(parent) + self.packetBuilderService = PacketBuilderService() + self._availableLayers = self.packetBuilderService.listAvailableLayers() + self._updatingFieldTable = False + self._filteredFieldNames: list[str] = [] + self._visibleFieldRecords: dict[str, LayerFieldRecord] = {} + self._editingStreamMode = False + + self._setup_ui() + self._refresh_all() + + def getCurrentPacket(self) -> object | None: + packet = self.packetBuilderService.buildPacket() + if packet is None: + return None + return packet.copy() + + def buildWorkspaceSnapshot(self) -> WorkspacePanelSnapshot: + statusText = self.builderStatusLabel.text() + taskState = TaskState.failed(statusText) if "失败" in statusText else TaskState.idle(statusText) + return WorkspacePanelSnapshot( + panelId="packet-builder", + title="包构建器", + taskState=taskState, + itemCount=len(self.packetBuilderService.getLayerRecords()), + detailText=self.summaryEdit.text(), + ) + + def loadPacket(self, packet: object) -> None: + try: + self.packetBuilderService.importPacket(packet) + except Exception as exc: + self.builderStatusLabel.setText(f"导入数据包失败: {exc}") + raise + + self.builderStatusLabel.setText("已导入当前数据包。") + self._refresh_all(selectLast=False, selectedRow=0) + + def _setup_ui(self) -> None: + rootLayout = QtWidgets.QVBoxLayout(self) + + controlsLayout = QtWidgets.QHBoxLayout() + self.layerCategoryCombo = QtWidgets.QComboBox() + self.layerCategoryCombo.addItem("全部", "全部") + for category in self.packetBuilderService.listAvailableLayerCategories(): + self.layerCategoryCombo.addItem(category, category) + self.layerTypeCombo = QtWidgets.QComboBox() + self.layerTypeCombo.setEditable(True) + self.layerTypeCombo.setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.NoInsert) + self.layerCategoryCombo.currentIndexChanged.connect(self._handle_layer_category_changed) + self._rebuild_layer_type_combo(preferredKey="ip") + + self.addLayerButton = QtWidgets.QPushButton("添加层") + self.removeLayerButton = QtWidgets.QPushButton("删除选中层") + self.moveLayerUpButton = QtWidgets.QPushButton("上移") + self.moveLayerDownButton = QtWidgets.QPushButton("下移") + self.createStreamButton = QtWidgets.QPushButton("创建流") + self.saveStreamButton = QtWidgets.QPushButton("保存到当前流") + self.saveTemplateButton = QtWidgets.QPushButton("保存模板") + self.loadTemplateButton = QtWidgets.QPushButton("加载模板") + self.resetButton = QtWidgets.QPushButton("重置") + self.builderStatusLabel = QtWidgets.QLabel("准备就绪。") + self.builderStatusLabel.setWordWrap(True) + + self.addLayerButton.clicked.connect(self._handle_add_layer) + self.removeLayerButton.clicked.connect(self._handle_remove_layer) + self.moveLayerUpButton.clicked.connect(self._handle_move_layer_up) + self.moveLayerDownButton.clicked.connect(self._handle_move_layer_down) + self.createStreamButton.clicked.connect(self._handle_create_stream) + self.saveStreamButton.clicked.connect(self._handle_save_stream) + self.saveTemplateButton.clicked.connect(self._handle_save_template) + self.loadTemplateButton.clicked.connect(self._handle_load_template) + self.resetButton.clicked.connect(self._handle_reset) + + controlsLayout.addWidget(QtWidgets.QLabel("分类")) + controlsLayout.addWidget(self.layerCategoryCombo) + controlsLayout.addWidget(QtWidgets.QLabel("层类型")) + controlsLayout.addWidget(self.layerTypeCombo) + controlsLayout.addWidget(self.addLayerButton) + controlsLayout.addWidget(self.removeLayerButton) + controlsLayout.addWidget(self.moveLayerUpButton) + controlsLayout.addWidget(self.moveLayerDownButton) + controlsLayout.addWidget(self.createStreamButton) + controlsLayout.addWidget(self.saveStreamButton) + controlsLayout.addWidget(self.saveTemplateButton) + controlsLayout.addWidget(self.loadTemplateButton) + controlsLayout.addWidget(self.resetButton) + controlsLayout.addWidget(self.builderStatusLabel, 1) + + contentSplitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal) + + leftPane = QtWidgets.QWidget() + leftLayout = QtWidgets.QVBoxLayout(leftPane) + leftLayout.addWidget(QtWidgets.QLabel("当前协议层")) + self.layerList = LayerListWidget() + self.layerList.currentRowChanged.connect(self._handle_layer_selection_changed) + self.layerList.orderChanged.connect(self._handle_layer_reordered) + leftLayout.addWidget(self.layerList, 1) + + middlePane = QtWidgets.QWidget() + middleLayout = QtWidgets.QVBoxLayout(middlePane) + middleLayout.addWidget(QtWidgets.QLabel("字段编辑")) + self.fieldSearchEdit = QtWidgets.QLineEdit() + self.fieldSearchEdit.setPlaceholderText("搜索字段名或字段类型") + self.fieldSearchEdit.textChanged.connect(self._handle_field_search_changed) + middleLayout.addWidget(self.fieldSearchEdit) + self.fieldTable = QtWidgets.QTableWidget(0, 4) + self.fieldTable.setHorizontalHeaderLabels(["字段", "类型", "默认值", "当前值"]) + self.fieldTable.verticalHeader().setVisible(False) + self.fieldTable.horizontalHeader().setStretchLastSection(True) + self.fieldTable.setSelectionBehavior( + QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows + ) + self.fieldTable.itemChanged.connect(self._handle_field_item_changed) + middleLayout.addWidget(self.fieldTable, 1) + + self.rawPayloadGroup = QtWidgets.QGroupBox("Raw Payload 编辑") + rawPayloadLayout = QtWidgets.QVBoxLayout(self.rawPayloadGroup) + rawPayloadHint = QtWidgets.QLabel( + "当选中 Raw 层时,可在这里用多行文本编辑 load 字段。" + ) + rawPayloadHint.setWordWrap(True) + self.rawPayloadEdit = QtWidgets.QPlainTextEdit() + self.applyRawPayloadButton = QtWidgets.QPushButton("应用 Payload") + self.applyRawPayloadButton.clicked.connect(self._handle_apply_raw_payload) + rawPayloadLayout.addWidget(rawPayloadHint) + rawPayloadLayout.addWidget(self.rawPayloadEdit, 1) + rawPayloadLayout.addWidget(self.applyRawPayloadButton) + self.rawPayloadGroup.setVisible(False) + middleLayout.addWidget(self.rawPayloadGroup) + + rightPane = QtWidgets.QWidget() + rightLayout = QtWidgets.QVBoxLayout(rightPane) + rightLayout.addWidget(QtWidgets.QLabel("摘要")) + self.summaryEdit = QtWidgets.QLineEdit() + self.summaryEdit.setReadOnly(True) + rightLayout.addWidget(self.summaryEdit) + + previewTabs = QtWidgets.QTabWidget() + self.structureEdit = QtWidgets.QPlainTextEdit() + self.structureEdit.setReadOnly(True) + self.hexdumpEdit = QtWidgets.QPlainTextEdit() + self.hexdumpEdit.setReadOnly(True) + previewTabs.addTab(self.structureEdit, "结构") + previewTabs.addTab(self.hexdumpEdit, "十六进制") + rightLayout.addWidget(previewTabs, 1) + + contentSplitter.addWidget(leftPane) + contentSplitter.addWidget(middlePane) + contentSplitter.addWidget(rightPane) + contentSplitter.setStretchFactor(0, 0) + contentSplitter.setStretchFactor(1, 1) + contentSplitter.setStretchFactor(2, 1) + + rootLayout.addLayout(controlsLayout) + rootLayout.addWidget(contentSplitter, 1) + self._update_stream_buttons() + + def setEditingStreamMode(self, isEditing: bool) -> None: + self._editingStreamMode = isEditing + self._update_stream_buttons() + + def _handle_add_layer(self) -> None: + layerKey = self._resolve_selected_layer_key() + if layerKey is None: + self.builderStatusLabel.setText("请选择有效的协议层。") + return + self.packetBuilderService.addLayer(layerKey) + self.builderStatusLabel.setText(f"已添加协议层: {self.layerTypeCombo.currentText()}") + self._refresh_all(selectLast=True) + + def _resolve_selected_layer_key(self) -> str | None: + currentText = self.layerTypeCombo.currentText().strip() + currentIndex = self.layerTypeCombo.currentIndex() + + if currentIndex >= 0 and self.layerTypeCombo.itemText(currentIndex) == currentText: + currentData = self.layerTypeCombo.itemData(currentIndex) + if isinstance(currentData, str) and currentData: + return currentData + + normalizedText = currentText.casefold() + for itemIndex in range(self.layerTypeCombo.count()): + if self.layerTypeCombo.itemText(itemIndex).casefold() != normalizedText: + continue + self.layerTypeCombo.setCurrentIndex(itemIndex) + currentData = self.layerTypeCombo.itemData(itemIndex) + if isinstance(currentData, str) and currentData: + return currentData + + return None + + def _handle_layer_category_changed(self) -> None: + self._rebuild_layer_type_combo() + + def _rebuild_layer_type_combo(self, preferredKey: str | None = None) -> None: + currentKey = preferredKey or self._resolve_combo_selected_key(self.layerTypeCombo) + selectedCategory = str(self.layerCategoryCombo.currentData() or "全部") + visibleLayers = self._filter_layers_by_category(selectedCategory) + + self.layerTypeCombo.blockSignals(True) + self.layerTypeCombo.clear() + defaultIndex = 0 + for index, layer in enumerate(visibleLayers): + self.layerTypeCombo.addItem(layer.label, layer.key) + if layer.key == currentKey: + defaultIndex = index + elif currentKey is None and layer.key == "ip": + defaultIndex = index + if self.layerTypeCombo.count() > 0: + self.layerTypeCombo.setCurrentIndex(defaultIndex) + self.layerTypeCombo.blockSignals(False) + self.layerTypeCombo.lineEdit().setPlaceholderText("搜索协议层") + self._install_contains_completer(self.layerTypeCombo) + + def _filter_layers_by_category(self, category: str) -> list[AvailableLayer]: + if category == "全部": + return list(self._availableLayers) + return [layer for layer in self._availableLayers if layer.category == category] + + def _resolve_combo_selected_key(self, comboBox: QtWidgets.QComboBox) -> str | None: + currentIndex = comboBox.currentIndex() + if currentIndex < 0: + return None + currentData = comboBox.itemData(currentIndex) + if isinstance(currentData, str) and currentData: + return currentData + return None + + def _install_contains_completer(self, comboBox: QtWidgets.QComboBox) -> None: + completer = QtWidgets.QCompleter( + [comboBox.itemText(index) for index in range(comboBox.count())], + comboBox, + ) + completer.setCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive) + completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains) + comboBox.setCompleter(completer) + + def _handle_remove_layer(self) -> None: + layerIndex = self.layerList.currentRow() + if layerIndex < 0: + self.builderStatusLabel.setText("请先选择要删除的协议层。") + return + self.packetBuilderService.removeLayer(layerIndex) + self.builderStatusLabel.setText("已删除选中协议层。") + self._refresh_all() + + def _handle_move_layer_up(self) -> None: + layerIndex = self.layerList.currentRow() + if layerIndex <= 0: + self.builderStatusLabel.setText("当前协议层已经在最上方。") + return + self.packetBuilderService.moveLayer(layerIndex, layerIndex - 1) + self.builderStatusLabel.setText("已上移选中协议层。") + self._refresh_all(selectedRow=layerIndex - 1) + + def _handle_move_layer_down(self) -> None: + layerIndex = self.layerList.currentRow() + if layerIndex < 0 or layerIndex >= self.layerList.count() - 1: + self.builderStatusLabel.setText("当前协议层已经在最下方。") + return + self.packetBuilderService.moveLayer(layerIndex, layerIndex + 1) + self.builderStatusLabel.setText("已下移选中协议层。") + self._refresh_all(selectedRow=layerIndex + 1) + + def _handle_reset(self) -> None: + self.packetBuilderService.reset() + self.builderStatusLabel.setText("包构建器已重置。") + self._refresh_all() + + def _handle_create_stream(self) -> None: + packet = self.getCurrentPacket() + if packet is None: + self.builderStatusLabel.setText("当前没有可创建流模板的数据包。") + return + self.createStreamRequested.emit(packet) + self.builderStatusLabel.setText("已创建流模板,可在发送任务中勾选发送。") + + def _handle_save_stream(self) -> None: + packet = self.getCurrentPacket() + if packet is None: + self.builderStatusLabel.setText("当前没有可保存回流模板的数据包。") + return + self.saveStreamRequested.emit(packet) + + def _handle_save_template(self) -> None: + filePath, _ = QtWidgets.QFileDialog.getSaveFileName( + self, + "保存包模板", + str(Path.home() / "packet-template.json"), + "JSON Files (*.json)", + ) + if not filePath: + return + self._save_template_to_path(filePath) + + def _handle_load_template(self) -> None: + filePath, _ = QtWidgets.QFileDialog.getOpenFileName( + self, + "加载包模板", + str(Path.home()), + "JSON Files (*.json)", + ) + if not filePath: + return + self._load_template_from_path(filePath) + + def _handle_layer_selection_changed(self, _currentRow: int) -> None: + self._refresh_field_table() + self._refresh_layer_actions() + self._refresh_raw_payload_editor() + + def _update_stream_buttons(self) -> None: + self.saveStreamButton.setEnabled(self._editingStreamMode) + + def _handle_field_search_changed(self, _text: str) -> None: + self._refresh_field_table() + + def _handle_layer_reordered(self) -> None: + order = [] + for rowIndex in range(self.layerList.count()): + item = self.layerList.item(rowIndex) + order.append(int(item.data(QtCore.Qt.ItemDataRole.UserRole))) + self.packetBuilderService.reorderLayers(order) + currentRow = self.layerList.currentRow() + self.builderStatusLabel.setText("已通过拖拽调整协议层顺序。") + self._refresh_all(selectedRow=currentRow) + + def _handle_apply_raw_payload(self) -> None: + layerIndex = self.layerList.currentRow() + if layerIndex < 0: + return + try: + self.packetBuilderService.setFieldValue( + layerIndex, + "load", + self.rawPayloadEdit.toPlainText(), + ) + except Exception as exc: + self.builderStatusLabel.setText(f"Raw Payload 更新失败: {exc}") + return + + self.builderStatusLabel.setText("已更新 Raw Payload。") + self._refresh_all(selectedRow=layerIndex) + + def _save_template_to_path(self, filePath: str) -> None: + try: + self.packetBuilderService.saveTemplate(filePath) + except Exception as exc: + self.builderStatusLabel.setText(f"模板保存失败: {exc}") + return + self.builderStatusLabel.setText(f"模板已保存: {filePath}") + + def _load_template_from_path(self, filePath: str) -> None: + try: + self.packetBuilderService.loadTemplate(filePath) + except Exception as exc: + self.builderStatusLabel.setText(f"模板加载失败: {exc}") + return + self.builderStatusLabel.setText(f"模板已加载: {filePath}") + self._refresh_all() + + def _handle_field_item_changed(self, item: QtWidgets.QTableWidgetItem) -> None: + if self._updatingFieldTable: + return + if item.column() != 3: + return + + layerIndex = self.layerList.currentRow() + if layerIndex < 0: + return + + fieldNameItem = self.fieldTable.item(item.row(), 0) + if fieldNameItem is None: + return + + self._apply_field_value(fieldNameItem.text(), item.text()) + + def _refresh_all( + self, + selectLast: bool = False, + selectedRow: int | None = None, + ) -> None: + self._refresh_layer_list(preserveSelection=not selectLast) + if selectedRow is not None and 0 <= selectedRow < self.layerList.count(): + self.layerList.setCurrentRow(selectedRow) + elif selectLast and self.layerList.count() > 0: + self.layerList.setCurrentRow(self.layerList.count() - 1) + elif self.layerList.currentRow() < 0 and self.layerList.count() > 0: + self.layerList.setCurrentRow(0) + self._refresh_field_table() + self._refresh_layer_actions() + self._refresh_raw_payload_editor() + self._refresh_previews() + self.removeLayerButton.setEnabled(self.layerList.count() > 0) + + def _refresh_layer_list(self, preserveSelection: bool = False) -> None: + selectedRow = self.layerList.currentRow() + self.layerList.clear() + for layerRecord in self.packetBuilderService.getLayerRecords(): + item = QtWidgets.QListWidgetItem( + f"{layerRecord.index + 1}. {layerRecord.name} [{layerRecord.summary}]" + ) + item.setData(QtCore.Qt.ItemDataRole.UserRole, layerRecord.index) + self.layerList.addItem(item) + if preserveSelection and 0 <= selectedRow < self.layerList.count(): + self.layerList.setCurrentRow(selectedRow) + + def _refresh_field_table(self) -> None: + layerIndex = self.layerList.currentRow() + self._updatingFieldTable = True + self.fieldTable.clearContents() + self._filteredFieldNames = [] + if layerIndex < 0: + self.fieldTable.setRowCount(0) + self._visibleFieldRecords = {} + self._updatingFieldTable = False + return + + fieldRecords = self.packetBuilderService.getFieldRecords(layerIndex) + searchText = self.fieldSearchEdit.text().strip().lower() + if searchText: + fieldRecords = [ + fieldRecord for fieldRecord in fieldRecords + if searchText in fieldRecord.name.lower() + or searchText in fieldRecord.fieldType.lower() + ] + self._visibleFieldRecords = { + fieldRecord.name: fieldRecord for fieldRecord in fieldRecords + } + self._filteredFieldNames = [fieldRecord.name for fieldRecord in fieldRecords] + self.fieldTable.setRowCount(len(fieldRecords)) + for rowIndex, fieldRecord in enumerate(fieldRecords): + nameItem = QtWidgets.QTableWidgetItem(fieldRecord.name) + nameItem.setFlags(nameItem.flags() & ~QtCore.Qt.ItemFlag.ItemIsEditable) + typeItem = QtWidgets.QTableWidgetItem(fieldRecord.fieldType) + typeItem.setFlags(typeItem.flags() & ~QtCore.Qt.ItemFlag.ItemIsEditable) + defaultItem = QtWidgets.QTableWidgetItem(fieldRecord.defaultValue) + defaultItem.setFlags(defaultItem.flags() & ~QtCore.Qt.ItemFlag.ItemIsEditable) + self.fieldTable.setItem(rowIndex, 0, nameItem) + self.fieldTable.setItem(rowIndex, 1, typeItem) + self.fieldTable.setItem(rowIndex, 2, defaultItem) + self._populate_value_cell(rowIndex, fieldRecord) + self._updatingFieldTable = False + + def _refresh_layer_actions(self) -> None: + currentRow = self.layerList.currentRow() + layerCount = self.layerList.count() + hasSelection = currentRow >= 0 + self.removeLayerButton.setEnabled(hasSelection) + self.moveLayerUpButton.setEnabled(hasSelection and currentRow > 0) + self.moveLayerDownButton.setEnabled(hasSelection and currentRow < layerCount - 1) + + def _refresh_raw_payload_editor(self) -> None: + layerIndex = self.layerList.currentRow() + isRawLayer = False + if layerIndex >= 0: + layerRecord = self.packetBuilderService.getLayerRecords()[layerIndex] + isRawLayer = layerRecord.name == "Raw" + self.rawPayloadGroup.setVisible(isRawLayer) + if isRawLayer: + self.rawPayloadEdit.setPlainText( + self.packetBuilderService.getFieldValue(layerIndex, "load") + ) + + def _refresh_previews(self) -> None: + packet = self.packetBuilderService.buildPacket() + if packet is None: + self.summaryEdit.setText("尚未添加任何协议层。") + self.structureEdit.setPlainText("尚未添加任何协议层。") + self.hexdumpEdit.setPlainText("") + self.packetChanged.emit(None) + return + + self.summaryEdit.setText(self.packetBuilderService.buildSummary()) + self.structureEdit.setPlainText(self.packetBuilderService.buildStructureDump()) + self.hexdumpEdit.setPlainText(self.packetBuilderService.buildHexdump()) + + self.packetChanged.emit(packet.copy()) + + def _populate_value_cell(self, rowIndex: int, fieldRecord: LayerFieldRecord) -> None: + if fieldRecord.editorKind == "collection": + container = QtWidgets.QWidget() + layout = QtWidgets.QHBoxLayout(container) + layout.setContentsMargins(0, 0, 0, 0) + valuePreview = QtWidgets.QLineEdit(self._collection_display_text(fieldRecord.currentValue)) + valuePreview.setReadOnly(True) + valuePreview.setPlaceholderText(fieldRecord.placeholderText) + editButton = QtWidgets.QPushButton("编辑...") + editButton.clicked.connect( + lambda _checked=False, record=fieldRecord: self._open_collection_editor(record) + ) + layout.addWidget(valuePreview, 1) + layout.addWidget(editButton) + self.fieldTable.setCellWidget(rowIndex, 3, container) + return + + if fieldRecord.editorKind == "enum": + comboBox = QtWidgets.QComboBox() + comboBox.setEditable(True) + comboBox.setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.NoInsert) + comboBox.addItem("使用默认值", "") + currentIndex = 0 + for choiceValue, choiceLabel in fieldRecord.choices: + comboBox.addItem(choiceLabel, choiceValue) + if choiceValue == fieldRecord.currentValue: + currentIndex = comboBox.count() - 1 + if fieldRecord.currentValue and currentIndex == 0: + comboBox.addItem(f"自定义值 ({fieldRecord.currentValue})", fieldRecord.currentValue) + currentIndex = comboBox.count() - 1 + comboBox.setCurrentIndex(currentIndex) + completer = QtWidgets.QCompleter( + [comboBox.itemText(index) for index in range(comboBox.count())], + comboBox, + ) + completer.setCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive) + completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains) + comboBox.setCompleter(completer) + comboBox.activated.connect( + lambda _index, name=fieldRecord.name, widget=comboBox: self._handle_enum_editor_activated( + name, + widget, + ) + ) + comboBox.lineEdit().editingFinished.connect( + lambda name=fieldRecord.name, widget=comboBox: self._handle_enum_editor_editing_finished( + name, + widget, + ) + ) + self.fieldTable.setCellWidget(rowIndex, 3, comboBox) + return + + if fieldRecord.editorKind == "bool": + checkBox = QtWidgets.QCheckBox() + checkBox.setChecked(fieldRecord.currentValue.lower() in {"1", "true"}) + container = QtWidgets.QWidget() + layout = QtWidgets.QHBoxLayout(container) + layout.setContentsMargins(6, 0, 6, 0) + layout.addStretch(1) + layout.addWidget(checkBox) + layout.addStretch(1) + checkBox.toggled.connect( + lambda checked, name=fieldRecord.name: self._handle_editor_value_changed( + name, + "1" if checked else "0", + ) + ) + self.fieldTable.setCellWidget(rowIndex, 3, container) + return + + if fieldRecord.editorKind == "bytes": + lineEdit = QtWidgets.QLineEdit(fieldRecord.currentValue) + lineEdit.setPlaceholderText(fieldRecord.placeholderText) + lineEdit.setClearButtonEnabled(True) + lineEdit.editingFinished.connect( + lambda name=fieldRecord.name, widget=lineEdit: self._handle_editor_value_changed( + name, + widget.text(), + ) + ) + self.fieldTable.setCellWidget(rowIndex, 3, lineEdit) + return + + if fieldRecord.editorKind in {"ipv4", "ipv6", "mac"}: + lineEdit = QtWidgets.QLineEdit(fieldRecord.currentValue) + lineEdit.setPlaceholderText(fieldRecord.placeholderText) + lineEdit.setClearButtonEnabled(True) + if fieldRecord.editorKind == "mac": + validator = QtGui.QRegularExpressionValidator( + QtCore.QRegularExpression(r"[0-9A-Fa-f:-]{0,17}"), + lineEdit, + ) + lineEdit.setValidator(validator) + lineEdit.editingFinished.connect( + lambda name=fieldRecord.name, widget=lineEdit: self._handle_editor_value_changed( + name, + widget.text(), + ) + ) + self.fieldTable.setCellWidget(rowIndex, 3, lineEdit) + return + + valueItem = QtWidgets.QTableWidgetItem(fieldRecord.currentValue) + self.fieldTable.setItem(rowIndex, 3, valueItem) + + def _open_collection_editor(self, fieldRecord: LayerFieldRecord) -> None: + if fieldRecord.collectionKind == "ip_options": + self._open_ip_options_editor(fieldRecord) + return + + if fieldRecord.collectionKind == "dns_questions": + self._open_dns_question_editor(fieldRecord) + return + + if fieldRecord.collectionKind == "literal_list": + self._open_literal_list_editor(fieldRecord) + return + + dialog = QtWidgets.QDialog(self) + dialog.setWindowTitle(f"编辑集合字段: {fieldRecord.name}") + dialog.resize(720, 420) + + layout = QtWidgets.QVBoxLayout(dialog) + hintLabel = QtWidgets.QLabel(fieldRecord.placeholderText or "请输入 Python 字面量集合。") + hintLabel.setWordWrap(True) + layout.addWidget(hintLabel) + + textEdit = QtWidgets.QPlainTextEdit(fieldRecord.currentValue or fieldRecord.defaultValue) + layout.addWidget(textEdit, 1) + + buttonBox = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButton.Ok + | QtWidgets.QDialogButtonBox.StandardButton.Cancel + ) + resetButton = buttonBox.addButton("恢复默认", QtWidgets.QDialogButtonBox.ButtonRole.ResetRole) + resetButton.clicked.connect(lambda: textEdit.setPlainText("")) + buttonBox.accepted.connect(dialog.accept) + buttonBox.rejected.connect(dialog.reject) + layout.addWidget(buttonBox) + + if dialog.exec() != int(QtWidgets.QDialog.DialogCode.Accepted): + return + + self._apply_field_value(fieldRecord.name, textEdit.toPlainText().strip()) + + def _open_ip_options_editor(self, fieldRecord: LayerFieldRecord) -> None: + optionItems = self._parse_ip_options_collection(fieldRecord.name) + + dialog = QtWidgets.QDialog(self) + dialog.setWindowTitle(f"编辑 IP Options: {fieldRecord.name}") + dialog.resize(860, 460) + + layout = QtWidgets.QVBoxLayout(dialog) + hintLabel = QtWidgets.QLabel( + "支持按模板添加常用 IP option:NOP、EOL、RR、LSRR、SSRR、Timestamp、Router Alert、Security。" + ) + hintLabel.setWordWrap(True) + layout.addWidget(hintLabel) + + table = QtWidgets.QTableWidget(0, 3) + table.setHorizontalHeaderLabels(["类型", "主要参数", "摘要"]) + table.horizontalHeader().setStretchLastSection(True) + table.verticalHeader().setVisible(False) + for optionItem in optionItems: + self._append_ip_option_row(table, optionItem) + layout.addWidget(table, 1) + + buttonLayout = QtWidgets.QHBoxLayout() + addMenuButton = QtWidgets.QPushButton("添加模板") + editButton = QtWidgets.QPushButton("编辑") + removeButton = QtWidgets.QPushButton("删除") + moveUpButton = QtWidgets.QPushButton("上移") + moveDownButton = QtWidgets.QPushButton("下移") + buttonLayout.addWidget(addMenuButton) + buttonLayout.addWidget(editButton) + buttonLayout.addWidget(removeButton) + buttonLayout.addWidget(moveUpButton) + buttonLayout.addWidget(moveDownButton) + buttonLayout.addStretch(1) + layout.addLayout(buttonLayout) + + menu = QtWidgets.QMenu(addMenuButton) + for optionType in ["NOP", "EOL", "RR", "LSRR", "SSRR", "Timestamp", "RouterAlert", "Security"]: + action = menu.addAction(optionType) + action.triggered.connect( + lambda _checked=False, optionType=optionType: self._add_ip_option_row(table, optionType) + ) + addMenuButton.setMenu(menu) + + editButton.clicked.connect(lambda: self._edit_ip_option_row(table)) + removeButton.clicked.connect(lambda: self._remove_table_row(table)) + moveUpButton.clicked.connect(lambda: self._move_table_row(table, -1)) + moveDownButton.clicked.connect(lambda: self._move_table_row(table, 1)) + + buttonBox = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButton.Ok + | QtWidgets.QDialogButtonBox.StandardButton.Cancel + ) + buttonBox.accepted.connect(dialog.accept) + buttonBox.rejected.connect(dialog.reject) + layout.addWidget(buttonBox) + + if dialog.exec() != int(QtWidgets.QDialog.DialogCode.Accepted): + return + + optionPayload = [] + for rowIndex in range(table.rowCount()): + itemData = table.item(rowIndex, 0).data(QtCore.Qt.ItemDataRole.UserRole) + if itemData is not None: + optionPayload.append(dict(itemData)) + self._apply_field_value(fieldRecord.name, repr(optionPayload)) + + def _open_literal_list_editor(self, fieldRecord: LayerFieldRecord) -> None: + values = self._parse_literal_collection(fieldRecord.currentValue, fieldRecord.defaultValue) + + dialog = QtWidgets.QDialog(self) + dialog.setWindowTitle(f"编辑列表字段: {fieldRecord.name}") + dialog.resize(720, 420) + + layout = QtWidgets.QVBoxLayout(dialog) + hintLabel = QtWidgets.QLabel("每一项按 Python 字面量编辑,例如 'text'、123、('RA', b'\\x00\\x00')。") + hintLabel.setWordWrap(True) + layout.addWidget(hintLabel) + + itemList = QtWidgets.QListWidget() + for value in values: + itemList.addItem(repr(value)) + layout.addWidget(itemList, 1) + + buttonLayout = QtWidgets.QHBoxLayout() + addButton = QtWidgets.QPushButton("添加") + editButton = QtWidgets.QPushButton("编辑") + removeButton = QtWidgets.QPushButton("删除") + moveUpButton = QtWidgets.QPushButton("上移") + moveDownButton = QtWidgets.QPushButton("下移") + buttonLayout.addWidget(addButton) + buttonLayout.addWidget(editButton) + buttonLayout.addWidget(removeButton) + buttonLayout.addWidget(moveUpButton) + buttonLayout.addWidget(moveDownButton) + buttonLayout.addStretch(1) + layout.addLayout(buttonLayout) + + addButton.clicked.connect(lambda: self._add_literal_list_item(itemList)) + editButton.clicked.connect(lambda: self._edit_literal_list_item(itemList)) + removeButton.clicked.connect(lambda: self._remove_literal_list_item(itemList)) + moveUpButton.clicked.connect(lambda: self._move_literal_list_item(itemList, -1)) + moveDownButton.clicked.connect(lambda: self._move_literal_list_item(itemList, 1)) + + buttonBox = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButton.Ok + | QtWidgets.QDialogButtonBox.StandardButton.Cancel + ) + buttonBox.accepted.connect(dialog.accept) + buttonBox.rejected.connect(dialog.reject) + layout.addWidget(buttonBox) + + if dialog.exec() != int(QtWidgets.QDialog.DialogCode.Accepted): + return + + items = [itemList.item(index).text() for index in range(itemList.count())] + rawValue = f"[{', '.join(items)}]" if items else "[]" + self._apply_field_value(fieldRecord.name, rawValue) + + def _open_dns_question_editor(self, fieldRecord: LayerFieldRecord) -> None: + questions = self._parse_dns_question_collection(fieldRecord.name) + + dialog = QtWidgets.QDialog(self) + dialog.setWindowTitle(f"编辑 DNS Question 列表: {fieldRecord.name}") + dialog.resize(760, 420) + + layout = QtWidgets.QVBoxLayout(dialog) + hintLabel = QtWidgets.QLabel("支持增删改 DNS question 子项,当前覆盖 qname、qtype、qclass。") + hintLabel.setWordWrap(True) + layout.addWidget(hintLabel) + + table = QtWidgets.QTableWidget(0, 3) + table.setHorizontalHeaderLabels(["qname", "qtype", "qclass"]) + table.horizontalHeader().setStretchLastSection(True) + table.verticalHeader().setVisible(False) + for question in questions: + self._append_dns_question_row(table, question) + layout.addWidget(table, 1) + + buttonLayout = QtWidgets.QHBoxLayout() + addButton = QtWidgets.QPushButton("添加") + removeButton = QtWidgets.QPushButton("删除") + moveUpButton = QtWidgets.QPushButton("上移") + moveDownButton = QtWidgets.QPushButton("下移") + buttonLayout.addWidget(addButton) + buttonLayout.addWidget(removeButton) + buttonLayout.addWidget(moveUpButton) + buttonLayout.addWidget(moveDownButton) + buttonLayout.addStretch(1) + layout.addLayout(buttonLayout) + + addButton.clicked.connect(lambda: self._append_dns_question_row(table, {})) + removeButton.clicked.connect(lambda: self._remove_table_row(table)) + moveUpButton.clicked.connect(lambda: self._move_table_row(table, -1)) + moveDownButton.clicked.connect(lambda: self._move_table_row(table, 1)) + + buttonBox = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButton.Ok + | QtWidgets.QDialogButtonBox.StandardButton.Cancel + ) + buttonBox.accepted.connect(dialog.accept) + buttonBox.rejected.connect(dialog.reject) + layout.addWidget(buttonBox) + + if dialog.exec() != int(QtWidgets.QDialog.DialogCode.Accepted): + return + + questionItems = [] + for rowIndex in range(table.rowCount()): + qname = self._table_item_text(table, rowIndex, 0) + if not qname: + continue + questionItem = { + "qname": qname, + "qtype": self._table_item_text(table, rowIndex, 1) or "A", + "qclass": self._table_item_text(table, rowIndex, 2) or "IN", + } + questionItems.append(questionItem) + + self._apply_field_value(fieldRecord.name, repr(questionItems)) + + def _add_literal_list_item(self, itemList: QtWidgets.QListWidget) -> None: + value = self._prompt_literal_value("添加列表项", "请输入 Python 字面量") + if value is None: + return + itemList.addItem(value) + itemList.setCurrentRow(itemList.count() - 1) + + def _edit_literal_list_item(self, itemList: QtWidgets.QListWidget) -> None: + currentItem = itemList.currentItem() + if currentItem is None: + return + value = self._prompt_literal_value("编辑列表项", "请输入 Python 字面量", currentItem.text()) + if value is None: + return + currentItem.setText(value) + + def _remove_literal_list_item(self, itemList: QtWidgets.QListWidget) -> None: + currentRow = itemList.currentRow() + if currentRow < 0: + return + itemList.takeItem(currentRow) + + def _move_literal_list_item(self, itemList: QtWidgets.QListWidget, step: int) -> None: + currentRow = itemList.currentRow() + targetRow = currentRow + step + if currentRow < 0 or targetRow < 0 or targetRow >= itemList.count(): + return + item = itemList.takeItem(currentRow) + itemList.insertItem(targetRow, item) + itemList.setCurrentRow(targetRow) + + def _prompt_literal_value( + self, + title: str, + label: str, + initialValue: str = "", + ) -> str | None: + value, accepted = QtWidgets.QInputDialog.getMultiLineText( + self, + title, + label, + initialValue, + ) + if not accepted: + return None + value = value.strip() + if value == "": + return None + try: + ast.literal_eval(value) + except Exception as exc: + self.builderStatusLabel.setText(f"列表项字面量无效: {exc}") + return None + return value + + def _append_dns_question_row( + self, + table: QtWidgets.QTableWidget, + question: dict[str, object], + ) -> None: + rowIndex = table.rowCount() + table.insertRow(rowIndex) + table.setItem(rowIndex, 0, QtWidgets.QTableWidgetItem(str(question.get("qname", "")))) + table.setItem(rowIndex, 1, QtWidgets.QTableWidgetItem(str(question.get("qtype", "A")))) + table.setItem(rowIndex, 2, QtWidgets.QTableWidgetItem(str(question.get("qclass", "IN")))) + + def _append_ip_option_row( + self, + table: QtWidgets.QTableWidget, + optionItem: dict[str, object], + ) -> None: + rowIndex = table.rowCount() + table.insertRow(rowIndex) + typeItem = QtWidgets.QTableWidgetItem(str(optionItem.get("type", "NOP"))) + typeItem.setData(QtCore.Qt.ItemDataRole.UserRole, dict(optionItem)) + argsItem = QtWidgets.QTableWidgetItem(self._ip_option_argument_text(optionItem)) + summaryItem = QtWidgets.QTableWidgetItem(self._ip_option_summary(optionItem)) + table.setItem(rowIndex, 0, typeItem) + table.setItem(rowIndex, 1, argsItem) + table.setItem(rowIndex, 2, summaryItem) + + def _add_ip_option_row(self, table: QtWidgets.QTableWidget, optionType: str) -> None: + optionItem = self._default_ip_option_item(optionType) + editedItem = self._edit_ip_option_item(optionItem) + if editedItem is None: + return + self._append_ip_option_row(table, editedItem) + table.setCurrentCell(table.rowCount() - 1, 0) + + def _edit_ip_option_row(self, table: QtWidgets.QTableWidget) -> None: + currentRow = table.currentRow() + if currentRow < 0: + return + typeItem = table.item(currentRow, 0) + if typeItem is None: + return + optionItem = typeItem.data(QtCore.Qt.ItemDataRole.UserRole) + if not isinstance(optionItem, dict): + return + editedItem = self._edit_ip_option_item(dict(optionItem)) + if editedItem is None: + return + typeItem.setText(str(editedItem.get("type", "NOP"))) + typeItem.setData(QtCore.Qt.ItemDataRole.UserRole, editedItem) + table.item(currentRow, 1).setText(self._ip_option_argument_text(editedItem)) + table.item(currentRow, 2).setText(self._ip_option_summary(editedItem)) + + def _edit_ip_option_item(self, optionItem: dict[str, object]) -> dict[str, object] | None: + optionType = str(optionItem.get("type", "NOP")) + if optionType in {"NOP", "EOL", "RouterAlert"}: + return optionItem + + if optionType in {"RR", "LSRR", "SSRR"}: + routers, accepted = QtWidgets.QInputDialog.getMultiLineText( + self, + f"编辑 {optionType}", + "请输入路由器 IP 列表,每行一个地址", + "\n".join(optionItem.get("routers", [])), + ) + if not accepted: + return None + optionItem["routers"] = [line.strip() for line in routers.splitlines() if line.strip()] + return optionItem + + if optionType == "Timestamp": + dialog = QtWidgets.QDialog(self) + dialog.setWindowTitle("编辑 Timestamp") + form = QtWidgets.QFormLayout(dialog) + flgCombo = QtWidgets.QComboBox() + flgCombo.addItem("timestamp_only", "timestamp_only") + flgCombo.addItem("timestamp_and_ip_addr", "timestamp_and_ip_addr") + flgCombo.addItem("prespecified_ip_addr", "prespecified_ip_addr") + currentFlag = str(optionItem.get("flg", "timestamp_only")) + index = flgCombo.findData(currentFlag) + flgCombo.setCurrentIndex(index if index >= 0 else 0) + addressEdit = QtWidgets.QLineEdit(str(optionItem.get("internet_address", "0.0.0.0"))) + timestampSpin = QtWidgets.QSpinBox() + timestampSpin.setRange(0, 2_147_483_647) + timestampSpin.setValue(int(optionItem.get("timestamp", 0))) + form.addRow("模式", flgCombo) + form.addRow("地址", addressEdit) + form.addRow("时间戳", timestampSpin) + buttonBox = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButton.Ok + | QtWidgets.QDialogButtonBox.StandardButton.Cancel + ) + form.addRow(buttonBox) + buttonBox.accepted.connect(dialog.accept) + buttonBox.rejected.connect(dialog.reject) + if dialog.exec() != int(QtWidgets.QDialog.DialogCode.Accepted): + return None + optionItem["flg"] = str(flgCombo.currentData()) + optionItem["internet_address"] = addressEdit.text().strip() or "0.0.0.0" + optionItem["timestamp"] = int(timestampSpin.value()) + return optionItem + + if optionType == "Security": + dialog = QtWidgets.QDialog(self) + dialog.setWindowTitle("编辑 Security") + form = QtWidgets.QFormLayout(dialog) + securitySpin = QtWidgets.QSpinBox() + compartmentSpin = QtWidgets.QSpinBox() + restrictionsSpin = QtWidgets.QSpinBox() + for widget, value in [ + (securitySpin, int(optionItem.get("security", 0))), + (compartmentSpin, int(optionItem.get("compartment", 0))), + (restrictionsSpin, int(optionItem.get("handling_restrictions", 0))), + ]: + widget.setRange(0, 65535) + widget.setValue(value) + tccEdit = QtWidgets.QLineEdit(str(optionItem.get("transmission_control_code", "xxx"))) + form.addRow("security", securitySpin) + form.addRow("compartment", compartmentSpin) + form.addRow("handling_restrictions", restrictionsSpin) + form.addRow("transmission_control_code", tccEdit) + buttonBox = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButton.Ok + | QtWidgets.QDialogButtonBox.StandardButton.Cancel + ) + form.addRow(buttonBox) + buttonBox.accepted.connect(dialog.accept) + buttonBox.rejected.connect(dialog.reject) + if dialog.exec() != int(QtWidgets.QDialog.DialogCode.Accepted): + return None + optionItem["security"] = int(securitySpin.value()) + optionItem["compartment"] = int(compartmentSpin.value()) + optionItem["handling_restrictions"] = int(restrictionsSpin.value()) + optionItem["transmission_control_code"] = tccEdit.text().strip() or "xxx" + return optionItem + + return optionItem + + def _default_ip_option_item(self, optionType: str) -> dict[str, object]: + defaults: dict[str, dict[str, object]] = { + "NOP": {"type": "NOP"}, + "EOL": {"type": "EOL"}, + "RR": {"type": "RR", "routers": []}, + "LSRR": {"type": "LSRR", "routers": []}, + "SSRR": {"type": "SSRR", "routers": []}, + "Timestamp": { + "type": "Timestamp", + "flg": "timestamp_only", + "internet_address": "0.0.0.0", + "timestamp": 0, + }, + "RouterAlert": {"type": "RouterAlert"}, + "Security": { + "type": "Security", + "security": 0, + "compartment": 0, + "handling_restrictions": 0, + "transmission_control_code": "xxx", + }, + } + return dict(defaults[optionType]) + + def _ip_option_argument_text(self, optionItem: dict[str, object]) -> str: + optionType = str(optionItem.get("type", "NOP")) + if optionType in {"RR", "LSRR", "SSRR"}: + routers = optionItem.get("routers", []) + return ", ".join(str(router) for router in routers) or "无" + if optionType == "Timestamp": + return f"{optionItem.get('flg', 'timestamp_only')}, ts={optionItem.get('timestamp', 0)}" + if optionType == "Security": + return f"sec={optionItem.get('security', 0)}, comp={optionItem.get('compartment', 0)}" + return "-" + + def _ip_option_summary(self, optionItem: dict[str, object]) -> str: + optionType = str(optionItem.get("type", "NOP")) + summaryMap = { + "NOP": "No Operation", + "EOL": "End of Options List", + "RR": "Record Route", + "LSRR": "Loose Source Route", + "SSRR": "Strict Source Route", + "Timestamp": "Timestamp", + "RouterAlert": "Router Alert", + "Security": "Security", + } + return summaryMap.get(optionType, optionType) + + def _remove_table_row(self, table: QtWidgets.QTableWidget) -> None: + currentRow = table.currentRow() + if currentRow >= 0: + table.removeRow(currentRow) + + def _move_table_row(self, table: QtWidgets.QTableWidget, step: int) -> None: + currentRow = table.currentRow() + targetRow = currentRow + step + if currentRow < 0 or targetRow < 0 or targetRow >= table.rowCount(): + return + + rowItems = [table.takeItem(currentRow, column) for column in range(table.columnCount())] + table.removeRow(currentRow) + table.insertRow(targetRow) + for columnIndex, item in enumerate(rowItems): + if item is None: + item = QtWidgets.QTableWidgetItem("") + table.setItem(targetRow, columnIndex, item) + table.setCurrentCell(targetRow, 0) + + def _table_item_text(self, table: QtWidgets.QTableWidget, rowIndex: int, columnIndex: int) -> str: + item = table.item(rowIndex, columnIndex) + return item.text().strip() if item is not None else "" + + def _parse_literal_collection(self, currentValue: str, defaultValue: str) -> list[object]: + for candidate in [currentValue, defaultValue]: + candidate = candidate.strip() + if not candidate: + continue + try: + value = ast.literal_eval(candidate) + except Exception: + continue + if isinstance(value, tuple): + return list(value) + if isinstance(value, list): + return list(value) + return [] + + def _parse_dns_question_collection(self, fieldName: str) -> list[dict[str, object]]: + layerIndex = self.layerList.currentRow() + if layerIndex < 0: + return [] + + nativeValue = self.packetBuilderService.getFieldNativeValue(layerIndex, fieldName) + questions: list[dict[str, object]] = [] + for item in nativeValue or []: + if isinstance(item, dict): + questions.append( + { + "qname": str(item.get("qname", "")), + "qtype": str(item.get("qtype", "A")), + "qclass": str(item.get("qclass", "IN")), + } + ) + continue + + if isinstance(item, str): + questions.append({"qname": item, "qtype": "A", "qclass": "IN"}) + continue + + if item.__class__.__name__ == "DNSQR": + qname = getattr(item, "qname", b"") + if isinstance(qname, bytes): + qname = qname.decode("utf-8", errors="replace") + questions.append( + { + "qname": str(qname).rstrip("."), + "qtype": self._dns_question_field_label(item, "qtype", "A"), + "qclass": self._dns_question_field_label(item, "qclass", "IN"), + } + ) + + return questions + + def _dns_question_field_label(self, packet: object, fieldName: str, fallback: str) -> str: + try: + field = packet.get_field(fieldName) + except Exception: + return fallback + + fieldValue = getattr(packet, fieldName, fallback) + label = field.i2repr(packet, fieldValue) + if isinstance(label, str) and label: + return label + return str(fieldValue) + + def _parse_ip_options_collection(self, fieldName: str) -> list[dict[str, object]]: + layerIndex = self.layerList.currentRow() + if layerIndex < 0: + return [] + + nativeValue = self.packetBuilderService.getFieldNativeValue(layerIndex, fieldName) + optionItems: list[dict[str, object]] = [] + for item in nativeValue or []: + className = item.__class__.__name__ + if className == "IPOption_NOP": + optionItems.append({"type": "NOP"}) + elif className == "IPOption_EOL": + optionItems.append({"type": "EOL"}) + elif className in {"IPOption_RR", "IPOption_LSRR", "IPOption_SSRR"}: + optionItems.append( + { + "type": className.removeprefix("IPOption_"), + "routers": list(getattr(item, "routers", [])), + } + ) + elif className == "IPOption_Timestamp": + optionItems.append( + { + "type": "Timestamp", + "flg": str(getattr(item, "flg", "timestamp_only")), + "internet_address": str(getattr(item, "internet_address", "0.0.0.0")), + "timestamp": int(getattr(item, "timestamp", 0)), + } + ) + elif className == "IPOption_Router_Alert": + optionItems.append({"type": "RouterAlert"}) + elif className == "IPOption_Security": + optionItems.append( + { + "type": "Security", + "security": int(getattr(item, "security", 0)), + "compartment": int(getattr(item, "compartment", 0)), + "handling_restrictions": int(getattr(item, "handling_restrictions", 0)), + "transmission_control_code": str(getattr(item, "transmission_control_code", "xxx")), + } + ) + return optionItems + + def _handle_editor_value_changed(self, fieldName: str, rawValue: str) -> None: + if self._updatingFieldTable: + return + self._apply_field_value(fieldName, rawValue) + + def _handle_enum_editor_activated( + self, + fieldName: str, + comboBox: QtWidgets.QComboBox, + ) -> None: + self._handle_editor_value_changed(fieldName, self._current_combo_value(comboBox)) + + def _handle_enum_editor_editing_finished( + self, + fieldName: str, + comboBox: QtWidgets.QComboBox, + ) -> None: + if comboBox.view().isVisible(): + return + + completer = comboBox.completer() + if completer is not None and completer.popup().isVisible(): + return + + self._handle_editor_value_changed(fieldName, self._current_combo_value(comboBox)) + + def _apply_field_value(self, fieldName: str, rawValue: str) -> None: + layerIndex = self.layerList.currentRow() + if layerIndex < 0: + return + + validationError = self._validate_field_value(fieldName, rawValue) + if validationError: + self.builderStatusLabel.setText(validationError) + self._refresh_field_table() + return + + try: + self.packetBuilderService.setFieldValue(layerIndex, fieldName, rawValue) + except Exception as exc: + self.builderStatusLabel.setText(f"字段更新失败: {exc}") + self._refresh_field_table() + return + + self.builderStatusLabel.setText(f"已更新字段: {fieldName}") + self._refresh_previews() + self._refresh_layer_list(preserveSelection=True) + self._refresh_raw_payload_editor() + + def _current_combo_value(self, comboBox: QtWidgets.QComboBox) -> str: + if comboBox.isEditable(): + currentText = comboBox.lineEdit().text().strip() + if currentText: + currentIndex = comboBox.currentIndex() + selectedText = comboBox.itemText(currentIndex).strip() if currentIndex >= 0 else "" + if currentText != selectedText: + return self._normalize_combo_text(currentText) + + currentData = comboBox.currentData() + if currentData is not None and comboBox.currentIndex() >= 0: + return str(currentData) + + currentText = comboBox.currentText().strip() + return self._normalize_combo_text(currentText) + + def _normalize_combo_text(self, currentText: str) -> str: + if currentText == "使用默认值": + return "" + + match = re.search(r"\(([^()]+)\)\s*$", currentText) + if match: + return match.group(1).strip() + return currentText + + def _validate_field_value(self, fieldName: str, rawValue: str) -> str | None: + if rawValue == "": + return None + + fieldRecord = self._visibleFieldRecords.get(fieldName) + if fieldRecord is None: + return None + + if fieldRecord.editorKind == "ipv4": + try: + ipaddress.IPv4Address(rawValue) + except ValueError: + return f"字段 {fieldName} 需要合法的 IPv4 地址。" + + if fieldRecord.editorKind == "ipv6": + try: + ipaddress.IPv6Address(rawValue) + except ValueError: + return f"字段 {fieldName} 需要合法的 IPv6 地址。" + + if fieldRecord.editorKind == "mac": + if not re.fullmatch(r"([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}", rawValue): + return f"字段 {fieldName} 需要合法的 MAC 地址。" + + return None + + def _collection_display_text(self, rawValue: str) -> str: + compactValue = rawValue.replace("\n", " ").strip() + if not compactValue: + return "使用默认值" + if len(compactValue) > 80: + return f"{compactValue[:77]}..." + return compactValue \ No newline at end of file diff --git a/gui/src/packet_studio/widgets/send_task_widget.py b/gui/src/packet_studio/widgets/send_task_widget.py new file mode 100644 index 00000000000..7fba7c48d73 --- /dev/null +++ b/gui/src/packet_studio/widgets/send_task_widget.py @@ -0,0 +1,652 @@ +from __future__ import annotations + +import re +import threading +from dataclasses import dataclass +from typing import Any, Optional + +from PySide6 import QtCore, QtGui, QtWidgets + +from packet_studio.domain.packet_models import PacketPreview +from packet_studio.domain.task_models import SendTaskResult, TaskError, TaskState +from packet_studio.domain.workspace_models import WorkspacePanelSnapshot +from packet_studio.services.interface_service import InterfaceRecord +from packet_studio.services.send_task_service import SendTaskRequest, SendTaskService + + +@dataclass +class StreamTemplateEntry: + templateId: int + name: str + packet: Any + preview: PacketPreview + enabled: bool = True + sourceMac: str = "" + destinationMac: str = "" + hasEtherLayer: bool = False + + +class SendTaskWorker(QtCore.QObject): + finished = QtCore.Signal(object) + failed = QtCore.Signal(object) + + def __init__( + self, + sendTaskService: SendTaskService, + request: SendTaskRequest, + packets: list[Any], + stopEvent: threading.Event, + ) -> None: + super().__init__() + self.sendTaskService = sendTaskService + self.request = request + self.packets = packets + self.stopEvent = stopEvent + + @QtCore.Slot() + def run(self) -> None: + try: + result = self.sendTaskService.execute( + self.request, + self.packets, + stopRequested=self.stopEvent.is_set, + ) + except Exception as exc: + self.failed.emit(TaskError(message=str(exc), logText=str(exc))) + return + self.finished.emit(result) + + +class SendTaskWidget(QtWidgets.QWidget): + """发送与 sr1 请求响应任务面板。""" + + statusMessage = QtCore.Signal(str) + editPacketRequested = QtCore.Signal(object) + _MAC_PATTERN = re.compile(r"^(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$") + + def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: + super().__init__(parent) + self.sendTaskService = SendTaskService() + self.interfaceRecords: list[InterfaceRecord] = [] + self.streamTemplates: list[StreamTemplateEntry] = [] + self.nextTemplateId = 1 + self.currentEditingTemplateId: int | None = None + self.workerThread: Optional[QtCore.QThread] = None + self.worker: Optional[SendTaskWorker] = None + self.stopEvent: Optional[threading.Event] = None + self.currentTaskState = TaskState.idle("准备就绪。") + + self._setup_ui() + self._refresh_stream_table() + self._update_mode_state() + + def hasRunningTask(self) -> bool: + return self.workerThread is not None + + def closeEvent(self, event: QtGui.QCloseEvent) -> None: + if self.hasRunningTask(): + QtWidgets.QMessageBox.warning( + self, + "发送任务仍在运行", + "请等待当前发送任务结束后再关闭窗口。", + ) + event.ignore() + return + super().closeEvent(event) + + def setInterfaceRecords(self, interfaceRecords: list[InterfaceRecord]) -> None: + self.interfaceRecords = list(interfaceRecords) + selectedValue = self.interfaceCombo.currentData() + self.interfaceCombo.blockSignals(True) + self.interfaceCombo.clear() + self.interfaceCombo.addItem("自动选择 / 路由决定", "") + restoredIndex = 0 + for index, interfaceRecord in enumerate(self.interfaceRecords, start=1): + label = f"{interfaceRecord.name} [{interfaceRecord.capabilitySummary}]" + self.interfaceCombo.addItem( + label, + interfaceRecord.networkName or interfaceRecord.name, + ) + if selectedValue and self.interfaceCombo.itemData(index) == selectedValue: + restoredIndex = index + self.interfaceCombo.setCurrentIndex(restoredIndex) + self.interfaceCombo.blockSignals(False) + + def buildWorkspaceSnapshot(self) -> WorkspacePanelSnapshot: + return WorkspacePanelSnapshot( + panelId="send-task", + title="发送任务", + taskState=self.currentTaskState, + itemCount=len(self.streamTemplates), + detailText=self.resultSummaryLabel.text(), + ) + + @QtCore.Slot(object) + def addStreamFromPacket(self, packet: Any) -> None: + packetCopy = packet.copy() if packet is not None and hasattr(packet, "copy") else packet + preview = self.sendTaskService.buildPacketPreview(packetCopy) + if packetCopy is None or preview is None: + self.statusLabel.setText("无法创建流模板:当前数据包为空。") + return + + sourceMac, destinationMac, hasEtherLayer = self._extract_mac_fields(packetCopy) + entry = StreamTemplateEntry( + templateId=self.nextTemplateId, + name=f"流 {self.nextTemplateId}", + packet=packetCopy, + preview=preview, + sourceMac=sourceMac, + destinationMac=destinationMac, + hasEtherLayer=hasEtherLayer, + ) + self.nextTemplateId += 1 + self.streamTemplates.append(entry) + self._refresh_stream_table(selectedTemplateId=entry.templateId) + self._apply_preview(entry.preview) + self.statusLabel.setText(f"已新增流模板: {entry.name}") + self.statusMessage.emit(f"已新增流模板: {entry.name}") + + def beginEditingStream(self, templateId: int) -> None: + if self._find_stream(templateId) is None: + self.currentEditingTemplateId = None + return + self.currentEditingTemplateId = templateId + + def getCurrentSelectedTemplateId(self) -> int | None: + entry = self._current_selected_stream() + if entry is None: + return None + return entry.templateId + + def saveEditedStream(self, packet: Any) -> bool: + if self.currentEditingTemplateId is None: + return False + entry = self._find_stream(self.currentEditingTemplateId) + if entry is None: + self.currentEditingTemplateId = None + return False + packetCopy = packet.copy() if packet is not None and hasattr(packet, "copy") else packet + preview = self.sendTaskService.buildPacketPreview(packetCopy) + if packetCopy is None or preview is None: + return False + entry.packet = packetCopy + entry.preview = preview + entry.sourceMac, entry.destinationMac, entry.hasEtherLayer = self._extract_mac_fields(packetCopy) + self._refresh_stream_table(selectedTemplateId=entry.templateId) + self.statusLabel.setText(f"已保存回流模板: {entry.name}") + self.statusMessage.emit(f"已保存回流模板: {entry.name}") + self.currentEditingTemplateId = None + return True + + def _setup_ui(self) -> None: + rootLayout = QtWidgets.QVBoxLayout(self) + + controlsLayout = QtWidgets.QGridLayout() + controlsLayout.addWidget(QtWidgets.QLabel("执行模式"), 0, 0) + self.modeCombo = QtWidgets.QComboBox() + self.modeCombo.addItem("send (L3 发送)", "send") + self.modeCombo.addItem("sendp (L2 发送)", "sendp") + self.modeCombo.addItem("sr1 (L3 请求/响应)", "sr1") + self.modeCombo.setCurrentIndex(1) + self.modeCombo.currentIndexChanged.connect(self._handle_mode_changed) + controlsLayout.addWidget(self.modeCombo, 0, 1) + + controlsLayout.addWidget(QtWidgets.QLabel("发送策略"), 0, 2) + self.strategyCombo = QtWidgets.QComboBox() + self.strategyCombo.addItem("burst (按轮次发送)", "burst") + self.strategyCombo.addItem("continuous (持续发送)", "continuous") + self.strategyCombo.currentIndexChanged.connect(self._handle_strategy_changed) + controlsLayout.addWidget(self.strategyCombo, 0, 3) + + controlsLayout.addWidget(QtWidgets.QLabel("接口"), 1, 0) + self.interfaceCombo = QtWidgets.QComboBox() + self.interfaceCombo.addItem("自动选择 / 路由决定", "") + controlsLayout.addWidget(self.interfaceCombo, 1, 1) + + controlsLayout.addWidget(QtWidgets.QLabel("发送轮次"), 1, 2) + self.countSpin = QtWidgets.QSpinBox() + self.countSpin.setRange(1, 100000) + self.countSpin.setValue(1) + controlsLayout.addWidget(self.countSpin, 1, 3) + + controlsLayout.addWidget(QtWidgets.QLabel("发送间隔 (s)"), 2, 0) + self.intervalSpin = QtWidgets.QDoubleSpinBox() + self.intervalSpin.setRange(0.0, 3600.0) + self.intervalSpin.setDecimals(3) + self.intervalSpin.setSingleStep(0.1) + controlsLayout.addWidget(self.intervalSpin, 2, 1) + + controlsLayout.addWidget(QtWidgets.QLabel("超时 (s)"), 2, 2) + self.timeoutSpin = QtWidgets.QDoubleSpinBox() + self.timeoutSpin.setRange(0.1, 3600.0) + self.timeoutSpin.setDecimals(3) + self.timeoutSpin.setValue(1.0) + controlsLayout.addWidget(self.timeoutSpin, 2, 3) + + controlsLayout.addWidget(QtWidgets.QLabel("重试次数"), 3, 0) + self.retrySpin = QtWidgets.QSpinBox() + self.retrySpin.setRange(0, 100) + controlsLayout.addWidget(self.retrySpin, 3, 1) + + actionLayout = QtWidgets.QHBoxLayout() + self.executeButton = QtWidgets.QPushButton("开始执行") + self.executeButton.clicked.connect(self._handle_execute) + self.stopButton = QtWidgets.QPushButton("停止") + self.stopButton.clicked.connect(self._handle_stop) + self.statusLabel = QtWidgets.QLabel("准备就绪。") + self.statusLabel.setWordWrap(True) + actionLayout.addWidget(self.executeButton) + actionLayout.addWidget(self.stopButton) + actionLayout.addWidget(self.statusLabel, 1) + + contentSplitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal) + + streamPane = QtWidgets.QWidget() + streamLayout = QtWidgets.QVBoxLayout(streamPane) + streamLayout.addWidget(QtWidgets.QLabel("流模板")) + self.streamTable = QtWidgets.QTableWidget(0, 5) + self.streamTable.setHorizontalHeaderLabels(["发送", "名称", "摘要", "源 MAC", "目标 MAC"]) + self.streamTable.verticalHeader().setVisible(False) + self.streamTable.setSelectionBehavior( + QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows + ) + self.streamTable.setSelectionMode( + QtWidgets.QAbstractItemView.SelectionMode.SingleSelection + ) + self.streamTable.horizontalHeader().setStretchLastSection(False) + self.streamTable.horizontalHeader().setSectionResizeMode( + 0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents + ) + self.streamTable.horizontalHeader().setSectionResizeMode( + 1, QtWidgets.QHeaderView.ResizeMode.Interactive + ) + self.streamTable.horizontalHeader().setSectionResizeMode( + 2, QtWidgets.QHeaderView.ResizeMode.Interactive + ) + self.streamTable.horizontalHeader().setSectionResizeMode( + 3, QtWidgets.QHeaderView.ResizeMode.Stretch + ) + self.streamTable.horizontalHeader().setSectionResizeMode( + 4, QtWidgets.QHeaderView.ResizeMode.Stretch + ) + self.streamTable.setColumnWidth(1, 130) + self.streamTable.setColumnWidth(2, 220) + self.streamTable.itemSelectionChanged.connect(self._handle_stream_selection_changed) + streamLayout.addWidget(self.streamTable, 1) + + streamActionsLayout = QtWidgets.QHBoxLayout() + self.selectAllStreamsButton = QtWidgets.QPushButton("全部勾选") + self.clearCheckedStreamsButton = QtWidgets.QPushButton("全部取消") + self.removeStreamButton = QtWidgets.QPushButton("移除选中流") + self.editInBuilderButton = QtWidgets.QPushButton("跳到包构建器编辑") + self.selectAllStreamsButton.clicked.connect(lambda: self._set_all_streams_enabled(True)) + self.clearCheckedStreamsButton.clicked.connect(lambda: self._set_all_streams_enabled(False)) + self.removeStreamButton.clicked.connect(self._handle_remove_selected_stream) + self.editInBuilderButton.clicked.connect(self._handle_edit_selected_stream) + streamActionsLayout.addWidget(self.selectAllStreamsButton) + streamActionsLayout.addWidget(self.clearCheckedStreamsButton) + streamActionsLayout.addWidget(self.removeStreamButton) + streamActionsLayout.addWidget(self.editInBuilderButton) + streamLayout.addLayout(streamActionsLayout) + + previewSplitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Vertical) + + requestPane = QtWidgets.QWidget() + requestLayout = QtWidgets.QVBoxLayout(requestPane) + requestLayout.addWidget(QtWidgets.QLabel("选中流模板预览")) + self.packetSummaryEdit = QtWidgets.QLineEdit() + self.packetSummaryEdit.setReadOnly(True) + requestLayout.addWidget(self.packetSummaryEdit) + requestTabs = QtWidgets.QTabWidget() + self.packetStructureEdit = QtWidgets.QPlainTextEdit() + self.packetStructureEdit.setReadOnly(True) + self.packetHexdumpEdit = QtWidgets.QPlainTextEdit() + self.packetHexdumpEdit.setReadOnly(True) + requestTabs.addTab(self.packetStructureEdit, "结构") + requestTabs.addTab(self.packetHexdumpEdit, "十六进制") + requestLayout.addWidget(requestTabs, 1) + + resultPane = QtWidgets.QWidget() + resultLayout = QtWidgets.QVBoxLayout(resultPane) + self.resultSummaryLabel = QtWidgets.QLabel("尚未执行发送任务。") + self.resultSummaryLabel.setWordWrap(True) + resultLayout.addWidget(self.resultSummaryLabel) + resultTabs = QtWidgets.QTabWidget() + self.resultLogEdit = QtWidgets.QPlainTextEdit() + self.resultLogEdit.setReadOnly(True) + self.answerStructureEdit = QtWidgets.QPlainTextEdit() + self.answerStructureEdit.setReadOnly(True) + self.answerHexdumpEdit = QtWidgets.QPlainTextEdit() + self.answerHexdumpEdit.setReadOnly(True) + resultTabs.addTab(self.resultLogEdit, "发送日志") + resultTabs.addTab(self.answerStructureEdit, "应答结构") + resultTabs.addTab(self.answerHexdumpEdit, "应答十六进制") + resultLayout.addWidget(resultTabs, 1) + + previewSplitter.addWidget(requestPane) + previewSplitter.addWidget(resultPane) + previewSplitter.setStretchFactor(0, 1) + previewSplitter.setStretchFactor(1, 1) + + contentSplitter.addWidget(streamPane) + contentSplitter.addWidget(previewSplitter) + contentSplitter.setStretchFactor(0, 1) + contentSplitter.setStretchFactor(1, 1) + + rootLayout.addLayout(controlsLayout) + rootLayout.addLayout(actionLayout) + rootLayout.addWidget(contentSplitter, 1) + + def _build_request(self) -> SendTaskRequest: + mode = str(self.modeCombo.currentData()) + interfaceName = "" + if mode == "sendp": + interfaceName = str(self.interfaceCombo.currentData() or "") + return SendTaskRequest( + mode=mode, + sendStrategy=str(self.strategyCombo.currentData()), + interfaceName=interfaceName, + count=int(self.countSpin.value()), + intervalSeconds=float(self.intervalSpin.value()), + timeoutSeconds=float(self.timeoutSpin.value()), + retryCount=int(self.retrySpin.value()), + ) + + def _handle_mode_changed(self) -> None: + self._update_mode_state() + + def _handle_strategy_changed(self) -> None: + self._update_mode_state() + + def _update_mode_state(self) -> None: + mode = str(self.modeCombo.currentData()) + isRunning = self.workerThread is not None + isSr1 = mode == "sr1" + usesInterface = mode == "sendp" + if isSr1 and self.strategyCombo.currentData() != "burst": + self.strategyCombo.blockSignals(True) + self.strategyCombo.setCurrentIndex(self.strategyCombo.findData("burst")) + self.strategyCombo.blockSignals(False) + strategy = str(self.strategyCombo.currentData()) + isContinuous = strategy == "continuous" + self.strategyCombo.setEnabled(not isSr1 and not isRunning) + self.countSpin.setEnabled(not isContinuous and not isRunning) + self.intervalSpin.setEnabled(not isSr1 and not isRunning) + self.timeoutSpin.setEnabled(isSr1 and not isRunning) + self.retrySpin.setEnabled(isSr1 and not isRunning) + self.interfaceCombo.setEnabled(usesInterface and not isRunning) + if usesInterface: + self.interfaceCombo.setToolTip("L2 发送会显式使用所选接口。") + else: + self.interfaceCombo.setToolTip("L3 send/sr1 由 Scapy 路由自动选择接口。") + + def _handle_execute(self) -> None: + packets = self._selected_packets() + if not packets: + self.statusLabel.setText("请先创建并勾选至少一条流模板。") + self.statusMessage.emit("发送任务启动失败:没有已勾选的流模板。") + return + if self.workerThread is not None: + self.statusLabel.setText("已有发送任务正在执行,请稍候。") + return + + request = self._build_request() + self.resultLogEdit.clear() + self.answerStructureEdit.clear() + self.answerHexdumpEdit.clear() + self.resultSummaryLabel.setText("发送任务执行中...") + self._apply_task_state(TaskState.running("正在后台执行发送任务...")) + self.statusMessage.emit( + f"开始执行发送任务: {request.mode} / {request.sendStrategy} / {len(packets)} 条流" + ) + self._set_running_state(True) + self.stopEvent = threading.Event() + + thread = QtCore.QThread(self) + worker = SendTaskWorker(self.sendTaskService, request, packets, self.stopEvent) + worker.moveToThread(thread) + thread.started.connect(worker.run) + worker.finished.connect(self._on_task_finished) + worker.failed.connect(self._on_task_failed) + worker.finished.connect(thread.quit) + worker.failed.connect(thread.quit) + thread.finished.connect(worker.deleteLater) + thread.finished.connect(self._on_thread_finished) + + self.workerThread = thread + self.worker = worker + thread.start() + + def _handle_stop(self) -> None: + if self.stopEvent is None or self.stopEvent.is_set(): + return + self.stopEvent.set() + self.stopButton.setEnabled(False) + self.statusLabel.setText("正在停止当前发送任务...") + self.statusMessage.emit("已请求停止当前发送任务。") + + @QtCore.Slot(object) + def _on_task_finished(self, result: SendTaskResult) -> None: + self.resultLogEdit.setPlainText(result.logText) + if result.answerPreview is None: + self.answerStructureEdit.setPlainText("本次任务没有应答数据包。") + self.answerHexdumpEdit.setPlainText("") + else: + self.answerStructureEdit.setPlainText(result.answerPreview.structure) + self.answerHexdumpEdit.setPlainText(result.answerPreview.hexdump) + self.resultSummaryLabel.setText(result.summaryText) + self._apply_task_state(result.state) + self.statusMessage.emit(result.summaryText) + + @QtCore.Slot(object) + def _on_task_failed(self, error: TaskError) -> None: + self.resultSummaryLabel.setText(f"发送任务失败: {error.summaryText}") + self.resultLogEdit.setPlainText(error.logText or error.summaryText) + self._apply_task_state(error.state) + self.statusMessage.emit(f"发送任务失败: {error.summaryText}") + + def _on_thread_finished(self) -> None: + self.workerThread = None + self.worker = None + self.stopEvent = None + self._set_running_state(False) + self._sync_preview_with_selection() + + def _set_running_state(self, isRunning: bool) -> None: + self.modeCombo.setEnabled(not isRunning) + self.streamTable.setEnabled(not isRunning) + hasSelectedStream = self._current_selected_stream() is not None + hasTemplates = bool(self.streamTemplates) + self.selectAllStreamsButton.setEnabled(not isRunning and hasTemplates) + self.clearCheckedStreamsButton.setEnabled(not isRunning and hasTemplates) + self.removeStreamButton.setEnabled(not isRunning and hasSelectedStream) + self.editInBuilderButton.setEnabled(not isRunning and hasSelectedStream) + self.executeButton.setEnabled(not isRunning and bool(self._selected_packets())) + self.stopButton.setEnabled(isRunning) + self._update_mode_state() + + def _apply_task_state(self, state: TaskState) -> None: + self.currentTaskState = state + self.statusLabel.setText(state.statusText) + + def _refresh_stream_table(self, selectedTemplateId: int | None = None) -> None: + currentSelected = selectedTemplateId + if currentSelected is None: + currentEntry = self._current_selected_stream() + currentSelected = currentEntry.templateId if currentEntry is not None else None + + self.streamTable.blockSignals(True) + self.streamTable.clearContents() + self.streamTable.setRowCount(len(self.streamTemplates)) + for rowIndex, entry in enumerate(self.streamTemplates): + self._populate_stream_row(rowIndex, entry) + self.streamTable.blockSignals(False) + + selectedRow = 0 if self.streamTemplates else -1 + if currentSelected is not None: + for rowIndex, entry in enumerate(self.streamTemplates): + if entry.templateId == currentSelected: + selectedRow = rowIndex + break + if selectedRow >= 0: + self.streamTable.selectRow(selectedRow) + self._sync_preview_with_selection() + self._set_running_state(self.workerThread is not None) + + def _populate_stream_row(self, rowIndex: int, entry: StreamTemplateEntry) -> None: + checkContainer = QtWidgets.QWidget() + checkLayout = QtWidgets.QHBoxLayout(checkContainer) + checkLayout.setContentsMargins(0, 0, 0, 0) + checkLayout.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + checkBox = QtWidgets.QCheckBox() + checkBox.setChecked(entry.enabled) + checkBox.stateChanged.connect( + lambda state, templateId=entry.templateId: self._handle_stream_enabled_changed(templateId, state) + ) + checkLayout.addWidget(checkBox) + self.streamTable.setCellWidget(rowIndex, 0, checkContainer) + + nameItem = QtWidgets.QTableWidgetItem(entry.name) + nameItem.setFlags(nameItem.flags() & ~QtCore.Qt.ItemFlag.ItemIsEditable) + summaryItem = QtWidgets.QTableWidgetItem(entry.preview.summary) + summaryItem.setFlags(summaryItem.flags() & ~QtCore.Qt.ItemFlag.ItemIsEditable) + self.streamTable.setItem(rowIndex, 1, nameItem) + self.streamTable.setItem(rowIndex, 2, summaryItem) + self.streamTable.setCellWidget(rowIndex, 3, self._build_mac_editor(entry, "src", entry.sourceMac)) + self.streamTable.setCellWidget(rowIndex, 4, self._build_mac_editor(entry, "dst", entry.destinationMac)) + + def _build_mac_editor( + self, + entry: StreamTemplateEntry, + fieldName: str, + value: str, + ) -> QtWidgets.QWidget: + lineEdit = QtWidgets.QLineEdit(value) + lineEdit.setPlaceholderText("无 Ether 层" if not entry.hasEtherLayer else "00:11:22:33:44:55") + lineEdit.setEnabled(entry.hasEtherLayer and self.workerThread is None) + if entry.hasEtherLayer: + lineEdit.editingFinished.connect( + lambda templateId=entry.templateId, targetField=fieldName, editor=lineEdit: self._apply_mac_edit( + templateId, + targetField, + editor, + ) + ) + return lineEdit + + def _handle_stream_enabled_changed(self, templateId: int, state: int) -> None: + entry = self._find_stream(templateId) + if entry is None: + return + entry.enabled = state == int(QtCore.Qt.CheckState.Checked.value) + self._set_running_state(self.workerThread is not None) + + def _apply_mac_edit( + self, + templateId: int, + fieldName: str, + editor: QtWidgets.QLineEdit, + ) -> None: + entry = self._find_stream(templateId) + if entry is None: + return + value = editor.text().strip() + if not value: + editor.setText(entry.sourceMac if fieldName == "src" else entry.destinationMac) + return + if self._MAC_PATTERN.match(value) is None: + self.statusLabel.setText("MAC 地址格式无效,请使用 00:11:22:33:44:55。") + editor.setText(entry.sourceMac if fieldName == "src" else entry.destinationMac) + return + etherLayer = self._extract_ether_layer(entry.packet) + if etherLayer is None: + self.statusLabel.setText("当前流模板不包含 Ether 层,无法修改 MAC 地址。") + return + setattr(etherLayer, fieldName, value) + entry.preview = self.sendTaskService.buildPacketPreview(entry.packet) or entry.preview + entry.sourceMac, entry.destinationMac, entry.hasEtherLayer = self._extract_mac_fields(entry.packet) + self.statusLabel.setText(f"已更新 {entry.name} 的 MAC 地址。") + self._refresh_stream_table(selectedTemplateId=templateId) + + def _set_all_streams_enabled(self, enabled: bool) -> None: + for entry in self.streamTemplates: + entry.enabled = enabled + self._refresh_stream_table() + + def _handle_remove_selected_stream(self) -> None: + entry = self._current_selected_stream() + if entry is None: + return + self.streamTemplates = [streamEntry for streamEntry in self.streamTemplates if streamEntry.templateId != entry.templateId] + self.statusLabel.setText(f"已移除流模板: {entry.name}") + self._refresh_stream_table() + + def _handle_edit_selected_stream(self) -> None: + entry = self._current_selected_stream() + if entry is None: + self.statusLabel.setText("请先选中要跳转编辑的流模板。") + return + packet = entry.packet.copy() if hasattr(entry.packet, "copy") else entry.packet + self.editPacketRequested.emit(packet) + self.statusMessage.emit(f"跳转到包构建器编辑: {entry.name}") + + def _handle_stream_selection_changed(self) -> None: + self._sync_preview_with_selection() + self._set_running_state(self.workerThread is not None) + + def _sync_preview_with_selection(self) -> None: + entry = self._current_selected_stream() + if entry is None: + self.packetSummaryEdit.setText("当前没有可发送的流模板。") + self.packetStructureEdit.setPlainText("请先在包构建器中创建流模板。") + self.packetHexdumpEdit.setPlainText("") + return + self._apply_preview(entry.preview) + + def _apply_preview(self, preview: PacketPreview) -> None: + self.packetSummaryEdit.setText(preview.summary) + self.packetStructureEdit.setPlainText(preview.structure) + self.packetHexdumpEdit.setPlainText(preview.hexdump) + + def _selected_packets(self) -> list[Any]: + packets: list[Any] = [] + for entry in self.streamTemplates: + if not entry.enabled: + continue + packets.append(entry.packet.copy() if hasattr(entry.packet, "copy") else entry.packet) + return packets + + def _current_selected_stream(self) -> StreamTemplateEntry | None: + selectedItems = self.streamTable.selectedItems() + if not selectedItems: + return None + rowIndex = selectedItems[0].row() + if rowIndex < 0 or rowIndex >= len(self.streamTemplates): + return None + return self.streamTemplates[rowIndex] + + def _find_stream(self, templateId: int) -> StreamTemplateEntry | None: + for entry in self.streamTemplates: + if entry.templateId == templateId: + return entry + return None + + def _extract_ether_layer(self, packet: Any) -> Any | None: + if packet is None or not hasattr(packet, "haslayer") or not hasattr(packet, "getlayer"): + return None + try: + if packet.haslayer("Ether"): + return packet.getlayer("Ether") + except Exception: + return None + return None + + def _extract_mac_fields(self, packet: Any) -> tuple[str, str, bool]: + etherLayer = self._extract_ether_layer(packet) + if etherLayer is None: + return "", "", False + return ( + str(getattr(etherLayer, "src", "") or ""), + str(getattr(etherLayer, "dst", "") or ""), + True, + ) diff --git a/gui/tests/test_gui_smoke.py b/gui/tests/test_gui_smoke.py new file mode 100644 index 00000000000..77e187fbb56 --- /dev/null +++ b/gui/tests/test_gui_smoke.py @@ -0,0 +1,323 @@ +from __future__ import annotations + +import os +import time +import unittest + +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") + +import scapy.all as scapy +from PySide6 import QtCore, QtWidgets +from scapy.contrib.mac_control import MACControlPause + +from packet_studio.domain.packet_models import PacketPreview, PcapPacketRecord +from packet_studio.domain.task_models import PcapLoadResult, SendTaskResult, TaskState +from packet_studio.widgets.offline_analysis_widget import OfflineAnalysisWidget +from packet_studio.widgets.packet_builder_widget import PacketBuilderWidget +from packet_studio.widgets.send_task_widget import SendTaskWidget + + +def getOrCreateApplication() -> QtWidgets.QApplication: + application = QtWidgets.QApplication.instance() + if application is None: + application = QtWidgets.QApplication([]) + return application + + +class FakePacket: + def __init__(self, summary: str, structure: str, payload: bytes) -> None: + self._summary = summary + self._structure = structure + self._payload = payload + + def copy(self) -> "FakePacket": + return FakePacket(self._summary, self._structure, self._payload) + + def summary(self) -> str: + return self._summary + + def show(self, dump: bool = False) -> str: + if dump: + return self._structure + raise AssertionError("仅测试 dump=True 路径") + + def __bytes__(self) -> bytes: + return self._payload + + +class FakeSendTaskService: + def buildPacketPreview(self, packet: FakePacket | None) -> PacketPreview | None: + if packet is None: + return None + return PacketPreview( + summary=packet.summary(), + structure=packet.show(dump=True), + hexdump="0000 01 02 ..", + ) + + def execute( + self, + _request: object, + _packets: object | None, + stopRequested: object | None = None, + ) -> SendTaskResult: + return SendTaskResult( + mode="send", + sentCount=1, + packetPreview=PacketPreview( + summary="IP / TCP", + structure="request dump", + hexdump="0000 01 .", + ), + answerPreview=PacketPreview( + summary="IP / ICMP", + structure="reply dump", + hexdump="0000 08 00 ..", + ), + unansweredCount=0, + summaryText="模式: send,已发送 1 个数据包,未应答 0 个。", + logText="执行模式: send (L3)", + state=TaskState.succeeded("发送任务执行完成。"), + ) + + +class GuiSmokeTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.application = getOrCreateApplication() + + def waitUntil(self, predicate: object, timeoutSeconds: float = 3.0) -> None: + deadline = time.monotonic() + timeoutSeconds + while time.monotonic() < deadline: + self.application.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 50) + if predicate(): + return + time.sleep(0.01) + self.fail("等待 GUI 状态变化超时。") + + def test_packet_builder_widget_add_layer_updates_preview(self) -> None: + widget = PacketBuilderWidget() + emittedPackets: list[object | None] = [] + widget.packetChanged.connect(emittedPackets.append) + emittedPackets.clear() + + widget.addLayerButton.click() + + self.application.processEvents() + self.assertNotEqual(widget.summaryEdit.text(), "尚未添加任何协议层。") + self.assertTrue(widget.structureEdit.toPlainText()) + self.assertTrue(widget.hexdumpEdit.toPlainText()) + self.assertTrue(emittedPackets) + self.assertIsNotNone(emittedPackets[-1]) + widget.deleteLater() + + def test_packet_builder_widget_category_filter_hides_common_layers(self) -> None: + widget = PacketBuilderWidget() + + wirelessIndex = widget.layerCategoryCombo.findText("无线与近场") + self.assertGreaterEqual(wirelessIndex, 0) + + widget.layerCategoryCombo.setCurrentIndex(wirelessIndex) + self.application.processEvents() + + self.assertEqual(widget.layerTypeCombo.findData("ip"), -1) + self.assertGreater(widget.layerTypeCombo.count(), 0) + + allIndex = widget.layerCategoryCombo.findText("全部") + widget.layerCategoryCombo.setCurrentIndex(allIndex) + self.application.processEvents() + + self.assertGreaterEqual(widget.layerTypeCombo.findData("ip"), 0) + widget.deleteLater() + + def test_packet_builder_enum_popup_does_not_commit_on_open(self) -> None: + widget = PacketBuilderWidget() + widget.packetBuilderService.addLayer("icmp") + widget._refresh_all(selectLast=True) + + typeRow = -1 + for rowIndex in range(widget.fieldTable.rowCount()): + item = widget.fieldTable.item(rowIndex, 0) + if item is not None and item.text() == "type": + typeRow = rowIndex + break + + self.assertGreaterEqual(typeRow, 0) + comboBox = widget.fieldTable.cellWidget(typeRow, 3) + self.assertIsInstance(comboBox, QtWidgets.QComboBox) + + appliedValues: list[tuple[str, str]] = [] + originalApplyFieldValue = widget._apply_field_value + + def trackingApplyFieldValue(fieldName: str, rawValue: str) -> None: + appliedValues.append((fieldName, rawValue)) + originalApplyFieldValue(fieldName, rawValue) + + widget._apply_field_value = trackingApplyFieldValue + comboBox.showPopup() + self.application.processEvents() + comboBox.lineEdit().editingFinished.emit() + self.application.processEvents() + + self.assertEqual(appliedValues, []) + self.assertTrue(comboBox.view().isVisible()) + comboBox.hidePopup() + widget.deleteLater() + + def test_packet_builder_enum_custom_hex_value_is_applied(self) -> None: + widget = PacketBuilderWidget() + widget.packetBuilderService.addLayer("ether") + widget._refresh_all(selectLast=True) + + typeRow = -1 + for rowIndex in range(widget.fieldTable.rowCount()): + item = widget.fieldTable.item(rowIndex, 0) + if item is not None and item.text() == "type": + typeRow = rowIndex + break + + self.assertGreaterEqual(typeRow, 0) + comboBox = widget.fieldTable.cellWidget(typeRow, 3) + self.assertIsInstance(comboBox, QtWidgets.QComboBox) + + comboBox.lineEdit().setText("0x8808") + widget._handle_enum_editor_editing_finished("type", comboBox) + self.application.processEvents() + + self.assertEqual(widget.packetBuilderService.getFieldNativeValue(0, "type"), 0x8808) + self.assertEqual(widget.packetBuilderService.getFieldValue(0, "type"), "0x8808") + self.assertIn("0x8808", widget.summaryEdit.text()) + widget.deleteLater() + + def test_send_task_widget_execute_updates_result_preview(self) -> None: + packet = FakePacket("IP / TCP", "request dump", b"\x01\x02") + widget = SendTaskWidget() + widget.sendTaskService = FakeSendTaskService() + widget.addStreamFromPacket(packet.copy()) + + widget.executeButton.click() + + self.waitUntil(lambda: widget.workerThread is None) + self.assertEqual(widget.streamTable.rowCount(), 1) + self.assertEqual(widget.packetSummaryEdit.text(), "IP / TCP") + self.assertIn("request dump", widget.packetStructureEdit.toPlainText()) + self.assertIn("已发送 1 个数据包", widget.resultSummaryLabel.text()) + self.assertIn("reply dump", widget.answerStructureEdit.toPlainText()) + widget.deleteLater() + + def test_send_task_widget_only_enables_interface_for_sendp(self) -> None: + widget = SendTaskWidget() + + self.assertTrue(widget.interfaceCombo.isEnabled()) + widget.modeCombo.setCurrentIndex(0) + self.application.processEvents() + self.assertFalse(widget.interfaceCombo.isEnabled()) + widget.modeCombo.setCurrentIndex(2) + self.application.processEvents() + self.assertFalse(widget.interfaceCombo.isEnabled()) + widget.deleteLater() + + def test_send_task_widget_defaults_to_l2_mode_and_fixed_width_columns(self) -> None: + widget = SendTaskWidget() + + self.assertEqual(widget.modeCombo.currentData(), "sendp") + self.assertEqual(widget.streamTable.columnWidth(1), 130) + self.assertEqual(widget.streamTable.columnWidth(2), 220) + widget.deleteLater() + + def test_packet_builder_create_stream_emits_packet(self) -> None: + widget = PacketBuilderWidget() + emittedPackets: list[object] = [] + widget.createStreamRequested.connect(emittedPackets.append) + + widget.addLayerButton.click() + self.application.processEvents() + widget.createStreamButton.click() + self.application.processEvents() + + self.assertEqual(len(emittedPackets), 1) + self.assertIsNotNone(emittedPackets[0]) + self.assertIn("已创建流模板", widget.builderStatusLabel.text()) + widget.deleteLater() + + def test_packet_builder_save_stream_emits_packet_in_edit_mode(self) -> None: + widget = PacketBuilderWidget() + savedPackets: list[object] = [] + widget.saveStreamRequested.connect(savedPackets.append) + + widget.addLayerButton.click() + self.application.processEvents() + self.assertFalse(widget.saveStreamButton.isEnabled()) + + widget.setEditingStreamMode(True) + self.application.processEvents() + self.assertTrue(widget.saveStreamButton.isEnabled()) + + widget.saveStreamButton.click() + self.application.processEvents() + + self.assertEqual(len(savedPackets), 1) + self.assertIsNotNone(savedPackets[0]) + widget.deleteLater() + + def test_offline_analysis_widget_load_result_populates_table_and_copy(self) -> None: + widget = OfflineAnalysisWidget() + emittedPackets: list[object] = [] + widget.importPacketRequested.connect(emittedPackets.append) + packet = FakePacket("IP / UDP", "offline dump", b"\x11\x22") + result = PcapLoadResult( + filePath="capture.pcap", + packetRecords=[ + PcapPacketRecord( + index=1, + timestampText="2026-05-11 10:00:00.000", + sourceText="Ethernet0", + protocolName="UDP", + preview=PacketPreview( + summary="IP / UDP", + structure="offline dump", + hexdump="0000 11 22 .\"", + ), + packet=packet, + ), + ], + summaryText="离线抓包文件加载完成,共 1 个数据包。", + logText="离线抓包文件加载完成,共 1 个数据包。", + state=TaskState.succeeded("离线抓包文件加载完成。"), + ) + + widget._on_load_finished(result) + + self.application.processEvents() + self.assertEqual(widget.packetTable.rowCount(), 1) + self.assertIn("capture.pcap", widget.summaryLabel.text()) + self.assertEqual(widget.packetDetailSummary.text(), "IP / UDP") + widget.copyToBuilderButton.click() + self.application.processEvents() + self.assertEqual(len(emittedPackets), 1) + self.assertEqual(emittedPackets[0].summary(), "IP / UDP") + widget.deleteLater() + + def test_packet_builder_widget_loads_decoded_mac_control_pause_packet(self) -> None: + widget = PacketBuilderWidget() + packet = scapy.Ether( + bytes( + scapy.Ether(type=0x8808) + / MACControlPause(pause_time=3) + ) + ) + + widget.loadPacket(packet) + + self.application.processEvents() + self.assertEqual(widget.builderStatusLabel.text(), "已导入当前数据包。") + self.assertEqual( + [record.name for record in widget.packetBuilderService.getLayerRecords()], + ["Ethernet", "MACControlPause", "Raw"], + ) + self.assertIn("MACControlPause", widget.summaryEdit.text()) + widget.deleteLater() + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/gui/tests/test_packet_builder_service_import.py b/gui/tests/test_packet_builder_service_import.py new file mode 100644 index 00000000000..063d930fe8d --- /dev/null +++ b/gui/tests/test_packet_builder_service_import.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import unittest + +from packet_studio.services.packet_builder_service import PacketBuilderService + + +class PacketBuilderServiceImportTests(unittest.TestCase): + def test_list_available_layers_includes_additional_scapy_protocols(self) -> None: + service = PacketBuilderService() + + availableLayerNames = {layer.packetClassName for layer in service.listAvailableLayers()} + + self.assertIn("LLC", availableLayerNames) + self.assertIn("STP", availableLayerNames) + self.assertIn("BOOTP", availableLayerNames) + + def test_dynamic_layer_labels_do_not_use_packet_name_descriptor_repr(self) -> None: + service = PacketBuilderService() + + dhcp6ReplyLayer = next( + layer for layer in service.listAvailableLayers() if layer.packetClassName == "DHCP6_Reply" + ) + + self.assertEqual(dhcp6ReplyLayer.label, "DHCP6_Reply") + self.assertNotIn(" None: + service = PacketBuilderService() + import scapy.all as scapy + + packet = scapy.IP(dst="1.1.1.1") / scapy.ICMP() / scapy.Raw(load=b"abc") + + service.importPacket(packet) + + layerRecords = service.getLayerRecords() + self.assertEqual([record.name for record in layerRecords], ["IP", "ICMP", "Raw"]) + rebuiltPacket = service.buildPacket() + self.assertIsNotNone(rebuiltPacket) + self.assertEqual(bytes(rebuiltPacket), bytes(packet)) + + def test_ether_type_field_uses_hex_and_contains_common_choices(self) -> None: + service = PacketBuilderService() + service.addLayer("ether") + + fieldRecords = service.getFieldRecords(0) + typeField = next(record for record in fieldRecords if record.name == "type") + + self.assertEqual(typeField.currentValue, "0x9000") + self.assertIn(("0x0800", "IPv4 (0x0800)"), typeField.choices) + self.assertIn(("0x8808", "Ethernet PAUSE (0x8808)"), typeField.choices) + + def test_ether_type_field_accepts_custom_hex_value(self) -> None: + service = PacketBuilderService() + service.addLayer("ether") + + service.setFieldValue(0, "type", "0x8808") + + self.assertEqual(service.getFieldNativeValue(0, "type"), 0x8808) + self.assertEqual(service.getFieldValue(0, "type"), "0x8808") + + def test_import_packet_skips_abstract_mac_control_wrapper(self) -> None: + service = PacketBuilderService() + import scapy.all as scapy + from scapy.contrib.mac_control import MACControlPause + + packet = scapy.Ether(bytes(scapy.Ether(type=0x8808) / MACControlPause(pause_time=3))) + + service.importPacket(packet) + + layerRecords = service.getLayerRecords() + self.assertEqual( + [record.name for record in layerRecords], + ["Ethernet", "MACControlPause", "Raw"], + ) + rebuiltPacket = service.buildPacket() + self.assertIsNotNone(rebuiltPacket) + self.assertEqual(bytes(rebuiltPacket), bytes(packet)) + + def test_import_packet_supports_previously_missing_llc_and_stp_layers(self) -> None: + service = PacketBuilderService() + import scapy.all as scapy + + packet = scapy.Ether() / scapy.LLC() / scapy.STP() + + service.importPacket(packet) + + layerRecords = service.getLayerRecords() + self.assertEqual( + [record.name for record in layerRecords], + ["Ethernet", "LLC", "Spanning Tree Protocol"], + ) + rebuiltPacket = service.buildPacket() + self.assertIsNotNone(rebuiltPacket) + self.assertEqual(bytes(rebuiltPacket), bytes(packet)) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/gui/tests/test_pcap_analysis_service.py b/gui/tests/test_pcap_analysis_service.py new file mode 100644 index 00000000000..a049da248e7 --- /dev/null +++ b/gui/tests/test_pcap_analysis_service.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import os +import tempfile +import unittest + +import scapy.all as scapy + +from packet_studio.domain.task_models import TaskPhase +from packet_studio.services.pcap_analysis_service import PcapAnalysisService + + +class PcapAnalysisServiceTests(unittest.TestCase): + def test_load_packets_reads_expected_count(self) -> None: + fd, filePath = tempfile.mkstemp(suffix=".pcap") + os.close(fd) + try: + scapy.wrpcap( + filePath, + [ + scapy.IP(dst="1.1.1.1") / scapy.ICMP(), + scapy.IP(dst="8.8.8.8") / scapy.UDP(dport=53), + ], + ) + + service = PcapAnalysisService() + result = service.loadPackets(filePath, maxPackets=10) + + self.assertEqual(result.packetCount, 2) + self.assertIn("ICMP", result.packetRecords[0].summary) + self.assertIn("UDP", result.packetRecords[1].summary) + self.assertEqual(result.packetRecords[0].protocolName, "ICMP") + self.assertIn("IP", result.packetRecords[0].preview.structure) + self.assertTrue(result.packetRecords[0].preview.hexdump) + self.assertIn("加载完成,共 2 个数据包", result.summaryText) + self.assertEqual(result.state.phase, TaskPhase.SUCCEEDED) + finally: + if os.path.exists(filePath): + os.unlink(filePath) + + def test_load_packets_honors_limit(self) -> None: + fd, filePath = tempfile.mkstemp(suffix=".pcap") + os.close(fd) + try: + scapy.wrpcap( + filePath, + [ + scapy.IP(dst="1.1.1.1") / scapy.ICMP(), + scapy.IP(dst="8.8.8.8") / scapy.UDP(dport=53), + ], + ) + + service = PcapAnalysisService() + result = service.loadPackets(filePath, maxPackets=1) + + self.assertEqual(result.packetCount, 1) + finally: + if os.path.exists(filePath): + os.unlink(filePath) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/gui/tests/test_scapy_packet_adapter.py b/gui/tests/test_scapy_packet_adapter.py new file mode 100644 index 00000000000..98023c29921 --- /dev/null +++ b/gui/tests/test_scapy_packet_adapter.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import unittest + +from packet_studio.adapters.scapy_packet_adapter import ScapyPacketAdapter + + +class FakePacket: + def __init__(self, summary: str, dump: str, payload: bytes) -> None: + self._summary = summary + self._dump = dump + self._payload = payload + + def copy(self) -> "FakePacket": + return FakePacket(self._summary, self._dump, self._payload) + + def summary(self) -> str: + return self._summary + + def show(self, dump: bool = False) -> str: + if dump: + return self._dump + raise AssertionError("仅测试 dump=True 路径") + + def __bytes__(self) -> bytes: + return self._payload + + +class ScapyPacketAdapterTests(unittest.TestCase): + def test_build_preview_formats_packet_fields(self) -> None: + adapter = ScapyPacketAdapter() + packet = FakePacket("IP / TCP", "packet dump", b"\x01\x02ABC") + + preview = adapter.buildPreview(packet) + + self.assertEqual(preview.summary, "IP / TCP") + self.assertEqual(preview.structure, "packet dump") + self.assertIn("0000", preview.hexdump) + self.assertIn("01 02 41 42 43", preview.hexdump) + + def test_clone_packet_returns_copy(self) -> None: + adapter = ScapyPacketAdapter() + packet = FakePacket("IP", "dump", b"\x00") + + clonedPacket = adapter.clonePacket(packet) + + self.assertIsNot(packet, clonedPacket) + self.assertEqual(bytes(clonedPacket), bytes(packet)) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/gui/tests/test_send_task_service.py b/gui/tests/test_send_task_service.py new file mode 100644 index 00000000000..34547614f94 --- /dev/null +++ b/gui/tests/test_send_task_service.py @@ -0,0 +1,185 @@ +from __future__ import annotations + +import unittest +from itertools import count + +from packet_studio.domain.task_models import TaskPhase +from packet_studio.services.send_task_service import SendTaskRequest, SendTaskService + + +class FakePacket: + def __init__(self, summary: str, dump: str, payload: bytes) -> None: + self._summary = summary + self._dump = dump + self._payload = payload + + def copy(self) -> "FakePacket": + return FakePacket(self._summary, self._dump, self._payload) + + def summary(self) -> str: + return self._summary + + def show(self, dump: bool = False) -> str: + if dump: + return self._dump + raise AssertionError("仅测试 dump=True 路径") + + def __bytes__(self) -> bytes: + return self._payload + + +class FakeScapy: + def __init__(self) -> None: + self.calls: list[tuple[str, object, dict[str, object]]] = [] + self.sr1Response: object | None = None + + def send(self, packet: object, **kwargs: object) -> list[object]: + self.calls.append(("send", packet, dict(kwargs))) + count = int(kwargs.get("count", 1)) + return [packet] * count + + def sendp(self, packet: object, **kwargs: object) -> list[object]: + self.calls.append(("sendp", packet, dict(kwargs))) + count = int(kwargs.get("count", 1)) + return [packet] * count + + def sr1(self, packet: object, **kwargs: object) -> object | None: + self.calls.append(("sr1", packet, dict(kwargs))) + return self.sr1Response + + +class SendTaskServiceTests(unittest.TestCase): + def test_send_ignores_interface_for_l3_mode(self) -> None: + fakeScapy = FakeScapy() + service = SendTaskService(fakeScapy) + packet = FakePacket("IP / TCP", "packet dump", b"\x01\x02") + + result = service.execute( + SendTaskRequest( + mode="send", + interfaceName="Ethernet0", + count=3, + intervalSeconds=0.25, + ), + packet, + ) + + self.assertEqual(result.sentCount, 3) + self.assertEqual(fakeScapy.calls[0][0], "send") + self.assertNotIn("iface", fakeScapy.calls[0][2]) + self.assertEqual(len(fakeScapy.calls), 3) + self.assertEqual(fakeScapy.calls[0][2]["count"], 1) + self.assertEqual(fakeScapy.calls[0][2]["inter"], 0.0) + self.assertIn("未显式传入 iface", result.logText) + self.assertIn("已完成发送 3 个", result.summaryText) + self.assertEqual(result.state.phase, TaskPhase.SUCCEEDED) + + def test_sendp_passes_interface_for_l2_mode(self) -> None: + fakeScapy = FakeScapy() + service = SendTaskService(fakeScapy) + packet = FakePacket("Ether / ARP", "arp dump", b"\xaa\xbb") + + result = service.execute( + SendTaskRequest( + mode="sendp", + interfaceName="\\Device\\NPF_{123}", + count=2, + intervalSeconds=0.1, + ), + packet, + ) + + self.assertEqual(result.sentCount, 2) + self.assertEqual(fakeScapy.calls[0][0], "sendp") + self.assertEqual(fakeScapy.calls[0][2]["iface"], "\\Device\\NPF_{123}") + self.assertIn("未应答 0 个", result.summaryText) + self.assertEqual(result.state.phase, TaskPhase.SUCCEEDED) + + def test_sr1_returns_answer_preview(self) -> None: + fakeScapy = FakeScapy() + fakeScapy.sr1Response = FakePacket("IP / ICMP", "reply dump", b"\x08\x00") + service = SendTaskService(fakeScapy) + packet = FakePacket("IP / ICMP", "request dump", b"\x01") + + result = service.execute( + SendTaskRequest( + mode="sr1", + timeoutSeconds=2.5, + retryCount=1, + ), + packet, + ) + + self.assertEqual(result.sentCount, 1) + self.assertEqual(result.unansweredCount, 0) + self.assertIsNotNone(result.answerPreview) + self.assertEqual(result.answerPreview.summary, "IP / ICMP") + self.assertEqual(fakeScapy.calls[0][0], "sr1") + self.assertEqual(fakeScapy.calls[0][2]["timeout"], 2.5) + self.assertEqual(fakeScapy.calls[0][2]["retry"], 1) + self.assertIn("未应答 0 个", result.summaryText) + self.assertEqual(result.state.phase, TaskPhase.SUCCEEDED) + + def test_send_rotates_across_multiple_streams(self) -> None: + fakeScapy = FakeScapy() + service = SendTaskService(fakeScapy) + packets = [ + FakePacket("IP / TCP 1", "packet dump 1", b"\x01"), + FakePacket("IP / TCP 2", "packet dump 2", b"\x02"), + ] + + result = service.execute( + SendTaskRequest( + mode="send", + count=2, + intervalSeconds=0.0, + ), + packets, + ) + + self.assertEqual(result.sentCount, 4) + self.assertEqual(len(fakeScapy.calls), 4) + self.assertIn("流数量: 2", result.logText) + self.assertIn("共 2 条流", result.summaryText) + + def test_continuous_send_can_be_stopped(self) -> None: + fakeScapy = FakeScapy() + service = SendTaskService(fakeScapy) + packet = FakePacket("IP / TCP", "packet dump", b"\x01\x02") + invocationCounter = count() + + def stopRequested() -> bool: + return next(invocationCounter) >= 3 + + sleepCalls: list[float] = [] + + result = service.execute( + SendTaskRequest( + mode="send", + sendStrategy="continuous", + intervalSeconds=0.2, + ), + packet, + stopRequested=stopRequested, + sleep=sleepCalls.append, + ) + + self.assertEqual(result.sentCount, 2) + self.assertEqual(result.state.phase, TaskPhase.STOPPED) + self.assertEqual(sleepCalls, [0.2]) + self.assertIn("已停止发送 2 个数据包", result.summaryText) + + def test_sr1_continuous_is_rejected(self) -> None: + fakeScapy = FakeScapy() + service = SendTaskService(fakeScapy) + packet = FakePacket("IP / ICMP", "request dump", b"\x01") + + with self.assertRaisesRegex(ValueError, "sr1 当前仅支持 burst 模式"): + service.execute( + SendTaskRequest(mode="sr1", sendStrategy="continuous"), + packet, + ) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/gui/tests/test_tool_registry_service.py b/gui/tests/test_tool_registry_service.py new file mode 100644 index 00000000000..722f8cae2fa --- /dev/null +++ b/gui/tests/test_tool_registry_service.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +import unittest + +from packet_studio.services.tool_registry_service import ToolRegistryService + + +class ToolRegistryServiceTests(unittest.TestCase): + def test_list_tools_contains_core_entries(self) -> None: + service = ToolRegistryService() + + tools = service.listTools() + + self.assertGreaterEqual(len(tools), 3) + titles = [tool.title for tool in tools] + self.assertIn("包构建器", titles) + self.assertIn("发送任务", titles) + self.assertIn("离线分析", titles) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/gui/tests/test_workspace_document_service.py b/gui/tests/test_workspace_document_service.py new file mode 100644 index 00000000000..b68b500610e --- /dev/null +++ b/gui/tests/test_workspace_document_service.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import unittest + +from packet_studio.domain.task_models import TaskPhase, TaskState +from packet_studio.domain.workspace_models import WorkspacePanelSnapshot +from packet_studio.services.workspace_document_service import WorkspaceDocumentService + + +class WorkspaceDocumentServiceTests(unittest.TestCase): + def test_create_task_record_assigns_fields(self) -> None: + service = WorkspaceDocumentService() + + record = service.createTaskRecord( + sequenceNumber=3, + sourceTitle="发送任务", + message="发送任务执行完成。", + phase=TaskPhase.SUCCEEDED, + detailText="模式: send", + ) + + self.assertEqual(record.sequenceNumber, 3) + self.assertEqual(record.sourceTitle, "发送任务") + self.assertEqual(record.phase, TaskPhase.SUCCEEDED) + self.assertEqual(record.detailText, "模式: send") + + def test_build_workspace_document_generates_summary_text(self) -> None: + service = WorkspaceDocumentService() + panelSnapshots = [ + WorkspacePanelSnapshot( + panelId="send-task", + title="发送任务", + taskState=TaskState.succeeded("发送任务执行完成。"), + itemCount=1, + detailText="模式: send", + ), + ] + taskRecords = [ + service.createTaskRecord(1, "发送任务", "发送任务执行完成。", TaskPhase.SUCCEEDED), + ] + + document = service.buildWorkspaceDocument( + activeTabTitle="发送任务", + openTabTitles=["欢迎", "发送任务"], + panelSnapshots=panelSnapshots, + taskRecords=taskRecords, + interfaceCount=2, + interfaceSummaryText="已发现 2 个可用接口。", + ) + + self.assertEqual(document.taskCount, 1) + summaryText = document.to_multiline_text() + self.assertIn("当前页签: 发送任务", summaryText) + self.assertIn("任务记录数: 1", summaryText) + self.assertIn("发送任务", summaryText) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file