use std::time::{Duration, Instant};
use chirograph::{
CarrierRequest, CarrierRequestBody, CarrierResponse, ProjectionSession, ResumeRequest,
Revision, SceneEpoch,
};
use graphshell::admission::open_session;
use graphshell::carrier::projection_alpn;
use notochord::{NetworkId, SessionReply, TrafficClass, initiate_session};
use personae::{IdentityProvider, InMemoryProvider};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::time::sleep;
use transport::p2panda_transport::{MdnsDiscoveryMode, P2pandaTransport};
use transport::{Transport, initiator_binding};
use crate::DIAL_DEADLINE;
use crate::identity::{assert_same_key, grant, hex8, now_ms, profile};
use crate::script::{RESUME_SESSION, intent_body, open_body, projection_request, summarize};
pub(crate) enum PeerSource {
Ticket(String),
Discovered(transport::PeerID),
}
pub(crate) async fn connect(
owner: InMemoryProvider,
me: InMemoryProvider,
seed: [u8; 32],
network: NetworkId,
source: PeerSource,
) -> Result<(), String> {
let carrier = P2pandaTransport::builder_from_seed(seed)
.alpns(vec![projection_alpn()])
.mdns(MdnsDiscoveryMode::Active)
.bind()
.await
.map_err(|e| format!("bind: {e}"))?;
assert_same_key(&carrier, &me)?;
println!("g5_peer connect");
let peer = match source {
PeerSource::Ticket(ticket) => {
let peer = carrier
.add_peer_ticket(&ticket)
.await
.map_err(|e| format!("ticket: {e}"))?;
println!(" peer from ticket: {}", hex8(&peer.to_bytes()));
peer
}
PeerSource::Discovered(peer) => {
carrier.ticket().await.map_err(|e| format!("ticket: {e}"))?;
println!(
" no ticket, no add_peer; waiting for mDNS to resolve {}",
hex8(&peer.to_bytes())
);
peer
}
};
println!(" --- session 1 ---");
let first = run_session(
&carrier,
peer,
&me,
&owner,
network,
vec![
(1, open_body()),
(2, CarrierRequestBody::Snapshot(projection_request())),
(3, CarrierRequestBody::Suspend),
],
)
.await?;
if first.is_empty() {
return Ok(());
}
println!(" --- interruption: reconnecting ---");
println!(" --- session 2 ---");
run_session(
&carrier,
peer,
&me,
&owner,
network,
vec![
(4, open_body()),
(
5,
CarrierRequestBody::Resume(ResumeRequest {
session: ProjectionSession(RESUME_SESSION.into()),
epoch: SceneEpoch(3),
revision: Revision(1),
}),
),
(6, intent_body()),
(7, CarrierRequestBody::Close),
],
)
.await?;
println!(" --- session 3: intent first ---");
run_session(
&carrier,
peer,
&me,
&owner,
network,
vec![(8, intent_body()), (9, CarrierRequestBody::Close)],
)
.await?;
Ok(())
}
async fn run_session(
carrier: &P2pandaTransport,
peer: transport::PeerID,
me: &InMemoryProvider,
owner: &InMemoryProvider,
network: NetworkId,
script: Vec<(u64, CarrierRequestBody)>,
) -> Result<Vec<CarrierResponse>, String> {
let started = Instant::now();
let mut stream = loop {
match carrier.connect(peer, projection_alpn()).await {
Ok(stream) => break stream,
Err(error) => {
if started.elapsed() >= DIAL_DEADLINE {
return Err(format!("connect: {error}"));
}
sleep(Duration::from_millis(250)).await;
}
}
};
let subject = me.master_public_key().to_bytes();
let local = transport::PeerID::from_bytes(&subject).map_err(|e| format!("peer id: {e}"))?;
let binding = initiator_binding(&projection_alpn(), local);
let hello = open_session(
me,
network,
profile(),
TrafficClass::Interactive,
[5; 32],
&binding,
vec![grant(owner, subject, network, now_ms() + 3_600_000)],
)
.map_err(|e| format!("hello: {e}"))?;
let limits = Default::default();
match initiate_session(&mut stream, &hello, &limits)
.await
.map_err(|e| format!("handshake: {e}"))?
{
SessionReply::Reject { reason } => {
println!(" refused at admission: {reason:?}");
return Ok(Vec::new());
}
SessionReply::Accept { .. } => println!(" admitted"),
}
let (reader, mut writer) = tokio::io::split(stream);
let mut lines = BufReader::new(reader).lines();
let mut answers = Vec::new();
for (id, body) in script {
let mut line =
serde_json::to_vec(&CarrierRequest { id, body }).map_err(|e| format!("encode: {e}"))?;
line.push(b'\n');
writer
.write_all(&line)
.await
.map_err(|e| format!("write: {e}"))?;
writer.flush().await.map_err(|e| format!("flush: {e}"))?;
match lines.next_line().await.map_err(|e| format!("read: {e}"))? {
Some(response) => {
let decoded: CarrierResponse =
serde_json::from_str(&response).map_err(|e| format!("decode: {e}"))?;
match &decoded.body {
Ok(body) => println!(" #{id} -> {}", summarize(body)),
Err(failure) => println!(" #{id} -> refused: {}", failure.message),
}
answers.push(decoded);
}
None => {
println!(" #{id} -> the endpoint closed without answering");
break;
}
}
}
Ok(answers)
}