diff --git a/src/crypto/wordcoding/WordCode.rs b/src/crypto/wordcoding/WordCode.rs new file mode 100644 index 00000000..026292ec --- /dev/null +++ b/src/crypto/wordcoding/WordCode.rs @@ -0,0 +1,93 @@ +use std::collections::HashMap; + +pub struct WordCode { + dict_name: String, + words: Vec, + bits_per_word: f64, + normalizer: Box String>, + word_positions: Option>, +} + +impl WordCode { + pub fn new( + dict_name: String, + words: Vec, + normalizer: Option String>>, + ) -> Self { + let bits_per_word = (words.len() as f64).log2(); + let normalizer = normalizer.unwrap_or_else(|| Box::new(|x: String| x.to_lowercase())); + + WordCode { + dict_name, + words, + bits_per_word, + normalizer, + word_positions: None, + } + } + + fn fill_word_positions(&mut self) { + if self.word_positions.is_none() { + let mut word_positions = HashMap::new(); + for (pos, word) in self.words.iter().enumerate() { + word_positions.insert((self.normalizer)(word.clone()), pos); + } + self.word_positions = Some(word_positions); + } + } + + pub fn encode(&self, hex: &str) -> Result, String> { + let nibbles_per_word = (self.bits_per_word / 4.0).ceil() as usize; + + if hex.len() % nibbles_per_word != 0 { + return Err("Hex string length is not a multiple of the bits-per-word constant.".to_string()); + } + + let words = hex + .as_bytes() + .chunks(nibbles_per_word) + .map(|chunk| { + let chunk_str = String::from_utf8_lossy(chunk); + let pos = usize::from_str_radix(&chunk_str, 16).unwrap(); + self.words.get(pos).unwrap().clone() + }) + .collect(); + + Ok(words) + } + + pub fn decode(&mut self, words: &[String]) -> Result { + self.fill_word_positions(); + + let nibbles_per_word = (self.bits_per_word / 4.0).ceil() as usize; + + let mut result = String::new(); + + for word in words { + let position = self + .word_positions + .as_ref() + .unwrap() + .get(&(self.normalizer)(word.clone())); + + match position { + Some(pos) => { + result.push_str(&format!("{:0width$X}", pos, width = nibbles_per_word)); + } + None => { + return Err(format!( + "Received a word that is not in the dictionary '{}': {}", + self.dict_name, word + )); + } + } + } + + Ok(result) + } + + pub fn check(&mut self, word: &str) -> bool { + self.fill_word_positions(); + self.word_positions.as_ref().unwrap().contains_key(&(self.normalizer)(word.to_string())) + } +} diff --git a/src/data/model/immutable/HashedObject.rs b/src/data/model/immutable/HashedObject.rs new file mode 100644 index 00000000..3de7c891 --- /dev/null +++ b/src/data/model/immutable/HashedObject.rs @@ -0,0 +1,57 @@ +use std::collections::{HashMap, HashSet}; +use crate::storage::store::Store; +use crate::crypto::random::RNGImpl; +use crate::identity::Identity; +use crate::hashing::{Hashing, Hash}; +use crate::serialization::Serialization; +use crate::immutable::{HashedSet, HashReference, HashedMap}; +use crate::literals::{Context, LiteralContext}; +use crate::mesh::service::Mesh; +use crate::spaces::spaces::Resources; +use crate::literals::literal_utils::{Literal, Dependency}; +use crate::util::logging::{Logger, LogLevel}; +use crate::literals::class_registry::ClassRegistry; +use crate::util::events::EventRelay; +use crate::mutable::MutationObserver; + +const BITS_FOR_ID: u32 = 128; + +pub trait HashedObject { + fn get_class_name(&self) -> String; + fn init(&mut self); + async fn validate(&self, references: &HashMap>) -> bool; + fn get_id(&self) -> Option; + fn set_id(&mut self, id: String); + fn set_random_id(&mut self); + fn has_id(&self) -> bool; + fn set_author(&mut self, author: Identity); + fn get_author(&self) -> Option<&Identity>; + fn has_author(&self) -> bool; + fn has_last_signature(&self) -> bool; + fn set_last_signature(&mut self, signature: String); + fn get_last_signature(&self) -> String; + fn override_children_id(&mut self); + fn override_id_for_path(&mut self, path: String, target: &mut dyn HashedObject); + fn has_store(&self) -> bool; + fn set_store(&mut self, store: Store); + fn get_store(&self) -> Store; + fn get_mesh(&self) -> Mesh; + fn has_last_literal(&self) -> bool; + fn get_last_literal(&self) -> Option<&Literal>; + fn set_last_literal(&mut self, literal: Literal); + fn should_sign_on_save(&self) -> bool; + fn has_last_hash(&self) -> bool; + fn get_last_hash(&self) -> Hash; + fn hash(&mut self, seed: Option) -> Hash; + fn custom_hash(&self, seed: Option) -> Option; + fn create_reference(&self) -> HashReference + where + Self: Sized; + fn equals(&self, another: Option<&dyn HashedObject>) -> bool; + fn clone(&self) -> Box; + fn add_derived_field(&mut self, field_name: String, object: Option>); + fn set_derived_field(&mut self, field_name: String, object: Box); + fn check_derived_field(&self, field_name: &str) -> bool; + fn get_derived_field_id(&self, field_name: &str) -> Hash; + fn set_resources(&mut self, resources: Resources); +} diff --git a/src/data/model/literals/Context.rs b/src/data/model/literals/Context.rs new file mode 100644 index 00000000..89490249 --- /dev/null +++ b/src/data/model/literals/Context.rs @@ -0,0 +1,121 @@ +use std::collections::{HashMap, HashSet}; +use crate::hashed_object::HashedObject; +use crate::hashing::{Hash, Literal}; +use crate::spaces::Resources; + +pub type LiteralContext = (Vec, HashMap); + +pub fn is_literal_context(obj: &dyn Any) -> bool { + obj.downcast_ref::().is_some() +} + +pub struct Context { + pub root_hashes: Vec, + pub objects: HashMap, + pub literals: HashMap, + pub resources: Option, +} + +impl Context { + pub fn new() -> Self { + Self { + root_hashes: Vec::new(), + objects: HashMap::new(), + literals: HashMap::new(), + resources: None, + } + } + + pub fn has(&self, hash: &Hash) -> bool { + self.literals.contains_key(hash) + || self.objects.contains_key(hash) + || self + .resources + .as_ref() + .map(|r| r.aliasing.as_ref().map(|a| a.contains_key(hash)).unwrap_or(false)) + .unwrap_or(false) + } + + pub fn to_literal_context(&self) -> LiteralContext { + ( + self.root_hashes.clone(), + self.literals.clone(), + ) + } + + pub fn from_literal_context(&mut self, literal_context: LiteralContext) { + self.root_hashes = literal_context.0; + self.literals = literal_context.1; + self.objects = HashMap::new(); + } + + pub fn merge(&mut self, other: &Context) { + let roots: HashSet = self + .root_hashes + .iter() + .chain(other.root_hashes.iter()) + .cloned() + .collect(); + self.root_hashes = roots.into_iter().collect(); + + for (hash, literal) in &other.literals { + self.literals.entry(*hash).or_insert_with(|| literal.clone()); + } + + for (hash, obj) in &other.objects { + self.objects.entry(*hash).or_insert_with(|| obj.clone()); + } + + if let Some(ref mut resources) = self.resources { + if let Some(ref other_aliasing) = other.resources.as_ref().and_then(|r| r.aliasing.as_ref()) { + if resources.aliasing.is_none() { + resources.aliasing = Some(HashMap::new()); + } + let aliasing = resources.aliasing.as_mut().unwrap(); + for (hash, aliased) in other_aliasing { + aliasing.entry(*hash).or_insert_with(|| aliased.clone()); + } + } + } else { + self.resources = other.resources.clone(); + } + } + + pub fn copy(&self) -> Context { + let mut another = Context::new(); + another.merge(self); + another + } + + // if a dependency is in more than one subobject, it will pick one of the shortest dep chains. + pub fn find_missing_deps(&self, hash: &Hash, chain: Option>, missing: Option>>) -> HashMap> { + let mut chain = chain.unwrap_or_default(); + let mut missing = missing.unwrap_or_default(); + + if let Some(literal) = self.literals.get(hash) { + for dep in &literal.dependencies { + let mut new_chain = chain.clone(); + new_chain.insert(0, *hash); + let new_missing = self.find_missing_deps(dep.hash, Some(new_chain), Some(missing)); + missing = missing.into_iter().chain(new_missing).collect(); + } + } else { + let prev_chain = missing.get(hash); + if prev_chain.is_none() || chain.len() < prev_chain.unwrap().len() { + missing.insert(*hash, chain); + } + } + + missing + } + + pub fn check_literal_hashes(&self) -> bool { + self.literals.iter().all(|(hash, literal)| { + hash == &literal.hash && literal.validate_hash() + }) + } + + pub fn check_root_hashes(&self) -> bool { + self.root_hashes.iter().all(|hash| self.literals.contains_key(hash)) + } +}