lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

event_head.rs (3511B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::format;
      3 #[cfg(not(feature = "std"))]
      4 use alloc::{
      5     string::{String, ToString},
      6     vec::Vec,
      7 };
      8 #[cfg(feature = "std")]
      9 use std::{string::String, vec::Vec};
     10 
     11 use serde_json::Value;
     12 use sha2::{Digest, Sha256};
     13 
     14 #[cfg(test)]
     15 use crate::error::RadrootsReplicaEventsError;
     16 
     17 #[cfg(test)]
     18 mod failpoints {
     19     use std::cell::Cell;
     20 
     21     thread_local! {
     22         static FORCE_ERROR: Cell<bool> = const { Cell::new(false) };
     23     }
     24 
     25     pub(crate) fn set_error() {
     26         FORCE_ERROR.with(|flag| flag.set(true));
     27     }
     28 
     29     pub(crate) fn take_error() -> bool {
     30         FORCE_ERROR.with(|flag| {
     31             let value = flag.get();
     32             flag.set(false);
     33             value
     34         })
     35     }
     36 }
     37 
     38 pub fn event_head_key(kind: u32, pubkey: &str, d_tag: &str) -> String {
     39     format!("{kind}:{pubkey}:{d_tag}")
     40 }
     41 
     42 fn event_content_hash_value(content: &str, tags: &[Vec<String>]) -> String {
     43     let tags_json = Value::Array(
     44         tags.iter()
     45             .map(|tag| Value::Array(tag.iter().cloned().map(Value::String).collect()))
     46             .collect(),
     47     )
     48     .to_string();
     49     let mut hasher = Sha256::new();
     50     hasher.update(content.as_bytes());
     51     hasher.update(tags_json.as_bytes());
     52     hex::encode(hasher.finalize())
     53 }
     54 
     55 #[cfg(test)]
     56 pub fn event_content_hash(
     57     content: &str,
     58     tags: &[Vec<String>],
     59 ) -> Result<String, RadrootsReplicaEventsError> {
     60     #[cfg(test)]
     61     if failpoints::take_error() {
     62         return Err(RadrootsReplicaEventsError::InvalidData(
     63             "content_hash".to_string(),
     64         ));
     65     }
     66     Ok(event_content_hash_value(content, tags))
     67 }
     68 
     69 #[cfg(not(test))]
     70 pub fn event_content_hash(content: &str, tags: &[Vec<String>]) -> String {
     71     event_content_hash_value(content, tags)
     72 }
     73 
     74 #[cfg(test)]
     75 pub(crate) fn event_content_hash_fail_next() {
     76     failpoints::set_error();
     77 }
     78 
     79 pub fn tag_value<'a>(tags: &'a [Vec<String>], key: &str) -> Option<&'a str> {
     80     tags.iter()
     81         .find(|tag| tag.first().map(|v| v.as_str()) == Some(key))
     82         .and_then(|tag| tag.get(1))
     83         .map(|value| value.as_str())
     84 }
     85 
     86 #[cfg(test)]
     87 mod tests {
     88     use super::{event_content_hash, event_head_key, tag_value};
     89 
     90     #[test]
     91     fn event_head_key_formats_consistently() {
     92         let key = event_head_key(30000, "author", "d-tag");
     93         assert_eq!(key, "30000:author:d-tag");
     94     }
     95 
     96     #[test]
     97     fn event_content_hash_is_stable_for_same_inputs() {
     98         let tags = vec![vec!["d".to_string(), "tag".to_string()]];
     99         let first = event_content_hash("content", &tags).expect("hash first");
    100         let second = event_content_hash("content", &tags).expect("hash second");
    101         assert_eq!(first, second);
    102         assert_eq!(first.len(), 64);
    103     }
    104 
    105     #[test]
    106     fn event_content_hash_reports_failpoint_errors() {
    107         super::failpoints::set_error();
    108         let tags = vec![vec!["d".to_string(), "tag".to_string()]];
    109         let err = event_content_hash("content", &tags).expect_err("failpoint");
    110         assert!(err.to_string().contains("content_hash"));
    111     }
    112 
    113     #[test]
    114     fn tag_value_finds_and_misses_keys() {
    115         let tags = vec![
    116             vec!["p".to_string(), "member".to_string()],
    117             vec!["d".to_string(), "farm".to_string()],
    118             vec!["x".to_string()],
    119         ];
    120         assert_eq!(tag_value(&tags, "p"), Some("member"));
    121         assert_eq!(tag_value(&tags, "d"), Some("farm"));
    122         assert_eq!(tag_value(&tags, "x"), None);
    123         assert_eq!(tag_value(&tags, "missing"), None);
    124     }
    125 }