Skip to content

Commit

Permalink
Merge pull request #10 from OpenArchive/group-urls
Browse files Browse the repository at this point in the history
Added group url generation and parsing
  • Loading branch information
RangerMauve authored Oct 3, 2024
2 parents 2652c55 + 31f50c9 commit e640421
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 71 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
- name: Run tests
env:
RUST_MIN_STACK: 8388608
run: cargo test --verbose
run: cargo test --verbose -- --test-threads=1 --nocapture
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ anyhow = "1.0.86"
tokio = {version ="1.39.3", features=["full"] }
async-stream = "0.3.5"
futures-core = "0.3.30"
url = "2.5.2"
hex = "0.4.3"
150 changes: 106 additions & 44 deletions src/backend.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
use crate::common::{CommonKeypair, DHTEntity};
use crate::group::Group;
use crate::group::{Group, URL_DHT_KEY, URL_ENCRYPTION_KEY, URL_PUBLIC_KEY, URL_SECRET_KEY};
use crate::repo::Repo;
use anyhow::{anyhow, Result};
use clap::builder::Str;
use iroh_blobs::Hash;
use std::collections::HashMap;
use std::mem;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::fs;
use tokio::sync::{mpsc::{self, Receiver}, oneshot, broadcast};
use tokio::sync::{
broadcast,
mpsc::{self, Receiver},
oneshot,
};
use tracing::info;
use url::Url;
use veilid_core::{
api_startup_config, vld0_generate_keypair, CryptoKey, CryptoSystem, CryptoSystemVLD0,
CryptoTyped, DHTSchema, KeyPair, ProtectedStore, RoutingContext, SharedSecret, UpdateCallback,
VeilidAPI, VeilidConfigInner, VeilidUpdate, CRYPTO_KIND_VLD0, TypedKey, VeilidConfigProtectedStore
CryptoTyped, DHTSchema, KeyPair, ProtectedStore, RoutingContext, SharedSecret, TypedKey,
UpdateCallback, VeilidAPI, VeilidConfigInner, VeilidConfigProtectedStore, VeilidUpdate,
CRYPTO_KEY_LENGTH, CRYPTO_KIND_VLD0,
};
use xdg::BaseDirectories;

Expand All @@ -32,7 +39,7 @@ impl Backend {
path: base_path.to_path_buf(),
port,
veilid_api: None,
update_rx: None,
update_rx: None,
groups: HashMap::new(),
repos: HashMap::new(),
})
Expand Down Expand Up @@ -129,7 +136,10 @@ impl Backend {
Ok(())
}

async fn wait_for_network(&self, mut update_rx: broadcast::Receiver<VeilidUpdate>) -> Result<()> {
async fn wait_for_network(
&self,
mut update_rx: broadcast::Receiver<VeilidUpdate>,
) -> Result<()> {
while let Ok(update) = update_rx.recv().await {
if let VeilidUpdate::Attachment(attachment_state) = update {
if attachment_state.public_internet_ready {
Expand All @@ -140,7 +150,48 @@ impl Backend {
}
Ok(())
}


pub async fn join_from_url(&mut self, url_string: &str) -> Result<Box<Group>> {
let keys = parse_url(url_string)?;
self.join_group(keys).await
}

pub async fn join_group(&mut self, keys: CommonKeypair) -> Result<Box<Group>> {
let routing_context = self.veilid_api.as_ref().unwrap().routing_context()?;
let crypto_system = CryptoSystemVLD0::new(self.veilid_api.as_ref().unwrap().crypto()?);

let record_key = TypedKey::new(CRYPTO_KIND_VLD0, keys.id);
// First open the DHT record
let dht_record = routing_context
.open_dht_record(record_key.clone(), None) // Don't pass a writer here yet
.await?;

// Use the owner key from the DHT record as the default writer
let owner_key = dht_record.owner(); // Call the owner() method to get the owner key

// Reopen the DHT record with the owner key as the writer
let dht_record = routing_context
.open_dht_record(
record_key.clone(),
Some(KeyPair::new(
owner_key.clone(),
keys.secret_key.clone().unwrap(),
)),
)
.await?;

let group = Group {
dht_record: dht_record.clone(),
encryption_key: keys.encryption_key.clone(),
routing_context: Arc::new(routing_context),
crypto_system,
repos: Vec::new(),
};
self.groups.insert(group.id(), Box::new(group.clone()));

Ok(Box::new(group))
}

pub async fn create_group(&mut self) -> Result<Group> {
let veilid = self
.veilid_api
Expand All @@ -149,75 +200,50 @@ impl Backend {
let routing_context = veilid.routing_context()?;
let schema = DHTSchema::dflt(3)?;
let kind = Some(CRYPTO_KIND_VLD0);

let dht_record = routing_context.create_dht_record(schema, kind).await?;
let keypair = vld0_generate_keypair();
let crypto_system = CryptoSystemVLD0::new(veilid.crypto()?);

let encryption_key = crypto_system.random_shared_secret();

let group = Group::new(
dht_record.clone(),
encryption_key,
Arc::new(routing_context),
crypto_system,
);

let protected_store = veilid.protected_store().unwrap();
CommonKeypair {
id: group.id(),
public_key: dht_record.owner().clone(),
id: group.id(),
public_key: dht_record.owner().clone(),
secret_key: group.get_secret_key(),
encryption_key: group.get_encryption_key(),
}
.store_keypair(&protected_store)
.await
.map_err(|e| anyhow!(e))?;

self.groups.insert(group.id(), Box::new(group.clone()));

Ok(group)
}

pub async fn get_group(&mut self, record_key: TypedKey) -> Result<Box<Group>> {
if let Some(group) = self.groups.get(&record_key.value) {
return Ok(group.clone());
}

let routing_context = self.veilid_api.as_ref().unwrap().routing_context()?;
let protected_store = self.veilid_api.as_ref().unwrap().protected_store().unwrap();

// Load the keypair associated with the record_key from the protected store
let retrieved_keypair = CommonKeypair::load_keypair(&protected_store, &record_key.value)
.await
.map_err(|_| anyhow!("Failed to load keypair"))?;

let crypto_system = CryptoSystemVLD0::new(self.veilid_api.as_ref().unwrap().crypto()?);

// First open the DHT record
let dht_record = routing_context
.open_dht_record(record_key.clone(), None) // Don't pass a writer here yet
.await?;

// Use the owner key from the DHT record as the default writer
let owner_key = dht_record.owner(); // Call the owner() method to get the owner key

// Reopen the DHT record with the owner key as the writer
let dht_record = routing_context
.open_dht_record(record_key.clone(), Some(KeyPair::new(owner_key.clone(), retrieved_keypair.secret_key.clone().unwrap())))
.await?;


let group = Group {
dht_record: dht_record.clone(),
encryption_key: retrieved_keypair.encryption_key.clone(),
routing_context: Arc::new(routing_context),
crypto_system,
repos: Vec::new(),
};
self.groups.insert(group.id(), Box::new(group.clone()));

Ok(Box::new(group))
self.join_group(retrieved_keypair).await
}

pub async fn list_groups(&self) -> Result<Vec<Box<Group>>> {
Expand Down Expand Up @@ -320,9 +346,45 @@ impl Backend {

pub fn subscribe_updates(&self) -> Option<broadcast::Receiver<VeilidUpdate>> {
self.update_rx.as_ref().map(|rx| rx.resubscribe())
}
}

pub fn get_veilid_api(&self) -> Option<&VeilidAPI> {
self.veilid_api.as_ref()
}
}

fn find_query(url: &Url, key: &str) -> Result<String> {
for (query_key, value) in url.query_pairs() {
if query_key == key {
return Ok(value.into_owned());
}
}

Err(anyhow!("Unable to find parameter {} in URL {:?}", key, url))
}

fn crypto_key_from_query(url: &Url, key: &str) -> Result<CryptoKey> {
let value = find_query(url, key)?;
let bytes = hex::decode(value)?;
let mut key_vec: [u8; CRYPTO_KEY_LENGTH] = [0; CRYPTO_KEY_LENGTH];
key_vec.copy_from_slice(bytes.as_slice());

let key = CryptoKey::from(key_vec);
Ok(key)
}

pub fn parse_url(url_string: &str) -> Result<CommonKeypair> {
let url = Url::parse(url_string)?;

let id = crypto_key_from_query(&url, URL_DHT_KEY)?;
let encryption_key = crypto_key_from_query(&url, URL_ENCRYPTION_KEY)?;
let public_key = crypto_key_from_query(&url, URL_PUBLIC_KEY)?;
let secret_key = Some(crypto_key_from_query(&url, URL_SECRET_KEY)?);

Ok(CommonKeypair {
id,
public_key,
secret_key,
encryption_key,
})
}
39 changes: 34 additions & 5 deletions src/group.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
use serde::{Serialize, Deserialize};
use eyre::{Result, Error, anyhow};
use eyre::{anyhow, Error, Result};
use hex::ToHex;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use url::Url;
use veilid_core::{
CryptoKey, DHTRecordDescriptor, CryptoTyped, CryptoSystemVLD0, RoutingContext, SharedSecret, TypedKey
CryptoKey, CryptoSystemVLD0, CryptoTyped, DHTRecordDescriptor, RoutingContext, SharedSecret,
TypedKey,
};

use crate::common::DHTEntity;
use crate::repo::Repo;

pub const PROTOCOL_SCHEME: &str = "save+dweb:";
pub const URL_DHT_KEY: &str = "dht";
pub const URL_ENCRYPTION_KEY: &str = "enc";
pub const URL_PUBLIC_KEY: &str = "pk";
pub const URL_SECRET_KEY: &str = "sk";

#[derive(Clone)]
pub struct Group {
pub dht_record: DHTRecordDescriptor,
Expand All @@ -29,7 +38,7 @@ impl Group {
encryption_key,
routing_context,
crypto_system,
repos: Vec::new(),
repos: Vec::new(),
}
}

Expand All @@ -44,7 +53,7 @@ impl Group {
pub fn owner_secret(&self) -> Option<CryptoKey> {
self.dht_record.owner_secret().cloned()
}

pub async fn add_repo(&mut self, repo: Repo) -> Result<()> {
self.repos.push(repo);
Ok(())
Expand All @@ -62,6 +71,26 @@ impl Group {
}
}

pub fn get_url(&self) -> String {
let mut url = Url::parse(format!("{0}:?", PROTOCOL_SCHEME).as_str()).unwrap();

url.query_pairs_mut()
.append_pair(URL_DHT_KEY, self.id().encode_hex::<String>().as_str())
.append_pair(
URL_ENCRYPTION_KEY,
self.get_encryption_key().encode_hex::<String>().as_str(),
)
.append_pair(
URL_PUBLIC_KEY,
self.owner_key().encode_hex::<String>().as_str(),
)
.append_pair(
URL_SECRET_KEY,
self.owner_secret().unwrap().encode_hex::<String>().as_str(),
);

url.to_string()
}
}

impl DHTEntity for Group {
Expand Down
Loading

0 comments on commit e640421

Please sign in to comment.