From 695663f30d24948ab1227ae28178251a7f41d2ef Mon Sep 17 00:00:00 2001 From: Jason Huang Date: Sun, 26 Jul 2026 01:51:39 +0800 Subject: [PATCH] egis_etu905: Fix identify cancel to report cancelled error correctly task_ssm_done cleared task_ssm before calling maybe_cancel, so identify_cancel_ssm_done couldn't tell whether the identify already completed or was cancelled. Use fpi_device_action_is_cancelled() and skip the duplicate identify_complete call in IDENTIFY_COMPLETE state when cancelled, letting the cancel flow handle completion. --- libfprint/drivers/egismoc/egis_etu905.c | 63 ++++++++- tests/egis_etu905/custom.pcapng | Bin 37276 -> 47156 bytes tests/egis_etu905/custom.py | 11 +- tests/egis_etu905/device | 171 ++++++------------------ 4 files changed, 104 insertions(+), 141 deletions(-) diff --git a/libfprint/drivers/egismoc/egis_etu905.c b/libfprint/drivers/egismoc/egis_etu905.c index 7ae973fb..c39e7b31 100644 --- a/libfprint/drivers/egismoc/egis_etu905.c +++ b/libfprint/drivers/egismoc/egis_etu905.c @@ -199,19 +199,26 @@ egis_etu905_task_ssm_done (FpiSsm *ssm, { g_autoptr(GError) task_error = g_steal_pointer (&error); FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + gboolean is_cancellation; fp_dbg ("Task SSM done"); /* task_ssm is going to be freed by completion of SSM */ g_assert (!self->task_ssm || self->task_ssm == ssm); - self->task_ssm = NULL; + /* Check if this is a cancellation before clearing task_ssm, so we can + * distinguish between "identify completed normally" and "identify was + * cancelled". The identify_cancel_ssm_done callback needs to know this + * to report the correct result. */ + is_cancellation = egis_etu905_maybe_cancel (device, task_error); + + self->task_ssm = NULL; g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref); /* On cancellation we need to send the device-side cancel command before * reporting the error, as the idle ->cancel vfunc may not be called due * to the operation already being completed by the cancellable abort. */ - if (egis_etu905_maybe_cancel (device, task_error)) + if (is_cancellation) return; if (task_error) @@ -1343,7 +1350,35 @@ egis_etu905_identify_cancel_ssm_done (FpiSsm *ssm, self->identify_cancel_ssm = NULL; - egis_etu905_cancel_cb (device, NULL, 0, error); + if (error) + { + g_warning ("Cancel command failed: %s", error->message); + g_clear_error (&error); + } + + /* The cancel flow is triggered in two scenarios: + * 1. Identify was cancelled before completion (task_ssm failed with + * G_IO_ERROR_CANCELLED). We need to report the cancel error. + * 2. Identify completed normally but was cancelled just as it finished + * (task_ssm completed, fpi_device_identify_complete was already called + * in IDENTIFY_COMPLETE state). We just need to clean up. + * + * We distinguish these by checking if the action is still cancelled. + * If fpi_device_action_is_cancelled returns TRUE, the identify was + * cancelled and we need to report the error. Otherwise, identify + * completed successfully and we just complete the action. */ + if (fpi_device_action_is_cancelled (device)) + { + fp_dbg ("Cancel completed, reporting cancel error"); + error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, + "Operation was cancelled"); + fpi_device_action_error (device, g_steal_pointer (&error)); + } + else + { + fp_dbg ("Cancel completed after identify done, completing identify action"); + fpi_device_identify_complete (device, NULL); + } } static void @@ -1479,9 +1514,15 @@ egis_etu905_identify_run_state (FpiSsm *ssm, * this extra step unnecessary and just skip it in this driver. This driver * will instead handle matching of the FpPrint from the gallery in the * callback egis_etu905_identify_check_cb. + * + * If the operation was cancelled, we don't complete the identify action + * here. Instead, we let the cancel flow (triggered by task_ssm_done) + * complete first, and it will call fpi_device_identify_complete when done. + * This ensures the device stays open until the cancel flow completes. */ case IDENTIFY_COMPLETE: - fpi_device_identify_complete (device, NULL); + if (!fpi_device_action_is_cancelled (device)) + fpi_device_identify_complete (device, NULL); fpi_ssm_mark_completed (ssm); break; @@ -1750,6 +1791,10 @@ egis_etu905_cancel_cb (FpDevice *device, g_clear_error (&error); } + /* This callback is only used for enroll cancellation. + * Identify cancellation uses egis_etu905_identify_cancel_ssm_done instead. + * We must always call fpi_device_action_error here to complete the enroll + * action, even if the task_ssm has already been freed. */ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Operation was cancelled"); @@ -1760,6 +1805,7 @@ static void egis_etu905_cancel (FpDevice *device) { FpiDeviceAction action = fpi_device_get_current_action (device); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); fp_dbg ("Cancelling action %d", action); @@ -1768,7 +1814,12 @@ egis_etu905_cancel (FpDevice *device) * already cancelled (it aborted the in-flight operation transfer), so we * must not bind these commands to it or they would never be sent. * Cancellation is effectively non-cancellable; the commands are allowed to - * fail (no callback), the device is reset by the next operation anyway. */ + * fail (no callback), the device is reset by the next operation anyway. + * + * Note: Even if task_ssm is NULL (operation already completed), we still + * need to execute the cancel commands to trigger firmware template update + * and device cleanup. The egis_etu905_cancel_cb will check task_ssm and + * avoid calling fpi_device_action_error if the action already completed. */ if (action == FPI_DEVICE_ACTION_ENROLL) { egis_etu905_exec_cmd_full (device, @@ -1779,8 +1830,6 @@ egis_etu905_cancel (FpDevice *device) } else if (action == FPI_DEVICE_ACTION_IDENTIFY) { - FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); - g_assert (self->identify_cancel_ssm == NULL); self->identify_cancel_ssm = fpi_ssm_new (device, diff --git a/tests/egis_etu905/custom.pcapng b/tests/egis_etu905/custom.pcapng index 4a24f72f4209aae668b2be50e61c6579fa19a071..b9b8f2f40fc1ca1506427c95e3209f9394134a47 100644 GIT binary patch literal 47156 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 literal 37276 zcmdU2378bc+3ua0-DNqHAc8@Z4IbQtMFAlk&B87$B96O)(Wo@AM3!@vOHuzUDl8zx zjpP^cG0M*+Vxo9NjHoP%tEe1mRDuK#JV=a>;(;az{NLAAZ+A^?&rEkS{ycy2)Lh;3 zzV+2x-&@r+%S^*2O&UI77{*0?JGIK9`(v_1jLt^M)EVO^w=eCGKWN%@#^9M#D#wkf%x`~L#dYJSPZ)FEqz?J?zOLQ#+YhOjkv~g3t{gLN(wJ+; zm*?3 z8;Rd6%ZMDKrp{^oaCYJHtVonv!CZPw{fN_~*SUgIkEtJVa!h%24i}1vSn!Nq&|;cw zpC$TiKjD|qbFRSGW7;ocVnhX1qR^eh&x^D(va*a7-RULf&|^8^BbEa)mUa?d#DZro zmnHER$n#kLol9oTyM~@~O%}_}AA~-PsGv#|y3=FHiy20gI@id}o*OX@x{HXrTcaio z9p=~L-2LMGd32`1)%f!L=hbm|jvR+~f7K-PIQ+P7zB3Mi56ytT=IbWRZjZrCjKlsf zrH|<-aq>7szXs9nXD`opv`ZG#2j7U8eoXCFQ#(xRH?C+0@(q1k{;i0U^4Bg^Iniid z4A1B#Ifg8H$YbLss$6)Cj@mYV-WBwiEAVygS{xE_wxUzG)+-XfRkWKi(73?pX%rYe zjJPp^o}EXJdeIx0Qy-U$UiFI2V1G0a+pzCNYz}808Iwn`=rtXG==S>fgCdOIr9bc+ z{71y*0h#cU7yc6?7rNT){l#`Qug;Zub-tMuIPSbHQz_Kx$)odc|7AXpY9jGCSH?X=L+6I zg10|-KOtT&sW|z#MODXNqu^KVk$w@K%W?Gu{yAf$E=?K5qe!o{=n&(pUcRDs;k;kb zYg~apNZ>pDmj94w|LJrN*Lp?bmz4}DqGMUTU!&V5y}R}3*0neNJHLC4>*aULH?w9( ziF-CZ=UV$FVoTKQzlfXjiRS~Z$qF=k>>E0xLws%(-L5B$jrzWEm7PSo^YGHzQAu~)Uuy0x?zqUwJ@A!jx!3sA zh^?dF-1~bWk>tjIN-NhaQ}XFLy-B=$%&xK@b)CwHmrI!3ThU3uKfLK3d#=`UFTaI| z%exbaSoWhu54rs{RJpKD{SGdg$NkCGl=!+%&%P@Fy3sjY>lKNQ+#9}}&S+a4FX+-u zmM-1ghf}AGomPGWx1xql({;@L*444<2;xo5$9eAs)6p10M7i-6>>&A6y*#v-cuhLS z6}-BRb;QdhjEoZ@Asju}b3WI#!R1+U@tNV>6y`+FtRSb)7rR zdvI{wZGU*b?Udr(&+TsX;i~G-CpF*p)Uen&U-x!(RIl;JQ+rD3?Of3wJs&@&_OP!e zT?5*!H4OOb&x+}VSlnL9dtKwNCQg%1;R;S&!?VjpA24UCV$nf8*IUj}@SU-lsNpig z=a~HFVx?>Z=VILlw>zHcjzJsWnFots6YXzG@8eppXt}6+rmE4>4_%FnFEJ()bM2Jv z!8B^a8z8ZGiwGr~mqyPMr%9)91*fi2A#rjEqfzO87ax9BrupR?GA8d1Bx2cz7Cq!K zplVc9w%E}K_Oc z<@<0aR!sCvi)V#Afydl)C3nr|#ihN#l=y=LzR^ow3m)Cx(0<1Ny5)+ZGMJDu)F4uq}REc-zSbyw9nYtFwney2CoolK8R4iaMyUv2eAw9 znr|{ESILWU3f{rQ%O%WwuvNjY{Q+?aF9CjRmx#;nI8}3d+S+CF*?YMHALA7Gk8~7! zc${aRxhBUAbp5MEC&scG)zBSsTji{IK;is1ViJA=&Rw0{ePd5f!sg=F^EVNvN#}E| zm7Kaxhl!I*Dn34LQPn+vuYzxWg_sg`YTa4HqDI%s zn7liXh-DvI^icQwZZEhR0bkdsa!4?ZQhEN^t!{tSVqTV>k33pARA^*Ud)1JTc*PON1$!*|coC4o`su4HO z&NI(k)4b$Oi%yP3l6w9jh0{DNKck5zYITS>Gr7(`YxY`4CvfUIo%I(tPL4NOvFL!V zQ?Y_?{D7E*pP(O$2|w&QzvQ;Hj^_hk*QumVv_BJ`e-rV+^NnMOCsCuDh%bmn={$ce zahlY(T*0Ypv{uIGIS%=KvRHJlYxJ~&ZyrTVi5fjEWAd9zm6DG=vTUiV5%6`5%H9g5 zQ7X^hOnfHsbFKQ1^aknq7;~G6k2T_R)Z)kUH&)Lg-faSGlJ!$iM?@%*_8KIRl$j{<)n;Ro>&)$>2z z_PCqda<0TU1^$|C;;#)j&pdNY^ZYj~Ix!Ya>iG>`bvdzq(6xH`mApWHl5TIOJ8_!S zEUw_xb^3}pxp>BC9}zbe-PQ9G72jEN2tPqT&UsbD6vj{9eCKh;^Yt|(@Rz4yI8$&%d8IP3l{&wUSfUXf$zh38T?r75`ksl&H}r8B-{Y zK2bE1_OMG~KAL;q8 zPv%Sz`#S&Edpji?HanhQ)e(A%F^cwWo05}p&tF8mCdJGZycnn8Z8%l*OBl}|_o$0+ zbVgiaoC1G8;fMA7pPawN^?ZqsaSHt39n7WZ{IBz0x9H?pEUD+mdnlY|A*MvFc72tb zQO{pWoF+Akt06gcolg1MjgwDHRxCQ8d;YYZE`Du3VoKEM3&Ibgle&g%Q@F+Te2K5? zbpJ8Y{!DoOwZvCSadO3)74tI2#X;iZc-VirCM&pZPUrde5~oQZc9rXJU8B~9SU;uW z<91k7b&Xz7@T(3cuJaMoAi@u;QOiqQjexIfwCp6&e!m)}^86*lXHuM8tGf^@HuMnYx;ThP^ z-(}JBSawp+UouJI+`f9Jx`qVj_D_QONxF6ZPbTkjb&{OAPM3V@#wjB5zUAvZ>=6)EavB=oJ7F|TBY;+J;Z6! z`F53@x=wqDlZ$7J_7QPo(Vec-eU~cuFC(Tzo$gx_?0QUH=U-**b3EUa__|J4Ef?+2 zgy)|=#?{Dp4e=!A)9LpF(8o7D4{{7K=& z`b2()3(kvH2lJD3p1+AWO==d`TFI&FbR%(c@r=#AyBC9}zbe-RYjcR>8-b z2!4WoJhx5weS@*Hj<}vL*L3g`;D6Xrv_BK;{LhK6l;Y&-u8D=`gKtWwU>c?K{JyhY zjlijE^bB!w38T@g3O=qQbd4_T9A2ZV=D8XHU)SjGy@P3#%JUx}K9l;wuDG_8>wIX` zu8)X^&rypX&#zdLSN$7$6IbWjQs5f{q~||)ENa7^2+u!A?Hft#T=o3|w6DO({5pT( zZ}X~6=2UnwPQkm4c)5h}{H+Q;_Fgbffgj5Xzs}!S*r*!04SbAK;D7gon6o&~JabL+ z{MRfxF_xFq^W%eEPQ%%+5`F^CnkU6PmXwnyxL^&L&hu{~PLs~(3Qk?8XNZ%FXN>j{ zabwY)?)k%u6nt%NMof!02AiAJHRPx9M%8iRJT0`QKmP+VOngW1Is2`*rTx$}`tA&wrHzi8V~>`KQfsIg#7CR;R5GwuVgS`9;KO zQnR>%Q`f18IJtPnXde+b7TxJO)hYP!WcUgCu};S1S0~j^qSdXFdOq-@8-uMOQ|;}n zB)(FLlPl&(U89x6m&v`ImJ3{sz^QB0@~Q9|byo0eowbIzW=0=&Cj78_J1hR%)d=`{ zF0FVmm`15Q|83$kNgli6b1~`pi05tM3%j>7>e>#D=VKj>y`6#5^IMD--gY89|McrR zIG&H|5R6f@?_Fvi$Dc{hw}{v2cjvP;dp*=2y!n>smoT2cPr=9aBD@6n_Yr=OajJTL z(cDgMZfpDbz|WrM=9#A-YM%d!MFTO`D5>W^dyC77Yj|C&n=6C)NjlHZzO|F9lk@pn zqE0UoCl}8c?IYsGqC4I5|DoXPdpqdI2d@q9C*%Lw$?<&P!%u*J&hB7-lFIW3eCBF| zy)a#)W5mbfj{7TBpJ5G^Zk<1wI8DOh3Ql-FV(k34@ERRb@bQ_Bo=Z;>evn*J=jC2k zc6K$=_DRwH@8ths@_Q|o%JcUSANtGrtWSDA`0kPacZbhYi=TD=t#|fvJRkV*{6gvZ zQTlENE+@kCdoJzecs};kF-Fn8C*^lfWYY5|5U)uwa|JKPDR>XaehK6GPb&EMTnOV7 z_&o=N-`g3|-~z|?X5Pox?R*K`wPJp)=y~7Q9o>5A%(5 z-;b6IG4b_UdaCPv%c*@Fz0rQ%KO$Bh^~$_YV$m{o484v&6h?J~G&MqNH0dKTn({^{rjC^)g~CBu*}2yz_kp zU%##az9nM{vq#dondxW*e0`4l?_F6FM$7# zjpDnc<8%(!dPQ4{ys(wdC`{*H>5kVJ8wiG7NY%OSq=Amk*w@j$xA%4?`xrEXA9tGdf_QRk3sO79HV!(z9gyOlUi3Ysf8b&UrTCznv3 ztKehbTb~y#8B-X~y>VKhC!?zi;7rO>qTrnsf?R_cM~jwdx+?{nkTroiM!TIc+#RFZ|9=C1 z<$Te8zhhMQk=@)kR5&O8O?*7$?P2d+9}2GrU+-!W&ulu6YqEmtspfCc85QxCd_83) zI~lL1TZ*JRPD*n;oa>gv$y7OrC{IVs^)6>1Xm;AW6nqWSHB)mBm4V? zy}w8QrO6jL>u+4|=o;m|9qhXo{ak0AHbv=2?Q=fF`Ag#E5@!8{d45^#l`7;TcKgvrHm3ckKC1^iVqCchf1xp?O65sqI1U(dy>-w*bk zfcm_tYIF%i_a5ZkhM*^_4gLyG>MNZICa0w`XIbU*7YvF{uv=OT0!_> z*EY8;S2S|wWbuC~fWI{oY+owX`uio~Lw{j!y-Ztw=SIVSSMOUrM?33p;A8zgSYA)O zb(PqcI1%gbie96gG3tIsBi43kU&Ym8UxRZxRbFFlmu~&Nj5wV>cRsff>n?CEBTg=< z`1rU*RbO{KuHfVIJIocpe_X~C#z*dWWVGWWz=w|j|Bkms`iYZV8%8_pZ}7oK!1wcaghv0L*55lQE|X%ltM>UL;@UxRaS5~jZvUu@?~ZNp zH*COfzdP8TojRY?oIJ)E2f)|IK~2wq`#CAr--DZuadTDs{1NdC?iFl5C*7PsjW}(6 zNCU^eqKUaWjX1f4$<<{lzH@DvsL?VRlXoH!vCJil9&+xe8r^@oqLDVA6iNRbP4D5t zdCIk3(Q;Lv^RJh0;A_gEKO5t$zcK$JSHahS8p-oxCilOqiPxlda0M^seee$9&Y|LW z{jIKLjuCz-y_YN2PI_GY5pIyROuDhxudN#sIQ5v0$e6;6z3&x#cMUDZ9{P;`gf_@p zW&?LH6&?#GwHfPNQve_S0Q?>NgU3|jvf5A78*S`m`kF!=yASmm>x^9^YgNMch3Wp1 z$+0^$KGqq#;DyfMg~#|EyO!)j*5Ts`3VsA}9iPXjI^Qza)!C5vy3X*J6Q#47%Wdu% z>*TT#MSmo&`7%9^JoA{Mf#gi4ahP1b{8a@%hM0~|<4R==9oj$End=RSpG@PX^ggcj zik8dzJfdo}^an+wY{YYX8l}5->~`unHxDIeCNe)&kO3qAbG_Xd&Z-AIGr_sK3ibfLucr{9uM>lR!G|EFf z$EQ*Lm6X}Bc#LOm9uK?(v8Zz4`R24eN*_wjOltJ!uN3@-h$(X#l{PAO^GM?Vm>PX? zR=Jx;jS$cAY1Bq)#Fp!&(c-@4uGdMXID?YOrq{Y;Fnuv)J6;ytQC=x#}($1j2xmeLXkNzGzN*9ktZuZ=W zNq;+>9T9i8MosE&=GXmbIkkh%G`ON2Wtw)EQ#<%Nhx*MfL4I?7)$QfgSJ1n-0$-1F zT&0LJ)4w_Y-eNbls=v$M!H$UEoX5Di|5_279T?vP_|5s*chtwnZ_We1*9;MxgP-Jq zMX$3q>NVG0>&u;ME~oub;eEg#b%$ub-)k;Ezd8RA@tV{YuHc3Lfp<0WatU+IwcsHa z-|U+B-Tc5GS|fGwTr3l@9QzhMlH2cGQF>LwK3#g}cxN1-UHUj^K3nu-sy1<3Ef4(ZD!$`WVhxIMa30|Y z83$^