summary refs log tree commit diff
path: root/event_rs/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'event_rs/src/lib.rs')
-rw-r--r--event_rs/src/lib.rs187
1 files changed, 187 insertions, 0 deletions
diff --git a/event_rs/src/lib.rs b/event_rs/src/lib.rs
new file mode 100644
index 0000000000..c281d9b1d9
--- /dev/null
+++ b/event_rs/src/lib.rs
@@ -0,0 +1,187 @@
+use std::collections::BTreeMap;
+
+use anyhow::Context;
+use base64::URL_SAFE_NO_PAD;
+use pyo3::exceptions::PyAttributeError;
+use pyo3::prelude::*;
+use pyo3::types::PyBytes;
+use pythonize::pythonize;
+use serde::Deserialize;
+use serde_json::Value;
+use sha2::{Digest, Sha256};
+use signed_json::Signed;
+
+/*
+
+depth: DictProperty[int] = DictProperty("depth")
+    content: DictProperty[JsonDict] = DictProperty("content")
+    hashes: DictProperty[Dict[str, str]] = DictProperty("hashes")
+    origin: DictProperty[str] = DictProperty("origin")
+    origin_server_ts: DictProperty[int] = DictProperty("origin_server_ts")
+    redacts: DefaultDictProperty[Optional[str]] = DefaultDictProperty("redacts", None)
+    room_id: DictProperty[str] = DictProperty("room_id")
+    sender: DictProperty[str] = DictProperty("sender")
+    # TODO state_key should be Optional[str]. This is generally asserted in Synapse
+    # by calling is_state() first (which ensures it is not None), but it is hard (not possible?)
+    # to properly annotate that calling is_state() asserts that state_key exists
+    # and is non-None. It would be better to replace such direct references with
+    # get_state_key() (and a check for None).
+    state_key: DictProperty[str] = DictProperty("state_key")
+    type: DictProperty[str] = DictProperty("type")
+    user_id: DictProperty[str] = DictProperty("sender")
+
+*/
+
+// FYI origin is not included here
+
+#[derive(Debug, Clone, Deserialize)]
+
+struct EventInner {
+    room_id: String,
+    depth: u64,
+    hashes: BTreeMap<String, String>,
+    origin_server_ts: u64,
+    redacts: Option<String>,
+    sender: String,
+    #[serde(rename = "type")]
+    event_type: String,
+    #[serde(default)]
+    state_key: Option<String>,
+
+    content: BTreeMap<String, Value>,
+}
+
+#[pyclass]
+#[derive(Debug, Clone, Deserialize)]
+struct Event {
+    #[pyo3(get)]
+    event_id: String,
+    #[serde(flatten)]
+    inner: Signed<EventInner>,
+}
+
+#[pymethods]
+impl Event {
+    #[getter]
+    fn room_id(&self) -> &str {
+        &self.inner.room_id
+    }
+
+    fn get_pdu_json(&self) -> PyResult<String> {
+        // TODO: Do all the other things `get_pdu_json` does.
+        Ok(serde_json::to_string(&self.inner).context("bah")?)
+    }
+
+    #[getter]
+    fn content(&self, py: Python) -> PyResult<PyObject> {
+        Ok(pythonize(py, &self.inner.content)?)
+    }
+
+    #[getter]
+    fn state_key(&self) -> PyResult<&str> {
+        if let Some(state_key) = &self.inner.state_key {
+            Ok(state_key)
+        } else {
+            Err(PyAttributeError::new_err("state_key"))
+        }
+    }
+}
+
+#[pyfunction]
+fn from_bytes(bytes: &PyBytes) -> PyResult<Event> {
+    let b = bytes.as_bytes();
+
+    let inner: Signed<EventInner> = serde_json::from_slice(b).context("parsing event")?;
+
+    let mut redacted: BTreeMap<String, Value> = redact(&inner).context("redacting")?;
+    redacted.remove("signatures");
+    redacted.remove("unsigned");
+    let redacted_json = serde_json::to_vec(&redacted).context("BAH")?;
+
+    let event_id = base64::encode_config(Sha256::digest(&redacted_json), URL_SAFE_NO_PAD);
+
+    let event = Event { event_id, inner };
+
+    Ok(event)
+}
+
+#[pymodule]
+fn synapse_events(_py: Python, m: &PyModule) -> PyResult<()> {
+    m.add_function(wrap_pyfunction!(from_bytes, m)?)?;
+    Ok(())
+}
+
+fn redact<E: serde::de::DeserializeOwned>(
+    event: &Signed<EventInner>,
+) -> Result<E, serde_json::Error> {
+    let etype = event.event_type.to_string();
+    let mut content = event.as_ref().content.clone();
+
+    let val = serde_json::to_value(event)?;
+
+    let allowed_keys = [
+        "event_id",
+        "sender",
+        "room_id",
+        "hashes",
+        "signatures",
+        "content",
+        "type",
+        "state_key",
+        "depth",
+        "prev_events",
+        "prev_state",
+        "auth_events",
+        "origin",
+        "origin_server_ts",
+        "membership",
+    ];
+
+    let val = match val {
+        serde_json::Value::Object(obj) => obj,
+        _ => unreachable!(), // Events always serialize to an object
+    };
+
+    let mut val: serde_json::Map<_, _> = val
+        .into_iter()
+        .filter(|(k, _)| allowed_keys.contains(&(k as &str)))
+        .collect();
+
+    let mut new_content = serde_json::Map::new();
+
+    let mut copy_content = |key: &str| {
+        if let Some(v) = content.remove(key) {
+            new_content.insert(key.to_string(), v);
+        }
+    };
+
+    match &etype[..] {
+        "m.room.member" => copy_content("membership"),
+        "m.room.create" => copy_content("creator"),
+        "m.room.join_rules" => copy_content("join_rule"),
+        "m.room.aliases" => copy_content("aliases"),
+        "m.room.history_visibility" => copy_content("history_visibility"),
+        "m.room.power_levels" => {
+            for key in &[
+                "ban",
+                "events",
+                "events_default",
+                "kick",
+                "redact",
+                "state_default",
+                "users",
+                "users_default",
+            ] {
+                copy_content(key);
+            }
+        }
+        _ => {}
+    }
+
+    val.insert(
+        "content".to_string(),
+        serde_json::Value::Object(new_content),
+    );
+
+    serde_json::from_value(serde_json::Value::Object(val))
+}