//! The privacy of held sets (extensions/http-delivery.md §12.5).
//!
//! A sink keeps the sets it holds per top-level site. The Sink Capability Descriptor's
//! `held_sets` member it sends to a site is off unless turned on for that site, and then lists
//! only sets held for that site that the site's content pins and that are not revoked.

use serde_json::{json, Map, Value};

/// A set a sink holds: the top-level site it was fetched for, the capability, its digest
/// (64 lowercase hexadecimal digits), and whether a key log the sink holds revokes it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Held {
    pub site: String,
    pub capability: String,
    pub sha256: String,
    pub revoked: bool,
}

/// The `held_sets` member a sink sends to `site`: `None` (no member) unless `enabled` for that
/// site; otherwise, per capability in the order `wanted` first names it, the digests of `wanted`
/// (the pins the site's content carries) that the sink holds for `site` and that no key log it
/// holds revokes, in `wanted`'s order, each once.
pub fn held_sets_for(store: &[Held], site: &str, wanted: &[(String, String)], enabled: bool) -> Option<Value> {
    if !enabled {
        return None;
    }
    let mut m = Map::new();
    for (cap, d) in wanted {
        let held = store.iter().any(|h| h.site == site && h.capability == *cap && h.sha256 == *d && !h.revoked);
        if !held {
            continue;
        }
        let list = m.entry(cap.clone()).or_insert_with(|| json!([]));
        if let Some(a) = list.as_array_mut() {
            if !a.iter().any(|x| x == d) {
                a.push(json!(d));
            }
        }
    }
    Some(Value::Object(m))
}

/// Whether a sink may name `sha256` in `Available-Dictionary` on a request it sends for `site`
/// (http-delivery §8.8, §12.5): it holds the set for that site and no key log it holds revokes it.
pub fn may_name_dictionary(store: &[Held], site: &str, sha256: &str) -> bool {
    store.iter().any(|h| h.site == site && h.sha256 == sha256 && !h.revoked)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn h(site: &str, cap: &str, d: &str, revoked: bool) -> Held {
        Held { site: site.into(), capability: cap.into(), sha256: d.into(), revoked }
    }

    #[test]
    fn per_site_and_only_what_is_pinned() {
        let store = [h("a.example", "c1", "11", false), h("b.example", "c1", "22", false), h("a.example", "c1", "33", true)];
        let wanted = [("c1".to_string(), "11".to_string()), ("c1".to_string(), "22".to_string()), ("c1".to_string(), "33".to_string())];
        assert_eq!(held_sets_for(&store, "a.example", &wanted, false), None);
        assert_eq!(held_sets_for(&store, "a.example", &wanted, true), Some(json!({ "c1": ["11"] })));
        assert_eq!(held_sets_for(&store, "b.example", &wanted[..1], true), Some(json!({})));
        assert!(may_name_dictionary(&store, "a.example", "11") && !may_name_dictionary(&store, "b.example", "11"));
    }
}
