@@ -121,8 +121,7 @@ struct IssuerStats {
121121 std::atomic<uint64_t > successful_validations{0 };
122122 std::atomic<uint64_t > unsuccessful_validations{0 };
123123 std::atomic<uint64_t > expired_tokens{0 };
124- std::atomic<uint64_t > total_time_ns{0 }; // Total time in nanoseconds
125- std::atomic<uint64_t > validation_count{0 }; // For computing average
124+ std::atomic<double > total_time_s{0.0 }; // Total time in seconds
126125};
127126
128127/* *
@@ -134,10 +133,13 @@ class MonitoringStats {
134133 public:
135134 static MonitoringStats &instance ();
136135
136+ // Get a reference to issuer stats for periodic updates
137+ IssuerStats* get_issuer_stats (const std::string &issuer);
138+
137139 void record_validation_success (const std::string &issuer,
138- uint64_t duration_ns );
140+ double duration_s );
139141 void record_validation_failure (const std::string &issuer,
140- uint64_t duration_ns );
142+ double duration_s );
141143 void record_expired_token (const std::string &issuer);
142144 void record_failed_issuer_lookup (const std::string &issuer);
143145
@@ -180,6 +182,17 @@ class CurlException : public std::runtime_error {
180182 explicit CurlException (const std::string &msg) : std::runtime_error(msg) {}
181183};
182184
185+ class IssuerLookupException : public CurlException {
186+ public:
187+ explicit IssuerLookupException (const std::string &msg) : CurlException(msg) {}
188+ };
189+
190+ class TokenExpiredException : public JWTVerificationException {
191+ public:
192+ explicit TokenExpiredException (const std::string &msg)
193+ : JWTVerificationException(msg) {}
194+ };
195+
183196class MissingIssuerException : public std ::runtime_error {
184197 public:
185198 MissingIssuerException ()
@@ -473,21 +486,39 @@ class Validator {
473486 void verify (const SciToken &scitoken, time_t expiry_time) {
474487 std::string issuer = " " ;
475488 auto start_time = std::chrono::steady_clock::now ();
476- bool has_issuer = false ;
489+ internal::IssuerStats* stats_ptr = nullptr ;
477490
478491 try {
479- // Try to extract issuer for monitoring
480- if (scitoken.m_decoded && scitoken.m_decoded ->has_payload_claim (" iss" )) {
481- issuer = scitoken.m_decoded ->get_issuer ();
482- has_issuer = true ;
492+ auto result = verify_async (scitoken);
493+
494+ // Extract issuer from the result's JWT string after decoding starts
495+ const jwt::decoded_jwt<jwt::traits::kazuho_picojson> *jwt_decoded =
496+ scitoken.m_decoded .get ();
497+ if (jwt_decoded && jwt_decoded->has_payload_claim (" iss" )) {
498+ issuer = jwt_decoded->get_issuer ();
499+ stats_ptr = internal::MonitoringStats::instance ().get_issuer_stats (issuer);
483500 }
484501
485- auto result = verify_async (scitoken);
486502 while (!result->m_done ) {
487503 auto timeout_val = result->get_timeout_val (expiry_time);
504+ // Limit select to 50ms for periodic updates
505+ if (timeout_val.tv_sec > 0 || timeout_val.tv_usec > 50000 ) {
506+ timeout_val.tv_sec = 0 ;
507+ timeout_val.tv_usec = 50000 ;
508+ }
509+
488510 select (result->get_max_fd () + 1 , result->get_read_fd_set (),
489511 result->get_write_fd_set (), result->get_exc_fd_set (),
490512 &timeout_val);
513+
514+ // Update elapsed time periodically
515+ if (stats_ptr) {
516+ auto current_time = std::chrono::steady_clock::now ();
517+ auto duration = std::chrono::duration_cast<std::chrono::duration<double >>(
518+ current_time - start_time);
519+ stats_ptr->total_time_s = duration.count ();
520+ }
521+
491522 if (time (NULL ) >= expiry_time) {
492523 throw CurlException (" Timeout when loading the OIDC metadata." );
493524 }
@@ -496,17 +527,20 @@ class Validator {
496527 }
497528
498529 // Record successful validation
499- if (has_issuer ) {
530+ if (!issuer. empty () ) {
500531 auto end_time = std::chrono::steady_clock::now ();
501- auto duration = std::chrono::duration_cast<std::chrono::nanoseconds >(
532+ auto duration = std::chrono::duration_cast<std::chrono::duration< double > >(
502533 end_time - start_time);
503534 internal::MonitoringStats::instance ().record_validation_success (
504535 issuer, duration.count ());
505536 }
506537 } catch (const std::exception &e) {
507538 // Record failure if we have an issuer
508- if (has_issuer) {
509- record_validation_error (issuer, e, start_time);
539+ if (!issuer.empty ()) {
540+ auto end_time = std::chrono::steady_clock::now ();
541+ auto duration = std::chrono::duration_cast<std::chrono::duration<double >>(
542+ end_time - start_time);
543+ record_validation_error (issuer, e, duration.count ());
510544 }
511545 throw ;
512546 }
@@ -515,13 +549,11 @@ class Validator {
515549 void verify (const jwt::decoded_jwt<jwt::traits::kazuho_picojson> &jwt) {
516550 std::string issuer = " " ;
517551 auto start_time = std::chrono::steady_clock::now ();
518- bool has_issuer = false ;
519552
520553 try {
521554 // Try to extract issuer for monitoring
522555 if (jwt.has_payload_claim (" iss" )) {
523556 issuer = jwt.get_issuer ();
524- has_issuer = true ;
525557 }
526558
527559 auto result = verify_async (jwt);
@@ -530,17 +562,20 @@ class Validator {
530562 }
531563
532564 // Record successful validation
533- if (has_issuer ) {
565+ if (!issuer. empty () ) {
534566 auto end_time = std::chrono::steady_clock::now ();
535- auto duration = std::chrono::duration_cast<std::chrono::nanoseconds >(
567+ auto duration = std::chrono::duration_cast<std::chrono::duration< double > >(
536568 end_time - start_time);
537569 internal::MonitoringStats::instance ().record_validation_success (
538570 issuer, duration.count ());
539571 }
540572 } catch (const std::exception &e) {
541573 // Record failure if we have an issuer
542- if (has_issuer) {
543- record_validation_error (issuer, e, start_time);
574+ if (!issuer.empty ()) {
575+ auto end_time = std::chrono::steady_clock::now ();
576+ auto duration = std::chrono::duration_cast<std::chrono::duration<double >>(
577+ end_time - start_time);
578+ record_validation_error (issuer, e, duration.count ());
544579 }
545580 throw ;
546581 }
@@ -652,7 +687,17 @@ class Validator {
652687
653688 const jwt::decoded_jwt<jwt::traits::kazuho_picojson> jwt (
654689 status->m_jwt_string );
655- verifier.verify (jwt);
690+ try {
691+ verifier.verify (jwt);
692+ } catch (const std::exception &e) {
693+ // Check if this is an expiration error from jwt-cpp
694+ std::string error_msg = e.what ();
695+ if (error_msg.find (" exp" ) != std::string::npos ||
696+ error_msg.find (" expir" ) != std::string::npos) {
697+ throw TokenExpiredException (error_msg);
698+ }
699+ throw ;
700+ }
656701
657702 bool must_verify_everything = true ;
658703 if (jwt.has_payload_claim (" ver" )) {
@@ -927,29 +972,18 @@ class Validator {
927972 */
928973 void record_validation_error (const std::string &issuer,
929974 const std::exception &e,
930- const std::chrono::steady_clock::time_point &start_time) {
931- auto end_time = std::chrono::steady_clock::now ();
932- auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
933- end_time - start_time);
934-
935- std::string error_msg = e.what ();
936-
937- // Check if this is a failed issuer lookup (network/DNS errors)
938- if (error_msg.find (" resolve" ) != std::string::npos ||
939- error_msg.find (" host" ) != std::string::npos ||
940- error_msg.find (" network" ) != std::string::npos ||
941- error_msg.find (" Failed to retrieve" ) != std::string::npos) {
975+ double duration_s) {
976+ // Check exception type instead of string introspection
977+ if (dynamic_cast <const IssuerLookupException*>(&e)) {
942978 internal::MonitoringStats::instance ().record_failed_issuer_lookup (issuer);
943979 }
944980
945- // Check if this is an expiration error
946- if (error_msg.find (" exp" ) != std::string::npos ||
947- error_msg.find (" expir" ) != std::string::npos) {
981+ if (dynamic_cast <const TokenExpiredException*>(&e)) {
948982 internal::MonitoringStats::instance ().record_expired_token (issuer);
949983 }
950984
951985 internal::MonitoringStats::instance ().record_validation_failure (
952- issuer, duration. count () );
986+ issuer, duration_s );
953987 }
954988
955989 bool m_validate_all_claims{true };
0 commit comments