@@ -826,51 +826,82 @@ import CZitiPrivate
826826 }
827827 } , ZitiEvent . EventType. Auth. rawValue | ZitiEvent . EventType. ConfigEvent. rawValue)
828828
829- // Wait for context to authenticate after enrollment, then read the real identity
829+ // Wait for context to authenticate after enrollment, then read the real identity.
830+ // The identity fetch (ziti_ctrl_current_identity) is async and can complete after
831+ // the first context event. We try on every event, and use a one-shot 3s fallback
832+ // timer for the no-services case where no further events would arrive.
833+ // All callbacks run on the single uv loop thread, so these flags need no locks.
834+ var contextAuthenticated = false
835+ var enrollmentCompleted = false
836+
837+ // Try to read the real identity and finish enrollment. Returns true if successful.
838+ let tryCompleteEnrollment : ( ) -> Bool = {
839+ guard !enrollmentCompleted else { return true }
840+ guard let ztx = ziti. ztx, let czid = ziti_get_identity ( ztx) , let cId = czid. pointee. id else {
841+ return false
842+ }
843+ enrollmentCompleted = true
844+ let realId = String ( cString: cId)
845+ log. info ( " \( modeLabel) identity id: \( realId) " , function: " runEnrollTo() " )
846+
847+ if mode == . cert && realId != tempKeychainTag {
848+ let zkc = ZitiKeychain ( tag: tempKeychainTag)
849+ if let zErr = zkc. retagPrivateKey ( to: realId) {
850+ log. error ( " \( modeLabel) \( zErr. localizedDescription) " , function: " runEnrollTo() " )
851+ } else {
852+ log. info ( " \( modeLabel) re-tagged keychain key to \( realId) " , function: " runEnrollTo() " )
853+ }
854+ }
855+ let finalId = ZitiIdentity ( id: realId,
856+ ztAPIs: ziti. id. ztAPIs ?? [ ziti. id. ztAPI] ,
857+ name: ziti. id. name,
858+ certs: ziti. id. certs,
859+ ca: ziti. id. ca)
860+ if let cName = czid. pointee. name {
861+ finalId. name = String ( cString: cName)
862+ }
863+ ziti. shutdown ( )
864+ enrollCallback ( finalId, nil )
865+ return true
866+ }
867+
830868 ziti. registerEventCallback ( { event in
831869 guard let event = event, let ziti = event. ziti else { return }
832- guard let ctxEvent = event. contextEvent else { return }
833- log. info ( " \( modeLabel) context event: \( event. type. debug) \( ctxEvent. err ?? " " ) status= \( ctxEvent. status) " , function: " runEnrollTo() " )
834-
835- if ctxEvent. status == 0 && enrollmentConfigReceived {
836- // Context authenticated - read the real identity ID from the controller
837- if let ztx = ziti. ztx, let czid = ziti_get_identity ( ztx) {
838- if let cId = czid. pointee. id {
839- let realId = String ( cString: cId)
840- log. info ( " \( modeLabel) identity id: \( realId) " , function: " runEnrollTo() " )
841-
842- // Re-tag keychain key from temp UUID to real identity ID
843- if mode == . cert && realId != tempKeychainTag {
844- let zkc = ZitiKeychain ( tag: tempKeychainTag)
845- if let zErr = zkc. retagPrivateKey ( to: realId) {
846- log. error ( " \( modeLabel) \( zErr. localizedDescription) " , function: " runEnrollTo() " )
847- } else {
848- log. info ( " \( modeLabel) re-tagged keychain key to \( realId) " , function: " runEnrollTo() " )
870+ guard !enrollmentCompleted else { return }
871+
872+ if event. type == . Context, let ctxEvent = event. contextEvent {
873+ log. info ( " \( modeLabel) context event: \( event. type. debug) \( ctxEvent. err ?? " " ) status= \( ctxEvent. status) " , function: " runEnrollTo() " )
874+ if ctxEvent. status == 0 && enrollmentConfigReceived {
875+ contextAuthenticated = true
876+ // Start a one-shot 3s fallback timer in case no further events arrive
877+ // (e.g. identity has no services). If identity is already available the
878+ // tryCompleteEnrollment call below handles it and the timer is a no-op.
879+ ziti. startTimer ( 3000 , 0 ) { timer in
880+ ziti. endTimer ( timer)
881+ if !enrollmentCompleted {
882+ log. info ( " \( modeLabel) identity fallback timer fired " , function: " runEnrollTo() " )
883+ if !tryCompleteEnrollment( ) {
884+ log. warn ( " \( modeLabel) identity not available after fallback, using temp ID " , function: " runEnrollTo() " )
885+ enrollmentCompleted = true
886+ ziti. shutdown ( )
887+ enrollCallback ( ziti. id, nil )
849888 }
850889 }
851- let finalId = ZitiIdentity ( id: realId,
852- ztAPIs: ziti. id. ztAPIs ?? [ ziti. id. ztAPI] ,
853- name: ziti. id. name,
854- certs: ziti. id. certs,
855- ca: ziti. id. ca)
856- if let cName = czid. pointee. name {
857- finalId. name = String ( cString: cName)
858- }
859- ziti. shutdown ( )
860- enrollCallback ( finalId, nil )
861- return
862890 }
891+ } else if ctxEvent. status != 0 {
892+ enrollmentCompleted = true
893+ let errMsg = ctxEvent. err ?? " context error (status \( ctxEvent. status) ) "
894+ log. error ( " \( modeLabel) context failed: \( errMsg) " , function: " runEnrollTo() " )
895+ ziti. shutdown ( )
896+ enrollCallback ( nil , ZitiError ( errMsg, errorCode: Int ( ctxEvent. status) ) )
897+ return
863898 }
864- // Fallback: no identity from controller, use what we have
865- ziti. shutdown ( )
866- enrollCallback ( ziti. id, nil )
867- } else if ctxEvent. status != 0 {
868- let errMsg = ctxEvent. err ?? " context error (status \( ctxEvent. status) ) "
869- log. error ( " \( modeLabel) context failed: \( errMsg) " , function: " runEnrollTo() " )
870- ziti. shutdown ( )
871- enrollCallback ( nil , ZitiError ( errMsg, errorCode: Int ( ctxEvent. status) ) )
872899 }
873- } , ZitiEvent . EventType. Context. rawValue)
900+
901+ // On every event after authentication, try to read the real identity ID.
902+ guard contextAuthenticated else { return }
903+ let _ = tryCompleteEnrollment ( )
904+ } , ZitiEvent . EventType. Context. rawValue | ZitiEvent . EventType. Service. rawValue)
874905
875906 ziti. runAsync { zErr in
876907 log. info ( " \( modeLabel) initCallback, error= \( String ( describing: zErr) ) " , function: " runEnrollTo() " )
0 commit comments