use anyhow::Result;
use noosphere_ipfs::{IpfsStore, KuboClient};
use noosphere_storage::{BlockStoreRetry, MemoryStore, UcanStore};
use std::{net::TcpListener, path::Path, sync::Arc};
use tempfile::TempDir;
use crate::{
cli::ConfigSetCommand,
commands::{key::key_create, sphere::config_set, sphere::sphere_create},
paths::STORAGE_DIRECTORY,
workspace::{CliSphereContext, Workspace},
};
use noosphere::{
NoosphereContext, NoosphereContextConfiguration, NoosphereNetwork, NoosphereSecurity,
NoosphereStorage, NoosphereStorageConfig, NoosphereStoragePath,
};
use noosphere_core::data::{Did, Mnemonic};
use noosphere_gateway::{Gateway, SingleTenantGatewayManager};
use noosphere_ns::{helpers::NameSystemNetwork, server::start_name_system_api_server};
use tokio::{sync::Mutex, task::JoinHandle};
use url::Url;
pub fn temporary_workspace() -> Result<(Workspace, (TempDir, TempDir))> {
let root = TempDir::new()?;
let global_root = TempDir::new()?;
Ok((
Workspace::new(root.path(), Some(global_root.path()), None)?,
(root, global_root),
))
}
async fn start_gateway_for_workspace(
workspace: &Workspace,
client_sphere_identity: &Did,
ipfs_url: &Url,
ns_url: &Url,
) -> Result<(Url, JoinHandle<()>)> {
let gateway_listener = TcpListener::bind("127.0.0.1:0")?;
let gateway_address = gateway_listener.local_addr()?;
let gateway_url = Url::parse(&format!(
"http://{}:{}",
gateway_address.ip(),
gateway_address.port()
))?;
let gateway_sphere_context = workspace.sphere_context().await?;
let client_sphere_identity = client_sphere_identity.to_owned();
let ns_url = ns_url.clone();
let ipfs_url = ipfs_url.clone();
let manager =
SingleTenantGatewayManager::new(gateway_sphere_context, client_sphere_identity).await?;
let join_handle = tokio::spawn(async move {
let gateway = Gateway::new(manager, ipfs_url, ns_url, None).unwrap();
gateway.start(gateway_listener).await.unwrap()
});
Ok((gateway_url, join_handle))
}
pub async fn start_name_system_server(ipfs_url: &Url) -> Result<(Url, JoinHandle<()>)> {
let use_validation = false;
let store = if use_validation {
let inner = MemoryStore::default();
let inner = IpfsStore::new(inner, Some(KuboClient::new(ipfs_url).unwrap()));
let inner = BlockStoreRetry::from(inner);
let inner = UcanStore(inner);
Some(inner)
} else {
None
};
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let url = Url::parse(format!("http://{}:{}", address.ip(), address.port()).as_str()).unwrap();
Ok((
url,
tokio::spawn(async move {
let mut network = NameSystemNetwork::generate(2, store).await.unwrap();
let node = network.nodes_mut().pop().unwrap();
start_name_system_api_server(Arc::new(node), listener)
.await
.unwrap();
}),
))
}
pub struct SphereData {
pub identity: Did,
pub mnemonic: Mnemonic,
pub workspace: Workspace,
pub key_name: String,
temp_dirs: (tempfile::TempDir, tempfile::TempDir),
}
impl SphereData {
pub async fn simulate_storage_corruption(&self) -> Result<()> {
let paths = self.workspace.require_sphere_paths()?;
let storage_path = paths.sphere().join(STORAGE_DIRECTORY);
let mut file_entries = tokio::fs::read_dir(storage_path).await?;
let zeroes: [u8; 1024] = [0; 1024];
while let Some(entry) = file_entries.next_entry().await? {
if entry.file_type().await?.is_file() {
tokio::fs::write(entry.path(), &zeroes).await?;
}
}
Ok(())
}
pub fn sphere_root(&self) -> &Path {
self.temp_dirs.0.path()
}
pub fn global_root(&self) -> &Path {
self.temp_dirs.1.path()
}
pub async fn as_noosphere_context(&self) -> Result<NoosphereContext> {
NoosphereContext::new(NoosphereContextConfiguration {
storage: NoosphereStorage {
path: NoosphereStoragePath::Unscoped(self.sphere_root().to_owned()),
config: NoosphereStorageConfig::default(),
},
security: NoosphereSecurity::Insecure {
path: self.global_root().to_owned(),
},
network: NoosphereNetwork::Http {
gateway_api: Some(self.workspace.gateway_url().await?),
ipfs_gateway_url: None,
},
})
}
}
pub struct SpherePair {
pub name: String,
pub client: SphereData,
pub gateway: SphereData,
pub ns_url: Url,
pub ipfs_url: Url,
gateway_url: Option<Url>,
gateway_task: Option<JoinHandle<()>>,
}
impl SpherePair {
pub async fn new(name: &str, ipfs_url: &Url, ns_url: &Url) -> Result<Self> {
let (mut client_workspace, client_temp_dirs) = temporary_workspace()?;
let (mut gateway_workspace, gateway_temp_dirs) = temporary_workspace()?;
let client_key_name = format!("{}-CLIENT_KEY", name);
let gateway_key_name = format!("{}-GATEWAY_KEY", name);
key_create(&client_key_name, &client_workspace).await?;
key_create(&gateway_key_name, &gateway_workspace).await?;
let (client_identity, client_mnemonic) =
sphere_create(&client_key_name, &mut client_workspace).await?;
let (gateway_identity, gateway_mnemonic) =
sphere_create(&gateway_key_name, &mut gateway_workspace).await?;
config_set(
ConfigSetCommand::Counterpart {
did: client_identity.clone().into(),
},
&gateway_workspace,
)
.await?;
let client = SphereData {
identity: client_identity,
mnemonic: client_mnemonic,
key_name: client_key_name,
workspace: client_workspace,
temp_dirs: client_temp_dirs,
};
let gateway = SphereData {
identity: gateway_identity,
mnemonic: gateway_mnemonic,
key_name: gateway_key_name,
workspace: gateway_workspace,
temp_dirs: gateway_temp_dirs,
};
Ok(SpherePair {
name: name.into(),
client,
gateway,
gateway_url: None,
gateway_task: None,
ipfs_url: ipfs_url.to_owned(),
ns_url: ns_url.to_owned(),
})
}
pub async fn start_gateway(&mut self) -> Result<()> {
if self.gateway_task.is_some() {
return Err(anyhow::anyhow!("Gateway already started."));
}
let (gateway_url, gateway_task) = start_gateway_for_workspace(
&self.gateway.workspace,
&self.client.identity,
&self.ipfs_url,
&self.ns_url,
)
.await?;
let client_sphere_context = self.client.workspace.sphere_context().await?;
client_sphere_context
.lock()
.await
.configure_gateway_url(Some(&gateway_url))
.await?;
self.gateway_url = Some(gateway_url);
self.gateway_task = Some(gateway_task);
Ok(())
}
pub async fn stop_gateway(&mut self) -> Result<()> {
if let Some(gateway_task) = self.gateway_task.take() {
gateway_task.abort();
} else {
return Ok(());
}
self.gateway_url = None;
let client_sphere_context = self.client.workspace.sphere_context().await?;
client_sphere_context
.lock()
.await
.configure_gateway_url(None)
.await?;
Ok(())
}
pub async fn sphere_context(&self) -> Result<Arc<Mutex<CliSphereContext>>> {
self.client.workspace.sphere_context().await
}
pub async fn spawn<T, F, Fut>(&self, f: F) -> Result<T>
where
T: Send + 'static,
F: FnOnce(Arc<Mutex<CliSphereContext>>) -> Fut + Send + 'static,
Fut: std::future::Future<Output = Result<T>> + Send + 'static,
{
let context = self.sphere_context().await?;
tokio::spawn(f(context)).await?
}
}
impl Drop for SpherePair {
fn drop(&mut self) {
if let Some(gateway_task) = self.gateway_task.take() {
gateway_task.abort();
}
}
}