rustls-sni-resolver 0.0.1

SNI-keyed certificate map implementing rustls::ResolvesServerCert, designed for hot-reload via ArcSwap.
Documentation
# Rustls SNI Resolver

A minimal `ResolvesServerCert` implementation backed by
`{ by_sni: HashMap<String, Arc<E>>, default: Option<Arc<E>> }`, with
the whole struct designed to live behind an `Arc<ArcSwap<_>>` so a
config reload is one atomic pointer swap.

`E` is generic over the [`EntryKey`] trait, so callers can attach
their own per-cert state (expiry timestamps, OCSP staple handles,
ACME order IDs, …) without forking the resolver.

rustls's built-in `ResolvesServerCertUsingSni` returns `None` on
unmatched SNI with no built-in fallback hook — every operator-facing
TLS service ends up writing this small "with a default" variant by
hand.

## Example

```rust,no_run
use std::sync::Arc;
use arc_swap::ArcSwap;
use rustls_sni_resolver::{CertStore, EntryKey, Resolver};

#[derive(Debug)]
struct MyEntry {
    key: Arc<rustls::sign::CertifiedKey>,
    not_after: std::time::SystemTime,
}

impl EntryKey for MyEntry {
    fn key(&self) -> Arc<rustls::sign::CertifiedKey> {
        Arc::clone(&self.key)
    }
}

# fn run(api_entry: Arc<MyEntry>, default_entry: Arc<MyEntry>) {
let mut store: CertStore<MyEntry> = CertStore::new();
store.by_sni.insert("api.example.com".into(), api_entry);
store.default = Some(default_entry);

let store = Arc::new(ArcSwap::from_pointee(store));
let resolver: Arc<dyn rustls::server::ResolvesServerCert> =
    Arc::new(Resolver::new(store.clone()));

// Later, on reload, swap atomically:
let mut fresh: CertStore<MyEntry> = CertStore::new();
// ... populate fresh ...
store.store(Arc::new(fresh));
# }
```

## Lookup semantics

`CertStore::lookup(Option<&str>)` returns:

- the entry under the matching SNI key, if one exists;
- otherwise the `default` entry, if one is set;
- otherwise `None`.

Because rustls already ASCII-lowercases the `server_name` per RFC 6066
§ 3, populators should also store `by_sni` keys in lowercase. This
crate does **not** lowercase on insert — callers own that invariant
(typical populators read keys from configuration that has already been
normalized at parse time).

## License

Released under the MIT License © 2026 [Canmi](https://canmi.net)