Skip to main content

po_node/
node.rs

1//! High-level PO node — the "ridiculously easy" API.
2//!
3//! # Examples
4//!
5//! ```rust,no_run
6//! use po_node::Po;
7//!
8//! #[tokio::main]
9//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
10//!     // Server: 2 lines
11//!     let mut server = Po::bind(4433).await?;
12//!     let (peer, data) = server.recv().await?.unwrap();
13//!     println!("Got: {}", String::from_utf8_lossy(&data));
14//!
15//!     // Client: 3 lines
16//!     let mut client = Po::connect("127.0.0.1:4433").await?;
17//!     client.send(b"Hello!").await?;
18//!     Ok(())
19//! }
20//! ```
21
22use po_crypto::identity::Identity;
23use po_session::channel::channels;
24use po_session::state::Session;
25use po_transport::quic::{QuicConfig, QuicListener, QuicTransport};
26use std::net::SocketAddr;
27
28use crate::peer::PeerInfo;
29
30/// A PO node — the main entry point for the protocol.
31///
32/// Wraps all the complexity of QUIC, crypto handshake, and encryption
33/// behind a dead-simple API.
34pub struct Po {
35    session: Session,
36    transport: QuicTransport,
37    identity: Identity,
38    peer_info: Option<PeerInfo>,
39}
40
41impl Po {
42    /// Connect to a remote PO node.
43    ///
44    /// This will:
45    /// 1. Establish a QUIC connection
46    /// 2. Perform the E2EE handshake (Ed25519 + X25519 + ChaCha20)
47    /// 3. Return a ready-to-use encrypted connection
48    ///
49    /// ```ignore
50    /// let mut po = Po::connect("192.168.1.5:4433").await?;
51    /// po.send(b"encrypted hello!").await?;
52    /// ```
53    pub async fn connect(addr: &str) -> Result<Self, PoError> {
54        let socket_addr: SocketAddr = addr
55            .parse()
56            .map_err(|e| PoError::Config(format!("invalid address '{addr}': {e}")))?;
57
58        let identity = Identity::generate();
59        let mut transport = QuicTransport::connect(socket_addr)
60            .await
61            .map_err(|e| PoError::Transport(e.to_string()))?;
62
63        let mut session = Session::new(Identity::from_bytes(&identity.secret_key_bytes()));
64        session
65            .handshake_initiator(&mut transport)
66            .await
67            .map_err(|e| PoError::Handshake(e.to_string()))?;
68
69        let peer_info = session.peer_node_id().map(|id| PeerInfo {
70            node_id: *id,
71            addr: socket_addr,
72            pubkey: [0u8; 32], // TODO: extract from session
73        });
74
75        Ok(Self {
76            session,
77            transport,
78            identity,
79            peer_info,
80        })
81    }
82
83    /// Listen for an incoming connection on the given port.
84    ///
85    /// This will:
86    /// 1. Start a QUIC listener
87    /// 2. Accept the first incoming connection
88    /// 3. Perform the E2EE handshake
89    /// 4. Return a ready-to-use encrypted connection
90    ///
91    /// ```ignore
92    /// let mut po = Po::bind(4433).await?;
93    /// let data = po.recv().await?;
94    /// ```
95    pub async fn bind(port: u16) -> Result<Self, PoError> {
96        let identity = Identity::generate();
97
98        let config = QuicConfig {
99            bind_addr: format!("0.0.0.0:{port}").parse().unwrap(),
100        };
101        let listener = QuicListener::bind(config)
102            .await
103            .map_err(|e| PoError::Transport(e.to_string()))?;
104
105        let mut transport = listener
106            .accept()
107            .await
108            .map_err(|e| PoError::Transport(e.to_string()))?;
109
110        let mut session = Session::new(Identity::from_bytes(&identity.secret_key_bytes()));
111        session
112            .handshake_responder(&mut transport)
113            .await
114            .map_err(|e| PoError::Handshake(e.to_string()))?;
115
116        let peer_info = session.peer_node_id().map(|id| PeerInfo {
117            node_id: *id,
118            addr: format!("0.0.0.0:{port}").parse().unwrap(),
119            pubkey: [0u8; 32],
120        });
121
122        Ok(Self {
123            session,
124            transport,
125            identity,
126            peer_info,
127        })
128    }
129
130    /// Send encrypted data to the connected peer.
131    pub async fn send(&mut self, data: &[u8]) -> Result<(), PoError> {
132        self.session
133            .send(&mut self.transport, channels::DEFAULT, data)
134            .await
135            .map_err(|e| PoError::Session(e.to_string()))
136    }
137
138    /// Receive the next message from the connected peer.
139    ///
140    /// Returns `Some((channel_id, data))` or `None` if the connection closed.
141    pub async fn recv(&mut self) -> Result<Option<(u32, Vec<u8>)>, PoError> {
142        self.session
143            .recv(&mut self.transport)
144            .await
145            .map_err(|e| PoError::Session(e.to_string()))
146    }
147
148    /// Get our node ID.
149    pub fn node_id(&self) -> String {
150        self.session.node_id().to_hex()
151    }
152
153    /// Get our Ed25519 public key bytes (32 bytes).
154    pub fn public_key(&self) -> [u8; 32] {
155        self.identity.public_key_bytes()
156    }
157
158    /// Get the peer's node ID (if connected).
159    pub fn peer_node_id(&self) -> Option<String> {
160        self.session.peer_node_id().map(|id| id.to_hex())
161    }
162
163    /// Get information about the connected peer.
164    pub fn peer_info(&self) -> Option<&PeerInfo> {
165        self.peer_info.as_ref()
166    }
167
168    /// Gracefully close the connection.
169    pub async fn close(&mut self) -> Result<(), PoError> {
170        self.session
171            .close(&mut self.transport)
172            .await
173            .map_err(|e| PoError::Session(e.to_string()))
174    }
175}
176
177/// Errors from the PO node.
178#[derive(Debug)]
179pub enum PoError {
180    Config(String),
181    Transport(String),
182    Handshake(String),
183    Session(String),
184}
185
186impl std::fmt::Display for PoError {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        match self {
189            Self::Config(e) => write!(f, "config: {e}"),
190            Self::Transport(e) => write!(f, "transport: {e}"),
191            Self::Handshake(e) => write!(f, "handshake: {e}"),
192            Self::Session(e) => write!(f, "session: {e}"),
193        }
194    }
195}
196
197impl std::error::Error for PoError {}