From af768ad840f2ecf689e5ec45daa707c64092dfb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 04:42:05 +0000 Subject: [PATCH 1/6] Initial plan From ca48aa59736ae3051171a1bea69ac26dda1a3c13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 04:45:11 +0000 Subject: [PATCH 2/6] Initial plan for cleanup and improvements Co-authored-by: meros <450310+meros@users.noreply.github.com> --- target/classes/.mavenResources.target.classes | 0 .../meros/pb4mina/BoundedInputStream.class | Bin 1551 -> 1555 bytes .../meros/pb4mina/ProtoBufCoderFactory.class | Bin 1655 -> 1643 bytes .../meros/pb4mina/ProtoBufCoderFilter.class | Bin 595 -> 595 bytes .../meros/pb4mina/ProtoBufDecoder$State.class | Bin 1180 -> 1211 bytes .../org/meros/pb4mina/ProtoBufDecoder.class | Bin 3536 -> 3544 bytes .../org/meros/pb4mina/ProtoBufEncoder.class | Bin 1968 -> 1968 bytes .../pb4mina/ProtoBufMessageFactory.class | Bin 332 -> 332 bytes target/maven-archiver/pom.properties | 5 ----- .../compile/default-compile/createdFiles.lst | 7 +++++++ .../compile/default-compile/inputFiles.lst | 6 ++++++ target/minaserver-1.0-SNAPSHOT.jar | Bin 2245 -> 0 bytes 12 files changed, 13 insertions(+), 5 deletions(-) delete mode 100644 target/classes/.mavenResources.target.classes delete mode 100644 target/maven-archiver/pom.properties create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst delete mode 100644 target/minaserver-1.0-SNAPSHOT.jar diff --git a/target/classes/.mavenResources.target.classes b/target/classes/.mavenResources.target.classes deleted file mode 100644 index e69de29..0000000 diff --git a/target/classes/org/meros/pb4mina/BoundedInputStream.class b/target/classes/org/meros/pb4mina/BoundedInputStream.class index 3a954224a3d711e90e6addfcc19870a9858234a1..7fcff268b73a08f0bf1c56f3d31099e3ea99f6b8 100644 GIT binary patch literal 1555 zcma)5U2hXd6g{)+#Kc)AjYA+#Oj%k;{Sj~rp|qIKAdoBts@72?ny0lriMQChX1y-( z-e1w@N`2v_Qc0>-3h(_gV_7bRs*4~-DbLZT1&zbrApFfWPY~XGN2Fx^47N#(* zu=s=Xqf>SLYTfG(!$ufrr>8J|!}Z+omV#LYZdV7k zAK3co(CJE2*wJ?LMW_e%xSs8L{EazgFpH0*hXsYD;?{%#TX4PoT4`qni#VCal7&+^ zt#IBC_NzS|_=9S{xzTeyr@HA6y|!+@)%k3q29Za=Z_85?g@4@P|Q5KXBz}o_^QbOCl-G(K>?{!QjJV5zXi$ z$7!$tF7kbeUq>*J;&`Qe2vw*YApHxne_{4AOJ z{Q5USq-bLi1%gxwvc|guq;IqL| zWsNn|Li83bC0(+mdzzY)-`H4it z-Ac0Fgh=5`GX8!Zm&xM_d0ZX4Mz}GLO3XuIHzTiPc8fFf|G2$zYnvQJ-h7h7nA-s= Z#}&CwCN(m-nNU9;GkLELw~21x&VOEU8}tAG literal 1551 zcma)5TTc@~7(KIFSX;LsAeTbb1#h%Ps^As8Vl-)j8Z9AFpSJBlSKHmX-FkWVSNLq= zi!UYuMiYJaN2zDF&9VU=3=K2$&H29X%sJow{`37OfCbz!pb4Dv{LNxh`d+)(s$6V3 zu3cR5I&MwYN^YwYlmlPdO#=ymkr(!ByXbhu*oDAhXBQ?gx_SU;Ss+n!HZ}xANud9_ z<2u1jfz0fI;8Jd#_`66R)G-P;JGH_I2GHl+k z-Ob{YjvF{ldGBpiwgSg<4`EE8{M74Mu9P(^@T_)AR-KJ^mTfhpyBXB2cGGS&q;Gk? zC0}*yh5`k3X;t0@vTa5EEXU<9k|u=_Oem5WzP|UOkO7r*+|A|OdJ+OtCi>8?cE?N@ zIHa;ps4PohBA&HY_q{jjSxhuA6IcCF^caD`RmYVNJI#vp*SaDMuX>XBUSb~epTLg)NqXNpXPbh7Sl9C=ZB#2kKTkkI>WV}H%X-U|1eMA ziAr!?%*g69 zh5nbtbOXYGPTXSy(`wxKu@GD!B*D{k_@XO}s8Z?tS4@&Pmu&4b16s&YJs+?_9%dKV z;~_AIbBrii=C~@Hrti%>9m~|B%y|^|5qU+aHB~E47Q(Owo%Q|{duEW}LM4*K8(aX0u`&U5UP zgMK6g%wlP^QY_sONUi-3wbX|c227lGFo?ANEM0vl7mYIlqd_8*EY@o~59*I?3?akQ z4C}6#vAO5GgA2H*@2NF0s^6-d&5Yv?E@2{gu@W_Lxd+i-Y8^vNvL-L6xqeZ{YIv>Ycyk4p|&0B)PdpOvS=#;E);BLp;cvAA6d!5 z$r_ZW8R^WDSEjfmYkM6CFZ&VZ`>@v680nMsug_wJu>{9Sw?#OCoCRjag8`^ z%FNOH64$B9g7}3Ale|26tmFoL3s^j!W1c)#W9(v#86HVw5IN>ocyBQMw u1GKOdwy-c3$j@LGlUp53?P077NjI^FIB!aX4E!$wNt!lAkpkT%ZvFvtJ7t0Z delta 730 zcmZXQPj3=Y6vfY*d30bp0tH$r6x*s`8K$N6PpuW2G%70_qYF)!IFg2jfT_f&8{)>5 z8}hb(f$o%QNNu9-O#CD!egHSdc;3{Y(YJVi?mhS1-+kYl?@s*7&-WhyoX3U*L!i)q znNj=FnXw=R;=A?7^>VA;-YMU@zuWM;7RCft)NLd0TbX&H<48J4Au2Fd2~-gXOgXR+wqYWvpVJN^u+>+i zsCr__)}d1l#^I=#`KoN+c;Yu6cAK5{BMW(fE`Cl8Q*!-PQtP;PRcpizQEF=T@bXhOc< zRyAsS+Rb)Z?v~ym_pd>k-KWWu2@Ix!-Zo}2$12tZgo1YSIL+J{#w1^71JV~n&=78E zfb<~>2DZG<$Q;4jcHbg5#L`x2kV4u!#IlKd-X~mqO}UATdKSv|H1XWA!Kk5&Ra|BR zo0&E42Ch&QhWLTJMXkU(uKowI#<(6+4lt=Z9RJW5qrl={gUL}JiPdP(NT3oFRDS`V C%w_ff diff --git a/target/classes/org/meros/pb4mina/ProtoBufCoderFilter.class b/target/classes/org/meros/pb4mina/ProtoBufCoderFilter.class index e43b7f06d7842bff2703698b6035bf075c1f7bc3..7dbc2fd9ea4cd4fb7dc985017b72aedf1dd3c07b 100644 GIT binary patch delta 135 zcmcc2a+!tq)W2Q(7#J8#7{u8bm?o~%=3r!C;bCB9V4Ik2DZs_R!NAGRzy%WEW@Ip! z_(7XBGp8iAXyR*2Hg*PHMh4Z18x3yUCkiNVXXcco7O^w1FftfS+^5Y7=1z35>IXsFgFD#qhkiE)+b3-vZPI9B+FpTvd^9O zyr~M@GGQXipwHL0YHxRJhSIyfxeW|qSVz{x2yzT5#|wPHkS|)b6Mo9e9`~E&y~c@f zqN;&B?&ugbF@|w!J?}%Zo*pt2O@pik~vtdUe=}_&hAPE$I!Ngrm zG7PM{z8h^YjP_E+nY&g!jg&#M6w=7Zb;iUj=A;Iyhc?4f(du6Xs`ooDxlQ9^7^=Cx zc-3h&MEHg`Jlc8ok4E~RAw#YfINYoA(3N~KY0~E{;?CVvhl~>)yL6F^?KXWLbwa{v zMXOF;Sa-ZwonBd`hU!^^{|Me~T zNZEgLl}3}NccX1i$VULsN;U?D3(~W6lA?GXbA)?9(JZaw5>R8<|}38rnGZR ypFvZ>y`&2C3*0}0A^W$qniN literal 1180 zcmb7DT~8B16g|VX-FDdmR=z(}v`Sl$0#RRD35u8ymyfv8r15FG49jA>OSW4Q{*{&o zh(_YGKgxJ#Tj|EcST>oRxjXlsd(O<+pTE9d16aeFiUEcN&$o*W;d{;EY3*6Vad~my z_X6*A>*$TJyhGt<&433&MT}wSgrD(Zox65%%WX9n((eU7bX?mIt{ogRjdizN-kevh;0(}B;pzvhINeK4nv~c zO&QM_Jt1b`lYr88zLKKcyE@`XB%#V+T!)H;TutgoB1KS!lv@N1TB_w`Tiwocz}m0<{3sJRL`4%Xy|x^EJKR>K0mj- z({nnjsGz;tiLipwc0}W!Pb88-4GHZXF}#eHb@H~pNS?A4WDboH1 zx+g{2hdiK}Udy+{TSp${=wHTLmgywabZnOgEuT`Ns1l;ww@22go4)P3!Vl?*rivAY z`TyFLAs+gFV})#>(2t{ROi7CXG?P1kk!k4-+KJJuVUY54iYgS#XZaggHIr5@F?4}U z7>vfkU@ZQfwv;w9W;NEvn0bMTFH|vrDS8iu-R6+SJxpVkhTM^Z%-rpET+Xh{zJrIRflZd_h8F`FI->)pttax>}Zuc9fYb++X@kI~^(=GoVdWIraHzt1rdczWV diff --git a/target/classes/org/meros/pb4mina/ProtoBufDecoder.class b/target/classes/org/meros/pb4mina/ProtoBufDecoder.class index c7a2061cf1f5209d8246f8c08911f06478634fc9..252f0f6b8d3fa32db7bb225323cede4001d3ecd5 100644 GIT binary patch delta 1450 zcmY*ZTT>iG6#ja4W_xzFcY_;P*aZTF7}yJvgb=PmBm{v#g0K=J-Ub*q%fdo7OM>x2 zG{(EpwX1wIRe8V{s>DC@e(zw@2%oavt` zf2j=r`1g0;12_t4L4%7!7ck&vI3~OG>U^Ry?CwrwQn?-mS7T(-0}~z&OTY^uZ?b49 zgKyliXH%Kvh?7a?&NDx#v&RJvqCt+k zy6R0t1fmcO{#|Bc#SqFbF3^N02QQ4ul))TwS*;LM9C~F*=QKI7DRgR>9M(_p#s@P;f^%8w6uz)f! zWXRUANMGuvf=xX_qfW1ZCGxmHR+4-aPEGVKSoAjdCVWxXCIZobe;dI~R7GQ(sQs2) zG!0q#hgGwBktV_7DdezBGdf>j!3g~lE*G&`5H5-Yq@#D>Bj`1Rqw#GVQ0R9M&~Cz_ zh>a>@!J%;t4RJDS)$TT8oq9;WkJ6@)zJq2ByX^GC>t7?>sgqMxpiS*{xDcbESHoG? zI>iP~V;rp%Jxg{E_Q)-7Y?XQjgGf-T*%(HOd{iDiaHE5=S;1?>JwZxc!RrN$(*Nd` z#v6FEfSbl!SS@6p##Ovc5#AvT5A?dGK91HPEcFkOgOc_>!EhSeqgy!H(@|Y@3kKV` zhg0WhVYKQlp6S$MJ4i5e8X-d^@;qe_GIsC^ExAMP4LVFY$gf00LGwX^3)7!|4et{4 zeqwzc?~%ePQ^nt{g5iB)sGj!SiYQBUfJUAz_<*b;Yd6?m>MHnQk}Df0Z*DhzG?5p_<~M`GKU9gS~cZk@b}N(T$oPSHg;M_3uUtS+F6YM^wkEu!tR xE2n*x1*3ffAK_z)s+4qrS^puV;S*Za@F~h~kP#r$q`v>`cm<~LIlgdv{s9d>10(G}PE9LQ8-aq_(xKf!rqHk-RnG_mlT~uk*XV zzWx?K2Ly+*@=LS9p)que$Gm=}$E&#K#hOwj92&3Km0fXeAeD=wootC#Gf+ zDQn2erV8ot+>DhS%iJUlHYgacE_eP*F7B{~at7m2CYve57}{IOPp$Z!RrrGw4BD|= z(sDq>(AwjunVhJTX6>n4G01IG(61IB*e{IW95EdYxB^KX^s0iC2(d zu;;D01a%~vDpq`uV@iCf1#K5$nYer|ZTnvK(_FqKAoP z4)fx?e%zi%fn$N8Nenx@&gP@nGP6l5Z{ixRi${9ADvw1IH*k}NX;R7<~ZLb1KJY;*_Ix ziF{H83Ksm-6qSZrhf6k;5afW+vj%p^mX#4!VA7MurHE++=+x+6iKj@zMJPo&3Z1^t z1MCTX1@|VrA$1e=As5?*Zxj1M;Y|eANku_W{O0g2g$TP#MhK&g-pIU2f(9Jz*k5wZ z%88&(vUF$zUh;e&wV}3cM5OZ#)GK%4pn#n+U{Rs-K4NVI15&z;?x^O`9wOZC(RL73 zuuD$czZ%s@W1s7=6dY9%rei?CS#^~H196OCm_i2$M^P?z%EC(nIE_JE!b$4(Fk}L; zV$IS|5=di|bfkS6XE0VwFO6}$NS+Q-x)V4{$(=?OUcxy-=SkBB)j!8E?56tC*FiQH z=vE^y!^PS@B+TiiHx}B$%dz^pd(he1H<&y}@5#CcNJq8s4rUmlx<{AUT%|NT`VL;D zE5l=~kufDAzAyvDst=Uh;WZ*Cf4#d^mq<2Qq4!R_PDrIa(ttPcCQ;Z+^p|jpWLj!DJdA6IAdC?$GHXl!xQb~aM5rYQufF>d?{lD9E_ z1{W9;1~rO_D;I8D_zC)o+|37y(yc=%#^84)vV1Si!0vH<8!NOMb zdp%aHX=*jISkKsHR3ywVo}evRk=KKKC@cni5wUKBtN7ggQdy)KRZ- z9U27cMWD<392g1K(d=-F)1GDYRkqTmX?N(LQ_<-?H#)sFV=355mqRyE@5pElYR)>u z==FXWBMq&@9nSgcthM~3?FT+a!l9o5Z`d5D9Aro{>@Y%7kr4d}@0HoMlTTRY^u}s- z=l!=gU(I>dKJ(;$cC(nyj~yX#r4Y!nprj4~$iqTB+EiwOa2@e}eA zm#8FKJ^QHFrIJbUei~|-67L)raEWQTDK0Z3L;U7n7z8DzW1rA33B99irMhCD(8t)( zJUVH*<{>8(gI2`S_YOFjv|W3TrUP1%+SL(jk29|6>O*=0xO$I1KYc#k>)MB02;lQE znDoMWsOU3UI!61;L~yynY>8c(tIU-IrD0*fep+~k(1>z8j^ YlzEy|2}4zJTMniSyCasCbAY>l04juTOaK4? delta 615 zcmXYt$xl;J6vlt|P4`hAMGz>j${-YNu~01tQY(Xq1CAh4S*a0>7y~GnxG+)w1Dos0 zxOCwXV`R|8Ve8iL-#DFDT--C=bH498XT5g4w&us5PhWsMa~jJORm#&iiaztu$?CEB z+be4ekIWmpztt-)E-kJNE9`V;R^f~<&o9KVsR^heW_IkK@z}(ZI}0moi_1%^nkMtt zZL&4Z<(us)S$^KB_LtvI`5~>*KwJ5D(ob+8po31+rjpe$x;Ugcs7RR?ZpsZvlQFN< zkl)4OfGj;`NA-F+j%tpWxRsWUyyzUTru`hp0tOf~FRd0q9SSIL!hE!bk~vNW3=>nh zcON`ny%({J28?ma_;!EQY0hXS6nR-UFMww`XP(*}AKp2W$~1eaXR@wbN70OnCtgct z;mcFe+G4Avk`u4QyWN`v?<*~FvC#lDP%AdrZD0>|k}2xhDtw+Z?wD z^#;Z0d!*D8dfSX!M9OU9#?^!OBTdu0Z2vV0FXx%8=qz!8i=r>8UE;D#B^c!jS4C|* ggG|Y8E*W|S@wQ+Fl;8x?zOEsTx}JV+`TuQ}lbAEG>!4=~NPm6TC&YjTl)^*}wf3hTOikS8R zm5QBnx`TwhgMv%uYONKK;TD=~93)yJd^+eeP{zx+*z3Tfohw~`9u;U5iCsS>XTr<0 zDz=Yq4%!tv8DP$0V7M57>UwUVvvM=@5{pxd%2JEa+z*svU;+_9AP2;Hxrt?|c|obg z`K3k4sl|FFiACwDC3?v@iN(dK#msPPI2d{Y<87s$%P{3LGBCUb8UgY>k~NM61rS|Z zgM7W24Fzg9ZoRwi>b0!DdyYi!-J&pW4Tn;d*4F^hBf(P6z3l4XU*OPRjCK@zf zEBqhjc|PFARkz=^Z86E8dlc&v^g{(z+fJE(yI}d6>5Rdh`BKZ<%%oO(>*daQ;Zc#x z>p$nMyO!L$4;xB~mhR{ier>Zo?}A9@gewydNxZI%|ES-z;mXx*=}&t<{SI1nBzk(| zqkBaM|K=|G*TXNEu&pQajNujOx%sxX@3%;93En%k&uo=-*Y8tvIVK$lcpag+$oOGN zRK3~fe_s42_n-3*zHQRkYR2Q;+r5UJNyqTc-$PsKw>5ne$#dWmGrE?-QC;?AiPfc* z*BV+(g9XeS?lZldx#+eDmq>R=Zt(O#zWD-kt{-Bb60bIc>*%4BNzPM~eloXKUeTDo zq2Z`cTNT^kH~ej>YXZ_D<}SE)j}eg$&{Hu#FoNKz7#z*;w1Ao9Wib`!7p3dvrWWNF z>m?Qx;L)lLQIEn@;)Fu4uAZu+Xi|5=jOE|HY<0$_mMgQ<%gV}*mo4kHa(cI@d&Sbf z9sK8BN-k}h?fUya zm0s#@S|}=*FvT^0?!|fEgz{pxKHurAp>a)aKhh@9B+P!02JP@OR&%1)CK;w>z;52C}c6@bAFQd8*F(4=0KJskXL{D^5;addj%2 z?#Gh3tg|+7@&zBgws7;@?{o7%7Rg?WPESnNHS~PnW7~TDr|i4K9No)w?Vs7bObWXe z!t<18iD_NtD)u#jD>leU{tNJCWD;SfNB@h4#zhgj0!UFSZ zMEDMS*#k3oNn;5Krb0_1WWzzlFnR_;7|z2=yputOBeGL~H!B;+94;Wd4-CT#K$934 E05nP2PXGV_ From ae1b2cfb1766a35684a79492fe05dd1f7b05b4f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 04:51:18 +0000 Subject: [PATCH 3/6] Update dependencies, add tests, CI workflow, and improve README Co-authored-by: meros <450310+meros@users.noreply.github.com> --- .classpath | 7 - .github/workflows/ci.yml | 37 +++++ .gitignore | 16 ++ .project | 35 ---- .settings/org.eclipse.jdt.core.prefs | 3 - .settings/org.maven.ide.eclipse.prefs | 9 - README.md | 140 +++++++++++++++- pom.xml | 66 ++++++-- .../meros/pb4mina/ProtoBufCoderFactory.java | 2 +- .../org/meros/pb4mina/ProtoBufDecoder.java | 8 +- .../meros/pb4mina/BoundedInputStreamTest.java | 115 +++++++++++++ .../pb4mina/ProtoBufCoderFactoryTest.java | 78 +++++++++ .../pb4mina/ProtoBufCoderFilterTest.java | 30 ++++ .../meros/pb4mina/ProtoBufDecoderTest.java | 154 ++++++++++++++++++ .../meros/pb4mina/ProtoBufEncoderTest.java | 82 ++++++++++ .../meros/pb4mina/BoundedInputStream.class | Bin 1555 -> 0 bytes .../meros/pb4mina/ProtoBufCoderFactory.class | Bin 1643 -> 0 bytes .../meros/pb4mina/ProtoBufCoderFilter.class | Bin 595 -> 0 bytes .../meros/pb4mina/ProtoBufDecoder$State.class | Bin 1211 -> 0 bytes .../org/meros/pb4mina/ProtoBufDecoder.class | Bin 3544 -> 0 bytes .../org/meros/pb4mina/ProtoBufEncoder.class | Bin 1968 -> 0 bytes .../pb4mina/ProtoBufMessageFactory.class | Bin 332 -> 0 bytes .../compile/default-compile/createdFiles.lst | 7 - .../compile/default-compile/inputFiles.lst | 6 - 24 files changed, 703 insertions(+), 92 deletions(-) delete mode 100644 .classpath create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore delete mode 100644 .project delete mode 100644 .settings/org.eclipse.jdt.core.prefs delete mode 100644 .settings/org.maven.ide.eclipse.prefs create mode 100644 src/test/java/org/meros/pb4mina/BoundedInputStreamTest.java create mode 100644 src/test/java/org/meros/pb4mina/ProtoBufCoderFactoryTest.java create mode 100644 src/test/java/org/meros/pb4mina/ProtoBufCoderFilterTest.java create mode 100644 src/test/java/org/meros/pb4mina/ProtoBufDecoderTest.java create mode 100644 src/test/java/org/meros/pb4mina/ProtoBufEncoderTest.java delete mode 100644 target/classes/org/meros/pb4mina/BoundedInputStream.class delete mode 100644 target/classes/org/meros/pb4mina/ProtoBufCoderFactory.class delete mode 100644 target/classes/org/meros/pb4mina/ProtoBufCoderFilter.class delete mode 100644 target/classes/org/meros/pb4mina/ProtoBufDecoder$State.class delete mode 100644 target/classes/org/meros/pb4mina/ProtoBufDecoder.class delete mode 100644 target/classes/org/meros/pb4mina/ProtoBufEncoder.class delete mode 100644 target/classes/org/meros/pb4mina/ProtoBufMessageFactory.class delete mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst delete mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst diff --git a/.classpath b/.classpath deleted file mode 100644 index 5e40682..0000000 --- a/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f466202 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: Java CI + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + java-version: [ '11', '17', '21' ] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK ${{ matrix.java-version }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java-version }} + distribution: 'temurin' + cache: maven + + - name: Build with Maven + run: mvn -B clean verify --file pom.xml + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results-java-${{ matrix.java-version }} + path: target/surefire-reports/ + retention-days: 7 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9307fa4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Maven build output +target/ + +# IDE files +.classpath +.project +.settings/ +*.iml +.idea/ + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log diff --git a/.project b/.project deleted file mode 100644 index 8eaa65c..0000000 --- a/.project +++ /dev/null @@ -1,35 +0,0 @@ - - - MINA test server - - - - - - com.googlecode.protoclipse.protobufBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.iam.jdt.core.mavenIncrementalBuilder - - - - - org.maven.ide.eclipse.maven2Builder - - - - - - org.maven.ide.eclipse.maven2Nature - org.eclipse.iam.jdt.core.mavenNature - org.eclipse.jdt.core.javanature - com.googlecode.protoclipse.protobufNature - - diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 97ea2b0..0000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,3 +0,0 @@ -#Sun Oct 31 10:46:32 CET 2010 -eclipse.preferences.version=1 -org.eclipse.jdt.core.builder.resourceCopyExclusionFilter=*.launch,*.proto diff --git a/.settings/org.maven.ide.eclipse.prefs b/.settings/org.maven.ide.eclipse.prefs deleted file mode 100644 index 1cc2e21..0000000 --- a/.settings/org.maven.ide.eclipse.prefs +++ /dev/null @@ -1,9 +0,0 @@ -#Sun Oct 31 10:09:49 CET 2010 -activeProfiles= -eclipse.preferences.version=1 -fullBuildGoals=process-test-resources -includeModules=false -resolveWorkspaceProjects=true -resourceFilterGoals=process-resources resources\:testResources -skipCompilerPlugin=true -version=1 diff --git a/README.md b/README.md index 9b3890a..812879a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,136 @@ -pb4mina -======= +# pb4mina -Protocol buffer encoder/decoder for mina/java servers +[![Java CI](https://github.com/meros/java-pb4mina/actions/workflows/ci.yml/badge.svg)](https://github.com/meros/java-pb4mina/actions/workflows/ci.yml) +[![Java Version](https://img.shields.io/badge/java-11%2B-blue)](https://www.oracle.com/java/) +[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE) -Status ---- -No plans, this is a quick PoC project only +Protocol Buffer encoder/decoder for [Apache MINA](https://mina.apache.org/) Java network application framework. + +## Description + +pb4mina provides a seamless integration between Google Protocol Buffers and Apache MINA, enabling efficient binary message serialization for network applications. It handles message framing using a 4-byte fixed-length header, making it suitable for TCP-based communication. + +## Features + +- **Protocol Buffer Integration**: Encode and decode Protocol Buffer messages over MINA sessions +- **Length-Prefixed Framing**: Messages are framed with a 4-byte fixed32 length header for reliable message boundaries +- **Session-Safe Decoder**: Stateful decoder maintains per-session state for handling partial messages +- **Shared Encoder**: Thread-safe encoder shared across all sessions for efficiency + +## Requirements + +- Java 11 or higher +- Apache Maven 3.6+ + +## Installation + +Add the following dependency to your `pom.xml`: + +```xml + + org.meros + pb4mina + 1.0.0-SNAPSHOT + +``` + +## Usage + +### Basic Setup + +1. Create a message factory that returns builders for your Protocol Buffer messages: + +```java +import com.google.protobuf.Message.Builder; +import org.meros.pb4mina.ProtoBufMessageFactory; + +public class MyMessageFactory implements ProtoBufMessageFactory { + @Override + public Builder createProtoBufMessage() { + return MyProtoBufMessage.newBuilder(); + } +} +``` + +2. Add the codec filter to your MINA filter chain: + +```java +import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; +import org.meros.pb4mina.ProtoBufCoderFilter; + +DefaultIoFilterChainBuilder filterChain = acceptor.getFilterChain(); +filterChain.addLast("codec", new ProtoBufCoderFilter(new MyMessageFactory())); +``` + +3. Handle messages in your IoHandler: + +```java +@Override +public void messageReceived(IoSession session, Object message) { + MyProtoBufMessage protoMessage = (MyProtoBufMessage) message; + // Process the message +} + +@Override +public void messageSent(IoSession session, Object message) { + // Message was sent successfully +} +``` + +### Sending Messages + +Simply write Protocol Buffer messages to the session: + +```java +MyProtoBufMessage message = MyProtoBufMessage.newBuilder() + .setField("value") + .build(); +session.write(message); +``` + +## Wire Format + +Messages are transmitted using the following format: + +``` ++----------------+------------------+ +| Length (4 bytes) | Protobuf Data | ++----------------+------------------+ +``` + +- **Length**: 4-byte fixed32 (little-endian) containing the size of the protobuf data +- **Protobuf Data**: The serialized Protocol Buffer message + +## Building from Source + +```bash +# Clone the repository +git clone https://github.com/meros/java-pb4mina.git +cd java-pb4mina + +# Build and run tests +mvn clean verify + +# Install to local repository +mvn install +``` + +## Dependencies + +| Dependency | Version | Description | +|------------|---------|-------------| +| Apache MINA Core | 2.0.27 | Network application framework | +| Protocol Buffers | 3.25.5 | Serialization library | +| SLF4J | 2.0.16 | Logging facade | + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +This project is open source. See the repository for license details. + +## Status + +This project is maintained and actively used. Bug reports and feature requests are welcome via GitHub issues. diff --git a/pom.xml b/pom.xml index dfff2b2..aa5485a 100644 --- a/pom.xml +++ b/pom.xml @@ -2,36 +2,74 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.meros.app - minaserver - 1.0-SNAPSHOT + org.meros + pb4mina + 1.0.0-SNAPSHOT jar - MINA test server - http://maven.apache.org + pb4mina + Protocol Buffer encoder/decoder for Apache MINA + https://github.com/meros/java-pb4mina + + + UTF-8 + 11 + 11 + 2.0.27 + 3.25.5 + 2.0.16 + 5.11.3 + org.apache.mina mina-core - 2.0.1 + ${mina.version} com.google.protobuf protobuf-java - 2.3.0 + ${protobuf.version} org.slf4j slf4j-api - 1.6.1 + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + runtime + - org.slf4j - slf4j-simple - 1.6.1 - + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.mockito + mockito-core + 5.14.2 + test + - - + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + diff --git a/src/main/java/org/meros/pb4mina/ProtoBufCoderFactory.java b/src/main/java/org/meros/pb4mina/ProtoBufCoderFactory.java index 536d63c..73bd951 100644 --- a/src/main/java/org/meros/pb4mina/ProtoBufCoderFactory.java +++ b/src/main/java/org/meros/pb4mina/ProtoBufCoderFactory.java @@ -19,7 +19,7 @@ public class ProtoBufCoderFactory implements ProtocolCodecFactory { private ProtoBufMessageFactory protoBufMessageFactory; /** - * @param protoBufMessageFactory factory that created builders that the decoded uses to parse incoming messages + * @param protoBufMessageFactory factory that creates builders that the decoder uses to parse incoming messages */ public ProtoBufCoderFactory(ProtoBufMessageFactory protoBufMessageFactory) { this.protoBufMessageFactory = protoBufMessageFactory; diff --git a/src/main/java/org/meros/pb4mina/ProtoBufDecoder.java b/src/main/java/org/meros/pb4mina/ProtoBufDecoder.java index cb9a714..bfd60e0 100644 --- a/src/main/java/org/meros/pb4mina/ProtoBufDecoder.java +++ b/src/main/java/org/meros/pb4mina/ProtoBufDecoder.java @@ -65,13 +65,13 @@ protected boolean doDecode( state = State.ReadingLength; - //There might be more message to be parsed + // There might be more messages to be parsed return true; } } } catch(IOException e) { - logger.error("IOException while trying to decode a readmessage", e); - ioSession.close(true); + logger.error("IOException while trying to decode a message", e); + ioSession.closeNow(); } //Need more data @@ -106,7 +106,7 @@ private static int readLength(IoBuffer inputStream) throws IOException { return -1; } - //Create lenght delimited input stream + // Create length-delimited input stream InputStream delimitedInputStream = new BoundedInputStream(inputStream.asInputStream(), packageLengthTokenSize); //Read length of incoming protobuf diff --git a/src/test/java/org/meros/pb4mina/BoundedInputStreamTest.java b/src/test/java/org/meros/pb4mina/BoundedInputStreamTest.java new file mode 100644 index 0000000..6526e9b --- /dev/null +++ b/src/test/java/org/meros/pb4mina/BoundedInputStreamTest.java @@ -0,0 +1,115 @@ +package org.meros.pb4mina; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link BoundedInputStream} + */ +class BoundedInputStreamTest { + + @Test + void testReadSingleByte() throws IOException { + byte[] data = {1, 2, 3, 4, 5}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + assertEquals(1, bounded.read()); + assertEquals(2, bounded.read()); + assertEquals(3, bounded.read()); + assertEquals(-1, bounded.read()); // Should return -1 after limit + } + + @Test + void testReadByteArray() throws IOException { + byte[] data = {1, 2, 3, 4, 5}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + byte[] buffer = new byte[10]; + int bytesRead = bounded.read(buffer); + + assertEquals(3, bytesRead); + assertEquals(1, buffer[0]); + assertEquals(2, buffer[1]); + assertEquals(3, buffer[2]); + } + + @Test + void testReadByteArrayWithOffset() throws IOException { + byte[] data = {1, 2, 3, 4, 5}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + byte[] buffer = new byte[10]; + int bytesRead = bounded.read(buffer, 2, 5); + + assertEquals(3, bytesRead); + assertEquals(0, buffer[0]); + assertEquals(0, buffer[1]); + assertEquals(1, buffer[2]); + assertEquals(2, buffer[3]); + assertEquals(3, buffer[4]); + } + + @Test + void testAvailable() throws IOException { + byte[] data = {1, 2, 3, 4, 5}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + assertEquals(3, bounded.available()); + bounded.read(); + assertEquals(2, bounded.available()); + bounded.read(); + assertEquals(1, bounded.available()); + bounded.read(); + assertEquals(0, bounded.available()); + } + + @Test + void testMarkNotSupported() { + byte[] data = {1, 2, 3}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + assertFalse(bounded.markSupported()); + } + + @Test + void testLengthLargerThanAvailableThrows() { + byte[] data = {1, 2, 3}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + + assertThrows(RuntimeException.class, () -> new BoundedInputStream(baseStream, 10)); + } + + @Test + void testZeroLengthBound() throws IOException { + byte[] data = {1, 2, 3}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 0); + + assertEquals(0, bounded.available()); + assertEquals(-1, bounded.read()); + } + + @Test + void testReadReturnsMinusOneAfterExhausted() throws IOException { + byte[] data = {1, 2}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 2); + + byte[] buffer = new byte[10]; + bounded.read(buffer); + + // Further reads should return -1 + assertEquals(-1, bounded.read()); + assertEquals(-1, bounded.read(buffer)); + assertEquals(-1, bounded.read(buffer, 0, 5)); + } +} diff --git a/src/test/java/org/meros/pb4mina/ProtoBufCoderFactoryTest.java b/src/test/java/org/meros/pb4mina/ProtoBufCoderFactoryTest.java new file mode 100644 index 0000000..fc47ad7 --- /dev/null +++ b/src/test/java/org/meros/pb4mina/ProtoBufCoderFactoryTest.java @@ -0,0 +1,78 @@ +package org.meros.pb4mina; + +import com.google.protobuf.Any; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolEncoder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link ProtoBufCoderFactory} + */ +class ProtoBufCoderFactoryTest { + + private ProtoBufCoderFactory factory; + private ProtoBufMessageFactory messageFactory; + + @BeforeEach + void setUp() { + messageFactory = () -> Any.newBuilder(); + factory = new ProtoBufCoderFactory(messageFactory); + } + + @Test + void testGetEncoderReturnsSameInstance() throws Exception { + IoSession session1 = mock(IoSession.class); + IoSession session2 = mock(IoSession.class); + + ProtocolEncoder encoder1 = factory.getEncoder(session1); + ProtocolEncoder encoder2 = factory.getEncoder(session2); + + assertNotNull(encoder1); + assertNotNull(encoder2); + assertSame(encoder1, encoder2, "Encoder should be shared across sessions"); + assertTrue(encoder1 instanceof ProtoBufEncoder); + } + + @Test + void testGetDecoderCreatesSeparateInstancePerSession() throws Exception { + IoSession session1 = mock(IoSession.class); + IoSession session2 = mock(IoSession.class); + + // First session should create a new decoder + when(session1.getAttribute(any())).thenReturn(null); + ProtocolDecoder decoder1 = factory.getDecoder(session1); + + // Second session should also create a new decoder + when(session2.getAttribute(any())).thenReturn(null); + ProtocolDecoder decoder2 = factory.getDecoder(session2); + + assertNotNull(decoder1); + assertNotNull(decoder2); + assertTrue(decoder1 instanceof ProtoBufDecoder); + assertTrue(decoder2 instanceof ProtoBufDecoder); + + // Verify setAttribute was called to store the decoder + verify(session1).setAttribute(any(), eq(decoder1)); + verify(session2).setAttribute(any(), eq(decoder2)); + } + + @Test + void testGetDecoderReusesExistingDecoder() throws Exception { + IoSession session = mock(IoSession.class); + ProtoBufDecoder existingDecoder = new ProtoBufDecoder(messageFactory); + + // Session already has a decoder + when(session.getAttribute(any())).thenReturn(existingDecoder); + + ProtocolDecoder decoder = factory.getDecoder(session); + + assertSame(existingDecoder, decoder); + // setAttribute should not be called since decoder already exists + verify(session, never()).setAttribute(any(), any()); + } +} diff --git a/src/test/java/org/meros/pb4mina/ProtoBufCoderFilterTest.java b/src/test/java/org/meros/pb4mina/ProtoBufCoderFilterTest.java new file mode 100644 index 0000000..03fea8e --- /dev/null +++ b/src/test/java/org/meros/pb4mina/ProtoBufCoderFilterTest.java @@ -0,0 +1,30 @@ +package org.meros.pb4mina; + +import com.google.protobuf.Any; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link ProtoBufCoderFilter} + */ +class ProtoBufCoderFilterTest { + + @Test + void testConstructor() { + ProtoBufMessageFactory messageFactory = () -> Any.newBuilder(); + + // Should create without throwing + ProtoBufCoderFilter filter = new ProtoBufCoderFilter(messageFactory); + + assertNotNull(filter); + } + + @Test + void testConstructorWithNullFactory() { + // Factory can be null in constructor but will fail later when used + // This is consistent with how the code works - it doesn't validate null + ProtoBufCoderFilter filter = new ProtoBufCoderFilter(null); + assertNotNull(filter); + } +} diff --git a/src/test/java/org/meros/pb4mina/ProtoBufDecoderTest.java b/src/test/java/org/meros/pb4mina/ProtoBufDecoderTest.java new file mode 100644 index 0000000..4c47fd9 --- /dev/null +++ b/src/test/java/org/meros/pb4mina/ProtoBufDecoderTest.java @@ -0,0 +1,154 @@ +package org.meros.pb4mina; + +import com.google.protobuf.Any; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.Message; +import com.google.protobuf.StringValue; +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.io.ByteArrayOutputStream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link ProtoBufDecoder} + */ +class ProtoBufDecoderTest { + + private ProtoBufDecoder decoder; + private IoSession mockSession; + private ProtocolDecoderOutput mockOutput; + private ProtoBufMessageFactory messageFactory; + + @BeforeEach + void setUp() { + // Create a factory that creates Any message builders + messageFactory = () -> Any.newBuilder(); + decoder = new ProtoBufDecoder(messageFactory); + mockSession = mock(IoSession.class); + mockOutput = mock(ProtocolDecoderOutput.class); + } + + @Test + void testDecodeCompleteMessage() throws Exception { + // Create a test message + Any testMessage = Any.pack(StringValue.of("test content")); + + // Encode the message with length prefix + IoBuffer buffer = createEncodedBuffer(testMessage); + + // Decode the message + boolean result = decoder.doDecode(mockSession, buffer, mockOutput); + + // Verify message was decoded + assertTrue(result); + + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + verify(mockOutput).write(messageCaptor.capture()); + + Message decodedMessage = messageCaptor.getValue(); + assertNotNull(decodedMessage); + assertTrue(decodedMessage instanceof Any); + assertEquals(testMessage, decodedMessage); + } + + @Test + void testDecodeIncompleteLength() throws Exception { + // Only 2 bytes when 4 are needed for length + IoBuffer buffer = IoBuffer.allocate(2); + buffer.put((byte) 0); + buffer.put((byte) 0); + buffer.flip(); + + boolean result = decoder.doDecode(mockSession, buffer, mockOutput); + + assertFalse(result); + verify(mockOutput, never()).write(any()); + } + + @Test + void testDecodeIncompleteMessage() throws Exception { + Any testMessage = Any.pack(StringValue.of("test content")); + int messageSize = testMessage.getSerializedSize(); + + // Create buffer with full length header but only partial message + IoBuffer buffer = IoBuffer.allocate(4 + messageSize / 2); + CodedOutputStream cos = CodedOutputStream.newInstance(buffer.asOutputStream()); + cos.writeFixed32NoTag(messageSize); + cos.flush(); + + // Write only half the message bytes + byte[] messageBytes = testMessage.toByteArray(); + buffer.put(messageBytes, 0, messageSize / 2); + buffer.flip(); + + boolean result = decoder.doDecode(mockSession, buffer, mockOutput); + + assertFalse(result); + verify(mockOutput, never()).write(any()); + } + + @Test + void testDecodeMultipleMessages() throws Exception { + Any testMessage1 = Any.pack(StringValue.of("message one")); + Any testMessage2 = Any.pack(StringValue.of("message two")); + + // Create buffer with two encoded messages + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CodedOutputStream cos = CodedOutputStream.newInstance(baos); + + // First message + cos.writeFixed32NoTag(testMessage1.getSerializedSize()); + testMessage1.writeTo(cos); + + // Second message + cos.writeFixed32NoTag(testMessage2.getSerializedSize()); + testMessage2.writeTo(cos); + + cos.flush(); + + IoBuffer buffer = IoBuffer.wrap(baos.toByteArray()); + + // Decode first message + boolean result1 = decoder.doDecode(mockSession, buffer, mockOutput); + assertTrue(result1); + + // Decode second message + boolean result2 = decoder.doDecode(mockSession, buffer, mockOutput); + assertTrue(result2); + + // No more messages + boolean result3 = decoder.doDecode(mockSession, buffer, mockOutput); + assertFalse(result3); + + verify(mockOutput, times(2)).write(any()); + } + + @Test + void testDecodeEmptyBuffer() throws Exception { + IoBuffer buffer = IoBuffer.allocate(0); + buffer.flip(); + + boolean result = decoder.doDecode(mockSession, buffer, mockOutput); + + assertFalse(result); + verify(mockOutput, never()).write(any()); + } + + private IoBuffer createEncodedBuffer(Message message) throws Exception { + int messageSize = message.getSerializedSize(); + IoBuffer buffer = IoBuffer.allocate(4 + messageSize); + CodedOutputStream cos = CodedOutputStream.newInstance(buffer.asOutputStream()); + cos.writeFixed32NoTag(messageSize); + message.writeTo(cos); + cos.flush(); + buffer.flip(); + return buffer; + } +} diff --git a/src/test/java/org/meros/pb4mina/ProtoBufEncoderTest.java b/src/test/java/org/meros/pb4mina/ProtoBufEncoderTest.java new file mode 100644 index 0000000..a3f43f9 --- /dev/null +++ b/src/test/java/org/meros/pb4mina/ProtoBufEncoderTest.java @@ -0,0 +1,82 @@ +package org.meros.pb4mina; + +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.Message; +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolEncoderOutput; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.io.InvalidObjectException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link ProtoBufEncoder} + */ +class ProtoBufEncoderTest { + + private ProtoBufEncoder encoder; + private IoSession mockSession; + private ProtocolEncoderOutput mockOutput; + + @BeforeEach + void setUp() { + encoder = new ProtoBufEncoder(); + mockSession = mock(IoSession.class); + mockOutput = mock(ProtocolEncoderOutput.class); + } + + @Test + void testEncodeValidMessage() throws Exception { + // Create a simple test message using ByteString which is a Message + Message testMessage = createTestMessage(); + + ArgumentCaptor bufferCaptor = ArgumentCaptor.forClass(IoBuffer.class); + + encoder.encode(mockSession, testMessage, mockOutput); + + verify(mockOutput).write(bufferCaptor.capture()); + + IoBuffer capturedBuffer = bufferCaptor.getValue(); + assertNotNull(capturedBuffer); + assertTrue(capturedBuffer.remaining() > 4); // At least 4 bytes for length header + + // Verify the length header + CodedInputStream cis = CodedInputStream.newInstance(capturedBuffer.asInputStream()); + int length = cis.readFixed32(); + assertEquals(testMessage.getSerializedSize(), length); + } + + @Test + void testEncodeNonMessageThrows() { + String invalidObject = "not a protobuf message"; + + assertThrows(InvalidObjectException.class, () -> + encoder.encode(mockSession, invalidObject, mockOutput) + ); + } + + @Test + void testEncodeNullObjectThrows() { + assertThrows(Exception.class, () -> + encoder.encode(mockSession, null, mockOutput) + ); + } + + /** + * Creates a simple test protobuf message for testing. + * Uses a dynamically created message with some fields. + */ + private Message createTestMessage() { + // Create a simple message using the Any type or a custom builder + return com.google.protobuf.Any.pack( + com.google.protobuf.StringValue.of("test message content") + ); + } +} diff --git a/target/classes/org/meros/pb4mina/BoundedInputStream.class b/target/classes/org/meros/pb4mina/BoundedInputStream.class deleted file mode 100644 index 7fcff268b73a08f0bf1c56f3d31099e3ea99f6b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1555 zcma)5U2hXd6g{)+#Kc)AjYA+#Oj%k;{Sj~rp|qIKAdoBts@72?ny0lriMQChX1y-( z-e1w@N`2v_Qc0>-3h(_gV_7bRs*4~-DbLZT1&zbrApFfWPY~XGN2Fx^47N#(* zu=s=Xqf>SLYTfG(!$ufrr>8J|!}Z+omV#LYZdV7k zAK3co(CJE2*wJ?LMW_e%xSs8L{EazgFpH0*hXsYD;?{%#TX4PoT4`qni#VCal7&+^ zt#IBC_NzS|_=9S{xzTeyr@HA6y|!+@)%k3q29Za=Z_85?g@4@P|Q5KXBz}o_^QbOCl-G(K>?{!QjJV5zXi$ z$7!$tF7kbeUq>*J;&`Qe2vw*YApHxne_{4AOJ z{Q5USq-bLi1%gxwvc|guq;IqL| zWsNn|Li83bC0(+mdzzY)-`H4it z-Ac0Fgh=5`GX8!Zm&xM_d0ZX4Mz}GLO3XuIHzTiPc8fFf|G2$zYnvQJ-h7h7nA-s= Z#}&CwCN(m-nNU9;GkLELw~21x&VOEU8}tAG diff --git a/target/classes/org/meros/pb4mina/ProtoBufCoderFactory.class b/target/classes/org/meros/pb4mina/ProtoBufCoderFactory.class deleted file mode 100644 index a29defe997d363e2ae4949a7cba02b59f9a15b43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1643 zcmb_dTTc^F5dO}#vSnFrMasns7XjN+SW#58NP~4+z0e_Ez zh9HUY&BWhij5F<0KnnK3Ha%x&&dhw@%$c43{QdPCfLSbPP!LiP)^Q0f0$tnYThl0- zrGl}V-@o?MXl{NPp4yT{~a#CHckAhEAMSspuCN zIX{bKX>lnu^kI;zd_`a?-h9@Hbx!A+j_VjAiry)Ta}6k-W!RAPh(L--pDfI#UG$}E zFkh>7k(N^o_;dAd=orJeK)dIgzHMbn)yxE@tLgkRQedA}ar4581$G)HxHXdkGfffY zjx-ZOpku=e)W0}Qu)3sR+lwp6%_5?1v7mjz-&}jjUnk6WW zz^j_$$qF=XvUH-kQU#F-M-3YFh+GpGm*4lq*rLylKT+J$uCepRc%wWc2E?cpF9T8N^z!0iq)(!4TB`| zt40+R?G|R}jAMQvKlV&c?xOtw-3oH49~eAD-$wE?)O`%(_#}0Jt0BBheaG-evJQbU zW5f*@qPU}kKSrqI^o>`shig&m*h$>R9YWSHOw+oH(C?CO1mYLQRg&(J#M$1b-vc}h z*v*l|8HaZ<%D74uw}rGIl;a%3b)0a3a~v;%G}wc)<*fPBdx(jReZ==LdI*J{#2(tY Vm^4dh<^K|(oAxMa=IL3%qd#e%qOkw~ diff --git a/target/classes/org/meros/pb4mina/ProtoBufCoderFilter.class b/target/classes/org/meros/pb4mina/ProtoBufCoderFilter.class deleted file mode 100644 index 7dbc2fd9ea4cd4fb7dc985017b72aedf1dd3c07b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 595 zcma)3%T59@6g_tw8680s72UerW=fY>H&&TY@5x8FY>UjXXZ_TUiqjqL>kWla=}I`x4bis0Uw*j$af*XCK-rjW6* zZ$1ca5sN4g3Kx2)<4eNHVQV(~Rz;EMshr<9YWrBivIjp=Ss|PyR522=uY%OJtHW5? zK(edMGDs698Duj(A)IG^|0HF>+1Nvgus1tt_RieHO%S2n(nEDO9(0s_6rGSOm6nkr zYzwOsoEP1=uOq_2oIdESAq%UEMv%`Rgd6-G?M1~ya4 IEk+KizgwB5y8r+H diff --git a/target/classes/org/meros/pb4mina/ProtoBufDecoder$State.class b/target/classes/org/meros/pb4mina/ProtoBufDecoder$State.class deleted file mode 100644 index c772ed4f88801f42e5ee90a2fb051cd8ed2e8780..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1211 zcma)5YflqF6g|^z*>>IXsFgFD#qhkiE)+b3-vZPI9B+FpTvd^9O zyr~M@GGQXipwHL0YHxRJhSIyfxeW|qSVz{x2yzT5#|wPHkS|)b6Mo9e9`~E&y~c@f zqN;&B?&ugbF@|w!J?}%Zo*pt2O@pik~vtdUe=}_&hAPE$I!Ngrm zG7PM{z8h^YjP_E+nY&g!jg&#M6w=7Zb;iUj=A;Iyhc?4f(du6Xs`ooDxlQ9^7^=Cx zc-3h&MEHg`Jlc8ok4E~RAw#YfINYoA(3N~KY0~E{;?CVvhl~>)yL6F^?KXWLbwa{v zMXOF;Sa-ZwonBd`hU!^^{|Me~T zNZEgLl}3}NccX1i$VULsN;U?D3(~W6lA?GXbA)?9(JZaw5>R8<|}38rnGZR ypFvZ>y`&2C3*0}0A^W$qniN diff --git a/target/classes/org/meros/pb4mina/ProtoBufDecoder.class b/target/classes/org/meros/pb4mina/ProtoBufDecoder.class deleted file mode 100644 index 252f0f6b8d3fa32db7bb225323cede4001d3ecd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3544 zcmb7HTUQfT6#h;~7=ckKDhi?oMNI-4tEfo46_gqUwLgwuuW(EkCV#->~oH>`h_qQ+KKKbjP-~R;AjUVd~LXC#7 zjtFW64%kjEX-pZJ37IUKmXRDc3$ApM89OU8$^LS&TrgbohCJ`suAQ+9C!|_&1ZoeP zmgyc9sENf#>#zcK8tQd4Ko{7lgchY^my%Os-JV$Qd|!DyINves8m<&rc|jUk)5;A> zE9XuK#0RT9nySMptk%$|;{~O%{ki7^A|<6%9bBVhE!I&zf#;2!oKY{*^;nM$8d`L; zVxz!%CAL%;@19H!+PR!`28@hrJGTVta?v+5JeIBNdhR6PN2*RcaT1?nC8 zdj@Sh<_W2ce#UlWa;!W)&J;*1y&R zBh!wl5_4T;NY9N;%8Z+ek7_s|u*QqEX(!X?PTtJODc7{E73dMzQ!x>po-hkC>N>ZW z-BH($vYc2rQ6s8+RP>|SfL^3D9M*9Jtfa<;$wqUe=Kc8#jg0tTj@IQ(0JJ`Gd=p% zz5#E{+#0~k8cyj*lFAn36KltjMT7KVgA0o8h6P#zKQ7pkR&IM0jPFGqmvC7irjX3H zW`@G%mlsndL(5#BAz3lA`M;C)ER(JsePy#iDrMSU#cLW~*KrMR2yCn33|&auNC%Ex z6ga#zsOq&FI>wM;3XCa}1hy=1IXx;dt|6ym0wyE3l*Rqb_R7eS9V|JWw#dO&hG}-> z{}{bcm}fy=M?oc>$2tS%O_|-dS21QKrH|NoX$_mVRjcW@J>~AGQ7FqAcEhm1E}x^S z2{cPfAgnAASUqT3@@%;{CY=#gb{>ysjKZklnCd&247(F%i4k3f;evjc5?GP7{SSdV z|G}J#sy0;}fiK$id?rlAuLx)XPYG;VE|Y1zpbOT`Ro@&ja;?ssBblm!(H~~ef5>5r z04&+37-OGhof$UoUUaYuur=C-V*3v81|1#X~{EcG=0u0k$ZCwKK#P z2m5qU#Adl-dVq3jKt7ni5l{cpGAOv9pWAs9Jr3&faiK_!-vjY6(9@GK9eF z@V#Hq+8gGcwx)w>=}-+i`8pQD#hQCm4V=XV?5A=CwrAo#yg(& zJl@57o_q3mA0N<;4=JM#;r3m98umBS6yEf>K?UX+#b|u@Bp%`9(M@eXBO>nqfisu+ zJ=FFXuk?mHpJGIyH_{SOXs-}vOXMlufTCNckl^MDTgV5D(*e_ln~TkPY_o%QVAB~&VBfdBOg{R z#NX;h`#JZ934cFz^OhW-yU97#CE67_jo3o>X0U0*HrV+D*`cmSn7qeLK8$8BWlVyZ zq$rC&C9b25kyLSr`oaR^)^lBf5L}Po3w%kX%F{Iv^%eGn@D(RR_!{5v-Nexf7S*@M NH_$)d;fLC~e*u=tzRCaq diff --git a/target/classes/org/meros/pb4mina/ProtoBufEncoder.class b/target/classes/org/meros/pb4mina/ProtoBufEncoder.class deleted file mode 100644 index 0513e47083ec6d14fce9d796bdffb23b8cfdcf8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1968 zcma)7YgZFj6y29R29rSxikMnxtb!RtrUKPAP+y2`+5onpp?vFPauY@;bJNKLUHvKj z7yW|LuGQAB{Y_n6?s+U_B`nK_+{fH|&e><*vrqo{@Ap3etY9yP5u`Mvb!3ngSXO@9 zw7QnPFU^kQS!T;|1L>Q#YD(LD;;TT}%H8n7z5k$Tb$KX|U2{AqxGRt<8FdY#0+(&o zG22SDU1@eht46om6;Po*7fys%|AaFjO+U7)(B z`bAI5W-(AjdO2{KvS@{?2t|=TThcFfVz=R?U|$X$%NT49XK*&m@|?hAsTzfMEw63X z0^jl4t42K!4dXf{a9&`%ErXi$9YXs>HftQBI)X1B2t(G)TBu-{6U z$zckYG!%4vh|2=k23fV0FU=UfSy8lO9s;A5>nhs{7|SSCjOuBuR>M%!I%e>(z*(!e z-4D9`fH_I4BXFr?#F>)l<0rJbf=@JD)o~580<$MQ*E-+0dE81M@5$#CuNPRJO_28z z8V8*l)l<2PKwQ^Rfc?6wsneJ4#@_bjlb; zJxtZq@o8AznU>q{?T3^t=~%`JW3*hSD^Ma)r=mNL+qk3Qa~)rxEU+|a^gg}BToOf; z#y}ay0$>>mT%`VlQ1S`{ZLf*mrsYSEG#ITgR)^cB~(PpGzk*)T8{USk`eZUacMz zSCZ9`<^R(ZF`*^S`9MQ1A`J}(XO$!kAg-VmM-ug17L5#D%LoE$=wL<^2f?Xmg7BsF*ys)nfaFx zzeQK>a}19`7Y{h<@dZ4@I(Gt(ut6uG@xMr6l=JSwYiPe>><^sTo5;V!*k22OLo25X z=_7m~a62=Z(SCY`i{)$~dx(!-p;*=m+GOSsbA@z4JHq@33fdtS!{b}KHw)P#d^Um* zP73!qE)fJU&Bi~2appF~c38wL49u}T7${?&;BMe+EMg0{P^U!*{rebR0-Zqd7+*!{ z?DB3UVrUoN;9JI-Vx%fIk*4Q6^s+{l4Eo!~6VkEFh~Lqlh8q2*AjYx7MViij;BS}f H5&ZZDhz}Xq diff --git a/target/classes/org/meros/pb4mina/ProtoBufMessageFactory.class b/target/classes/org/meros/pb4mina/ProtoBufMessageFactory.class deleted file mode 100644 index 5f905644f9369faec03b8e50bf07fdfbd06e2e2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 332 zcma)&zfJ-{5XQejxs?O|G**_Hh>hFO(P2VDqS3@B*jtCqk-ZzTduQUSQSbmhlwl!P zQ4pJ%DZcrA-^}mdW(#0~YlR+REWzoDgG9Y9CRJG*{Sl;*S-X7W7>(n(v5kZug>%Bq z%6uE`P3`o1v0~d0Zf#)G@X-?Cas2dZrP5BsdDiPav}l*Qqdv^q(tmN767o-JgXMYY znQ(Um>fE3A#)Q$Lt~tDT6C+23A>sZX9txKUd_qW2U7U Date: Fri, 28 Nov 2025 04:55:33 +0000 Subject: [PATCH 4/6] Add explicit permissions to GitHub Actions workflow for security Co-authored-by: meros <450310+meros@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f466202..d0dee7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ main, master ] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest From e2a07ce9f89e253098c6bb919fef103fcae20627 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 04:58:15 +0000 Subject: [PATCH 5/6] Update README status to reflect project is not actively maintained Co-authored-by: meros <450310+meros@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 812879a..fd38965 100644 --- a/README.md +++ b/README.md @@ -133,4 +133,4 @@ This project is open source. See the repository for license details. ## Status -This project is maintained and actively used. Bug reports and feature requests are welcome via GitHub issues. +This is a proof-of-concept project. It is not actively maintained but contributions are welcome. From f0aedf1d37daf13fe1a0c8c3a75794b437456682 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 05:10:44 +0000 Subject: [PATCH 6/6] Add code formatting with Spotless and Google Java Format Co-authored-by: meros <450310+meros@users.noreply.github.com> --- .github/workflows/ci.yml | 17 ++ README.md | 12 + pom.xml | 22 ++ .../org/meros/pb4mina/BoundedInputStream.java | 103 ++++--- .../meros/pb4mina/ProtoBufCoderFactory.java | 65 ++--- .../meros/pb4mina/ProtoBufCoderFilter.java | 10 +- .../org/meros/pb4mina/ProtoBufDecoder.java | 194 ++++++------- .../org/meros/pb4mina/ProtoBufEncoder.java | 66 ++--- .../meros/pb4mina/ProtoBufMessageFactory.java | 7 +- .../meros/pb4mina/BoundedInputStreamTest.java | 209 +++++++------- .../pb4mina/ProtoBufCoderFactoryTest.java | 130 +++++---- .../pb4mina/ProtoBufCoderFilterTest.java | 40 ++- .../meros/pb4mina/ProtoBufDecoderTest.java | 271 +++++++++--------- .../meros/pb4mina/ProtoBufEncoderTest.java | 114 ++++---- 14 files changed, 635 insertions(+), 625 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0dee7e..fa738b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,25 @@ permissions: contents: read jobs: + format: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + cache: maven + + - name: Check code formatting + run: mvn -B spotless:check + build: runs-on: ubuntu-latest + needs: format strategy: matrix: diff --git a/README.md b/README.md index fd38965..070d2fb 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,18 @@ mvn clean verify mvn install ``` +## Code Formatting + +This project uses [Spotless](https://github.com/diffplug/spotless) with Google Java Format for code formatting. + +```bash +# Check formatting +mvn spotless:check + +# Apply formatting +mvn spotless:apply +``` + ## Dependencies | Dependency | Version | Description | diff --git a/pom.xml b/pom.xml index aa5485a..4473095 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,28 @@ maven-surefire-plugin 3.5.2 + + com.diffplug.spotless + spotless-maven-plugin + 2.43.0 + + + + 1.19.2 + + + + + + + + + check + + validate + + + diff --git a/src/main/java/org/meros/pb4mina/BoundedInputStream.java b/src/main/java/org/meros/pb4mina/BoundedInputStream.java index d0a325b..ce4bec1 100644 --- a/src/main/java/org/meros/pb4mina/BoundedInputStream.java +++ b/src/main/java/org/meros/pb4mina/BoundedInputStream.java @@ -4,60 +4,57 @@ import java.io.InputStream; /** - * - * Input stream that wraps another input stream and sets a limit of number of bytes that is readable from the original stream - * - * @author meros + * Input stream that wraps another input stream and sets a limit of number of bytes that is readable + * from the original stream * + * @author meros */ public class BoundedInputStream extends InputStream { - - private InputStream inputStream; - private int diff; - - public BoundedInputStream(InputStream inputStream, int length) { - try { - if (length > inputStream.available()) - throw new RuntimeException("You need to specify a length smaller or equal to the bytes available in inputStream"); - - this.inputStream = inputStream; - this.diff = inputStream.available()-length; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public int available() throws IOException { - return inputStream.available()-diff; - } - - @Override - public boolean markSupported() { - return false; - } - - @Override - public int read(byte[] b) throws IOException { - if (inputStream.available()-diff == 0) - return -1; - - return inputStream.read(b, 0, Math.min(b.length, inputStream.available()-diff)); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (inputStream.available()-diff == 0) - return -1; - - return inputStream.read(b, off, Math.min(len, inputStream.available()-diff)); - } - - @Override - public int read() throws IOException { - if (inputStream.available()-diff == 0) - return -1; - - return inputStream.read(); - } + + private InputStream inputStream; + private int diff; + + public BoundedInputStream(InputStream inputStream, int length) { + try { + if (length > inputStream.available()) + throw new RuntimeException( + "You need to specify a length smaller or equal to the bytes available in inputStream"); + + this.inputStream = inputStream; + this.diff = inputStream.available() - length; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public int available() throws IOException { + return inputStream.available() - diff; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public int read(byte[] b) throws IOException { + if (inputStream.available() - diff == 0) return -1; + + return inputStream.read(b, 0, Math.min(b.length, inputStream.available() - diff)); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (inputStream.available() - diff == 0) return -1; + + return inputStream.read(b, off, Math.min(len, inputStream.available() - diff)); + } + + @Override + public int read() throws IOException { + if (inputStream.available() - diff == 0) return -1; + + return inputStream.read(); + } } diff --git a/src/main/java/org/meros/pb4mina/ProtoBufCoderFactory.java b/src/main/java/org/meros/pb4mina/ProtoBufCoderFactory.java index 73bd951..f37f922 100644 --- a/src/main/java/org/meros/pb4mina/ProtoBufCoderFactory.java +++ b/src/main/java/org/meros/pb4mina/ProtoBufCoderFactory.java @@ -6,43 +6,40 @@ import org.apache.mina.filter.codec.ProtocolEncoder; /** - * * Factory for the protobuf coder classes - * - * @author meros * + * @author meros */ public class ProtoBufCoderFactory implements ProtocolCodecFactory { - private static ProtocolEncoder staticEncoder = new ProtoBufEncoder(); - private final static Object DECODER = new Object(); - private ProtoBufMessageFactory protoBufMessageFactory; - - /** - * @param protoBufMessageFactory factory that creates builders that the decoder uses to parse incoming messages - */ - public ProtoBufCoderFactory(ProtoBufMessageFactory protoBufMessageFactory) { - this.protoBufMessageFactory = protoBufMessageFactory; - } - - @Override - public ProtocolDecoder getDecoder(IoSession session) throws Exception { - //The decoder is stateful, hence we need one/session - Object decoder = session.getAttribute(DECODER); - - //No decoder created for this session - if (decoder == null) - { - decoder = new ProtoBufDecoder(protoBufMessageFactory); - session.setAttribute(DECODER , decoder); - } - - return (ProtocolDecoder)decoder; - } - - @Override - public ProtocolEncoder getEncoder(IoSession session) throws Exception { - return staticEncoder ; - } - + private static ProtocolEncoder staticEncoder = new ProtoBufEncoder(); + private static final Object DECODER = new Object(); + private ProtoBufMessageFactory protoBufMessageFactory; + + /** + * @param protoBufMessageFactory factory that creates builders that the decoder uses to parse + * incoming messages + */ + public ProtoBufCoderFactory(ProtoBufMessageFactory protoBufMessageFactory) { + this.protoBufMessageFactory = protoBufMessageFactory; + } + + @Override + public ProtocolDecoder getDecoder(IoSession session) throws Exception { + // The decoder is stateful, hence we need one/session + Object decoder = session.getAttribute(DECODER); + + // No decoder created for this session + if (decoder == null) { + decoder = new ProtoBufDecoder(protoBufMessageFactory); + session.setAttribute(DECODER, decoder); + } + + return (ProtocolDecoder) decoder; + } + + @Override + public ProtocolEncoder getEncoder(IoSession session) throws Exception { + return staticEncoder; + } } diff --git a/src/main/java/org/meros/pb4mina/ProtoBufCoderFilter.java b/src/main/java/org/meros/pb4mina/ProtoBufCoderFilter.java index 86ca777..e6630ec 100644 --- a/src/main/java/org/meros/pb4mina/ProtoBufCoderFilter.java +++ b/src/main/java/org/meros/pb4mina/ProtoBufCoderFilter.java @@ -3,14 +3,12 @@ import org.apache.mina.filter.codec.ProtocolCodecFilter; /** - * * Helper class to make it easier to create a protobuf filter - * - * @author meros * + * @author meros */ public class ProtoBufCoderFilter extends ProtocolCodecFilter { - public ProtoBufCoderFilter(ProtoBufMessageFactory protoBufMessageFactory) { - super(new ProtoBufCoderFactory(protoBufMessageFactory)); - } + public ProtoBufCoderFilter(ProtoBufMessageFactory protoBufMessageFactory) { + super(new ProtoBufCoderFactory(protoBufMessageFactory)); + } } diff --git a/src/main/java/org/meros/pb4mina/ProtoBufDecoder.java b/src/main/java/org/meros/pb4mina/ProtoBufDecoder.java index bfd60e0..a0a8a3f 100644 --- a/src/main/java/org/meros/pb4mina/ProtoBufDecoder.java +++ b/src/main/java/org/meros/pb4mina/ProtoBufDecoder.java @@ -1,8 +1,10 @@ package org.meros.pb4mina; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.Message; +import com.google.protobuf.Message.Builder; import java.io.IOException; import java.io.InputStream; - import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.CumulativeProtocolDecoder; @@ -10,108 +12,100 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.protobuf.CodedInputStream; -import com.google.protobuf.Message; -import com.google.protobuf.Message.Builder; - /** - * - * Decoder for protobuf messages. The messages are delimited by a 4 byte uint32 size header - * - * @author meros + * Decoder for protobuf messages. The messages are delimited by a 4 byte uint32 size header * + * @author meros */ public class ProtoBufDecoder extends CumulativeProtocolDecoder { - private final ProtoBufMessageFactory protoBufMessageFactory; - - enum State { - ReadingLength, - ReadingPackage, - } - - State state = State.ReadingLength; - - static final int packageLengthTokenSize = 4; - int packageLength = 0; - - private Logger logger; - - ProtoBufDecoder(ProtoBufMessageFactory protoBufMessageFactory) { - logger = LoggerFactory.getLogger(ProtoBufDecoder.class); - this.protoBufMessageFactory = protoBufMessageFactory; - } - - @Override - protected boolean doDecode( - IoSession ioSession, - IoBuffer ioBuffer, - ProtocolDecoderOutput decoderOutput) { - try { - - if (state == State.ReadingLength) { - packageLength = readLength(ioBuffer); - - if (packageLength != -1) { - state = State.ReadingPackage; - } - } - - if (state == State.ReadingPackage) { - Message message = readMessage(ioBuffer, protoBufMessageFactory, packageLength); - - if (message != null) { - decoderOutput.write(message); - - state = State.ReadingLength; - - // There might be more messages to be parsed - return true; - } - } - } catch(IOException e) { - logger.error("IOException while trying to decode a message", e); - ioSession.closeNow(); - } - - //Need more data - return false; - } - - private static Message readMessage( - IoBuffer inputStream, - ProtoBufMessageFactory protoBufMessageFactory, - int packageLength) throws IOException { - int remainingBytesInStream = inputStream.remaining(); - if (remainingBytesInStream < packageLength) { - //Not enough data to parse message - return null; - } else { - //Create a delimited input stream around the real input stream - InputStream delimitedInputStream = new BoundedInputStream(inputStream.asInputStream(), packageLength); - - //Retrieve a builder - Builder builder = protoBufMessageFactory.createProtoBufMessage(); - - //And parse/build the message - builder.mergeFrom(delimitedInputStream); - Message message = builder.build(); - - return message; - } - } - - private static int readLength(IoBuffer inputStream) throws IOException { - if (inputStream.remaining() < packageLengthTokenSize) { - return -1; - } - - // Create length-delimited input stream - InputStream delimitedInputStream = new BoundedInputStream(inputStream.asInputStream(), packageLengthTokenSize); - - //Read length of incoming protobuf - CodedInputStream codedInputStream = CodedInputStream.newInstance(delimitedInputStream); - return codedInputStream.readFixed32(); - } - + private final ProtoBufMessageFactory protoBufMessageFactory; + + enum State { + ReadingLength, + ReadingPackage, + } + + State state = State.ReadingLength; + + static final int packageLengthTokenSize = 4; + int packageLength = 0; + + private Logger logger; + + ProtoBufDecoder(ProtoBufMessageFactory protoBufMessageFactory) { + logger = LoggerFactory.getLogger(ProtoBufDecoder.class); + this.protoBufMessageFactory = protoBufMessageFactory; + } + + @Override + protected boolean doDecode( + IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput decoderOutput) { + try { + + if (state == State.ReadingLength) { + packageLength = readLength(ioBuffer); + + if (packageLength != -1) { + state = State.ReadingPackage; + } + } + + if (state == State.ReadingPackage) { + Message message = readMessage(ioBuffer, protoBufMessageFactory, packageLength); + + if (message != null) { + decoderOutput.write(message); + + state = State.ReadingLength; + + // There might be more messages to be parsed + return true; + } + } + } catch (IOException e) { + logger.error("IOException while trying to decode a message", e); + ioSession.closeNow(); + } + + // Need more data + return false; + } + + private static Message readMessage( + IoBuffer inputStream, ProtoBufMessageFactory protoBufMessageFactory, int packageLength) + throws IOException { + int remainingBytesInStream = inputStream.remaining(); + if (remainingBytesInStream < packageLength) { + // Not enough data to parse message + return null; + } else { + // Create a delimited input stream around the real input stream + InputStream delimitedInputStream = + new BoundedInputStream(inputStream.asInputStream(), packageLength); + + // Retrieve a builder + Builder builder = protoBufMessageFactory.createProtoBufMessage(); + + // And parse/build the message + builder.mergeFrom(delimitedInputStream); + Message message = builder.build(); + + return message; + } + } + + private static int readLength(IoBuffer inputStream) throws IOException { + if (inputStream.remaining() < packageLengthTokenSize) { + return -1; + } + + // Create length-delimited input stream + InputStream delimitedInputStream = + new BoundedInputStream(inputStream.asInputStream(), packageLengthTokenSize); + + // Read length of incoming protobuf + CodedInputStream codedInputStream = CodedInputStream.newInstance(delimitedInputStream); + return codedInputStream.readFixed32(); + } } diff --git a/src/main/java/org/meros/pb4mina/ProtoBufEncoder.java b/src/main/java/org/meros/pb4mina/ProtoBufEncoder.java index e4089cc..d302657 100644 --- a/src/main/java/org/meros/pb4mina/ProtoBufEncoder.java +++ b/src/main/java/org/meros/pb4mina/ProtoBufEncoder.java @@ -1,52 +1,46 @@ package org.meros.pb4mina; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.Message; import java.io.InvalidObjectException; - import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolEncoderAdapter; import org.apache.mina.filter.codec.ProtocolEncoderOutput; -import com.google.protobuf.CodedOutputStream; -import com.google.protobuf.Message; - /** - * * Encoder for protobuf messages - * - * @author meros * + * @author meros */ public class ProtoBufEncoder extends ProtocolEncoderAdapter { - static final int packageLengthTokenSize = 4; - - @Override - public void encode( - IoSession session, - Object message, - ProtocolEncoderOutput out) throws Exception { - if (!(message instanceof Message)) { - throw new InvalidObjectException("You need to provide a protocol buffer message to the protocol buffer encoder"); - } - - Message protoMessage = (Message) message; - - //Get size of message - int messageSize = protoMessage.getSerializedSize(); - IoBuffer buffer = IoBuffer.allocate(messageSize + packageLengthTokenSize); - CodedOutputStream outputStream = CodedOutputStream.newInstance(buffer.asOutputStream()); - - //Write length delimited - outputStream.writeFixed32NoTag(messageSize); - protoMessage.writeTo(outputStream); - outputStream.flush(); - - //Flip the buffer to be able to start reading from the buffer - buffer.flip(); - - //Pass on the serialized data - out.write(buffer); - } + static final int packageLengthTokenSize = 4; + + @Override + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) + throws Exception { + if (!(message instanceof Message)) { + throw new InvalidObjectException( + "You need to provide a protocol buffer message to the protocol buffer encoder"); + } + + Message protoMessage = (Message) message; + + // Get size of message + int messageSize = protoMessage.getSerializedSize(); + IoBuffer buffer = IoBuffer.allocate(messageSize + packageLengthTokenSize); + CodedOutputStream outputStream = CodedOutputStream.newInstance(buffer.asOutputStream()); + + // Write length delimited + outputStream.writeFixed32NoTag(messageSize); + protoMessage.writeTo(outputStream); + outputStream.flush(); + + // Flip the buffer to be able to start reading from the buffer + buffer.flip(); + // Pass on the serialized data + out.write(buffer); + } } diff --git a/src/main/java/org/meros/pb4mina/ProtoBufMessageFactory.java b/src/main/java/org/meros/pb4mina/ProtoBufMessageFactory.java index b43c88a..0f09301 100644 --- a/src/main/java/org/meros/pb4mina/ProtoBufMessageFactory.java +++ b/src/main/java/org/meros/pb4mina/ProtoBufMessageFactory.java @@ -3,11 +3,10 @@ import com.google.protobuf.Message.Builder; /** - * Interface for a factory creating protobuf builders for the protobuf decoder - * - * @author meros + * Interface for a factory creating protobuf builders for the protobuf decoder * + * @author meros */ public interface ProtoBufMessageFactory { - Builder createProtoBufMessage(); + Builder createProtoBufMessage(); } diff --git a/src/test/java/org/meros/pb4mina/BoundedInputStreamTest.java b/src/test/java/org/meros/pb4mina/BoundedInputStreamTest.java index 6526e9b..5cb66da 100644 --- a/src/test/java/org/meros/pb4mina/BoundedInputStreamTest.java +++ b/src/test/java/org/meros/pb4mina/BoundedInputStreamTest.java @@ -1,115 +1,112 @@ package org.meros.pb4mina; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayInputStream; import java.io.IOException; +import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Unit tests for {@link BoundedInputStream} - */ +/** Unit tests for {@link BoundedInputStream} */ class BoundedInputStreamTest { - @Test - void testReadSingleByte() throws IOException { - byte[] data = {1, 2, 3, 4, 5}; - ByteArrayInputStream baseStream = new ByteArrayInputStream(data); - BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); - - assertEquals(1, bounded.read()); - assertEquals(2, bounded.read()); - assertEquals(3, bounded.read()); - assertEquals(-1, bounded.read()); // Should return -1 after limit - } - - @Test - void testReadByteArray() throws IOException { - byte[] data = {1, 2, 3, 4, 5}; - ByteArrayInputStream baseStream = new ByteArrayInputStream(data); - BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); - - byte[] buffer = new byte[10]; - int bytesRead = bounded.read(buffer); - - assertEquals(3, bytesRead); - assertEquals(1, buffer[0]); - assertEquals(2, buffer[1]); - assertEquals(3, buffer[2]); - } - - @Test - void testReadByteArrayWithOffset() throws IOException { - byte[] data = {1, 2, 3, 4, 5}; - ByteArrayInputStream baseStream = new ByteArrayInputStream(data); - BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); - - byte[] buffer = new byte[10]; - int bytesRead = bounded.read(buffer, 2, 5); - - assertEquals(3, bytesRead); - assertEquals(0, buffer[0]); - assertEquals(0, buffer[1]); - assertEquals(1, buffer[2]); - assertEquals(2, buffer[3]); - assertEquals(3, buffer[4]); - } - - @Test - void testAvailable() throws IOException { - byte[] data = {1, 2, 3, 4, 5}; - ByteArrayInputStream baseStream = new ByteArrayInputStream(data); - BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); - - assertEquals(3, bounded.available()); - bounded.read(); - assertEquals(2, bounded.available()); - bounded.read(); - assertEquals(1, bounded.available()); - bounded.read(); - assertEquals(0, bounded.available()); - } - - @Test - void testMarkNotSupported() { - byte[] data = {1, 2, 3}; - ByteArrayInputStream baseStream = new ByteArrayInputStream(data); - BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); - - assertFalse(bounded.markSupported()); - } - - @Test - void testLengthLargerThanAvailableThrows() { - byte[] data = {1, 2, 3}; - ByteArrayInputStream baseStream = new ByteArrayInputStream(data); - - assertThrows(RuntimeException.class, () -> new BoundedInputStream(baseStream, 10)); - } - - @Test - void testZeroLengthBound() throws IOException { - byte[] data = {1, 2, 3}; - ByteArrayInputStream baseStream = new ByteArrayInputStream(data); - BoundedInputStream bounded = new BoundedInputStream(baseStream, 0); - - assertEquals(0, bounded.available()); - assertEquals(-1, bounded.read()); - } - - @Test - void testReadReturnsMinusOneAfterExhausted() throws IOException { - byte[] data = {1, 2}; - ByteArrayInputStream baseStream = new ByteArrayInputStream(data); - BoundedInputStream bounded = new BoundedInputStream(baseStream, 2); - - byte[] buffer = new byte[10]; - bounded.read(buffer); - - // Further reads should return -1 - assertEquals(-1, bounded.read()); - assertEquals(-1, bounded.read(buffer)); - assertEquals(-1, bounded.read(buffer, 0, 5)); - } + @Test + void testReadSingleByte() throws IOException { + byte[] data = {1, 2, 3, 4, 5}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + assertEquals(1, bounded.read()); + assertEquals(2, bounded.read()); + assertEquals(3, bounded.read()); + assertEquals(-1, bounded.read()); // Should return -1 after limit + } + + @Test + void testReadByteArray() throws IOException { + byte[] data = {1, 2, 3, 4, 5}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + byte[] buffer = new byte[10]; + int bytesRead = bounded.read(buffer); + + assertEquals(3, bytesRead); + assertEquals(1, buffer[0]); + assertEquals(2, buffer[1]); + assertEquals(3, buffer[2]); + } + + @Test + void testReadByteArrayWithOffset() throws IOException { + byte[] data = {1, 2, 3, 4, 5}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + byte[] buffer = new byte[10]; + int bytesRead = bounded.read(buffer, 2, 5); + + assertEquals(3, bytesRead); + assertEquals(0, buffer[0]); + assertEquals(0, buffer[1]); + assertEquals(1, buffer[2]); + assertEquals(2, buffer[3]); + assertEquals(3, buffer[4]); + } + + @Test + void testAvailable() throws IOException { + byte[] data = {1, 2, 3, 4, 5}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + assertEquals(3, bounded.available()); + bounded.read(); + assertEquals(2, bounded.available()); + bounded.read(); + assertEquals(1, bounded.available()); + bounded.read(); + assertEquals(0, bounded.available()); + } + + @Test + void testMarkNotSupported() { + byte[] data = {1, 2, 3}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 3); + + assertFalse(bounded.markSupported()); + } + + @Test + void testLengthLargerThanAvailableThrows() { + byte[] data = {1, 2, 3}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + + assertThrows(RuntimeException.class, () -> new BoundedInputStream(baseStream, 10)); + } + + @Test + void testZeroLengthBound() throws IOException { + byte[] data = {1, 2, 3}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 0); + + assertEquals(0, bounded.available()); + assertEquals(-1, bounded.read()); + } + + @Test + void testReadReturnsMinusOneAfterExhausted() throws IOException { + byte[] data = {1, 2}; + ByteArrayInputStream baseStream = new ByteArrayInputStream(data); + BoundedInputStream bounded = new BoundedInputStream(baseStream, 2); + + byte[] buffer = new byte[10]; + bounded.read(buffer); + + // Further reads should return -1 + assertEquals(-1, bounded.read()); + assertEquals(-1, bounded.read(buffer)); + assertEquals(-1, bounded.read(buffer, 0, 5)); + } } diff --git a/src/test/java/org/meros/pb4mina/ProtoBufCoderFactoryTest.java b/src/test/java/org/meros/pb4mina/ProtoBufCoderFactoryTest.java index fc47ad7..3419a96 100644 --- a/src/test/java/org/meros/pb4mina/ProtoBufCoderFactoryTest.java +++ b/src/test/java/org/meros/pb4mina/ProtoBufCoderFactoryTest.java @@ -1,5 +1,8 @@ package org.meros.pb4mina; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + import com.google.protobuf.Any; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolDecoder; @@ -7,72 +10,67 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -/** - * Unit tests for {@link ProtoBufCoderFactory} - */ +/** Unit tests for {@link ProtoBufCoderFactory} */ class ProtoBufCoderFactoryTest { - private ProtoBufCoderFactory factory; - private ProtoBufMessageFactory messageFactory; - - @BeforeEach - void setUp() { - messageFactory = () -> Any.newBuilder(); - factory = new ProtoBufCoderFactory(messageFactory); - } - - @Test - void testGetEncoderReturnsSameInstance() throws Exception { - IoSession session1 = mock(IoSession.class); - IoSession session2 = mock(IoSession.class); - - ProtocolEncoder encoder1 = factory.getEncoder(session1); - ProtocolEncoder encoder2 = factory.getEncoder(session2); - - assertNotNull(encoder1); - assertNotNull(encoder2); - assertSame(encoder1, encoder2, "Encoder should be shared across sessions"); - assertTrue(encoder1 instanceof ProtoBufEncoder); - } - - @Test - void testGetDecoderCreatesSeparateInstancePerSession() throws Exception { - IoSession session1 = mock(IoSession.class); - IoSession session2 = mock(IoSession.class); - - // First session should create a new decoder - when(session1.getAttribute(any())).thenReturn(null); - ProtocolDecoder decoder1 = factory.getDecoder(session1); - - // Second session should also create a new decoder - when(session2.getAttribute(any())).thenReturn(null); - ProtocolDecoder decoder2 = factory.getDecoder(session2); - - assertNotNull(decoder1); - assertNotNull(decoder2); - assertTrue(decoder1 instanceof ProtoBufDecoder); - assertTrue(decoder2 instanceof ProtoBufDecoder); - - // Verify setAttribute was called to store the decoder - verify(session1).setAttribute(any(), eq(decoder1)); - verify(session2).setAttribute(any(), eq(decoder2)); - } - - @Test - void testGetDecoderReusesExistingDecoder() throws Exception { - IoSession session = mock(IoSession.class); - ProtoBufDecoder existingDecoder = new ProtoBufDecoder(messageFactory); - - // Session already has a decoder - when(session.getAttribute(any())).thenReturn(existingDecoder); - - ProtocolDecoder decoder = factory.getDecoder(session); - - assertSame(existingDecoder, decoder); - // setAttribute should not be called since decoder already exists - verify(session, never()).setAttribute(any(), any()); - } + private ProtoBufCoderFactory factory; + private ProtoBufMessageFactory messageFactory; + + @BeforeEach + void setUp() { + messageFactory = () -> Any.newBuilder(); + factory = new ProtoBufCoderFactory(messageFactory); + } + + @Test + void testGetEncoderReturnsSameInstance() throws Exception { + IoSession session1 = mock(IoSession.class); + IoSession session2 = mock(IoSession.class); + + ProtocolEncoder encoder1 = factory.getEncoder(session1); + ProtocolEncoder encoder2 = factory.getEncoder(session2); + + assertNotNull(encoder1); + assertNotNull(encoder2); + assertSame(encoder1, encoder2, "Encoder should be shared across sessions"); + assertTrue(encoder1 instanceof ProtoBufEncoder); + } + + @Test + void testGetDecoderCreatesSeparateInstancePerSession() throws Exception { + IoSession session1 = mock(IoSession.class); + IoSession session2 = mock(IoSession.class); + + // First session should create a new decoder + when(session1.getAttribute(any())).thenReturn(null); + ProtocolDecoder decoder1 = factory.getDecoder(session1); + + // Second session should also create a new decoder + when(session2.getAttribute(any())).thenReturn(null); + ProtocolDecoder decoder2 = factory.getDecoder(session2); + + assertNotNull(decoder1); + assertNotNull(decoder2); + assertTrue(decoder1 instanceof ProtoBufDecoder); + assertTrue(decoder2 instanceof ProtoBufDecoder); + + // Verify setAttribute was called to store the decoder + verify(session1).setAttribute(any(), eq(decoder1)); + verify(session2).setAttribute(any(), eq(decoder2)); + } + + @Test + void testGetDecoderReusesExistingDecoder() throws Exception { + IoSession session = mock(IoSession.class); + ProtoBufDecoder existingDecoder = new ProtoBufDecoder(messageFactory); + + // Session already has a decoder + when(session.getAttribute(any())).thenReturn(existingDecoder); + + ProtocolDecoder decoder = factory.getDecoder(session); + + assertSame(existingDecoder, decoder); + // setAttribute should not be called since decoder already exists + verify(session, never()).setAttribute(any(), any()); + } } diff --git a/src/test/java/org/meros/pb4mina/ProtoBufCoderFilterTest.java b/src/test/java/org/meros/pb4mina/ProtoBufCoderFilterTest.java index 03fea8e..e156401 100644 --- a/src/test/java/org/meros/pb4mina/ProtoBufCoderFilterTest.java +++ b/src/test/java/org/meros/pb4mina/ProtoBufCoderFilterTest.java @@ -1,30 +1,28 @@ package org.meros.pb4mina; +import static org.junit.jupiter.api.Assertions.*; + import com.google.protobuf.Any; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Unit tests for {@link ProtoBufCoderFilter} - */ +/** Unit tests for {@link ProtoBufCoderFilter} */ class ProtoBufCoderFilterTest { - @Test - void testConstructor() { - ProtoBufMessageFactory messageFactory = () -> Any.newBuilder(); - - // Should create without throwing - ProtoBufCoderFilter filter = new ProtoBufCoderFilter(messageFactory); - - assertNotNull(filter); - } + @Test + void testConstructor() { + ProtoBufMessageFactory messageFactory = () -> Any.newBuilder(); + + // Should create without throwing + ProtoBufCoderFilter filter = new ProtoBufCoderFilter(messageFactory); + + assertNotNull(filter); + } - @Test - void testConstructorWithNullFactory() { - // Factory can be null in constructor but will fail later when used - // This is consistent with how the code works - it doesn't validate null - ProtoBufCoderFilter filter = new ProtoBufCoderFilter(null); - assertNotNull(filter); - } + @Test + void testConstructorWithNullFactory() { + // Factory can be null in constructor but will fail later when used + // This is consistent with how the code works - it doesn't validate null + ProtoBufCoderFilter filter = new ProtoBufCoderFilter(null); + assertNotNull(filter); + } } diff --git a/src/test/java/org/meros/pb4mina/ProtoBufDecoderTest.java b/src/test/java/org/meros/pb4mina/ProtoBufDecoderTest.java index 4c47fd9..9d8e73e 100644 --- a/src/test/java/org/meros/pb4mina/ProtoBufDecoderTest.java +++ b/src/test/java/org/meros/pb4mina/ProtoBufDecoderTest.java @@ -1,9 +1,13 @@ package org.meros.pb4mina; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + import com.google.protobuf.Any; import com.google.protobuf.CodedOutputStream; import com.google.protobuf.Message; import com.google.protobuf.StringValue; +import java.io.ByteArrayOutputStream; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolDecoderOutput; @@ -11,144 +15,137 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import java.io.ByteArrayOutputStream; +/** Unit tests for {@link ProtoBufDecoder} */ +class ProtoBufDecoderTest { -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; + private ProtoBufDecoder decoder; + private IoSession mockSession; + private ProtocolDecoderOutput mockOutput; + private ProtoBufMessageFactory messageFactory; -/** - * Unit tests for {@link ProtoBufDecoder} - */ -class ProtoBufDecoderTest { + @BeforeEach + void setUp() { + // Create a factory that creates Any message builders + messageFactory = () -> Any.newBuilder(); + decoder = new ProtoBufDecoder(messageFactory); + mockSession = mock(IoSession.class); + mockOutput = mock(ProtocolDecoderOutput.class); + } + + @Test + void testDecodeCompleteMessage() throws Exception { + // Create a test message + Any testMessage = Any.pack(StringValue.of("test content")); + + // Encode the message with length prefix + IoBuffer buffer = createEncodedBuffer(testMessage); + + // Decode the message + boolean result = decoder.doDecode(mockSession, buffer, mockOutput); + + // Verify message was decoded + assertTrue(result); + + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + verify(mockOutput).write(messageCaptor.capture()); + + Message decodedMessage = messageCaptor.getValue(); + assertNotNull(decodedMessage); + assertTrue(decodedMessage instanceof Any); + assertEquals(testMessage, decodedMessage); + } + + @Test + void testDecodeIncompleteLength() throws Exception { + // Only 2 bytes when 4 are needed for length + IoBuffer buffer = IoBuffer.allocate(2); + buffer.put((byte) 0); + buffer.put((byte) 0); + buffer.flip(); + + boolean result = decoder.doDecode(mockSession, buffer, mockOutput); + + assertFalse(result); + verify(mockOutput, never()).write(any()); + } + + @Test + void testDecodeIncompleteMessage() throws Exception { + Any testMessage = Any.pack(StringValue.of("test content")); + int messageSize = testMessage.getSerializedSize(); + + // Create buffer with full length header but only partial message + IoBuffer buffer = IoBuffer.allocate(4 + messageSize / 2); + CodedOutputStream cos = CodedOutputStream.newInstance(buffer.asOutputStream()); + cos.writeFixed32NoTag(messageSize); + cos.flush(); + + // Write only half the message bytes + byte[] messageBytes = testMessage.toByteArray(); + buffer.put(messageBytes, 0, messageSize / 2); + buffer.flip(); + + boolean result = decoder.doDecode(mockSession, buffer, mockOutput); + + assertFalse(result); + verify(mockOutput, never()).write(any()); + } + + @Test + void testDecodeMultipleMessages() throws Exception { + Any testMessage1 = Any.pack(StringValue.of("message one")); + Any testMessage2 = Any.pack(StringValue.of("message two")); + + // Create buffer with two encoded messages + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CodedOutputStream cos = CodedOutputStream.newInstance(baos); + + // First message + cos.writeFixed32NoTag(testMessage1.getSerializedSize()); + testMessage1.writeTo(cos); + + // Second message + cos.writeFixed32NoTag(testMessage2.getSerializedSize()); + testMessage2.writeTo(cos); + + cos.flush(); + + IoBuffer buffer = IoBuffer.wrap(baos.toByteArray()); + + // Decode first message + boolean result1 = decoder.doDecode(mockSession, buffer, mockOutput); + assertTrue(result1); + + // Decode second message + boolean result2 = decoder.doDecode(mockSession, buffer, mockOutput); + assertTrue(result2); + + // No more messages + boolean result3 = decoder.doDecode(mockSession, buffer, mockOutput); + assertFalse(result3); + + verify(mockOutput, times(2)).write(any()); + } + + @Test + void testDecodeEmptyBuffer() throws Exception { + IoBuffer buffer = IoBuffer.allocate(0); + buffer.flip(); + + boolean result = decoder.doDecode(mockSession, buffer, mockOutput); + + assertFalse(result); + verify(mockOutput, never()).write(any()); + } - private ProtoBufDecoder decoder; - private IoSession mockSession; - private ProtocolDecoderOutput mockOutput; - private ProtoBufMessageFactory messageFactory; - - @BeforeEach - void setUp() { - // Create a factory that creates Any message builders - messageFactory = () -> Any.newBuilder(); - decoder = new ProtoBufDecoder(messageFactory); - mockSession = mock(IoSession.class); - mockOutput = mock(ProtocolDecoderOutput.class); - } - - @Test - void testDecodeCompleteMessage() throws Exception { - // Create a test message - Any testMessage = Any.pack(StringValue.of("test content")); - - // Encode the message with length prefix - IoBuffer buffer = createEncodedBuffer(testMessage); - - // Decode the message - boolean result = decoder.doDecode(mockSession, buffer, mockOutput); - - // Verify message was decoded - assertTrue(result); - - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - verify(mockOutput).write(messageCaptor.capture()); - - Message decodedMessage = messageCaptor.getValue(); - assertNotNull(decodedMessage); - assertTrue(decodedMessage instanceof Any); - assertEquals(testMessage, decodedMessage); - } - - @Test - void testDecodeIncompleteLength() throws Exception { - // Only 2 bytes when 4 are needed for length - IoBuffer buffer = IoBuffer.allocate(2); - buffer.put((byte) 0); - buffer.put((byte) 0); - buffer.flip(); - - boolean result = decoder.doDecode(mockSession, buffer, mockOutput); - - assertFalse(result); - verify(mockOutput, never()).write(any()); - } - - @Test - void testDecodeIncompleteMessage() throws Exception { - Any testMessage = Any.pack(StringValue.of("test content")); - int messageSize = testMessage.getSerializedSize(); - - // Create buffer with full length header but only partial message - IoBuffer buffer = IoBuffer.allocate(4 + messageSize / 2); - CodedOutputStream cos = CodedOutputStream.newInstance(buffer.asOutputStream()); - cos.writeFixed32NoTag(messageSize); - cos.flush(); - - // Write only half the message bytes - byte[] messageBytes = testMessage.toByteArray(); - buffer.put(messageBytes, 0, messageSize / 2); - buffer.flip(); - - boolean result = decoder.doDecode(mockSession, buffer, mockOutput); - - assertFalse(result); - verify(mockOutput, never()).write(any()); - } - - @Test - void testDecodeMultipleMessages() throws Exception { - Any testMessage1 = Any.pack(StringValue.of("message one")); - Any testMessage2 = Any.pack(StringValue.of("message two")); - - // Create buffer with two encoded messages - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - CodedOutputStream cos = CodedOutputStream.newInstance(baos); - - // First message - cos.writeFixed32NoTag(testMessage1.getSerializedSize()); - testMessage1.writeTo(cos); - - // Second message - cos.writeFixed32NoTag(testMessage2.getSerializedSize()); - testMessage2.writeTo(cos); - - cos.flush(); - - IoBuffer buffer = IoBuffer.wrap(baos.toByteArray()); - - // Decode first message - boolean result1 = decoder.doDecode(mockSession, buffer, mockOutput); - assertTrue(result1); - - // Decode second message - boolean result2 = decoder.doDecode(mockSession, buffer, mockOutput); - assertTrue(result2); - - // No more messages - boolean result3 = decoder.doDecode(mockSession, buffer, mockOutput); - assertFalse(result3); - - verify(mockOutput, times(2)).write(any()); - } - - @Test - void testDecodeEmptyBuffer() throws Exception { - IoBuffer buffer = IoBuffer.allocate(0); - buffer.flip(); - - boolean result = decoder.doDecode(mockSession, buffer, mockOutput); - - assertFalse(result); - verify(mockOutput, never()).write(any()); - } - - private IoBuffer createEncodedBuffer(Message message) throws Exception { - int messageSize = message.getSerializedSize(); - IoBuffer buffer = IoBuffer.allocate(4 + messageSize); - CodedOutputStream cos = CodedOutputStream.newInstance(buffer.asOutputStream()); - cos.writeFixed32NoTag(messageSize); - message.writeTo(cos); - cos.flush(); - buffer.flip(); - return buffer; - } + private IoBuffer createEncodedBuffer(Message message) throws Exception { + int messageSize = message.getSerializedSize(); + IoBuffer buffer = IoBuffer.allocate(4 + messageSize); + CodedOutputStream cos = CodedOutputStream.newInstance(buffer.asOutputStream()); + cos.writeFixed32NoTag(messageSize); + message.writeTo(cos); + cos.flush(); + buffer.flip(); + return buffer; + } } diff --git a/src/test/java/org/meros/pb4mina/ProtoBufEncoderTest.java b/src/test/java/org/meros/pb4mina/ProtoBufEncoderTest.java index a3f43f9..88b466a 100644 --- a/src/test/java/org/meros/pb4mina/ProtoBufEncoderTest.java +++ b/src/test/java/org/meros/pb4mina/ProtoBufEncoderTest.java @@ -1,9 +1,11 @@ package org.meros.pb4mina; -import com.google.protobuf.ByteString; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + import com.google.protobuf.CodedInputStream; -import com.google.protobuf.CodedOutputStream; import com.google.protobuf.Message; +import java.io.InvalidObjectException; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolEncoderOutput; @@ -11,72 +13,60 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import java.io.InvalidObjectException; +/** Unit tests for {@link ProtoBufEncoder} */ +class ProtoBufEncoderTest { -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; + private ProtoBufEncoder encoder; + private IoSession mockSession; + private ProtocolEncoderOutput mockOutput; -/** - * Unit tests for {@link ProtoBufEncoder} - */ -class ProtoBufEncoderTest { + @BeforeEach + void setUp() { + encoder = new ProtoBufEncoder(); + mockSession = mock(IoSession.class); + mockOutput = mock(ProtocolEncoderOutput.class); + } + + @Test + void testEncodeValidMessage() throws Exception { + // Create a simple test message using ByteString which is a Message + Message testMessage = createTestMessage(); + + ArgumentCaptor bufferCaptor = ArgumentCaptor.forClass(IoBuffer.class); + + encoder.encode(mockSession, testMessage, mockOutput); + + verify(mockOutput).write(bufferCaptor.capture()); - private ProtoBufEncoder encoder; - private IoSession mockSession; - private ProtocolEncoderOutput mockOutput; + IoBuffer capturedBuffer = bufferCaptor.getValue(); + assertNotNull(capturedBuffer); + assertTrue(capturedBuffer.remaining() > 4); // At least 4 bytes for length header - @BeforeEach - void setUp() { - encoder = new ProtoBufEncoder(); - mockSession = mock(IoSession.class); - mockOutput = mock(ProtocolEncoderOutput.class); - } + // Verify the length header + CodedInputStream cis = CodedInputStream.newInstance(capturedBuffer.asInputStream()); + int length = cis.readFixed32(); + assertEquals(testMessage.getSerializedSize(), length); + } - @Test - void testEncodeValidMessage() throws Exception { - // Create a simple test message using ByteString which is a Message - Message testMessage = createTestMessage(); - - ArgumentCaptor bufferCaptor = ArgumentCaptor.forClass(IoBuffer.class); - - encoder.encode(mockSession, testMessage, mockOutput); - - verify(mockOutput).write(bufferCaptor.capture()); - - IoBuffer capturedBuffer = bufferCaptor.getValue(); - assertNotNull(capturedBuffer); - assertTrue(capturedBuffer.remaining() > 4); // At least 4 bytes for length header - - // Verify the length header - CodedInputStream cis = CodedInputStream.newInstance(capturedBuffer.asInputStream()); - int length = cis.readFixed32(); - assertEquals(testMessage.getSerializedSize(), length); - } + @Test + void testEncodeNonMessageThrows() { + String invalidObject = "not a protobuf message"; - @Test - void testEncodeNonMessageThrows() { - String invalidObject = "not a protobuf message"; - - assertThrows(InvalidObjectException.class, () -> - encoder.encode(mockSession, invalidObject, mockOutput) - ); - } + assertThrows( + InvalidObjectException.class, () -> encoder.encode(mockSession, invalidObject, mockOutput)); + } - @Test - void testEncodeNullObjectThrows() { - assertThrows(Exception.class, () -> - encoder.encode(mockSession, null, mockOutput) - ); - } + @Test + void testEncodeNullObjectThrows() { + assertThrows(Exception.class, () -> encoder.encode(mockSession, null, mockOutput)); + } - /** - * Creates a simple test protobuf message for testing. - * Uses a dynamically created message with some fields. - */ - private Message createTestMessage() { - // Create a simple message using the Any type or a custom builder - return com.google.protobuf.Any.pack( - com.google.protobuf.StringValue.of("test message content") - ); - } + /** + * Creates a simple test protobuf message for testing. Uses a dynamically created message with + * some fields. + */ + private Message createTestMessage() { + // Create a simple message using the Any type or a custom builder + return com.google.protobuf.Any.pack(com.google.protobuf.StringValue.of("test message content")); + } }