Skip to content

Commit 0445560

Browse files
abbraclaude
andcommitted
x509/crl: replace serial number linear scan with O(1) HashMap lookup
get_revoked_certificate_by_serial_number previously iterated over every revoked certificate on each call (O(n)). Build a HashMap<Vec<u8>, OwnedRevokedCertificate> on first call using the existing iterator infrastructure, then answer subsequent lookups in O(1). Also removes the now-unused try_map_crl_to_revoked_cert unsafe helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
1 parent 6f71190 commit 0445560

File tree

1 file changed

+39
-40
lines changed

1 file changed

+39
-40
lines changed

src/rust/src/x509/crl.rs

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
// for complete details.
44

5+
use std::collections::HashMap;
6+
57
use cryptography_x509::certificate::SerialNumber;
68
use cryptography_x509::common::{self, Asn1Read};
79
use cryptography_x509::crl::{
@@ -46,6 +48,7 @@ pub(crate) fn load_der_x509_crl(
4648
Ok(CertificateRevocationList {
4749
owned,
4850
revoked_certs: pyo3::sync::PyOnceLock::new(),
51+
serial_number_map: pyo3::sync::PyOnceLock::new(),
4952
cached_extensions: pyo3::sync::PyOnceLock::new(),
5053
cached_issuer: pyo3::sync::PyOnceLock::new(),
5154
cached_signature_algorithm_oid: pyo3::sync::PyOnceLock::new(),
@@ -87,6 +90,7 @@ pub(crate) struct CertificateRevocationList {
8790
owned: OwnedCertificateRevocationList,
8891

8992
revoked_certs: pyo3::sync::PyOnceLock<Vec<OwnedRevokedCertificate>>,
93+
serial_number_map: pyo3::sync::PyOnceLock<HashMap<Vec<u8>, OwnedRevokedCertificate>>,
9094
cached_extensions: pyo3::sync::PyOnceLock<pyo3::Py<pyo3::PyAny>>,
9195
cached_issuer: pyo3::sync::PyOnceLock<pyo3::Py<pyo3::PyAny>>,
9296
cached_signature_algorithm_oid: pyo3::sync::PyOnceLock<pyo3::Py<pyo3::PyAny>>,
@@ -417,21 +421,43 @@ impl CertificateRevocationList {
417421
) -> pyo3::PyResult<Option<RevokedCertificate>> {
418422
let serial_bytes = py_uint_to_big_endian_bytes(py, serial)?;
419423

420-
// Use try_map_crl_to_revoked_cert to soundly extract the certificate
421-
let owned = try_map_crl_to_revoked_cert(&self.owned, py, |crl| {
422-
let certs = crl.tbs_cert_list.revoked_certificates.as_ref()?;
423-
424-
// TODO: linear scan. Make a hash or bisect!
425-
certs
426-
.unwrap_read()
427-
.clone()
428-
.find(|cert| serial_bytes == cert.user_certificate.as_bytes())
424+
let map = self.serial_number_map.get_or_init(py, || {
425+
let mut map = HashMap::new();
426+
let mut it_data = map_crl_to_iterator_data(&self.owned, py, |crl| {
427+
crl.tbs_cert_list
428+
.revoked_certificates
429+
.as_ref()
430+
.map(|v| v.unwrap_read().clone())
431+
});
432+
loop {
433+
let revoked = try_map_arc_data_mut_crl_iterator(py, &mut it_data, |v| match v {
434+
Some(v) => match v.next() {
435+
Some(revoked) => Ok(revoked),
436+
None => Err(()),
437+
},
438+
None => Err(()),
439+
});
440+
match revoked {
441+
Ok(owned) => {
442+
let key = owned
443+
.borrow_dependent()
444+
.user_certificate
445+
.as_bytes()
446+
.to_vec();
447+
map.insert(key, owned);
448+
}
449+
Err(()) => break,
450+
}
451+
}
452+
map
429453
});
430454

431-
Ok(owned.map(|o| RevokedCertificate {
432-
owned: o,
433-
cached_extensions: pyo3::sync::PyOnceLock::new(),
434-
}))
455+
Ok(map
456+
.get(serial_bytes.as_ref())
457+
.map(|owned| RevokedCertificate {
458+
owned: owned.clone_with_py(py),
459+
cached_extensions: pyo3::sync::PyOnceLock::new(),
460+
}))
435461
}
436462

437463
fn is_signature_valid<'p>(
@@ -500,33 +526,6 @@ where
500526
})
501527
}
502528

503-
// Open-coded implementation of the API discussed in
504-
// https://github.com/joshua-maros/ouroboros/issues/38
505-
fn try_map_crl_to_revoked_cert<F>(
506-
source: &OwnedCertificateRevocationList,
507-
py: pyo3::Python<'_>,
508-
f: F,
509-
) -> Option<OwnedRevokedCertificate>
510-
where
511-
F: for<'a> FnOnce(&'a RawCertificateRevocationList<'a>) -> Option<RawRevokedCertificate<'a>>,
512-
{
513-
OwnedRevokedCertificate::try_new(source.borrow_owner().clone_ref(py), |_| {
514-
// SAFETY: This is safe because cloning the PyBytes Py<> ensures the data is
515-
// alive, but Rust doesn't understand the lifetime relationship it
516-
// produces.
517-
match f(unsafe {
518-
std::mem::transmute::<
519-
&RawCertificateRevocationList<'_>,
520-
&RawCertificateRevocationList<'_>,
521-
>(source.borrow_dependent())
522-
}) {
523-
Some(cert) => Ok(cert),
524-
None => Err(()),
525-
}
526-
})
527-
.ok()
528-
}
529-
530529
// Open-coded implementation of the API discussed in
531530
// https://github.com/joshua-maros/ouroboros/issues/38
532531
fn map_revoked_cert<F>(

0 commit comments

Comments
 (0)