Skip to content

Commit 6cc8fab

Browse files
committed
Add example that just sets up a store and allows writes to it via PUSH requests.
1 parent 45b4dc9 commit 6cc8fab

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

examples/writable-store.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//! A blob store that allows writes from a set of authorized clients.
2+
mod common;
3+
use std::{
4+
collections::HashSet,
5+
path::PathBuf,
6+
};
7+
8+
use anyhow::Result;
9+
use clap::Parser;
10+
use common::setup_logging;
11+
use iroh::{protocol::Router, EndpointAddr, EndpointId};
12+
use iroh_blobs::{
13+
BlobsProtocol, api::Store, provider::events::{
14+
AbortReason, ConnectMode, EventMask, EventSender, ProviderMessage, RequestMode,
15+
}, store::{fs::FsStore, mem::MemStore},
16+
};
17+
use iroh_tickets::endpoint::EndpointTicket;
18+
19+
use crate::common::get_or_generate_secret_key;
20+
21+
#[derive(Debug, Parser)]
22+
#[command(version, about)]
23+
pub struct Args {
24+
/// Path for the blob store.
25+
path: Option<PathBuf>,
26+
#[clap(long("allow"))]
27+
/// Endpoints that are allowed to download content.
28+
allowed_endpoints: Vec<EndpointId>,
29+
}
30+
31+
fn limit_by_node_id(allowed_nodes: HashSet<EndpointId>) -> EventSender {
32+
let mask = EventMask {
33+
// We want a request for each incoming connection so we can accept
34+
// or reject them. We don't need any other events.
35+
connected: ConnectMode::Intercept,
36+
// We explicitly allow all request types without any logging.
37+
push: RequestMode::None,
38+
get: RequestMode::None,
39+
get_many: RequestMode::None,
40+
..EventMask::DEFAULT
41+
};
42+
let (tx, mut rx) = EventSender::channel(32, mask);
43+
n0_future::task::spawn(async move {
44+
while let Some(msg) = rx.recv().await {
45+
if let ProviderMessage::ClientConnected(msg) = msg {
46+
let res: std::result::Result<(), AbortReason> = match msg.endpoint_id {
47+
Some(endpoint_id) if allowed_nodes.contains(&endpoint_id) => {
48+
println!("Client connected: {endpoint_id}");
49+
Ok(())
50+
}
51+
Some(endpoint_id) => {
52+
println!("Client rejected: {endpoint_id}");
53+
Err(AbortReason::Permission)
54+
}
55+
None => {
56+
println!("Client rejected: no endpoint id");
57+
Err(AbortReason::Permission)
58+
}
59+
};
60+
msg.tx.send(res).await.ok();
61+
}
62+
}
63+
});
64+
tx
65+
}
66+
67+
#[tokio::main]
68+
async fn main() -> Result<()> {
69+
setup_logging();
70+
let args = Args::parse();
71+
let Args {
72+
path,
73+
allowed_endpoints,
74+
} = args;
75+
let allowed_endpoints = allowed_endpoints.into_iter().collect::<HashSet<_>>();
76+
let store: Store = if let Some(path) = path {
77+
let abs_path = std::path::absolute(path)?;
78+
(*FsStore::load(abs_path).await?).clone()
79+
} else {
80+
(*MemStore::new()).clone()
81+
};
82+
let events = limit_by_node_id(allowed_endpoints.clone());
83+
let (router, addr) = setup(store, events).await?;
84+
let ticket: EndpointTicket = addr.into();
85+
println!("Endpoint id: {}", router.endpoint().id());
86+
println!("Ticket: {}", ticket);
87+
for id in &allowed_endpoints {
88+
println!("Allowed endpoint: {id}");
89+
}
90+
91+
tokio::signal::ctrl_c().await?;
92+
router.shutdown().await?;
93+
Ok(())
94+
}
95+
96+
async fn setup(store: Store, events: EventSender) -> Result<(Router, EndpointAddr)> {
97+
let secret = get_or_generate_secret_key()?;
98+
let endpoint = iroh::Endpoint::builder().secret_key(secret).bind().await?;
99+
endpoint.online().await;
100+
let addr = endpoint.addr();
101+
let blobs = BlobsProtocol::new(&store, Some(events));
102+
let router = Router::builder(endpoint)
103+
.accept(iroh_blobs::ALPN, blobs)
104+
.spawn();
105+
Ok((router, addr))
106+
}

0 commit comments

Comments
 (0)