gundb 0.2.1

Distributed graph database that syncs over websockets and multicast
Documentation
use serde_json::{json, Value as SerdeJsonValue};
use crate::utils::random_string;
use std::collections::{HashSet, BTreeMap};
use crate::types::*;
use log::{debug, info, error};
use std::convert::TryFrom;
use crate::actor::Addr;
use java_utils::HashCode;
use ring::signature::{self, KeyPair};
use jsonwebtoken::jwk::*;
use jsonwebtoken::{Header, Algorithm};
use ring::digest::{digest, SHA256};

#[derive(Clone, Debug)]
pub struct Get {
    pub id: String,
    pub from: Addr,
    pub recipients: Option<HashSet<String>>,
    pub node_id: String,
    pub child_key: Option<String>,
    pub json_str: Option<String>
}
impl Get {
    pub fn new(node_id: String, child_key: Option<String>, from: Addr) -> Self {
        Self {
            id: random_string(8),
            from,
            recipients: None,
            node_id,
            child_key,
            json_str: None
        }
    }

    pub fn to_string(&self) -> String {
        if let Some(json_str) = self.json_str.clone() {
            return json_str;
        }

        let mut json = json!({
            "get": {
                "#": &self.node_id
            },
            "#": &self.id
        });
        if let Some(child_key) = self.child_key.clone() {
            json["get"]["."] = json!(child_key);
        }
        json.to_string()
    }
}

#[derive(Clone, Debug)]
pub struct Put {
    pub id: String,
    pub from: Addr,
    pub recipients: Option<HashSet<String>>,
    pub in_response_to: Option<String>,
    pub updated_nodes: BTreeMap<String, Children>,
    pub checksum: Option<i32>,
    pub json_str: Option<String>
}
impl Put {
    pub fn new(updated_nodes: BTreeMap<String, Children>, in_response_to: Option<String>, from: Addr) -> Self {
        Self {
            id: random_string(8),
            from,
            recipients: None,
            in_response_to,
            updated_nodes,
            checksum: None,
            json_str: None
        }
    }

    pub fn new_from_kv(key: String, children: Children, from: Addr) -> Self {
        let mut updated_nodes = BTreeMap::new();
        updated_nodes.insert(key, children);
        Put::new(updated_nodes, None, from)
    }

    pub fn to_string(&self) -> String {
        if let Some(json_str) = self.json_str.clone() {
            return json_str;
        }

        let mut json = json!({
            "put": {},
            "#": self.id.to_string(),
        });

        if let Some(in_response_to) = &self.in_response_to {
            json["@"] = json!(in_response_to);
        }

        for (node_id, children) in self.updated_nodes.iter() {
            let node = &mut json["put"][node_id];
            node["_"] = json!({
                "#": node_id,
                ">": {}
            });
            for (k, v) in children.iter() {
                node["_"][">"][k] = json!(v.updated_at);
                node[k] = v.value.clone().into();
            }
        }

        let checksum = match &self.checksum {
            Some(s) => *s,
            _ => {
                let put_str = json["put"].to_string();
                put_str.hash_code()
            }
        };
        json["##"] = json!(checksum);

        json.to_string()
    }
}

#[derive(Clone, Debug)]
pub enum Message { // TODO: NetworkMessage and InternalMessage
    Get(Get),
    Put(Put),
    Hi { from: Addr, peer_id: String }
}

impl Message {
    pub fn to_string(self) -> String {
        match self {
            Message::Get(get) => get.to_string(),
            Message::Put(put) => put.to_string(),
            Message::Hi { from, peer_id } => json!({"dam": "hi","#": peer_id}).to_string()
        }
    }

    pub fn get_id(&self) -> String {
        match self {
            Message::Get(get) => get.id.clone(),
            Message::Put(put) => put.id.clone(),
            Message::Hi { from: _, peer_id } => peer_id.to_string()
        }
    }

    pub fn is_from(&self, addr: &Addr) -> bool {
        match self {
            Message::Get(get) => get.from == *addr,
            Message::Put(put) => put.from == *addr,
            Message::Hi { from, peer_id: _ } => *from == *addr
        }
    }

    fn verify_sig(node_id: &str, value: &GunValue) -> Result<(), &'static str> {
        let text = match value {
            GunValue::Text(t) => t,
            _ => { return Err("Values in user space must be signed strings"); }
        };
        let json: SerdeJsonValue = match serde_json::from_str(text) {
            Ok(json) => json,
            Err(_) => { return Err("Failed to parse signature as JSON"); }
        };
        let obj = match json.as_object() {
            Some(obj) => obj,
            _ => { return Err("signature json was not an object"); }
        };
        let signed_str = match obj.get(":") {
            Some(s) => s.as_str(),
            _ => { return Err("no signed string (:) in signature json")}
        };
        let signed_str = match signed_str {
            Some(s) => s,
            _ => { return Err("signed value (:) in signature json was not a string")}
        };
        let signature = match obj.get("~") {
            Some(s) => s.as_str(),
            _ => { return Err("no signature (~) in signature json")}
        };
        let signature = match signature {
            Some(s) => s,
            _ => { return Err("signature (~) in signature json was not a string")}
        };

        // TODO calculate the hash of signed_str
        // https://github.com/amark/gun/blob/master/sea.js#L443
        // json web key library useful? or just decode the base64 str?

        let key = &node_id.split("/").next().unwrap()[1..];
        let mut split = key.split(".");
        let x = split.next().unwrap().to_string();
        let y = match split.next() {
            Some(y) => y.to_string(),
            _ => { return Err("invalid key string"); }
        };

        let algorithm = AlgorithmParameters::EllipticCurve(EllipticCurveKeyParameters {
            key_type: EllipticCurveKeyType::EC,
            curve: EllipticCurve::P256,
            x,
            y
        });
        let jwk = Jwk {
            common: CommonParameters::default(),
            algorithm,
        };
        let header = Header {
            jwk: Some(jwk),
            alg: Algorithm::ES256,
            ..Header::default()
        };
        // how to verify using the jwk?

        // OR:
        let peer_public_key = signature::UnparsedPublicKey::new(
            &signature::ECDSA_P256_SHA256_FIXED,
            key.as_bytes() // how to convert x.y into a valid key?
        );
        match peer_public_key.verify(signed_str.as_bytes(), signature.as_bytes()) { // base64 decode first
            Ok(_) => {
                info!("good signature :)");
                Ok(())
            },
            Err(_) => {
                error!("bad signature {} of {}", signature, signed_str);
                Err("bad signature")
            }
        }
    }

    fn from_put_obj(json: &SerdeJsonValue, json_str: String, msg_id: String, from: Addr) -> Result<Self, &'static str> {
        let obj = match json.get("put").unwrap().as_object() {
            Some(obj) => obj,
            _ => { return Err("invalid message: msg.put was not an object"); }
        };
        let in_response_to = match json.get("@") {
            Some(in_response_to) => match in_response_to.as_str() {
                Some(in_response_to) => Some(in_response_to.to_string()),
                _ => { return Err("message @ field was not a string"); }
            },
            _ => None
        };
        let checksum = match json.get("##") {
            Some(checksum) => match checksum.as_i64() {
                Some(checksum) => Some(checksum as i32),
                _ => None,
            },
            _ => None
        };
        let mut updated_nodes = BTreeMap::<String, Children>::new();
        for (node_id, node_data) in obj.iter() {
            let node_data = match node_data.as_object() {
                Some(obj) => obj,
                _ => { return Err("put node data was not an object"); }
            };
            let updated_at_times = match node_data["_"][">"].as_object() {
                Some(obj) => obj,
                _ => { return Err("no metadata _ in Put node object"); }
            };
            let mut children = Children::default();
            for (child_key, child_val) in node_data.iter() {
                if child_key == "_" { continue; }
                let updated_at = match updated_at_times.get(child_key) {
                    Some(updated_at) => updated_at,
                    _ => { return Err("no updated_at found for Put key"); }
                };
                let updated_at = match updated_at.as_f64() {
                    Some(val) => val,
                    None => { return Err("updated_at was not a number"); }
                };
                let value = match GunValue::try_from(child_val.clone()) {
                    Ok(v) => v,
                    Err(e) => { return Err(e) }
                };

                if let Some(first_letter) = node_id.chars().next() {
                    if first_letter == '~' { // signed data
                        /*
                        if let Err(e) = Self::verify_sig(node_id, &value) {
                            return Err(e);
                        }*/
                    } else if node_id == "#" { // content-hash addressed data
                        let content_hash = digest(&SHA256, value.to_string().as_bytes());
                        if *child_key != base64::encode(content_hash.as_ref()) {
                            return Err("invalid content hash");
                        }
                    }
                }

                children.insert(child_key.to_string(), NodeData { updated_at, value });
            }
            updated_nodes.insert(node_id.to_string(), children);
        }
        let put = Put {
            id: msg_id.to_string(),
            from,
            recipients: None,
            in_response_to,
            updated_nodes,
            checksum,
            json_str: Some(json_str)
        };
        Ok(Message::Put(put))
    }

    fn from_get_obj(json: &SerdeJsonValue, json_str: String, msg_id: String, from: Addr) -> Result<Self, &'static str> {
        /* TODO: other types of child_key selectors than equality.

        node.get({'.': {'<': cursor, '-': true}, '%': 20 * 1000}).once().map().on((value, key) => { ...

        '*' wildcard selector

         */

        let get = json.get("get").unwrap();
        let node_id = match get["#"].as_str() {
            Some(str) => str,
            _ => { return Err("no node id (#) found in get message"); }
        };
        let child_key = match get.get(".") {
            Some(child_key) => match child_key.as_str() {
                Some(child_key) => Some(child_key.to_string()),
                _ => { return Err("get child_key . was not a string") }
            },
            _ => None
        };
        debug!("get node_id {}", node_id);
        let msg_id = msg_id.replace("\"", "");
        let get = Get {
            id: msg_id,
            from,
            recipients: None,
            node_id: node_id.to_string(),
            child_key,
            json_str: Some(json_str)
        };
        Ok(Message::Get(get))
    }
    
    pub fn from_json_obj(json: &SerdeJsonValue, json_str: String, from: Addr) -> Result<Self, &'static str> {
        let obj = match json.as_object() {
            Some(obj) => obj,
            _ => { return Err("not a json object"); }
        };
        let msg_id = match obj["#"].as_str() {
            Some(str) => str.to_string(),
            _ => { return Err("msg id not a string"); }
        };
        if msg_id.len() > 32 {
            return Err("msg id too long (> 32)");
        }
        if !msg_id.chars().all(char::is_alphanumeric) {
            return Err("msg_id must be alphanumeric");
        }
        if obj.contains_key("put") {
            Self::from_put_obj(json, json_str, msg_id, from)
        } else if obj.contains_key("get") {
            Self::from_get_obj(json, json_str, msg_id, from)
        } else if let Some(_dam) = obj.get("dam") {
            Ok(Message::Hi { from, peer_id: msg_id })
        } else {
            Err("Unrecognized message")
        }
    }

    pub fn try_from(s: &str, from: Addr) -> Result<Vec<Self>, &str> {
        let json: SerdeJsonValue = match serde_json::from_str(s) {
            Ok(json) => json,
            Err(_) => { return Err("Failed to parse message as JSON"); }
        };
        
        if let Some(arr) = json.as_array() {
            let mut vec = Vec::<Self>::new();
            for msg in arr {
                vec.push(Self::from_json_obj(msg, msg.to_string(), from.clone())?);
            }
            Ok(vec)
        } else {
            match Self::from_json_obj(&json, s.to_string(), from) {
                Ok(msg) => Ok(vec![msg]),
                Err(e) => Err(e)
            }
        }
    }
}