use super::register::EntryHash;
use crate::{Error, Result, Safe};
use log::debug;
use sn_url::{SafeContentType, SafeUrl, XorUrl};
use std::collections::BTreeSet;
use xor_name::XorName;
pub type MultimapKey = Vec<u8>;
pub type MultimapValue = Vec<u8>;
pub type MultimapKeyValue = (MultimapKey, MultimapValue);
pub type MultimapKeyValues = BTreeSet<(EntryHash, MultimapKeyValue)>;
const MULTIMAP_REMOVED_MARK: &[u8] = b"";
impl Safe {
pub async fn multimap_create(
&self,
name: Option<XorName>,
type_tag: u64,
private: bool,
) -> Result<XorUrl> {
debug!("Creating a Multimap");
let xorname = self
.safe_client
.store_register(name, type_tag, None, private)
.await?;
let xorurl = SafeUrl::encode_register(
xorname,
type_tag,
SafeContentType::Multimap,
self.xorurl_base,
private,
)?;
Ok(xorurl)
}
pub async fn multimap_get_by_key(&self, url: &str, key: &[u8]) -> Result<MultimapKeyValues> {
debug!("Getting value by key from Multimap at: {}", url);
let (safeurl, _) = self.parse_and_resolve_url(url).await?;
self.fetch_multimap_value_by_key(&safeurl, key).await
}
pub async fn multimap_get_by_hash(
&self,
url: &str,
hash: EntryHash,
) -> Result<Option<MultimapKeyValue>> {
debug!("Getting value by hash from Multimap at: {}", url);
let (safeurl, _) = self.parse_and_resolve_url(url).await?;
self.fetch_multimap_value_by_hash(&safeurl, hash).await
}
pub(crate) async fn fetch_multimap_value_by_key(
&self,
safeurl: &SafeUrl,
key: &[u8],
) -> Result<MultimapKeyValues> {
let entries = self.fetch_multimap_values(safeurl).await?;
Ok(entries
.into_iter()
.filter(|(_, (entry_key, _))| entry_key == key)
.collect())
}
pub async fn multimap_insert(
&self,
url: &str,
entry: MultimapKeyValue,
replace: BTreeSet<EntryHash>,
) -> Result<EntryHash> {
debug!("Inserting '{:?}' into Multimap at {}", entry, url);
let serialised_entry = rmp_serde::to_vec_named(&entry).map_err(|err| {
Error::Serialisation(format!(
"Couldn't serialise the Multimap entry '{:?}': {:?}",
entry, err
))
})?;
let safeurl = Safe::parse_url(url)?;
let address = safeurl.register_address()?;
self.safe_client
.write_to_register(address, serialised_entry, replace)
.await
}
pub async fn multimap_remove(
&self,
url: &str,
to_remove: BTreeSet<EntryHash>,
) -> Result<EntryHash> {
debug!("Removing from Multimap at {}: {:?}", url, to_remove);
let safeurl = Safe::parse_url(url)?;
let address = safeurl.register_address()?;
let hash = self
.safe_client
.write_to_register(address, MULTIMAP_REMOVED_MARK.to_vec(), to_remove)
.await?;
Ok(hash)
}
pub(crate) async fn fetch_multimap_values(
&self,
safeurl: &SafeUrl,
) -> Result<MultimapKeyValues> {
let entries = match self.fetch_register_entries(safeurl).await {
Ok(data) => {
debug!("Multimap retrieved...");
Ok(data)
}
Err(Error::EmptyContent(_)) => Err(Error::EmptyContent(format!(
"Multimap found at \"{}\" was empty",
safeurl
))),
Err(Error::ContentNotFound(_)) => Err(Error::ContentNotFound(
"No Multimap found at this address".to_string(),
)),
other => other,
}?;
let mut multimap_key_vals = MultimapKeyValues::new();
for (hash, entry) in entries.iter() {
if entry == MULTIMAP_REMOVED_MARK {
continue;
}
let key_val = Self::decode_multimap_entry(&entry)?;
multimap_key_vals.insert((*hash, key_val));
}
Ok(multimap_key_vals)
}
pub(crate) async fn fetch_multimap_value_by_hash(
&self,
safeurl: &SafeUrl,
hash: EntryHash,
) -> Result<Option<MultimapKeyValue>> {
let entry = match self.fetch_register_entry(safeurl, hash).await {
Ok(data) => {
debug!("Multimap retrieved...");
Ok(data)
}
Err(Error::EmptyContent(_)) => Err(Error::EmptyContent(format!(
"Multimap found at \"{}\" was empty",
safeurl
))),
Err(Error::ContentNotFound(_)) => Err(Error::ContentNotFound(
"No Multimap found at this address".to_string(),
)),
Err(other) => Err(other),
}?;
if entry == MULTIMAP_REMOVED_MARK {
Ok(None)
} else {
let key_val = Self::decode_multimap_entry(&entry)?;
Ok(Some(key_val))
}
}
fn decode_multimap_entry(entry: &[u8]) -> Result<MultimapKeyValue> {
rmp_serde::from_slice(&entry)
.map_err(|err| Error::ContentError(format!("Couldn't parse Multimap entry: {:?}", err)))
}
}
#[cfg(test)]
mod tests {
use crate::{api::app::test_helpers::new_safe_instance, retry_loop, retry_loop_for_pattern};
use anyhow::Result;
use std::collections::BTreeSet;
#[tokio::test]
async fn test_multimap_create() -> Result<()> {
let safe = new_safe_instance().await?;
let xorurl = safe.multimap_create(None, 25_000, false).await?;
let xorurl_priv = safe.multimap_create(None, 25_000, true).await?;
let key = b"".to_vec();
let received_data = retry_loop!(safe.multimap_get_by_key(&xorurl, &key));
let received_data_priv = retry_loop!(safe.multimap_get_by_key(&xorurl_priv, &key));
assert_eq!(received_data, Default::default());
assert_eq!(received_data_priv, Default::default());
Ok(())
}
#[tokio::test]
async fn test_multimap_insert() -> Result<()> {
let safe = new_safe_instance().await?;
let key = b"key".to_vec();
let val = b"value".to_vec();
let key_val = (key.clone(), val.clone());
let val2 = b"value2".to_vec();
let key_val2 = (key.clone(), val2.clone());
let xorurl = safe.multimap_create(None, 25_000, false).await?;
let xorurl_priv = safe.multimap_create(None, 25_000, true).await?;
let _ = retry_loop!(safe.multimap_get_by_key(&xorurl, &key));
let _ = retry_loop!(safe.multimap_get_by_key(&xorurl_priv, &key));
let hash = safe
.multimap_insert(&xorurl, key_val.clone(), BTreeSet::new())
.await?;
let hash_priv = safe
.multimap_insert(&xorurl_priv, key_val.clone(), BTreeSet::new())
.await?;
let received_data = retry_loop_for_pattern!(safe.multimap_get_by_key(&xorurl, &key), Ok(v) if !v.is_empty())?;
let received_data_priv = retry_loop_for_pattern!(safe.multimap_get_by_key(&xorurl_priv, &key), Ok(v) if !v.is_empty())?;
assert_eq!(
received_data,
vec![(hash, key_val.clone())].into_iter().collect()
);
assert_eq!(
received_data_priv,
vec![(hash_priv, key_val.clone())].into_iter().collect()
);
let hashes_to_replace = vec![hash].into_iter().collect();
let hash2 = safe
.multimap_insert(&xorurl, key_val2.clone(), hashes_to_replace)
.await?;
let hashes_priv_to_replace = vec![hash_priv].into_iter().collect();
let hash_priv2 = safe
.multimap_insert(&xorurl_priv, key_val2.clone(), hashes_priv_to_replace)
.await?;
let received_data = retry_loop_for_pattern!(safe.multimap_get_by_key(&xorurl, &key),
Ok(v) if v.iter().all(|(_, kv)| *kv != key_val))?;
let received_data_priv = retry_loop_for_pattern!(safe.multimap_get_by_key(&xorurl_priv, &key),
Ok(v) if v.iter().all(|(_, kv)| *kv != key_val))?;
assert_eq!(
received_data,
vec![(hash2, key_val2.clone())].into_iter().collect()
);
assert_eq!(
received_data_priv,
vec![(hash_priv2, key_val2.clone())].into_iter().collect()
);
Ok(())
}
#[tokio::test]
async fn test_multimap_get_by_hash() -> Result<()> {
let safe = new_safe_instance().await?;
let key = b"key".to_vec();
let val = b"value".to_vec();
let key_val = (key.clone(), val.clone());
let key2 = b"key2".to_vec();
let val2 = b"value2".to_vec();
let key_val2 = (key2.clone(), val2.clone());
let xorurl = safe.multimap_create(None, 25_000, false).await?;
let xorurl_priv = safe.multimap_create(None, 25_000, true).await?;
let _ = retry_loop!(safe.multimap_get_by_key(&xorurl, &key));
let _ = retry_loop!(safe.multimap_get_by_key(&xorurl_priv, &key));
let hash = safe
.multimap_insert(&xorurl, key_val.clone(), BTreeSet::new())
.await?;
let hash2 = safe
.multimap_insert(&xorurl, key_val2.clone(), BTreeSet::new())
.await?;
let hash_priv = safe
.multimap_insert(&xorurl_priv, key_val.clone(), BTreeSet::new())
.await?;
let hash_priv2 = safe
.multimap_insert(&xorurl_priv, key_val2.clone(), BTreeSet::new())
.await?;
let received_data = retry_loop_for_pattern!(safe.multimap_get_by_hash(&xorurl, hash), Ok(v) if v.is_some())?;
let received_data_priv = retry_loop_for_pattern!(safe.multimap_get_by_hash(&xorurl_priv, hash_priv), Ok(v) if v.is_some())?;
assert_eq!(received_data, Some(key_val.clone()));
assert_eq!(received_data_priv, Some(key_val));
let received_data = retry_loop_for_pattern!(safe.multimap_get_by_hash(&xorurl, hash2), Ok(v) if v.is_some())?;
let received_data_priv = retry_loop_for_pattern!(safe.multimap_get_by_hash(&xorurl_priv, hash_priv2), Ok(v) if v.is_some())?;
assert_eq!(received_data, Some(key_val2.clone()));
assert_eq!(received_data_priv, Some(key_val2));
Ok(())
}
#[tokio::test]
async fn test_multimap_remove() -> Result<()> {
let safe = new_safe_instance().await?;
let key = b"key".to_vec();
let val = b"value".to_vec();
let key_val = (key.clone(), val.clone());
let xorurl = safe.multimap_create(None, 25_000, false).await?;
let xorurl_priv = safe.multimap_create(None, 25_000, true).await?;
let _ = retry_loop!(safe.multimap_get_by_key(&xorurl, &key));
let _ = retry_loop!(safe.multimap_get_by_key(&xorurl_priv, &key));
let hash = safe
.multimap_insert(&xorurl, key_val.clone(), BTreeSet::new())
.await?;
let hash_priv = safe
.multimap_insert(&xorurl_priv, key_val, BTreeSet::new())
.await?;
let received_data = retry_loop_for_pattern!(safe.multimap_get_by_key(&xorurl, &key), Ok(v) if !v.is_empty())?;
let received_data_priv = retry_loop_for_pattern!(safe.multimap_get_by_key(&xorurl_priv, &key), Ok(v) if !v.is_empty())?;
assert_eq!(received_data.len(), 1);
let (read_hash, read_key_val) = received_data.into_iter().next().unwrap();
assert_eq!(
(read_hash, read_key_val),
(hash, (key.clone(), val.clone()))
);
let hashes_to_remove = vec![hash].into_iter().collect();
let removed_mark_hash = safe.multimap_remove(&xorurl, hashes_to_remove).await?;
assert_ne!(removed_mark_hash, hash);
assert_eq!(
retry_loop_for_pattern!(safe.multimap_get_by_key(&xorurl, &key),
Ok(entries) if entries.is_empty())?,
Default::default()
);
assert_eq!(received_data_priv.len(), 1);
let (read_hash, read_key_val) = received_data_priv.into_iter().next().unwrap();
assert_eq!((read_hash, read_key_val), (hash_priv, (key.clone(), val)));
let hashes_to_remove = vec![hash_priv].into_iter().collect();
let removed_mark_hash = safe.multimap_remove(&xorurl_priv, hashes_to_remove).await?;
assert_ne!(removed_mark_hash, hash_priv);
assert_eq!(
retry_loop_for_pattern!(
safe.multimap_get_by_key(&xorurl_priv, &key),
Ok(entries) if entries.is_empty())?,
Default::default()
);
Ok(())
}
}