1use std::{collections::BTreeSet, future::Future, path::PathBuf, sync::Arc, time::Duration};
6
7use api::{
8 framing,
9 v2::client::{MessageReader, MessageWriter, RpcTransport},
10};
11use iroh::endpoint::Connection;
12use objects::{
13 object::thread_replication::{OPERATION_FORMAT, ThreadFacet},
14 store::ObjectStore,
15};
16use prost::Message;
17use repo::thread_replication::ThreadReplica;
18
19use crate::{
20 Rpc,
21 authority::RootAuthority,
22 contract::*,
23 live_replication::{self, Feed, Side},
24 replication::{
25 self, Session,
26 native::LocalReplica,
27 opening::{self, FRAME_LIMIT, validate_endpoint},
28 },
29 rpc,
30 transport::{self, Authorize, IrohTransport, Reader, Writer},
31};
32
33const TIMEOUT: Duration = Duration::from_secs(10);
34
35#[derive(Clone)]
38pub struct Peer {
39 replica: ThreadReplica,
40 endpoint: EndpointRef,
41 facets: BTreeSet<ThreadFacet>,
42 genesis: ThreadGenesisRecord,
43 authority_home: Option<PathBuf>,
44}
45impl Peer {
46 pub fn new(
47 replica: ThreadReplica,
48 endpoint: EndpointRef,
49 facets: BTreeSet<ThreadFacet>,
50 ) -> Result<Self, transport::Error> {
51 validate_endpoint(&endpoint)?;
52 if facets.is_empty() {
53 return Err(transport::Error::Protocol(
54 "replication requires an admission facet",
55 ));
56 }
57 let genesis = replica.genesis_record().map_err(io_error)?;
58 Ok(Self {
59 replica,
60 endpoint,
61 facets,
62 genesis,
63 authority_home: None,
64 })
65 }
66
67 pub fn with_device_authority(mut self, home: PathBuf) -> Self {
70 self.authority_home = Some(home);
71 self
72 }
73 fn local_replica<S: ObjectStore + Send + Sync + 'static>(
74 &self,
75 store: Arc<S>,
76 ) -> LocalReplica<S> {
77 let local = LocalReplica::new(self.replica.clone(), store);
78 match &self.authority_home {
79 Some(home) => local.with_device_authority(home.clone()),
80 None => local,
81 }
82 }
83 fn reference(&self) -> Result<ThreadRef, transport::Error> {
84 let genesis = self.replica.genesis().map_err(store_error)?;
85 Ok(ThreadRef {
86 spool: Some(SpoolRef { id: genesis.spool }),
87 id: Some(ThreadId {
88 value: self.replica.thread_id().as_bytes().to_vec(),
89 }),
90 })
91 }
92
93 pub async fn connect<S, A, G, F>(
96 &self,
97 connection: Connection,
98 destination: EndpointKind,
99 signer: A,
100 store: Arc<S>,
101 feed: &Feed,
102 authorize: G,
103 ) -> live_replication::Result<(), replication::native::Error>
104 where
105 S: ObjectStore + Send + Sync + 'static,
106 A: Authorize,
107 G: Fn() -> F + Clone + Send + Sync + 'static,
108 F: Future<Output = Result<(), transport::Error>> + Send,
109 {
110 let remote_key = *connection.remote_id().as_bytes();
111 let destination = EndpointRef {
112 public_key: remote_key.to_vec(),
113 kind: destination as i32,
114 };
115 validate_endpoint(&destination)?;
116 let thread = self.reference()?;
117 let (_, version) = self.replica.sharing(&remote_key).map_err(store_error)?;
118 let opening = ReplicationOpen {
119 thread: Some(thread.clone()),
120 facets: self
121 .facets
122 .iter()
123 .copied()
124 .map(replication::wire_facet)
125 .collect(),
126 sharing_policy_version: version.map(|v| v.as_bytes().to_vec()).unwrap_or_default(),
127 thread_genesis: Some(self.genesis.clone()),
128 budget: Some(ReadBudget {
129 max_items: 64,
130 max_frame_bytes: FRAME_LIMIT as u32,
131 max_snapshot_bytes: 0,
132 }),
133 record_formats: vec![OPERATION_FORMAT.into()],
134 session_nonce: uuid::Uuid::new_v4().as_bytes().to_vec(),
135 source: Some(self.endpoint.clone()),
136 destination: Some(destination.clone()),
137 protocol: None,
139 import_authority: None,
140 };
141 let transport = IrohTransport::new(connection, signer, FRAME_LIMIT, TIMEOUT)?;
142 let (writer, mut reader) = transport
143 .exchange(
144 rpc::SyncServiceReplicateThread::METHOD,
145 ReplicateThreadRequest {
146 body: Some(replicate_thread_request::Body::Open(opening)),
147 }
148 .encode_to_vec(),
149 )
150 .await?;
151 let bytes = reader.next().await?.ok_or(transport::Error::Protocol(
152 "replication closed before Ready",
153 ))?;
154 let response =
155 ReplicateThreadResponse::decode(bytes.as_slice()).map_err(transport::Error::from)?;
156 let Some(replicate_thread_response::Body::Ready(ready)) = response.body else {
157 return Err(transport::Error::Protocol("replication requires Ready").into());
158 };
159 let (facets, max_items) =
160 opening::validate_ready(&ready, &thread, &destination, &self.facets, 64)?;
161 let session = Session::new(self.local_replica(store), remote_key, facets, max_items)?;
162 live_replication::run(
163 session,
164 reader,
165 writer,
166 Side::Initiator,
167 feed,
168 move |activity| {
169 let authorize = authorize.clone();
170 async move {
171 if matches!(activity, live_replication::Activity::InputConsumed { .. }) {
172 Ok(())
173 } else {
174 authorize().await
175 }
176 }
177 },
178 )
179 .await
180 }
181
182 pub async fn accept<S>(
185 &self,
186 connection: Connection,
187 authority: Arc<RootAuthority>,
188 store: Arc<S>,
189 feed: &Feed,
190 ) -> live_replication::Result<(), replication::native::Error>
191 where
192 S: ObjectStore + Send + Sync + 'static,
193 {
194 let remote_key = *connection.remote_id().as_bytes();
195 let (send, mut recv) = tokio::time::timeout(TIMEOUT, connection.accept_bi())
196 .await
197 .map_err(|_| transport::Error::Timeout)?
198 .map_err(io_error)?;
199 let mut writer = Writer::new(send, FRAME_LIMIT, TIMEOUT);
200 let opening = async {
201 let mut prelude = vec![0; 6];
202 recv.read_exact(&mut prelude).await.map_err(io_error)?;
203 let method_size = u16::from_be_bytes([prelude[0], prelude[1]]) as usize;
204 let context_size =
205 u32::from_be_bytes([prelude[2], prelude[3], prelude[4], prelude[5]]) as usize;
206 if method_size == 0
207 || method_size > framing::MAX_METHOD_PATH
208 || context_size > framing::MAX_CALL_CONTEXT
209 {
210 return Err(transport::Error::Protocol(
211 "opening exceeds metadata limits",
212 ));
213 }
214 prelude.resize(6 + method_size + context_size, 0);
215 recv.read_exact(&mut prelude[6..]).await.map_err(io_error)?;
216 let (request, _) = framing::decode_request_prelude(&prelude)?
217 .ok_or(transport::Error::Protocol("incomplete opening metadata"))?;
218 if request.method != rpc::SyncServiceReplicateThread::METHOD.path {
219 return Err(transport::Error::Protocol(
220 "unsupported RPC on replication boundary",
221 ));
222 }
223 Ok(request.context)
224 };
225 let context = tokio::time::timeout(TIMEOUT, opening)
226 .await
227 .map_err(|_| transport::Error::Timeout)??;
228 let mut reader = Reader::for_method(
229 recv,
230 FRAME_LIMIT,
231 TIMEOUT,
232 rpc::SyncServiceReplicateThread::METHOD,
233 );
234 let bytes = reader
235 .next()
236 .await?
237 .ok_or(transport::Error::Protocol("missing replication opening"))?;
238 let peer = self.clone();
239 let verifier = authority.clone();
240 let checked = tokio::task::spawn_blocking(move || -> Result<_, transport::Error> {
241 let request = ReplicateThreadRequest::decode(bytes.as_slice())?;
242 let Some(replicate_thread_request::Body::Open(open)) = request.body else {
243 return Err(transport::Error::Protocol("replication requires Open"));
244 };
245 let (_, version) = peer.replica.sharing(&remote_key).map_err(store_error)?;
246 let ready = opening::accept(
247 &open,
248 &peer.reference()?,
249 &peer.endpoint,
250 remote_key,
251 &peer.facets,
252 version.map(|v| v.as_bytes().to_vec()).unwrap_or_default(),
253 )?
254 .ready;
255 let (facets, max_items) = opening::validate_ready(
256 &ready,
257 &peer.reference()?,
258 &peer.endpoint,
259 &peer.facets,
260 64,
261 )?;
262 let verified = verifier.verify(
263 &context,
264 rpc::SyncServiceReplicateThread::METHOD,
265 &bytes,
266 "write",
267 &peer.replica,
268 )?;
269 Ok((verified, facets, max_items, ready))
270 })
271 .await
272 .map_err(|e| transport::Error::Io(e.to_string()))?;
273 let (verified, facets, max_items, ready) = match checked {
274 Ok(value) => value,
275 Err(error) => {
276 writer
277 .fail(&api::heddle::api::common::CallFailure {
278 code: 7,
279 message: error.to_string(),
280 ..Default::default()
281 })
282 .await?;
283 return Err(error.into());
284 }
285 };
286 writer
287 .send(
288 ReplicateThreadResponse {
289 body: Some(replicate_thread_response::Body::Ready(ready)),
290 }
291 .encode_to_vec(),
292 )
293 .await?;
294 let session = Session::new(self.local_replica(store), remote_key, facets, max_items)?;
295 live_replication::run(
296 session,
297 reader,
298 writer,
299 Side::Acceptor,
300 feed,
301 move |activity| {
302 std::future::ready(
303 if matches!(activity, live_replication::Activity::InputConsumed { .. }) {
304 Ok(())
305 } else {
306 authority.recheck(&verified)
307 },
308 )
309 },
310 )
311 .await
312 }
313}
314
315fn io_error(error: impl std::fmt::Display) -> transport::Error {
316 transport::Error::Io(error.to_string())
317}
318fn store_error(error: repo::thread_replication::Error) -> transport::Error {
319 io_error(error)
320}
321
322#[cfg(test)]
323#[path = "replication_rpc_tests.rs"]
324mod tests;