use std::borrow::Cow;
use std::str;
use anyhow::Context;
use sequoia_openpgp as openpgp;
use openpgp::Fingerprint;
use openpgp::KeyHandle;
use openpgp::Result;
use openpgp::cert::Cert;
use openpgp::cert::ValidCert;
use openpgp::packet::UserID;
mod userid_index;
pub use userid_index::UserIDIndex;
pub mod certd;
pub use certd::CertD;
pub mod certs;
pub use certs::Certs;
pub mod keyserver;
pub use keyserver::KeyServer;
pub mod pep;
pub use pep::Pep;
use super::TRACE;
use crate::LazyCert;
#[derive(Debug, Clone)]
pub struct UserIDQueryParams {
anchor_start: bool,
anchor_end: bool,
email: bool,
ignore_case: bool,
}
impl UserIDQueryParams {
pub fn new() -> Self {
Self {
anchor_start: true,
anchor_end: true,
email: false,
ignore_case: false,
}
}
pub fn set_anchor_start(&mut self, anchor_start: bool) -> &mut Self {
self.anchor_start = anchor_start;
self
}
pub fn anchor_start(&self) -> bool {
self.anchor_start
}
pub fn set_anchor_end(&mut self, anchor_end: bool) -> &mut Self {
self.anchor_end = anchor_end;
self
}
pub fn anchor_end(&self) -> bool {
self.anchor_end
}
pub fn set_email(&mut self, email: bool) -> &mut Self {
self.email = email;
self
}
pub fn email(&self) -> bool {
self.email
}
pub fn set_ignore_case(&mut self, ignore_case: bool) -> &mut Self {
self.ignore_case = ignore_case;
self
}
pub fn ignore_case(&self) -> bool {
self.ignore_case
}
pub fn check(&self, userid: &UserID, pattern: &str) -> bool {
tracer!(TRACE, "UserIDQueryParams::check");
match self {
UserIDQueryParams {
anchor_start: true,
anchor_end: true,
email: false,
ignore_case: false,
} => {
userid.value() == pattern.as_bytes()
}
UserIDQueryParams {
anchor_start: true,
anchor_end: true,
email: true,
ignore_case: false,
} => {
if let Ok(Some(email)) = userid.email_normalized() {
email == pattern
} else {
false
}
}
UserIDQueryParams {
anchor_start,
anchor_end,
email,
ignore_case,
} => {
t!("Considering if {:?} matches {:?} \
(anchors: {}, {}, ignore case: {})",
pattern, userid, anchor_start, anchor_end, ignore_case);
let mut userid = if *email {
if let Ok(Some(email)) = userid.email_normalized() {
email
} else {
t!("User ID does not contain a valid email address");
return false;
}
} else {
if let Ok(userid)
= String::from_utf8(userid.value().to_vec())
{
userid
} else {
t!("User ID is not UTF-8 encoded");
return false;
}
};
if *ignore_case {
userid = userid.to_lowercase();
}
let mut pattern = pattern;
let _pattern;
if *ignore_case {
_pattern = pattern.to_lowercase();
pattern = &_pattern[..];
}
if match (*anchor_start, *anchor_end) {
(true, true) => userid == pattern,
(true, false) => userid.starts_with(pattern),
(false, true) => userid.ends_with(pattern),
(false, false) => userid.contains(pattern),
}
{
t!("*** {:?} matches {:?} (anchors: {}, {})",
pattern, userid, *anchor_start, anchor_end);
true
} else {
false
}
}
}
}
pub fn check_lazy_cert(&self, cert: &LazyCert, pattern: &str) -> bool {
cert.userids().any(|userid| self.check(&userid, pattern))
}
pub fn check_cert(&self, cert: &Cert, pattern: &str) -> bool {
cert.userids().any(|ua| self.check(ua.userid(), pattern))
}
pub fn check_valid_cert(&self, vc: &ValidCert, pattern: &str) -> bool {
vc.userids().any(|ua| self.check(ua.userid(), pattern))
}
pub fn is_email(email: &str) -> Result<String> {
let email_check = UserID::from(format!("<{}>", email));
match email_check.email() {
Ok(Some(email_check)) => {
if email != email_check {
return Err(StoreError::InvalidEmail(
email.to_string(), None).into());
}
}
Ok(None) => {
return Err(StoreError::InvalidEmail(
email.to_string(), None).into());
}
Err(err) => {
return Err(StoreError::InvalidEmail(
email.to_string(), Some(err)).into());
}
}
match UserID::from(&email[..]).email_normalized() {
Err(err) => {
Err(StoreError::InvalidEmail(
email.to_string(), Some(err)).into())
}
Ok(None) => {
Err(StoreError::InvalidEmail(
email.to_string(), None).into())
}
Ok(Some(email)) => {
Ok(email)
}
}
}
pub fn is_domain(domain: &str) -> Result<String> {
let localpart = "user@";
let email = format!("{}{}", localpart, domain);
let email = Self::is_email(&email)?;
assert!(email.starts_with(localpart));
Ok(email[localpart.len()..].to_string())
}
}
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum StoreError {
#[error("{0} was not found")]
NotFound(KeyHandle),
#[error("No certificates matched {0}")]
NoMatches(String),
#[error("{0:?} does not appear to be a valid email address")]
InvalidEmail(String, #[source] Option<anyhow::Error>),
}
#[non_exhaustive]
#[derive(Debug)]
pub enum StatusUpdate<'a, 'c: 'rc, 'rc> {
LookupStarted(usize, &'a str, &'a KeyHandle, Option<&'a str>),
LookupStatus(usize, &'a str, &'a KeyHandle, &'a str),
LookupFinished(usize, &'a str, &'a KeyHandle,
&'a [Cow<'rc, LazyCert<'c>>], Option<&'a str>),
LookupFailed(usize, &'a str, &'a KeyHandle, Option<&'a anyhow::Error>),
SearchStarted(usize, &'a str, &'a str, Option<&'a str>),
SearchStatus(usize, &'a str, &'a str, &'a str),
SearchFinished(usize, &'a str, &'a str, &'a [Cow<'rc, LazyCert<'c>>],
Option<&'a str>),
SearchFailed(usize, &'a str, &'a str, Option<&'a anyhow::Error>),
}
impl<'a, 'c: 'rc, 'rc> std::fmt::Display for StatusUpdate<'a, 'c, 'rc> {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>)
-> std::result::Result<(), std::fmt::Error>
{
use StatusUpdate::*;
match self {
LookupStarted(_tx, backend, kh, msg) => {
if let Some(msg) = msg {
write!(fmt, "{}: Looking up {}: {}...",
backend, kh, msg)
} else {
write!(fmt, "{}: Looking up {}...",
backend, kh)
}
}
LookupStatus(_tx, backend, kh, msg) => {
write!(fmt, "{}: Looking up {}: {}",
backend, kh, msg)
}
LookupFinished(_tx, backend, kh, results, msg) => {
if let Some(msg) = msg {
write!(fmt, "{}: Looking up {}, returned {} results: {}",
backend, kh, results.len(), msg)
} else {
write!(fmt, "{}: Looking up {}, returned {} results",
backend, kh, results.len())
}
}
LookupFailed(_tx, backend, kh, err) => {
if let Some(err) = err {
write!(fmt, "{}: Looking up {}, failed: {}",
backend, kh, err)
} else {
write!(fmt, "{}: Looking up {}, returned no results",
backend, kh)
}
}
SearchStarted(_tx, backend, pattern, msg) => {
if let Some(msg) = msg {
write!(fmt, "{}: Searching for {:?}: {}...",
backend, pattern, msg)
} else {
write!(fmt, "{}: Searching for {:?}...",
backend, pattern)
}
}
SearchStatus(_tx, backend, pattern, msg) => {
write!(fmt, "{}: Searching for {:?}: {}",
backend, pattern, msg)
}
SearchFinished(_tx, backend, pattern, results, msg) => {
if let Some(msg) = msg {
write!(fmt, "{}: Searching for {:?}, returned {} results: {}",
backend, pattern, results.len(), msg)
} else {
write!(fmt, "{}: Searching for {:?}, returned {} results",
backend, pattern, results.len())
}
}
SearchFailed(_tx, backend, pattern, err) => {
if let Some(err) = err {
write!(fmt, "{}: Searching for {:?} failed: {}",
backend, pattern, err)
} else {
write!(fmt, "{}: Searching for {:?}, returned no results",
backend, pattern)
}
}
}
}
}
pub trait StatusListener {
fn update(&self, status: &StatusUpdate);
}
pub trait Store<'a> {
fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Cow<LazyCert<'a>>>>;
fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
-> Result<Cow<LazyCert<'a>>>
{
let kh = KeyHandle::from(fingerprint.clone());
self.lookup_by_cert(&kh)
.and_then(|v| {
assert!(v.len() <= 1);
v.into_iter().next()
.ok_or(StoreError::NotFound(kh).into())
})
}
fn lookup_by_key(&self, kh: &KeyHandle) -> Result<Vec<Cow<LazyCert<'a>>>>;
fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
-> Result<Vec<Cow<LazyCert<'a>>>>;
fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.select_userid(
&UserIDQueryParams::new()
.set_email(false)
.set_anchor_start(true)
.set_anchor_end(true)
.set_ignore_case(false),
&String::from_utf8(userid.value().to_vec())?)
}
fn grep_userid(&self, pattern: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.select_userid(
&UserIDQueryParams::new()
.set_email(false)
.set_anchor_start(false)
.set_anchor_end(false)
.set_ignore_case(true),
pattern)
}
fn lookup_by_email(&self, email: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
let userid = crate::email_to_userid(&email)?;
let email = userid.email_normalized()?.expect("have one");
self.select_userid(
&UserIDQueryParams::new()
.set_email(true)
.set_anchor_start(true)
.set_anchor_end(true)
.set_ignore_case(false),
&email)
}
fn grep_email(&self, pattern: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.select_userid(
&UserIDQueryParams::new()
.set_email(true)
.set_anchor_start(false)
.set_anchor_end(false)
.set_ignore_case(true),
pattern)
}
fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
let localpart = "localpart";
let email = format!("{}@{}", localpart, domain);
let userid = crate::email_to_userid(&email)?;
let email = userid.email_normalized()?.expect("have one");
let domain = &email[email.rfind('@').expect("have an @")..];
self.select_userid(
&UserIDQueryParams::new()
.set_email(true)
.set_anchor_start(false)
.set_anchor_end(true)
.set_ignore_case(false),
domain)
}
fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b>;
fn certs<'b>(&'b self)
-> Box<dyn Iterator<Item=Cow<'b, LazyCert<'a>>> + 'b>
where 'a: 'b
{
Box::new(self.fingerprints()
.filter_map(|fpr| {
self.lookup_by_cert_fpr(&fpr).ok()
}))
}
fn prefetch_all(&mut self) {
}
fn prefetch_some(&mut self, certs: Vec<KeyHandle>) {
let _ = certs;
}
}
impl<'a: 't, 't, T> Store<'a> for Box<T>
where T: Store<'a> + ?Sized + 't
{
fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.as_ref().lookup_by_cert(kh)
}
fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
-> Result<Cow<LazyCert<'a>>>
{
self.as_ref().lookup_by_cert_fpr(fingerprint)
}
fn lookup_by_key(&self, kh: &KeyHandle) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.as_ref().lookup_by_key(kh)
}
fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
-> Result<Vec<Cow<LazyCert<'a>>>>
{
self.as_ref().select_userid(query, pattern)
}
fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.as_ref().lookup_by_userid(userid)
}
fn grep_userid(&self, pattern: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.as_ref().grep_userid(pattern)
}
fn lookup_by_email(&self, email: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.as_ref().lookup_by_email(email)
}
fn grep_email(&self, pattern: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.as_ref().grep_email(pattern)
}
fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
self.as_ref().lookup_by_email_domain(domain)
}
fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
self.as_ref().fingerprints()
}
fn certs<'b>(&'b self)
-> Box<dyn Iterator<Item=Cow<'b, LazyCert<'a>>> + 'b>
where 'a: 'b
{
self.as_ref().certs()
}
fn prefetch_all(&mut self) {
self.as_ref().prefetch_all()
}
fn prefetch_some(&mut self, certs: Vec<KeyHandle>) {
self.as_ref().prefetch_some(certs)
}
}
impl<'a: 't, 't, T> Store<'a> for &'t T
where T: Store<'a> + ?Sized
{
fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Cow<LazyCert<'a>>>> {
(*self).lookup_by_cert(kh)
}
fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
-> Result<Cow<LazyCert<'a>>>
{
(*self).lookup_by_cert_fpr(fingerprint)
}
fn lookup_by_key(&self, kh: &KeyHandle) -> Result<Vec<Cow<LazyCert<'a>>>> {
(*self).lookup_by_key(kh)
}
fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
-> Result<Vec<Cow<LazyCert<'a>>>>
{
(*self).select_userid(query, pattern)
}
fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Cow<LazyCert<'a>>>> {
(*self).lookup_by_userid(userid)
}
fn grep_userid(&self, pattern: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
(*self).grep_userid(pattern)
}
fn lookup_by_email(&self, email: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
(*self).lookup_by_email(email)
}
fn grep_email(&self, pattern: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
(*self).grep_email(pattern)
}
fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
(*self).lookup_by_email_domain(domain)
}
fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
(*self).fingerprints()
}
fn certs<'b>(&'b self)
-> Box<dyn Iterator<Item=Cow<'b, LazyCert<'a>>> + 'b>
where 'a: 'b
{
(*self).certs()
}
}
impl<'a: 't, 't, T> Store<'a> for &'t mut T
where T: Store<'a> + ?Sized
{
fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Cow<LazyCert<'a>>>> {
(**self).lookup_by_cert(kh)
}
fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
-> Result<Cow<LazyCert<'a>>>
{
(**self).lookup_by_cert_fpr(fingerprint)
}
fn lookup_by_key(&self, kh: &KeyHandle) -> Result<Vec<Cow<LazyCert<'a>>>> {
(**self).lookup_by_key(kh)
}
fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
-> Result<Vec<Cow<LazyCert<'a>>>>
{
(**self).select_userid(query, pattern)
}
fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Cow<LazyCert<'a>>>> {
(**self).lookup_by_userid(userid)
}
fn grep_userid(&self, pattern: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
(**self).grep_userid(pattern)
}
fn lookup_by_email(&self, email: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
(**self).lookup_by_email(email)
}
fn grep_email(&self, pattern: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
(**self).grep_email(pattern)
}
fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Cow<LazyCert<'a>>>> {
(**self).lookup_by_email_domain(domain)
}
fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
(**self).fingerprints()
}
fn certs<'b>(&'b self)
-> Box<dyn Iterator<Item=Cow<'b, LazyCert<'a>>> + 'b>
where 'a: 'b
{
(**self).certs()
}
fn prefetch_all(&mut self) {
(**self).prefetch_all()
}
fn prefetch_some(&mut self, certs: Vec<KeyHandle>) {
(**self).prefetch_some(certs)
}
}
pub trait MergeCerts<'a: 'ra, 'ra> {
fn merge_public<'b, 'rb>(&mut self,
new: Cow<'ra, LazyCert<'a>>,
disk: Option<Cow<'rb, LazyCert<'b>>>)
-> Result<Cow<'ra, LazyCert<'a>>>
{
if let Some(disk) = disk {
let merged = disk.as_cert()?
.merge_public(new.into_owned().into_cert()?)?;
Ok(Cow::Owned(LazyCert::from(merged)))
} else {
if new.is_tsk() {
Ok(Cow::Owned(LazyCert::from(new.into_owned().into_cert()?
.strip_secret_key_material())))
} else {
Ok(new)
}
}
}
}
impl<'a: 'ra, 'ra> MergeCerts<'a, 'ra> for () {
}
pub trait StoreUpdate<'a>: Store<'a> {
fn update(&mut self, cert: Cow<LazyCert<'a>>) -> Result<()> {
self.update_by(cert, &mut ())?;
Ok(())
}
fn update_by<'ra>(&'ra mut self, cert: Cow<'ra, LazyCert<'a>>,
merge_strategy: &mut dyn MergeCerts<'a, 'ra>)
-> Result<Cow<'ra, LazyCert<'a>>>;
}
impl<'a: 't, 't, T> StoreUpdate<'a> for Box<T>
where T: StoreUpdate<'a> + ?Sized + 't
{
fn update(&mut self, cert: Cow<LazyCert<'a>>) -> Result<()> {
self.as_mut().update(cert)
}
fn update_by<'ra>(&'ra mut self, cert: Cow<'ra, LazyCert<'a>>,
merge_strategy: &mut dyn MergeCerts<'a, 'ra>)
-> Result<Cow<'ra, LazyCert<'a>>>
{
self.as_mut().update_by(cert, merge_strategy)
}
}
impl<'a: 't, 't, T> StoreUpdate<'a> for &'t mut T
where T: StoreUpdate<'a> + ?Sized
{
fn update(&mut self, cert: Cow<LazyCert<'a>>) -> Result<()> {
(*self).update(cert)
}
fn update_by<'ra>(&'ra mut self, cert: Cow<'ra, LazyCert<'a>>,
merge_strategy: &mut dyn MergeCerts<'a, 'ra>)
-> Result<Cow<'ra, LazyCert<'a>>>
{
(*self).update_by(cert, merge_strategy)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct MergePublicCollectStats {
pub new: usize,
pub unchanged: usize,
pub updated: usize,
pub errors: usize,
}
impl MergePublicCollectStats {
pub fn new() -> Self {
Self {
new: 0,
unchanged: 0,
updated: 0,
errors: 0,
}
}
}
impl<'a: 'ra, 'ra> MergeCerts<'a, 'ra> for MergePublicCollectStats {
fn merge_public<'b, 'rb>(&mut self,
new: Cow<'ra, LazyCert<'a>>,
disk: Option<Cow<'rb, LazyCert<'b>>>)
-> Result<Cow<'ra, LazyCert<'a>>>
{
let disk = if let Some(disk) = disk {
disk
} else {
self.new += 1;
if new.is_tsk() {
return Ok(Cow::Owned(LazyCert::from(
new.into_owned().into_cert()?.strip_secret_key_material())))
} else {
return Ok(new);
}
};
let fpr = new.fingerprint();
let disk = disk.into_owned().as_cert()
.with_context(|| {
format!("Parsing {} as returned from the cert directory", fpr)
})?;
let new = new.into_owned().as_cert()
.with_context(|| {
format!("Parsing {} as being inserted into \
the cert directory",
fpr)
})?;
if disk == new {
self.unchanged += 1;
Ok(Cow::Owned(LazyCert::from(new)))
} else {
match disk.merge_public(new) {
Ok(merged) => {
self.updated += 1;
Ok(Cow::Owned(LazyCert::from(merged)))
}
Err(err) => {
self.errors += 1;
Err(err.into())
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::store;
#[test]
fn store_boxed() -> Result<()> {
struct Foo<'a, B>
where B: Store<'a>
{
backend: B,
_a: std::marker::PhantomData<&'a ()>,
}
impl<'a, B> Foo<'a, B>
where B: Store<'a>
{
fn new(backend: B) -> Self
{
Foo {
backend,
_a: std::marker::PhantomData,
}
}
fn count(&self) -> usize {
self.backend.certs().count()
}
}
let backend = store::Certs::empty();
let backend: Box<dyn Store> = Box::new(backend);
let foo = Foo::new(&backend);
assert_eq!(foo.count(), 0);
Ok(())
}
#[test]
fn store_update_boxed() -> Result<()> {
struct Foo<'a, B>
where B: StoreUpdate<'a>
{
backend: B,
_a: std::marker::PhantomData<&'a ()>,
}
impl<'a, B> Foo<'a, B>
where B: StoreUpdate<'a>
{
fn new(backend: B) -> Self
{
Foo {
backend,
_a: std::marker::PhantomData,
}
}
fn count(&self) -> usize {
self.backend.certs().count()
}
}
let backend = store::Certs::empty();
let mut backend: Box<dyn StoreUpdate> = Box::new(backend);
let foo = Foo::new(&mut backend);
assert_eq!(foo.count(), 0);
Ok(())
}
#[test]
fn is_email() {
assert!(UserIDQueryParams::is_email("foo@domain.com").is_ok());
assert!(UserIDQueryParams::is_email("@domain.com").is_err());
assert!(UserIDQueryParams::is_email("foo@").is_err());
assert!(UserIDQueryParams::is_email("foo").is_err());
assert!(UserIDQueryParams::is_email("foo@@domain.com").is_err());
assert!(UserIDQueryParams::is_email("foo@a@domain.com").is_err());
assert!(UserIDQueryParams::is_email("<foo@domain.com>").is_err());
assert!(UserIDQueryParams::is_email(" foo@domain.com").is_err());
assert!(UserIDQueryParams::is_email("foo o@domain.com").is_err());
assert!(UserIDQueryParams::is_email("foo@do main.com").is_err());
assert!(UserIDQueryParams::is_email("foo@domain.com ").is_err());
}
#[test]
fn is_domain() {
assert!(UserIDQueryParams::is_domain("domain.com").is_ok());
assert!(UserIDQueryParams::is_domain("foo").is_ok());
assert!(UserIDQueryParams::is_domain("@domain.com").is_err());
assert!(UserIDQueryParams::is_domain("foo@").is_err());
assert!(UserIDQueryParams::is_domain("foo@@domain.com").is_err());
assert!(UserIDQueryParams::is_domain("foo@a@domain.com").is_err());
assert!(UserIDQueryParams::is_domain("<foo@domain.com>").is_err());
}
include!("../tests/keyring.rs");
#[test]
fn store_update_merge_public_collect_stats() {
use std::borrow::Cow;
use std::collections::HashSet;
use openpgp::Cert;
use openpgp::parse::Parse;
use crate::CertStore;
use crate::store::MergePublicCollectStats;
assert_eq!(keyring::certs.len(), 12);
let mut certs = CertStore::empty();
let mut stats = MergePublicCollectStats::new();
let mut seen = HashSet::new();
for (i, cert) in keyring::certs.iter().enumerate() {
let cert = Cert::from_bytes(&cert.bytes()).expect("valid");
let fpr = cert.fingerprint();
seen.insert(fpr.clone());
certs.update_by(Cow::Owned(LazyCert::from(cert)), &mut stats)
.expect("valid");
eprintln!("After inserting {} ({}), stats: {:?}",
i, fpr, stats);
assert_eq!(stats.new, seen.len());
assert_eq!(stats.new + stats.updated + stats.unchanged, i + 1);
}
let new = stats.new;
let updated = stats.updated;
let unchanged = stats.unchanged;
for (i, cert) in keyring::certs.iter().enumerate() {
let cert = Cert::from_bytes(&cert.bytes()).expect("valid");
let fpr = cert.fingerprint();
certs.update_by(Cow::Owned(LazyCert::from(cert)), &mut stats)
.expect("valid");
eprintln!("After reinserting {} ({}), stats: {:?}",
i, fpr, stats);
assert_eq!(stats.new, new);
assert_eq!(stats.unchanged + stats.updated,
updated + unchanged + i + 1);
}
}
}