use std::{fs, path::PathBuf};
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use gxhash::HashMap;
use s0_cdc::hash128;
use super::PeerError;
use crate::{
s0_const::Perm,
s0_profile::{ProfileDraft, SecretKey, SignedProfile},
s0_region::Region,
};
#[derive(Debug, Clone, Default)]
pub enum KeyMode {
#[default]
Ephemeral,
Fixed([u8; 32]),
Persistent(PathBuf),
}
impl KeyMode {
pub fn resolve_secret_key(&self) -> Result<SecretKey, PeerError> {
match self {
Self::Ephemeral => {
let mut seed = [0u8; 32];
for chunk in seed.as_chunks_mut::<8>().0 {
chunk.copy_from_slice(&fastrand::u64(..).to_le_bytes());
}
Ok(SecretKey::from_bytes(&seed))
}
Self::Fixed(seed) => Ok(SecretKey::from_bytes(seed)),
Self::Persistent(dir) => {
fs::create_dir_all(dir)?;
let sk_path = dir.join("iroh.key");
let id_path = dir.join("iroh.id");
if let Ok(text) = fs::read_to_string(&sk_path)
&& let Some(bytes) = decode_id_b64(text.trim())
{
let sk = SecretKey::from_bytes(&bytes);
let _ = fs::write(
&id_path,
format!("{}\n", encode_id_b64(sk.public().as_bytes())),
);
return Ok(sk);
}
let sk = Self::Ephemeral.resolve_secret_key()?;
fs::write(&sk_path, format!("{}\n", encode_id_b64(&sk.to_bytes())))?;
fs::write(
&id_path,
format!("{}\n", encode_id_b64(sk.public().as_bytes())),
)?;
Ok(sk)
}
}
}
}
#[inline]
pub fn encode_id_b64(bytes: &[u8; 32]) -> String {
URL_SAFE_NO_PAD.encode(bytes)
}
#[inline]
pub fn decode_id_b64(s: &str) -> Option<[u8; 32]> {
let clean = s.trim().strip_prefix("iroh:").unwrap_or(s.trim());
let raw = URL_SAFE_NO_PAD.decode(clean).ok()?;
raw.try_into().ok()
}
#[inline]
pub fn derive_share_id(name: &str) -> [u8; 16] {
hash128(name.as_bytes())
}
#[derive(Debug, Clone, Copy)]
pub struct PeerQuota {
pub perm: Perm,
pub max_rate: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResolvedAccess {
pub perm: Perm,
pub eff_rate: u64,
}
#[derive(Debug, Clone)]
pub struct ShareConfig {
pub share_id: [u8; 16],
pub root: PathBuf,
pub auth_required: bool,
pub default_perm: Perm,
pub default_max_rate: u64,
pub rw_token: Option<[u8; 16]>,
pub peer_quotas: HashMap<[u8; 32], PeerQuota>,
}
impl ShareConfig {
pub fn public_ro(name: &str, root: impl Into<PathBuf>, default_max_rate: u64) -> Self {
Self {
share_id: derive_share_id(name),
root: root.into(),
auth_required: false,
default_perm: Perm::Ro,
default_max_rate,
rw_token: None,
peer_quotas: HashMap::default(),
}
}
pub fn public_rw(name: &str, root: impl Into<PathBuf>, default_max_rate: u64) -> Self {
Self {
share_id: derive_share_id(name),
root: root.into(),
auth_required: false,
default_perm: Perm::Rw,
default_max_rate,
rw_token: None,
peer_quotas: HashMap::default(),
}
}
pub fn with_auth(mut self, auth_required: bool) -> Self {
self.auth_required = auth_required;
self
}
pub fn with_rw_token(mut self, token: [u8; 16]) -> Self {
self.rw_token = Some(token);
self
}
pub fn grant_peer(mut self, endpoint_id: [u8; 32], perm: Perm, max_rate: u64) -> Self {
self
.peer_quotas
.insert(endpoint_id, PeerQuota { perm, max_rate });
self
}
pub fn resolve_access(
&self,
remote_id: &[u8; 32],
req_rate: u64,
token: Option<[u8; 16]>,
) -> Option<ResolvedAccess> {
let has_valid_rw_token = self.rw_token.is_some() && self.rw_token == token;
if let Some(q) = self.peer_quotas.get(remote_id) {
let perm = if has_valid_rw_token { Perm::Rw } else { q.perm };
let peer_or_def = min_nonzero(q.max_rate, self.default_max_rate);
return Some(ResolvedAccess {
perm,
eff_rate: min_nonzero(req_rate, peer_or_def),
});
}
if self.auth_required && !has_valid_rw_token {
return None;
}
let perm = if has_valid_rw_token {
Perm::Rw
} else {
self.default_perm
};
Some(ResolvedAccess {
perm,
eff_rate: min_nonzero(req_rate, self.default_max_rate),
})
}
}
#[inline]
pub const fn min_nonzero(a: u64, b: u64) -> u64 {
match (a, b) {
(0, x) | (x, 0) => x,
(x, y) => {
if x < y {
x
} else {
y
}
}
}
}
#[derive(Debug, Clone)]
pub struct NodeConfig {
pub key_mode: KeyMode,
pub profile: ProfileDraft,
pub enable_iroh: bool,
}
impl Default for NodeConfig {
fn default() -> Self {
Self {
key_mode: KeyMode::Ephemeral,
profile: ProfileDraft::new("peer", Region::Unknown),
enable_iroh: false,
}
}
}
impl NodeConfig {
pub fn sign_profile(&self, sk: &SecretKey) -> Result<SignedProfile, PeerError> {
Ok(self.profile.clone().sign(sk)?)
}
}