Ctrl K
ComunidadGuías…

src/support/token_equivalence.rs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
//! Strict bounded revalidation of token relationships, at capture and export.
use serde_json::{Map, Value, json};

pub(crate) const MAX_PASSES: usize = 64;

fn ordinal(value: &Value, maximum: u64) -> bool {
    value.as_u64().is_some_and(|n| n > 0 && n <= maximum)
}

fn row(value: &Value) -> Option<Value> {
    let source = value.as_object()?;
    let pass = source.get("pass")?;
    if !ordinal(pass, MAX_PASSES as u64) {
        return None;
    }
    let mut out = Map::new();
    out.insert("pass".into(), pass.clone());
    for (key, allowed) in [
        ("pass_kind", &["album", "smart_folder", "unfiled"][..]),
        (
            "query_kind",
            &[
                "library_wide",
                "album_membership",
                "smart_album",
                "burst",
                "hidden",
                "deleted",
                "other",
            ][..],
        ),
        (
            "scope",
            &[
                "unavailable",
                "different_selected_zone",
                "same_first_pass",
                "different_first_pass",
            ][..],
        ),
    ] {
        let label = source.get(key)?.as_str()?;
        if !allowed.contains(&label) {
            return None;
        }
        out.insert(key.into(), json!(label));
    }
    for (status_key, group_key, order_key) in [
        (
            "initial_status",
            "initial_group",
            "initial_completion_order",
        ),
        (
            "recovery_status",
            "recovery_group",
            "recovery_completion_order",
        ),
    ] {
        let status = source.get(status_key)?.as_str()?;
        if ![
            "not_observed",
            "not_retried",
            "present",
            "missing",
            "blank",
            "receiver_dropped",
            "enumeration_incomplete",
            "token_too_large",
            "group_limit",
        ]
        .contains(&status)
        {
            return None;
        }
        let group = source.get(group_key)?;
        if (status == "present" && !ordinal(group, (MAX_PASSES * 2) as u64))
            || (status != "present" && !group.is_null())
        {
            return None;
        }
        let order = source.get(order_key)?;
        if !order.is_null() && !ordinal(order, u32::MAX.into()) {
            return None;
        }
        out.insert(status_key.into(), json!(status));
        out.insert(group_key.into(), group.clone());
        out.insert(order_key.into(), order.clone());
    }
    Some(Value::Object(out))
}

pub(super) fn retain_rows(source: &Map<String, Value>, out: &mut Map<String, Value>) {
    let input = source.get("rows").and_then(Value::as_array);
    let mut rejected = source
        .get("rows_rejected")
        .and_then(Value::as_u64)
        .unwrap_or(0);
    let mut seen = [false; MAX_PASSES];
    let mut rows = Vec::new();
    if let Some(input) = input {
        rejected = rejected.saturating_add(input.len().saturating_sub(MAX_PASSES) as u64);
        for value in input.iter().take(MAX_PASSES) {
            if let Some(row) = row(value)
                && let Some(index) = row
                    .get("pass")
                    .and_then(Value::as_u64)
                    .and_then(|n| usize::try_from(n - 1).ok())
                && let Some(seen) = seen.get_mut(index)
                && !*seen
            {
                *seen = true;
                rows.push(row);
            } else {
                rejected = rejected.saturating_add(1);
            }
        }
    }
    out.insert("rows".into(), json!(rows));
    out.insert("rows_rejected".into(), json!(rejected));
    out.insert(
        "rows_unavailable".into(),
        json!(input.is_none() || source.get("rows_unavailable") == Some(&Value::Bool(true))),
    );
}

#[cfg(test)]
mod tests {
    use super::MAX_PASSES;
    use crate::support::privacy;
    use serde_json::{Value, json};

    fn valid_row(pass: usize) -> Value {
        json!({"pass":pass,"pass_kind":"album","query_kind":"album_membership","scope":"same_first_pass",
            "initial_status":"present","initial_group":1,"initial_completion_order":1,
            "recovery_status":"present","recovery_group":1,"recovery_completion_order":2})
    }
    fn clean(value: &Value) -> Value {
        Value::Object(
            privacy::diagnostic("support_token_equivalence_v1", value.as_object().unwrap())
                .unwrap(),
        )
    }

    #[test]
    fn token_equivalence_privacy_roundtrip_rejects_private_and_malformed_values() {
        let mut row = valid_row(1);
        row["token"] = json!("PRIVATE_TOKEN");
        row["album"] = json!("PRIVATE_ALBUM");
        let mut input = json!({"phase":"same_cycle_recovery","passes_total":1,"passes_omitted":0,"rows":[row],"error":"PRIVATE_ERROR","owner":"PRIVATE_OWNER"});
        let safe = clean(&input);
        assert_eq!(
            safe["rows"][0]["initial_group"],
            safe["rows"][0]["recovery_group"]
        );
        assert!(!safe.to_string().contains("PRIVATE"));
        assert_eq!(clean(&safe), safe);
        for key in [
            "pass",
            "pass_kind",
            "query_kind",
            "scope",
            "initial_status",
            "initial_group",
            "initial_completion_order",
            "recovery_status",
            "recovery_group",
            "recovery_completion_order",
        ] {
            let mut malformed = input.clone();
            malformed["rows"][0][key] = json!("PRIVATE_VALUE");
            let safe = clean(&malformed);
            assert_eq!(safe["rows"], json!([]), "{key}");
            assert_eq!(safe["rows_rejected"], 1);
            assert!(!safe.to_string().contains("PRIVATE"));
            assert_eq!(clean(&safe), safe);
        }
        input["rows"][0]["initial_group"] = json!(129);
        assert_eq!(clean(&input)["rows_rejected"], 1);
        input["rows"][0]["initial_group"] = json!(null);
        assert_eq!(clean(&input)["rows_rejected"], 1);
        input["rows"][0]["initial_status"] = json!("blank");
        assert_eq!(clean(&input)["rows_rejected"], 0);
        input["rows"][0]["initial_group"] = json!(1);
        assert_eq!(clean(&input)["rows_rejected"], 1);
    }

    #[test]
    fn token_equivalence_export_bounds_and_missing_rows_are_explicit() {
        let rows = (1..=MAX_PASSES + 5).map(valid_row).collect::<Vec<_>>();
        let input = json!({"phase":"initial","passes_total":69,"passes_omitted":0,"rows":rows});
        let safe = clean(&input);
        assert_eq!(safe["rows"].as_array().unwrap().len(), MAX_PASSES);
        assert_eq!(safe["rows_rejected"], 5);
        assert_eq!(clean(&safe), safe);
        assert!(safe.to_string().len() < 64 * 1024);
        for missing in [
            json!({}),
            json!({"rows":"PRIVATE_ROWS"}),
            json!({"rows":null}),
        ] {
            let safe = clean(&missing);
            assert_eq!(safe["rows_unavailable"], true);
            assert_eq!(safe["rows"], json!([]));
            assert_eq!(clean(&safe), safe);
        }
        let duplicate = clean(&json!({"rows":[valid_row(1),valid_row(1)]}));
        assert_eq!(duplicate["rows_rejected"], 1);
        assert_eq!(duplicate["rows"].as_array().unwrap().len(), 1);
    }
}